Merge branch 'dev' into lm_sla_supports_ui

This commit is contained in:
Lukas Matena 2018-09-17 10:42:16 +02:00
commit 3957c5bd8e
70 changed files with 1711 additions and 1163 deletions

View File

@ -356,28 +356,4 @@ sub set_menu_item_icon {
} }
} }
sub save_window_pos {
my ($self, $window, $name) = @_;
$self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY);
$self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH);
$self->{app_config}->set("${name}_maximized", $window->IsMaximized);
$self->{app_config}->save;
}
sub restore_window_pos {
my ($self, $window, $name) = @_;
if ($self->{app_config}->has("${name}_pos")) {
my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ];
$window->SetSize($size);
my $display = Wx::Display->new->GetClientArea();
my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ];
if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) {
$window->Move($pos);
}
$window->Maximize(1) if $self->{app_config}->get("${name}_maximized");
}
}
1; 1;

View File

@ -74,7 +74,8 @@ sub new {
$self->{statusbar}->Embed; $self->{statusbar}->Embed;
$self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")); $self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"));
# Make the global status bar and its progress indicator available in C++ # Make the global status bar and its progress indicator available in C++
$appController->set_global_progress_indicator($self->{statusbar}); #FIXME Vojtech: Merging error
# $appController->set_global_progress_indicator($self->{statusbar});
$appController->set_model($self->{plater}->{model}); $appController->set_model($self->{plater}->{model});
$appController->set_print($self->{plater}->{print}); $appController->set_print($self->{plater}->{print});
@ -92,7 +93,7 @@ sub new {
$self->Fit; $self->Fit;
$self->SetMinSize([760, 490]); $self->SetMinSize([760, 490]);
$self->SetSize($self->GetMinSize); $self->SetSize($self->GetMinSize);
wxTheApp->restore_window_pos($self, "main_frame"); Slic3r::GUI::restore_window_size($self, "main_frame");
$self->Show; $self->Show;
$self->Layout; $self->Layout;
} }
@ -105,7 +106,7 @@ sub new {
return; return;
} }
# save window size # save window size
wxTheApp->save_window_pos($self, "main_frame"); Slic3r::GUI::save_window_size($self, "main_frame");
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
# but in rare cases it may not have been called yet. # but in rare cases it may not have been called yet.
wxTheApp->{app_config}->save; wxTheApp->{app_config}->save;

View File

@ -1804,20 +1804,37 @@ sub print_info_box_show {
$grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(1, 1);
$grid_sizer->AddGrowableCol(3, 1); $grid_sizer->AddGrowableCol(3, 1);
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0;
my @info = ( my @info = (
L("Used Filament (m)") L("Used Filament (m)")
=> sprintf("%.2f" , $self->{print}->total_used_filament / 1000), => $is_wipe_tower ?
sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000,
($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000,
L("objects"),
$self->{print}->total_wipe_tower_filament / 1000,
L("wipe tower")) :
sprintf("%.2f" , $self->{print}->total_used_filament / 1000),
L("Used Filament (mm³)") L("Used Filament (mm³)")
=> sprintf("%.2f" , $self->{print}->total_extruded_volume), => sprintf("%.2f" , $self->{print}->total_extruded_volume),
L("Used Filament (g)"), L("Used Filament (g)"),
=> sprintf("%.2f" , $self->{print}->total_weight), => sprintf("%.2f" , $self->{print}->total_weight),
L("Cost"), L("Cost"),
=> sprintf("%.2f" , $self->{print}->total_cost), => $is_wipe_tower ?
sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost,
($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost),
L("objects"),
$self->{print}->total_wipe_tower_cost,
L("wipe tower")) :
sprintf("%.2f" , $self->{print}->total_cost),
L("Estimated printing time (normal mode)") L("Estimated printing time (normal mode)")
=> $self->{print}->estimated_normal_print_time, => $self->{print}->estimated_normal_print_time,
L("Estimated printing time (silent mode)") L("Estimated printing time (silent mode)")
=> $self->{print}->estimated_silent_print_time => $self->{print}->estimated_silent_print_time
); );
# if there is a wipe tower, insert number of toolchanges info into the array:
splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->m_wipe_tower_number_of_toolchanges)) if ($is_wipe_tower);
while ( my $label = shift @info) { while ( my $label = shift @info) {
my $value = shift @info; my $value = shift @info;
next if $value eq "N/A"; next if $value eq "N/A";

View File

@ -238,7 +238,7 @@ sub _update {
my @expolygons = (); my @expolygons = ();
foreach my $volume (@{$self->{model_object}->volumes}) { foreach my $volume (@{$self->{model_object}->volumes}) {
next if !$volume->mesh; next if !$volume->mesh;
next if $volume->modifier; next if !$volume->model_part;
my $expp = $volume->mesh->slice([ $z_cut ])->[0]; my $expp = $volume->mesh->slice([ $z_cut ])->[0];
push @expolygons, @$expp; push @expolygons, @$expp;
} }

View File

@ -16,6 +16,8 @@ use base 'Wx::Panel';
use constant ICON_OBJECT => 0; use constant ICON_OBJECT => 0;
use constant ICON_SOLIDMESH => 1; use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2; use constant ICON_MODIFIERMESH => 2;
use constant ICON_SUPPORT_ENFORCER => 3;
use constant ICON_SUPPORT_BLOCKER => 4;
sub new { sub new {
my ($class, $parent, %params) = @_; my ($class, $parent, %params) = @_;
@ -46,6 +48,8 @@ sub new {
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_ENFORCER
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_BLOCKER
my $rootId = $tree->AddRoot("Object", ICON_OBJECT); my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
$tree->SetPlData($rootId, { type => 'object' }); $tree->SetPlData($rootId, { type => 'object' });
@ -89,7 +93,14 @@ sub new {
$self->{btn_move_down}->SetFont($Slic3r::GUI::small_font); $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
# part settings panel # part settings panel
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; }); $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub {
my ($key, $value) = @_;
wxTheApp->CallAfter(sub {
$self->set_part_type($value) if ($key eq "part_type");
$self->{part_settings_changed} = 1;
$self->_update_canvas;
});
});
my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
@ -225,8 +236,11 @@ sub reload_tree {
my $selectedId = $rootId; my $selectedId = $rootId;
foreach my $volume_id (0..$#{$object->volumes}) { foreach my $volume_id (0..$#{$object->volumes}) {
my $volume = $object->volumes->[$volume_id]; my $volume = $object->volumes->[$volume_id];
my $icon =
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; $volume->modifier ? ICON_MODIFIERMESH :
$volume->support_enforcer ? ICON_SUPPORT_ENFORCER :
$volume->support_blocker ? ICON_SUPPORT_BLOCKER :
ICON_SOLIDMESH;
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon); my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
if ($volume_id == $selected_volume_idx) { if ($volume_id == $selected_volume_idx) {
$selectedId = $itemId; $selectedId = $itemId;
@ -288,6 +302,8 @@ sub selection_changed {
if (my $itemData = $self->get_selection) { if (my $itemData = $self->get_selection) {
my ($config, @opt_keys); my ($config, @opt_keys);
my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT;
my $support = 0;
if ($itemData->{type} eq 'volume') { if ($itemData->{type} eq 'volume') {
# select volume in 3D preview # select volume in 3D preview
if ($self->{canvas}) { if ($self->{canvas}) {
@ -301,16 +317,24 @@ sub selection_changed {
# attach volume config to settings panel # attach volume config to settings panel
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
if ($volume->modifier) { if (! $volume->model_part) {
$self->{optgroup_movers}->enable; $self->{optgroup_movers}->enable;
if ($volume->support_enforcer || $volume->support_blocker) {
$support = 1;
$type = $volume->support_enforcer ?
Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER :
Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER;
} else {
$type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER;
}
} else { } else {
$type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART;
$self->{optgroup_movers}->disable; $self->{optgroup_movers}->disable;
} }
$config = $volume->config; $config = $volume->config;
$self->{staticbox}->SetLabel('Part Settings'); $self->{staticbox}->SetLabel('Part Settings');
# get default values # get default values
@opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys};
} elsif ($itemData->{type} eq 'object') { } elsif ($itemData->{type} eq 'object') {
# select nothing in 3D preview # select nothing in 3D preview
@ -323,33 +347,54 @@ sub selection_changed {
# get default values # get default values
my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
# decide which settings will be shown by default # decide which settings will be shown by default
if ($itemData->{type} eq 'object') { if ($itemData->{type} eq 'object') {
$config->set_ifndef('wipe_into_objects', 0); $config->set_ifndef('wipe_into_objects', 0);
$config->set_ifndef('wipe_into_infill', 0); $config->set_ifndef('wipe_into_infill', 0);
} }
# append default extruder # append default extruder
push @opt_keys, 'extruder'; if (! $support) {
$default_config->set('extruder', 0); push @opt_keys, 'extruder';
$config->set_ifndef('extruder', 0); $default_config->set('extruder', 0);
$config->set_ifndef('extruder', 0);
}
$self->{settings_panel}->set_type($type);
$self->{settings_panel}->set_default_config($default_config); $self->{settings_panel}->set_default_config($default_config);
$self->{settings_panel}->set_config($config); $self->{settings_panel}->set_config($config);
$self->{settings_panel}->set_opt_keys(\@opt_keys); $self->{settings_panel}->set_opt_keys(\@opt_keys);
# disable minus icon to remove the settings # disable minus icon to remove the settings
if ($itemData->{type} eq 'object') { my $fixed_options =
$self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]); ($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] :
} else { $support ? [] : [qw(extruder)];
$self->{settings_panel}->set_fixed_options([qw(extruder)]); $self->{settings_panel}->set_fixed_options($fixed_options);
}
$self->{settings_panel}->enable; $self->{settings_panel}->enable;
} }
Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas}; Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
} }
sub set_part_type
{
my ($self, $part_type) = @_;
if (my $itemData = $self->get_selection) {
if ($itemData->{type} eq 'volume') {
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER ||
$part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) {
$volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER);
} elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) {
$volume->set_support_enforcer;
} elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) {
$volume->set_support_blocker;
}
# We want the icon of the selected item to be changed as well.
$self->reload_tree($itemData->{volume_id});
}
}
}
sub on_btn_load { sub on_btn_load {
my ($self, $is_modifier) = @_; my ($self, $is_modifier) = @_;

View File

@ -33,7 +33,7 @@ sub new {
$self->{layers}->Closing; $self->{layers}->Closing;
# save window size # save window size
wxTheApp->save_window_pos($self, "object_settings"); Slic3r::GUI::save_window_size($self, "object_settings");
$self->EndModal(wxID_OK); $self->EndModal(wxID_OK);
$self->{parts}->Destroy; $self->{parts}->Destroy;
@ -49,7 +49,7 @@ sub new {
$self->Layout; $self->Layout;
wxTheApp->restore_window_pos($self, "object_settings"); Slic3r::GUI::restore_window_size($self, "object_settings");
return $self; return $self;
} }

View File

@ -7,15 +7,20 @@ use warnings;
use utf8; use utf8;
use List::Util qw(first); use List::Util qw(first);
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp);
wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU);
use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU);
use base 'Wx::ScrolledWindow'; use base 'Wx::ScrolledWindow';
use constant ICON_MATERIAL => 0; use constant ICON_MATERIAL => 0;
use constant ICON_SOLIDMESH => 1; use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2; use constant ICON_MODIFIERMESH => 2;
use constant TYPE_OBJECT => -1;
use constant TYPE_PART => 0;
use constant TYPE_MODIFIER => 1;
use constant TYPE_SUPPORT_ENFORCER => 2;
use constant TYPE_SUPPORT_BLOCKER => 3;
my %icons = ( my %icons = (
'Advanced' => 'wand.png', 'Advanced' => 'wand.png',
'Extruders' => 'funnel.png', 'Extruders' => 'funnel.png',
@ -36,6 +41,7 @@ sub new {
$self->{config} = Slic3r::Config->new; $self->{config} = Slic3r::Config->new;
# On change callback. # On change callback.
$self->{on_change} = $params{on_change}; $self->{on_change} = $params{on_change};
$self->{type} = TYPE_OBJECT;
$self->{fixed_options} = {}; $self->{fixed_options} = {};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
@ -110,6 +116,16 @@ sub set_opt_keys {
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
} }
sub set_type {
my ($self, $type) = @_;
$self->{type} = $type;
if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) {
$self->{btn_add}->Hide;
} else {
$self->{btn_add}->Show;
}
}
sub set_fixed_options { sub set_fixed_options {
my ($self, $opt_keys) = @_; my ($self, $opt_keys) = @_;
$self->{fixed_options} = { map {$_ => 1} @$opt_keys }; $self->{fixed_options} = { map {$_ => 1} @$opt_keys };
@ -122,11 +138,27 @@ sub update_optgroup {
$self->{options_sizer}->Clear(1); $self->{options_sizer}->Clear(1);
return if !defined $self->{config}; return if !defined $self->{config};
if ($self->{type} != TYPE_OBJECT) {
my $label = Wx::StaticText->new($self, -1, "Type:"),
my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ];
my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($label, 1, wxEXPAND | wxALL, 5);
$sizer->Add($field, 0, wxALL, 5);
EVT_COMBOBOX($self, $field, sub {
my $idx = $field->GetSelection; # get index of selected value
$self->{on_change}->("part_type", $idx) if $self->{on_change};
});
$self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0);
}
my %categories = (); my %categories = ();
foreach my $opt_key (@{$self->{config}->get_keys}) { if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) {
my $category = $Slic3r::Config::Options->{$opt_key}{category}; foreach my $opt_key (@{$self->{config}->get_keys}) {
$categories{$category} ||= []; my $category = $Slic3r::Config::Options->{$opt_key}{category};
push @{$categories{$category}}, $opt_key; $categories{$category} ||= [];
push @{$categories{$category}}, $opt_key;
}
} }
foreach my $category (sort keys %categories) { foreach my $category (sort keys %categories) {
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

View File

@ -288,7 +288,6 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/AppController.hpp ${LIBDIR}/slic3r/AppController.hpp
${LIBDIR}/slic3r/AppController.cpp ${LIBDIR}/slic3r/AppController.cpp
${LIBDIR}/slic3r/AppControllerWx.cpp ${LIBDIR}/slic3r/AppControllerWx.cpp
${LIBDIR}/slic3r/Strings.hpp
) )
add_library(admesh STATIC add_library(admesh STATIC
@ -565,7 +564,7 @@ if (WIN32)
endif () endif ()
# SLIC3R_MSVC_PDB # SLIC3R_MSVC_PDB
if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release") if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release")
set_target_properties(XS PROPERTIES set_target_properties(XS PROPERTIES
COMPILE_FLAGS "/Zi" COMPILE_FLAGS "/Zi"
LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF" LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"

View File

@ -1225,8 +1225,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo
return true; return true;
} }
Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const
{ {
assert(std::abs(2 * offset) < m_resolution);
typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType; typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType;
// 0) Prepare a binary grid. // 0) Prepare a binary grid.
size_t cell_rows = m_rows + 2; size_t cell_rows = m_rows + 2;
@ -1237,7 +1239,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const
cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1);
// Fill in empty cells, which have a left / right neighbor filled. // Fill in empty cells, which have a left / right neighbor filled.
// Fill in empty cells, which have the top / bottom neighbor filled. // Fill in empty cells, which have the top / bottom neighbor filled.
{ if (fill_holes) {
std::vector<char> cell_inside2(cell_inside); std::vector<char> cell_inside2(cell_inside);
for (int r = 1; r + 1 < int(cell_rows); ++ r) { for (int r = 1; r + 1 < int(cell_rows); ++ r) {
for (int c = 1; c + 1 < int(cell_cols); ++ c) { for (int c = 1; c + 1 < int(cell_cols); ++ c) {

View File

@ -58,7 +58,7 @@ public:
const size_t cols() const { return m_cols; } const size_t cols() const { return m_cols; }
// For supports: Contours enclosing the rasterized edges. // For supports: Contours enclosing the rasterized edges.
Polygons contours_simplified(coord_t offset) const; Polygons contours_simplified(coord_t offset, bool fill_holes) const;
protected: protected:
struct Cell { struct Cell {

View File

@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point &center)
} }
template <class T> template <class T>
bool bool ExPolygonCollection::contains(const T &item) const
ExPolygonCollection::contains(const T &item) const
{ {
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { for (const ExPolygon &poly : this->expolygons)
if (it->contains(item)) return true; if (poly.contains(item))
} return true;
return false; return false;
} }
template bool ExPolygonCollection::contains<Point>(const Point &item) const; template bool ExPolygonCollection::contains<Point>(const Point &item) const;

View File

@ -91,6 +91,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
virtual double min_mm3_per_mm() const = 0; virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0; virtual Polyline as_polyline() const = 0;
virtual void collect_polylines(Polylines &dst) const = 0;
virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
virtual double length() const = 0; virtual double length() const = 0;
virtual double total_volume() const = 0; virtual double total_volume() const = 0;
}; };
@ -123,8 +125,11 @@ public:
ExtrusionPath* clone() const { return new ExtrusionPath (*this); } ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
void reverse() { this->polyline.reverse(); } void reverse() { this->polyline.reverse(); }
Point first_point() const { return this->polyline.points.front(); } Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const { return this->polyline.points.back(); } Point last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
// Currently not used. // Currently not used.
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
@ -133,8 +138,8 @@ public:
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance); void clip_end(double distance);
void simplify(double tolerance); void simplify(double tolerance);
virtual double length() const; double length() const override;
virtual ExtrusionRole role() const { return m_role; } ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@ -149,7 +154,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const { return this->mm3_per_mm; } double min_mm3_per_mm() const { return this->mm3_per_mm; }
Polyline as_polyline() const { return this->polyline; } Polyline as_polyline() const { return this->polyline; }
virtual double total_volume() const { return mm3_per_mm * unscale<double>(length()); } void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
private: private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
@ -178,10 +184,10 @@ public:
bool can_reverse() const { return true; } bool can_reverse() const { return true; }
ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); } ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
void reverse(); void reverse();
Point first_point() const { return this->paths.front().polyline.points.front(); } Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const { return this->paths.back().polyline.points.back(); } Point last_point() const override { return this->paths.back().polyline.points.back(); }
virtual double length() const; double length() const override;
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@ -196,7 +202,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const; double min_mm3_per_mm() const;
Polyline as_polyline() const; Polyline as_polyline() const;
virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
}; };
// Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
@ -218,18 +225,18 @@ public:
bool make_clockwise(); bool make_clockwise();
bool make_counter_clockwise(); bool make_counter_clockwise();
void reverse(); void reverse();
Point first_point() const { return this->paths.front().polyline.points.front(); } Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
Polygon polygon() const; Polygon polygon() const;
virtual double length() const; double length() const override;
bool split_at_vertex(const Point &point); bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang); void split_at(const Point &point, bool prefer_non_overhang);
void clip_end(double distance, ExtrusionPaths* paths) const; void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow. // Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
bool has_overhang_point(const Point &point) const; bool has_overhang_point(const Point &point) const;
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
ExtrusionLoopRole loop_role() const { return m_loop_role; } ExtrusionLoopRole loop_role() const { return m_loop_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
@ -244,7 +251,8 @@ public:
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const; double min_mm3_per_mm() const;
Polyline as_polyline() const { return this->polygon().split_at_first_point(); } Polyline as_polyline() const { return this->polygon().split_at_first_point(); }
virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
private: private:
ExtrusionLoopRole m_loop_role; ExtrusionLoopRole m_loop_role;

View File

@ -24,7 +24,7 @@ public:
explicit operator ExtrusionPaths() const; explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; }; bool is_collection() const { return true; };
virtual ExtrusionRole role() const { ExtrusionRole role() const override {
ExtrusionRole out = erNone; ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) { for (const ExtrusionEntity *ee : entities) {
ExtrusionRole er = ee->role(); ExtrusionRole er = ee->role();
@ -71,11 +71,11 @@ public:
Point last_point() const { return this->entities.back()->last_point(); } Point last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
@ -84,14 +84,20 @@ public:
void flatten(ExtrusionEntityCollection* retval) const; void flatten(ExtrusionEntityCollection* retval) const;
ExtrusionEntityCollection flatten() const; ExtrusionEntityCollection flatten() const;
double min_mm3_per_mm() const; double min_mm3_per_mm() const;
virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
// Following methods shall never be called on an ExtrusionEntityCollection. // Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const { Polyline as_polyline() const {
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline(); return Polyline();
}; };
virtual double length() const {
void collect_polylines(Polylines &dst) const override {
for (ExtrusionEntity* extrusion_entity : this->entities)
extrusion_entity->collect_polylines(dst);
}
double length() const override {
CONFESS("Calling length() on a ExtrusionEntityCollection"); CONFESS("Calling length() on a ExtrusionEntityCollection");
return 0.; return 0.;
} }

View File

@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single(
Polylines paths; Polylines paths;
{ {
Polylines p; Polylines p;
for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) for (Polygon &poly : polygons)
p.push_back((Polyline)(*it)); p.emplace_back(poly.points);
paths = intersection_pl(p, to_polygons(expolygon)); paths = intersection_pl(p, to_polygons(expolygon));
} }

View File

@ -115,7 +115,8 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
// if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. // if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
false); // bridge_flow_ratio
0.f);
} }
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
@ -127,7 +128,8 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
(width.value > 0) ? width : object->config.extrusion_width, (width.value > 0) ? width : object->config.extrusion_width,
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)),
false); // bridge_flow_ratio
0.f);
} }
Flow support_material_interface_flow(const PrintObject *object, float layer_height) Flow support_material_interface_flow(const PrintObject *object, float layer_height)
@ -139,7 +141,8 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
// if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. // if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)), float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
false); // bridge_flow_ratio
0.f);
} }
} }

View File

@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume";
const char* NAME_KEY = "name"; const char* NAME_KEY = "name";
const char* MODIFIER_KEY = "modifier"; const char* MODIFIER_KEY = "modifier";
const char* VOLUME_TYPE_KEY = "volume_type";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] = const char* VALID_OBJECT_TYPES[] =
@ -1442,7 +1443,9 @@ namespace Slic3r {
if (metadata.key == NAME_KEY) if (metadata.key == NAME_KEY)
volume->name = metadata.value; volume->name = metadata.value;
else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
volume->modifier = true; volume->set_type(ModelVolume::PARAMETER_MODIFIER);
else if (metadata.key == VOLUME_TYPE_KEY)
volume->set_type(ModelVolume::type_from_string(metadata.value));
else else
volume->config.set_deserialize(metadata.key, metadata.value); volume->config.set_deserialize(metadata.key, metadata.value);
} }
@ -1957,9 +1960,12 @@ namespace Slic3r {
if (!volume->name.empty()) if (!volume->name.empty())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
// stores volume's modifier field // stores volume's modifier field (legacy, to support old slicers)
if (volume->modifier) if (volume->is_modifier())
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
// stores volume's type (overrides the modifier field above)
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
// stores volume's config data // stores volume's config data
for (const std::string& key : volume->config.keys()) for (const std::string& key : volume->config.keys())

View File

@ -495,9 +495,14 @@ void AMFParserContext::endElement(const char * /* name */)
p = end + 1; p = end + 1;
} }
m_object->layer_height_profile_valid = true; m_object->layer_height_profile_valid = true;
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
// Is this volume a modifier volume? if (strcmp(opt_key, "modifier") == 0) {
m_volume->modifier = atoi(m_value[1].c_str()) == 1; // Is this volume a modifier volume?
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
} }
} else if (m_path.size() == 3) { } else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_path[1] == NODE_TYPE_MATERIAL) {
@ -822,8 +827,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n"; stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
if (!volume->name.empty()) if (!volume->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n"; stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
if (volume->modifier) if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n"; stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
stream << " <triangle>\n"; stream << " <triangle>\n";
for (int j = 0; j < 3; ++j) for (int j = 0; j < 3; ++j)

View File

@ -276,7 +276,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
} }
std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string WipeTowerIntegration::prime(GCode &gcodegen)
{ {
assert(m_layer_idx == 0); assert(m_layer_idx == 0);
@ -967,17 +966,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
// Get filament stats. // Get filament stats.
print.filament_stats.clear(); print.filament_stats.clear();
print.total_used_filament = 0.; print.total_used_filament = 0.;
print.total_extruded_volume = 0.; print.total_extruded_volume = 0.;
print.total_weight = 0.; print.total_weight = 0.;
print.total_cost = 0.; print.total_cost = 0.;
print.total_wipe_tower_cost = 0.;
print.total_wipe_tower_filament = 0.;
print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
for (const Extruder &extruder : m_writer.extruders()) { for (const Extruder &extruder : m_writer.extruders()) {
double used_filament = extruder.used_filament(); double used_filament = extruder.used_filament() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] : 0.f);
double extruded_volume = extruder.extruded_volume(); double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
double filament_cost = filament_weight * extruder.filament_cost() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament)); print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
_write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001); _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
if (filament_weight > 0.) { if (filament_weight > 0.) {
@ -988,8 +990,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
_write_format(file, "; filament cost = %.1lf\n", filament_cost); _write_format(file, "; filament cost = %.1lf\n", filament_cost);
} }
} }
print.total_used_filament = print.total_used_filament + used_filament; print.total_used_filament += used_filament;
print.total_extruded_volume = print.total_extruded_volume + extruded_volume; print.total_extruded_volume += extruded_volume;
print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
print.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
} }
_write_format(file, "; total filament cost = %.1lf\n", print.total_cost); _write_format(file, "; total filament cost = %.1lf\n", print.total_cost);
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());

View File

@ -98,6 +98,7 @@ public:
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen); std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;
private: private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&); WipeTowerIntegration& operator=(const WipeTowerIntegration&);

View File

@ -154,6 +154,12 @@ public:
// the wipe tower has been completely covered by the tool change extrusions, // the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method. // or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const = 0; virtual bool layer_finished() const = 0;
// Returns used filament length per extruder:
virtual std::vector<float> get_used_filament() const = 0;
// Returns total number of toolchanges:
virtual int get_number_of_toolchanges() const = 0;
}; };
}; // namespace Slic3r }; // namespace Slic3r

View File

@ -111,9 +111,10 @@ public:
const WipeTower::xy start_pos_rotated() const { return m_start_pos; } const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
float elapsed_time() const { return m_elapsed_time; } float elapsed_time() const { return m_elapsed_time; }
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
// Extrude with an explicitely provided amount of extrusion. // Extrude with an explicitely provided amount of extrusion.
Writer& extrude_explicit(float x, float y, float e, float f = 0.f) Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false)
{ {
if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
// Neither extrusion nor a travel move. // Neither extrusion nor a travel move.
@ -122,6 +123,8 @@ public:
float dx = x - m_current_pos.x; float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y; float dy = y - m_current_pos.y;
double len = sqrt(dx*dx+dy*dy); double len = sqrt(dx*dx+dy*dy);
if (record_length)
m_used_filament_length += e;
// Now do the "internal rotation" with respect to the wipe tower center // Now do the "internal rotation" with respect to the wipe tower center
@ -162,8 +165,8 @@ public:
return *this; return *this;
} }
Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f) Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false)
{ return extrude_explicit(dest.x, dest.y, e, f); } { return extrude_explicit(dest.x, dest.y, e, f, record_length); }
// Travel to a new XY position. f=0 means use the current value. // Travel to a new XY position. f=0 means use the current value.
Writer& travel(float x, float y, float f = 0.f) Writer& travel(float x, float y, float f = 0.f)
@ -177,7 +180,7 @@ public:
{ {
float dx = x - m_current_pos.x; float dx = x - m_current_pos.x;
float dy = y - m_current_pos.y; float dy = y - m_current_pos.y;
return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f); return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
} }
Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
@ -259,8 +262,8 @@ public:
// extrude quickly amount e to x2 with feed f. // extrude quickly amount e to x2 with feed f.
Writer& ram(float x1, float x2, float dy, float e0, float e, float f) Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
{ {
extrude_explicit(x1, m_current_pos.y + dy, e0, f); extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
extrude_explicit(x2, m_current_pos.y, e); extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
return *this; return *this;
} }
@ -404,6 +407,7 @@ private:
float m_last_fan_speed = 0.f; float m_last_fan_speed = 0.f;
int current_temp = -1; int current_temp = -1;
const float m_default_analyzer_line_width; const float m_default_analyzer_line_width;
float m_used_filament_length = 0.f;
std::string set_format_X(float x) std::string set_format_X(float x)
{ {
@ -525,6 +529,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
++ m_num_tool_changes; ++ m_num_tool_changes;
} }
m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
// in the output gcode - we should not remember emitting them (we will output them twice in the worst case)
// Reset the extruder current to a normal value. // Reset the extruder current to a normal value.
writer.set_extruder_trimpot(550) writer.set_extruder_trimpot(550)
.feedrate(6000) .feedrate(6000)
@ -537,6 +544,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
// so that tool_change() will know to extrude the wipe tower brim: // so that tool_change() will know to extrude the wipe tower brim:
m_print_brim = true; m_print_brim = true;
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result; ToolChangeResult result;
result.priming = true; result.priming = true;
result.print_z = this->m_z_pos; result.print_z = this->m_z_pos;
@ -606,10 +616,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
toolchange_Load(writer, cleaning_box); toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
++ m_num_tool_changes;
} else } else
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
++ m_num_tool_changes;
m_depth_traversed += wipe_area; m_depth_traversed += wipe_area;
if (last_change_in_layer) {// draw perimeter line if (last_change_in_layer) {// draw perimeter line
@ -632,6 +642,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
";------------------\n" ";------------------\n"
"\n\n"); "\n\n");
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result; ToolChangeResult result;
result.priming = false; result.priming = false;
result.print_z = this->m_z_pos; result.print_z = this->m_z_pos;
@ -683,6 +696,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
m_print_brim = false; // Mark the brim as extruded m_print_brim = false; // Mark the brim as extruded
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result; ToolChangeResult result;
result.priming = false; result.priming = false;
result.print_z = this->m_z_pos; result.print_z = this->m_z_pos;
@ -804,8 +820,9 @@ void WipeTowerPrusaMM::toolchange_Unload(
.load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
.travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
.resume_preview(); .resume_preview();
if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait.
if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
// However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
writer.set_extruder_temp(new_temperature, false); writer.set_extruder_temp(new_temperature, false);
m_old_temperature = new_temperature; m_old_temperature = new_temperature;
} }
@ -849,6 +866,9 @@ void WipeTowerPrusaMM::toolchange_Change(
const unsigned int new_tool, const unsigned int new_tool,
material_type new_material) material_type new_material)
{ {
// Ask the writer about how much of the old filament we consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
// Speed override for the material. Go slow for flex and soluble materials. // Speed override for the material. Go slow for flex and soluble materials.
int speed_override; int speed_override;
switch (new_material) { switch (new_material) {
@ -911,7 +931,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
const float& xl = cleaning_box.ld.x; const float& xl = cleaning_box.ld.x;
const float& xr = cleaning_box.rd.x; const float& xr = cleaning_box.rd.x;
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
// the ordered volume, even if it means violating the box. This can later be removed and simply // the ordered volume, even if it means violating the box. This can later be removed and simply
// wipe until the end of the assigned area. // wipe until the end of the assigned area.
@ -926,7 +945,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
m_left_to_right = !m_left_to_right; m_left_to_right = !m_left_to_right;
} }
// now the wiping itself: // now the wiping itself:
for (int i = 0; true; ++i) { for (int i = 0; true; ++i) {
if (i!=0) { if (i!=0) {
@ -1050,6 +1068,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
// Ask our writer about how much material was consumed:
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
ToolChangeResult result; ToolChangeResult result;
result.priming = false; result.priming = false;
result.print_z = this->m_z_pos; result.print_z = this->m_z_pos;
@ -1167,6 +1188,8 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
m_layer_info = m_plan.begin(); m_layer_info = m_plan.begin();
m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange
for (auto& used : m_used_filament_length) // reset used filament stats
used = 0.f;
std::vector<WipeTower::ToolChangeResult> layer_result; std::vector<WipeTower::ToolChangeResult> layer_result;
for (auto layer : m_plan) for (auto layer : m_plan)
@ -1208,9 +1231,6 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
} }
} }
void WipeTowerPrusaMM::make_wipe_tower_square() void WipeTowerPrusaMM::make_wipe_tower_square()
{ {
const float width = m_wipe_tower_width - 3 * m_perimeter_width; const float width = m_wipe_tower_width - 3 * m_perimeter_width;
@ -1234,7 +1254,6 @@ void WipeTowerPrusaMM::make_wipe_tower_square()
plan_tower(); // propagates depth downwards again (width has changed) plan_tower(); // propagates depth downwards again (width has changed)
for (auto& lay : m_plan) // depths set, now the spacing for (auto& lay : m_plan) // depths set, now the spacing
lay.extra_spacing = lay.depth / lay.toolchanges_depth(); lay.extra_spacing = lay.depth / lay.toolchanges_depth();
} }

View File

@ -46,7 +46,7 @@ public:
WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) : const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) :
m_wipe_tower_pos(x, y), m_wipe_tower_pos(x, y),
m_wipe_tower_width(width), m_wipe_tower_width(width),
m_wipe_tower_rotation_angle(rotation_angle), m_wipe_tower_rotation_angle(rotation_angle),
m_y_shift(0.f), m_y_shift(0.f),
@ -94,6 +94,8 @@ public:
m_filpar[idx].ramming_step_multiplicator /= 100; m_filpar[idx].ramming_step_multiplicator /= 100;
while (stream >> speed) while (stream >> speed)
m_filpar[idx].ramming_speed.push_back(speed); m_filpar[idx].ramming_speed.push_back(speed);
m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
} }
@ -172,6 +174,9 @@ public:
return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
} }
virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; }
virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; }
private: private:
WipeTowerPrusaMM(); WipeTowerPrusaMM();
@ -331,6 +336,9 @@ private:
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end(); std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
// Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length;
// Returns gcode for wipe tower brim // Returns gcode for wipe tower brim
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower

View File

@ -21,45 +21,37 @@ class LayerRegion
friend class Layer; friend class Layer;
public: public:
Layer* layer() { return this->_layer; } Layer* layer() { return this->_layer; }
const Layer* layer() const { return this->_layer; } const Layer* layer() const { return this->_layer; }
PrintRegion* region() { return this->_region; } PrintRegion* region() { return this->_region; }
const PrintRegion* region() const { return this->_region; } const PrintRegion* region() const { return this->_region; }
// collection of surfaces generated by slicing the original geometry // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal.
// divided by type top/bottom/internal SurfaceCollection slices;
SurfaceCollection slices;
// collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection thin_fills;
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills. // and for re-starting of infills.
ExPolygons fill_expolygons; ExPolygons fill_expolygons;
// collection of surfaces for infill generation // collection of surfaces for infill generation
SurfaceCollection fill_surfaces; SurfaceCollection fill_surfaces;
// Collection of extrusion paths/loops filling gaps.
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection thin_fills;
// Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces). // Collection of expolygons representing the bridged areas (thus not needing support material).
// While not necessary, the memory consumption is meager and it speeds up calculation. //FIXME Not used as of now.
// The perimeter_surfaces keep the IDs of the slices (top/bottom/) Polygons bridged;
SurfaceCollection perimeter_surfaces;
// collection of expolygons representing the bridged areas (thus not
// needing support material)
Polygons bridged;
// collection of polylines representing the unsupported bridge edges // collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges; PolylineCollection unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters // Ordered collection of extrusion paths/loops to build all perimeters.
// (this collection contains only ExtrusionEntityCollection objects) // This collection contains only ExtrusionEntityCollection objects.
ExtrusionEntityCollection perimeters; ExtrusionEntityCollection perimeters;
// Ordered collection of extrusion paths to fill surfaces.
// ordered collection of extrusion paths to fill surfaces // This collection contains only ExtrusionEntityCollection objects.
// (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills;
ExtrusionEntityCollection fills;
Flow flow(FlowRole role, bool bridge = false, double width = -1) const; Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
void slices_to_fill_surfaces_clipped(); void slices_to_fill_surfaces_clipped();

View File

@ -15,8 +15,7 @@
namespace Slic3r { namespace Slic3r {
Flow Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
LayerRegion::flow(FlowRole role, bool bridge, double width) const
{ {
return this->_region->flow( return this->_region->flow(
role, role,
@ -51,8 +50,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
} }
} }
void void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
{ {
this->perimeters.clear(); this->perimeters.clear();
this->thin_fills.clear(); this->thin_fills.clear();
@ -340,8 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} }
void void LayerRegion::prepare_fill_surfaces()
LayerRegion::prepare_fill_surfaces()
{ {
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
@ -382,8 +379,7 @@ LayerRegion::prepare_fill_surfaces()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} }
double double LayerRegion::infill_area_threshold() const
LayerRegion::infill_area_threshold() const
{ {
double ss = this->flow(frSolidInfill).scaled_spacing(); double ss = this->flow(frSolidInfill).scaled_spacing();
return ss*ss; return ss*ss;

View File

@ -627,7 +627,8 @@ const BoundingBoxf3& ModelObject::bounding_box() const
if (! m_bounding_box_valid) { if (! m_bounding_box_valid) {
BoundingBoxf3 raw_bbox; BoundingBoxf3 raw_bbox;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (! v->modifier) if (v->is_model_part())
// mesh.bounding_box() returns a cached value.
raw_bbox.merge(v->mesh.bounding_box()); raw_bbox.merge(v->mesh.bounding_box());
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (const ModelInstance *i : this->instances) for (const ModelInstance *i : this->instances)
@ -658,7 +659,7 @@ TriangleMesh ModelObject::raw_mesh() const
{ {
TriangleMesh mesh; TriangleMesh mesh;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (! v->modifier) if (v->is_model_part())
mesh.merge(v->mesh); mesh.merge(v->mesh);
return mesh; return mesh;
} }
@ -669,7 +670,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
{ {
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (! v->modifier) { if (v->is_model_part()) {
if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances");
bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
} }
@ -681,7 +682,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
{ {
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes) for (ModelVolume *v : this->volumes)
if (! v->modifier) if (v->is_model_part())
bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate));
return bb; return bb;
} }
@ -692,7 +693,7 @@ void ModelObject::center_around_origin()
// center this object around the origin // center this object around the origin
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (ModelVolume *v : this->volumes) for (ModelVolume *v : this->volumes)
if (! v->modifier) if (v->is_model_part())
bb.merge(v->mesh.bounding_box()); bb.merge(v->mesh.bounding_box());
// Shift is the vector from the center of the bottom face of the bounding box to the origin // Shift is the vector from the center of the bottom face of the bounding box to the origin
@ -798,7 +799,7 @@ size_t ModelObject::facets_count() const
{ {
size_t num = 0; size_t num = 0;
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (! v->modifier) if (v->is_model_part())
num += v->mesh.stl.stats.number_of_facets; num += v->mesh.stl.stats.number_of_facets;
return num; return num;
} }
@ -806,7 +807,7 @@ size_t ModelObject::facets_count() const
bool ModelObject::needed_repair() const bool ModelObject::needed_repair() const
{ {
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
if (! v->modifier && v->mesh.needed_repair()) if (v->is_model_part() && v->mesh.needed_repair())
return true; return true;
return false; return false;
} }
@ -822,7 +823,7 @@ void ModelObject::cut(coordf_t z, Model* model) const
lower->input_file = ""; lower->input_file = "";
for (ModelVolume *volume : this->volumes) { for (ModelVolume *volume : this->volumes) {
if (volume->modifier) { if (! volume->is_model_part()) {
// don't cut modifiers // don't cut modifiers
upper->add_volume(*volume); upper->add_volume(*volume);
lower->add_volume(*volume); lower->add_volume(*volume);
@ -874,7 +875,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
ModelVolume* new_volume = new_object->add_volume(*mesh); ModelVolume* new_volume = new_object->add_volume(*mesh);
new_volume->name = volume->name; new_volume->name = volume->name;
new_volume->config = volume->config; new_volume->config = volume->config;
new_volume->modifier = volume->modifier; new_volume->set_type(volume->type());
new_volume->material_id(volume->material_id()); new_volume->material_id(volume->material_id());
new_objects->push_back(new_object); new_objects->push_back(new_object);
@ -888,7 +889,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_
{ {
for (const ModelVolume* vol : this->volumes) for (const ModelVolume* vol : this->volumes)
{ {
if (!vol->modifier) if (vol->is_model_part())
{ {
for (ModelInstance* inst : this->instances) for (ModelInstance* inst : this->instances)
{ {
@ -985,6 +986,37 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
return m_convex_hull; return m_convex_hull;
} }
ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
{
// Legacy support
if (s == "0")
return MODEL_PART;
if (s == "1")
return PARAMETER_MODIFIER;
// New type (supporting the support enforcers & blockers)
if (s == "ModelPart")
return MODEL_PART;
if (s == "ParameterModifier")
return PARAMETER_MODIFIER;
if (s == "SupportEnforcer")
return SUPPORT_ENFORCER;
if (s == "SupportBlocker")
return SUPPORT_BLOCKER;
}
std::string ModelVolume::type_to_string(const Type t)
{
switch (t) {
case MODEL_PART: return "ModelPart";
case PARAMETER_MODIFIER: return "ParameterModifier";
case SUPPORT_ENFORCER: return "SupportEnforcer";
case SUPPORT_BLOCKER: return "SupportBlocker";
default:
assert(false);
return "ModelPart";
}
}
// Split this volume, append the result to the object owning this volume. // Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one. // Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object. // This is useful to assign different materials to different volumes of an object.

View File

@ -167,15 +167,27 @@ public:
// Configuration parameters specific to an object model geometry or a modifier volume, // Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings. // overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config; DynamicPrintConfig config;
// Is it an object to be printed, or a modifier volume?
bool modifier; enum Type {
MODEL_TYPE_INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
// A parent object owning this modifier volume. // A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }; ModelObject* get_object() const { return this->object; };
Type type() const { return m_type; }
void set_type(const Type t) { m_type = t; }
bool is_model_part() const { return m_type == MODEL_PART; }
bool is_modifier() const { return m_type == PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; }
t_model_material_id material_id() const { return this->_material_id; } t_model_material_id material_id() const { return this->_material_id; }
void material_id(t_model_material_id material_id); void material_id(t_model_material_id material_id);
ModelMaterial* material() const; ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material); void set_material(t_model_material_id material_id, const ModelMaterial &material);
// Split this volume, append the result to the object owning this volume. // Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one. // Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object. // This is useful to assign different materials to different volumes of an object.
@ -186,24 +198,30 @@ public:
void calculate_convex_hull(); void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const; const TriangleMesh& get_convex_hull() const;
// Helpers for loading / storing into AMF / 3MF files.
static Type type_from_string(const std::string &s);
static std::string type_to_string(const Type t);
private: private:
// Parent object owning this ModelVolume. // Parent object owning this ModelVolume.
ModelObject* object; ModelObject* object;
t_model_material_id _material_id; // Is it an object to be printed, or a modifier volume?
Type m_type;
t_model_material_id _material_id;
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object)
{ {
if (mesh.stl.stats.number_of_facets > 1) if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull(); calculate_convex_hull();
} }
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) : ModelVolume(ModelObject *object, const ModelVolume &other) :
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
{ {
this->material_id(other.material_id()); this->material_id(other.material_id());
} }
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
{ {
this->material_id(other.material_id()); this->material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1) if (mesh.stl.stats.number_of_facets > 1)

View File

@ -130,6 +130,7 @@ objfunc(const PointImpl& bincenter,
double norm, // A norming factor for physical dimensions double norm, // A norming factor for physical dimensions
// a spatial index to quickly get neighbors of the candidate item // a spatial index to quickly get neighbors of the candidate item
const SpatIndex& spatindex, const SpatIndex& spatindex,
const SpatIndex& smalls_spatindex,
const ItemGroup& remaining const ItemGroup& remaining
) )
{ {
@ -161,7 +162,7 @@ objfunc(const PointImpl& bincenter,
// Will hold the resulting score // Will hold the resulting score
double score = 0; double score = 0;
if(isBig(item.area())) { if(isBig(item.area()) || spatindex.empty()) {
// This branch is for the bigger items.. // This branch is for the bigger items..
auto minc = ibb.minCorner(); // bottom left corner auto minc = ibb.minCorner(); // bottom left corner
@ -183,6 +184,8 @@ objfunc(const PointImpl& bincenter,
// The smalles distance from the arranged pile center: // The smalles distance from the arranged pile center:
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
dist = 0.8*dist + 0.2*bindist;
// Density is the pack density: how big is the arranged pile // Density is the pack density: how big is the arranged pile
double density = 0; double density = 0;
@ -207,14 +210,20 @@ objfunc(const PointImpl& bincenter,
// candidate to be aligned with only one item. // candidate to be aligned with only one item.
auto alignment_score = 1.0; auto alignment_score = 1.0;
density = (fullbb.width()*fullbb.height()) / (norm*norm); density = std::sqrt((fullbb.width() / norm )*
(fullbb.height() / norm));
auto querybb = item.boundingBox(); auto querybb = item.boundingBox();
// Query the spatial index for the neighbors // Query the spatial index for the neighbors
std::vector<SpatElement> result; std::vector<SpatElement> result;
result.reserve(spatindex.size()); result.reserve(spatindex.size());
spatindex.query(bgi::intersects(querybb), if(isBig(item.area())) {
std::back_inserter(result)); spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
} else {
smalls_spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
}
for(auto& e : result) { // now get the score for the best alignment for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second; auto idx = e.second;
@ -235,12 +244,8 @@ objfunc(const PointImpl& bincenter,
if(result.empty()) if(result.empty())
score = 0.5 * dist + 0.5 * density; score = 0.5 * dist + 0.5 * density;
else else
score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
} }
} else if( !isBig(item.area()) && spatindex.empty()) {
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
// Bindist is surprisingly enough...
score = bindist;
} else { } else {
// Here there are the small items that should be placed around the // Here there are the small items that should be placed around the
// already processed bigger items. // already processed bigger items.
@ -291,6 +296,7 @@ protected:
PConfig pconf_; // Placement configuration PConfig pconf_; // Placement configuration
double bin_area_; double bin_area_;
SpatIndex rtree_; SpatIndex rtree_;
SpatIndex smallsrtree_;
double norm_; double norm_;
Pile merged_pile_; Pile merged_pile_;
Box pilebb_; Box pilebb_;
@ -318,6 +324,7 @@ public:
pilebb_ = sl::boundingBox(merged_pile); pilebb_ = sl::boundingBox(merged_pile);
rtree_.clear(); rtree_.clear();
smallsrtree_.clear();
// We will treat big items (compared to the print bed) differently // We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) { auto isBig = [this](double a) {
@ -327,6 +334,7 @@ public:
for(unsigned idx = 0; idx < items.size(); ++idx) { for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx]; Item& itm = items[idx];
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
smallsrtree_.insert({itm.boundingBox(), idx});
} }
}; };
@ -360,6 +368,7 @@ public:
bin_area_, bin_area_,
norm_, norm_,
rtree_, rtree_,
smallsrtree_,
remaining_); remaining_);
double score = std::get<0>(result); double score = std::get<0>(result);
@ -397,6 +406,7 @@ public:
bin_area_, bin_area_,
norm_, norm_,
rtree_, rtree_,
smallsrtree_,
remaining_); remaining_);
double score = std::get<0>(result); double score = std::get<0>(result);
@ -440,6 +450,7 @@ public:
bin_area_, bin_area_,
norm_, norm_,
rtree_, rtree_,
smallsrtree_,
remaining_); remaining_);
double score = std::get<0>(result); double score = std::get<0>(result);
@ -468,6 +479,7 @@ public:
0, 0,
norm_, norm_,
rtree_, rtree_,
smallsrtree_,
remaining_); remaining_);
return std::get<0>(result); return std::get<0>(result);
}; };

View File

@ -35,8 +35,10 @@ public:
Point first_point() const; Point first_point() const;
virtual Point last_point() const = 0; virtual Point last_point() const = 0;
virtual Lines lines() const = 0; virtual Lines lines() const = 0;
size_t size() const { return points.size(); }
bool empty() const { return points.empty(); }
double length() const; double length() const;
bool is_valid() const { return this->points.size() >= 2; } bool is_valid() const { return this->points.size() >= 2; }
int find_point(const Point &point) const; int find_point(const Point &point) const;
bool has_boundary_point(const Point &point) const; bool has_boundary_point(const Point &point) const;

View File

@ -22,6 +22,7 @@ typedef Point Vector;
// Vector types with a fixed point coordinate base type. // Vector types with a fixed point coordinate base type.
typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd; typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd; typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64; typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64;
typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64; typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64;

View File

@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double angle)
p.rotate(cos_angle, sin_angle); p.rotate(cos_angle, sin_angle);
} }
inline void polygons_reverse(Polygons &polys)
{
for (Polygon &p : polys)
p.reverse();
}
inline Points to_points(const Polygon &poly) inline Points to_points(const Polygon &poly)
{ {
return poly.points; return poly.points;

View File

@ -184,15 +184,13 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
bool Polyline::is_straight() const bool Polyline::is_straight() const
{ {
/* Check that each segment's direction is equal to the line connecting // Check that each segment's direction is equal to the line connecting
first point and last point. (Checking each line against the previous // first point and last point. (Checking each line against the previous
one would cause the error to accumulate.) */ // one would cause the error to accumulate.)
double dir = Line(this->first_point(), this->last_point()).direction(); double dir = Line(this->first_point(), this->last_point()).direction();
for (const auto &line: this->lines())
Lines lines = this->lines(); if (! line.parallel_to(dir))
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { return false;
if (!line->parallel_to(dir)) return false;
}
return true; return true;
} }

View File

@ -21,6 +21,8 @@ public:
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {} Polyline(std::initializer_list<Point> list) : MultiPoint(list) {}
explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); } explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); }
explicit Polyline(const Points &points) : MultiPoint(points) {}
explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {}
Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
static Polyline new_scale(const std::vector<Vec2d> &points) { static Polyline new_scale(const std::vector<Vec2d> &points) {

View File

@ -366,9 +366,12 @@ void Print::add_model_object(ModelObject* model_object, int idx)
// Invalidate all print steps. // Invalidate all print steps.
this->invalidate_all_steps(); this->invalidate_all_steps();
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { size_t volume_id = 0;
for (const ModelVolume *volume : model_object->volumes) {
if (! volume->is_model_part() && ! volume->is_modifier())
continue;
// Get the config applied to this volume. // Get the config applied to this volume.
PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]); PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
// Find an existing print region with the same config. // Find an existing print region with the same config.
size_t region_id = size_t(-1); size_t region_id = size_t(-1);
for (size_t i = 0; i < this->regions.size(); ++ i) for (size_t i = 0; i < this->regions.size(); ++ i)
@ -383,6 +386,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
} }
// Assign volume to a region. // Assign volume to a region.
object->add_region_volume(region_id, volume_id); object->add_region_volume(region_id, volume_id);
++ volume_id;
} }
// Apply config to print object. // Apply config to print object.
@ -857,7 +861,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
ModelVolume *volume = model_object->volumes[volume_id]; ModelVolume *volume = model_object->volumes[volume_id];
//FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
if (! volume->material_id().empty() && ! volume->config.has("extruder")) if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder"))
volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1); volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
} }
} }
@ -1197,6 +1201,9 @@ void Print::_make_wipe_tower()
} }
m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>( m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
wipe_tower.tool_change((unsigned int)-1, false)); wipe_tower.tool_change((unsigned int)-1, false));
m_wipe_tower_used_filament = wipe_tower.get_used_filament();
m_wipe_tower_number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
} }
std::string Print::output_filename() std::string Print::output_filename()

View File

@ -80,7 +80,10 @@ public:
Print* print() { return this->_print; } Print* print() { return this->_print; }
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t bridging_height_avg(const PrintConfig &print_config) const;
private: private:
Print* _print; Print* _print;
@ -211,6 +214,10 @@ public:
bool is_printable() const { return !this->_shifted_copies.empty(); } bool is_printable() const { return !this->_shifted_copies.empty(); }
// Helpers to slice support enforcer / blocker meshes by the support generator.
std::vector<ExPolygons> slice_support_enforcers() const;
std::vector<ExPolygons> slice_support_blockers() const;
private: private:
Print* _print; Print* _print;
ModelObject* _model_object; ModelObject* _model_object;
@ -222,6 +229,7 @@ private:
~PrintObject() {} ~PrintObject() {}
std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier); std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
}; };
typedef std::vector<PrintObject*> PrintObjectPtrs; typedef std::vector<PrintObject*> PrintObjectPtrs;
@ -246,7 +254,7 @@ public:
std::string estimated_normal_print_time; std::string estimated_normal_print_time;
std::string estimated_silent_print_time; std::string estimated_silent_print_time;
double total_used_filament, total_extruded_volume, total_cost, total_weight; double total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament;
std::map<size_t, float> filament_stats; std::map<size_t, float> filament_stats;
PrintState<PrintStep, psCount> state; PrintState<PrintStep, psCount> state;
@ -315,6 +323,8 @@ public:
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming; std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming;
std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes; std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge; std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge;
std::vector<float> m_wipe_tower_used_filament;
int m_wipe_tower_number_of_toolchanges = -1;
std::string output_filename(); std::string output_filename();
std::string output_filepath(const std::string &path); std::string output_filepath(const std::string &path);

View File

@ -1717,6 +1717,14 @@ void PrintConfigDef::init_fff_params()
def->cli = "support-material!"; def->cli = "support-material!";
def->default_value = new ConfigOptionBool(false); def->default_value = new ConfigOptionBool(false);
def = this->add("support_material_auto", coBool);
def->label = L("Auto generated supports");
def->category = L("Support material");
def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\
" If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only.");
def->cli = "support-material-auto!";
def->default_value = new ConfigOptionBool(true);
def = this->add("support_material_xy_spacing", coFloatOrPercent); def = this->add("support_material_xy_spacing", coFloatOrPercent);
def->label = L("XY separation between an object and its support"); def->label = L("XY separation between an object and its support");
def->category = L("Support material"); def->category = L("Support material");
@ -1755,7 +1763,7 @@ void PrintConfigDef::init_fff_params()
"for the first object layer."); "for the first object layer.");
def->sidetext = L("mm"); def->sidetext = L("mm");
def->cli = "support-material-contact-distance=f"; def->cli = "support-material-contact-distance=f";
def->min = 0; // def->min = 0;
def->enum_values.push_back("0"); def->enum_values.push_back("0");
def->enum_values.push_back("0.2"); def->enum_values.push_back("0.2");
def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());

View File

@ -345,6 +345,7 @@ public:
ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent extrusion_width;
ConfigOptionFloatOrPercent first_layer_height; ConfigOptionFloatOrPercent first_layer_height;
ConfigOptionBool infill_only_where_needed; ConfigOptionBool infill_only_where_needed;
// Force the generation of solid shells between adjacent materials/volumes.
ConfigOptionBool interface_shells; ConfigOptionBool interface_shells;
ConfigOptionFloat layer_height; ConfigOptionFloat layer_height;
ConfigOptionInt raft_layers; ConfigOptionInt raft_layers;
@ -352,6 +353,9 @@ public:
// ConfigOptionFloat seam_preferred_direction; // ConfigOptionFloat seam_preferred_direction;
// ConfigOptionFloat seam_preferred_direction_jitter; // ConfigOptionFloat seam_preferred_direction_jitter;
ConfigOptionBool support_material; ConfigOptionBool support_material;
// Automatic supports (generated based on support_material_threshold).
ConfigOptionBool support_material_auto;
// Direction of the support pattern (in XY plane).
ConfigOptionFloat support_material_angle; ConfigOptionFloat support_material_angle;
ConfigOptionBool support_material_buildplate_only; ConfigOptionBool support_material_buildplate_only;
ConfigOptionFloat support_material_contact_distance; ConfigOptionFloat support_material_contact_distance;
@ -361,12 +365,15 @@ public:
ConfigOptionBool support_material_interface_contact_loops; ConfigOptionBool support_material_interface_contact_loops;
ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_extruder;
ConfigOptionInt support_material_interface_layers; ConfigOptionInt support_material_interface_layers;
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloat support_material_interface_spacing;
ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionFloatOrPercent support_material_interface_speed;
ConfigOptionEnum<SupportMaterialPattern> support_material_pattern; ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
// Spacing between support material lines (the hatching distance).
ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_spacing;
ConfigOptionFloat support_material_speed; ConfigOptionFloat support_material_speed;
ConfigOptionBool support_material_synchronize_layers; ConfigOptionBool support_material_synchronize_layers;
// Overhang angle threshold.
ConfigOptionInt support_material_threshold; ConfigOptionInt support_material_threshold;
ConfigOptionBool support_material_with_sheath; ConfigOptionBool support_material_with_sheath;
ConfigOptionFloatOrPercent support_material_xy_spacing; ConfigOptionFloatOrPercent support_material_xy_spacing;
@ -389,6 +396,7 @@ protected:
// OPT_PTR(seam_preferred_direction); // OPT_PTR(seam_preferred_direction);
// OPT_PTR(seam_preferred_direction_jitter); // OPT_PTR(seam_preferred_direction_jitter);
OPT_PTR(support_material); OPT_PTR(support_material);
OPT_PTR(support_material_auto);
OPT_PTR(support_material_angle); OPT_PTR(support_material_angle);
OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_buildplate_only);
OPT_PTR(support_material_contact_distance); OPT_PTR(support_material_contact_distance);
@ -436,10 +444,12 @@ public:
ConfigOptionInt infill_every_layers; ConfigOptionInt infill_every_layers;
ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloatOrPercent infill_overlap;
ConfigOptionFloat infill_speed; ConfigOptionFloat infill_speed;
// Detect bridging perimeters
ConfigOptionBool overhangs; ConfigOptionBool overhangs;
ConfigOptionInt perimeter_extruder; ConfigOptionInt perimeter_extruder;
ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloatOrPercent perimeter_extrusion_width;
ConfigOptionFloat perimeter_speed; ConfigOptionFloat perimeter_speed;
// Total number of perimeters.
ConfigOptionInt perimeters; ConfigOptionInt perimeters;
ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloatOrPercent small_perimeter_speed;
ConfigOptionFloat solid_infill_below_area; ConfigOptionFloat solid_infill_below_area;
@ -447,6 +457,7 @@ public:
ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers; ConfigOptionInt solid_infill_every_layers;
ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionFloatOrPercent solid_infill_speed;
// Detect thin walls.
ConfigOptionBool thin_walls; ConfigOptionBool thin_walls;
ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionFloatOrPercent top_infill_extrusion_width;
ConfigOptionInt top_solid_layers; ConfigOptionInt top_solid_layers;

View File

@ -177,6 +177,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
steps.emplace_back(posSlice); steps.emplace_back(posSlice);
} else if ( } else if (
opt_key == "support_material" opt_key == "support_material"
|| opt_key == "support_material_auto"
|| opt_key == "support_material_angle" || opt_key == "support_material_angle"
|| opt_key == "support_material_buildplate_only" || opt_key == "support_material_buildplate_only"
|| opt_key == "support_material_enforce_layers" || opt_key == "support_material_enforce_layers"
@ -1325,29 +1326,62 @@ end:
std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier) std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
{ {
std::vector<ExPolygons> layers; std::vector<const ModelVolume*> volumes;
if (region_id < this->region_volumes.size()) { if (region_id < this->region_volumes.size()) {
std::vector<int> &volumes = this->region_volumes[region_id]; for (int volume_id : this->region_volumes[region_id]) {
if (! volumes.empty()) { const ModelVolume *volume = this->model_object()->volumes[volume_id];
// Compose mesh. if (modifier ? volume->is_modifier() : volume->is_model_part())
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. volumes.emplace_back(volume);
TriangleMesh mesh; }
for (int volume_id : volumes) { }
ModelVolume *volume = this->model_object()->volumes[volume_id]; return this->_slice_volumes(z, volumes);
if (volume->modifier == modifier) }
mesh.merge(volume->mesh);
} std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
if (mesh.stl.stats.number_of_facets > 0) { {
// transform mesh std::vector<const ModelVolume*> volumes;
// we ignore the per-instance transformations currently and only for (const ModelVolume *volume : this->model_object()->volumes)
// consider the first one if (volume->is_support_enforcer())
this->model_object()->instances.front()->transform_mesh(&mesh, true); volumes.emplace_back(volume);
// align mesh to Z = 0 (it should be already aligned actually) and apply XY shift std::vector<float> zs;
mesh.translate(- unscale<float>(this->_copies_shift(0)), - unscale<float>(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); zs.reserve(this->layers.size());
// perform actual slicing for (const Layer *l : this->layers)
TriangleMeshSlicer mslicer(&mesh); zs.emplace_back(l->slice_z);
mslicer.slice(z, &layers); return this->_slice_volumes(zs, volumes);
} }
std::vector<ExPolygons> PrintObject::slice_support_blockers() const
{
std::vector<const ModelVolume*> volumes;
for (const ModelVolume *volume : this->model_object()->volumes)
if (volume->is_support_blocker())
volumes.emplace_back(volume);
std::vector<float> zs;
zs.reserve(this->layers.size());
for (const Layer *l : this->layers)
zs.emplace_back(l->slice_z);
return this->_slice_volumes(zs, volumes);
}
std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
{
std::vector<ExPolygons> layers;
if (! volumes.empty()) {
// Compose mesh.
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
TriangleMesh mesh;
for (const ModelVolume *v : volumes)
mesh.merge(v->mesh);
if (mesh.stl.stats.number_of_facets > 0) {
// transform mesh
// we ignore the per-instance transformations currently and only
// consider the first one
this->model_object()->instances.front()->transform_mesh(&mesh, true);
// align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
mesh.translate(- unscale<float>(this->_copies_shift(0)), - unscale<float>(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2)));
// perform actual slicing
TriangleMeshSlicer mslicer(&mesh);
mslicer.slice(z, &layers);
} }
} }
return layers; return layers;

View File

@ -57,4 +57,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.; print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.;
} }
coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
{
return this->nozzle_dmr_avg(print_config) * sqrt(this->config.bridge_flow_ratio.value);
}
} }

View File

@ -224,9 +224,9 @@ std::vector<coordf_t> layer_height_profile_adaptive(
// 1) Initialize the SlicingAdaptive class with the object meshes. // 1) Initialize the SlicingAdaptive class with the object meshes.
SlicingAdaptive as; SlicingAdaptive as;
as.set_slicing_parameters(slicing_params); as.set_slicing_parameters(slicing_params);
for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) for (const ModelVolume *volume : volumes)
if (! (*it)->modifier) if (volume->is_model_part())
as.add_mesh(&(*it)->mesh); as.add_mesh(&volume->mesh);
as.prepare(); as.prepare();
// 2) Generate layers using the algorithm of @platsch // 2) Generate layers using the algorithm of @platsch

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ class PrintConfig;
class PrintObjectConfig; class PrintObjectConfig;
// how much we extend support around the actual contact area // how much we extend support around the actual contact area
//FIXME this should be dependent on the nozzle diameter!
#define SUPPORT_MATERIAL_MARGIN 1.5 #define SUPPORT_MATERIAL_MARGIN 1.5
// This class manages raft and supports for a single PrintObject. // This class manages raft and supports for a single PrintObject.
@ -71,6 +72,21 @@ public:
overhang_polygons = nullptr; overhang_polygons = nullptr;
} }
void reset() {
layer_type = sltUnknown;
print_z = 0.;
bottom_z = 0.;
height = 0.;
idx_object_layer_above = size_t(-1);
idx_object_layer_below = size_t(-1);
bridging = false;
polygons.clear();
delete contact_polygons;
contact_polygons = nullptr;
delete overhang_polygons;
overhang_polygons = nullptr;
}
bool operator==(const MyLayer &layer2) const { bool operator==(const MyLayer &layer2) const {
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
} }

View File

@ -37,6 +37,11 @@ public:
void clear() { surfaces.clear(); } void clear() { surfaces.clear(); }
bool empty() const { return surfaces.empty(); } bool empty() const { return surfaces.empty(); }
bool has(SurfaceType type) const {
for (const Surface &surface : this->surfaces)
if (surface.surface_type == type) return true;
return false;
}
void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }

View File

@ -21,16 +21,20 @@
#include <Eigen/Dense> #include <Eigen/Dense>
// for SLIC3R_DEBUG_SLICE_PROCESSING
#include "libslic3r.h"
#if 0 #if 0
#define DEBUG #define DEBUG
#define _DEBUG #define _DEBUG
#undef NDEBUG #undef NDEBUG
#define SLIC3R_DEBUG
// #define SLIC3R_TRIANGLEMESH_DEBUG
#endif #endif
#include <assert.h> #include <assert.h>
#ifdef SLIC3R_DEBUG #if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
// #define SLIC3R_TRIANGLEMESH_DEBUG
#include "SVG.hpp" #include "SVG.hpp"
#endif #endif
@ -156,7 +160,6 @@ void TriangleMesh::repair()
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
} }
float TriangleMesh::volume() float TriangleMesh::volume()
{ {
if (this->stl.stats.volume == -1) if (this->stl.stats.volume == -1)
@ -320,7 +323,7 @@ bool TriangleMesh::has_multiple_patches() const
facet_visited[facet_idx] = true; facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) { for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
if (! facet_visited[neighbor_idx]) if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx; facet_queue[facet_queue_cnt ++] = neighbor_idx;
} }
} }
@ -363,7 +366,7 @@ size_t TriangleMesh::number_of_patches() const
facet_visited[facet_idx] = true; facet_visited[facet_idx] = true;
for (int j = 0; j < 3; ++ j) { for (int j = 0; j < 3; ++ j) {
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
if (! facet_visited[neighbor_idx]) if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
facet_queue[facet_queue_cnt ++] = neighbor_idx; facet_queue[facet_queue_cnt ++] = neighbor_idx;
} }
} }
@ -623,10 +626,23 @@ void TriangleMesh::require_shared_vertices()
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
stl_generate_shared_vertices(&(this->stl)); stl_generate_shared_vertices(&(this->stl));
} }
#ifdef _DEBUG
// Verify validity of neighborship data.
for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) {
const stl_neighbors &nbr = stl.neighbors_start[facet_idx];
const int *vertices = stl.v_indices[facet_idx].vertex;
for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) {
int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx];
if (nbr_face != -1) {
assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]);
assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]);
}
}
}
#endif /* _DEBUG */
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
} }
TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
mesh(_mesh) mesh(_mesh)
{ {
@ -698,13 +714,13 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
} }
} }
// Assign an edge index to the 1st face. // Assign an edge index to the 1st face.
this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges;
if (found) { if (found) {
EdgeToFace &edge_j = edges_map[j]; EdgeToFace &edge_j = edges_map[j];
this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges;
// Mark the edge as connected. // Mark the edge as connected.
edge_j.face = -1; edge_j.face = -1;
} }
++ num_edges; ++ num_edges;
} }
} }
@ -771,13 +787,30 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
{ {
static int iRun = 0; static int iRun = 0;
for (size_t i = 0; i < z.size(); ++ i) { for (size_t i = 0; i < z.size(); ++ i) {
Polygons &polygons = (*layers)[i]; Polygons &polygons = (*layers)[i];
SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true)); ExPolygons expolygons = union_ex(polygons, true);
SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons);
{
BoundingBox bbox;
for (const IntersectionLine &l : lines[i]) {
bbox.merge(l.a);
bbox.merge(l.b);
}
SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox);
svg.draw(expolygons);
for (const IntersectionLine &l : lines[i])
svg.draw(l, "red", 0);
svg.draw_outline(expolygons, "black", "blue", 0);
svg.Close();
}
#if 0
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
for (Polygon &poly : polygons) { for (Polygon &poly : polygons) {
for (size_t i = 1; i < poly.points.size(); ++ i) for (size_t i = 1; i < poly.points.size(); ++ i)
assert(poly.points[i-1] != poly.points[i]); assert(poly.points[i-1] != poly.points[i]);
assert(poly.points.front() != poly.points.back()); assert(poly.points.front() != poly.points.back());
} }
#endif
} }
++ iRun; ++ iRun;
} }
@ -793,91 +826,94 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2))); const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2)));
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2), facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2),
facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2), facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2),
facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2)); facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2));
printf("z: min = %.2f, max = %.2f\n", min_z, max_z); printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
#endif #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
// find layer extents // find layer extents
std::vector<float>::const_iterator min_layer, max_layer; std::vector<float>::const_iterator min_layer, max_layer;
min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z
max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()));
#endif #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) { for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
std::vector<float>::size_type layer_idx = it - z.begin(); std::vector<float>::size_type layer_idx = it - z.begin();
IntersectionLine il; IntersectionLine il;
if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) {
boost::lock_guard<boost::mutex> l(*lines_mutex); boost::lock_guard<boost::mutex> l(*lines_mutex);
if (il.edge_type == feHorizontal) { if (il.edge_type == feHorizontal) {
// Insert all three edges of the face. // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face
// (they may not have a nighbor, or their neighbor is vertical)
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0; const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0;
for (int j = 0; j < 3; ++ j) { for (int j = 0; j < 3; ++ j)
int a_id = vertices[j % 3]; if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) {
int b_id = vertices[(j+1) % 3]; int a_id = vertices[j % 3];
if (reverse) int b_id = vertices[(j+1) % 3];
std::swap(a_id, b_id); if (reverse)
const stl_vertex &a = this->v_scaled_shared[a_id]; std::swap(a_id, b_id);
const stl_vertex &b = this->v_scaled_shared[b_id]; const stl_vertex &a = this->v_scaled_shared[a_id];
il.a(0) = a(0); const stl_vertex &b = this->v_scaled_shared[b_id];
il.a(1) = a(1); il.a(0) = a(0);
il.b(0) = b(0); il.a(1) = a(1);
il.b(1) = b(1); il.b(0) = b(0);
il.a_id = a_id; il.b(1) = b(1);
il.b_id = b_id; il.a_id = a_id;
(*lines)[layer_idx].emplace_back(il); il.b_id = b_id;
} assert(il.a != il.b);
// This edge will not be used as a seed for loop extraction if it was added due to a fold of two overlapping horizontal faces.
il.set_no_seed((IntersectionLine::EDGE0_FOLD << j) != 0);
(*lines)[layer_idx].emplace_back(il);
}
} else } else
(*lines)[layer_idx].emplace_back(il); (*lines)[layer_idx].emplace_back(il);
} }
} }
} }
void void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
{ {
std::vector<Polygons> layers_p; std::vector<Polygons> layers_p;
this->slice(z, &layers_p); this->slice(z, &layers_p);
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
layers->resize(z.size()); layers->resize(z.size());
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, z.size()), tbb::blocked_range<size_t>(0, z.size()),
[&layers_p, layers, this](const tbb::blocked_range<size_t>& range) { [&layers_p, layers, this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
#ifdef SLIC3R_TRIANGLEMESH_DEBUG #ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]);
#endif #endif
this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]); this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]);
} }
}); });
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end";
} }
// Return true, if the facet has been sliced and line_out has been filled. // Return true, if the facet has been sliced and line_out has been filled.
bool TriangleMeshSlicer::slice_facet( TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
float slice_z, const stl_facet &facet, const int facet_idx, float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z, const float min_z, const float max_z,
IntersectionLine *line_out) const IntersectionLine *line_out) const
{ {
IntersectionPoint points[3]; IntersectionPoint points[3];
size_t num_points = 0; size_t num_points = 0;
size_t points_on_layer[3]; size_t point_on_layer = size_t(-1);
size_t num_points_on_layer = 0;
// Reorder vertices so that the first one is the one with lowest Z. // Reorder vertices so that the first one is the one with lowest Z.
// This is needed to get all intersection lines in a consistent order // This is needed to get all intersection lines in a consistent order
// (external on the right of the line) // (external on the right of the line)
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0); int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0);
for (int j = i; j - i < 3; ++ j) { // loop through facet edges for (int j = i; j - i < 3; ++j ) { // loop through facet edges
int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int a_id = vertices[j % 3]; int a_id = vertices[j % 3];
int b_id = vertices[(j+1) % 3]; int b_id = vertices[(j+1) % 3];
const stl_vertex &a = this->v_scaled_shared[a_id]; const stl_vertex &a = this->v_scaled_shared[a_id];
@ -890,116 +926,279 @@ bool TriangleMeshSlicer::slice_facet(
const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
bool swap = false; bool swap = false;
const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
FacetSliceType result = Slicing;
const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx];
if (min_z == max_z) { if (min_z == max_z) {
// All three vertices are aligned with slice_z. // All three vertices are aligned with slice_z.
line_out->edge_type = feHorizontal; line_out->edge_type = feHorizontal;
if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) { // Mark neighbor edges, which do not have a neighbor.
uint32_t edges = 0;
for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) {
// If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no
// opposite face, add it to the edges to process when slicing.
if (nbr.neighbor[nbr_idx] == -1) {
// Mark this edge to be added to the slice.
edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx);
}
#if 1
else if (normal(2) > 0) {
// Produce edges for opposite faced overlapping horizontal faces aka folds.
// This method often produces connecting lines (noise) at the cutting plane.
// Produce the edges for the top facing face of the pair of top / bottom facing faces.
// Index of a neighbor face.
const int nbr_face = nbr.neighbor[nbr_idx];
const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex;
int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]];
const stl_vertex &c2 = this->v_scaled_shared[idx_vertex_opposite];
if (c2(2) == slice_z) {
// Edge shared by facet_idx and nbr_face.
int a_id = vertices[nbr_idx];
int b_id = vertices[(nbr_idx + 1) % 3];
int c1_id = vertices[(nbr_idx + 2) % 3];
const stl_vertex &a = this->v_scaled_shared[a_id];
const stl_vertex &b = this->v_scaled_shared[b_id];
const stl_vertex &c1 = this->v_scaled_shared[c1_id];
// Verify that the two neighbor faces share a common edge.
assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
double n1 = (double(c1(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c1(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
double n2 = (double(c2(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c2(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
if (n1 * n2 > 0)
// The two faces overlap. This indicates an invalid mesh geometry (non-manifold),
// but these are the real world objects, and leaving out these edges leads to missing contours.
edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx);
}
}
#endif
}
// Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face.
result = (edges == 0) ? Cutting : Slicing;
line_out->flags |= edges;
if (normal(2) < 0) {
// If normal points downwards this is a bottom horizontal facet so we reverse its point order. // If normal points downwards this is a bottom horizontal facet so we reverse its point order.
swap = true; swap = true;
} }
} else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) {
// Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
line_out->edge_type = feTop;
swap = true;
} else { } else {
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
line_out->edge_type = feBottom; int nbr_idx = j % 3;
int nbr_face = nbr.neighbor[nbr_idx];
// Is the third vertex below the cutting plane?
bool third_below = v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z;
// Is this a concave corner?
if (nbr_face == -1) {
#ifdef _DEBUG
printf("Face has no neighbor!\n");
#endif
} else {
assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]];
const stl_vertex &c = this->v_scaled_shared[idx_vertex_opposite];
if (c(2) == slice_z) {
double normal_nbr = (double(c(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
#if 0
if ((normal_nbr < 0) == third_below) {
printf("Flipped normal?\n");
}
#endif
result =
// A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner.
// Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge,
// and leth the code downstream hopefully handle it.
#if 1
// Ignore concave corners for slicing.
// This method has the unfortunate property, that folds in a horizontal plane create concave corners,
// leading to broken contours, if these concave corners are not replaced by edges of the folds, see above.
((normal_nbr < 0) == third_below) ? Cutting : Slicing;
#else
// Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing,
// and rightly so.
Slicing;
#endif
} else {
// For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index.
result = (facet_idx < nbr_face) ? Slicing : Cutting;
}
}
if (third_below) {
line_out->edge_type = feTop;
swap = true;
} else
line_out->edge_type = feBottom;
} }
line_out->a = to_2d(swap ? b : a).cast<coord_t>(); line_out->a = to_2d(swap ? b : a).cast<coord_t>();
line_out->b = to_2d(swap ? a : b).cast<coord_t>(); line_out->b = to_2d(swap ? a : b).cast<coord_t>();
line_out->a_id = swap ? b_id : a_id; line_out->a_id = swap ? b_id : a_id;
line_out->b_id = swap ? a_id : b_id; line_out->b_id = swap ? a_id : b_id;
return true; assert(line_out->a != line_out->b);
return result;
} }
if (a(2) == slice_z) { if (a(2) == slice_z) {
// Only point a alings with the cutting plane. // Only point a alings with the cutting plane.
points_on_layer[num_points_on_layer ++] = num_points; if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
IntersectionPoint &point = points[num_points ++]; point_on_layer = num_points;
point(0) = a(0); IntersectionPoint &point = points[num_points ++];
point(1) = a(1); point(0) = a(0);
point.point_id = a_id; point(1) = a(1);
point.point_id = a_id;
}
} else if (b(2) == slice_z) { } else if (b(2) == slice_z) {
// Only point b alings with the cutting plane. // Only point b alings with the cutting plane.
points_on_layer[num_points_on_layer ++] = num_points; if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
IntersectionPoint &point = points[num_points ++]; point_on_layer = num_points;
point(0) = b(0); IntersectionPoint &point = points[num_points ++];
point(1) = b(1); point(0) = b(0);
point.point_id = b_id; point(1) = b(1);
point.point_id = b_id;
}
} else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) { } else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) {
// A general case. The face edge intersects the cutting plane. Calculate the intersection point. // A general case. The face edge intersects the cutting plane. Calculate the intersection point.
IntersectionPoint &point = points[num_points ++]; assert(a_id != b_id);
point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2)); // Sort the edge to give a consistent answer.
point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2)); const stl_vertex *pa = &a;
point.edge_id = edge_id; const stl_vertex *pb = &b;
if (a_id > b_id) {
std::swap(a_id, b_id);
std::swap(pa, pb);
}
IntersectionPoint &point = points[num_points];
double t = (double(slice_z) - double((*pb)(2))) / (double((*pa)(2)) - double((*pb)(2)));
if (t <= 0.) {
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
point(0) = (*pa)(0);
point(1) = (*pa)(1);
point_on_layer = num_points ++;
point.point_id = a_id;
}
} else if (t >= 1.) {
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
point(0) = (*pb)(0);
point(1) = (*pb)(1);
point_on_layer = num_points ++;
point.point_id = b_id;
}
} else {
point(0) = coord_t(floor(double((*pb)(0)) + (double((*pa)(0)) - double((*pb)(0))) * t + 0.5));
point(1) = coord_t(floor(double((*pb)(1)) + (double((*pa)(1)) - double((*pb)(1))) * t + 0.5));
point.edge_id = edge_id;
++ num_points;
}
} }
} }
// We can't have only one point on layer because each vertex gets detected // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only.
// twice (once for each edge), and we can't have three points on layer, assert(num_points < 3);
// because we assume this code is not getting called for horizontal facets.
assert(num_points_on_layer == 0 || num_points_on_layer == 2);
if (num_points_on_layer > 0) {
assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id);
assert(num_points == 2 || num_points == 3);
if (num_points < 3)
// This triangle touches the cutting plane with a single vertex. Ignore it.
return false;
// Erase one of the duplicate points.
-- num_points;
for (int i = points_on_layer[1]; i < num_points; ++ i)
points[i] = points[i + 1];
}
// Facets must intersect each plane 0 or 2 times.
assert(num_points == 0 || num_points == 2);
if (num_points == 2) { if (num_points == 2) {
line_out->edge_type = feNone; line_out->edge_type = feGeneral;
line_out->a = (Point)points[1]; line_out->a = (Point)points[1];
line_out->b = (Point)points[0]; line_out->b = (Point)points[0];
line_out->a_id = points[1].point_id; line_out->a_id = points[1].point_id;
line_out->b_id = points[0].point_id; line_out->b_id = points[0].point_id;
line_out->edge_a_id = points[1].edge_id; line_out->edge_a_id = points[1].edge_id;
line_out->edge_b_id = points[0].edge_id; line_out->edge_b_id = points[0].edge_id;
return true; // Not a zero lenght edge.
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
//assert(line_out->a != line_out->b);
// The plane cuts at least one edge in a general position.
assert(line_out->a_id == -1 || line_out->b_id == -1);
assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1);
// General slicing position, use the segment for both slicing and object cutting.
#if 0
if (line_out->a_id != -1 && line_out->b_id != -1) {
// Solving a degenerate case, where both the intersections snapped to an edge.
// Correctly classify the face as below or above based on the position of the 3rd point.
int i = vertices[0];
if (i == line_out->a_id || i == line_out->b_id)
i = vertices[1];
if (i == line_out->a_id || i == line_out->b_id)
i = vertices[2];
assert(i != line_out->a_id && i != line_out->b_id);
line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom;
}
#endif
return Slicing;
}
return NoSlice;
}
//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
// So the following code makes only sense now to handle degenerate meshes with more than two faces
// sharing a single edge.
static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines)
{
std::vector<IntersectionLine*> by_vertex_pair;
by_vertex_pair.reserve(lines.size());
for (IntersectionLine& line : lines)
if (line.edge_type != feGeneral && line.a_id != -1)
// This is a face edge. Check whether there is its neighbor stored in lines.
by_vertex_pair.emplace_back(&line);
auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) {
// Sort vertices of l1, l2 lexicographically
int l1a = l1->a_id;
int l1b = l1->b_id;
int l2a = l2->a_id;
int l2b = l2->b_id;
if (l1a > l1b)
std::swap(l1a, l1b);
if (l2a > l2b)
std::swap(l2a, l2b);
// Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored.
return l1a < l2a || (l1a == l2a && l1b < l2b);
};
std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted);
for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) {
IntersectionLine &l1 = **line;
if (! l1.skip()) {
// Iterate as long as line and line2 edges share the same end points.
for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) {
// Lines must share the end points.
assert(! edges_lower_sorted(*line, *line2));
assert(! edges_lower_sorted(*line2, *line));
IntersectionLine &l2 = **line2;
if (l2.skip())
continue;
if (l1.a_id == l2.a_id) {
assert(l1.b_id == l2.b_id);
l2.set_skip();
// If they are both oriented upwards or downwards (like a 'V'),
// then we can remove both edges from this layer since it won't
// affect the sliced shape.
// If one of them is oriented upwards and the other is oriented
// downwards, let's only keep one of them (it doesn't matter which
// one since all 'top' lines were reversed at slicing).
if (l1.edge_type == l2.edge_type) {
l1.set_skip();
break;
}
} else {
assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id);
// If this edge joins two horizontal facets, remove both of them.
if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) {
l1.set_skip();
l2.set_skip();
break;
}
}
}
}
} }
return false;
} }
void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
{ {
// Remove tangent edges. #if 0
//FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) //#ifdef _DEBUG
if (! line->skip && line->edge_type != feNone) { for (const Line &l : lines)
// This line is af facet edge. There may be a duplicate line with the same end vertices. assert(l.a != l.b);
// If the line is is an edge connecting two facets, find another facet edge #endif /* _DEBUG */
// having the same endpoints but in reverse order.
for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) remove_tangent_edges(lines);
if (! line2->skip && line2->edge_type != feNone) {
// Are these facets adjacent? (sharing a common edge on this layer)
if (line->a_id == line2->a_id && line->b_id == line2->b_id) {
line2->skip = true;
/* if they are both oriented upwards or downwards (like a 'V')
then we can remove both edges from this layer since it won't
affect the sliced shape */
/* if one of them is oriented upwards and the other is oriented
downwards, let's only keep one of them (it doesn't matter which
one since all 'top' lines were reversed at slicing) */
if (line->edge_type == line2->edge_type) {
line->skip = true;
break;
}
} else if (line->a_id == line2->b_id && line->b_id == line2->a_id) {
/* if this edge joins two horizontal facets, remove both of them */
if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) {
line->skip = true;
line2->skip = true;
break;
}
}
}
}
struct OpenPolyline { struct OpenPolyline {
OpenPolyline() {}; OpenPolyline() {};
@ -1022,7 +1221,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
by_edge_a_id.reserve(lines.size()); by_edge_a_id.reserve(lines.size());
by_a_id.reserve(lines.size()); by_a_id.reserve(lines.size());
for (IntersectionLine &line : lines) { for (IntersectionLine &line : lines) {
if (! line.skip) { if (! line.skip()) {
if (line.edge_a_id != -1) if (line.edge_a_id != -1)
by_edge_a_id.emplace_back(&line); by_edge_a_id.emplace_back(&line);
if (line.a_id != -1) if (line.a_id != -1)
@ -1039,13 +1238,14 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
// take first spare line and start a new loop // take first spare line and start a new loop
IntersectionLine *first_line = nullptr; IntersectionLine *first_line = nullptr;
for (; it_line_seed != lines.end(); ++ it_line_seed) for (; it_line_seed != lines.end(); ++ it_line_seed)
if (! it_line_seed->skip) { if (it_line_seed->is_seed_candidate()) {
//if (! it_line_seed->skip()) {
first_line = &(*it_line_seed ++); first_line = &(*it_line_seed ++);
break; break;
} }
if (first_line == nullptr) if (first_line == nullptr)
break; break;
first_line->skip = true; first_line->set_skip();
Points loop_pts; Points loop_pts;
loop_pts.emplace_back(first_line->a); loop_pts.emplace_back(first_line->a);
IntersectionLine *last_line = first_line; IntersectionLine *last_line = first_line;
@ -1066,7 +1266,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if (it_begin != by_edge_a_id.end()) { if (it_begin != by_edge_a_id.end()) {
auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
for (auto it_line = it_begin; it_line != it_end; ++ it_line) for (auto it_line = it_begin; it_line != it_end; ++ it_line)
if (! (*it_line)->skip) { if (! (*it_line)->skip()) {
next_line = *it_line; next_line = *it_line;
break; break;
} }
@ -1078,7 +1278,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if (it_begin != by_a_id.end()) { if (it_begin != by_a_id.end()) {
auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
for (auto it_line = it_begin; it_line != it_end; ++ it_line) for (auto it_line = it_begin; it_line != it_end; ++ it_line)
if (! (*it_line)->skip) { if (! (*it_line)->skip()) {
next_line = *it_line; next_line = *it_line;
break; break;
} }
@ -1109,7 +1309,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
*/ */
loop_pts.emplace_back(next_line->a); loop_pts.emplace_back(next_line->a);
last_line = next_line; last_line = next_line;
next_line->skip = true; next_line->set_skip();
} }
} }
} }
@ -1176,12 +1376,12 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
} }
} }
} }
if (next_start == nullptr) { if (next_start == nullptr) {
// The current loop could not be closed. Unmark the segment. // The current loop could not be closed. Unmark the segment.
opl.consumed = false; opl.consumed = false;
break; break;
} }
// Attach this polyline to the end of the initial polyline. // Attach this polyline to the end of the initial polyline.
if (next_start->start) { if (next_start->start) {
auto it = next_start->polyline->points.begin(); auto it = next_start->polyline->points.begin();
std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points));
@ -1201,8 +1401,8 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
(ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
// The current loop is complete. Add it to the output. // The current loop is complete. Add it to the output.
/*assert(opl.points.front().point_id == opl.points.back().point_id); //assert(opl.points.front().point_id == opl.points.back().point_id);
assert(opl.points.front().edge_id == opl.points.back().edge_id);*/ //assert(opl.points.front().edge_id == opl.points.back().edge_id);
// Remove the duplicate last point. // Remove the duplicate last point.
opl.points.pop_back(); opl.points.pop_back();
if (opl.points.size() >= 3) { if (opl.points.size() >= 3) {
@ -1217,9 +1417,9 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
loops->emplace_back(std::move(opl.points)); loops->emplace_back(std::move(opl.points));
} }
opl.points.clear(); opl.points.clear();
break; break;
} }
// Continue with the current loop. // Continue with the current loop.
} }
} }
} }
@ -1267,15 +1467,15 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l
if (slice_idx == -1) if (slice_idx == -1)
// Ignore this hole. // Ignore this hole.
continue; continue;
assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area()); assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area());
(*slices)[slice_idx].holes.emplace_back(std::move(*hole)); (*slices)[slice_idx].holes.emplace_back(std::move(*hole));
} }
#if 0 #if 0
// If the input mesh is not valid, the holes may intersect with the external contour. // If the input mesh is not valid, the holes may intersect with the external contour.
// Rather subtract them from the outer contour. // Rather subtract them from the outer contour.
Polygons poly; Polygons poly;
for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) {
if (it_slice->holes.empty()) { if (it_slice->holes.empty()) {
poly.emplace_back(std::move(it_slice->contour)); poly.emplace_back(std::move(it_slice->contour));
} else { } else {
@ -1285,7 +1485,7 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l
it->reverse(); it->reverse();
polygons_append(poly, diff(contours, it_slice->holes)); polygons_append(poly, diff(contours, it_slice->holes));
} }
} }
// If the input mesh is not valid, the input contours may intersect. // If the input mesh is not valid, the input contours may intersect.
*slices = union_ex(poly); *slices = union_ex(poly);
#endif #endif
@ -1402,7 +1602,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
// intersect facet with cutting plane // intersect facet with cutting plane
IntersectionLine line; IntersectionLine line;
if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) {
// Save intersection lines for generating correct triangulations. // Save intersection lines for generating correct triangulations.
if (line.edge_type == feTop) { if (line.edge_type == feTop) {
lower_lines.emplace_back(line); lower_lines.emplace_back(line);

View File

@ -82,7 +82,7 @@ private:
enum FacetEdgeType { enum FacetEdgeType {
// A general case, the cutting plane intersect a face at two different edges. // A general case, the cutting plane intersect a face at two different edges.
feNone, feGeneral,
// Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
feTop, feTop,
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
@ -116,6 +116,14 @@ public:
class IntersectionLine : public Line class IntersectionLine : public Line
{ {
public: public:
IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {}
bool skip() const { return (this->flags & SKIP) != 0; }
void set_skip() { this->flags |= SKIP; }
bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
// Inherits Point a, b // Inherits Point a, b
// For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
// Vertex indices of the line end points. // Vertex indices of the line end points.
@ -124,11 +132,23 @@ public:
// Source mesh edges of the line end points. // Source mesh edges of the line end points.
int edge_a_id; int edge_a_id;
int edge_b_id; int edge_b_id;
// feNone, feTop, feBottom, feHorizontal // feGeneral, feTop, feBottom, feHorizontal
FacetEdgeType edge_type; FacetEdgeType edge_type;
// Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. // Used by TriangleMeshSlicer::slice() to skip duplicate edges.
bool skip; enum {
IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; // Triangle edge added, because it has no neighbor.
EDGE0_NO_NEIGHBOR = 0x001,
EDGE1_NO_NEIGHBOR = 0x002,
EDGE2_NO_NEIGHBOR = 0x004,
// Triangle edge added, because it makes a fold with another horizontal edge.
EDGE0_FOLD = 0x010,
EDGE1_FOLD = 0x020,
EDGE2_FOLD = 0x040,
// The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds).
NO_SEED = 0x100,
SKIP = 0x200,
};
uint32_t flags;
}; };
typedef std::vector<IntersectionLine> IntersectionLines; typedef std::vector<IntersectionLine> IntersectionLines;
typedef std::vector<IntersectionLine*> IntersectionLinePtrs; typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
@ -139,7 +159,12 @@ public:
TriangleMeshSlicer(TriangleMesh* _mesh); TriangleMeshSlicer(TriangleMesh* _mesh);
void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const; void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const;
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const; void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const;
bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, enum FacetSliceType {
NoSlice = 0,
Slicing = 1,
Cutting = 2
};
FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z, IntersectionLine *line_out) const; const float min_z, const float max_z, IntersectionLine *line_out) const;
void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;

View File

@ -9,6 +9,7 @@ namespace Slic3r {
extern void set_logging_level(unsigned int level); extern void set_logging_level(unsigned int level);
extern void trace(unsigned int level, const char *message); extern void trace(unsigned int level, const char *message);
extern void disable_multi_threading();
// Set a path with GUI resource files. // Set a path with GUI resource files.
void set_var_dir(const std::string &path); void set_var_dir(const std::string &path);

View File

@ -24,6 +24,8 @@
#include <boost/nowide/integration/filesystem.hpp> #include <boost/nowide/integration/filesystem.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#include <tbb/task_scheduler_init.h>
namespace Slic3r { namespace Slic3r {
static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
@ -82,6 +84,14 @@ void trace(unsigned int level, const char *message)
(::boost::log::keywords::severity = severity)) << message; (::boost::log::keywords::severity = severity)) << message;
} }
void disable_multi_threading()
{
// Disable parallelization so the Shiny profiler works
static tbb::task_scheduler_init *tbb_init = nullptr;
if (tbb_init == nullptr)
tbb_init = new tbb::task_scheduler_init(1);
}
static std::string g_var_dir; static std::string g_var_dir;
void set_var_dir(const std::string &dir) void set_var_dir(const std::string &dir)

View File

@ -11,13 +11,11 @@
#include <ModelArrange.hpp> #include <ModelArrange.hpp>
#include <slic3r/GUI/PresetBundle.hpp> #include <slic3r/GUI/PresetBundle.hpp>
#include <Geometry.hpp>
#include <PrintConfig.hpp> #include <PrintConfig.hpp>
#include <Print.hpp> #include <Print.hpp>
#include <PrintExport.hpp>
#include <Geometry.hpp>
#include <Model.hpp> #include <Model.hpp>
#include <Utils.hpp> #include <Utils.hpp>
#include <SLABasePool.hpp>
namespace Slic3r { namespace Slic3r {
@ -45,15 +43,6 @@ namespace GUI {
PresetBundle* get_preset_bundle(); PresetBundle* get_preset_bundle();
} }
static const PrintObjectStep STEP_SLICE = posSlice;
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
static const PrintObjectStep STEP_INFILL = posInfill;
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
static const PrintStep STEP_SKIRT = psSkirt;
static const PrintStep STEP_BRIM = psBrim;
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::global_progress_indicator() { AppControllerBoilerplate::global_progress_indicator() {
ProgresIndicatorPtr ret; ProgresIndicatorPtr ret;
@ -73,336 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator(
pri_data_->m.unlock(); pri_data_->m.unlock();
} }
void PrintController::make_skirt()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
if(!print_->state.is_done(STEP_SKIRT)) {
print_->state.set_started(STEP_SKIRT);
print_->skirt.clear();
if(print_->has_skirt()) print_->_make_skirt();
print_->state.set_done(STEP_SKIRT);
}
}
void PrintController::make_brim()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
make_skirt();
if(!print_->state.is_done(STEP_BRIM)) {
print_->state.set_started(STEP_BRIM);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->config.brim_width > 0) print_->_make_brim();
print_->state.set_done(STEP_BRIM);
}
}
void PrintController::make_wipe_tower()
{
assert(print_ != nullptr);
// prerequisites
for(auto obj : print_->objects) make_perimeters(obj);
for(auto obj : print_->objects) infill(obj);
for(auto obj : print_->objects) gen_support_material(obj);
make_skirt();
make_brim();
if(!print_->state.is_done(STEP_WIPE_TOWER)) {
print_->state.set_started(STEP_WIPE_TOWER);
// since this method must be idempotent, we clear brim paths *before*
// checking whether we need to generate them
print_->brim.clear();
if(print_->has_wipe_tower()) print_->_make_wipe_tower();
print_->state.set_done(STEP_WIPE_TOWER);
}
}
void PrintController::slice(PrintObject *pobj)
{
assert(pobj != nullptr && print_ != nullptr);
if(pobj->state.is_done(STEP_SLICE)) return;
pobj->state.set_started(STEP_SLICE);
pobj->_slice();
auto msg = pobj->_fix_slicing_errors();
if(!msg.empty()) report_issue(IssueType::WARN, msg);
// simplify slices if required
if (print_->config.resolution)
pobj->_simplify_slices(scale_(print_->config.resolution));
if(pobj->layers.empty())
report_issue(IssueType::ERR,
_(L("No layers were detected. You might want to repair your "
"STL file(s) or check their size or thickness and retry"))
);
pobj->state.set_done(STEP_SLICE);
}
void PrintController::make_perimeters(PrintObject *pobj)
{
assert(pobj != nullptr);
slice(pobj);
if (!pobj->state.is_done(STEP_PERIMETERS)) {
pobj->_make_perimeters();
}
}
void PrintController::infill(PrintObject *pobj)
{
assert(pobj != nullptr);
make_perimeters(pobj);
if (!pobj->state.is_done(STEP_PREPARE_INFILL)) {
pobj->state.set_started(STEP_PREPARE_INFILL);
pobj->_prepare_infill();
pobj->state.set_done(STEP_PREPARE_INFILL);
}
pobj->_infill();
}
void PrintController::gen_support_material(PrintObject *pobj)
{
assert(pobj != nullptr);
// prerequisites
slice(pobj);
if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) {
pobj->state.set_started(STEP_SUPPORTMATERIAL);
pobj->clear_support_layers();
if((pobj->config.support_material || pobj->config.raft_layers > 0)
&& pobj->layers.size() > 1) {
pobj->_generate_support_material();
}
pobj->state.set_done(STEP_SUPPORTMATERIAL);
}
}
PrintController::PngExportData
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
{
PngExportData ret;
auto zippath = query_destination_path("Output zip file", "*.zip", "out");
ret.zippath = zippath;
ret.width_mm = conf.opt_float("display_width");
ret.height_mm = conf.opt_float("display_height");
ret.width_px = conf.opt_int("display_pixels_x");
ret.height_px = conf.opt_int("display_pixels_y");
auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction");
if(opt_corr) {
ret.corr_x = opt_corr->values[0];
ret.corr_y = opt_corr->values[1];
ret.corr_z = opt_corr->values[2];
}
ret.exp_time_first_s = conf.opt_float("initial_exposure_time");
ret.exp_time_s = conf.opt_float("exposure_time");
return ret;
}
void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri)
{
auto st = pri->state();
Slic3r::trace(3, "Starting the slicing process.");
pri->update(st+20, _(L("Generating perimeters")));
for(auto obj : print_->objects) make_perimeters(obj);
pri->update(st+60, _(L("Infilling layers")));
for(auto obj : print_->objects) infill(obj);
pri->update(st+70, _(L("Generating support material")));
for(auto obj : print_->objects) gen_support_material(obj);
pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")),
print_->total_weight, print_->total_cost);
pri->state(st+85);
pri->update(st+88, _(L("Generating skirt")));
make_skirt();
pri->update(st+90, _(L("Generating brim")));
make_brim();
pri->update(st+95, _(L("Generating wipe tower")));
make_wipe_tower();
pri->update(st+100, _(L("Done")));
// time to make some statistics..
Slic3r::trace(3, _(L("Slicing process finished.")));
}
void PrintController::slice()
{
auto pri = global_progress_indicator();
if(!pri) pri = create_progress_indicator(100, L("Slicing"));
slice(pri);
}
void PrintController::slice_to_png()
{
using Pointf3 = Vec3d;
auto presetbundle = GUI::get_preset_bundle();
assert(presetbundle);
auto pt = presetbundle->printers.get_selected_preset().printer_technology();
if(pt != ptSLA) {
report_issue(IssueType::ERR, _("Printer technology is not SLA!"),
_("Error"));
return;
}
auto conf = presetbundle->full_config();
conf.validate();
auto exd = query_png_export_data(conf);
if(exd.zippath.empty()) return;
try {
print_->apply_config(conf);
print_->validate();
} catch(std::exception& e) {
report_issue(IssueType::ERR, e.what(), "Error");
return;
}
// TODO: copy the model and work with the copy only
bool correction = false;
if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
correction = true;
print_->invalidate_all_steps();
for(auto po : print_->objects) {
po->model_object()->scale(
Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
// Turn back the correction scaling on the model.
auto scale_back = [this, correction, exd]() {
if(correction) { // scale the model back
print_->invalidate_all_steps();
for(auto po : print_->objects) {
po->model_object()->scale(
Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
);
po->model_object()->invalidate_bounding_box();
po->reload_model_instances();
po->invalidate_all_steps();
}
}
};
auto print_bb = print_->bounding_box();
Vec2d punsc = unscale(print_bb.size());
// If the print does not fit into the print area we should cry about it.
if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
std::stringstream ss;
ss << _(L("Print will not fit and will be truncated!")) << "\n"
<< _(L("Width needed: ")) << px(punsc) << " mm\n"
<< _(L("Height needed: ")) << py(punsc) << " mm\n";
if(!report_issue(IssueType::WARN_Q, ss.str(), _(L("Warning")))) {
scale_back();
return;
}
}
// std::async(supports_asynch()? std::launch::async : std::launch::deferred,
// [this, exd, scale_back]()
// {
auto pri = create_progress_indicator(
200, _(L("Slicing to zipped png files...")));
try {
pri->update(0, _(L("Slicing...")));
slice(pri);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
scale_back();
return;
}
auto pbak = print_->progressindicator;
print_->progressindicator = pri;
try {
print_to<FilePrinterFormat::PNG>( *print_, exd.zippath,
exd.width_mm, exd.height_mm,
exd.width_px, exd.height_px,
exd.exp_time_s, exd.exp_time_first_s);
} catch (std::exception& e) {
pri->cancel();
report_issue(IssueType::ERR, e.what(), _(L("Exception occured")));
}
print_->progressindicator = pbak;
scale_back();
// });
}
void ProgressIndicator::message_fmt( void ProgressIndicator::message_fmt(
const string &fmtstr, ...) { const std::string &fmtstr, ...) {
std::stringstream ss; std::stringstream ss;
va_list args; va_list args;
va_start(args, fmtstr); va_start(args, fmtstr);
@ -433,80 +94,77 @@ const PrintConfig &PrintController::config() const
return print_->config; return print_->config;
} }
void AppController::arrange_model() void AppController::arrange_model()
{ {
using Coord = libnest2d::TCoord<libnest2d::PointImpl>; auto ftr = std::async(
supports_asynch()? std::launch::async : std::launch::deferred,
[this]()
{
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
if(arranging_.load()) return; unsigned count = 0;
for(auto obj : model_->objects) count += obj->instances.size();
// to prevent UI reentrancies auto pind = global_progress_indicator();
arranging_.store(true);
unsigned count = 0; float pmax = 1.0;
for(auto obj : model_->objects) count += obj->instances.size();
auto pind = global_progress_indicator(); if(pind) {
pmax = pind->max();
float pmax = 1.0; // Set the range of the progress to the object count
pind->max(count);
if(pind) { }
pmax = pind->max();
// Set the range of the progress to the object count auto dist = print_ctl()->config().min_object_distance();
pind->max(count);
pind->on_cancel([this](){ // Create the arranger config
arranging_.store(false); auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
});
auto& bedpoints = print_ctl()->config().bed_shape.values;
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints)
bed.append(Point::new_scale(v(0), v(1)));
if(pind) pind->update(0, L("Arranging objects..."));
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI
hint.type = arr::BedShapeType::WHO_KNOWS;
//FIXME merge error
/*
arr::arrange(*model_,
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, L("Arranging objects..."));
});
*/
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
L("Could not arrange model objects! "
"Some geometries may be invalid."),
L("Exception occurred"));
}
// Restore previous max value
if(pind) {
pind->max(pmax);
pind->update(0, L("Arranging done."));
}
});
while( ftr.wait_for(std::chrono::milliseconds(10))
!= std::future_status::ready) {
process_events();
} }
auto dist = print_ctl()->config().min_object_distance();
// Create the arranger config
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
auto& bedpoints = print_ctl()->config().bed_shape.values;
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints)
bed.append(Point::new_scale(v(0), v(1)));
if(pind) pind->update(0, _(L("Arranging objects...")));
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI
hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(*model_,
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[this, pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, L("Arranging objects..."));
process_events();
}, [this] () { return !arranging_.load(); });
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")),
_(L("Exception occurred")));
}
// Restore previous max value
if(pind) {
pind->max(pmax);
pind->update(0, arranging_.load() ? L("Arranging done.") :
L("Arranging canceled."));
pind->on_cancel(/*remove cancel function*/);
}
arranging_.store(false);
} }
} }

View File

@ -16,6 +16,7 @@ class Print;
class PrintObject; class PrintObject;
class PrintConfig; class PrintConfig;
class ProgressStatusBar; class ProgressStatusBar;
class DynamicPrintConfig;
/** /**
* @brief A boilerplate class for creating application logic. It should provide * @brief A boilerplate class for creating application logic. It should provide
@ -46,7 +47,7 @@ public:
AppControllerBoilerplate(); AppControllerBoilerplate();
~AppControllerBoilerplate(); ~AppControllerBoilerplate();
using Path = string; using Path = std::string;
using PathList = std::vector<Path>; using PathList = std::vector<Path>;
/// Common runtime issue types /// Common runtime issue types
@ -67,20 +68,20 @@ public:
* @return Returns a list of paths choosed by the user. * @return Returns a list of paths choosed by the user.
*/ */
PathList query_destination_paths( PathList query_destination_paths(
const string& title, const std::string& title,
const std::string& extensions) const; const std::string& extensions) const;
/** /**
* @brief Same as query_destination_paths but works for directories only. * @brief Same as query_destination_paths but works for directories only.
*/ */
PathList query_destination_dirs( PathList query_destination_dirs(
const string& title) const; const std::string& title) const;
/** /**
* @brief Same as query_destination_paths but returns only one path. * @brief Same as query_destination_paths but returns only one path.
*/ */
Path query_destination_path( Path query_destination_path(
const string& title, const std::string& title,
const std::string& extensions, const std::string& extensions,
const std::string& hint = "") const; const std::string& hint = "") const;
@ -95,11 +96,11 @@ public:
* title. * title.
*/ */
bool report_issue(IssueType issuetype, bool report_issue(IssueType issuetype,
const string& description, const std::string& description,
const string& brief); const std::string& brief);
bool report_issue(IssueType issuetype, bool report_issue(IssueType issuetype,
const string& description); const std::string& description);
/** /**
* @brief Return the global progress indicator for the current controller. * @brief Return the global progress indicator for the current controller.
@ -150,12 +151,12 @@ protected:
*/ */
ProgresIndicatorPtr create_progress_indicator( ProgresIndicatorPtr create_progress_indicator(
unsigned statenum, unsigned statenum,
const string& title, const std::string& title,
const string& firstmsg) const; const std::string& firstmsg) const;
ProgresIndicatorPtr create_progress_indicator( ProgresIndicatorPtr create_progress_indicator(
unsigned statenum, unsigned statenum,
const string& title) const; const std::string& title) const;
// This is a global progress indicator placeholder. In the Slic3r UI it can // This is a global progress indicator placeholder. In the Slic3r UI it can
// contain the progress indicator on the statusbar. // contain the progress indicator on the statusbar.
@ -167,43 +168,6 @@ protected:
*/ */
class PrintController: public AppControllerBoilerplate { class PrintController: public AppControllerBoilerplate {
Print *print_ = nullptr; Print *print_ = nullptr;
protected:
void make_skirt();
void make_brim();
void make_wipe_tower();
void make_perimeters(PrintObject *pobj);
void infill(PrintObject *pobj);
void gen_support_material(PrintObject *pobj);
// Data structure with the png export input data
struct PngExportData {
std::string zippath; // output zip file
unsigned long width_px = 1440; // resolution - rows
unsigned long height_px = 2560; // resolution columns
double width_mm = 68.0, height_mm = 120.0; // dimensions in mm
double exp_time_first_s = 35.0; // first exposure time
double exp_time_s = 8.0; // global exposure time
double corr_x = 1.0; // offsetting in x
double corr_y = 1.0; // offsetting in y
double corr_z = 1.0; // offsetting in y
};
// Should display a dialog with the input fields for printing to png
PngExportData query_png_export_data(const DynamicPrintConfig&);
// The previous export data, to pre-populate the dialog
PngExportData prev_expdata_;
/**
* @brief Slice one pront object.
* @param pobj The print object.
*/
void slice(PrintObject *pobj);
void slice(ProgresIndicatorPtr pri);
public: public:
// Must be public for perl to use it // Must be public for perl to use it
@ -218,15 +182,9 @@ public:
return PrintController::Ptr( new PrintController(print) ); return PrintController::Ptr( new PrintController(print) );
} }
/** //FIXME Vojtech: Merging error
* @brief Slice the loaded print scene. void slice() {}
*/ void slice_to_png() {}
void slice();
/**
* @brief Slice the print into zipped png files.
*/
void slice_to_png();
const PrintConfig& config() const; const PrintConfig& config() const;
}; };
@ -237,7 +195,6 @@ public:
class AppController: public AppControllerBoilerplate { class AppController: public AppControllerBoilerplate {
Model *model_ = nullptr; Model *model_ = nullptr;
PrintController::Ptr printctl; PrintController::Ptr printctl;
std::atomic<bool> arranging_;
public: public:
/** /**
@ -273,14 +230,15 @@ public:
* In perl we have a progress indicating status bar on the bottom of the * In perl we have a progress indicating status bar on the bottom of the
* window which is defined and created in perl. We can pass the ID-s of the * window which is defined and created in perl. We can pass the ID-s of the
* gauge and the statusbar id and make a wrapper implementation of the * gauge and the statusbar id and make a wrapper implementation of the
* IProgressIndicator interface so we can use this GUI widget from C++. * ProgressIndicator interface so we can use this GUI widget from C++.
* *
* This function should be called from perl. * This function should be called from perl.
* *
* @param gauge_id The ID of the gague widget of the status bar. * @param gauge_id The ID of the gague widget of the status bar.
* @param statusbar_id The ID of the status bar. * @param statusbar_id The ID of the status bar.
*/ */
void set_global_progress_indicator(ProgressStatusBar *prs); void set_global_progress_indicator(unsigned gauge_id,
unsigned statusbar_id);
void arrange_model(); void arrange_model();
}; };

View File

@ -4,7 +4,6 @@
#include <future> #include <future>
#include <slic3r/GUI/GUI.hpp> #include <slic3r/GUI/GUI.hpp>
#include <slic3r/GUI/ProgressStatusBar.hpp>
#include <wx/app.h> #include <wx/app.h>
#include <wx/filedlg.h> #include <wx/filedlg.h>
@ -28,16 +27,16 @@ bool AppControllerBoilerplate::supports_asynch() const
void AppControllerBoilerplate::process_events() void AppControllerBoilerplate::process_events()
{ {
wxYieldIfNeeded(); wxSafeYield();
} }
AppControllerBoilerplate::PathList AppControllerBoilerplate::PathList
AppControllerBoilerplate::query_destination_paths( AppControllerBoilerplate::query_destination_paths(
const string &title, const std::string &title,
const std::string &extensions) const const std::string &extensions) const
{ {
wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions); dlg.SetWildcard(extensions);
dlg.ShowModal(); dlg.ShowModal();
@ -53,11 +52,11 @@ AppControllerBoilerplate::query_destination_paths(
AppControllerBoilerplate::Path AppControllerBoilerplate::Path
AppControllerBoilerplate::query_destination_path( AppControllerBoilerplate::query_destination_path(
const string &title, const std::string &title,
const std::string &extensions, const std::string &extensions,
const std::string& hint) const const std::string& hint) const
{ {
wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
dlg.SetWildcard(extensions); dlg.SetWildcard(extensions);
dlg.SetFilename(hint); dlg.SetFilename(hint);
@ -72,8 +71,8 @@ AppControllerBoilerplate::query_destination_path(
} }
bool AppControllerBoilerplate::report_issue(IssueType issuetype, bool AppControllerBoilerplate::report_issue(IssueType issuetype,
const string &description, const std::string &description,
const string &brief) const std::string &brief)
{ {
auto icon = wxICON_INFORMATION; auto icon = wxICON_INFORMATION;
auto style = wxOK|wxCENTRE; auto style = wxOK|wxCENTRE;
@ -85,15 +84,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype,
case IssueType::FATAL: icon = wxICON_ERROR; case IssueType::FATAL: icon = wxICON_ERROR;
} }
auto ret = wxMessageBox(description, brief, icon | style); auto ret = wxMessageBox(_(description), _(brief), icon | style);
return ret != wxCANCEL; return ret != wxCANCEL;
} }
bool AppControllerBoilerplate::report_issue( bool AppControllerBoilerplate::report_issue(
AppControllerBoilerplate::IssueType issuetype, AppControllerBoilerplate::IssueType issuetype,
const string &description) const std::string &description)
{ {
return report_issue(issuetype, description, string()); return report_issue(issuetype, description, std::string());
} }
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
@ -137,8 +136,8 @@ public:
/// Get the mode of parallel operation. /// Get the mode of parallel operation.
inline bool asynch() const { return is_asynch_; } inline bool asynch() const { return is_asynch_; }
inline GuiProgressIndicator(int range, const string& title, inline GuiProgressIndicator(int range, const wxString& title,
const string& firstmsg) : const wxString& firstmsg) :
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
wxPD_APP_MODAL | wxPD_AUTO_HIDE), wxPD_APP_MODAL | wxPD_AUTO_HIDE),
message_(firstmsg), message_(firstmsg),
@ -152,11 +151,6 @@ public:
this, id_); this, id_);
} }
virtual void cancel() override {
update(max(), "Abort");
ProgressIndicator::cancel();
}
virtual void state(float val) override { virtual void state(float val) override {
state(static_cast<unsigned>(val)); state(static_cast<unsigned>(val));
} }
@ -171,26 +165,28 @@ public:
} else _state(st); } else _state(st);
} }
virtual void message(const string & msg) override { virtual void message(const std::string & msg) override {
message_ = msg; message_ = _(msg);
} }
virtual void messageFmt(const string& fmt, ...) { virtual void messageFmt(const std::string& fmt, ...) {
va_list arglist; va_list arglist;
va_start(arglist, fmt); va_start(arglist, fmt);
message_ = wxString::Format(wxString(fmt), arglist); message_ = wxString::Format(_(fmt), arglist);
va_end(arglist); va_end(arglist);
} }
virtual void title(const string & title) override { virtual void title(const std::string & title) override {
title_ = title; title_ = _(title);
} }
}; };
} }
AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator( AppControllerBoilerplate::create_progress_indicator(
unsigned statenum, const string& title, const string& firstmsg) const unsigned statenum,
const std::string& title,
const std::string& firstmsg) const
{ {
auto pri = auto pri =
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg); std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
@ -203,29 +199,39 @@ AppControllerBoilerplate::create_progress_indicator(
} }
AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::ProgresIndicatorPtr
AppControllerBoilerplate::create_progress_indicator(unsigned statenum, AppControllerBoilerplate::create_progress_indicator(
const string &title) const unsigned statenum, const std::string &title) const
{ {
return create_progress_indicator(statenum, title, string()); return create_progress_indicator(statenum, title, std::string());
} }
namespace { namespace {
// A wrapper progress indicator class around the statusbar created in perl.
class Wrapper: public ProgressIndicator, public wxEvtHandler { class Wrapper: public ProgressIndicator, public wxEvtHandler {
ProgressStatusBar *sbar_; wxGauge *gauge_;
wxStatusBar *stbar_;
using Base = ProgressIndicator; using Base = ProgressIndicator;
std::string message_; wxString message_;
AppControllerBoilerplate& ctl_; AppControllerBoilerplate& ctl_;
void showProgress(bool show = true) { void showProgress(bool show = true) {
sbar_->show_progress(show); gauge_->Show(show);
} }
void _state(unsigned st) { void _state(unsigned st) {
if( st <= ProgressIndicator::max() ) { if( st <= ProgressIndicator::max() ) {
Base::state(st); Base::state(st);
sbar_->set_status_text(message_);
sbar_->set_progress(st); if(!gauge_->IsShown()) showProgress(true);
stbar_->SetStatusText(message_);
if(static_cast<long>(st) == gauge_->GetRange()) {
gauge_->SetValue(0);
showProgress(false);
} else {
gauge_->SetValue(static_cast<int>(st));
}
} }
} }
@ -238,12 +244,12 @@ class Wrapper: public ProgressIndicator, public wxEvtHandler {
public: public:
inline Wrapper(ProgressStatusBar *sbar, inline Wrapper(wxGauge *gauge, wxStatusBar *stbar,
AppControllerBoilerplate& ctl): AppControllerBoilerplate& ctl):
sbar_(sbar), ctl_(ctl) gauge_(gauge), stbar_(stbar), ctl_(ctl)
{ {
Base::max(static_cast<float>(sbar_->get_range())); Base::max(static_cast<float>(gauge->GetRange()));
Base::states(static_cast<unsigned>(sbar_->get_range())); Base::states(static_cast<unsigned>(gauge->GetRange()));
Bind(PROGRESS_STATUS_UPDATE_EVENT, Bind(PROGRESS_STATUS_UPDATE_EVENT,
&Wrapper::_state, &Wrapper::_state,
@ -256,7 +262,7 @@ public:
virtual void max(float val) override { virtual void max(float val) override {
if(val > 1.0) { if(val > 1.0) {
sbar_->set_range(static_cast<int>(val)); gauge_->SetRange(static_cast<int>(val));
ProgressIndicator::max(val); ProgressIndicator::max(val);
} }
} }
@ -271,32 +277,31 @@ public:
} }
} }
virtual void message(const string & msg) override { virtual void message(const std::string & msg) override {
message_ = msg; message_ = _(msg);
} }
virtual void message_fmt(const string& fmt, ...) override { virtual void message_fmt(const std::string& fmt, ...) override {
va_list arglist; va_list arglist;
va_start(arglist, fmt); va_start(arglist, fmt);
message_ = wxString::Format(fmt, arglist); message_ = wxString::Format(_(fmt), arglist);
va_end(arglist); va_end(arglist);
} }
virtual void title(const string & /*title*/) override {} virtual void title(const std::string & /*title*/) override {}
virtual void on_cancel(CancelFn fn) override {
sbar_->set_cancel_callback(fn);
Base::on_cancel(fn);
}
}; };
} }
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb) void AppController::set_global_progress_indicator(
unsigned gid,
unsigned sid)
{ {
if(prsb) { wxGauge* gauge = dynamic_cast<wxGauge*>(wxWindow::FindWindowById(gid));
global_progress_indicator(std::make_shared<Wrapper>(prsb, *this)); wxStatusBar* sb = dynamic_cast<wxStatusBar*>(wxWindow::FindWindowById(sid));
if(gauge && sb) {
global_progressind_ = std::make_shared<Wrapper>(gauge, sb, *this);
} }
} }
} }

View File

@ -648,7 +648,7 @@ std::vector<int> GLVolumeCollection::load_object(
const ModelVolume *model_volume = model_object->volumes[volume_idx]; const ModelVolume *model_volume = model_object->volumes[volume_idx];
int extruder_id = -1; int extruder_id = -1;
if (!model_volume->modifier) if (model_volume->is_model_part())
{ {
extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
if (extruder_id == 0) if (extruder_id == 0)
@ -661,7 +661,16 @@ std::vector<int> GLVolumeCollection::load_object(
volumes_idx.push_back(int(this->volumes.size())); volumes_idx.push_back(int(this->volumes.size()));
float color[4]; float color[4];
memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
color[3] = model_volume->modifier ? 0.5f : 1.f; if (model_volume->is_support_blocker()) {
color[0] = 1.0f;
color[1] = 0.2f;
color[2] = 0.2f;
} else if (model_volume->is_support_enforcer()) {
color[0] = 0.2f;
color[1] = 0.2f;
color[2] = 1.0f;
}
color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
this->volumes.emplace_back(new GLVolume(color)); this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back(); GLVolume &v = *this->volumes.back();
if (use_VBOs) if (use_VBOs)
@ -675,15 +684,15 @@ std::vector<int> GLVolumeCollection::load_object(
v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx; v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx;
v.set_select_group_id(select_by); v.set_select_group_id(select_by);
v.set_drag_group_id(drag_by); v.set_drag_group_id(drag_by);
if (!model_volume->modifier) if (model_volume->is_model_part())
{ {
v.set_convex_hull(model_volume->get_convex_hull()); v.set_convex_hull(model_volume->get_convex_hull());
v.layer_height_texture = layer_height_texture; v.layer_height_texture = layer_height_texture;
if (extruder_id != -1) if (extruder_id != -1)
v.extruder_id = extruder_id; v.extruder_id = extruder_id;
} }
v.is_modifier = model_volume->modifier; v.is_modifier = ! model_volume->is_model_part();
v.shader_outside_printer_detection_enabled = !model_volume->modifier; v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
#if ENABLE_MODELINSTANCE_3D_OFFSET #if ENABLE_MODELINSTANCE_3D_OFFSET
v.set_offset(instance->get_offset()); v.set_offset(instance->get_offset());
#else #else

View File

@ -60,6 +60,14 @@ void AppConfig::set_defaults()
if (get("remember_output_path").empty()) if (get("remember_output_path").empty())
set("remember_output_path", "1"); set("remember_output_path", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
erase("", "main_frame_size");
erase("", "object_settings_maximized");
erase("", "object_settings_pos");
erase("", "object_settings_size");
} }
void AppConfig::load() void AppConfig::load()

View File

@ -72,6 +72,14 @@ public:
bool has(const std::string &key) const bool has(const std::string &key) const
{ return this->has("", key); } { return this->has("", key); }
void erase(const std::string &section, const std::string &key)
{
auto it = m_storage.find(section);
if (it != m_storage.end()) {
it->second.erase(key);
}
}
void clear_section(const std::string &section) void clear_section(const std::string &section)
{ m_storage[section].clear(); } { m_storage[section].clear(); }

View File

@ -409,11 +409,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) :
void PageFirmware::apply_custom_config(DynamicPrintConfig &config) void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{ {
ConfigOptionEnum<GCodeFlavor> opt;
auto sel = gcode_picker->GetSelection(); auto sel = gcode_picker->GetSelection();
if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
config.set_key_value("gcode_flavor", &opt); auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
config.set_key_value("gcode_flavor", opt);
} }
} }
@ -871,10 +870,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
// If the screen is smaller, resize wizrad to match, which will enable scrollbars. // If the screen is smaller, resize wizrad to match, which will enable scrollbars.
auto wizard_size = GetSize(); auto wizard_size = GetSize();
unsigned width, height; unsigned width, height;
GUI::get_current_screen_size(width, height); if (GUI::get_current_screen_size(this, width, height)) {
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
SetMinSize(wizard_size); SetMinSize(wizard_size);
}
Fit(); Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });

View File

@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
auto ports = Utils::scan_serial_ports_extended(); auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
}), ports.end()); }), ports.end());
if (ports.size() == 1) { if (ports.size() == 1) {
@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
void FirmwareDialog::priv::lookup_port_mmu() void FirmwareDialog::priv::lookup_port_mmu()
{ {
static const auto msg_not_found =
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ...";
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
auto ports = Utils::scan_serial_ports_extended(); auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA && return port.id_vendor != USB_VID_PRUSA ||
port.id_product != USB_PID_MMU_BOOT && port.id_product != USB_PID_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP; port.id_product != USB_PID_MMU_APP;
}), ports.end()); }), ports.end());
if (ports.size() == 0) { if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
queue_status(_(L(
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ..."
)));
wait_for_mmu_bootloader(30); wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) { } else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
@ -417,6 +416,13 @@ void FirmwareDialog::priv::lookup_port_mmu()
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]); mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10); wait_for_mmu_bootloader(10);
if (! port) {
// The device in bootloader mode was not found, inform the user and wait some more...
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
}
} else { } else {
port = ports[0]; port = ports[0];
} }
@ -702,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
panel->SetSizer(vsizer); panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
"Hex files (*.hex)|*.hex|All files|*.*");
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY); p->port_picker = new wxComboBox(panel, wxID_ANY);

View File

@ -7,6 +7,7 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
@ -1226,12 +1227,63 @@ int get_export_option(wxFileDialog* dlg)
} }
void get_current_screen_size(unsigned &width, unsigned &height) bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
{ {
wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame)); const auto idx = wxDisplay::GetFromWindow(window);
if (idx == wxNOT_FOUND) {
return false;
}
wxDisplay display(idx);
const auto disp_size = display.GetClientArea(); const auto disp_size = display.GetClientArea();
width = disp_size.GetWidth(); width = disp_size.GetWidth();
height = disp_size.GetHeight(); height = disp_size.GetHeight();
return true;
}
void save_window_size(wxTopLevelWindow *window, const std::string &name)
{
const wxSize size = window->GetSize();
const wxPoint pos = window->GetPosition();
const auto maximized = window->IsMaximized() ? "1" : "0";
g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str());
g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized);
}
void restore_window_size(wxTopLevelWindow *window, const std::string &name)
{
// XXX: This still doesn't behave nicely in some situations (mostly on Linux).
// The problem is that it's hard to obtain window position with respect to screen geometry reliably
// from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which
// it's actually visible. I suspect this has something to do with window initialization (maybe we
// restore window geometry too early), but haven't yet found a workaround.
const auto display_idx = wxDisplay::GetFromWindow(window);
if (display_idx == wxNOT_FOUND) { return; }
const auto display = wxDisplay(display_idx).GetClientArea();
std::vector<std::string> pair;
try {
const auto key_size = (boost::format("window_%1%_size") % name).str();
if (g_AppConfig->has(key_size)) {
if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) {
auto width = boost::lexical_cast<int>(pair[0]);
auto height = boost::lexical_cast<int>(pair[1]);
window->SetSize(width, height);
}
}
} catch(const boost::bad_lexical_cast &) {}
// Maximizing should be the last thing to do.
// This ensure the size and position are sane when the user un-maximizes the window.
const auto key_maximized = (boost::format("window_%1%_maximized") % name).str();
if (g_AppConfig->get(key_maximized) == "1") {
window->Maximize(true);
}
} }
void enable_action_buttons(bool enable) void enable_action_buttons(bool enable)

View File

@ -26,6 +26,7 @@ class wxButton;
class wxFileDialog; class wxFileDialog;
class wxStaticBitmap; class wxStaticBitmap;
class wxFont; class wxFont;
class wxTopLevelWindow;
namespace Slic3r { namespace Slic3r {
@ -223,7 +224,12 @@ void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg); int get_export_option(wxFileDialog* dlg);
// Returns the dimensions of the screen on which the main frame is displayed // Returns the dimensions of the screen on which the main frame is displayed
void get_current_screen_size(unsigned &width, unsigned &height); bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
// Save window size and maximized status into AppConfig
void save_window_size(wxTopLevelWindow *window, const std::string &name);
// Restore the above
void restore_window_size(wxTopLevelWindow *window, const std::string &name);
// Update buttons view according to enable/disable // Update buttons view according to enable/disable
void enable_action_buttons(bool enable); void enable_action_buttons(bool enable);

View File

@ -1225,7 +1225,7 @@ void load_part( ModelObject* model_object,
for ( auto object : model.objects) { for ( auto object : model.objects) {
for (auto volume : object->volumes) { for (auto volume : object->volumes) {
auto new_volume = model_object->add_volume(*volume); auto new_volume = model_object->add_volume(*volume);
new_volume->modifier = is_modifier; new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
boost::filesystem::path(input_file).filename().string(); boost::filesystem::path(input_file).filename().string();
new_volume->name = boost::filesystem::path(input_file).filename().string(); new_volume->name = boost::filesystem::path(input_file).filename().string();
@ -1283,7 +1283,8 @@ void load_lambda( ModelObject* model_object,
mesh.repair(); mesh.repair();
auto new_volume = model_object->add_volume(mesh); auto new_volume = model_object->add_volume(mesh);
new_volume->modifier = is_modifier; new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
new_volume->name = name; new_volume->name = name;
// set a default extruder value, since user can't add it manually // set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
@ -1320,7 +1321,8 @@ void load_lambda(const std::string& type_name)
mesh.repair(); mesh.repair();
auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh); auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh);
new_volume->modifier = true; new_volume->set_type(ModelVolume::PARAMETER_MODIFIER);
new_volume->name = name; new_volume->name = name;
// set a default extruder value, since user can't add it manually // set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
@ -1385,9 +1387,9 @@ bool remove_subobject_from_object(const int volume_id)
// if user is deleting the last solid part, throw error // if user is deleting the last solid part, throw error
int solid_cnt = 0; int solid_cnt = 0;
for (auto vol : (*m_objects)[m_selected_object_id]->volumes) for (auto vol : (*m_objects)[m_selected_object_id]->volumes)
if (!vol->modifier) if (vol->is_model_part())
++solid_cnt; ++solid_cnt;
if (!volume->modifier && solid_cnt == 1) { if (volume->is_model_part() && solid_cnt == 1) {
Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object.")));
return false; return false;
} }
@ -1477,7 +1479,7 @@ void on_btn_split(const bool split_part)
for (auto id = 0; id < model_object->volumes.size(); id++) for (auto id = 0; id < model_object->volumes.size(); id++)
m_objects_model->AddChild(parent, model_object->volumes[id]->name, m_objects_model->AddChild(parent, model_object->volumes[id]->name,
model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh, model_object->volumes[id]->is_modifier() ? m_icon_modifiermesh : m_icon_solidmesh,
model_object->volumes[id]->config.has("extruder") ? model_object->volumes[id]->config.has("extruder") ?
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0, model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
false); false);

View File

@ -295,7 +295,7 @@ const std::vector<std::string>& Preset::print_options()
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height",
"min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
"support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",

View File

@ -880,6 +880,7 @@ void TabPrint::build()
page = add_options_page(_(L("Support material")), "building.png"); page = add_options_page(_(L("Support material")), "building.png");
optgroup = page->new_optgroup(_(L("Support material"))); optgroup = page->new_optgroup(_(L("Support material")));
optgroup->append_single_option_line("support_material"); optgroup->append_single_option_line("support_material");
optgroup->append_single_option_line("support_material_auto");
optgroup->append_single_option_line("support_material_threshold"); optgroup->append_single_option_line("support_material_threshold");
optgroup->append_single_option_line("support_material_enforce_layers"); optgroup->append_single_option_line("support_material_enforce_layers");
@ -1219,13 +1220,15 @@ void TabPrint::update()
bool have_raft = m_config->opt_int("raft_layers") > 0; bool have_raft = m_config->opt_int("raft_layers") > 0;
bool have_support_material = m_config->opt_bool("support_material") || have_raft; bool have_support_material = m_config->opt_bool("support_material") || have_raft;
bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto");
bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0;
bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0;
for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath", for (auto el : {"support_material_pattern", "support_material_with_sheath",
"support_material_spacing", "support_material_angle", "support_material_interface_layers", "support_material_spacing", "support_material_angle", "support_material_interface_layers",
"dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
"support_material_xy_spacing" }) "support_material_xy_spacing" })
get_field(el)->toggle(have_support_material); get_field(el)->toggle(have_support_material);
get_field("support_material_threshold")->toggle(have_support_material_auto);
for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder",
"support_material_interface_speed", "support_material_interface_contact_loops" }) "support_material_interface_speed", "support_material_interface_contact_loops" })

View File

@ -3,7 +3,6 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include "Strings.hpp"
namespace Slic3r { namespace Slic3r {
@ -43,13 +42,13 @@ public:
} }
/// Message shown on the next status update. /// Message shown on the next status update.
virtual void message(const string&) = 0; virtual void message(const std::string&) = 0;
/// Title of the operation. /// Title of the operation.
virtual void title(const string&) = 0; virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprintf. /// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const string& fmt, ...); virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible. /// Set up a cancel callback for the operation if feasible.
virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; } virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; }
@ -61,7 +60,7 @@ public:
virtual void cancel() { cancelfunc_(); } virtual void cancel() { cancelfunc_(); }
/// Convenience function to call message and status update in one function. /// Convenience function to call message and status update in one function.
void update(float st, const string& msg) { void update(float st, const std::string& msg) {
message(msg); state(st); message(msg); state(st);
} }
}; };

View File

@ -1,10 +0,0 @@
#ifndef STRINGS_HPP
#define STRINGS_HPP
#include "GUI/GUI.hpp"
namespace Slic3r {
using string = wxString;
}
#endif // STRINGS_HPP

View File

@ -231,7 +231,12 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
spi.port = path; spi.port = path;
#ifdef __linux__ #ifdef __linux__
auto friendly_name = sysfs_tty_prop(name, "product"); auto friendly_name = sysfs_tty_prop(name, "product");
spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; if (friendly_name) {
spi.is_printer = looks_like_printer(*friendly_name);
spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
} else {
spi.friendly_name = path;
}
auto vid = sysfs_tty_prop_hex(name, "idVendor"); auto vid = sysfs_tty_prop_hex(name, "idVendor");
auto pid = sysfs_tty_prop_hex(name, "idProduct"); auto pid = sysfs_tty_prop_hex(name, "idProduct");
if (vid && pid) { if (vid && pid) {

View File

@ -23,7 +23,7 @@
PrintController *print_ctl(); PrintController *print_ctl();
void set_model(Model *model); void set_model(Model *model);
void set_print(Print *print); void set_print(Print *print);
void set_global_progress_indicator(ProgressStatusBar *prs); void set_global_progress_indicator(unsigned gauge_id, unsigned statusbar_id);
void arrange_model(); void arrange_model();
}; };

View File

@ -186,3 +186,8 @@ void reset_double_slider()
void enable_action_buttons(bool enable) void enable_action_buttons(bool enable)
%code%{ Slic3r::GUI::enable_action_buttons(enable); %}; %code%{ Slic3r::GUI::enable_action_buttons(enable); %};
void save_window_size(SV *window, std::string name)
%code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};
void restore_window_size(SV *window, std::string name)
%code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %};

View File

@ -17,8 +17,6 @@
%code%{ RETVAL = &THIS->thin_fills; %}; %code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces() Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %}; %code%{ RETVAL = &THIS->fill_surfaces; %};
Ref<SurfaceCollection> perimeter_surfaces()
%code%{ RETVAL = &THIS->perimeter_surfaces; %};
Polygons bridged() Polygons bridged()
%code%{ RETVAL = THIS->bridged; %}; %code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges() Ref<PolylineCollection> unsupported_bridge_edges()

View File

@ -340,9 +340,19 @@ ModelMaterial::attributes()
%code%{ RETVAL = &THIS->mesh; %}; %code%{ RETVAL = &THIS->mesh; %};
bool modifier() bool modifier()
%code%{ RETVAL = THIS->modifier; %}; %code%{ RETVAL = THIS->is_modifier(); %};
void set_modifier(bool modifier) void set_modifier(bool modifier)
%code%{ THIS->modifier = modifier; %}; %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %};
bool model_part()
%code%{ RETVAL = THIS->is_model_part(); %};
bool support_enforcer()
%code%{ RETVAL = THIS->is_support_enforcer(); %};
void set_support_enforcer()
%code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %};
bool support_blocker()
%code%{ RETVAL = THIS->is_support_blocker(); %};
void set_support_blocker()
%code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %};
size_t split(unsigned int max_extruders); size_t split(unsigned int max_extruders);

View File

@ -269,5 +269,35 @@ Print::total_cost(...)
RETVAL = THIS->total_cost; RETVAL = THIS->total_cost;
OUTPUT: OUTPUT:
RETVAL RETVAL
double
Print::total_wipe_tower_cost(...)
CODE:
if (items > 1) {
THIS->total_wipe_tower_cost = (double)SvNV(ST(1));
}
RETVAL = THIS->total_wipe_tower_cost;
OUTPUT:
RETVAL
double
Print::total_wipe_tower_filament(...)
CODE:
if (items > 1) {
THIS->total_wipe_tower_filament = (double)SvNV(ST(1));
}
RETVAL = THIS->total_wipe_tower_filament;
OUTPUT:
RETVAL
int
Print::m_wipe_tower_number_of_toolchanges(...)
CODE:
if (items > 1) {
THIS->m_wipe_tower_number_of_toolchanges = (double)SvNV(ST(1));
}
RETVAL = THIS->m_wipe_tower_number_of_toolchanges;
OUTPUT:
RETVAL
%} %}
}; };

View File

@ -48,6 +48,11 @@ trace(level, message)
CODE: CODE:
Slic3r::trace(level, message); Slic3r::trace(level, message);
void
disable_multi_threading()
CODE:
Slic3r::disable_multi_threading();
void void
set_var_dir(dir) set_var_dir(dir)
char *dir; char *dir;