Merge remote-tracking branch 'origin/master' into profile_changes_reset
This commit is contained in:
commit
08a8fe84a4
3
Build.PL
3
Build.PL
@ -40,10 +40,7 @@ if ($gui) {
|
||||
Socket 2.016
|
||||
);
|
||||
%recommends = qw(
|
||||
Growl::GNTP 0.15
|
||||
Wx::GLCanvas 0
|
||||
LWP::UserAgent 0
|
||||
Net::Bonjour 0
|
||||
);
|
||||
if ($^O eq 'MSWin32') {
|
||||
$recommends{"Win32::TieRegistry"} = 0;
|
||||
|
@ -9,13 +9,11 @@ use List::Util qw(first);
|
||||
use Slic3r::GUI::2DBed;
|
||||
use Slic3r::GUI::AboutDialog;
|
||||
use Slic3r::GUI::BedShapeDialog;
|
||||
use Slic3r::GUI::BonjourBrowser;
|
||||
use Slic3r::GUI::ConfigWizard;
|
||||
use Slic3r::GUI::Controller;
|
||||
use Slic3r::GUI::Controller::ManualControlDialog;
|
||||
use Slic3r::GUI::Controller::PrinterPanel;
|
||||
use Slic3r::GUI::MainFrame;
|
||||
use Slic3r::GUI::Notifier;
|
||||
use Slic3r::GUI::Plater;
|
||||
use Slic3r::GUI::Plater::2D;
|
||||
use Slic3r::GUI::Plater::2DToolpaths;
|
||||
@ -34,7 +32,6 @@ use Slic3r::GUI::SystemInfo;
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
|
||||
our $have_LWP = eval "use LWP::UserAgent; 1";
|
||||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font);
|
||||
use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU);
|
||||
@ -88,7 +85,6 @@ sub OnInit {
|
||||
Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir);
|
||||
Slic3r::GUI::set_wxapp($self);
|
||||
|
||||
$self->{notifier} = Slic3r::GUI::Notifier->new;
|
||||
$self->{app_config} = Slic3r::GUI::AppConfig->new;
|
||||
$self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
|
||||
|
||||
@ -177,6 +173,13 @@ sub recreate_GUI{
|
||||
$topwindow->Destroy;
|
||||
}
|
||||
|
||||
EVT_IDLE($self->{mainframe}, sub {
|
||||
while (my $cb = shift @cb) {
|
||||
$cb->();
|
||||
}
|
||||
$self->{app_config}->save if $self->{app_config}->dirty;
|
||||
});
|
||||
|
||||
my $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only;
|
||||
if ($run_wizard) {
|
||||
# On OSX the UI was not initialized correctly if the wizard was called
|
||||
@ -271,7 +274,9 @@ sub notify {
|
||||
$frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO)
|
||||
unless ($frame->IsActive);
|
||||
|
||||
$self->{notifier}->notify($message);
|
||||
# There used to be notifier using a Growl application for OSX, but Growl is dead.
|
||||
# The notifier also supported the Linux X D-bus notifications, but that support was broken.
|
||||
#TODO use wxNotificationMessage?
|
||||
}
|
||||
|
||||
# Called after the Preferences dialog is closed and the program settings are saved.
|
||||
|
@ -1,53 +0,0 @@
|
||||
# A tiny dialog to select an OctoPrint device to print to.
|
||||
|
||||
package Slic3r::GUI::BonjourBrowser;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $devices) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
|
||||
$self->{devices} = $devices;
|
||||
|
||||
# label
|
||||
my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
# selector
|
||||
$self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize,
|
||||
[ map $_->name, @{$self->{devices}} ]);
|
||||
|
||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$main_sizer->Add($text, 1, wxEXPAND | wxALL, 10);
|
||||
$main_sizer->Add($choice, 1, wxEXPAND | wxALL, 10);
|
||||
$main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
|
||||
|
||||
$self->SetSizer($main_sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$main_sizer->SetSizeHints($self);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub GetValue {
|
||||
my ($self) = @_;
|
||||
return $self->{devices}[ $self->{choice}->GetSelection ]->address;
|
||||
}
|
||||
sub GetPort {
|
||||
my ($self) = @_;
|
||||
return $self->{devices}[ $self->{choice}->GetSelection ]->port;
|
||||
}
|
||||
|
||||
1;
|
@ -25,10 +25,6 @@ our $last_config;
|
||||
our $VALUE_CHANGE_EVENT = Wx::NewEventType;
|
||||
# 2) To inform about a preset selection change or a "modified" status change.
|
||||
our $PRESETS_CHANGED_EVENT = Wx::NewEventType;
|
||||
# 3) To inform about a click on Browse button
|
||||
our $BUTTON_BROWSE_EVENT = Wx::NewEventType;
|
||||
# 4) To inform about a click on Test button
|
||||
our $BUTTON_TEST_EVENT = Wx::NewEventType;
|
||||
|
||||
sub new {
|
||||
my ($class, %params) = @_;
|
||||
@ -169,59 +165,7 @@ sub _init_tabpanel {
|
||||
}
|
||||
}
|
||||
});
|
||||
# The following event is emited by the C++ Tab implementation ,
|
||||
# when the Browse button was clicked
|
||||
EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
my $msg = $event->GetString;
|
||||
|
||||
# look for devices
|
||||
my $entries;
|
||||
{
|
||||
my $res = Net::Bonjour->new('http');
|
||||
$res->discover;
|
||||
$entries = [ $res->entries ];
|
||||
}
|
||||
if (@{$entries}) {
|
||||
my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
|
||||
my $tab = Slic3r::GUI::get_preset_tab("printer");
|
||||
$tab->load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort)
|
||||
if $dlg->ShowModal == wxID_OK;
|
||||
} else {
|
||||
Wx::MessageDialog->new($self, L('No Bonjour device found'), L('Device Browser'), wxOK | wxICON_INFORMATION)->ShowModal;
|
||||
}
|
||||
});
|
||||
# The following event is emited by the C++ Tab implementation ,
|
||||
# when the Test button was clicked
|
||||
EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
my $msg = $event->GetString;
|
||||
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
|
||||
my $config = Slic3r::GUI::get_preset_tab("printer")->get_config;
|
||||
my $res = $ua->get(
|
||||
"http://" . $config->octoprint_host . "/api/version",
|
||||
'X-Api-Key' => $config->octoprint_apikey,
|
||||
);
|
||||
if ($res->is_success) {
|
||||
Slic3r::GUI::show_info($self, L("Connection to OctoPrint works correctly."), _L("Success!"));
|
||||
} else {
|
||||
Slic3r::GUI::show_error($self,
|
||||
L("I wasn't able to connect to OctoPrint (") . $res->status_line .
|
||||
L("). Check hostname and OctoPrint version (at least 1.1.0 is required)."));
|
||||
}
|
||||
});
|
||||
# A variable to inform C++ Tab implementation about disabling of Browse button
|
||||
$self->{is_disabled_button_browse} = (!eval "use Net::Bonjour; 1") ? 1 : 0 ;
|
||||
# A variable to inform C++ Tab implementation about user_agent
|
||||
$self->{is_user_agent} = (eval "use LWP::UserAgent; 1") ? 1 : 0 ;
|
||||
Slic3r::GUI::create_preset_tabs($self->{no_controller},
|
||||
$self->{is_disabled_button_browse},
|
||||
$self->{is_user_agent},
|
||||
$VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT,
|
||||
$BUTTON_BROWSE_EVENT, $BUTTON_TEST_EVENT);
|
||||
Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT);
|
||||
$self->{options_tabs} = {};
|
||||
for my $tab_name (qw(print filament printer)) {
|
||||
$self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name");
|
||||
|
@ -1,46 +0,0 @@
|
||||
# Notify about the end of slicing.
|
||||
# The notifications are sent out using the Growl protocol if installed, and using DBus XWindow protocol.
|
||||
|
||||
package Slic3r::GUI::Notifier;
|
||||
use Moo;
|
||||
|
||||
has 'growler' => (is => 'rw');
|
||||
|
||||
my $icon = Slic3r::var("Slic3r.png");
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
if (eval 'use Growl::GNTP; 1') {
|
||||
# register with growl
|
||||
eval {
|
||||
$self->growler(Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $icon));
|
||||
$self->growler->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]);
|
||||
};
|
||||
# if register() fails (for example because of a timeout), disable growler at all
|
||||
$self->growler(undef) if $@;
|
||||
}
|
||||
}
|
||||
|
||||
sub notify {
|
||||
my ($self, $message) = @_;
|
||||
my $title = 'Slicing Done!';
|
||||
|
||||
eval {
|
||||
$self->growler->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message)
|
||||
if $self->growler;
|
||||
};
|
||||
# Net::DBus is broken in multithreaded environment
|
||||
if (0 && eval 'use Net::DBus; 1') {
|
||||
eval {
|
||||
my $session = Net::DBus->session;
|
||||
my $serv = $session->get_service('org.freedesktop.Notifications');
|
||||
my $notifier = $serv->get_object('/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications');
|
||||
$notifier->Notify('Slic3r', 0, $icon, $title, $message, [], {}, -1);
|
||||
undef $Net::DBus::bus_session;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
@ -8,7 +8,6 @@ use utf8;
|
||||
use File::Basename qw(basename dirname);
|
||||
use List::Util qw(sum first max);
|
||||
use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg);
|
||||
use LWP::UserAgent;
|
||||
use threads::shared qw(shared_clone);
|
||||
use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
|
||||
:panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap);
|
||||
@ -1334,6 +1333,9 @@ sub export_gcode {
|
||||
} else {
|
||||
my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
|
||||
Slic3r::GUI::catch_error($self) and return;
|
||||
# If possible, remove accents from accented latin characters.
|
||||
# This function is useful for generating file names to be processed by legacy firmwares.
|
||||
$default_output_file = Slic3r::GUI::fold_utf8_to_ascii($default_output_file);
|
||||
my $dlg = Wx::FileDialog->new($self, L('Save G-code file as:'),
|
||||
wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)),
|
||||
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
@ -1941,7 +1943,7 @@ sub selection_changed {
|
||||
$self->{object_info_manifold}->SetToolTipString($message);
|
||||
$self->{object_info_manifold_warning_icon}->SetToolTipString($message);
|
||||
} else {
|
||||
$self->{object_info_manifold}->SetLabel(L("Yes"));
|
||||
$self->{object_info_manifold}->SetLabel(L("Yes"));
|
||||
$self->{object_info_manifold_warning_icon}->Hide;
|
||||
}
|
||||
} else {
|
||||
|
@ -7,7 +7,6 @@ require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(_eq);
|
||||
|
||||
use IO::Scalar;
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(epsilon X Y Z);
|
||||
|
||||
|
@ -1,177 +0,0 @@
|
||||
# 2D cut in the XZ plane through the toolpaths.
|
||||
# For debugging purposes.
|
||||
|
||||
package Slic3r::Test::SectionCut;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first min max);
|
||||
use Slic3r::Geometry qw(unscale);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
use SVG;
|
||||
use Slic3r::SVG;
|
||||
|
||||
has 'print' => (is => 'ro', required => 1);
|
||||
has 'scale' => (is => 'ro', default => sub { 30 });
|
||||
has 'y_percent' => (is => 'ro', default => sub { 0.5 }); # Y coord of section line expressed as factor
|
||||
has 'line' => (is => 'rw');
|
||||
has '_height' => (is => 'rw');
|
||||
has '_svg' => (is => 'rw');
|
||||
has '_svg_style' => (is => 'rw', default => sub { {} });
|
||||
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
# calculate the Y coordinate of the section line
|
||||
my $bb = $self->print->bounding_box;
|
||||
my $y = ($bb->y_min + $bb->y_max) * $self->y_percent;
|
||||
|
||||
# store our section line
|
||||
$self->line(Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ]));
|
||||
}
|
||||
|
||||
sub export_svg {
|
||||
my $self = shift;
|
||||
my ($filename) = @_;
|
||||
|
||||
# get bounding box of print and its height
|
||||
# (Print should return a BoundingBox3 object instead)
|
||||
my $bb = $self->print->bounding_box;
|
||||
my $print_size = $bb->size;
|
||||
$self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
|
||||
|
||||
# initialize the SVG canvas
|
||||
$self->_svg(my $svg = SVG->new(
|
||||
width => $self->scale * unscale($print_size->x),
|
||||
height => $self->scale * $self->_height,
|
||||
));
|
||||
|
||||
# set default styles
|
||||
$self->_svg_style->{'stroke-width'} = 1;
|
||||
$self->_svg_style->{'fill-opacity'} = 0.5;
|
||||
$self->_svg_style->{'stroke-opacity'} = 0.2;
|
||||
|
||||
# plot perimeters
|
||||
$self->_svg_style->{'stroke'} = '#EE0000';
|
||||
$self->_svg_style->{'fill'} = '#FF0000';
|
||||
$self->_plot_group(sub { map @{$_->perimeters}, @{$_[0]->regions} });
|
||||
|
||||
# plot infill
|
||||
$self->_svg_style->{'stroke'} = '#444444';
|
||||
$self->_svg_style->{'fill'} = '#454545';
|
||||
$self->_plot_group(sub { map @{$_->fills}, @{$_[0]->regions} });
|
||||
|
||||
# plot support material
|
||||
$self->_svg_style->{'stroke'} = '#12EF00';
|
||||
$self->_svg_style->{'fill'} = '#22FF00';
|
||||
$self->_plot_group(sub { $_[0]->isa('Slic3r::Layer::Support') ? ($_[0]->support_fills) : () });
|
||||
|
||||
Slic3r::open(\my $fh, '>', $filename);
|
||||
print $fh $svg->xmlify;
|
||||
close $fh;
|
||||
printf "Section cut SVG written to %s\n", $filename;
|
||||
}
|
||||
|
||||
sub _plot_group {
|
||||
my $self = shift;
|
||||
my ($filter) = @_;
|
||||
|
||||
my $bb = $self->print->bounding_box;
|
||||
my $g = $self->_svg->group(style => { %{$self->_svg_style} });
|
||||
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
foreach my $copy (@{$object->_shifted_copies}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
# get all ExtrusionPath objects
|
||||
my @paths = map $_->clone,
|
||||
map { ($_->isa('Slic3r::ExtrusionLoop') || $_->isa('Slic3r::ExtrusionPath::Collection')) ? @$_ : $_ }
|
||||
grep defined $_,
|
||||
$filter->($layer);
|
||||
|
||||
# move paths to location of copy
|
||||
$_->polyline->translate(@$copy) for @paths;
|
||||
|
||||
if (0) {
|
||||
# export plan with section line and exit
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"line.svg",
|
||||
no_arrows => 1,
|
||||
lines => [ $self->line ],
|
||||
red_polylines => [ map $_->polyline, @paths ],
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach my $path (@paths) {
|
||||
foreach my $line (@{$path->lines}) {
|
||||
my @intersections = @{intersection_pl(
|
||||
[ $self->line->as_polyline ],
|
||||
$line->grow(Slic3r::Geometry::scale $path->width/2),
|
||||
)};
|
||||
|
||||
die "Intersection has more than two points!\n"
|
||||
if defined first { @$_ > 2 } @intersections;
|
||||
|
||||
# turn intersections to lines
|
||||
my @lines = map Slic3r::Line->new(@$_), @intersections;
|
||||
|
||||
# align intersections to canvas
|
||||
$_->translate(-$bb->x_min, 0) for @lines;
|
||||
|
||||
# we want lines oriented from left to right in order to draw
|
||||
# rectangles correctly
|
||||
foreach my $line (@lines) {
|
||||
$line->reverse if $line->a->x > $line->b->x;
|
||||
}
|
||||
|
||||
if ($path->is_bridge) {
|
||||
foreach my $line (@lines) {
|
||||
my $radius = $path->width / 2;
|
||||
my $width = unscale abs($line->b->x - $line->a->x);
|
||||
if ((10 * $radius) < $width) {
|
||||
# we're cutting the path in the longitudinal direction, so we've got a rectangle
|
||||
$g->rectangle(
|
||||
'x' => $self->scale * unscale($line->a->x),
|
||||
'y' => $self->scale * $self->_y($layer->print_z),
|
||||
'width' => $self->scale * $width,
|
||||
'height' => $self->scale * $radius * 2,
|
||||
'rx' => $self->scale * $radius * 0.35,
|
||||
'ry' => $self->scale * $radius * 0.35,
|
||||
);
|
||||
} else {
|
||||
$g->circle(
|
||||
'cx' => $self->scale * (unscale($line->a->x) + $radius),
|
||||
'cy' => $self->scale * $self->_y($layer->print_z - $radius),
|
||||
'r' => $self->scale * $radius,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach my $line (@lines) {
|
||||
my $height = $path->height;
|
||||
$height = $layer->height if $height == -1;
|
||||
$g->rectangle(
|
||||
'x' => $self->scale * unscale($line->a->x),
|
||||
'y' => $self->scale * $self->_y($layer->print_z),
|
||||
'width' => $self->scale * unscale($line->b->x - $line->a->x),
|
||||
'height' => $self->scale * $height,
|
||||
'rx' => $self->scale * $height * 0.5,
|
||||
'ry' => $self->scale * $height * 0.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _y {
|
||||
my $self = shift;
|
||||
my ($y) = @_;
|
||||
|
||||
return $self->_height - $y;
|
||||
}
|
||||
|
||||
1;
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
resources/localization/it/Slic3rPE.mo
Normal file
BIN
resources/localization/it/Slic3rPE.mo
Normal file
Binary file not shown.
3515
resources/localization/it/Slic3rPE_it.po
Normal file
3515
resources/localization/it/Slic3rPE_it.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,8 @@ xs/src/slic3r/GUI/2DBed.cpp
|
||||
xs/src/slic3r/GUI/PresetHints.cpp
|
||||
xs/src/slic3r/GUI/Preferences.hpp
|
||||
xs/src/slic3r/GUI/Preferences.cpp
|
||||
xs/src/slic3r/GUI/BonjourDialog.cpp
|
||||
xs/src/slic3r/Utils/OctoPrint.cpp
|
||||
xs/src/libslic3r/PrintConfig.cpp
|
||||
xs/src/libslic3r/GCode/PreviewData.cpp
|
||||
lib/Slic3r/GUI.pm
|
||||
|
@ -909,10 +909,10 @@ use_volumetric_e = 0
|
||||
variable_layer_height = 1
|
||||
wipe = 1
|
||||
z_offset = 0
|
||||
printer_model = MK2S
|
||||
printer_variant = 0.4
|
||||
default_print_profile = 0.15mm OPTIMAL
|
||||
default_filament_profile = Prusa PLA
|
||||
#printer_model = MK2S
|
||||
#printer_variant = 0.4
|
||||
#default_print_profile = 0.15mm OPTIMAL
|
||||
#default_filament_profile = Prusa PLA
|
||||
|
||||
[printer:*multimaterial*]
|
||||
inherits = *common*
|
||||
@ -928,7 +928,7 @@ retract_restart_extra = 0
|
||||
retract_restart_extra_toolchange = 0
|
||||
retract_speed = 80
|
||||
single_extruder_multi_material = 1
|
||||
printer_model = MK2SMM
|
||||
#printer_model = MK2SMM
|
||||
|
||||
[printer:*mm-single*]
|
||||
inherits = *multimaterial*
|
||||
@ -956,15 +956,15 @@ nozzle_diameter = 0.25
|
||||
retract_length = 1
|
||||
retract_speed = 50
|
||||
variable_layer_height = 0
|
||||
printer_variant = 0.25
|
||||
default_print_profile = 0.10mm DETAIL 0.25 nozzle
|
||||
#printer_variant = 0.25
|
||||
#default_print_profile = 0.10mm DETAIL 0.25 nozzle
|
||||
|
||||
[printer:Original Prusa i3 MK2 0.6 nozzle]
|
||||
inherits = *common*
|
||||
max_layer_height = 0.35
|
||||
min_layer_height = 0.1
|
||||
nozzle_diameter = 0.6
|
||||
printer_variant = 0.6
|
||||
#printer_variant = 0.6
|
||||
|
||||
[printer:Original Prusa i3 MK2 MM Single Mode]
|
||||
inherits = *mm-single*
|
||||
@ -972,7 +972,7 @@ inherits = *mm-single*
|
||||
[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle]
|
||||
inherits = *mm-single*
|
||||
nozzle_diameter = 0.6
|
||||
printer_variant = 0.6
|
||||
#printer_variant = 0.6
|
||||
|
||||
[printer:Original Prusa i3 MK2 MultiMaterial]
|
||||
inherits = *mm-multi*
|
||||
@ -981,7 +981,7 @@ nozzle_diameter = 0.4,0.4,0.4,0.4
|
||||
[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle]
|
||||
inherits = *mm-multi*
|
||||
nozzle_diameter = 0.6,0.6,0.6,0.6
|
||||
printer_variant = 0.6
|
||||
#printer_variant = 0.6
|
||||
|
||||
[printer:Original Prusa i3 MK3]
|
||||
inherits = *common*
|
||||
@ -989,8 +989,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn
|
||||
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n
|
||||
retract_lift_below = 209
|
||||
start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif}
|
||||
printer_model = MK3
|
||||
default_print_profile = 0.15mm OPTIMAL MK3
|
||||
#printer_model = MK3
|
||||
#default_print_profile = 0.15mm OPTIMAL MK3
|
||||
|
||||
[printer:Original Prusa i3 MK3 0.25 nozzle]
|
||||
inherits = *common*
|
||||
@ -999,8 +999,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn
|
||||
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n
|
||||
retract_lift_below = 209
|
||||
start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif}
|
||||
printer_model = MK3
|
||||
default_print_profile = 0.10mm DETAIL MK3
|
||||
#printer_model = MK3
|
||||
#default_print_profile = 0.10mm DETAIL MK3
|
||||
|
||||
[printer:Original Prusa i3 MK3 0.6 nozzle]
|
||||
inherits = *common*
|
||||
@ -1009,5 +1009,10 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn
|
||||
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n
|
||||
retract_lift_below = 209
|
||||
start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif}
|
||||
printer_model = MK3
|
||||
default_print_profile = 0.15mm OPTIMAL MK3
|
||||
#printer_model = MK3
|
||||
#default_print_profile = 0.15mm OPTIMAL MK3
|
||||
|
||||
[presets]
|
||||
print = 0.15mm OPTIMAL MK3
|
||||
printer = Original Prusa i3 MK3
|
||||
filament = Prusa PLA
|
||||
|
1
t/svg.t
1
t/svg.t
@ -10,6 +10,7 @@ BEGIN {
|
||||
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
use IO::Scalar;
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
|
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
# This script generates section cuts from a given G-Code file
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use Getopt::Long qw(:config no_auto_abbrev);
|
||||
use IO::All;
|
||||
use List::Util qw(max);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
|
||||
use Slic3r::Test;
|
||||
use SVG;
|
||||
|
||||
my %opt = (
|
||||
layer_height => 0.2,
|
||||
extrusion_width => 0.5,
|
||||
scale => 30,
|
||||
);
|
||||
{
|
||||
my %options = (
|
||||
'help' => sub { usage() },
|
||||
'layer-height|h=f' => \$opt{layer_height},
|
||||
'extrusion-width|w=f' => \$opt{extrusion_width},
|
||||
'scale|s=i' => \$opt{scale},
|
||||
);
|
||||
GetOptions(%options) or usage(1);
|
||||
$ARGV[0] or usage(1);
|
||||
}
|
||||
|
||||
{
|
||||
my $input_file = $ARGV[0];
|
||||
my $output_file = $input_file;
|
||||
$output_file =~ s/\.(?:gcode|gco|ngc|g)$/.svg/;
|
||||
|
||||
# read paths
|
||||
my %paths = (); # z => [ path, path ... ]
|
||||
Slic3r::GCode::Reader->new->parse(io($input_file)->all, sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && $info->{extruding}) {
|
||||
$paths{ $self->Z } ||= [];
|
||||
push @{ $paths{ $self->Z } }, Slic3r::Line->new(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
# calculate print extents
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %paths ]);
|
||||
|
||||
# calculate section line
|
||||
my $section_y = $bounding_box->center->[Y];
|
||||
my $section_line = [
|
||||
[ $bounding_box->x_min, $section_y ],
|
||||
[ $bounding_box->x_max, $section_y ],
|
||||
];
|
||||
|
||||
# initialize output
|
||||
my $max_z = max(keys %paths);
|
||||
my $svg = SVG->new(
|
||||
width => $opt{scale} * $bounding_box->size->[X],
|
||||
height => $opt{scale} * $max_z,
|
||||
);
|
||||
|
||||
# put everything into a group
|
||||
my $g = $svg->group(style => {
|
||||
'stroke-width' => 1,
|
||||
'stroke' => '#444444',
|
||||
'fill' => 'grey',
|
||||
});
|
||||
|
||||
# draw paths
|
||||
foreach my $z (sort keys %paths) {
|
||||
foreach my $line (@{ $paths{$z} }) {
|
||||
my @intersections = @{intersection_pl(
|
||||
[ $section_line ],
|
||||
[ _grow($line, $opt{extrusion_width}/2) ],
|
||||
)};
|
||||
|
||||
$g->rectangle(
|
||||
'x' => $opt{scale} * ($_->[0][X] - $bounding_box->x_min),
|
||||
'y' => $opt{scale} * ($max_z - $z),
|
||||
'width' => $opt{scale} * abs($_->[1][X] - $_->[0][X]),
|
||||
'height' => $opt{scale} * $opt{layer_height},
|
||||
'rx' => $opt{scale} * $opt{layer_height} * 0.35,
|
||||
'ry' => $opt{scale} * $opt{layer_height} * 0.35,
|
||||
) for @intersections;
|
||||
}
|
||||
}
|
||||
|
||||
# write output
|
||||
Slic3r::open(\my $fh, '>', $output_file);
|
||||
print $fh $svg->xmlify;
|
||||
close $fh;
|
||||
printf "Section cut SVG written to %s\n", $output_file;
|
||||
}
|
||||
|
||||
# replace built-in Line->grow method which relies on int_offset()
|
||||
sub _grow {
|
||||
my ($line, $distance) = @_;
|
||||
|
||||
my $polygon = [ @$line, CORE::reverse @$line[1..($#$line-1)] ];
|
||||
return @{Math::Clipper::offset([$polygon], $distance, 100000, JT_SQUARE, 2)};
|
||||
}
|
||||
|
||||
sub usage {
|
||||
my ($exit_code) = @_;
|
||||
|
||||
print <<"EOF";
|
||||
Usage: gcode_sectioncut.pl [ OPTIONS ] file.gcode
|
||||
|
||||
--help Output this usage screen and exit
|
||||
--layer-height, -h Use the specified layer height
|
||||
--extrusion-width, -w Use the specified extrusion width
|
||||
--scale Factor for converting G-code units to SVG units
|
||||
|
||||
EOF
|
||||
exit ($exit_code || 0);
|
||||
}
|
||||
|
||||
__END__
|
@ -201,6 +201,10 @@ add_library(libslic3r_gui STATIC
|
||||
${LIBDIR}/slic3r/GUI/2DBed.hpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
|
||||
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
|
||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
|
||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
|
||||
${LIBDIR}/slic3r/Utils/Http.cpp
|
||||
${LIBDIR}/slic3r/Utils/Http.hpp
|
||||
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
|
||||
|
@ -1427,10 +1427,12 @@ Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &
|
||||
// Each linear fill covers 1/3 of the target coverage.
|
||||
FillParams params2 = params;
|
||||
params2.density *= 0.333333333f;
|
||||
FillParams params3 = params2;
|
||||
params3.dont_connect = true;
|
||||
Polylines polylines_out;
|
||||
if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) ||
|
||||
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) ||
|
||||
! fill_surface_by_lines(surface, params2, float(2. * M_PI / 3.), 0., polylines_out)) {
|
||||
! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0., polylines_out)) {
|
||||
printf("FillTriangles::fill_surface() failed to fill a region.\n");
|
||||
}
|
||||
return polylines_out;
|
||||
@ -1441,10 +1443,12 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶
|
||||
// Each linear fill covers 1/3 of the target coverage.
|
||||
FillParams params2 = params;
|
||||
params2.density *= 0.333333333f;
|
||||
FillParams params3 = params2;
|
||||
params3.dont_connect = true;
|
||||
Polylines polylines_out;
|
||||
if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) ||
|
||||
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) ||
|
||||
! fill_surface_by_lines(surface, params2, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) {
|
||||
! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) {
|
||||
printf("FillStars::fill_surface() failed to fill a region.\n");
|
||||
}
|
||||
return polylines_out;
|
||||
@ -1455,12 +1459,14 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶
|
||||
// Each linear fill covers 1/3 of the target coverage.
|
||||
FillParams params2 = params;
|
||||
params2.density *= 0.333333333f;
|
||||
FillParams params3 = params2;
|
||||
params3.dont_connect = true;
|
||||
Polylines polylines_out;
|
||||
coordf_t dx = sqrt(0.5) * z;
|
||||
if (! fill_surface_by_lines(surface, params2, 0.f, dx, polylines_out) ||
|
||||
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - dx, polylines_out) ||
|
||||
// Rotated by PI*2/3 + PI to achieve reverse sloping wall.
|
||||
! fill_surface_by_lines(surface, params2, float(M_PI * 2. / 3.), dx, polylines_out)) {
|
||||
! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), dx, polylines_out)) {
|
||||
printf("FillCubic::fill_surface() failed to fill a region.\n");
|
||||
}
|
||||
return polylines_out;
|
||||
|
@ -107,9 +107,11 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
TYPE_WIPE = 1 << 8,
|
||||
TYPE_G4 = 1 << 9,
|
||||
TYPE_G92 = 1 << 10,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
};
|
||||
|
||||
Line(unsigned int type, size_t line_start, size_t line_end) :
|
||||
@ -187,9 +189,13 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
new_pos[axis] = float(atof(++c));
|
||||
if (axis == 4)
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
if ((line.type & Adjustment::Line::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= Adjustment::Line::TYPE_HAS_F;
|
||||
}
|
||||
}
|
||||
// Skip this word.
|
||||
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
|
||||
@ -227,6 +233,8 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
|
||||
if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
|
||||
line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed);
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0);
|
||||
Adjustment::Line &sm = adjustment->lines[active_speed_modifier];
|
||||
sm.length += line.length;
|
||||
sm.time += line.time;
|
||||
@ -415,17 +423,20 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
|
||||
};
|
||||
change_extruder_set_fan();
|
||||
|
||||
size_t pos = 0;
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
for (const Adjustment::Line *line : lines) {
|
||||
if (line->line_start > pos)
|
||||
new_gcode.append(gcode.c_str() + pos, line->line_start - pos);
|
||||
const char *line_start = gcode.c_str() + line->line_start;
|
||||
const char *line_end = gcode.c_str() + line->line_end;
|
||||
if (line_start > pos)
|
||||
new_gcode.append(pos, line_start - pos);
|
||||
if (line->type & Adjustment::Line::TYPE_SET_TOOL) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(gcode.c_str() + line->line_start + toolchange_prefix.size());
|
||||
unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size());
|
||||
if (new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
change_extruder_set_fan();
|
||||
}
|
||||
new_gcode.append(gcode.c_str() + line->line_start, line->line_end - line->line_start);
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) {
|
||||
if (bridge_fan_control)
|
||||
new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true);
|
||||
@ -434,40 +445,80 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
|
||||
new_gcode += m_gcodegen.writer().set_fan(fan_speed, true);
|
||||
} else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
} else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) {
|
||||
// Start of the comment. The line type indicates there must be some comment present.
|
||||
const char *end = strchr(gcode.c_str() + line->line_start, ';');
|
||||
} else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) {
|
||||
// Find the start of a comment, or roll to the end of line.
|
||||
const char *end = line_start;
|
||||
for (; end < line_end && *end != ';'; ++ end);
|
||||
// Find the 'F' word.
|
||||
const char *fpos = strstr(line_start + 2, " F") + 2;
|
||||
int new_feedrate = current_feedrate;
|
||||
bool modify = false;
|
||||
assert(fpos != nullptr);
|
||||
if (line->slowdown) {
|
||||
// Replace the feedrate.
|
||||
const char *pos = strstr(gcode.c_str() + line->line_start + 2, " F") + 2;
|
||||
new_gcode.append(gcode.c_str() + line->line_start, pos - gcode.c_str() - line->line_start);
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", int(floor(60. * (line->length / line->time) + 0.5)));
|
||||
new_gcode += buf;
|
||||
// Skip the non-whitespaces up to the comment.
|
||||
for (; *pos != ' ' && *pos != ';'; ++ pos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (pos < end)
|
||||
new_gcode.append(pos, end - pos);
|
||||
modify = true;
|
||||
new_feedrate = int(floor(60. * (line->length / line->time) + 0.5));
|
||||
} else {
|
||||
// Append the line without the comment.
|
||||
new_gcode.append(gcode.c_str() + line->line_start, end - gcode.c_str() - line->line_start);
|
||||
new_feedrate = atoi(fpos);
|
||||
if (new_feedrate != current_feedrate) {
|
||||
// Append the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
} else if ((line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) {
|
||||
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
|
||||
end = line_end;
|
||||
} else {
|
||||
// Remove the feedrate from the G0/G1 line.
|
||||
modify = true;
|
||||
}
|
||||
}
|
||||
if (modify) {
|
||||
if (new_feedrate != current_feedrate) {
|
||||
// Replace the feedrate.
|
||||
new_gcode.append(line_start, fpos - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", int(current_feedrate));
|
||||
new_gcode += buf;
|
||||
} else {
|
||||
// Remove the feedrate word.
|
||||
const char *f = fpos;
|
||||
// Roll the pointer before the 'F' word.
|
||||
for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
|
||||
// Append up to the F word, without the trailing whitespace.
|
||||
new_gcode.append(line_start, f - line_start + 1);
|
||||
}
|
||||
// Skip the non-whitespaces of the F parameter up the comment or end of line.
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (fpos < end)
|
||||
new_gcode.append(fpos, end - fpos);
|
||||
// There should never be an empty G1 statement emited by the filter. Such lines should be removed completely.
|
||||
assert(new_gcode.size() < 4 || new_gcode.substr(new_gcode.size() - 4) != "G1 \n");
|
||||
}
|
||||
// Process the rest of the line.
|
||||
if (end < line_end) {
|
||||
if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) {
|
||||
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||
std::string comment(end, line_end);
|
||||
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||
if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER)
|
||||
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||
if (line->type & Adjustment::Line::TYPE_WIPE)
|
||||
boost::replace_all(comment, ";_WIPE", "");
|
||||
new_gcode += comment;
|
||||
} else {
|
||||
// Just attach the rest of the source line.
|
||||
new_gcode.append(end, line_end - end);
|
||||
}
|
||||
}
|
||||
// Process the comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||
std::string comment(end, gcode.c_str() + line->line_end);
|
||||
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||
if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER)
|
||||
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||
if (line->type & Adjustment::Line::TYPE_WIPE)
|
||||
boost::replace_all(comment, ";_WIPE", "");
|
||||
new_gcode += comment;
|
||||
} else {
|
||||
new_gcode.append(gcode.c_str() + line->line_start, line->line_end - line->line_start);
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
}
|
||||
pos = line->line_end;
|
||||
pos = line_end;
|
||||
}
|
||||
if (pos < gcode.size())
|
||||
new_gcode.append(gcode.c_str() + pos, gcode.size() - pos);
|
||||
const char *gcode_end = gcode.c_str() + gcode.size();
|
||||
if (pos < gcode_end)
|
||||
new_gcode.append(pos, gcode_end - pos);
|
||||
|
||||
return new_gcode;
|
||||
}
|
||||
|
@ -987,101 +987,227 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build a map of lines by edge_a_id and a_id.
|
||||
std::vector<IntersectionLine*> by_edge_a_id;
|
||||
std::vector<IntersectionLine*> by_a_id;
|
||||
by_edge_a_id.reserve(lines.size());
|
||||
by_a_id.reserve(lines.size());
|
||||
for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) {
|
||||
if (! line->skip) {
|
||||
if (line->edge_a_id != -1)
|
||||
by_edge_a_id.push_back(&(*line)); // [line->edge_a_id].push_back();
|
||||
if (line->a_id != -1)
|
||||
by_a_id.push_back(&(*line)); // [line->a_id].push_back(&(*line));
|
||||
|
||||
struct OpenPolyline {
|
||||
OpenPolyline() {};
|
||||
OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
|
||||
start(start), end(end), points(std::move(points)), consumed(false) {}
|
||||
void reverse() {
|
||||
std::swap(start, end);
|
||||
std::reverse(points.begin(), points.end());
|
||||
}
|
||||
IntersectionReference start;
|
||||
IntersectionReference end;
|
||||
Points points;
|
||||
bool consumed;
|
||||
};
|
||||
std::vector<OpenPolyline> open_polylines;
|
||||
{
|
||||
// Build a map of lines by edge_a_id and a_id.
|
||||
std::vector<IntersectionLine*> by_edge_a_id;
|
||||
std::vector<IntersectionLine*> by_a_id;
|
||||
by_edge_a_id.reserve(lines.size());
|
||||
by_a_id.reserve(lines.size());
|
||||
for (IntersectionLine &line : lines) {
|
||||
if (! line.skip) {
|
||||
if (line.edge_a_id != -1)
|
||||
by_edge_a_id.emplace_back(&line);
|
||||
if (line.a_id != -1)
|
||||
by_a_id.emplace_back(&line);
|
||||
}
|
||||
}
|
||||
auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
|
||||
auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
|
||||
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
|
||||
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
|
||||
// Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
|
||||
IntersectionLines::iterator it_line_seed = lines.begin();
|
||||
for (;;) {
|
||||
// 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) {
|
||||
first_line = &(*it_line_seed ++);
|
||||
break;
|
||||
}
|
||||
if (first_line == nullptr)
|
||||
break;
|
||||
first_line->skip = true;
|
||||
Points loop_pts;
|
||||
loop_pts.emplace_back(first_line->a);
|
||||
IntersectionLine *last_line = first_line;
|
||||
|
||||
/*
|
||||
printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
|
||||
first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
|
||||
*/
|
||||
|
||||
IntersectionLine key;
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
IntersectionLine* next_line = nullptr;
|
||||
if (last_line->edge_b_id != -1) {
|
||||
key.edge_a_id = last_line->edge_b_id;
|
||||
auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
|
||||
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) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr && last_line->b_id != -1) {
|
||||
key.a_id = last_line->b_id;
|
||||
auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
|
||||
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) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr) {
|
||||
// Check whether we closed this loop.
|
||||
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
|
||||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
loops->emplace_back(std::move(loop_pts));
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
|
||||
#endif
|
||||
} else {
|
||||
// This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
|
||||
loop_pts.emplace_back(last_line->b);
|
||||
open_polylines.emplace_back(OpenPolyline(
|
||||
IntersectionReference(first_line->a_id, first_line->edge_a_id),
|
||||
IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
|
||||
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
|
||||
*/
|
||||
loop_pts.emplace_back(next_line->a);
|
||||
last_line = next_line;
|
||||
next_line->skip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
|
||||
auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
|
||||
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
|
||||
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
|
||||
|
||||
IntersectionLines::iterator it_line_seed = lines.begin();
|
||||
CYCLE: while (1) {
|
||||
// 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) {
|
||||
first_line = &(*it_line_seed ++);
|
||||
break;
|
||||
}
|
||||
if (first_line == nullptr)
|
||||
break;
|
||||
first_line->skip = true;
|
||||
Points loop_pts;
|
||||
loop_pts.push_back(first_line->a);
|
||||
IntersectionLine *last_line = first_line;
|
||||
|
||||
/*
|
||||
printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
|
||||
first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
|
||||
*/
|
||||
|
||||
IntersectionLine key;
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
IntersectionLine* next_line = nullptr;
|
||||
if (last_line->edge_b_id != -1) {
|
||||
key.edge_a_id = last_line->edge_b_id;
|
||||
auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
|
||||
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) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
// Now process the open polylines.
|
||||
if (! open_polylines.empty()) {
|
||||
// Store the end points of open_polylines into vectors sorted
|
||||
struct OpenPolylineEnd {
|
||||
OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
|
||||
OpenPolyline *polyline;
|
||||
// Is it the start or end point?
|
||||
bool start;
|
||||
const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
|
||||
int point_id() const { return ipref().point_id; }
|
||||
int edge_id () const { return ipref().edge_id; }
|
||||
};
|
||||
auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); };
|
||||
auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); };
|
||||
std::vector<OpenPolylineEnd> by_edge_id;
|
||||
std::vector<OpenPolylineEnd> by_point_id;
|
||||
by_edge_id.reserve(2 * open_polylines.size());
|
||||
by_point_id.reserve(2 * open_polylines.size());
|
||||
for (OpenPolyline &opl : open_polylines) {
|
||||
if (opl.start.edge_id != -1)
|
||||
by_edge_id .emplace_back(OpenPolylineEnd(&opl, true));
|
||||
if (opl.end.edge_id != -1)
|
||||
by_edge_id .emplace_back(OpenPolylineEnd(&opl, false));
|
||||
if (opl.start.point_id != -1)
|
||||
by_point_id.emplace_back(OpenPolylineEnd(&opl, true));
|
||||
if (opl.end.point_id != -1)
|
||||
by_point_id.emplace_back(OpenPolylineEnd(&opl, false));
|
||||
}
|
||||
std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower);
|
||||
std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower);
|
||||
|
||||
// Try to connect the loops.
|
||||
for (OpenPolyline &opl : open_polylines) {
|
||||
if (opl.consumed)
|
||||
continue;
|
||||
opl.consumed = true;
|
||||
OpenPolylineEnd end(&opl, false);
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
OpenPolylineEnd* next_start = nullptr;
|
||||
if (end.edge_id() != -1) {
|
||||
auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower);
|
||||
if (it_begin != by_edge_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower);
|
||||
for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge)
|
||||
if (! it_edge->polyline->consumed) {
|
||||
next_start = &(*it_edge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr && last_line->b_id != -1) {
|
||||
key.a_id = last_line->b_id;
|
||||
auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
|
||||
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) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
if (next_start == nullptr && end.point_id() != -1) {
|
||||
auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower);
|
||||
if (it_begin != by_point_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower);
|
||||
for (auto it_point = it_begin; it_point != it_end; ++ it_point)
|
||||
if (! it_point->polyline->consumed) {
|
||||
next_start = &(*it_point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr) {
|
||||
// check whether we closed this loop
|
||||
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
|
||||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
|
||||
// loop is complete
|
||||
loops->emplace_back(std::move(loop_pts));
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
|
||||
#endif
|
||||
goto CYCLE;
|
||||
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));
|
||||
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end());
|
||||
} else {
|
||||
auto it = next_start->polyline->points.rbegin();
|
||||
std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points));
|
||||
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend());
|
||||
}
|
||||
// we can't close this loop!
|
||||
//// push @failed_loops, [@loop];
|
||||
//#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Unable to close this loop having %d points\n", (int)loop_pts.size());
|
||||
//#endif
|
||||
goto CYCLE;
|
||||
end = *next_start;
|
||||
end.start = !end.start;
|
||||
next_start->polyline->points.clear();
|
||||
next_start->polyline->consumed = true;
|
||||
// Check whether we closed this loop.
|
||||
const IntersectionReference &ip1 = opl.start;
|
||||
const IntersectionReference &ip2 = end.ipref();
|
||||
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);
|
||||
// Remove the duplicate last point.
|
||||
opl.points.pop_back();
|
||||
if (opl.points.size() >= 3) {
|
||||
// The closed polygon is patched from pieces with messed up orientation, therefore
|
||||
// the orientation of the patched up polygon is not known.
|
||||
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
|
||||
double area = 0.;
|
||||
for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++)
|
||||
area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y);
|
||||
if (area < 0)
|
||||
std::reverse(opl.points.begin(), opl.points.end());
|
||||
loops->emplace_back(std::move(opl.points));
|
||||
}
|
||||
opl.points.clear();
|
||||
break;
|
||||
}
|
||||
// Continue with the current loop.
|
||||
}
|
||||
/*
|
||||
printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
|
||||
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
|
||||
*/
|
||||
loop_pts.push_back(next_line->a);
|
||||
last_line = next_line;
|
||||
next_line->skip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,11 +85,11 @@ enum FacetEdgeType {
|
||||
feHorizontal
|
||||
};
|
||||
|
||||
class IntersectionPoint : public Point
|
||||
class IntersectionReference
|
||||
{
|
||||
public:
|
||||
IntersectionPoint() : point_id(-1), edge_id(-1) {};
|
||||
// Inherits coord_t x, y
|
||||
IntersectionReference() : point_id(-1), edge_id(-1) {};
|
||||
IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {}
|
||||
// Where is this intersection point located? On mesh vertex or mesh edge?
|
||||
// Only one of the following will be set, the other will remain set to -1.
|
||||
// Index of the mesh vertex.
|
||||
@ -98,6 +98,15 @@ public:
|
||||
int edge_id;
|
||||
};
|
||||
|
||||
class IntersectionPoint : public Point, public IntersectionReference
|
||||
{
|
||||
public:
|
||||
IntersectionPoint() {};
|
||||
IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {}
|
||||
IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {}
|
||||
// Inherits coord_t x, y
|
||||
};
|
||||
|
||||
class IntersectionLine : public Line
|
||||
{
|
||||
public:
|
||||
|
200
xs/src/slic3r/GUI/BonjourDialog.cpp
Normal file
200
xs/src/slic3r/GUI/BonjourDialog.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
|
||||
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/Bonjour.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
struct BonjourReplyEvent : public wxEvent
|
||||
{
|
||||
BonjourReply reply;
|
||||
|
||||
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
|
||||
wxEvent(winid, eventType),
|
||||
reply(std::move(reply))
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new BonjourReplyEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
|
||||
class ReplySet: public std::set<BonjourReply> {};
|
||||
|
||||
struct LifetimeGuard
|
||||
{
|
||||
std::mutex mutex;
|
||||
BonjourDialog *dialog;
|
||||
|
||||
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
|
||||
};
|
||||
|
||||
|
||||
BonjourDialog::BonjourDialog(wxWindow *parent) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
|
||||
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
|
||||
replies(new ReplySet),
|
||||
label(new wxStaticText(this, wxID_ANY, "")),
|
||||
timer(new wxTimer()),
|
||||
timer_state(0)
|
||||
{
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
|
||||
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
|
||||
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
|
||||
|
||||
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
|
||||
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||
|
||||
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
|
||||
this->timer_state = 0;
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||
}
|
||||
|
||||
BonjourDialog::~BonjourDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
bool BonjourDialog::show_and_lookup()
|
||||
{
|
||||
Show(); // Because we need GetId() to work before ShowModal()
|
||||
|
||||
timer->Stop();
|
||||
timer->SetOwner(this);
|
||||
timer_state = 1;
|
||||
timer->Start(1000);
|
||||
wxTimerEvent evt_dummy;
|
||||
on_timer(evt_dummy);
|
||||
|
||||
// The background thread needs to queue messages for this dialog
|
||||
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
|
||||
// Here we put the pointer under a shared_ptr and protect it by a mutex,
|
||||
// so that both threads can access it safely.
|
||||
auto dguard = std::make_shared<LifetimeGuard>(this);
|
||||
|
||||
bonjour = std::move(Bonjour("octoprint")
|
||||
.set_retries(3)
|
||||
.set_timeout(4)
|
||||
.on_reply([dguard](BonjourReply &&reply) {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.on_complete([dguard]() {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.lookup()
|
||||
);
|
||||
|
||||
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
|
||||
{
|
||||
// Tell the background thread the dialog is going away...
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
dguard->dialog = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString BonjourDialog::get_selected() const
|
||||
{
|
||||
auto sel = list->GetFirstSelected();
|
||||
return sel >= 0 ? list->GetItemText(sel) : wxString();
|
||||
}
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
void BonjourDialog::on_reply(BonjourReplyEvent &e)
|
||||
{
|
||||
if (replies->find(e.reply) != replies->end()) {
|
||||
// We already have this reply
|
||||
return;
|
||||
}
|
||||
|
||||
replies->insert(std::move(e.reply));
|
||||
|
||||
auto selected = get_selected();
|
||||
list->DeleteAllItems();
|
||||
|
||||
// The whole list is recreated so that we benefit from it already being sorted in the set.
|
||||
// (And also because wxListView's sorting API is bananas.)
|
||||
for (const auto &reply : *replies) {
|
||||
auto item = list->InsertItem(0, reply.full_address);
|
||||
list->SetItem(item, 1, reply.hostname);
|
||||
list->SetItem(item, 2, reply.service_name);
|
||||
list->SetItem(item, 3, reply.version);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
|
||||
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
|
||||
}
|
||||
|
||||
if (!selected.IsEmpty()) {
|
||||
// Attempt to preserve selection
|
||||
auto hit = list->FindItem(-1, selected);
|
||||
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
|
||||
}
|
||||
}
|
||||
|
||||
void BonjourDialog::on_timer(wxTimerEvent &)
|
||||
{
|
||||
const auto search_str = _(L("Searching for devices"));
|
||||
|
||||
if (timer_state > 0) {
|
||||
const std::string dots(timer_state, '.');
|
||||
label->SetLabel(wxString::Format("%s %s", search_str, dots));
|
||||
timer_state = (timer_state) % 3 + 1;
|
||||
} else {
|
||||
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished."))));
|
||||
timer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
49
xs/src/slic3r/GUI/BonjourDialog.hpp
Normal file
49
xs/src/slic3r/GUI/BonjourDialog.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef slic3r_BonjourDialog_hpp_
|
||||
#define slic3r_BonjourDialog_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class wxListView;
|
||||
class wxStaticText;
|
||||
class wxTimer;
|
||||
class wxTimerEvent;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Bonjour;
|
||||
class BonjourReplyEvent;
|
||||
class ReplySet;
|
||||
|
||||
|
||||
class BonjourDialog: public wxDialog
|
||||
{
|
||||
public:
|
||||
BonjourDialog(wxWindow *parent);
|
||||
BonjourDialog(BonjourDialog &&) = delete;
|
||||
BonjourDialog(const BonjourDialog &) = delete;
|
||||
BonjourDialog &operator=(BonjourDialog &&) = delete;
|
||||
BonjourDialog &operator=(const BonjourDialog &) = delete;
|
||||
~BonjourDialog();
|
||||
|
||||
bool show_and_lookup();
|
||||
wxString get_selected() const;
|
||||
private:
|
||||
wxListView *list;
|
||||
std::unique_ptr<ReplySet> replies;
|
||||
wxStaticText *label;
|
||||
std::shared_ptr<Bonjour> bonjour;
|
||||
std::unique_ptr<wxTimer> timer;
|
||||
unsigned timer_state;
|
||||
|
||||
void on_reply(BonjourReplyEvent &);
|
||||
void on_timer(wxTimerEvent &);
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -273,7 +273,7 @@ void SpinCtrl::BUILD() {
|
||||
// # when it was changed from the text control, so the on_change callback
|
||||
// # gets the old one, and on_kill_focus resets the control to the old value.
|
||||
// # As a workaround, we get the new value from $event->GetString and store
|
||||
// # here temporarily so that we can return it from $self->get_value
|
||||
// # here temporarily so that we can return it from $self->get_value
|
||||
std::string value = e.GetString().utf8_str().data();
|
||||
if (is_matched(value, "^\\d+$"))
|
||||
tmp_value = std::stoi(value);
|
||||
@ -377,9 +377,9 @@ void Choice::set_selection()
|
||||
}
|
||||
}
|
||||
|
||||
void Choice::set_value(const std::string value) //! Redundant?
|
||||
void Choice::set_value(const std::string value, bool change_event) //! Redundant?
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
size_t idx=0;
|
||||
for (auto el : m_opt.enum_values)
|
||||
@ -396,9 +396,9 @@ void Choice::set_value(const std::string value) //! Redundant?
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void Choice::set_value(boost::any value)
|
||||
void Choice::set_value(boost::any value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
switch (m_opt.type){
|
||||
case coInt:
|
||||
@ -441,7 +441,7 @@ void Choice::set_values(const std::vector<std::string> values)
|
||||
return;
|
||||
m_disable_change_event = true;
|
||||
|
||||
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
// # but we want to preserve it
|
||||
auto ww = dynamic_cast<wxComboBox*>(window);
|
||||
auto value = ww->GetValue();
|
||||
@ -553,9 +553,9 @@ void PointCtrl::BUILD()
|
||||
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(const Pointf value)
|
||||
void PointCtrl::set_value(const Pointf value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = true;
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
double val = value.x;
|
||||
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
|
||||
@ -565,7 +565,7 @@ void PointCtrl::set_value(const Pointf value)
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
void PointCtrl::set_value(boost::any value)
|
||||
void PointCtrl::set_value(boost::any value, bool change_event)
|
||||
{
|
||||
Pointf pt;
|
||||
Pointf *ptf = boost::any_cast<Pointf>(&value);
|
||||
@ -591,7 +591,7 @@ void PointCtrl::set_value(boost::any value)
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
set_value(pt);
|
||||
set_value(pt, change_event);
|
||||
}
|
||||
|
||||
boost::any PointCtrl::get_value()
|
||||
|
@ -81,7 +81,7 @@ public:
|
||||
/// Sets a value for this control.
|
||||
/// subclasses should overload with a specific version
|
||||
/// Postcondition: Method does not fire the on_change event.
|
||||
virtual void set_value(boost::any value) = 0;
|
||||
virtual void set_value(boost::any value, bool change_event) = 0;
|
||||
|
||||
/// Gets a boost::any representing this control.
|
||||
/// subclasses should overload with a specific version
|
||||
@ -141,13 +141,13 @@ public:
|
||||
void BUILD();
|
||||
wxWindow* window {nullptr};
|
||||
|
||||
virtual void set_value(std::string value) {
|
||||
m_disable_change_event = true;
|
||||
virtual void set_value(std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
virtual void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
virtual void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
@ -168,13 +168,13 @@ public:
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const bool value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const bool value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
@ -196,13 +196,13 @@ public:
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(boost::any_cast<int>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
@ -225,8 +225,8 @@ public:
|
||||
void BUILD() override;
|
||||
|
||||
void set_selection();
|
||||
void set_value(const std::string value);
|
||||
void set_value(boost::any value);
|
||||
void set_value(const std::string value, bool change_event = false);
|
||||
void set_value(boost::any value, bool change_event = false);
|
||||
void set_values(const std::vector<std::string> values);
|
||||
boost::any get_value() override;
|
||||
|
||||
@ -244,13 +244,13 @@ public:
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(const std::string value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(boost::any value) {
|
||||
m_disable_change_event = true;
|
||||
void set_value(boost::any value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
@ -274,8 +274,8 @@ public:
|
||||
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const Pointf value);
|
||||
void set_value(boost::any value);
|
||||
void set_value(const Pointf value, bool change_event = false);
|
||||
void set_value(boost::any value, bool change_event = false);
|
||||
boost::any get_value() override;
|
||||
|
||||
void enable() override {
|
||||
|
@ -358,24 +358,17 @@ void open_preferences_dialog(int event_preferences)
|
||||
dlg->ShowModal();
|
||||
}
|
||||
|
||||
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test)
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
|
||||
{
|
||||
add_created_tab(new TabPrint (g_wxTabPanel, no_controller));
|
||||
add_created_tab(new TabFilament (g_wxTabPanel, no_controller));
|
||||
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller, is_disabled_button_browse, is_user_agent));
|
||||
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller));
|
||||
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
|
||||
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
|
||||
if (! tab)
|
||||
continue;
|
||||
tab->set_event_value_change(wxEventType(event_value_change));
|
||||
tab->set_event_presets_changed(wxEventType(event_presets_changed));
|
||||
if (tab->name() == "printer"){
|
||||
TabPrinter* tab_printer = static_cast<TabPrinter*>(tab);
|
||||
tab_printer->set_event_button_browse(wxEventType(event_button_browse));
|
||||
tab_printer->set_event_button_test(wxEventType(event_button_test));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,19 +587,6 @@ wxString from_u8(const std::string &str)
|
||||
return wxString::FromUTF8(str.c_str());
|
||||
}
|
||||
|
||||
wxWindow *get_widget_by_id(int id)
|
||||
{
|
||||
if (g_wxMainFrame == nullptr) {
|
||||
throw std::runtime_error("Main frame not set");
|
||||
}
|
||||
|
||||
wxWindow *window = g_wxMainFrame->FindWindow(id);
|
||||
if (window == nullptr) {
|
||||
throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str());
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer)
|
||||
{
|
||||
|
@ -87,9 +87,7 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change);
|
||||
void open_preferences_dialog(int event_preferences);
|
||||
|
||||
// Create a new preset tab (print, filament and printer),
|
||||
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test);
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed);
|
||||
TabIface* get_preset_tab_iface(char *name);
|
||||
|
||||
// add it at the end of the tab panel.
|
||||
@ -128,7 +126,6 @@ wxString L_str(const std::string &str);
|
||||
// Return wxString from std::string in UTF8
|
||||
wxString from_u8(const std::string &str);
|
||||
|
||||
wxWindow *get_widget_by_id(int id);
|
||||
|
||||
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
|
||||
|
||||
|
@ -102,9 +102,9 @@ public:
|
||||
if (m_fields.find(id) == m_fields.end()) return nullptr;
|
||||
return m_fields.at(id).get();
|
||||
}
|
||||
bool set_value(t_config_option_key id, boost::any value) {
|
||||
bool set_value(t_config_option_key id, boost::any value, bool change_event = false) {
|
||||
if (m_fields.find(id) == m_fields.end()) return false;
|
||||
m_fields.at(id)->set_value(value);
|
||||
m_fields.at(id)->set_value(value, change_event);
|
||||
return true;
|
||||
}
|
||||
boost::any get_value(t_config_option_key id) {
|
||||
|
@ -3,6 +3,9 @@
|
||||
#include "PresetBundle.hpp"
|
||||
#include "PresetHints.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/OctoPrint.hpp"
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
@ -14,6 +17,7 @@
|
||||
#include <wx/treectrl.h>
|
||||
#include <wx/imaglist.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/filedlg.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@ -1388,39 +1392,18 @@ void TabPrinter::build()
|
||||
}
|
||||
|
||||
optgroup = page->new_optgroup(_(L("OctoPrint upload")));
|
||||
// # append two buttons to the Host line
|
||||
auto octoprint_host_browse = [this] (wxWindow* parent) {
|
||||
|
||||
auto octoprint_host_browse = [this, optgroup] (wxWindow* parent) {
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
// btn->SetFont($Slic3r::GUI::small_font);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
if (m_is_disabled_button_browse)
|
||||
btn->Disable();
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){
|
||||
if (m_event_button_browse > 0){
|
||||
wxCommandEvent event(m_event_button_browse);
|
||||
event.SetString("Button BROWSE was clicked!");
|
||||
g_wxMainFrame->ProcessWindowEvent(event);
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) {
|
||||
BonjourDialog dialog(parent);
|
||||
if (dialog.show_and_lookup()) {
|
||||
optgroup->set_value("octoprint_host", std::move(dialog.get_selected()), true);
|
||||
}
|
||||
// // # look for devices
|
||||
// auto entries;
|
||||
// {
|
||||
// my $res = Net::Bonjour->new('http');
|
||||
// $res->discover;
|
||||
// $entries = [$res->entries];
|
||||
// }
|
||||
// if (@{$entries}) {
|
||||
// my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
|
||||
// $self->_load_key_value('octoprint_host', $dlg->GetValue . ":".$dlg->GetPort)
|
||||
// if $dlg->ShowModal == wxID_OK;
|
||||
// }
|
||||
// else {
|
||||
// auto msg_window = new wxMessageDialog(parent, "No Bonjour device found", "Device Browser", wxOK | wxICON_INFORMATION);
|
||||
// msg_window->ShowModal();
|
||||
// }
|
||||
});
|
||||
|
||||
return sizer;
|
||||
@ -1429,33 +1412,23 @@ void TabPrinter::build()
|
||||
auto octoprint_host_test = [this](wxWindow* parent) {
|
||||
auto btn = m_octoprint_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")),
|
||||
wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
// btn->SetFont($Slic3r::GUI::small_font);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) {
|
||||
if (m_event_button_test > 0){
|
||||
wxCommandEvent event(m_event_button_test);
|
||||
event.SetString("Button TEST was clicked!");
|
||||
g_wxMainFrame->ProcessWindowEvent(event);
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
|
||||
OctoPrint octoprint(m_config);
|
||||
wxString msg;
|
||||
if (octoprint.test(msg)) {
|
||||
show_info(this, _(L("Connection to OctoPrint works correctly.")), _(L("Success!")));
|
||||
} else {
|
||||
const auto text = wxString::Format("%s: %s\n\n%s",
|
||||
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))
|
||||
);
|
||||
show_error(this, text);
|
||||
}
|
||||
// my $ua = LWP::UserAgent->new;
|
||||
// $ua->timeout(10);
|
||||
//
|
||||
// my $res = $ua->get(
|
||||
// "http://".$self->{config}->octoprint_host . "/api/version",
|
||||
// 'X-Api-Key' = > $self->{config}->octoprint_apikey,
|
||||
// );
|
||||
// if ($res->is_success) {
|
||||
// show_info(parent, "Connection to OctoPrint works correctly.", "Success!");
|
||||
// }
|
||||
// else {
|
||||
// show_error(parent,
|
||||
// "I wasn't able to connect to OctoPrint (".$res->status_line . "). "
|
||||
// . "Check hostname and OctoPrint version (at least 1.1.0 is required).");
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
@ -1465,6 +1438,45 @@ void TabPrinter::build()
|
||||
optgroup->append_line(host_line);
|
||||
optgroup->append_single_option_line("octoprint_apikey");
|
||||
|
||||
if (Http::ca_file_supported()) {
|
||||
|
||||
Line cafile_line = optgroup->create_single_option_line("octoprint_cafile");
|
||||
|
||||
auto octoprint_cafile_browse = [this, optgroup] (wxWindow* parent) {
|
||||
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e){
|
||||
static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"));
|
||||
wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (openFileDialog.ShowModal() != wxID_CANCEL) {
|
||||
optgroup->set_value("octoprint_cafile", std::move(openFileDialog.GetPath()), true);
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
cafile_line.append_widget(octoprint_cafile_browse);
|
||||
optgroup->append_line(cafile_line);
|
||||
|
||||
auto octoprint_cafile_hint = [this, optgroup] (wxWindow* parent) {
|
||||
auto txt = new wxStaticText(parent, wxID_ANY,
|
||||
_(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")));
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(txt);
|
||||
return sizer;
|
||||
};
|
||||
|
||||
Line cafile_hint { "", "" };
|
||||
cafile_hint.full_width = 1;
|
||||
cafile_hint.widget = std::move(octoprint_cafile_hint);
|
||||
optgroup->append_line(cafile_hint);
|
||||
|
||||
}
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Firmware")));
|
||||
optgroup->append_single_option_line("gcode_flavor");
|
||||
|
||||
@ -1632,13 +1644,8 @@ void TabPrinter::update(){
|
||||
m_serial_test_btn->Disable();
|
||||
}
|
||||
|
||||
en = !m_config->opt_string("octoprint_host").empty();
|
||||
if ( en && m_is_user_agent)
|
||||
m_octoprint_host_test_btn->Enable();
|
||||
else
|
||||
m_octoprint_host_test_btn->Disable();
|
||||
get_field("octoprint_apikey")->toggle(en);
|
||||
|
||||
m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
|
||||
|
||||
bool have_multiple_extruders = m_extruders_count > 1;
|
||||
get_field("toolchange_gcode")->toggle(have_multiple_extruders);
|
||||
get_field("single_extruder_multi_material")->toggle(have_multiple_extruders);
|
||||
|
@ -234,11 +234,6 @@ public:
|
||||
//Slic3r::GUI::Tab::Printer;
|
||||
class TabPrinter : public Tab
|
||||
{
|
||||
bool m_is_disabled_button_browse;
|
||||
bool m_is_user_agent;
|
||||
// similar event by clicking Buttons "Browse" & "Test"
|
||||
wxEventType m_event_button_browse = 0;
|
||||
wxEventType m_event_button_test = 0;
|
||||
public:
|
||||
wxButton* m_serial_test_btn;
|
||||
wxButton* m_octoprint_host_test_btn;
|
||||
@ -249,10 +244,7 @@ public:
|
||||
std::vector<PageShp> m_extruder_pages;
|
||||
|
||||
TabPrinter() {}
|
||||
TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) :
|
||||
Tab(parent, _(L("Printer Settings")), "printer", no_controller),
|
||||
m_is_disabled_button_browse(is_disabled_btn_browse),
|
||||
m_is_user_agent(is_user_agent) {}
|
||||
TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {}
|
||||
~TabPrinter(){}
|
||||
|
||||
void build() override;
|
||||
@ -261,10 +253,6 @@ public:
|
||||
void extruders_count_changed(size_t extruders_count);
|
||||
void build_extruder_pages();
|
||||
void on_preset_loaded() override;
|
||||
|
||||
// Set the events to the callbacks posted to the main frame window (currently implemented in Perl).
|
||||
void set_event_button_browse(wxEventType evt) { m_event_button_browse = evt; }
|
||||
void set_event_button_test(wxEventType evt) { m_event_button_test = evt; }
|
||||
};
|
||||
|
||||
class SavePresetWindow :public wxDialog
|
||||
|
1954
xs/src/slic3r/Utils/ASCIIFolding.cpp
Normal file
1954
xs/src/slic3r/Utils/ASCIIFolding.cpp
Normal file
File diff suppressed because it is too large
Load Diff
15
xs/src/slic3r/Utils/ASCIIFolding.hpp
Normal file
15
xs/src/slic3r/Utils/ASCIIFolding.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef slic3r_ASCIIFolding_hpp_
|
||||
#define slic3r_ASCIIFolding_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// If possible, remove accents from accented latin characters.
|
||||
// This function is useful for generating file names to be processed by legacy firmwares.
|
||||
extern std::string fold_utf8_to_ascii(const char *src);
|
||||
extern std::string fold_utf8_to_ascii(const std::string &src);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_ASCIIFolding_hpp_ */
|
@ -1,9 +1,7 @@
|
||||
#include "Bonjour.hpp"
|
||||
|
||||
#include <iostream> // XXX
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
@ -23,16 +21,18 @@ namespace asio = boost::asio;
|
||||
using boost::asio::ip::udp;
|
||||
|
||||
|
||||
// TODO: Fuzzing test (done without TXT)
|
||||
// FIXME: check char retype to unsigned
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
// Minimal implementation of a MDNS/DNS-SD client
|
||||
// This implementation is extremely simple, only the bits that are useful
|
||||
// for very basic MDNS discovery are present.
|
||||
// for basic MDNS discovery of OctoPi devices are present.
|
||||
// However, the bits that are present are implemented with security in mind.
|
||||
// Only fully correct DNS replies are allowed through.
|
||||
// While decoding the decoder will bail the moment it encounters anything fishy.
|
||||
// At least that's the idea. To help prove this is actually the case,
|
||||
// the implementations has been tested with AFL.
|
||||
|
||||
|
||||
struct DnsName: public std::string
|
||||
{
|
||||
@ -48,8 +48,7 @@ struct DnsName: public std::string
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
// Check for recursion depth to prevent parsing names that are nested too deeply
|
||||
// or end up cyclic:
|
||||
// Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic:
|
||||
if (depth >= MAX_RECURSION) {
|
||||
return boost::none;
|
||||
}
|
||||
@ -443,6 +442,30 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
|
||||
{
|
||||
os << "DnsMessage(ID: " << msg.header.id << ", "
|
||||
<< "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
|
||||
<< "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
|
||||
<< "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
|
||||
<< "services: [";
|
||||
|
||||
enum { SRV_PRINT_MAX = 3 };
|
||||
unsigned i = 0;
|
||||
for (const auto &sdpair : msg.sdmap) {
|
||||
os << sdpair.first << ", ";
|
||||
|
||||
if (++i >= SRV_PRINT_MAX) {
|
||||
os << "...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
os << "])";
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
struct BonjourRequest
|
||||
{
|
||||
@ -515,6 +538,7 @@ struct Bonjour::priv
|
||||
const std::string protocol;
|
||||
const std::string service_dn;
|
||||
unsigned timeout;
|
||||
unsigned retries;
|
||||
uint16_t rq_id;
|
||||
|
||||
std::vector<char> buffer;
|
||||
@ -524,6 +548,7 @@ struct Bonjour::priv
|
||||
|
||||
priv(std::string service, std::string protocol);
|
||||
|
||||
std::string strip_service_dn(const std::string &service_name) const;
|
||||
void udp_receive(udp::endpoint from, size_t bytes);
|
||||
void lookup_perform();
|
||||
};
|
||||
@ -533,11 +558,26 @@ Bonjour::priv::priv(std::string service, std::string protocol) :
|
||||
protocol(std::move(protocol)),
|
||||
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
|
||||
timeout(10),
|
||||
retries(1),
|
||||
rq_id(0)
|
||||
{
|
||||
buffer.resize(DnsMessage::MAX_SIZE);
|
||||
}
|
||||
|
||||
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
|
||||
{
|
||||
if (service_name.size() <= service_dn.size()) {
|
||||
return service_name;
|
||||
}
|
||||
|
||||
auto needle = service_name.rfind(service_dn);
|
||||
if (needle == service_name.size() - service_dn.size()) {
|
||||
return service_name.substr(0, needle - 1);
|
||||
} else {
|
||||
return service_name;
|
||||
}
|
||||
}
|
||||
|
||||
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
|
||||
{
|
||||
if (bytes == 0 || !replyfn) {
|
||||
@ -557,7 +597,10 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
|
||||
}
|
||||
|
||||
const auto &srv = *sdpair.second.srv;
|
||||
BonjourReply reply(ip, sdpair.first, srv.hostname);
|
||||
auto service_name = strip_service_dn(sdpair.first);
|
||||
|
||||
std::string path;
|
||||
std::string version;
|
||||
|
||||
if (sdpair.second.txt) {
|
||||
static const std::string tag_path = "path=";
|
||||
@ -565,13 +608,14 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
|
||||
|
||||
for (const auto &value : sdpair.second.txt->values) {
|
||||
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
|
||||
reply.path = value.substr(tag_path.size());
|
||||
path = std::move(value.substr(tag_path.size()));
|
||||
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
|
||||
reply.version = value.substr(tag_version.size());
|
||||
version = std::move(value.substr(tag_version.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
|
||||
replyfn(std::move(reply));
|
||||
}
|
||||
}
|
||||
@ -595,15 +639,26 @@ void Bonjour::priv::lookup_perform()
|
||||
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
|
||||
socket.send_to(asio::buffer(brq->data), mcast);
|
||||
|
||||
bool timeout = false;
|
||||
bool expired = false;
|
||||
bool retry = false;
|
||||
asio::deadline_timer timer(io_service);
|
||||
timer.expires_from_now(boost::posix_time::seconds(10));
|
||||
timer.async_wait([=, &timeout](const error_code &error) {
|
||||
timeout = true;
|
||||
if (self->completefn) {
|
||||
self->completefn();
|
||||
retries--;
|
||||
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
|
||||
if (retries == 0 || error) {
|
||||
expired = true;
|
||||
if (self->completefn) {
|
||||
self->completefn();
|
||||
}
|
||||
} else {
|
||||
retry = true;
|
||||
retries--;
|
||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||
timer.async_wait(timer_handler);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||
timer.async_wait(timer_handler);
|
||||
|
||||
udp::endpoint recv_from;
|
||||
const auto recv_handler = [&](const error_code &error, size_t bytes) {
|
||||
@ -612,8 +667,11 @@ void Bonjour::priv::lookup_perform()
|
||||
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
|
||||
|
||||
while (io_service.run_one()) {
|
||||
if (timeout) {
|
||||
if (expired) {
|
||||
socket.cancel();
|
||||
} else if (retry) {
|
||||
retry = false;
|
||||
socket.send_to(asio::buffer(brq->data), mcast);
|
||||
} else {
|
||||
buffer.resize(DnsMessage::MAX_SIZE);
|
||||
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
|
||||
@ -626,13 +684,39 @@ void Bonjour::priv::lookup_perform()
|
||||
|
||||
// API - public part
|
||||
|
||||
BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
|
||||
BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
|
||||
ip(std::move(ip)),
|
||||
port(port),
|
||||
service_name(std::move(service_name)),
|
||||
hostname(std::move(hostname)),
|
||||
path("/"),
|
||||
version("Unknown")
|
||||
{}
|
||||
path(path.empty() ? std::move(std::string("/")) : std::move(path)),
|
||||
version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
|
||||
{
|
||||
std::string proto;
|
||||
std::string port_suffix;
|
||||
if (port == 443) { proto = "https://"; }
|
||||
if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
|
||||
if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
|
||||
full_address = proto + ip.to_string() + port_suffix;
|
||||
if (this->path != "/") { full_address += path; }
|
||||
}
|
||||
|
||||
bool BonjourReply::operator==(const BonjourReply &other) const
|
||||
{
|
||||
return this->full_address == other.full_address
|
||||
&& this->service_name == other.service_name;
|
||||
}
|
||||
|
||||
bool BonjourReply::operator<(const BonjourReply &other) const
|
||||
{
|
||||
if (this->ip != other.ip) {
|
||||
// So that the common case doesn't involve string comparison
|
||||
return this->ip < other.ip;
|
||||
} else {
|
||||
auto cmp = this->full_address.compare(other.full_address);
|
||||
return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
|
||||
{
|
||||
@ -641,6 +725,7 @@ std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
Bonjour::Bonjour(std::string service, std::string protocol) :
|
||||
p(new priv(std::move(service), std::move(protocol)))
|
||||
{}
|
||||
@ -660,6 +745,12 @@ Bonjour& Bonjour::set_timeout(unsigned timeout)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bonjour& Bonjour::set_retries(unsigned retries)
|
||||
{
|
||||
if (p && retries > 0) { p->retries = retries; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bonjour& Bonjour::on_reply(ReplyFn fn)
|
||||
{
|
||||
if (p) { p->replyfn = std::move(fn); }
|
||||
@ -677,7 +768,7 @@ Bonjour::Ptr Bonjour::lookup()
|
||||
auto self = std::make_shared<Bonjour>(std::move(*this));
|
||||
|
||||
if (self->p) {
|
||||
auto io_thread = std::thread([self](){
|
||||
auto io_thread = std::thread([self]() {
|
||||
self->p->lookup_perform();
|
||||
});
|
||||
self->p->io_thread = std::move(io_thread);
|
||||
@ -687,18 +778,4 @@ Bonjour::Ptr Bonjour::lookup()
|
||||
}
|
||||
|
||||
|
||||
void Bonjour::pokus() // XXX
|
||||
{
|
||||
auto bonjour = Bonjour("octoprint")
|
||||
.set_timeout(15)
|
||||
.on_reply([](BonjourReply &&reply) {
|
||||
std::cerr << "BonjourReply: " << reply << std::endl;
|
||||
})
|
||||
.on_complete([](){
|
||||
std::cerr << "MDNS lookup complete" << std::endl;
|
||||
})
|
||||
.lookup();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,26 +1,31 @@
|
||||
#ifndef slic3r_Bonjour_hpp_
|
||||
#define slic3r_Bonjour_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
// #include <ostream>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
// TODO: reply data structure
|
||||
struct BonjourReply
|
||||
{
|
||||
boost::asio::ip::address ip;
|
||||
uint16_t port;
|
||||
std::string service_name;
|
||||
std::string hostname;
|
||||
std::string full_address;
|
||||
std::string path;
|
||||
std::string version;
|
||||
|
||||
BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname);
|
||||
BonjourReply() = delete;
|
||||
BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version);
|
||||
|
||||
bool operator==(const BonjourReply &other) const;
|
||||
bool operator<(const BonjourReply &other) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &, const BonjourReply &);
|
||||
@ -32,7 +37,7 @@ private:
|
||||
struct priv;
|
||||
public:
|
||||
typedef std::shared_ptr<Bonjour> Ptr;
|
||||
typedef std::function<void(BonjourReply &&reply)> ReplyFn;
|
||||
typedef std::function<void(BonjourReply &&)> ReplyFn;
|
||||
typedef std::function<void()> CompleteFn;
|
||||
|
||||
Bonjour(std::string service, std::string protocol = "tcp");
|
||||
@ -40,12 +45,15 @@ public:
|
||||
~Bonjour();
|
||||
|
||||
Bonjour& set_timeout(unsigned timeout);
|
||||
Bonjour& set_retries(unsigned retries);
|
||||
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
|
||||
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
|
||||
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
|
||||
|
||||
Bonjour& on_reply(ReplyFn fn);
|
||||
Bonjour& on_complete(CompleteFn fn);
|
||||
|
||||
Ptr lookup();
|
||||
|
||||
static void pokus(); // XXX: remove
|
||||
private:
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
@ -45,7 +44,9 @@ struct Http::priv
|
||||
priv(const std::string &url);
|
||||
~priv();
|
||||
|
||||
static bool ca_file_supported(::CURL *curl);
|
||||
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
|
||||
std::string curl_error(CURLcode curlcode);
|
||||
std::string body_size_error();
|
||||
void http_perform();
|
||||
};
|
||||
@ -71,6 +72,29 @@ Http::priv::~priv()
|
||||
::curl_slist_free_all(headerlist);
|
||||
}
|
||||
|
||||
bool Http::priv::ca_file_supported(::CURL *curl)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
bool res = false;
|
||||
#else
|
||||
bool res = true;
|
||||
#endif
|
||||
|
||||
if (curl == nullptr) { return res; }
|
||||
|
||||
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
|
||||
::curl_tlssessioninfo *tls;
|
||||
if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
|
||||
if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
|
||||
// With Windows and OS X native SSL support, cert files cannot be set
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
auto self = static_cast<priv*>(userp);
|
||||
@ -88,6 +112,14 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
|
||||
return realsize;
|
||||
}
|
||||
|
||||
std::string Http::priv::curl_error(CURLcode curlcode)
|
||||
{
|
||||
return (boost::format("%1% (%2%)")
|
||||
% ::curl_easy_strerror(curlcode)
|
||||
% curlcode
|
||||
).str();
|
||||
}
|
||||
|
||||
std::string Http::priv::body_size_error()
|
||||
{
|
||||
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
|
||||
@ -121,7 +153,7 @@ void Http::priv::http_perform()
|
||||
if (res == CURLE_WRITE_ERROR) {
|
||||
error = std::move(body_size_error());
|
||||
} else {
|
||||
error = ::curl_easy_strerror(res);
|
||||
error = std::move(curl_error(res));
|
||||
};
|
||||
|
||||
if (errorfn) {
|
||||
@ -180,7 +212,7 @@ Http& Http::remove_header(std::string name)
|
||||
|
||||
Http& Http::ca_file(const std::string &name)
|
||||
{
|
||||
if (p) {
|
||||
if (p && priv::ca_file_supported(p->curl)) {
|
||||
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
|
||||
}
|
||||
|
||||
@ -257,5 +289,13 @@ Http Http::post(std::string url)
|
||||
return http;
|
||||
}
|
||||
|
||||
bool Http::ca_file_supported()
|
||||
{
|
||||
::CURL *curl = ::curl_easy_init();
|
||||
bool res = priv::ca_file_supported(curl);
|
||||
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public:
|
||||
Ptr perform();
|
||||
void perform_sync();
|
||||
|
||||
static bool ca_file_supported();
|
||||
private:
|
||||
Http(const std::string &url);
|
||||
|
||||
|
@ -20,16 +20,19 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||
cafile(config->opt_string("octoprint_cafile"))
|
||||
{}
|
||||
|
||||
std::string OctoPrint::test() const
|
||||
bool OctoPrint::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `res` from within the closure
|
||||
std::string res;
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
auto http = Http::get(std::move(make_url("api/version")));
|
||||
bool res = true;
|
||||
|
||||
auto url = std::move(make_url("api/version"));
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string, std::string error, unsigned status) {
|
||||
res = format_error(error, status);
|
||||
res = false;
|
||||
msg = format_error(error, status);
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
@ -43,21 +46,26 @@ void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const st
|
||||
http.form_add("print", print ? "true" : "false")
|
||||
.form_add_file("file", filename)
|
||||
.on_complete([=](std::string body, unsigned status) {
|
||||
wxWindow *window = GUI::get_widget_by_id(windowId);
|
||||
wxWindow *window = wxWindow::FindWindowById(windowId);
|
||||
if (window == nullptr) { return; }
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
|
||||
evt->SetString("G-code file successfully uploaded to the OctoPrint server");
|
||||
evt->SetString(_(L("G-code file successfully uploaded to the OctoPrint server")));
|
||||
evt->SetInt(100);
|
||||
wxQueueEvent(window, evt);
|
||||
})
|
||||
.on_error([=](std::string body, std::string error, unsigned status) {
|
||||
wxWindow *window = GUI::get_widget_by_id(windowId);
|
||||
wxWindow *window = wxWindow::FindWindowById(windowId);
|
||||
if (window == nullptr) { return; }
|
||||
|
||||
wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
|
||||
evt_complete->SetInt(100);
|
||||
wxQueueEvent(window, evt_complete);
|
||||
|
||||
wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
|
||||
evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
|
||||
evt_error->SetString(wxString::Format("%s: %s",
|
||||
_(L("Error while uploading to the OctoPrint server")),
|
||||
format_error(error, status)));
|
||||
wxQueueEvent(window, evt_error);
|
||||
})
|
||||
.perform();
|
||||
@ -85,19 +93,15 @@ std::string OctoPrint::make_url(const std::string &path) const
|
||||
}
|
||||
}
|
||||
|
||||
std::string OctoPrint::format_error(std::string error, unsigned status)
|
||||
wxString OctoPrint::format_error(std::string error, unsigned status)
|
||||
{
|
||||
const wxString wxerror = error;
|
||||
|
||||
if (status != 0) {
|
||||
std::string res{"HTTP "};
|
||||
res.append(std::to_string(status));
|
||||
|
||||
if (status == 401) {
|
||||
res.append(": Invalid API key");
|
||||
}
|
||||
|
||||
return std::move(res);
|
||||
return wxString::Format("HTTP %u: %s", status,
|
||||
(status == 401 ? _(L("Invalid API key")) : wxerror));
|
||||
} else {
|
||||
return std::move(error);
|
||||
return std::move(wxerror);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
#define slic3r_OctoPrint_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
// #include "Http.hpp" // XXX: ?
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -16,8 +16,7 @@ class OctoPrint
|
||||
public:
|
||||
OctoPrint(DynamicPrintConfig *config);
|
||||
|
||||
std::string test() const;
|
||||
// XXX: style
|
||||
bool test(wxString &curl_msg) const;
|
||||
void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
|
||||
private:
|
||||
std::string host;
|
||||
@ -26,7 +25,7 @@ private:
|
||||
|
||||
void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
static std::string format_error(std::string error, unsigned status);
|
||||
static wxString format_error(std::string error, unsigned status);
|
||||
};
|
||||
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
try {
|
||||
THIS->do_export(print, path);
|
||||
} catch (std::exception& e) {
|
||||
croak(e.what());
|
||||
croak("%s\n", e.what());
|
||||
}
|
||||
%};
|
||||
void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data)
|
||||
|
@ -3,6 +3,7 @@
|
||||
%{
|
||||
#include <xsinit.h>
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/ASCIIFolding.hpp"
|
||||
%}
|
||||
|
||||
|
||||
@ -35,12 +36,8 @@ void set_tab_panel(SV *ui)
|
||||
void add_debug_menu(SV *ui, int event_language_change)
|
||||
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
|
||||
|
||||
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
|
||||
int event_value_change, int event_presets_changed,
|
||||
int event_button_browse, int event_button_test)
|
||||
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, is_disabled_button_browse, is_user_agent,
|
||||
event_value_change, event_presets_changed,
|
||||
event_button_browse, event_button_test); %};
|
||||
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
|
||||
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %};
|
||||
|
||||
Ref<TabIface> get_preset_tab(char *name)
|
||||
%code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %};
|
||||
@ -67,3 +64,6 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz
|
||||
%code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
|
||||
(wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),
|
||||
(wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %};
|
||||
|
||||
std::string fold_utf8_to_ascii(const char *src)
|
||||
%code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %};
|
||||
|
@ -18,7 +18,7 @@
|
||||
try {
|
||||
RETVAL = THIS->process(str, 0);
|
||||
} catch (std::exception& e) {
|
||||
croak(e.what());
|
||||
croak("%s\n", e.what());
|
||||
}
|
||||
%};
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
try {
|
||||
RETVAL = THIS->evaluate_boolean_expression(str, THIS->config());
|
||||
} catch (std::exception& e) {
|
||||
croak(e.what());
|
||||
croak("%s\n", e.what());
|
||||
}
|
||||
%};
|
||||
};
|
||||
|
@ -212,7 +212,7 @@ _constant()
|
||||
try {
|
||||
RETVAL = THIS->output_filepath(path);
|
||||
} catch (std::exception& e) {
|
||||
croak(e.what());
|
||||
croak("%s\n", e.what());
|
||||
}
|
||||
%};
|
||||
|
||||
|
@ -9,6 +9,5 @@
|
||||
OctoPrint(DynamicPrintConfig *config);
|
||||
~OctoPrint();
|
||||
|
||||
std::string test() const;
|
||||
void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user