Merge branch 'dev' into lm_sla_supports_ui
This commit is contained in:
commit
3957c5bd8e
70 changed files with 1711 additions and 1163 deletions
|
@ -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;
|
||||
|
|
|
@ -74,7 +74,8 @@ sub new {
|
|||
$self->{statusbar}->Embed;
|
||||
$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++
|
||||
$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_print($self->{plater}->{print});
|
||||
|
@ -92,7 +93,7 @@ sub new {
|
|||
$self->Fit;
|
||||
$self->SetMinSize([760, 490]);
|
||||
$self->SetSize($self->GetMinSize);
|
||||
wxTheApp->restore_window_pos($self, "main_frame");
|
||||
Slic3r::GUI::restore_window_size($self, "main_frame");
|
||||
$self->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ sub new {
|
|||
return;
|
||||
}
|
||||
# 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,
|
||||
# but in rare cases it may not have been called yet.
|
||||
wxTheApp->{app_config}->save;
|
||||
|
|
|
@ -1804,20 +1804,37 @@ sub print_info_box_show {
|
|||
$grid_sizer->AddGrowableCol(1, 1);
|
||||
$grid_sizer->AddGrowableCol(3, 1);
|
||||
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
|
||||
my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0;
|
||||
my @info = (
|
||||
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³)")
|
||||
=> sprintf("%.2f" , $self->{print}->total_extruded_volume),
|
||||
L("Used Filament (g)"),
|
||||
=> sprintf("%.2f" , $self->{print}->total_weight),
|
||||
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)")
|
||||
=> $self->{print}->estimated_normal_print_time,
|
||||
L("Estimated printing time (silent mode)")
|
||||
=> $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) {
|
||||
my $value = shift @info;
|
||||
next if $value eq "N/A";
|
||||
|
|
|
@ -238,7 +238,7 @@ sub _update {
|
|||
my @expolygons = ();
|
||||
foreach my $volume (@{$self->{model_object}->volumes}) {
|
||||
next if !$volume->mesh;
|
||||
next if $volume->modifier;
|
||||
next if !$volume->model_part;
|
||||
my $expp = $volume->mesh->slice([ $z_cut ])->[0];
|
||||
push @expolygons, @$expp;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ use base 'Wx::Panel';
|
|||
use constant ICON_OBJECT => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
use constant ICON_SUPPORT_ENFORCER => 3;
|
||||
use constant ICON_SUPPORT_BLOCKER => 4;
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, %params) = @_;
|
||||
|
@ -35,7 +37,7 @@ sub new {
|
|||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
|
||||
|
||||
# create TreeCtrl
|
||||
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
|
||||
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
|
||||
|
@ -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("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("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);
|
||||
$tree->SetPlData($rootId, { type => 'object' });
|
||||
|
@ -89,7 +93,14 @@ sub new {
|
|||
$self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
# 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);
|
||||
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
|
||||
|
||||
|
@ -225,8 +236,11 @@ sub reload_tree {
|
|||
my $selectedId = $rootId;
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
|
||||
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
|
||||
my $icon =
|
||||
$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);
|
||||
if ($volume_id == $selected_volume_idx) {
|
||||
$selectedId = $itemId;
|
||||
|
@ -288,6 +302,8 @@ sub selection_changed {
|
|||
|
||||
if (my $itemData = $self->get_selection) {
|
||||
my ($config, @opt_keys);
|
||||
my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT;
|
||||
my $support = 0;
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
# select volume in 3D preview
|
||||
if ($self->{canvas}) {
|
||||
|
@ -301,16 +317,24 @@ sub selection_changed {
|
|||
# attach volume config to settings panel
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
|
||||
if ($volume->modifier) {
|
||||
if (! $volume->model_part) {
|
||||
$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 {
|
||||
$type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART;
|
||||
$self->{optgroup_movers}->disable;
|
||||
}
|
||||
$config = $volume->config;
|
||||
$self->{staticbox}->SetLabel('Part Settings');
|
||||
|
||||
# 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') {
|
||||
# select nothing in 3D preview
|
||||
|
||||
|
@ -323,33 +347,54 @@ sub selection_changed {
|
|||
# get default values
|
||||
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') {
|
||||
$config->set_ifndef('wipe_into_objects', 0);
|
||||
$config->set_ifndef('wipe_into_infill', 0);
|
||||
}
|
||||
|
||||
# append default extruder
|
||||
push @opt_keys, 'extruder';
|
||||
$default_config->set('extruder', 0);
|
||||
$config->set_ifndef('extruder', 0);
|
||||
if (! $support) {
|
||||
push @opt_keys, 'extruder';
|
||||
$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_config($config);
|
||||
$self->{settings_panel}->set_opt_keys(\@opt_keys);
|
||||
|
||||
# disable minus icon to remove the settings
|
||||
if ($itemData->{type} eq 'object') {
|
||||
$self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]);
|
||||
} else {
|
||||
$self->{settings_panel}->set_fixed_options([qw(extruder)]);
|
||||
}
|
||||
|
||||
my $fixed_options =
|
||||
($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] :
|
||||
$support ? [] : [qw(extruder)];
|
||||
$self->{settings_panel}->set_fixed_options($fixed_options);
|
||||
$self->{settings_panel}->enable;
|
||||
}
|
||||
|
||||
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 {
|
||||
my ($self, $is_modifier) = @_;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ sub new {
|
|||
$self->{layers}->Closing;
|
||||
|
||||
# save window size
|
||||
wxTheApp->save_window_pos($self, "object_settings");
|
||||
Slic3r::GUI::save_window_size($self, "object_settings");
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->{parts}->Destroy;
|
||||
|
@ -49,7 +49,7 @@ sub new {
|
|||
|
||||
$self->Layout;
|
||||
|
||||
wxTheApp->restore_window_pos($self, "object_settings");
|
||||
Slic3r::GUI::restore_window_size($self, "object_settings");
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
|
|
@ -7,15 +7,20 @@ use warnings;
|
|||
use utf8;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG
|
||||
wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU);
|
||||
use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
use constant ICON_MATERIAL => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
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 = (
|
||||
'Advanced' => 'wand.png',
|
||||
'Extruders' => 'funnel.png',
|
||||
|
@ -36,13 +41,14 @@ sub new {
|
|||
$self->{config} = Slic3r::Config->new;
|
||||
# On change callback.
|
||||
$self->{on_change} = $params{on_change};
|
||||
$self->{type} = TYPE_OBJECT;
|
||||
$self->{fixed_options} = {};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
|
||||
|
||||
|
||||
# option selector
|
||||
{
|
||||
# create the button
|
||||
|
@ -110,6 +116,16 @@ sub set_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 {
|
||||
my ($self, $opt_keys) = @_;
|
||||
$self->{fixed_options} = { map {$_ => 1} @$opt_keys };
|
||||
|
@ -121,12 +137,28 @@ sub update_optgroup {
|
|||
|
||||
$self->{options_sizer}->Clear(1);
|
||||
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 = ();
|
||||
foreach my $opt_key (@{$self->{config}->get_keys}) {
|
||||
my $category = $Slic3r::Config::Options->{$opt_key}{category};
|
||||
$categories{$category} ||= [];
|
||||
push @{$categories{$category}}, $opt_key;
|
||||
if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) {
|
||||
foreach my $opt_key (@{$self->{config}->get_keys}) {
|
||||
my $category = $Slic3r::Config::Options->{$opt_key}{category};
|
||||
$categories{$category} ||= [];
|
||||
push @{$categories{$category}}, $opt_key;
|
||||
}
|
||||
}
|
||||
foreach my $category (sort keys %categories) {
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
|
|
BIN
resources/icons/support_blocker.png
Normal file
BIN
resources/icons/support_blocker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 656 B |
BIN
resources/icons/support_enforcer.png
Normal file
BIN
resources/icons/support_enforcer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 509 B |
|
@ -288,7 +288,6 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/AppController.hpp
|
||||
${LIBDIR}/slic3r/AppController.cpp
|
||||
${LIBDIR}/slic3r/AppControllerWx.cpp
|
||||
${LIBDIR}/slic3r/Strings.hpp
|
||||
)
|
||||
|
||||
add_library(admesh STATIC
|
||||
|
@ -565,7 +564,7 @@ if (WIN32)
|
|||
endif ()
|
||||
|
||||
# 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
|
||||
COMPILE_FLAGS "/Zi"
|
||||
LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"
|
||||
|
|
|
@ -1225,8 +1225,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo
|
|||
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;
|
||||
// 0) Prepare a binary grid.
|
||||
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);
|
||||
// Fill in empty cells, which have a left / right neighbor filled.
|
||||
// Fill in empty cells, which have the top / bottom neighbor filled.
|
||||
{
|
||||
if (fill_holes) {
|
||||
std::vector<char> cell_inside2(cell_inside);
|
||||
for (int r = 1; r + 1 < int(cell_rows); ++ r) {
|
||||
for (int c = 1; c + 1 < int(cell_cols); ++ c) {
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
const size_t cols() const { return m_cols; }
|
||||
|
||||
// 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:
|
||||
struct Cell {
|
||||
|
|
|
@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point ¢er)
|
|||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
ExPolygonCollection::contains(const T &item) const
|
||||
bool ExPolygonCollection::contains(const T &item) const
|
||||
{
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
if (it->contains(item)) return true;
|
||||
}
|
||||
for (const ExPolygon &poly : this->expolygons)
|
||||
if (poly.contains(item))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template bool ExPolygonCollection::contains<Point>(const Point &item) const;
|
||||
|
|
|
@ -91,6 +91,8 @@ public:
|
|||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
virtual double min_mm3_per_mm() 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 total_volume() const = 0;
|
||||
};
|
||||
|
@ -123,8 +125,11 @@ public:
|
|||
|
||||
ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
|
||||
void reverse() { this->polyline.reverse(); }
|
||||
Point first_point() const { return this->polyline.points.front(); }
|
||||
Point last_point() const { return this->polyline.points.back(); }
|
||||
Point first_point() const override { return this->polyline.points.front(); }
|
||||
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.
|
||||
// Currently not used.
|
||||
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
|
@ -133,8 +138,8 @@ public:
|
|||
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
virtual double length() const;
|
||||
virtual ExtrusionRole role() const { return m_role; }
|
||||
double length() const override;
|
||||
ExtrusionRole role() const override { return m_role; }
|
||||
// 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.
|
||||
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.
|
||||
double min_mm3_per_mm() const { return this->mm3_per_mm; }
|
||||
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:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
@ -178,10 +184,10 @@ public:
|
|||
bool can_reverse() const { return true; }
|
||||
ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
|
||||
void reverse();
|
||||
Point first_point() const { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const { return this->paths.back().polyline.points.back(); }
|
||||
virtual double length() const;
|
||||
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { return this->paths.back().polyline.points.back(); }
|
||||
double length() const override;
|
||||
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.
|
||||
// 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;
|
||||
|
@ -196,7 +202,8 @@ public:
|
|||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() 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.
|
||||
|
@ -218,18 +225,18 @@ public:
|
|||
bool make_clockwise();
|
||||
bool make_counter_clockwise();
|
||||
void reverse();
|
||||
Point first_point() const { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
|
||||
Polygon polygon() const;
|
||||
virtual double length() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point);
|
||||
void split_at(const Point &point, bool prefer_non_overhang);
|
||||
void clip_end(double distance, ExtrusionPaths* paths) const;
|
||||
// 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.
|
||||
bool has_overhang_point(const Point &point) const;
|
||||
virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); }
|
||||
ExtrusionLoopRole loop_role() const { return m_loop_role; }
|
||||
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().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.
|
||||
// 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;
|
||||
|
@ -244,7 +251,8 @@ public:
|
|||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const;
|
||||
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:
|
||||
ExtrusionLoopRole m_loop_role;
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
explicit operator ExtrusionPaths() const;
|
||||
|
||||
bool is_collection() const { return true; };
|
||||
virtual ExtrusionRole role() const {
|
||||
ExtrusionRole role() const override {
|
||||
ExtrusionRole out = erNone;
|
||||
for (const ExtrusionEntity *ee : entities) {
|
||||
ExtrusionRole er = ee->role();
|
||||
|
@ -71,11 +71,11 @@ public:
|
|||
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.
|
||||
// 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.
|
||||
// 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.
|
||||
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 out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
|
@ -84,14 +84,20 @@ public:
|
|||
void flatten(ExtrusionEntityCollection* retval) const;
|
||||
ExtrusionEntityCollection flatten() 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.
|
||||
Polyline as_polyline() const {
|
||||
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
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");
|
||||
return 0.;
|
||||
}
|
||||
|
|
|
@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single(
|
|||
Polylines paths;
|
||||
{
|
||||
Polylines p;
|
||||
for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it)
|
||||
p.push_back((Polyline)(*it));
|
||||
for (Polygon &poly : polygons)
|
||||
p.emplace_back(poly.points);
|
||||
paths = intersection_pl(p, to_polygons(expolygon));
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
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),
|
||||
false);
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
||||
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,
|
||||
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)),
|
||||
false);
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
||||
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.
|
||||
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),
|
||||
false);
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume";
|
|||
|
||||
const char* NAME_KEY = "name";
|
||||
const char* MODIFIER_KEY = "modifier";
|
||||
const char* VOLUME_TYPE_KEY = "volume_type";
|
||||
|
||||
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
|
||||
const char* VALID_OBJECT_TYPES[] =
|
||||
|
@ -1442,7 +1443,9 @@ namespace Slic3r {
|
|||
if (metadata.key == NAME_KEY)
|
||||
volume->name = metadata.value;
|
||||
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
|
||||
volume->config.set_deserialize(metadata.key, metadata.value);
|
||||
}
|
||||
|
@ -1957,9 +1960,12 @@ namespace Slic3r {
|
|||
if (!volume->name.empty())
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
|
||||
|
||||
// stores volume's modifier field
|
||||
if (volume->modifier)
|
||||
// stores volume's modifier field (legacy, to support old slicers)
|
||||
if (volume->is_modifier())
|
||||
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
|
||||
for (const std::string& key : volume->config.keys())
|
||||
|
|
|
@ -495,9 +495,14 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
p = end + 1;
|
||||
}
|
||||
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) {
|
||||
// Is this volume a modifier volume?
|
||||
m_volume->modifier = atoi(m_value[1].c_str()) == 1;
|
||||
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
|
||||
if (strcmp(opt_key, "modifier") == 0) {
|
||||
// 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) {
|
||||
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";
|
||||
if (!volume->name.empty())
|
||||
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.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
|
||||
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
|
||||
stream << " <triangle>\n";
|
||||
for (int j = 0; j < 3; ++j)
|
||||
|
|
|
@ -276,7 +276,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
|
|||
}
|
||||
|
||||
|
||||
|
||||
std::string WipeTowerIntegration::prime(GCode &gcodegen)
|
||||
{
|
||||
assert(m_layer_idx == 0);
|
||||
|
@ -967,17 +966,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
|
||||
// Get filament stats.
|
||||
print.filament_stats.clear();
|
||||
print.total_used_filament = 0.;
|
||||
print.total_extruded_volume = 0.;
|
||||
print.total_weight = 0.;
|
||||
print.total_cost = 0.;
|
||||
print.total_used_filament = 0.;
|
||||
print.total_extruded_volume = 0.;
|
||||
print.total_weight = 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_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
|
||||
for (const Extruder &extruder : m_writer.extruders()) {
|
||||
double used_filament = extruder.used_filament();
|
||||
double extruded_volume = extruder.extruded_volume();
|
||||
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() + (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_cost = filament_weight * extruder.filament_cost() * 0.001;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
print.total_used_filament = print.total_used_filament + used_filament;
|
||||
print.total_extruded_volume = print.total_extruded_volume + extruded_volume;
|
||||
print.total_used_filament += used_filament;
|
||||
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, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
|
||||
|
|
|
@ -98,6 +98,7 @@ public:
|
|||
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 finalize(GCode &gcodegen);
|
||||
std::vector<float> used_filament_length() const;
|
||||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
|
|
|
@ -154,6 +154,12 @@ public:
|
|||
// 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.
|
||||
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
|
||||
|
|
|
@ -111,9 +111,10 @@ public:
|
|||
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); }
|
||||
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.
|
||||
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))
|
||||
// Neither extrusion nor a travel move.
|
||||
|
@ -122,6 +123,8 @@ public:
|
|||
float dx = x - m_current_pos.x;
|
||||
float dy = y - m_current_pos.y;
|
||||
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
|
||||
|
@ -162,8 +165,8 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f)
|
||||
{ return extrude_explicit(dest.x, dest.y, e, 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, record_length); }
|
||||
|
||||
// Travel to a new XY position. f=0 means use the current value.
|
||||
Writer& travel(float x, float y, float f = 0.f)
|
||||
|
@ -177,7 +180,7 @@ public:
|
|||
{
|
||||
float dx = x - m_current_pos.x;
|
||||
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)
|
||||
|
@ -259,8 +262,8 @@ public:
|
|||
// extrude quickly amount e to x2 with feed 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(x2, m_current_pos.y, e);
|
||||
extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
|
||||
extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -404,6 +407,7 @@ private:
|
|||
float m_last_fan_speed = 0.f;
|
||||
int current_temp = -1;
|
||||
const float m_default_analyzer_line_width;
|
||||
float m_used_filament_length = 0.f;
|
||||
|
||||
std::string set_format_X(float x)
|
||||
{
|
||||
|
@ -525,6 +529,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
|||
++ 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.
|
||||
writer.set_extruder_trimpot(550)
|
||||
.feedrate(6000)
|
||||
|
@ -537,6 +544,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
|||
// so that tool_change() will know to extrude the wipe tower brim:
|
||||
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;
|
||||
result.priming = true;
|
||||
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);
|
||||
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.
|
||||
++ m_num_tool_changes;
|
||||
} else
|
||||
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;
|
||||
|
||||
if (last_change_in_layer) {// draw perimeter line
|
||||
|
@ -632,6 +642,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
";------------------\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;
|
||||
result.priming = false;
|
||||
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
|
||||
|
||||
// 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;
|
||||
result.priming = false;
|
||||
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)
|
||||
.travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
|
||||
.resume_preview();
|
||||
|
||||
if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait.
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // 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);
|
||||
m_old_temperature = new_temperature;
|
||||
}
|
||||
|
@ -849,6 +866,9 @@ void WipeTowerPrusaMM::toolchange_Change(
|
|||
const unsigned int new_tool,
|
||||
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.
|
||||
int speed_override;
|
||||
switch (new_material) {
|
||||
|
@ -911,7 +931,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
|
|||
const float& xl = cleaning_box.ld.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
|
||||
// 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.
|
||||
|
@ -926,7 +945,6 @@ void WipeTowerPrusaMM::toolchange_Wipe(
|
|||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
|
||||
// now the wiping itself:
|
||||
for (int i = 0; true; ++i) {
|
||||
if (i!=0) {
|
||||
|
@ -935,7 +953,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
|
|||
else if (wipe_speed < 2210.f) wipe_speed = 4200.f;
|
||||
else wipe_speed = std::min(4800.f, wipe_speed + 50.f);
|
||||
}
|
||||
|
||||
|
||||
float traversed_x = writer.x();
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
|
||||
|
@ -1050,6 +1068,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
|
|||
|
||||
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;
|
||||
result.priming = false;
|
||||
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_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;
|
||||
for (auto layer : m_plan)
|
||||
|
@ -1208,9 +1231,6 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void WipeTowerPrusaMM::make_wipe_tower_square()
|
||||
{
|
||||
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)
|
||||
for (auto& lay : m_plan) // depths set, now the spacing
|
||||
lay.extra_spacing = lay.depth / lay.toolchanges_depth();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ public:
|
|||
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,
|
||||
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_rotation_angle(rotation_angle),
|
||||
m_y_shift(0.f),
|
||||
|
@ -94,6 +94,8 @@ public:
|
|||
m_filpar[idx].ramming_step_multiplicator /= 100;
|
||||
while (stream >> 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);
|
||||
}
|
||||
|
||||
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:
|
||||
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>::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
|
||||
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
|
||||
|
|
|
@ -21,45 +21,37 @@ class LayerRegion
|
|||
friend class Layer;
|
||||
|
||||
public:
|
||||
Layer* layer() { return this->_layer; }
|
||||
const Layer* layer() const { return this->_layer; }
|
||||
PrintRegion* region() { return this->_region; }
|
||||
const PrintRegion* region() const { return this->_region; }
|
||||
Layer* layer() { return this->_layer; }
|
||||
const Layer* layer() const { return this->_layer; }
|
||||
PrintRegion* region() { return this->_region; }
|
||||
const PrintRegion* region() const { return this->_region; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
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;
|
||||
// Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal.
|
||||
SurfaceCollection slices;
|
||||
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
ExPolygons fill_expolygons;
|
||||
ExPolygons fill_expolygons;
|
||||
// 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).
|
||||
// While not necessary, the memory consumption is meager and it speeds up calculation.
|
||||
// The perimeter_surfaces keep the IDs of the slices (top/bottom/)
|
||||
SurfaceCollection perimeter_surfaces;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
Polygons bridged;
|
||||
// Collection of expolygons representing the bridged areas (thus not needing support material).
|
||||
//FIXME Not used as of now.
|
||||
Polygons bridged;
|
||||
|
||||
// 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
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection perimeters;
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection fills;
|
||||
// Ordered collection of extrusion paths/loops to build all perimeters.
|
||||
// This collection contains only ExtrusionEntityCollection objects.
|
||||
ExtrusionEntityCollection perimeters;
|
||||
// Ordered collection of extrusion paths to fill surfaces.
|
||||
// This collection contains only ExtrusionEntityCollection objects.
|
||||
ExtrusionEntityCollection fills;
|
||||
|
||||
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
Flow
|
||||
LayerRegion::flow(FlowRole role, bool bridge, double width) const
|
||||
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
|
||||
{
|
||||
return this->_region->flow(
|
||||
role,
|
||||
|
@ -51,8 +50,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
|
||||
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
|
||||
{
|
||||
this->perimeters.clear();
|
||||
this->thin_fills.clear();
|
||||
|
@ -340,8 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
|
||||
void
|
||||
LayerRegion::prepare_fill_surfaces()
|
||||
void LayerRegion::prepare_fill_surfaces()
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
|
||||
|
@ -382,8 +379,7 @@ LayerRegion::prepare_fill_surfaces()
|
|||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
|
||||
double
|
||||
LayerRegion::infill_area_threshold() const
|
||||
double LayerRegion::infill_area_threshold() const
|
||||
{
|
||||
double ss = this->flow(frSolidInfill).scaled_spacing();
|
||||
return ss*ss;
|
||||
|
|
|
@ -627,7 +627,8 @@ const BoundingBoxf3& ModelObject::bounding_box() const
|
|||
if (! m_bounding_box_valid) {
|
||||
BoundingBoxf3 raw_bbox;
|
||||
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());
|
||||
BoundingBoxf3 bb;
|
||||
for (const ModelInstance *i : this->instances)
|
||||
|
@ -658,7 +659,7 @@ TriangleMesh ModelObject::raw_mesh() const
|
|||
{
|
||||
TriangleMesh mesh;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (! v->modifier)
|
||||
if (v->is_model_part())
|
||||
mesh.merge(v->mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
@ -669,7 +670,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
|
|||
{
|
||||
BoundingBoxf3 bb;
|
||||
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");
|
||||
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;
|
||||
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));
|
||||
return bb;
|
||||
}
|
||||
|
@ -692,7 +693,7 @@ void ModelObject::center_around_origin()
|
|||
// center this object around the origin
|
||||
BoundingBoxf3 bb;
|
||||
for (ModelVolume *v : this->volumes)
|
||||
if (! v->modifier)
|
||||
if (v->is_model_part())
|
||||
bb.merge(v->mesh.bounding_box());
|
||||
|
||||
// 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;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (! v->modifier)
|
||||
if (v->is_model_part())
|
||||
num += v->mesh.stl.stats.number_of_facets;
|
||||
return num;
|
||||
}
|
||||
|
@ -806,7 +807,7 @@ size_t ModelObject::facets_count() const
|
|||
bool ModelObject::needed_repair() const
|
||||
{
|
||||
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 false;
|
||||
}
|
||||
|
@ -822,7 +823,7 @@ void ModelObject::cut(coordf_t z, Model* model) const
|
|||
lower->input_file = "";
|
||||
|
||||
for (ModelVolume *volume : this->volumes) {
|
||||
if (volume->modifier) {
|
||||
if (! volume->is_model_part()) {
|
||||
// don't cut modifiers
|
||||
upper->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);
|
||||
new_volume->name = volume->name;
|
||||
new_volume->config = volume->config;
|
||||
new_volume->modifier = volume->modifier;
|
||||
new_volume->set_type(volume->type());
|
||||
new_volume->material_id(volume->material_id());
|
||||
|
||||
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)
|
||||
{
|
||||
if (!vol->modifier)
|
||||
if (vol->is_model_part())
|
||||
{
|
||||
for (ModelInstance* inst : this->instances)
|
||||
{
|
||||
|
@ -985,6 +986,37 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
|
|||
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.
|
||||
// Return the number of volumes created from this one.
|
||||
// This is useful to assign different materials to different volumes of an object.
|
||||
|
|
|
@ -167,15 +167,27 @@ public:
|
|||
// Configuration parameters specific to an object model geometry or a modifier volume,
|
||||
// overriding the global Slic3r settings and the ModelObject settings.
|
||||
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.
|
||||
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; }
|
||||
void material_id(t_model_material_id material_id);
|
||||
ModelMaterial* material() const;
|
||||
void set_material(t_model_material_id material_id, const ModelMaterial &material);
|
||||
void material_id(t_model_material_id material_id);
|
||||
ModelMaterial* material() const;
|
||||
void set_material(t_model_material_id material_id, const ModelMaterial &material);
|
||||
// Split this volume, append the result to the object owning this volume.
|
||||
// Return the number of volumes created from this one.
|
||||
// This is useful to assign different materials to different volumes of an object.
|
||||
|
@ -186,24 +198,30 @@ public:
|
|||
void calculate_convex_hull();
|
||||
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:
|
||||
// Parent object owning this ModelVolume.
|
||||
ModelObject* object;
|
||||
t_model_material_id _material_id;
|
||||
ModelObject* object;
|
||||
// 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)
|
||||
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) :
|
||||
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());
|
||||
}
|
||||
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());
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
|
|
|
@ -130,6 +130,7 @@ objfunc(const PointImpl& bincenter,
|
|||
double norm, // A norming factor for physical dimensions
|
||||
// a spatial index to quickly get neighbors of the candidate item
|
||||
const SpatIndex& spatindex,
|
||||
const SpatIndex& smalls_spatindex,
|
||||
const ItemGroup& remaining
|
||||
)
|
||||
{
|
||||
|
@ -161,7 +162,7 @@ objfunc(const PointImpl& bincenter,
|
|||
// Will hold the resulting score
|
||||
double score = 0;
|
||||
|
||||
if(isBig(item.area())) {
|
||||
if(isBig(item.area()) || spatindex.empty()) {
|
||||
// This branch is for the bigger items..
|
||||
|
||||
auto minc = ibb.minCorner(); // bottom left corner
|
||||
|
@ -183,6 +184,8 @@ objfunc(const PointImpl& bincenter,
|
|||
|
||||
// The smalles distance from the arranged pile center:
|
||||
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
|
||||
double density = 0;
|
||||
|
@ -207,14 +210,20 @@ objfunc(const PointImpl& bincenter,
|
|||
// candidate to be aligned with only one item.
|
||||
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();
|
||||
|
||||
// Query the spatial index for the neighbors
|
||||
std::vector<SpatElement> result;
|
||||
result.reserve(spatindex.size());
|
||||
spatindex.query(bgi::intersects(querybb),
|
||||
std::back_inserter(result));
|
||||
if(isBig(item.area())) {
|
||||
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
|
||||
auto idx = e.second;
|
||||
|
@ -235,12 +244,8 @@ objfunc(const PointImpl& bincenter,
|
|||
if(result.empty())
|
||||
score = 0.5 * dist + 0.5 * density;
|
||||
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 {
|
||||
// Here there are the small items that should be placed around the
|
||||
// already processed bigger items.
|
||||
|
@ -291,6 +296,7 @@ protected:
|
|||
PConfig pconf_; // Placement configuration
|
||||
double bin_area_;
|
||||
SpatIndex rtree_;
|
||||
SpatIndex smallsrtree_;
|
||||
double norm_;
|
||||
Pile merged_pile_;
|
||||
Box pilebb_;
|
||||
|
@ -318,6 +324,7 @@ public:
|
|||
pilebb_ = sl::boundingBox(merged_pile);
|
||||
|
||||
rtree_.clear();
|
||||
smallsrtree_.clear();
|
||||
|
||||
// We will treat big items (compared to the print bed) differently
|
||||
auto isBig = [this](double a) {
|
||||
|
@ -327,6 +334,7 @@ public:
|
|||
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
||||
Item& itm = items[idx];
|
||||
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
|
||||
smallsrtree_.insert({itm.boundingBox(), idx});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -360,6 +368,7 @@ public:
|
|||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
|
@ -397,6 +406,7 @@ public:
|
|||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
|
@ -440,6 +450,7 @@ public:
|
|||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
double score = std::get<0>(result);
|
||||
|
||||
|
@ -468,6 +479,7 @@ public:
|
|||
0,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
return std::get<0>(result);
|
||||
};
|
||||
|
|
|
@ -35,8 +35,10 @@ public:
|
|||
Point first_point() const;
|
||||
virtual Point last_point() const = 0;
|
||||
virtual Lines lines() const = 0;
|
||||
size_t size() const { return points.size(); }
|
||||
bool empty() const { return points.empty(); }
|
||||
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;
|
||||
bool has_boundary_point(const Point &point) const;
|
||||
|
|
|
@ -22,6 +22,7 @@ typedef Point Vector;
|
|||
// Vector types with a fixed point coordinate base type.
|
||||
typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
|
||||
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, 3, 1, Eigen::DontAlign> Vec3i64;
|
||||
|
||||
|
|
|
@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double 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)
|
||||
{
|
||||
return poly.points;
|
||||
|
|
|
@ -184,15 +184,13 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
|
|||
|
||||
bool Polyline::is_straight() const
|
||||
{
|
||||
/* Check that each segment's direction is equal to the line connecting
|
||||
first point and last point. (Checking each line against the previous
|
||||
one would cause the error to accumulate.) */
|
||||
// Check that each segment's direction is equal to the line connecting
|
||||
// first point and last point. (Checking each line against the previous
|
||||
// one would cause the error to accumulate.)
|
||||
double dir = Line(this->first_point(), this->last_point()).direction();
|
||||
|
||||
Lines lines = this->lines();
|
||||
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
|
||||
if (!line->parallel_to(dir)) return false;
|
||||
}
|
||||
for (const auto &line: this->lines())
|
||||
if (! line.parallel_to(dir))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ public:
|
|||
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
|
||||
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 Points &points) : MultiPoint(points) {}
|
||||
explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {}
|
||||
Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
|
||||
Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
|
||||
static Polyline new_scale(const std::vector<Vec2d> &points) {
|
||||
|
|
|
@ -366,9 +366,12 @@ void Print::add_model_object(ModelObject* model_object, int idx)
|
|||
// Invalidate all print 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.
|
||||
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.
|
||||
size_t region_id = size_t(-1);
|
||||
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.
|
||||
object->add_region_volume(region_id, volume_id);
|
||||
++ volume_id;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1197,6 +1201,9 @@ void Print::_make_wipe_tower()
|
|||
}
|
||||
m_wipe_tower_final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
|
||||
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()
|
||||
|
|
|
@ -80,7 +80,10 @@ public:
|
|||
|
||||
Print* print() { return this->_print; }
|
||||
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;
|
||||
// Average diameter of nozzles participating on extruding this region.
|
||||
coordf_t bridging_height_avg(const PrintConfig &print_config) const;
|
||||
|
||||
private:
|
||||
Print* _print;
|
||||
|
@ -211,6 +214,10 @@ public:
|
|||
|
||||
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:
|
||||
Print* _print;
|
||||
ModelObject* _model_object;
|
||||
|
@ -222,6 +229,7 @@ private:
|
|||
~PrintObject() {}
|
||||
|
||||
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;
|
||||
|
@ -246,7 +254,7 @@ public:
|
|||
|
||||
std::string estimated_normal_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;
|
||||
PrintState<PrintStep, psCount> state;
|
||||
|
||||
|
@ -315,6 +323,8 @@ public:
|
|||
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming;
|
||||
std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
|
||||
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_filepath(const std::string &path);
|
||||
|
|
|
@ -1717,6 +1717,14 @@ void PrintConfigDef::init_fff_params()
|
|||
def->cli = "support-material!";
|
||||
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->label = L("XY separation between an object and its support");
|
||||
def->category = L("Support material");
|
||||
|
@ -1755,7 +1763,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"for the first object layer.");
|
||||
def->sidetext = L("mm");
|
||||
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.2");
|
||||
def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
|
||||
|
|
|
@ -345,6 +345,7 @@ public:
|
|||
ConfigOptionFloatOrPercent extrusion_width;
|
||||
ConfigOptionFloatOrPercent first_layer_height;
|
||||
ConfigOptionBool infill_only_where_needed;
|
||||
// Force the generation of solid shells between adjacent materials/volumes.
|
||||
ConfigOptionBool interface_shells;
|
||||
ConfigOptionFloat layer_height;
|
||||
ConfigOptionInt raft_layers;
|
||||
|
@ -352,6 +353,9 @@ public:
|
|||
// ConfigOptionFloat seam_preferred_direction;
|
||||
// ConfigOptionFloat seam_preferred_direction_jitter;
|
||||
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;
|
||||
ConfigOptionBool support_material_buildplate_only;
|
||||
ConfigOptionFloat support_material_contact_distance;
|
||||
|
@ -361,12 +365,15 @@ public:
|
|||
ConfigOptionBool support_material_interface_contact_loops;
|
||||
ConfigOptionInt support_material_interface_extruder;
|
||||
ConfigOptionInt support_material_interface_layers;
|
||||
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
|
||||
ConfigOptionFloat support_material_interface_spacing;
|
||||
ConfigOptionFloatOrPercent support_material_interface_speed;
|
||||
ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
|
||||
// Spacing between support material lines (the hatching distance).
|
||||
ConfigOptionFloat support_material_spacing;
|
||||
ConfigOptionFloat support_material_speed;
|
||||
ConfigOptionBool support_material_synchronize_layers;
|
||||
// Overhang angle threshold.
|
||||
ConfigOptionInt support_material_threshold;
|
||||
ConfigOptionBool support_material_with_sheath;
|
||||
ConfigOptionFloatOrPercent support_material_xy_spacing;
|
||||
|
@ -389,6 +396,7 @@ protected:
|
|||
// OPT_PTR(seam_preferred_direction);
|
||||
// OPT_PTR(seam_preferred_direction_jitter);
|
||||
OPT_PTR(support_material);
|
||||
OPT_PTR(support_material_auto);
|
||||
OPT_PTR(support_material_angle);
|
||||
OPT_PTR(support_material_buildplate_only);
|
||||
OPT_PTR(support_material_contact_distance);
|
||||
|
@ -436,10 +444,12 @@ public:
|
|||
ConfigOptionInt infill_every_layers;
|
||||
ConfigOptionFloatOrPercent infill_overlap;
|
||||
ConfigOptionFloat infill_speed;
|
||||
// Detect bridging perimeters
|
||||
ConfigOptionBool overhangs;
|
||||
ConfigOptionInt perimeter_extruder;
|
||||
ConfigOptionFloatOrPercent perimeter_extrusion_width;
|
||||
ConfigOptionFloat perimeter_speed;
|
||||
// Total number of perimeters.
|
||||
ConfigOptionInt perimeters;
|
||||
ConfigOptionFloatOrPercent small_perimeter_speed;
|
||||
ConfigOptionFloat solid_infill_below_area;
|
||||
|
@ -447,6 +457,7 @@ public:
|
|||
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
|
||||
ConfigOptionInt solid_infill_every_layers;
|
||||
ConfigOptionFloatOrPercent solid_infill_speed;
|
||||
// Detect thin walls.
|
||||
ConfigOptionBool thin_walls;
|
||||
ConfigOptionFloatOrPercent top_infill_extrusion_width;
|
||||
ConfigOptionInt top_solid_layers;
|
||||
|
|
|
@ -177,6 +177,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|
|||
steps.emplace_back(posSlice);
|
||||
} else if (
|
||||
opt_key == "support_material"
|
||||
|| opt_key == "support_material_auto"
|
||||
|| opt_key == "support_material_angle"
|
||||
|| opt_key == "support_material_buildplate_only"
|
||||
|| 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> layers;
|
||||
std::vector<const ModelVolume*> volumes;
|
||||
if (region_id < this->region_volumes.size()) {
|
||||
std::vector<int> &volumes = this->region_volumes[region_id];
|
||||
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 (int volume_id : volumes) {
|
||||
ModelVolume *volume = this->model_object()->volumes[volume_id];
|
||||
if (volume->modifier == modifier)
|
||||
mesh.merge(volume->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);
|
||||
}
|
||||
for (int volume_id : this->region_volumes[region_id]) {
|
||||
const ModelVolume *volume = this->model_object()->volumes[volume_id];
|
||||
if (modifier ? volume->is_modifier() : volume->is_model_part())
|
||||
volumes.emplace_back(volume);
|
||||
}
|
||||
}
|
||||
return this->_slice_volumes(z, volumes);
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
|
||||
{
|
||||
std::vector<const ModelVolume*> volumes;
|
||||
for (const ModelVolume *volume : this->model_object()->volumes)
|
||||
if (volume->is_support_enforcer())
|
||||
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_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;
|
||||
|
|
|
@ -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.;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -224,9 +224,9 @@ std::vector<coordf_t> layer_height_profile_adaptive(
|
|||
// 1) Initialize the SlicingAdaptive class with the object meshes.
|
||||
SlicingAdaptive as;
|
||||
as.set_slicing_parameters(slicing_params);
|
||||
for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it)
|
||||
if (! (*it)->modifier)
|
||||
as.add_mesh(&(*it)->mesh);
|
||||
for (const ModelVolume *volume : volumes)
|
||||
if (volume->is_model_part())
|
||||
as.add_mesh(&volume->mesh);
|
||||
as.prepare();
|
||||
|
||||
// 2) Generate layers using the algorithm of @platsch
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ class PrintConfig;
|
|||
class PrintObjectConfig;
|
||||
|
||||
// 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
|
||||
|
||||
// This class manages raft and supports for a single PrintObject.
|
||||
|
@ -71,6 +72,21 @@ public:
|
|||
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 {
|
||||
return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,11 @@ public:
|
|||
|
||||
void clear() { surfaces.clear(); }
|
||||
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(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
|
||||
|
|
|
@ -21,16 +21,20 @@
|
|||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
// for SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
#include "libslic3r.h"
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#define SLIC3R_DEBUG
|
||||
// #define SLIC3R_TRIANGLEMESH_DEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
// #define SLIC3R_TRIANGLEMESH_DEBUG
|
||||
#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
|
||||
#include "SVG.hpp"
|
||||
#endif
|
||||
|
||||
|
@ -156,7 +160,6 @@ void TriangleMesh::repair()
|
|||
BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
|
||||
}
|
||||
|
||||
|
||||
float TriangleMesh::volume()
|
||||
{
|
||||
if (this->stl.stats.volume == -1)
|
||||
|
@ -320,7 +323,7 @@ bool TriangleMesh::has_multiple_patches() const
|
|||
facet_visited[facet_idx] = true;
|
||||
for (int j = 0; j < 3; ++ 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;
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +366,7 @@ size_t TriangleMesh::number_of_patches() const
|
|||
facet_visited[facet_idx] = true;
|
||||
for (int j = 0; j < 3; ++ 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;
|
||||
}
|
||||
}
|
||||
|
@ -623,10 +626,23 @@ void TriangleMesh::require_shared_vertices()
|
|||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
|
||||
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";
|
||||
}
|
||||
|
||||
|
||||
TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
|
||||
mesh(_mesh)
|
||||
{
|
||||
|
@ -698,13 +714,13 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
|
|||
}
|
||||
}
|
||||
// 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) {
|
||||
EdgeToFace &edge_j = edges_map[j];
|
||||
this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges;
|
||||
// Mark the edge as connected.
|
||||
edge_j.face = -1;
|
||||
}
|
||||
this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges;
|
||||
// Mark the edge as connected.
|
||||
edge_j.face = -1;
|
||||
}
|
||||
++ num_edges;
|
||||
}
|
||||
}
|
||||
|
@ -771,13 +787,30 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||
{
|
||||
static int iRun = 0;
|
||||
for (size_t i = 0; i < z.size(); ++ i) {
|
||||
Polygons &polygons = (*layers)[i];
|
||||
SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true));
|
||||
Polygons &polygons = (*layers)[i];
|
||||
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 (size_t i = 1; i < poly.points.size(); ++ i)
|
||||
assert(poly.points[i-1] != poly.points[i]);
|
||||
assert(poly.points.front() != poly.points.back());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
++ 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 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,
|
||||
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[2].x, facet.vertex[2].y, facet.vertex[2](2));
|
||||
printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
|
||||
#endif
|
||||
#endif /* SLIC3R_TRIANGLEMESH_DEBUG */
|
||||
|
||||
// find layer extents
|
||||
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
|
||||
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()));
|
||||
#endif
|
||||
#endif /* SLIC3R_TRIANGLEMESH_DEBUG */
|
||||
|
||||
for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
|
||||
std::vector<float>::size_type layer_idx = it - z.begin();
|
||||
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);
|
||||
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 bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0;
|
||||
for (int j = 0; j < 3; ++ j) {
|
||||
int a_id = vertices[j % 3];
|
||||
int b_id = vertices[(j+1) % 3];
|
||||
if (reverse)
|
||||
std::swap(a_id, b_id);
|
||||
const stl_vertex &a = this->v_scaled_shared[a_id];
|
||||
const stl_vertex &b = this->v_scaled_shared[b_id];
|
||||
il.a(0) = a(0);
|
||||
il.a(1) = a(1);
|
||||
il.b(0) = b(0);
|
||||
il.b(1) = b(1);
|
||||
il.a_id = a_id;
|
||||
il.b_id = b_id;
|
||||
(*lines)[layer_idx].emplace_back(il);
|
||||
}
|
||||
for (int j = 0; j < 3; ++ j)
|
||||
if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) {
|
||||
int a_id = vertices[j % 3];
|
||||
int b_id = vertices[(j+1) % 3];
|
||||
if (reverse)
|
||||
std::swap(a_id, b_id);
|
||||
const stl_vertex &a = this->v_scaled_shared[a_id];
|
||||
const stl_vertex &b = this->v_scaled_shared[b_id];
|
||||
il.a(0) = a(0);
|
||||
il.a(1) = a(1);
|
||||
il.b(0) = b(0);
|
||||
il.b(1) = b(1);
|
||||
il.a_id = a_id;
|
||||
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
|
||||
(*lines)[layer_idx].emplace_back(il);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
|
||||
{
|
||||
std::vector<Polygons> layers_p;
|
||||
this->slice(z, &layers_p);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
|
||||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&layers_p, layers, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
|
||||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&layers_p, layers, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
||||
#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
|
||||
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.
|
||||
bool TriangleMeshSlicer::slice_facet(
|
||||
TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::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
|
||||
{
|
||||
IntersectionPoint points[3];
|
||||
size_t num_points = 0;
|
||||
size_t points_on_layer[3];
|
||||
size_t num_points_on_layer = 0;
|
||||
size_t point_on_layer = size_t(-1);
|
||||
|
||||
// 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
|
||||
// (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);
|
||||
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)];
|
||||
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
|
||||
int a_id = vertices[j % 3];
|
||||
int b_id = vertices[(j+1) % 3];
|
||||
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 &v2 = this->v_scaled_shared[vertices[2]];
|
||||
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) {
|
||||
// All three vertices are aligned with slice_z.
|
||||
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.
|
||||
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 {
|
||||
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
|
||||
line_out->edge_type = feBottom;
|
||||
// Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
|
||||
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->b = to_2d(swap ? a : b).cast<coord_t>();
|
||||
line_out->a_id = swap ? b_id : a_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) {
|
||||
// Only point a alings with the cutting plane.
|
||||
points_on_layer[num_points_on_layer ++] = num_points;
|
||||
IntersectionPoint &point = points[num_points ++];
|
||||
point(0) = a(0);
|
||||
point(1) = a(1);
|
||||
point.point_id = a_id;
|
||||
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
|
||||
point_on_layer = num_points;
|
||||
IntersectionPoint &point = points[num_points ++];
|
||||
point(0) = a(0);
|
||||
point(1) = a(1);
|
||||
point.point_id = a_id;
|
||||
}
|
||||
} else if (b(2) == slice_z) {
|
||||
// Only point b alings with the cutting plane.
|
||||
points_on_layer[num_points_on_layer ++] = num_points;
|
||||
IntersectionPoint &point = points[num_points ++];
|
||||
point(0) = b(0);
|
||||
point(1) = b(1);
|
||||
point.point_id = b_id;
|
||||
if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
|
||||
point_on_layer = num_points;
|
||||
IntersectionPoint &point = points[num_points ++];
|
||||
point(0) = b(0);
|
||||
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)) {
|
||||
// A general case. The face edge intersects the cutting plane. Calculate the intersection point.
|
||||
IntersectionPoint &point = points[num_points ++];
|
||||
point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2));
|
||||
point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2));
|
||||
point.edge_id = edge_id;
|
||||
assert(a_id != b_id);
|
||||
// Sort the edge to give a consistent answer.
|
||||
const stl_vertex *pa = &a;
|
||||
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
|
||||
// twice (once for each edge), and we can't have three points on layer,
|
||||
// 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);
|
||||
// Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only.
|
||||
assert(num_points < 3);
|
||||
if (num_points == 2) {
|
||||
line_out->edge_type = feNone;
|
||||
line_out->edge_type = feGeneral;
|
||||
line_out->a = (Point)points[1];
|
||||
line_out->b = (Point)points[0];
|
||||
line_out->a_id = points[1].point_id;
|
||||
line_out->b_id = points[0].point_id;
|
||||
line_out->edge_a_id = points[1].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
|
||||
{
|
||||
// Remove tangent edges.
|
||||
//FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane.
|
||||
for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line)
|
||||
if (! line->skip && line->edge_type != feNone) {
|
||||
// This line is af facet edge. There may be a duplicate line with the same end vertices.
|
||||
// If the line is is an edge connecting two facets, find another facet edge
|
||||
// having the same endpoints but in reverse order.
|
||||
for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
|
||||
//#ifdef _DEBUG
|
||||
for (const Line &l : lines)
|
||||
assert(l.a != l.b);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
remove_tangent_edges(lines);
|
||||
|
||||
struct OpenPolyline {
|
||||
OpenPolyline() {};
|
||||
|
@ -1022,7 +1221,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
|
|||
by_edge_a_id.reserve(lines.size());
|
||||
by_a_id.reserve(lines.size());
|
||||
for (IntersectionLine &line : lines) {
|
||||
if (! line.skip) {
|
||||
if (! line.skip()) {
|
||||
if (line.edge_a_id != -1)
|
||||
by_edge_a_id.emplace_back(&line);
|
||||
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
|
||||
IntersectionLine *first_line = nullptr;
|
||||
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 ++);
|
||||
break;
|
||||
}
|
||||
if (first_line == nullptr)
|
||||
break;
|
||||
first_line->skip = true;
|
||||
first_line->set_skip();
|
||||
Points loop_pts;
|
||||
loop_pts.emplace_back(first_line->a);
|
||||
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()) {
|
||||
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)
|
||||
if (! (*it_line)->skip) {
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
|
@ -1078,7 +1278,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
|
|||
if (it_begin != by_a_id.end()) {
|
||||
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)
|
||||
if (! (*it_line)->skip) {
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
|
@ -1109,7 +1309,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
|
|||
*/
|
||||
loop_pts.emplace_back(next_line->a);
|
||||
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) {
|
||||
// The current loop could not be closed. Unmark the segment.
|
||||
opl.consumed = false;
|
||||
break;
|
||||
}
|
||||
// Attach this polyline to the end of the initial polyline.
|
||||
if (next_start == nullptr) {
|
||||
// The current loop could not be closed. Unmark the segment.
|
||||
opl.consumed = false;
|
||||
break;
|
||||
}
|
||||
// Attach this polyline to the end of the initial polyline.
|
||||
if (next_start->start) {
|
||||
auto it = next_start->polyline->points.begin();
|
||||
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) ||
|
||||
(ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
|
||||
// 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().edge_id == opl.points.back().edge_id);*/
|
||||
//assert(opl.points.front().point_id == opl.points.back().point_id);
|
||||
//assert(opl.points.front().edge_id == opl.points.back().edge_id);
|
||||
// Remove the duplicate last point.
|
||||
opl.points.pop_back();
|
||||
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));
|
||||
}
|
||||
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)
|
||||
// Ignore this hole.
|
||||
continue;
|
||||
assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area());
|
||||
(*slices)[slice_idx].holes.emplace_back(std::move(*hole));
|
||||
assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area());
|
||||
(*slices)[slice_idx].holes.emplace_back(std::move(*hole));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// If the input mesh is not valid, the holes may intersect with the external contour.
|
||||
// Rather subtract them from the outer contour.
|
||||
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()) {
|
||||
poly.emplace_back(std::move(it_slice->contour));
|
||||
} else {
|
||||
|
@ -1285,7 +1485,7 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l
|
|||
it->reverse();
|
||||
polygons_append(poly, diff(contours, it_slice->holes));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the input mesh is not valid, the input contours may intersect.
|
||||
*slices = union_ex(poly);
|
||||
#endif
|
||||
|
@ -1402,7 +1602,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
|
||||
// intersect facet with cutting plane
|
||||
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.
|
||||
if (line.edge_type == feTop) {
|
||||
lower_lines.emplace_back(line);
|
||||
|
|
|
@ -82,7 +82,7 @@ private:
|
|||
|
||||
enum FacetEdgeType {
|
||||
// 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.
|
||||
feTop,
|
||||
// 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
|
||||
{
|
||||
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
|
||||
// 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.
|
||||
|
@ -124,11 +132,23 @@ public:
|
|||
// Source mesh edges of the line end points.
|
||||
int edge_a_id;
|
||||
int edge_b_id;
|
||||
// feNone, feTop, feBottom, feHorizontal
|
||||
// feGeneral, feTop, feBottom, feHorizontal
|
||||
FacetEdgeType edge_type;
|
||||
// Used by TriangleMeshSlicer::make_loops() to skip duplicate edges.
|
||||
bool skip;
|
||||
IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {};
|
||||
// Used by TriangleMeshSlicer::slice() to skip duplicate edges.
|
||||
enum {
|
||||
// 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*> IntersectionLinePtrs;
|
||||
|
@ -139,7 +159,12 @@ public:
|
|||
TriangleMeshSlicer(TriangleMesh* _mesh);
|
||||
void slice(const std::vector<float> &z, std::vector<Polygons>* 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;
|
||||
void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Slic3r {
|
|||
|
||||
extern void set_logging_level(unsigned int level);
|
||||
extern void trace(unsigned int level, const char *message);
|
||||
extern void disable_multi_threading();
|
||||
|
||||
// Set a path with GUI resource files.
|
||||
void set_var_dir(const std::string &path);
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <boost/nowide/integration/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
void set_var_dir(const std::string &dir)
|
||||
|
|
|
@ -11,13 +11,11 @@
|
|||
#include <ModelArrange.hpp>
|
||||
#include <slic3r/GUI/PresetBundle.hpp>
|
||||
|
||||
#include <Geometry.hpp>
|
||||
#include <PrintConfig.hpp>
|
||||
#include <Print.hpp>
|
||||
#include <PrintExport.hpp>
|
||||
#include <Geometry.hpp>
|
||||
#include <Model.hpp>
|
||||
#include <Utils.hpp>
|
||||
#include <SLABasePool.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -45,15 +43,6 @@ namespace GUI {
|
|||
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::global_progress_indicator() {
|
||||
ProgresIndicatorPtr ret;
|
||||
|
@ -73,336 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator(
|
|||
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(
|
||||
const string &fmtstr, ...) {
|
||||
const std::string &fmtstr, ...) {
|
||||
std::stringstream ss;
|
||||
va_list args;
|
||||
va_start(args, fmtstr);
|
||||
|
@ -433,80 +94,77 @@ const PrintConfig &PrintController::config() const
|
|||
return print_->config;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
arranging_.store(true);
|
||||
auto pind = global_progress_indicator();
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : model_->objects) count += obj->instances.size();
|
||||
float pmax = 1.0;
|
||||
|
||||
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
|
||||
pind->max(count);
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
|
||||
pind->on_cancel([this](){
|
||||
arranging_.store(false);
|
||||
});
|
||||
// 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;
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ class Print;
|
|||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class ProgressStatusBar;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
/**
|
||||
* @brief A boilerplate class for creating application logic. It should provide
|
||||
|
@ -46,7 +47,7 @@ public:
|
|||
AppControllerBoilerplate();
|
||||
~AppControllerBoilerplate();
|
||||
|
||||
using Path = string;
|
||||
using Path = std::string;
|
||||
using PathList = std::vector<Path>;
|
||||
|
||||
/// Common runtime issue types
|
||||
|
@ -67,20 +68,20 @@ public:
|
|||
* @return Returns a list of paths choosed by the user.
|
||||
*/
|
||||
PathList query_destination_paths(
|
||||
const string& title,
|
||||
const std::string& title,
|
||||
const std::string& extensions) const;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but works for directories only.
|
||||
*/
|
||||
PathList query_destination_dirs(
|
||||
const string& title) const;
|
||||
const std::string& title) const;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but returns only one path.
|
||||
*/
|
||||
Path query_destination_path(
|
||||
const string& title,
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& hint = "") const;
|
||||
|
||||
|
@ -95,11 +96,11 @@ public:
|
|||
* title.
|
||||
*/
|
||||
bool report_issue(IssueType issuetype,
|
||||
const string& description,
|
||||
const string& brief);
|
||||
const std::string& description,
|
||||
const std::string& brief);
|
||||
|
||||
bool report_issue(IssueType issuetype,
|
||||
const string& description);
|
||||
const std::string& description);
|
||||
|
||||
/**
|
||||
* @brief Return the global progress indicator for the current controller.
|
||||
|
@ -150,12 +151,12 @@ protected:
|
|||
*/
|
||||
ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const string& title,
|
||||
const string& firstmsg) const;
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const;
|
||||
|
||||
ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const string& title) const;
|
||||
const std::string& title) const;
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
|
@ -167,43 +168,6 @@ protected:
|
|||
*/
|
||||
class PrintController: public AppControllerBoilerplate {
|
||||
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:
|
||||
|
||||
// Must be public for perl to use it
|
||||
|
@ -218,15 +182,9 @@ public:
|
|||
return PrintController::Ptr( new PrintController(print) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Slice the loaded print scene.
|
||||
*/
|
||||
void slice();
|
||||
|
||||
/**
|
||||
* @brief Slice the print into zipped png files.
|
||||
*/
|
||||
void slice_to_png();
|
||||
//FIXME Vojtech: Merging error
|
||||
void slice() {}
|
||||
void slice_to_png() {}
|
||||
|
||||
const PrintConfig& config() const;
|
||||
};
|
||||
|
@ -237,7 +195,6 @@ public:
|
|||
class AppController: public AppControllerBoilerplate {
|
||||
Model *model_ = nullptr;
|
||||
PrintController::Ptr printctl;
|
||||
std::atomic<bool> arranging_;
|
||||
public:
|
||||
|
||||
/**
|
||||
|
@ -273,14 +230,15 @@ public:
|
|||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @param gauge_id The ID of the gague widget 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();
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include <future>
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <slic3r/GUI/ProgressStatusBar.hpp>
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/filedlg.h>
|
||||
|
@ -28,16 +27,16 @@ bool AppControllerBoilerplate::supports_asynch() const
|
|||
|
||||
void AppControllerBoilerplate::process_events()
|
||||
{
|
||||
wxYieldIfNeeded();
|
||||
wxSafeYield();
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::PathList
|
||||
AppControllerBoilerplate::query_destination_paths(
|
||||
const string &title,
|
||||
const std::string &title,
|
||||
const std::string &extensions) const
|
||||
{
|
||||
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.ShowModal();
|
||||
|
@ -53,11 +52,11 @@ AppControllerBoilerplate::query_destination_paths(
|
|||
|
||||
AppControllerBoilerplate::Path
|
||||
AppControllerBoilerplate::query_destination_path(
|
||||
const string &title,
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string& hint) const
|
||||
{
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), title );
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.SetFilename(hint);
|
||||
|
@ -72,8 +71,8 @@ AppControllerBoilerplate::query_destination_path(
|
|||
}
|
||||
|
||||
bool AppControllerBoilerplate::report_issue(IssueType issuetype,
|
||||
const string &description,
|
||||
const string &brief)
|
||||
const std::string &description,
|
||||
const std::string &brief)
|
||||
{
|
||||
auto icon = wxICON_INFORMATION;
|
||||
auto style = wxOK|wxCENTRE;
|
||||
|
@ -85,15 +84,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype,
|
|||
case IssueType::FATAL: icon = wxICON_ERROR;
|
||||
}
|
||||
|
||||
auto ret = wxMessageBox(description, brief, icon | style);
|
||||
auto ret = wxMessageBox(_(description), _(brief), icon | style);
|
||||
return ret != wxCANCEL;
|
||||
}
|
||||
|
||||
bool AppControllerBoilerplate::report_issue(
|
||||
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);
|
||||
|
@ -137,8 +136,8 @@ public:
|
|||
/// Get the mode of parallel operation.
|
||||
inline bool asynch() const { return is_asynch_; }
|
||||
|
||||
inline GuiProgressIndicator(int range, const string& title,
|
||||
const string& firstmsg) :
|
||||
inline GuiProgressIndicator(int range, const wxString& title,
|
||||
const wxString& firstmsg) :
|
||||
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE),
|
||||
message_(firstmsg),
|
||||
|
@ -152,11 +151,6 @@ public:
|
|||
this, id_);
|
||||
}
|
||||
|
||||
virtual void cancel() override {
|
||||
update(max(), "Abort");
|
||||
ProgressIndicator::cancel();
|
||||
}
|
||||
|
||||
virtual void state(float val) override {
|
||||
state(static_cast<unsigned>(val));
|
||||
}
|
||||
|
@ -171,26 +165,28 @@ public:
|
|||
} else _state(st);
|
||||
}
|
||||
|
||||
virtual void message(const string & msg) override {
|
||||
message_ = msg;
|
||||
virtual void message(const std::string & msg) override {
|
||||
message_ = _(msg);
|
||||
}
|
||||
|
||||
virtual void messageFmt(const string& fmt, ...) {
|
||||
virtual void messageFmt(const std::string& fmt, ...) {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
message_ = wxString::Format(wxString(fmt), arglist);
|
||||
message_ = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const string & title) override {
|
||||
title_ = title;
|
||||
virtual void title(const std::string & title) override {
|
||||
title_ = _(title);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr
|
||||
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 =
|
||||
std::make_shared<GuiProgressIndicator>(statenum, title, firstmsg);
|
||||
|
@ -203,29 +199,39 @@ AppControllerBoilerplate::create_progress_indicator(
|
|||
}
|
||||
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr
|
||||
AppControllerBoilerplate::create_progress_indicator(unsigned statenum,
|
||||
const string &title) const
|
||||
AppControllerBoilerplate::create_progress_indicator(
|
||||
unsigned statenum, const std::string &title) const
|
||||
{
|
||||
return create_progress_indicator(statenum, title, string());
|
||||
return create_progress_indicator(statenum, title, std::string());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A wrapper progress indicator class around the statusbar created in perl.
|
||||
class Wrapper: public ProgressIndicator, public wxEvtHandler {
|
||||
ProgressStatusBar *sbar_;
|
||||
wxGauge *gauge_;
|
||||
wxStatusBar *stbar_;
|
||||
using Base = ProgressIndicator;
|
||||
std::string message_;
|
||||
wxString message_;
|
||||
AppControllerBoilerplate& ctl_;
|
||||
|
||||
void showProgress(bool show = true) {
|
||||
sbar_->show_progress(show);
|
||||
gauge_->Show(show);
|
||||
}
|
||||
|
||||
void _state(unsigned st) {
|
||||
if( st <= ProgressIndicator::max() ) {
|
||||
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:
|
||||
|
||||
inline Wrapper(ProgressStatusBar *sbar,
|
||||
inline Wrapper(wxGauge *gauge, wxStatusBar *stbar,
|
||||
AppControllerBoilerplate& ctl):
|
||||
sbar_(sbar), ctl_(ctl)
|
||||
gauge_(gauge), stbar_(stbar), ctl_(ctl)
|
||||
{
|
||||
Base::max(static_cast<float>(sbar_->get_range()));
|
||||
Base::states(static_cast<unsigned>(sbar_->get_range()));
|
||||
Base::max(static_cast<float>(gauge->GetRange()));
|
||||
Base::states(static_cast<unsigned>(gauge->GetRange()));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&Wrapper::_state,
|
||||
|
@ -256,7 +262,7 @@ public:
|
|||
|
||||
virtual void max(float val) override {
|
||||
if(val > 1.0) {
|
||||
sbar_->set_range(static_cast<int>(val));
|
||||
gauge_->SetRange(static_cast<int>(val));
|
||||
ProgressIndicator::max(val);
|
||||
}
|
||||
}
|
||||
|
@ -271,32 +277,31 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
virtual void message(const string & msg) override {
|
||||
message_ = msg;
|
||||
virtual void message(const std::string & msg) override {
|
||||
message_ = _(msg);
|
||||
}
|
||||
|
||||
virtual void message_fmt(const string& fmt, ...) override {
|
||||
virtual void message_fmt(const std::string& fmt, ...) override {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
message_ = wxString::Format(fmt, arglist);
|
||||
message_ = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const string & /*title*/) override {}
|
||||
|
||||
virtual void on_cancel(CancelFn fn) override {
|
||||
sbar_->set_cancel_callback(fn);
|
||||
Base::on_cancel(fn);
|
||||
}
|
||||
virtual void title(const std::string & /*title*/) override {}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb)
|
||||
void AppController::set_global_progress_indicator(
|
||||
unsigned gid,
|
||||
unsigned sid)
|
||||
{
|
||||
if(prsb) {
|
||||
global_progress_indicator(std::make_shared<Wrapper>(prsb, *this));
|
||||
wxGauge* gauge = dynamic_cast<wxGauge*>(wxWindow::FindWindowById(gid));
|
||||
wxStatusBar* sb = dynamic_cast<wxStatusBar*>(wxWindow::FindWindowById(sid));
|
||||
|
||||
if(gauge && sb) {
|
||||
global_progressind_ = std::make_shared<Wrapper>(gauge, sb, *this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -648,7 +648,7 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
const ModelVolume *model_volume = model_object->volumes[volume_idx];
|
||||
|
||||
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;
|
||||
if (extruder_id == 0)
|
||||
|
@ -661,7 +661,16 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
volumes_idx.push_back(int(this->volumes.size()));
|
||||
float color[4];
|
||||
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));
|
||||
GLVolume &v = *this->volumes.back();
|
||||
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.set_select_group_id(select_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.layer_height_texture = layer_height_texture;
|
||||
if (extruder_id != -1)
|
||||
v.extruder_id = extruder_id;
|
||||
}
|
||||
v.is_modifier = model_volume->modifier;
|
||||
v.shader_outside_printer_detection_enabled = !model_volume->modifier;
|
||||
v.is_modifier = ! model_volume->is_model_part();
|
||||
v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
v.set_offset(instance->get_offset());
|
||||
#else
|
||||
|
|
|
@ -60,6 +60,14 @@ void AppConfig::set_defaults()
|
|||
|
||||
if (get("remember_output_path").empty())
|
||||
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()
|
||||
|
|
|
@ -72,6 +72,14 @@ public:
|
|||
bool has(const std::string &key) const
|
||||
{ return this->has("", key); }
|
||||
|
||||
void erase(const std::string §ion, 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 §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
|
|
|
@ -409,11 +409,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) :
|
|||
|
||||
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
|
||||
{
|
||||
ConfigOptionEnum<GCodeFlavor> opt;
|
||||
|
||||
auto sel = gcode_picker->GetSelection();
|
||||
if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) {
|
||||
config.set_key_value("gcode_flavor", &opt);
|
||||
if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
|
||||
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.
|
||||
auto wizard_size = GetSize();
|
||||
unsigned width, height;
|
||||
GUI::get_current_screen_size(width, height);
|
||||
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)));
|
||||
SetMinSize(wizard_size);
|
||||
if (GUI::get_current_screen_size(this, width, height)) {
|
||||
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)));
|
||||
SetMinSize(wizard_size);
|
||||
}
|
||||
Fit();
|
||||
|
||||
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
|
||||
|
|
|
@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
|
|||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
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());
|
||||
|
||||
if (ports.size() == 1) {
|
||||
|
@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
|
|||
|
||||
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 ...";
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
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_APP;
|
||||
}), ports.end());
|
||||
|
||||
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 ...";
|
||||
|
||||
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 ..."
|
||||
)));
|
||||
|
||||
queue_status(_(L(msg_not_found)));
|
||||
wait_for_mmu_bootloader(30);
|
||||
} else if (ports.size() > 1) {
|
||||
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;
|
||||
mmu_reboot(ports[0]);
|
||||
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 {
|
||||
port = ports[0];
|
||||
}
|
||||
|
@ -702,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
panel->SetSizer(vsizer);
|
||||
|
||||
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:")));
|
||||
p->port_picker = new wxComboBox(panel, wxID_ANY);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#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();
|
||||
width = disp_size.GetWidth();
|
||||
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)
|
||||
|
|
|
@ -26,6 +26,7 @@ class wxButton;
|
|||
class wxFileDialog;
|
||||
class wxStaticBitmap;
|
||||
class wxFont;
|
||||
class wxTopLevelWindow;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -223,7 +224,12 @@ void add_export_option(wxFileDialog* dlg, const std::string& format);
|
|||
int get_export_option(wxFileDialog* dlg);
|
||||
|
||||
// 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
|
||||
void enable_action_buttons(bool enable);
|
||||
|
|
|
@ -1225,7 +1225,7 @@ void load_part( ModelObject* model_object,
|
|||
for ( auto object : model.objects) {
|
||||
for (auto volume : object->volumes) {
|
||||
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();
|
||||
new_volume->name = boost::filesystem::path(input_file).filename().string();
|
||||
|
||||
|
@ -1283,7 +1283,8 @@ void load_lambda( ModelObject* model_object,
|
|||
mesh.repair();
|
||||
|
||||
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;
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
|
@ -1320,7 +1321,8 @@ void load_lambda(const std::string& type_name)
|
|||
mesh.repair();
|
||||
|
||||
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;
|
||||
// set a default extruder value, since user can't add it manually
|
||||
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
|
||||
int solid_cnt = 0;
|
||||
for (auto vol : (*m_objects)[m_selected_object_id]->volumes)
|
||||
if (!vol->modifier)
|
||||
if (vol->is_model_part())
|
||||
++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.")));
|
||||
return false;
|
||||
}
|
||||
|
@ -1477,7 +1479,7 @@ void on_btn_split(const bool split_part)
|
|||
|
||||
for (auto id = 0; id < model_object->volumes.size(); id++)
|
||||
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.option<ConfigOptionInt>("extruder")->value : 0,
|
||||
false);
|
||||
|
|
|
@ -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",
|
||||
"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",
|
||||
"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",
|
||||
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
|
||||
"support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
|
||||
|
|
|
@ -880,6 +880,7 @@ void TabPrint::build()
|
|||
page = add_options_page(_(L("Support material")), "building.png");
|
||||
optgroup = page->new_optgroup(_(L("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_enforce_layers");
|
||||
|
||||
|
@ -1219,13 +1220,15 @@ void TabPrint::update()
|
|||
|
||||
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_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_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",
|
||||
"dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
|
||||
"support_material_xy_spacing" })
|
||||
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",
|
||||
"support_material_interface_speed", "support_material_interface_contact_loops" })
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "Strings.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -43,13 +42,13 @@ public:
|
|||
}
|
||||
|
||||
/// Message shown on the next status update.
|
||||
virtual void message(const string&) = 0;
|
||||
virtual void message(const std::string&) = 0;
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; }
|
||||
|
@ -61,7 +60,7 @@ public:
|
|||
virtual void cancel() { cancelfunc_(); }
|
||||
|
||||
/// 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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
#ifndef STRINGS_HPP
|
||||
#define STRINGS_HPP
|
||||
|
||||
#include "GUI/GUI.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
using string = wxString;
|
||||
}
|
||||
|
||||
#endif // STRINGS_HPP
|
|
@ -231,7 +231,12 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
spi.port = path;
|
||||
#ifdef __linux__
|
||||
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 pid = sysfs_tty_prop_hex(name, "idProduct");
|
||||
if (vid && pid) {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
PrintController *print_ctl();
|
||||
void set_model(Model *model);
|
||||
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();
|
||||
};
|
|
@ -186,3 +186,8 @@ void reset_double_slider()
|
|||
void enable_action_buttons(bool 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); %};
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
%code%{ RETVAL = &THIS->thin_fills; %};
|
||||
Ref<SurfaceCollection> fill_surfaces()
|
||||
%code%{ RETVAL = &THIS->fill_surfaces; %};
|
||||
Ref<SurfaceCollection> perimeter_surfaces()
|
||||
%code%{ RETVAL = &THIS->perimeter_surfaces; %};
|
||||
Polygons bridged()
|
||||
%code%{ RETVAL = THIS->bridged; %};
|
||||
Ref<PolylineCollection> unsupported_bridge_edges()
|
||||
|
|
|
@ -340,9 +340,19 @@ ModelMaterial::attributes()
|
|||
%code%{ RETVAL = &THIS->mesh; %};
|
||||
|
||||
bool modifier()
|
||||
%code%{ RETVAL = THIS->modifier; %};
|
||||
%code%{ RETVAL = THIS->is_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);
|
||||
|
||||
|
|
|
@ -267,6 +267,36 @@ Print::total_cost(...)
|
|||
THIS->total_cost = (double)SvNV(ST(1));
|
||||
}
|
||||
RETVAL = THIS->total_cost;
|
||||
OUTPUT:
|
||||
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
|
||||
%}
|
||||
|
|
|
@ -48,6 +48,11 @@ trace(level, message)
|
|||
CODE:
|
||||
Slic3r::trace(level, message);
|
||||
|
||||
void
|
||||
disable_multi_threading()
|
||||
CODE:
|
||||
Slic3r::disable_multi_threading();
|
||||
|
||||
void
|
||||
set_var_dir(dir)
|
||||
char *dir;
|
||||
|
|
Loading…
Reference in a new issue