mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-11-02 20:51:23 -07:00
Merge branch 'dev2' into lm_sla_supports_ui
This commit is contained in:
commit
088fe6cec6
135 changed files with 6108 additions and 9593 deletions
4
Build.PL
4
Build.PL
|
|
@ -21,9 +21,7 @@ my %prereqs = qw(
|
|||
POSIX 0
|
||||
Scalar::Util 0
|
||||
Test::More 0
|
||||
Thread::Semaphore 0
|
||||
IO::Scalar 0
|
||||
threads 1.96
|
||||
Time::HiRes 0
|
||||
);
|
||||
my %recommends = qw(
|
||||
|
|
@ -44,8 +42,6 @@ if ($gui) {
|
|||
);
|
||||
if ($^O eq 'MSWin32') {
|
||||
$recommends{"Win32::TieRegistry"} = 0;
|
||||
# we need an up-to-date Win32::API because older aren't thread-safe (GH #2517)
|
||||
$prereqs{'Win32::API'} = 0.79;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ The author of the Silk icon set is Mark James.
|
|||
and [input_filename] (default: [input_filename_base].gcode)
|
||||
--post-process Generated G-code will be processed with the supplied script;
|
||||
call this more than once to process through multiple scripts.
|
||||
--export-svg Export a SVG file containing slices instead of G-code.
|
||||
--export-png Export zipped PNG files containing slices instead of G-code.
|
||||
-m, --merge If multiple files are supplied, they will be composed into a single
|
||||
print rather than processed individually.
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
###############################################################################
|
||||
# Find Flann
|
||||
#
|
||||
# This sets the following variables:
|
||||
# FLANN_FOUND - True if FLANN was found.
|
||||
# FLANN_INCLUDE_DIRS - Directories containing the FLANN include files.
|
||||
# FLANN_LIBRARIES - Libraries needed to use FLANN.
|
||||
# FLANN_DEFINITIONS - Compiler flags for FLANN.
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_FLANN flann)
|
||||
set(FLANN_DEFINITIONS ${PC_FLANN_CFLAGS_OTHER})
|
||||
|
||||
find_path(FLANN_INCLUDE_DIR flann/flann.hpp
|
||||
HINTS ${PC_FLANN_INCLUDEDIR} ${PC_FLANN_INCLUDE_DIRS})
|
||||
|
||||
find_library(FLANN_LIBRARY flann_cpp
|
||||
HINTS ${PC_FLANN_LIBDIR} ${PC_FLANN_LIBRARY_DIRS})
|
||||
|
||||
set(FLANN_INCLUDE_DIRS ${FLANN_INCLUDE_DIR})
|
||||
set(FLANN_LIBRARIES ${FLANN_LIBRARY})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Flann DEFAULT_MSG
|
||||
FLANN_LIBRARY FLANN_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(FLANN_LIBRARY FLANN_INCLUDE_DIR)
|
||||
|
||||
147
lib/Slic3r.pm
147
lib/Slic3r.pm
|
|
@ -1,5 +1,4 @@
|
|||
# This package loads all the non-GUI Slic3r perl packages.
|
||||
# In addition, it implements utility functions for file handling and threading.
|
||||
|
||||
package Slic3r;
|
||||
|
||||
|
|
@ -22,22 +21,11 @@ sub debugf {
|
|||
|
||||
our $loglevel = 0;
|
||||
|
||||
# load threads before Moo as required by it
|
||||
BEGIN {
|
||||
# Test, whether the perl was compiled with ithreads support and ithreads actually work.
|
||||
use Config;
|
||||
use Moo;
|
||||
my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
|
||||
die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads;
|
||||
die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96;
|
||||
die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000;
|
||||
$debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1);
|
||||
print "Debugging output enabled\n" if $debug;
|
||||
}
|
||||
|
||||
warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n"
|
||||
if $^V == v5.16;
|
||||
|
||||
use FindBin;
|
||||
|
||||
# Let the XS module know where the GUI resources reside.
|
||||
|
|
@ -61,22 +49,15 @@ use Slic3r::Model;
|
|||
use Slic3r::Point;
|
||||
use Slic3r::Polygon;
|
||||
use Slic3r::Polyline;
|
||||
use Slic3r::Print;
|
||||
use Slic3r::Print::Object;
|
||||
use Slic3r::Print::Simple;
|
||||
use Slic3r::Surface;
|
||||
our $build = eval "use Slic3r::Build; 1";
|
||||
use Thread::Semaphore;
|
||||
|
||||
# Scaling between the float and integer coordinates.
|
||||
# Floats are in mm.
|
||||
use constant SCALING_FACTOR => 0.000001;
|
||||
|
||||
# Keep track of threads we created. Perl worker threads shall not create further threads.
|
||||
my @threads = ();
|
||||
my $pause_sema = Thread::Semaphore->new;
|
||||
my $paused = 0;
|
||||
|
||||
# Set the logging level at the Slic3r XS module.
|
||||
$Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0;
|
||||
set_logging_level($Slic3r::loglevel);
|
||||
|
|
@ -85,129 +66,6 @@ set_logging_level($Slic3r::loglevel);
|
|||
# class instance in a thread safe manner.
|
||||
Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1');
|
||||
|
||||
sub spawn_thread {
|
||||
my ($cb) = @_;
|
||||
@_ = ();
|
||||
my $thread = threads->create(sub {
|
||||
Slic3r::debugf "Starting thread %d...\n", threads->tid;
|
||||
local $SIG{'KILL'} = sub {
|
||||
Slic3r::debugf "Exiting thread %d...\n", threads->tid;
|
||||
Slic3r::thread_cleanup();
|
||||
threads->exit();
|
||||
};
|
||||
local $SIG{'STOP'} = sub {
|
||||
$pause_sema->down;
|
||||
$pause_sema->up;
|
||||
};
|
||||
$cb->();
|
||||
});
|
||||
push @threads, $thread->tid;
|
||||
return $thread;
|
||||
}
|
||||
|
||||
# call this at the very end of each thread (except the main one)
|
||||
# so that it does not try to free existing objects.
|
||||
# at that stage, existing objects are only those that we
|
||||
# inherited at the thread creation (thus shared) and those
|
||||
# that we are returning: destruction will be handled by the
|
||||
# main thread in both cases.
|
||||
# reminder: do not destroy inherited objects in other threads,
|
||||
# as the main thread will still try to destroy them when they
|
||||
# go out of scope; in other words, if you're undef()'ing an
|
||||
# object in a thread, make sure the main thread still holds a
|
||||
# reference so that it won't be destroyed in thread.
|
||||
sub thread_cleanup {
|
||||
# prevent destruction of shared objects
|
||||
no warnings 'redefine';
|
||||
*Slic3r::BridgeDetector::DESTROY = sub {};
|
||||
*Slic3r::Config::DESTROY = sub {};
|
||||
*Slic3r::Config::Full::DESTROY = sub {};
|
||||
*Slic3r::Config::GCode::DESTROY = sub {};
|
||||
*Slic3r::Config::Print::DESTROY = sub {};
|
||||
*Slic3r::Config::PrintObject::DESTROY = sub {};
|
||||
*Slic3r::Config::PrintRegion::DESTROY = sub {};
|
||||
*Slic3r::Config::Static::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::Collection::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionMultiPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionSimulator::DESTROY = sub {};
|
||||
*Slic3r::Flow::DESTROY = sub {};
|
||||
*Slic3r::GCode::DESTROY = sub {};
|
||||
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
|
||||
*Slic3r::GCode::PreviewData::DESTROY = sub {};
|
||||
*Slic3r::GCode::Sender::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
|
||||
*Slic3r::Layer::PerimeterGenerator::DESTROY = sub {};
|
||||
*Slic3r::Line::DESTROY = sub {};
|
||||
*Slic3r::Linef3::DESTROY = sub {};
|
||||
*Slic3r::Model::DESTROY = sub {};
|
||||
*Slic3r::Model::Object::DESTROY = sub {};
|
||||
*Slic3r::Point::DESTROY = sub {};
|
||||
*Slic3r::Pointf::DESTROY = sub {};
|
||||
*Slic3r::Pointf3::DESTROY = sub {};
|
||||
*Slic3r::Polygon::DESTROY = sub {};
|
||||
*Slic3r::Polyline::DESTROY = sub {};
|
||||
*Slic3r::Polyline::Collection::DESTROY = sub {};
|
||||
*Slic3r::Print::DESTROY = sub {};
|
||||
*Slic3r::Print::Object::DESTROY = sub {};
|
||||
*Slic3r::Print::Region::DESTROY = sub {};
|
||||
*Slic3r::Surface::DESTROY = sub {};
|
||||
*Slic3r::Surface::Collection::DESTROY = sub {};
|
||||
*Slic3r::Print::SupportMaterial2::DESTROY = sub {};
|
||||
*Slic3r::TriangleMesh::DESTROY = sub {};
|
||||
*Slic3r::GUI::AppConfig::DESTROY = sub {};
|
||||
*Slic3r::GUI::GCodePreviewData::DESTROY = sub {};
|
||||
*Slic3r::GUI::PresetBundle::DESTROY = sub {};
|
||||
*Slic3r::GUI::Tab::DESTROY = sub {};
|
||||
*Slic3r::GUI::PresetHints::DESTROY = sub {};
|
||||
*Slic3r::GUI::TabIface::DESTROY = sub {};
|
||||
*Slic3r::GUI::ProgressStatusBar::DESTROY= sub {};
|
||||
*Slic3r::OctoPrint::DESTROY = sub {};
|
||||
*Slic3r::Duet::DESTROY = sub {};
|
||||
*Slic3r::PresetUpdater::DESTROY = sub {};
|
||||
return undef; # this prevents a "Scalars leaked" warning
|
||||
}
|
||||
|
||||
sub _get_running_threads {
|
||||
return grep defined($_), map threads->object($_), @threads;
|
||||
}
|
||||
|
||||
sub kill_all_threads {
|
||||
# Send SIGKILL to all the running threads to let them die.
|
||||
foreach my $thread (_get_running_threads) {
|
||||
Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid;
|
||||
$thread->kill('KILL');
|
||||
}
|
||||
# unlock semaphore before we block on wait
|
||||
# otherwise we'd get a deadlock if threads were paused
|
||||
resume_all_threads();
|
||||
# in any thread we wait for our children
|
||||
foreach my $thread (_get_running_threads) {
|
||||
Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid;
|
||||
$thread->join; # block until threads are killed
|
||||
Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid;
|
||||
}
|
||||
@threads = ();
|
||||
}
|
||||
|
||||
sub pause_all_threads {
|
||||
return if $paused;
|
||||
$paused = 1;
|
||||
$pause_sema->down;
|
||||
$_->kill('STOP') for _get_running_threads;
|
||||
}
|
||||
|
||||
sub resume_all_threads {
|
||||
return unless $paused;
|
||||
$paused = 0;
|
||||
$pause_sema->up;
|
||||
}
|
||||
|
||||
# Open a file by converting $filename to local file system locales.
|
||||
sub open {
|
||||
my ($fh, $mode, $filename) = @_;
|
||||
|
|
@ -282,9 +140,4 @@ sub system_info
|
|||
return $out;
|
||||
}
|
||||
|
||||
# this package declaration prevents an ugly fatal warning to be emitted when
|
||||
# spawning a new thread
|
||||
package GLUquadricObjPtr;
|
||||
package Wx::Printout;
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -6,25 +6,10 @@ use utf8;
|
|||
use File::Basename qw(basename);
|
||||
use FindBin;
|
||||
use List::Util qw(first);
|
||||
use Slic3r::GUI::2DBed;
|
||||
use Slic3r::GUI::Controller;
|
||||
use Slic3r::GUI::Controller::ManualControlDialog;
|
||||
use Slic3r::GUI::Controller::PrinterPanel;
|
||||
use Slic3r::GUI::MainFrame;
|
||||
use Slic3r::GUI::Plater;
|
||||
use Slic3r::GUI::Plater::2D;
|
||||
use Slic3r::GUI::Plater::2DToolpaths;
|
||||
use Slic3r::GUI::Plater::3D;
|
||||
use Slic3r::GUI::Plater::3DPreview;
|
||||
use Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use Slic3r::GUI::Plater::LambdaObjectDialog;
|
||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use Slic3r::GUI::ProgressStatusBar;
|
||||
use Slic3r::GUI::OptionsGroup;
|
||||
use Slic3r::GUI::OptionsGroup::Field;
|
||||
use Slic3r::GUI::SystemInfo;
|
||||
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
|
|
@ -43,13 +28,11 @@ use constant FILE_WILDCARDS => {
|
|||
prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA',
|
||||
ini => 'INI files *.ini|*.ini;*.INI',
|
||||
gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
|
||||
svg => 'SVG files *.svg|*.svg;*.SVG',
|
||||
};
|
||||
use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf threemf prusa)};
|
||||
|
||||
# Datadir provided on the command line.
|
||||
our $datadir;
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
our $no_plater;
|
||||
our @cb;
|
||||
|
||||
|
|
@ -119,8 +102,6 @@ sub OnInit {
|
|||
print STDERR "Creating main frame...\n";
|
||||
Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new);
|
||||
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
no_controller => $self->{app_config}->get('no_controller'),
|
||||
no_plater => $no_plater,
|
||||
lang_ch_event => $LANGUAGE_CHANGE_EVENT,
|
||||
preferences_event => $PREFERENCES_EVENT,
|
||||
|
|
@ -186,8 +167,6 @@ sub recreate_GUI{
|
|||
my ($self) = @_;
|
||||
my $topwindow = $self->GetTopWindow();
|
||||
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
no_controller => $self->{app_config}->get('no_controller'),
|
||||
no_plater => $no_plater,
|
||||
lang_ch_event => $LANGUAGE_CHANGE_EVENT,
|
||||
preferences_event => $PREFERENCES_EVENT,
|
||||
|
|
@ -226,16 +205,15 @@ sub system_info {
|
|||
$opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1);
|
||||
$opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1);
|
||||
}
|
||||
my $about = Slic3r::GUI::SystemInfo->new(
|
||||
parent => undef,
|
||||
slic3r_info => $slic3r_info,
|
||||
# copyright_info => $copyright_info,
|
||||
system_info => $system_info,
|
||||
opengl_info => $opengl_info,
|
||||
text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt,
|
||||
);
|
||||
$about->ShowModal;
|
||||
$about->Destroy;
|
||||
# my $about = Slic3r::GUI::SystemInfo->new(
|
||||
# parent => undef,
|
||||
# slic3r_info => $slic3r_info,
|
||||
# system_info => $system_info,
|
||||
# opengl_info => $opengl_info,
|
||||
# text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt,
|
||||
# );
|
||||
# $about->ShowModal;
|
||||
# $about->Destroy;
|
||||
}
|
||||
|
||||
# static method accepting a wxWindow object as first parameter
|
||||
|
|
|
|||
|
|
@ -1,217 +0,0 @@
|
|||
# Bed shape dialog
|
||||
# still used by the Slic3r::GUI::Controller::ManualControlDialog Perl module.
|
||||
|
||||
package Slic3r::GUI::2DBed;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(min max);
|
||||
use Slic3r::Geometry qw(X Y unscale deg2rad);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
use Wx qw(:misc :pen :brush :font :systemsettings wxTAB_TRAVERSAL wxSOLID);
|
||||
use Wx::Event qw(EVT_PAINT EVT_ERASE_BACKGROUND EVT_MOUSE_EVENTS EVT_SIZE);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(bed_shape interactive pos _scale_factor _shift on_move _painted));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $bed_shape) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [250,-1], wxTAB_TRAVERSAL);
|
||||
$self->{user_drawn_background} = $^O ne 'darwin';
|
||||
$self->bed_shape($bed_shape // []);
|
||||
EVT_PAINT($self, \&_repaint);
|
||||
EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
|
||||
EVT_MOUSE_EVENTS($self, \&_mouse_event);
|
||||
EVT_SIZE($self, sub { $self->Refresh; });
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::AutoBufferedPaintDC->new($self);
|
||||
my ($cw, $ch) = $self->GetSizeWH;
|
||||
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
|
||||
|
||||
if ($self->{user_drawn_background}) {
|
||||
# On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
# On MacOS the background is erased, on Windows the background is not erased
|
||||
# and on Linux/GTK the background is erased to gray color.
|
||||
# Fill DC with the background on Windows & Linux/GTK.
|
||||
my $color = Wx::SystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT);
|
||||
$dc->SetPen(Wx::Pen->new($color, 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new($color, wxSOLID));
|
||||
my $rect = $self->GetUpdateRegion()->GetBox();
|
||||
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
|
||||
}
|
||||
|
||||
# turn $cw and $ch from sizes to max coordinates
|
||||
$cw--;
|
||||
$ch--;
|
||||
|
||||
my $cbb = Slic3r::Geometry::BoundingBoxf->new_from_points([
|
||||
Slic3r::Pointf->new(0, 0),
|
||||
Slic3r::Pointf->new($cw, $ch),
|
||||
]);
|
||||
|
||||
# leave space for origin point
|
||||
$cbb->set_x_min($cbb->x_min + 4);
|
||||
$cbb->set_x_max($cbb->x_max - 4);
|
||||
$cbb->set_y_max($cbb->y_max - 4);
|
||||
|
||||
# leave space for origin label
|
||||
$cbb->set_y_max($cbb->y_max - 13);
|
||||
|
||||
# read new size
|
||||
($cw, $ch) = @{$cbb->size};
|
||||
my $ccenter = $cbb->center;
|
||||
|
||||
# get bounding box of bed shape in G-code coordinates
|
||||
my $bed_shape = $self->bed_shape;
|
||||
my $bed_polygon = Slic3r::Polygon->new_scale(@$bed_shape);
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape);
|
||||
$bb->merge_point(Slic3r::Pointf->new(0,0)); # origin needs to be in the visible area
|
||||
my ($bw, $bh) = @{$bb->size};
|
||||
my $bcenter = $bb->center;
|
||||
|
||||
# calculate the scaling factor for fitting bed shape in canvas area
|
||||
my $sfactor = min($cw/$bw, $ch/$bh);
|
||||
my $shift = Slic3r::Pointf->new(
|
||||
$ccenter->x - $bcenter->x * $sfactor,
|
||||
$ccenter->y - $bcenter->y * $sfactor, #-
|
||||
);
|
||||
$self->_scale_factor($sfactor);
|
||||
$self->_shift(Slic3r::Pointf->new(
|
||||
$shift->x + $cbb->x_min,
|
||||
$shift->y - ($cbb->y_max-$self->GetSize->GetHeight), #++
|
||||
));
|
||||
|
||||
# draw bed fill
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxSOLID));
|
||||
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
# draw grid
|
||||
{
|
||||
my $step = 10; # 1cm grid
|
||||
my @polylines = ();
|
||||
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$x, $bb->y_min], [$x, $bb->y_max]);
|
||||
}
|
||||
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
|
||||
$dc->DrawLine(map @{$self->to_pixels([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
|
||||
}
|
||||
|
||||
# draw bed contour
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxTRANSPARENT));
|
||||
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
my $origin_px = $self->to_pixels(Slic3r::Pointf->new(0,0));
|
||||
|
||||
# draw axes
|
||||
{
|
||||
my $axes_len = 50;
|
||||
my $arrow_len = 6;
|
||||
my $arrow_angle = deg2rad(45);
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(255,0,0), 2, wxSOLID)); # red
|
||||
my $x_end = Slic3r::Pointf->new($origin_px->[X] + $axes_len, $origin_px->[Y]);
|
||||
$dc->DrawLine(@$origin_px, @$x_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $x_end->clone;
|
||||
$end->translate(-$arrow_len, 0);
|
||||
$end->rotate($angle, $x_end);
|
||||
$dc->DrawLine(@$x_end, @$end);
|
||||
}
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,255,0), 2, wxSOLID)); # green
|
||||
my $y_end = Slic3r::Pointf->new($origin_px->[X], $origin_px->[Y] - $axes_len);
|
||||
$dc->DrawLine(@$origin_px, @$y_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $y_end->clone;
|
||||
$end->translate(0, +$arrow_len);
|
||||
$end->rotate($angle, $y_end);
|
||||
$dc->DrawLine(@$y_end, @$end);
|
||||
}
|
||||
}
|
||||
|
||||
# draw origin
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(0,0,0), wxSOLID));
|
||||
$dc->DrawCircle(@$origin_px, 3);
|
||||
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawText("(0,0)", $origin_px->[X] + 1, $origin_px->[Y] + 2);
|
||||
}
|
||||
|
||||
# draw current position
|
||||
if (defined $self->pos) {
|
||||
my $pos_px = $self->to_pixels($self->pos);
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(200,0,0), 2, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(200,0,0), wxTRANSPARENT));
|
||||
$dc->DrawCircle(@$pos_px, 5);
|
||||
|
||||
$dc->DrawLine($pos_px->[X]-15, $pos_px->[Y], $pos_px->[X]+15, $pos_px->[Y]);
|
||||
$dc->DrawLine($pos_px->[X], $pos_px->[Y]-15, $pos_px->[X], $pos_px->[Y]+15);
|
||||
}
|
||||
|
||||
$self->_painted(1);
|
||||
}
|
||||
|
||||
sub _mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
return if !$self->interactive;
|
||||
return if !$self->_painted;
|
||||
|
||||
my $pos = $event->GetPosition;
|
||||
my $point = $self->to_units([ $pos->x, $pos->y ]); #]]
|
||||
if ($event->LeftDown || $event->Dragging) {
|
||||
$self->on_move->($point) if $self->on_move;
|
||||
$self->Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
# convert G-code coordinates into pixels
|
||||
sub to_pixels {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $p = Slic3r::Pointf->new(@$point);
|
||||
$p->scale($self->_scale_factor);
|
||||
$p->translate(@{$self->_shift});
|
||||
return [$p->x, $self->GetSize->GetHeight - $p->y]; #]]
|
||||
}
|
||||
|
||||
# convert pixels into G-code coordinates
|
||||
sub to_units {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $p = Slic3r::Pointf->new(
|
||||
$point->[X],
|
||||
$self->GetSize->GetHeight - $point->[Y],
|
||||
);
|
||||
$p->translate(@{$self->_shift->negative});
|
||||
$p->scale(1/$self->_scale_factor);
|
||||
return $p;
|
||||
}
|
||||
|
||||
sub set_pos {
|
||||
my ($self, $pos) = @_;
|
||||
|
||||
$self->pos($pos);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
# The "Controller" tab to control the printer using serial / USB.
|
||||
# This feature is rarely used. Much more often, the firmware reads the G-codes from a SD card.
|
||||
# May there be multiple subtabs per each printer connected?
|
||||
|
||||
package Slic3r::GUI::Controller;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog wxBORDER_NONE);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU);
|
||||
use base qw(Wx::ScrolledWindow Class::Accessor);
|
||||
use List::Util qw(first);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(_selected_printer_preset));
|
||||
|
||||
our @ConfigOptions = qw(bed_shape serial_port serial_speed);
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [600,350]);
|
||||
|
||||
$self->SetScrollbars(0, 1, 0, 1);
|
||||
$self->{sizer} = my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# warning to show when there are no printers configured
|
||||
{
|
||||
$self->{text_no_printers} = Wx::StaticText->new($self, -1,
|
||||
"No printers were configured for USB/serial control.",
|
||||
wxDefaultPosition, wxDefaultSize);
|
||||
$self->{sizer}->Add($self->{text_no_printers}, 0, wxTOP | wxLEFT, 30);
|
||||
}
|
||||
|
||||
# button for adding new printer panels
|
||||
{
|
||||
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
$btn->SetToolTipString("Add printer…")
|
||||
if $btn->can('SetToolTipString');
|
||||
|
||||
EVT_LEFT_DOWN($btn, sub {
|
||||
my $menu = Wx::Menu->new;
|
||||
my @panels = $self->print_panels;
|
||||
# remove printers that already exist
|
||||
# update configs of currently loaded print panels
|
||||
foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) {
|
||||
my $preset_name = $preset->name;
|
||||
next if ! $preset->config->serial_port ||
|
||||
defined first { defined $_ && $_->printer_name eq $preset_name } @panels;
|
||||
my $myconfig = $preset->config->clone_only(\@ConfigOptions);
|
||||
my $id = &Wx::NewId();
|
||||
$menu->Append($id, $preset_name);
|
||||
EVT_MENU($menu, $id, sub {
|
||||
$self->add_printer($preset_name, $myconfig);
|
||||
});
|
||||
}
|
||||
$self->PopupMenu($menu, $btn->GetPosition);
|
||||
$menu->Destroy;
|
||||
});
|
||||
$self->{sizer}->Add($btn, 0, wxTOP | wxLEFT, 10);
|
||||
}
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
#$sizer->SetSizeHints($self);
|
||||
|
||||
EVT_CLOSE($self, sub {
|
||||
my (undef, $event) = @_;
|
||||
|
||||
if ($event->CanVeto) {
|
||||
foreach my $panel ($self->print_panels) {
|
||||
if ($panel->printing) {
|
||||
my $confirm = Wx::MessageDialog->new(
|
||||
$self, "Printer '" . $panel->printer_name . "' is printing.\n\nDo you want to stop printing?",
|
||||
'Unfinished Print', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION,
|
||||
);
|
||||
if ($confirm->ShowModal == wxID_NO) {
|
||||
$event->Veto;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach my $panel ($self->print_panels) {
|
||||
$panel->disconnect;
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
});
|
||||
|
||||
$self->Layout;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub OnActivate {
|
||||
my ($self) = @_;
|
||||
|
||||
# get all available presets
|
||||
my %presets = map { $_->name => $_->config->clone_only(\@ConfigOptions) }
|
||||
grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer};
|
||||
|
||||
# decide which ones we want to keep
|
||||
my %active = ();
|
||||
|
||||
# keep the ones that are currently connected or have jobs in queue
|
||||
$active{$_} = 1 for map $_->printer_name,
|
||||
grep { $_->is_connected || @{$_->jobs} > 0 }
|
||||
$self->print_panels;
|
||||
|
||||
if (%presets) {
|
||||
# if there are no active panels, use sensible defaults
|
||||
if (!%active && keys %presets <= 2) {
|
||||
# if only one or two presets exist, load them
|
||||
$active{$_} = 1 for keys %presets;
|
||||
}
|
||||
if (!%active) {
|
||||
# enable printers whose port is available
|
||||
my %ports = map { $_ => 1 } Slic3r::GUI::scan_serial_ports;
|
||||
$active{$_} = 1
|
||||
for grep exists $ports{$presets{$_}->serial_port}, keys %presets;
|
||||
}
|
||||
if (!%active && $self->_selected_printer_preset) {
|
||||
# enable currently selected printer if it is configured
|
||||
$active{$self->_selected_printer_preset} = 1
|
||||
if $presets{$self->_selected_printer_preset};
|
||||
}
|
||||
}
|
||||
|
||||
# apply changes
|
||||
for my $panel ($self->print_panels) {
|
||||
next if $active{$panel->printer_name};
|
||||
|
||||
$self->{sizer}->DetachWindow($panel);
|
||||
$panel->Destroy;
|
||||
}
|
||||
$self->add_printer($_, $presets{$_}) for sort keys %active;
|
||||
|
||||
# show/hide the warning about no printers
|
||||
$self->{text_no_printers}->Show(!%presets);
|
||||
|
||||
# show/hide the Add button
|
||||
$self->{btn_add}->Show(keys %presets != keys %active);
|
||||
|
||||
$self->Layout;
|
||||
|
||||
# we need this in order to trigger the OnSize event of wxScrolledWindow which
|
||||
# recalculates the virtual size
|
||||
Wx::GetTopLevelParent($self)->SendSizeEvent;
|
||||
}
|
||||
|
||||
sub add_printer {
|
||||
my ($self, $printer_name, $config) = @_;
|
||||
|
||||
# check that printer doesn't exist already
|
||||
foreach my $panel ($self->print_panels) {
|
||||
if ($panel->printer_name eq $printer_name) {
|
||||
return $panel;
|
||||
}
|
||||
}
|
||||
|
||||
my $printer_panel = Slic3r::GUI::Controller::PrinterPanel->new($self, $printer_name, $config);
|
||||
$self->{sizer}->Prepend($printer_panel, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
$self->Layout;
|
||||
|
||||
return $printer_panel;
|
||||
}
|
||||
|
||||
sub print_panels {
|
||||
my ($self) = @_;
|
||||
return grep $_->isa('Slic3r::GUI::Controller::PrinterPanel'),
|
||||
map $_->GetWindow, $self->{sizer}->GetChildren;
|
||||
}
|
||||
|
||||
# Called by Slic3r::GUI::Tab::Printer::_on_presets_changed
|
||||
# when the presets are loaded or the user selects another preset.
|
||||
sub update_presets {
|
||||
my ($self, $presets) = @_;
|
||||
# update configs of currently loaded print panels
|
||||
my @presets = @$presets;
|
||||
foreach my $panel ($self->print_panels) {
|
||||
my $preset = $presets->find_preset($panel->printer_name, 0);
|
||||
$panel->config($preset->config->clone_only(\@ConfigOptions))
|
||||
if defined $preset;
|
||||
}
|
||||
|
||||
$self->_selected_printer_preset($presets->get_selected_preset->name);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected?
|
||||
|
||||
package Slic3r::GUI::Controller::ManualControlDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap
|
||||
wxBORDER_NONE wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||
use base qw(Wx::Dialog Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(sender config2 x_homed y_homed));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $config, $sender) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, "Manual Control", wxDefaultPosition,
|
||||
[500,380], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->sender($sender);
|
||||
|
||||
$self->config2({
|
||||
xy_travel_speed => 130,
|
||||
z_travel_speed => 10,
|
||||
});
|
||||
|
||||
my $bed_sizer = Wx::FlexGridSizer->new(2, 3, 1, 1);
|
||||
$bed_sizer->AddGrowableCol(1, 1);
|
||||
$bed_sizer->AddGrowableRow(0, 1);
|
||||
|
||||
my $move_button = sub {
|
||||
my ($sizer, $label, $icon, $bold, $pos, $handler) = @_;
|
||||
|
||||
my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
|
||||
wxBU_LEFT | wxBU_EXACTFIT);
|
||||
$btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("$icon.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapPosition($pos);
|
||||
EVT_BUTTON($self, $btn, $handler);
|
||||
$sizer->Add($btn, 1, wxEXPAND | wxALL, 0);
|
||||
};
|
||||
|
||||
# Y buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
for my $d (qw(+10 +1 +0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'Y', 'house', 1, wxLEFT, sub { $self->home('Y') });
|
||||
for my $d (qw(-0.1 -1 -10)) {
|
||||
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
|
||||
};
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# Bed canvas
|
||||
{
|
||||
my $bed_shape = $config->bed_shape;
|
||||
$self->{canvas} = my $canvas = Slic3r::GUI::2DBed->new($self, $bed_shape);
|
||||
$canvas->interactive(1);
|
||||
$canvas->on_move(sub {
|
||||
my ($pos) = @_;
|
||||
|
||||
if (!($self->x_homed && $self->y_homed)) {
|
||||
Slic3r::GUI::show_error($self, "Please home both X and Y before moving.");
|
||||
return ;
|
||||
}
|
||||
|
||||
# delete any pending commands to get a smoother movement
|
||||
$self->sender->purge_queue(1);
|
||||
$self->abs_xy_move($pos);
|
||||
});
|
||||
$bed_sizer->Add($canvas, 0, wxEXPAND | wxRIGHT, 3);
|
||||
}
|
||||
|
||||
# Z buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
for my $d (qw(+10 +1 +0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'Z', 'house', 1, wxLEFT, sub { $self->home('Z') });
|
||||
for my $d (qw(-0.1 -1 -10)) {
|
||||
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
|
||||
};
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# XYZ home button
|
||||
$move_button->($bed_sizer, 'XYZ', 'house', 1, wxTOP, sub { $self->home(undef) });
|
||||
|
||||
# X buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
for my $d (qw(-10 -1 -0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_left', 0, wxTOP, sub { $self->rel_move('X', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'X', 'house', 1, wxTOP, sub { $self->home('X') });
|
||||
for my $d (qw(+0.1 +1 +10)) {
|
||||
$move_button->($sizer, $d, 'arrow_right', 0, wxTOP, sub { $self->rel_move('X', $d) });
|
||||
}
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Settings',
|
||||
on_change => sub {
|
||||
my ($opt_id, $value) = @_;
|
||||
$self->config2->{$opt_id} = $value;
|
||||
},
|
||||
);
|
||||
{
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Speed (mm/s)',
|
||||
);
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'xy_travel_speed',
|
||||
type => 'f',
|
||||
label => 'X/Y',
|
||||
tooltip => '',
|
||||
default => $self->config2->{xy_travel_speed},
|
||||
));
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'z_travel_speed',
|
||||
type => 'f',
|
||||
label => 'Z',
|
||||
tooltip => '',
|
||||
default => $self->config2->{z_travel_speed},
|
||||
));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
|
||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$main_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10);
|
||||
$main_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||
#$main_sizer->Add($self->CreateButtonSizer(wxCLOSE), 0, wxEXPAND);
|
||||
#EVT_BUTTON($self, wxID_CLOSE, sub { $self->Close });
|
||||
|
||||
$self->SetSizer($main_sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
#$main_sizer->SetSizeHints($self);
|
||||
$self->Layout;
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub abs_xy_move {
|
||||
my ($self, $pos) = @_;
|
||||
|
||||
$self->sender->send("G90", 1); # set absolute positioning
|
||||
$self->sender->send(sprintf("G1 X%.1f Y%.1f F%d", @$pos, $self->config2->{xy_travel_speed}*60), 1);
|
||||
$self->{canvas}->set_pos($pos);
|
||||
}
|
||||
|
||||
sub rel_move {
|
||||
my ($self, $axis, $distance) = @_;
|
||||
|
||||
my $speed = ($axis eq 'Z') ? $self->config2->{z_travel_speed} : $self->config2->{xy_travel_speed};
|
||||
$self->sender->send("G91", 1); # set relative positioning
|
||||
$self->sender->send(sprintf("G1 %s%.1f F%d", $axis, $distance, $speed*60), 1);
|
||||
$self->sender->send("G90", 1); # set absolute positioning
|
||||
|
||||
if (my $pos = $self->{canvas}->pos) {
|
||||
if ($axis eq 'X') {
|
||||
$pos->translate($distance, 0);
|
||||
} elsif ($axis eq 'Y') {
|
||||
$pos->translate(0, $distance);
|
||||
}
|
||||
$self->{canvas}->set_pos($pos);
|
||||
}
|
||||
}
|
||||
|
||||
sub home {
|
||||
my ($self, $axis) = @_;
|
||||
|
||||
$axis //= '';
|
||||
$self->sender->send(sprintf("G28 %s", $axis), 1);
|
||||
$self->{canvas}->set_pos(undef);
|
||||
$self->x_homed(1) if $axis eq 'X';
|
||||
$self->y_homed(1) if $axis eq 'Y';
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,707 +0,0 @@
|
|||
package Slic3r::GUI::Controller::PrinterPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :window :gauge :timer
|
||||
:textctrl :font :systemsettings);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_MOUSEWHEEL EVT_TIMER EVT_SCROLLWIN);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(printer_name config sender jobs
|
||||
printing status_timer temp_timer));
|
||||
|
||||
use constant CONNECTION_TIMEOUT => 3; # seconds
|
||||
use constant STATUS_TIMER_INTERVAL => 1000; # milliseconds
|
||||
use constant TEMP_TIMER_INTERVAL => 5000; # milliseconds
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $printer_name, $config) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]);
|
||||
|
||||
$self->printer_name($printer_name || 'Printer');
|
||||
$self->config($config);
|
||||
$self->jobs([]);
|
||||
|
||||
# set up the timer that polls for updates
|
||||
{
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->status_timer(Wx::Timer->new($self, $timer_id));
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
if ($self->printing) {
|
||||
my $queue_size = $self->sender->queue_size;
|
||||
$self->{gauge}->SetValue($self->{gauge}->GetRange - $queue_size);
|
||||
if ($queue_size == 0) {
|
||||
$self->print_completed;
|
||||
}
|
||||
}
|
||||
$self->{log_textctrl}->AppendText("$_\n") for @{$self->sender->purge_log};
|
||||
{
|
||||
my $temp = $self->sender->getT;
|
||||
if ($temp eq '') {
|
||||
$self->{temp_panel}->Hide;
|
||||
} else {
|
||||
if (!$self->{temp_panel}->IsShown) {
|
||||
$self->{temp_panel}->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
$self->{temp_text}->SetLabel($temp . "°C");
|
||||
|
||||
$temp = $self->sender->getB;
|
||||
if ($temp eq '') {
|
||||
$self->{bed_temp_text}->SetLabel('n.a.');
|
||||
} else {
|
||||
$self->{bed_temp_text}->SetLabel($temp . "°C");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
# set up the timer that sends temperature requests
|
||||
# (responses are handled by status_timer)
|
||||
{
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->temp_timer(Wx::Timer->new($self, $timer_id));
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
$self->sender->send("M105", 1); # send it through priority queue
|
||||
});
|
||||
}
|
||||
|
||||
my $box = Wx::StaticBox->new($self, -1, "");
|
||||
my $sizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# printer name
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, $self->printer_name, wxDefaultPosition, [220,-1]);
|
||||
my $font = $text->GetFont;
|
||||
$font->SetPointSize(20);
|
||||
$text->SetFont($font);
|
||||
$left_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# connection info
|
||||
{
|
||||
my $conn_sizer = Wx::FlexGridSizer->new(2, 2, 1, 0);
|
||||
$conn_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
$conn_sizer->AddGrowableCol(1, 1);
|
||||
$left_sizer->Add($conn_sizer, 0, wxEXPAND | wxTOP, 5);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Port:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
my $serial_port_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
{
|
||||
$self->{serial_port_combobox} = Wx::ComboBox->new($box, -1, $config->serial_port, wxDefaultPosition, wxDefaultSize, []);
|
||||
$self->{serial_port_combobox}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->update_serial_ports;
|
||||
$serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
|
||||
}
|
||||
{
|
||||
$self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
|
||||
$btn->SetToolTipString("Rescan serial ports")
|
||||
if $btn->can('SetToolTipString');
|
||||
$serial_port_sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
EVT_BUTTON($self, $btn, sub { $self->update_serial_ports });
|
||||
}
|
||||
$conn_sizer->Add($serial_port_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Speed:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
{
|
||||
$self->{serial_speed_combobox} = Wx::ComboBox->new($box, -1, $config->serial_speed, wxDefaultPosition, wxDefaultSize,
|
||||
["115200", "250000"]);
|
||||
$self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font);
|
||||
$serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
{
|
||||
$self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
|
||||
$serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
|
||||
EVT_BUTTON($self, $btn, \&disconnect);
|
||||
}
|
||||
$conn_sizer->Add($serial_speed_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
|
||||
# buttons
|
||||
{
|
||||
$self->{btn_connect} = my $btn = Wx::Button->new($box, -1, "Connect to printer", wxDefaultPosition, [-1, 40]);
|
||||
my $font = $btn->GetFont;
|
||||
$font->SetPointSize($font->GetPointSize + 2);
|
||||
$btn->SetFont($font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG));
|
||||
$left_sizer->Add($btn, 0, wxTOP, 15);
|
||||
EVT_BUTTON($self, $btn, \&connect);
|
||||
}
|
||||
|
||||
# print progress bar
|
||||
{
|
||||
my $gauge = $self->{gauge} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
|
||||
$left_sizer->Add($self->{gauge}, 0, wxEXPAND | wxTOP, 15);
|
||||
$gauge->Hide;
|
||||
}
|
||||
|
||||
# status
|
||||
$self->{status_text} = Wx::StaticText->new($box, -1, "", wxDefaultPosition, [200,-1]);
|
||||
$left_sizer->Add($self->{status_text}, 1, wxEXPAND | wxTOP, 15);
|
||||
|
||||
# manual control
|
||||
{
|
||||
$self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->Hide;
|
||||
$left_sizer->Add($btn, 0, wxTOP, 15);
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
|
||||
($self, $self->config, $self->sender);
|
||||
$dlg->ShowModal;
|
||||
});
|
||||
}
|
||||
|
||||
# temperature
|
||||
{
|
||||
my $temp_panel = $self->{temp_panel} = Wx::Panel->new($box, -1);
|
||||
my $temp_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
|
||||
my $temp_font = Wx::Font->new($Slic3r::GUI::small_font);
|
||||
$temp_font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
{
|
||||
my $text = Wx::StaticText->new($temp_panel, -1, "Temperature:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
$self->{temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||
$self->{temp_text}->SetFont($temp_font);
|
||||
$self->{temp_text}->SetForegroundColour(Wx::wxRED);
|
||||
$temp_sizer->Add($self->{temp_text}, 1, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
{
|
||||
my $text = Wx::StaticText->new($temp_panel, -1, "Bed:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
$self->{bed_temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||
$self->{bed_temp_text}->SetFont($temp_font);
|
||||
$self->{bed_temp_text}->SetForegroundColour(Wx::wxRED);
|
||||
$temp_sizer->Add($self->{bed_temp_text}, 1, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
$temp_panel->SetSizer($temp_sizer);
|
||||
$temp_panel->Hide;
|
||||
$left_sizer->Add($temp_panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
|
||||
}
|
||||
|
||||
# print jobs panel
|
||||
$self->{print_jobs_sizer} = my $print_jobs_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Queue:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$print_jobs_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
|
||||
$self->{jobs_panel} = Wx::ScrolledWindow->new($box, -1, wxDefaultPosition, wxDefaultSize,
|
||||
wxVSCROLL | wxBORDER_NONE);
|
||||
$self->{jobs_panel}->SetScrollbars(0, 1, 0, 1);
|
||||
$self->{jobs_panel_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{jobs_panel}->SetSizer($self->{jobs_panel_sizer});
|
||||
$print_jobs_sizer->Add($self->{jobs_panel}, 1, wxEXPAND, 0);
|
||||
|
||||
# TODO: fix this. We're trying to pass the scroll event to the parent but it
|
||||
# doesn't work.
|
||||
EVT_SCROLLWIN($self->{jobs_panel}, sub {
|
||||
my ($panel, $event) = @_;
|
||||
|
||||
my $controller = $self->GetParent;
|
||||
my $new_event = Wx::ScrollWinEvent->new(
|
||||
$event->GetEventType,
|
||||
$event->GetPosition,
|
||||
$event->GetOrientation,
|
||||
);
|
||||
$controller->ProcessEvent($new_event);
|
||||
}) if 0;
|
||||
}
|
||||
|
||||
my $log_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Log:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$log_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
|
||||
my $log = $self->{log_textctrl} = Wx::TextCtrl->new($box, -1, "", wxDefaultPosition, wxDefaultSize,
|
||||
wxTE_MULTILINE | wxBORDER_NONE);
|
||||
$log->SetBackgroundColour($box->GetBackgroundColour);
|
||||
$log->SetFont($Slic3r::GUI::small_font);
|
||||
$log->SetEditable(0);
|
||||
$log_sizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
$sizer->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
|
||||
$sizer->Add($print_jobs_sizer, 2, wxEXPAND | wxALL, 0);
|
||||
$sizer->Add($log_sizer, 1, wxEXPAND | wxLEFT, 15);
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
$self->_update_connection_controls;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub is_connected {
|
||||
my ($self) = @_;
|
||||
return $self->sender && $self->sender->is_connected;
|
||||
}
|
||||
|
||||
sub _update_connection_controls {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->{btn_connect}->Show;
|
||||
$self->{btn_disconnect}->Hide;
|
||||
$self->{serial_port_combobox}->Enable;
|
||||
$self->{serial_speed_combobox}->Enable;
|
||||
$self->{btn_rescan_serial}->Enable;
|
||||
$self->{btn_manual_control}->Hide;
|
||||
$self->{btn_manual_control}->Disable;
|
||||
|
||||
if ($self->is_connected) {
|
||||
$self->{btn_connect}->Hide;
|
||||
$self->{btn_manual_control}->Show;
|
||||
if (!$self->printing || $self->printing->paused) {
|
||||
$self->{btn_disconnect}->Show;
|
||||
$self->{btn_manual_control}->Enable;
|
||||
}
|
||||
$self->{serial_port_combobox}->Disable;
|
||||
$self->{serial_speed_combobox}->Disable;
|
||||
$self->{btn_rescan_serial}->Disable;
|
||||
}
|
||||
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub set_status {
|
||||
my ($self, $status) = @_;
|
||||
$self->{status_text}->SetLabel($status);
|
||||
$self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
|
||||
$self->{status_text}->Refresh;
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub connect {
|
||||
my ($self) = @_;
|
||||
|
||||
return if $self->is_connected;
|
||||
|
||||
$self->set_status("Connecting...");
|
||||
$self->sender(Slic3r::GCode::Sender->new);
|
||||
my $res = $self->sender->connect(
|
||||
$self->{serial_port_combobox}->GetValue,
|
||||
$self->{serial_speed_combobox}->GetValue,
|
||||
);
|
||||
if (!$res) {
|
||||
$self->set_status("Connection failed. Check serial port and speed.");
|
||||
} else {
|
||||
if ($self->sender->wait_connected) {
|
||||
$self->set_status("Printer is online. You can now start printing from the queue on the right.");
|
||||
$self->status_timer->Start(STATUS_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
|
||||
$self->temp_timer->Start(TEMP_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
|
||||
|
||||
# request temperature now, without waiting for the timer
|
||||
$self->sender->send("M105", 1);
|
||||
} else {
|
||||
$self->set_status("Connection failed. Check serial port and speed.");
|
||||
}
|
||||
}
|
||||
$self->_update_connection_controls;
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub disconnect {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->status_timer->Stop;
|
||||
$self->temp_timer->Stop;
|
||||
return if !$self->is_connected;
|
||||
|
||||
$self->printing->printing(0) if $self->printing;
|
||||
$self->printing(undef);
|
||||
$self->{gauge}->Hide;
|
||||
$self->{temp_panel}->Hide;
|
||||
$self->sender->disconnect;
|
||||
$self->set_status("");
|
||||
$self->_update_connection_controls;
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub update_serial_ports {
|
||||
my ($self) = @_;
|
||||
|
||||
my $cb = $self->{serial_port_combobox};
|
||||
my $current = $cb->GetValue;
|
||||
$cb->Clear;
|
||||
$cb->Append($_) for Slic3r::GUI::scan_serial_ports;
|
||||
$cb->SetValue($current);
|
||||
}
|
||||
|
||||
sub load_print_job {
|
||||
my ($self, $gcode_file, $filament_stats) = @_;
|
||||
|
||||
push @{$self->jobs}, my $job = Slic3r::GUI::Controller::PrinterPanel::PrintJob->new(
|
||||
id => time() . $gcode_file . rand(1000),
|
||||
gcode_file => $gcode_file,
|
||||
filament_stats => $filament_stats,
|
||||
);
|
||||
$self->reload_jobs;
|
||||
return $job;
|
||||
}
|
||||
|
||||
sub delete_job {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
$self->jobs([ grep $_->id ne $job->id, @{$self->jobs} ]);
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub print_job {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
$self->printing($job);
|
||||
$job->printing(1);
|
||||
$self->reload_jobs;
|
||||
|
||||
open my $fh, '<', $job->gcode_file;
|
||||
my $line_count = 0;
|
||||
while (my $row = <$fh>) {
|
||||
$self->sender->send($row);
|
||||
$line_count++;
|
||||
}
|
||||
close $fh;
|
||||
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->SetRange($line_count);
|
||||
$self->{gauge}->SetValue(0);
|
||||
$self->{gauge}->Enable;
|
||||
$self->{gauge}->Show;
|
||||
$self->Layout;
|
||||
|
||||
$self->set_status('Printing...');
|
||||
$self->{log_textctrl}->AppendText(sprintf "=====\n");
|
||||
$self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name);
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp);
|
||||
}
|
||||
|
||||
sub print_completed {
|
||||
my ($self) = @_;
|
||||
|
||||
my $job = $self->printing;
|
||||
$self->printing(undef);
|
||||
$job->printing(0);
|
||||
$job->printed(1);
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Hide;
|
||||
$self->Layout;
|
||||
|
||||
$self->set_status('Print completed.');
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print completed at %s\n", $self->_timestamp);
|
||||
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub reload_jobs {
|
||||
my ($self) = @_;
|
||||
|
||||
# reorder jobs
|
||||
@{$self->jobs} = sort { ($a->printed <=> $b->printed) || ($a->timestamp <=> $b->timestamp) }
|
||||
@{$self->jobs};
|
||||
|
||||
# remove all panels
|
||||
foreach my $child ($self->{jobs_panel_sizer}->GetChildren) {
|
||||
my $window = $child->GetWindow;
|
||||
$self->{jobs_panel_sizer}->Detach($window);
|
||||
# now $child does not exist anymore
|
||||
$window->Destroy;
|
||||
}
|
||||
|
||||
# re-add all panels
|
||||
foreach my $job (@{$self->jobs}) {
|
||||
my $panel = Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel->new($self->{jobs_panel}, $job);
|
||||
$self->{jobs_panel_sizer}->Add($panel, 0, wxEXPAND | wxBOTTOM, 5);
|
||||
|
||||
$panel->on_delete_job(sub {
|
||||
my ($job) = @_;
|
||||
$self->delete_job($job);
|
||||
});
|
||||
$panel->on_print_job(sub {
|
||||
my ($job) = @_;
|
||||
$self->print_job($job);
|
||||
});
|
||||
$panel->on_pause_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->pause_queue;
|
||||
$job->paused(1);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Disable;
|
||||
$self->set_status('Print is paused. Click on Resume to continue.');
|
||||
});
|
||||
$panel->on_abort_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->purge_queue;
|
||||
$self->printing(undef);
|
||||
$job->printing(0);
|
||||
$job->paused(0);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Disable;
|
||||
$self->{gauge}->Hide;
|
||||
$self->set_status('Print was aborted.');
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print aborted at %s\n", $self->_timestamp);
|
||||
});
|
||||
$panel->on_resume_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->resume_queue;
|
||||
$job->paused(0);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Enable;
|
||||
$self->set_status('Printing...');
|
||||
});
|
||||
$panel->enable_print if $self->is_connected && !$self->printing;
|
||||
|
||||
EVT_MOUSEWHEEL($panel, sub {
|
||||
my (undef, $event) = @_;
|
||||
Wx::PostEvent($self->{jobs_panel}, $event);
|
||||
$event->Skip;
|
||||
});
|
||||
}
|
||||
|
||||
$self->{jobs_panel}->Layout;
|
||||
$self->{print_jobs_sizer}->Layout;
|
||||
}
|
||||
|
||||
sub _timestamp {
|
||||
my ($self) = @_;
|
||||
|
||||
my @time = localtime(time);
|
||||
return sprintf '%02d:%02d:%02d', @time[2,1,0];
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Controller::PrinterPanel::PrintJob;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
|
||||
has 'id' => (is => 'ro', required => 1);
|
||||
has 'timestamp' => (is => 'ro', default => sub { time });
|
||||
has 'gcode_file' => (is => 'ro', required => 1);
|
||||
has 'filament_stats' => (is => 'rw');
|
||||
has 'printing' => (is => 'rw', default => sub { 0 });
|
||||
has 'paused' => (is => 'rw', default => sub { 0 });
|
||||
has 'printed' => (is => 'rw', default => sub { 0 });
|
||||
|
||||
sub name {
|
||||
my ($self) = @_;
|
||||
return basename($self->gcode_file);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :font :dialog :icon :timer
|
||||
:colour :brush :pen);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TIMER EVT_ERASE_BACKGROUND);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(job on_delete_job on_print_job on_pause_print on_resume_print
|
||||
on_abort_print blink_timer));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $job) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
$self->job($job);
|
||||
$self->SetBackgroundColour(wxWHITE);
|
||||
|
||||
{
|
||||
my $white_brush = Wx::Brush->new(wxWHITE, wxSOLID);
|
||||
my $pen = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
EVT_ERASE_BACKGROUND($self, sub {
|
||||
my ($self, $event) = @_;
|
||||
my $dc = $event->GetDC;
|
||||
my $size = $self->GetSize;
|
||||
$dc->SetBrush($white_brush);
|
||||
$dc->SetPen($pen);
|
||||
$dc->DrawRoundedRectangle(0, 0, $size->GetWidth,$size->GetHeight, 6);
|
||||
});
|
||||
}
|
||||
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
$self->{job_name_textctrl} = my $text = Wx::StaticText->new($self, -1, $job->name, wxDefaultPosition, wxDefaultSize);
|
||||
my $font = $text->GetFont;
|
||||
$font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
$text->SetFont($font);
|
||||
if ($job->printed) {
|
||||
$text->SetForegroundColour($Slic3r::GUI::grey);
|
||||
}
|
||||
$left_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
}
|
||||
{
|
||||
my $filament_stats = join "\n",
|
||||
map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)",
|
||||
sort keys %{$job->filament_stats};
|
||||
my $text = Wx::StaticText->new($self, -1, $filament_stats, wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
if ($job->printed && !$job->printing) {
|
||||
$text->SetForegroundColour($Slic3r::GUI::grey);
|
||||
}
|
||||
$left_sizer->Add($text, 0, wxEXPAND | wxTOP, 6);
|
||||
}
|
||||
|
||||
my $buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
my $button_style = Wx::wxBORDER_NONE | wxBU_EXACTFIT;
|
||||
{
|
||||
my $btn = $self->{btn_delete} = Wx::Button->new($self, -1, 'Delete',
|
||||
wxDefaultPosition, wxDefaultSize, $button_style);
|
||||
$btn->SetToolTipString("Delete this job from print queue")
|
||||
if $btn->can('SetToolTipString');
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
|
||||
if ($job->printing) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete this print job?", 'Delete Job', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
|
||||
return unless $res == wxID_YES;
|
||||
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_delete_job->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $label = $job->printed ? 'Print Again' : 'Print This';
|
||||
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_bold_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
#$btn->SetBitmapPosition(wxRIGHT);
|
||||
$btn->Hide;
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_print_job->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_pause} = Wx::Button->new($self, -1, "Pause", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing || $job->paused) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_pause_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_resume} = Wx::Button->new($self, -1, "Resume", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing || !$job->paused) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_resume_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_abort} = Wx::Button->new($self, -1, "Abort", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_abort_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($left_sizer, 1, wxEXPAND | wxALL, 6);
|
||||
$sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 6);
|
||||
$self->SetSizer($sizer);
|
||||
|
||||
# set-up the timer that changes the job name color while printing
|
||||
if ($self->job->printing && !$self->job->paused) {
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->blink_timer(Wx::Timer->new($self, $timer_id));
|
||||
my $blink = 0; # closure
|
||||
my $colour = Wx::Colour->new(0, 190, 0);
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
$self->{job_name_textctrl}->SetForegroundColour($blink ? Wx::wxBLACK : $colour);
|
||||
$blink = !$blink;
|
||||
});
|
||||
$self->blink_timer->Start(1000, wxTIMER_CONTINUOUS);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub enable_print {
|
||||
my ($self) = @_;
|
||||
|
||||
if (!$self->job->printing) {
|
||||
$self->{btn_print}->Show;
|
||||
}
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
|
||||
# There's a gap between the time Perl destroys the wxPanel object and
|
||||
# the blink_timer member, so the wxTimer might still fire an event which
|
||||
# isn't handled properly, causing a crash. So we ensure that blink_timer
|
||||
# is stopped before we destroy the wxPanel.
|
||||
$self->blink_timer->Stop if $self->blink_timer;
|
||||
return $self->SUPER::Destroy;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -26,13 +26,17 @@ our $appController;
|
|||
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 change of object selection
|
||||
# 3) To update the status bar with the progress information.
|
||||
our $PROGRESS_BAR_EVENT = Wx::NewEventType;
|
||||
# 4) To display an error dialog box from a thread on the UI thread.
|
||||
our $ERROR_EVENT = Wx::NewEventType;
|
||||
# 5) To inform about a change of object selection
|
||||
our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType;
|
||||
# 4) To inform about a change of object settings
|
||||
# 6) To inform about a change of object settings
|
||||
our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType;
|
||||
# 5) To inform about a remove of object
|
||||
# 7) To inform about a remove of object
|
||||
our $OBJECT_REMOVE_EVENT = Wx::NewEventType;
|
||||
# 6) To inform about a update of the scene
|
||||
# 8) To inform about a update of the scene
|
||||
our $UPDATE_SCENE_EVENT = Wx::NewEventType;
|
||||
|
||||
sub new {
|
||||
|
|
@ -53,13 +57,11 @@ sub new {
|
|||
}
|
||||
|
||||
# store input params
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
$self->{no_controller} = $params{no_controller};
|
||||
$self->{no_plater} = $params{no_plater};
|
||||
$self->{loaded} = 0;
|
||||
$self->{lang_ch_event} = $params{lang_ch_event};
|
||||
$self->{preferences_event} = $params{preferences_event};
|
||||
|
||||
|
||||
# initialize tabpanel and menubar
|
||||
$self->_init_tabpanel;
|
||||
$self->_init_menubar;
|
||||
|
|
@ -74,8 +76,8 @@ sub new {
|
|||
$self->{statusbar}->Embed;
|
||||
$self->{statusbar}->SetStatusText(L("Version ").$Slic3r::VERSION.L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"));
|
||||
# Make the global status bar and its progress indicator available in C++
|
||||
#FIXME Vojtech: Merging error
|
||||
# $appController->set_global_progress_indicator($self->{statusbar});
|
||||
Slic3r::GUI::set_progress_status_bar($self->{statusbar});
|
||||
$appController->set_global_progress_indicator($self->{statusbar});
|
||||
|
||||
$appController->set_model($self->{plater}->{model});
|
||||
$appController->set_print($self->{plater}->{print});
|
||||
|
|
@ -110,6 +112,7 @@ sub new {
|
|||
# Save the slic3r.ini. Usually the ini file is saved from "on idle" callback,
|
||||
# but in rare cases it may not have been called yet.
|
||||
wxTheApp->{app_config}->save;
|
||||
$self->{statusbar}->ResetCancelCallback();
|
||||
$self->{plater}->{print} = undef if($self->{plater});
|
||||
Slic3r::GUI::_3DScene::remove_all_canvases();
|
||||
Slic3r::GUI::deregister_on_request_update_callback();
|
||||
|
|
@ -146,9 +149,6 @@ sub _init_tabpanel {
|
|||
event_remove_object => $OBJECT_REMOVE_EVENT,
|
||||
event_update_scene => $UPDATE_SCENE_EVENT,
|
||||
), L("Plater"));
|
||||
if (!$self->{no_controller}) {
|
||||
$panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller"));
|
||||
}
|
||||
}
|
||||
|
||||
#TODO this is an example of a Slic3r XS interface call to add a new preset editor page to the main view.
|
||||
|
|
@ -197,8 +197,6 @@ sub _init_tabpanel {
|
|||
? 'load_current_preset' : 'update_tab_ui';
|
||||
$self->{options_tabs}{$tab_name_other}->$update_action;
|
||||
}
|
||||
# Update the controller printers.
|
||||
$self->{controller}->update_presets($presets) if $self->{controller};
|
||||
}
|
||||
$self->{plater}->on_config_change($tab->get_config);
|
||||
}
|
||||
|
|
@ -239,12 +237,29 @@ sub _init_tabpanel {
|
|||
$self->{plater}->update();
|
||||
});
|
||||
|
||||
|
||||
Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT);
|
||||
Slic3r::GUI::create_preset_tabs($VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT);
|
||||
$self->{options_tabs} = {};
|
||||
for my $tab_name (qw(print filament sla_material printer)) {
|
||||
$self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name");
|
||||
}
|
||||
|
||||
# Update progress bar with an event sent by the slicing core.
|
||||
EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
if (defined $self->{progress_dialog}) {
|
||||
# If a progress dialog is open, update it.
|
||||
$self->{progress_dialog}->Update($event->GetInt, $event->GetString . "…");
|
||||
} else {
|
||||
# Otherwise update the main window status bar.
|
||||
$self->{statusbar}->SetProgress($event->GetInt);
|
||||
$self->{statusbar}->SetStatusText($event->GetString . "…");
|
||||
}
|
||||
});
|
||||
|
||||
EVT_COMMAND($self, -1, $ERROR_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
Slic3r::GUI::show_error($self, $event->GetString);
|
||||
});
|
||||
|
||||
if ($self->{plater}) {
|
||||
$self->{plater}->on_select_preset(sub {
|
||||
|
|
@ -287,31 +302,8 @@ sub _init_menubar {
|
|||
$self->export_configbundle;
|
||||
}, undef, 'lorry_go.png');
|
||||
$fileMenu->AppendSeparator();
|
||||
my $repeat;
|
||||
$self->_append_menu_item($fileMenu, L("Q&uick Slice…\tCtrl+U"), L('Slice a file into a G-code'), sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->quick_slice;
|
||||
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
|
||||
});
|
||||
}, undef, 'cog_go.png');
|
||||
$self->_append_menu_item($fileMenu, L("Quick Slice and Save &As…\tCtrl+Alt+U"), L('Slice a file into a G-code, save as'), sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->quick_slice(save_as => 1);
|
||||
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
|
||||
});
|
||||
}, undef, 'cog_go.png');
|
||||
$repeat = $self->_append_menu_item($fileMenu, L("&Repeat Last Quick Slice\tCtrl+Shift+U"), L('Repeat last quick slice'), sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->quick_slice(reslice => 1);
|
||||
});
|
||||
}, undef, 'cog_go.png');
|
||||
$repeat->Enable(0);
|
||||
$fileMenu->AppendSeparator();
|
||||
$self->_append_menu_item($fileMenu, L("Slice to SV&G…\tCtrl+G"), L('Slice file to a multi-layer SVG'), sub {
|
||||
$self->quick_slice(save_as => 1, export_svg => 1);
|
||||
}, undef, 'shape_handles.png');
|
||||
$self->_append_menu_item($fileMenu, L("Slice to PNG…"), L('Slice file to a set of PNG files'), sub {
|
||||
$self->slice_to_png; #$self->quick_slice(save_as => 0, export_png => 1);
|
||||
$self->slice_to_png;
|
||||
}, undef, 'shape_handles.png');
|
||||
$self->{menu_item_reslice_now} = $self->_append_menu_item(
|
||||
$fileMenu, L("(&Re)Slice Now\tCtrl+S"), L('Start new slicing process'),
|
||||
|
|
@ -465,135 +457,6 @@ sub slice_to_png {
|
|||
$appController->print_ctl()->slice_to_png();
|
||||
}
|
||||
|
||||
# To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
|
||||
sub quick_slice {
|
||||
my ($self, %params) = @_;
|
||||
|
||||
my $progress_dialog;
|
||||
eval {
|
||||
# validate configuration
|
||||
my $config = wxTheApp->{preset_bundle}->full_config();
|
||||
$config->validate;
|
||||
|
||||
# select input file
|
||||
my $input_file;
|
||||
if (!$params{reslice}) {
|
||||
my $dialog = Wx::FileDialog->new($self, L('Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):'),
|
||||
wxTheApp->{app_config}->get_last_dir, "",
|
||||
&Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if ($dialog->ShowModal != wxID_OK) {
|
||||
$dialog->Destroy;
|
||||
return;
|
||||
}
|
||||
$input_file = $dialog->GetPaths;
|
||||
$dialog->Destroy;
|
||||
$qs_last_input_file = $input_file unless $params{export_svg};
|
||||
} else {
|
||||
if (!defined $qs_last_input_file) {
|
||||
Wx::MessageDialog->new($self, L("No previously sliced file."),
|
||||
L('Error'), wxICON_ERROR | wxOK)->ShowModal();
|
||||
return;
|
||||
}
|
||||
if (! -e $qs_last_input_file) {
|
||||
Wx::MessageDialog->new($self, L("Previously sliced file (").$qs_last_input_file.L(") not found."),
|
||||
L('File Not Found'), wxICON_ERROR | wxOK)->ShowModal();
|
||||
return;
|
||||
}
|
||||
$input_file = $qs_last_input_file;
|
||||
}
|
||||
my $input_file_basename = basename($input_file);
|
||||
wxTheApp->{app_config}->update_skein_dir(dirname($input_file));
|
||||
|
||||
my $print_center;
|
||||
{
|
||||
my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape});
|
||||
$print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center});
|
||||
}
|
||||
|
||||
my $sprint = Slic3r::Print::Simple->new(
|
||||
print_center => $print_center,
|
||||
status_cb => sub {
|
||||
my ($percent, $message) = @_;
|
||||
$progress_dialog->Update($percent, "$message…");
|
||||
},
|
||||
);
|
||||
|
||||
# keep model around
|
||||
my $model = Slic3r::Model->read_from_file($input_file);
|
||||
|
||||
$sprint->apply_config($config);
|
||||
$sprint->set_model($model);
|
||||
|
||||
# Copy the names of active presets into the placeholder parser.
|
||||
wxTheApp->{preset_bundle}->export_selections_pp($sprint->placeholder_parser);
|
||||
|
||||
# select output file
|
||||
my $output_file;
|
||||
if ($params{reslice}) {
|
||||
$output_file = $qs_last_output_file if defined $qs_last_output_file;
|
||||
} elsif ($params{save_as}) {
|
||||
# The following line may die if the output_filename_format template substitution fails.
|
||||
$output_file = $sprint->output_filepath;
|
||||
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg};
|
||||
my $dlg = Wx::FileDialog->new($self, L('Save ') . ($params{export_svg} ? L('SVG') : L('G-code')) . L(' file as:'),
|
||||
wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)),
|
||||
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if ($dlg->ShowModal != wxID_OK) {
|
||||
$dlg->Destroy;
|
||||
return;
|
||||
}
|
||||
$output_file = $dlg->GetPath;
|
||||
$qs_last_output_file = $output_file unless $params{export_svg};
|
||||
wxTheApp->{app_config}->update_last_output_dir(dirname($output_file));
|
||||
$dlg->Destroy;
|
||||
} elsif($params{export_png}) {
|
||||
$output_file = $sprint->output_filepath;
|
||||
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/.zip/;
|
||||
# my $dlg = Wx::DirDialog->new($self, L('Choose output directory'));
|
||||
my $dlg = Wx::FileDialog->new($self, L('Save zip file as:'),
|
||||
wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)),
|
||||
basename($output_file), '*.zip', wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if ($dlg->ShowModal != wxID_OK) {
|
||||
$dlg->Destroy;
|
||||
return;
|
||||
}
|
||||
$output_file = $dlg->GetPath;
|
||||
$dlg->Destroy;
|
||||
}
|
||||
|
||||
# show processbar dialog
|
||||
$progress_dialog = Wx::ProgressDialog->new(L('Slicing…'), L("Processing ").$input_file_basename."…",
|
||||
100, $self, 4);
|
||||
$progress_dialog->Pulse;
|
||||
|
||||
{
|
||||
my @warnings = ();
|
||||
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
|
||||
|
||||
$sprint->output_file($output_file);
|
||||
if ($params{export_svg}) {
|
||||
$sprint->export_svg;
|
||||
}
|
||||
elsif($params{export_png}) {
|
||||
$sprint->export_png;
|
||||
}
|
||||
else {
|
||||
$sprint->export_gcode;
|
||||
}
|
||||
$sprint->status_cb(undef);
|
||||
Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
|
||||
}
|
||||
$progress_dialog->Destroy;
|
||||
undef $progress_dialog;
|
||||
|
||||
my $message = $input_file_basename.L(" was successfully sliced.");
|
||||
wxTheApp->notify($message);
|
||||
Wx::MessageDialog->new($self, $message, L('Slicing Done!'),
|
||||
wxOK | wxICON_INFORMATION)->ShowModal;
|
||||
};
|
||||
Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog });
|
||||
}
|
||||
|
||||
sub reslice_now {
|
||||
my ($self) = @_;
|
||||
$self->{plater}->reslice if $self->{plater};
|
||||
|
|
|
|||
|
|
@ -1,498 +0,0 @@
|
|||
# A dialog group object. Used by the Tab, Preferences dialog, ManualControlDialog etc.
|
||||
|
||||
package Slic3r::GUI::OptionsGroup;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp);
|
||||
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
|
||||
|
||||
has 'parent' => (is => 'ro', required => 1);
|
||||
has 'title' => (is => 'ro', required => 1);
|
||||
has 'on_change' => (is => 'rw', default => sub { sub {} });
|
||||
has 'staticbox' => (is => 'ro', default => sub { 1 });
|
||||
has 'label_width' => (is => 'rw', default => sub { 180 });
|
||||
has 'extra_column' => (is => 'rw', default => sub { undef });
|
||||
has 'label_font' => (is => 'rw');
|
||||
has 'sidetext_font' => (is => 'rw', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) });
|
||||
has 'sizer' => (is => 'rw');
|
||||
has '_disabled' => (is => 'rw', default => sub { 0 });
|
||||
has '_grid_sizer' => (is => 'rw');
|
||||
has '_options' => (is => 'ro', default => sub { {} });
|
||||
has '_fields' => (is => 'ro', default => sub { {} });
|
||||
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
if ($self->staticbox) {
|
||||
my $box = Wx::StaticBox->new($self->parent, -1, $self->title);
|
||||
$self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
|
||||
} else {
|
||||
$self->sizer(Wx::BoxSizer->new(wxVERTICAL));
|
||||
}
|
||||
|
||||
my $num_columns = 1;
|
||||
++$num_columns if $self->label_width != 0;
|
||||
++$num_columns if $self->extra_column;
|
||||
$self->_grid_sizer(Wx::FlexGridSizer->new(0, $num_columns, 0, 0));
|
||||
$self->_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
$self->_grid_sizer->AddGrowableCol($self->label_width != 0);
|
||||
|
||||
# TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
|
||||
$self->sizer->Add($self->_grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
|
||||
}
|
||||
|
||||
# this method accepts a Slic3r::GUI::OptionsGroup::Line object
|
||||
sub append_line {
|
||||
my ($self, $line) = @_;
|
||||
|
||||
if ($line->sizer || ($line->widget && $line->full_width)) {
|
||||
# full-width widgets are appended *after* the grid sizer, so after all the non-full-width lines
|
||||
my $sizer = $line->sizer // $line->widget->($self->parent);
|
||||
$self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
||||
return;
|
||||
}
|
||||
|
||||
my $grid_sizer = $self->_grid_sizer;
|
||||
|
||||
# if we have an extra column, build it
|
||||
if ($self->extra_column) {
|
||||
if (defined (my $item = $self->extra_column->($line))) {
|
||||
$grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
} else {
|
||||
# if the callback provides no sizer for the extra cell, put a spacer
|
||||
$grid_sizer->AddSpacer(1);
|
||||
}
|
||||
}
|
||||
|
||||
# build label if we have it
|
||||
my $label;
|
||||
if ($self->label_width != 0) {
|
||||
$label = Wx::StaticText->new($self->parent, -1, $line->label ? $line->label . ":" : "", wxDefaultPosition, [$self->label_width, -1]);
|
||||
$label->SetFont($self->label_font) if $self->label_font;
|
||||
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
|
||||
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
$label->SetToolTipString($line->label_tooltip) if $line->label_tooltip;
|
||||
}
|
||||
|
||||
# if we have a widget, add it to the sizer
|
||||
if ($line->widget) {
|
||||
my $widget_sizer = $line->widget->($self->parent);
|
||||
$grid_sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
|
||||
return;
|
||||
}
|
||||
|
||||
# if we have a single option with no sidetext just add it directly to the grid sizer
|
||||
my @options = @{$line->get_options};
|
||||
$self->_options->{$_->opt_id} = $_ for @options;
|
||||
if (@options == 1 && !$options[0]->sidetext && !$options[0]->side_widget && !@{$line->get_extra_widgets}) {
|
||||
my $option = $options[0];
|
||||
my $field = $self->_build_field($option);
|
||||
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
# if we're here, we have more than one option or a single option with sidetext
|
||||
# so we need a horizontal sizer to arrange these things
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$grid_sizer->Add($sizer, 0, 0, 0);
|
||||
|
||||
foreach my $i (0..$#options) {
|
||||
my $option = $options[$i];
|
||||
|
||||
# add label if any
|
||||
if ($option->label) {
|
||||
my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize);
|
||||
$field_label->SetFont($self->sidetext_font);
|
||||
$sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
|
||||
# add field
|
||||
my $field = $self->_build_field($option);
|
||||
$sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
||||
# add sidetext if any
|
||||
if ($option->sidetext) {
|
||||
my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize);
|
||||
$sidetext->SetFont($self->sidetext_font);
|
||||
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
|
||||
}
|
||||
|
||||
# add side widget if any
|
||||
if ($option->side_widget) {
|
||||
$sizer->Add($option->side_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);
|
||||
}
|
||||
|
||||
if ($option != $#options) {
|
||||
$sizer->AddSpacer(4);
|
||||
}
|
||||
}
|
||||
|
||||
# add extra sizers if any
|
||||
foreach my $extra_widget (@{$line->get_extra_widgets}) {
|
||||
$sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
||||
}
|
||||
}
|
||||
|
||||
sub create_single_option_line {
|
||||
my ($self, $option) = @_;
|
||||
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => $option->label,
|
||||
label_tooltip => $option->tooltip,
|
||||
);
|
||||
$option->label("");
|
||||
$line->append_option($option);
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
my ($self, $option) = @_;
|
||||
return $self->append_line($self->create_single_option_line($option));
|
||||
}
|
||||
|
||||
sub _build_field {
|
||||
my $self = shift;
|
||||
my ($opt) = @_;
|
||||
|
||||
my $opt_id = $opt->opt_id;
|
||||
my $on_change = sub {
|
||||
#! This function will be called from Field.
|
||||
my ($opt_id, $value) = @_;
|
||||
#! Call OptionGroup._on_change(...)
|
||||
$self->_on_change($opt_id, $value)
|
||||
unless $self->_disabled;
|
||||
};
|
||||
my $on_kill_focus = sub {
|
||||
my ($opt_id) = @_;
|
||||
$self->_on_kill_focus($opt_id);
|
||||
};
|
||||
|
||||
my $type = $opt->{gui_type} || $opt->{type};
|
||||
|
||||
my $field;
|
||||
if ($type eq 'bool') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::Checkbox->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'i') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::SpinCtrl->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'color') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type =~ /^(f|s|s@|percent)$/) {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'select' || $type eq 'select_open') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::Choice->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'f_enum_open' || $type eq 'i_enum_open' || $type eq 'i_enum_closed') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::NumericChoice->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'point') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::Point->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'slider') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::Slider->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
}
|
||||
return undef if !$field;
|
||||
|
||||
#! setting up a function that will be triggered when the field changes
|
||||
#! think of it as $field->on_change = ($on_change)
|
||||
$field->on_change($on_change);
|
||||
$field->on_kill_focus($on_kill_focus);
|
||||
$self->_fields->{$opt_id} = $field;
|
||||
|
||||
return $field->isa('Slic3r::GUI::OptionsGroup::Field::wxWindow')
|
||||
? $field->wxWindow
|
||||
: $field->wxSizer;
|
||||
}
|
||||
|
||||
sub get_option {
|
||||
my ($self, $opt_id) = @_;
|
||||
return undef if !exists $self->_options->{$opt_id};
|
||||
return $self->_options->{$opt_id};
|
||||
}
|
||||
|
||||
sub get_field {
|
||||
my ($self, $opt_id) = @_;
|
||||
return undef if !exists $self->_fields->{$opt_id};
|
||||
return $self->_fields->{$opt_id};
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self, $opt_id) = @_;
|
||||
|
||||
return if !exists $self->_fields->{$opt_id};
|
||||
return $self->_fields->{$opt_id}->get_value;
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $opt_id, $value) = @_;
|
||||
|
||||
return if !exists $self->_fields->{$opt_id};
|
||||
$self->_fields->{$opt_id}->set_value($value);
|
||||
}
|
||||
|
||||
sub _on_change {
|
||||
my ($self, $opt_id, $value) = @_;
|
||||
$self->on_change->($opt_id, $value);
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$_->enable for values %{$self->_fields};
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$_->disable for values %{$self->_fields};
|
||||
}
|
||||
|
||||
sub _on_kill_focus {
|
||||
my ($self, $opt_id) = @_;
|
||||
# nothing
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Line;
|
||||
use Moo;
|
||||
|
||||
has 'label' => (is => 'rw', default => sub { "" });
|
||||
has 'full_width' => (is => 'rw', default => sub { 0 });
|
||||
has 'label_tooltip' => (is => 'rw', default => sub { "" });
|
||||
has 'sizer' => (is => 'rw');
|
||||
has 'widget' => (is => 'rw');
|
||||
has '_options' => (is => 'ro', default => sub { [] });
|
||||
# Extra UI components after the label and the edit widget of the option.
|
||||
has '_extra_widgets' => (is => 'ro', default => sub { [] });
|
||||
|
||||
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
|
||||
sub append_option {
|
||||
my ($self, $option) = @_;
|
||||
push @{$self->_options}, $option;
|
||||
}
|
||||
|
||||
sub append_widget {
|
||||
my ($self, $widget) = @_;
|
||||
push @{$self->_extra_widgets}, $widget;
|
||||
}
|
||||
|
||||
sub get_options {
|
||||
my ($self) = @_;
|
||||
return [ @{$self->_options} ];
|
||||
}
|
||||
|
||||
sub get_extra_widgets {
|
||||
my ($self) = @_;
|
||||
return [ @{$self->_extra_widgets} ];
|
||||
}
|
||||
|
||||
|
||||
# Configuration of an option.
|
||||
# This very much reflects the content of the C++ ConfigOptionDef class.
|
||||
package Slic3r::GUI::OptionsGroup::Option;
|
||||
use Moo;
|
||||
|
||||
has 'opt_id' => (is => 'rw', required => 1);
|
||||
has 'type' => (is => 'rw', required => 1);
|
||||
has 'default' => (is => 'rw', required => 1);
|
||||
has 'gui_type' => (is => 'rw', default => sub { undef });
|
||||
has 'gui_flags' => (is => 'rw', default => sub { "" });
|
||||
has 'label' => (is => 'rw', default => sub { "" });
|
||||
has 'sidetext' => (is => 'rw', default => sub { "" });
|
||||
has 'tooltip' => (is => 'rw', default => sub { "" });
|
||||
has 'multiline' => (is => 'rw', default => sub { 0 });
|
||||
has 'full_width' => (is => 'rw', default => sub { 0 });
|
||||
has 'width' => (is => 'rw', default => sub { undef });
|
||||
has 'height' => (is => 'rw', default => sub { undef });
|
||||
has 'min' => (is => 'rw', default => sub { undef });
|
||||
has 'max' => (is => 'rw', default => sub { undef });
|
||||
has 'labels' => (is => 'rw', default => sub { [] });
|
||||
has 'values' => (is => 'rw', default => sub { [] });
|
||||
has 'readonly' => (is => 'rw', default => sub { 0 });
|
||||
has 'side_widget' => (is => 'rw', default => sub { undef });
|
||||
|
||||
|
||||
package Slic3r::GUI::ConfigOptionsGroup;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
extends 'Slic3r::GUI::OptionsGroup';
|
||||
has 'config' => (is => 'ro', required => 1);
|
||||
has 'full_labels' => (is => 'ro', default => sub { 0 });
|
||||
has '_opt_map' => (is => 'ro', default => sub { {} });
|
||||
|
||||
sub get_option {
|
||||
my ($self, $opt_key, $opt_index) = @_;
|
||||
|
||||
$opt_index //= -1;
|
||||
|
||||
if (!$self->config->has($opt_key)) {
|
||||
die "No $opt_key in ConfigOptionsGroup config";
|
||||
}
|
||||
|
||||
my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}");
|
||||
$self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ];
|
||||
|
||||
# Slic3r::Config::Options is a C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
|
||||
# The C++ counterpart is a constant singleton.
|
||||
my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config
|
||||
my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{gui_flags} =~ /\bserialized\b/);
|
||||
|
||||
return Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => $opt_id,
|
||||
type => $optdef->{type},
|
||||
default => $default_value,
|
||||
gui_type => $optdef->{gui_type},
|
||||
gui_flags => $optdef->{gui_flags},
|
||||
label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label},
|
||||
sidetext => $optdef->{sidetext},
|
||||
# calling serialize() ensures we get a stringified value
|
||||
tooltip => $optdef->{tooltip} . " (default: " . $self->config->serialize($opt_key) . ")",
|
||||
multiline => $optdef->{multiline},
|
||||
width => $optdef->{width},
|
||||
min => $optdef->{min},
|
||||
max => $optdef->{max},
|
||||
labels => $optdef->{labels},
|
||||
values => $optdef->{values},
|
||||
readonly => $optdef->{readonly},
|
||||
);
|
||||
}
|
||||
|
||||
sub create_single_option_line {
|
||||
my ($self, $opt_key, $opt_index) = @_;
|
||||
|
||||
my $option;
|
||||
if (ref($opt_key)) {
|
||||
$option = $opt_key;
|
||||
} else {
|
||||
$option = $self->get_option($opt_key, $opt_index);
|
||||
}
|
||||
return $self->SUPER::create_single_option_line($option);
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
my ($self, $option, $opt_index) = @_;
|
||||
return $self->append_line($self->create_single_option_line($option, $opt_index));
|
||||
}
|
||||
|
||||
# Initialize UI components with the config values.
|
||||
sub reload_config {
|
||||
my ($self) = @_;
|
||||
|
||||
foreach my $opt_id (keys %{ $self->_opt_map }) {
|
||||
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
|
||||
my $option = $self->_options->{$opt_id};
|
||||
$self->set_value($opt_id, $self->_get_config_value($opt_key, $opt_index, $option->gui_flags =~ /\bserialized\b/));
|
||||
}
|
||||
}
|
||||
|
||||
sub get_fieldc {
|
||||
my ($self, $opt_key, $opt_index) = @_;
|
||||
|
||||
$opt_index //= -1;
|
||||
my $opt_id = first { $self->_opt_map->{$_}[0] eq $opt_key && $self->_opt_map->{$_}[1] == $opt_index }
|
||||
keys %{$self->_opt_map};
|
||||
return defined($opt_id) ? $self->get_field($opt_id) : undef;
|
||||
}
|
||||
|
||||
sub _get_config_value {
|
||||
my ($self, $opt_key, $opt_index, $deserialize) = @_;
|
||||
|
||||
if ($deserialize) {
|
||||
# Want to edit a vector value (currently only multi-strings) in a single edit box.
|
||||
# Aggregate the strings the old way.
|
||||
# Currently used for the post_process config value only.
|
||||
die "Can't deserialize option indexed value" if $opt_index != -1;
|
||||
return join(';', @{$self->config->get($opt_key)});
|
||||
} else {
|
||||
return $opt_index == -1
|
||||
? $self->config->get($opt_key)
|
||||
: $self->config->get_at($opt_key, $opt_index);
|
||||
}
|
||||
}
|
||||
|
||||
sub _on_change {
|
||||
my ($self, $opt_id, $value) = @_;
|
||||
|
||||
if (exists $self->_opt_map->{$opt_id}) {
|
||||
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
|
||||
my $option = $self->_options->{$opt_id};
|
||||
|
||||
# get value
|
||||
my $field_value = $self->get_value($opt_id);
|
||||
if ($option->gui_flags =~ /\bserialized\b/) {
|
||||
die "Can't set serialized option indexed value" if $opt_index != -1;
|
||||
# Split a string to multiple strings by a semi-colon. This is the old way of storing multi-string values.
|
||||
# Currently used for the post_process config value only.
|
||||
my @values = split /;/, $field_value;
|
||||
$self->config->set($opt_key, \@values);
|
||||
} else {
|
||||
if ($opt_index == -1) {
|
||||
$self->config->set($opt_key, $field_value);
|
||||
} else {
|
||||
my $value = $self->config->get($opt_key);
|
||||
$value->[$opt_index] = $field_value;
|
||||
$self->config->set($opt_key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->SUPER::_on_change($opt_id, $value);
|
||||
}
|
||||
|
||||
sub _on_kill_focus {
|
||||
my ($self, $opt_id) = @_;
|
||||
|
||||
# when a field loses focus, reapply the config value to it
|
||||
# (thus discarding any invalid input and reverting to the last
|
||||
# accepted value)
|
||||
$self->reload_config;
|
||||
}
|
||||
|
||||
# Static text shown among the options.
|
||||
# Currently used for the filament cooling legend only.
|
||||
package Slic3r::GUI::OptionsGroup::StaticText;
|
||||
use Wx qw(:misc :systemsettings);
|
||||
use base 'Wx::StaticText';
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$self->SetFont($font);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub SetText {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->SetLabel($value);
|
||||
$self->Wrap(400);
|
||||
$self->GetParent->Layout;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,605 +0,0 @@
|
|||
# An input field class prototype.
|
||||
package Slic3r::GUI::OptionsGroup::Field;
|
||||
use Moo;
|
||||
|
||||
# This is a base class for option fields.
|
||||
|
||||
has 'parent' => (is => 'ro', required => 1);
|
||||
# Slic3r::GUI::OptionsGroup::Option
|
||||
has 'option' => (is => 'ro', required => 1);
|
||||
# On change callback
|
||||
has 'on_change' => (is => 'rw', default => sub { sub {} });
|
||||
has 'on_kill_focus' => (is => 'rw', default => sub { sub {} });
|
||||
# If set, the callback $self->on_change is not called.
|
||||
# This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
has 'disable_change_event' => (is => 'rw', default => sub { 0 });
|
||||
|
||||
# This method should not fire the on_change event
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
die "Method not implemented";
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
die "Method not implemented";
|
||||
}
|
||||
|
||||
sub set_tooltip {
|
||||
my ($self, $tooltip) = @_;
|
||||
|
||||
$self->SetToolTipString($tooltip)
|
||||
if $tooltip && $self->can('SetToolTipString');
|
||||
}
|
||||
|
||||
sub toggle {
|
||||
my ($self, $enable) = @_;
|
||||
$enable ? $self->enable : $self->disable;
|
||||
}
|
||||
|
||||
sub _on_change {
|
||||
my ($self, $opt_id) = @_;
|
||||
|
||||
$self->on_change->($opt_id, $self->get_value)
|
||||
unless $self->disable_change_event;
|
||||
}
|
||||
|
||||
sub _on_kill_focus {
|
||||
my ($self, $opt_id, $s, $event) = @_;
|
||||
|
||||
# Without this, there will be nasty focus bugs on Windows.
|
||||
# Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
|
||||
# non-command events to allow the default handling to take place."
|
||||
$event->Skip(1);
|
||||
|
||||
$self->on_kill_focus->($opt_id);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::wxWindow;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field';
|
||||
|
||||
has 'wxWindow' => (is => 'rw', trigger => 1); # wxWindow object
|
||||
|
||||
sub _default_size {
|
||||
my ($self) = @_;
|
||||
|
||||
# default width on Windows is too large
|
||||
return Wx::Size->new($self->option->width || 60, $self->option->height || -1);
|
||||
}
|
||||
|
||||
sub _trigger_wxWindow {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->wxWindow->SetToolTipString($self->option->tooltip)
|
||||
if $self->option->tooltip && $self->wxWindow->can('SetToolTipString');
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->wxWindow->SetValue($value);
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->wxWindow->GetValue;
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->wxWindow->Enable;
|
||||
$self->wxWindow->Refresh;
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->wxWindow->Disable;
|
||||
$self->wxWindow->Refresh;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::Checkbox;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use Wx qw(:misc);
|
||||
use Wx::Event qw(EVT_CHECKBOX);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $field = Wx::CheckBox->new($self->parent, -1, "");
|
||||
$self->wxWindow($field);
|
||||
$field->SetValue($self->option->default);
|
||||
$field->Disable if $self->option->readonly;
|
||||
|
||||
EVT_CHECKBOX($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->wxWindow->GetValue ? 1 : 0;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use Wx qw(:misc);
|
||||
use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
|
||||
|
||||
has 'tmp_value' => (is => 'rw');
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $field = Wx::SpinCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
|
||||
0, $self->option->min || 0, $self->option->max || 2147483647, $self->option->default);
|
||||
$self->wxWindow($field);
|
||||
|
||||
EVT_SPINCTRL($self->parent, $field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
my ($s, $event) = @_;
|
||||
|
||||
# On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
# 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
|
||||
$self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
|
||||
$self->_on_change($self->option->opt_id);
|
||||
# We don't reset tmp_value here because _on_change might put callbacks
|
||||
# in the CallAfter queue, and we want the tmp value to be available from
|
||||
# them as well.
|
||||
});
|
||||
EVT_KILL_FOCUS($field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->tmp_value // $self->wxWindow->GetValue;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use Wx qw(:misc :textctrl);
|
||||
use Wx::Event qw(EVT_TEXT EVT_KILL_FOCUS);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $style = 0;
|
||||
$style = wxTE_MULTILINE if $self->option->multiline;
|
||||
my $field = Wx::TextCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition,
|
||||
$self->_default_size, $style);
|
||||
$self->wxWindow($field);
|
||||
|
||||
# TODO: test loading a config that has empty string for multi-value options like 'wipe'
|
||||
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_KILL_FOCUS($field, sub {
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->wxWindow->Enable;
|
||||
$self->wxWindow->SetEditable(1);
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->wxWindow->Disable;
|
||||
$self->wxWindow->SetEditable(0);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::Choice;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:misc :combobox);
|
||||
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $style = 0;
|
||||
$style |= wxCB_READONLY if defined $self->option->gui_type && $self->option->gui_type ne 'select_open';
|
||||
my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
|
||||
$self->option->labels || $self->option->values || [], $style);
|
||||
$self->wxWindow($field);
|
||||
|
||||
$self->set_value($self->option->default);
|
||||
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
|
||||
my $idx;
|
||||
if ($self->option->values) {
|
||||
$idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
|
||||
# if value is not among indexes values we use SetValue()
|
||||
}
|
||||
|
||||
if (defined $idx) {
|
||||
$self->wxWindow->SetSelection($idx);
|
||||
} else {
|
||||
$self->wxWindow->SetValue($value);
|
||||
}
|
||||
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub set_values {
|
||||
my ($self, $values) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
|
||||
# it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
# but we want to preserve it
|
||||
my $ww = $self->wxWindow;
|
||||
my $value = $ww->GetValue;
|
||||
$ww->Clear;
|
||||
$ww->Append($_) for @$values;
|
||||
$ww->SetValue($value);
|
||||
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
|
||||
if ($self->option->values) {
|
||||
my $idx = $self->wxWindow->GetSelection;
|
||||
if ($idx != &Wx::wxNOT_FOUND) {
|
||||
return $self->option->values->[$idx];
|
||||
}
|
||||
}
|
||||
return $self->wxWindow->GetValue;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::NumericChoice;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(wxTheApp :misc :combobox);
|
||||
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
|
||||
|
||||
# if option has no 'values', indices are values
|
||||
# if option has no 'labels', values are labels
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $field = Wx::ComboBox->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
|
||||
$self->option->labels || $self->option->values);
|
||||
$self->wxWindow($field);
|
||||
|
||||
$self->set_value($self->option->default);
|
||||
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
my $disable_change_event = $self->disable_change_event;
|
||||
$self->disable_change_event(1);
|
||||
|
||||
my $idx = $field->GetSelection; # get index of selected value
|
||||
my $label;
|
||||
|
||||
if ($self->option->labels && $idx <= $#{$self->option->labels}) {
|
||||
$label = $self->option->labels->[$idx];
|
||||
} elsif ($self->option->values && $idx <= $#{$self->option->values}) {
|
||||
$label = $self->option->values->[$idx];
|
||||
} else {
|
||||
$label = $idx;
|
||||
}
|
||||
|
||||
# The MSW implementation of wxComboBox will leave the field blank if we call
|
||||
# SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
|
||||
wxTheApp->CallAfter(sub {
|
||||
my $dce = $self->disable_change_event;
|
||||
$self->disable_change_event(1);
|
||||
|
||||
# ChangeValue() is not exported in wxPerl
|
||||
$field->SetValue($label);
|
||||
|
||||
$self->disable_change_event($dce);
|
||||
});
|
||||
|
||||
$self->disable_change_event($disable_change_event);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
|
||||
my $field = $self->wxWindow;
|
||||
if ($self->option->gui_flags =~ /\bshow_value\b/) {
|
||||
$field->SetValue($value);
|
||||
} else {
|
||||
if ($self->option->values) {
|
||||
# check whether we have a value index
|
||||
my $value_idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
|
||||
if (defined $value_idx) {
|
||||
$field->SetSelection($value_idx);
|
||||
$self->disable_change_event(0);
|
||||
return;
|
||||
}
|
||||
} elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
|
||||
# if we have no values, we expect value to be an index
|
||||
$field->SetValue($self->option->labels->[$value]);
|
||||
$self->disable_change_event(0);
|
||||
return;
|
||||
}
|
||||
$field->SetValue($value);
|
||||
}
|
||||
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
|
||||
my $label = $self->wxWindow->GetValue;
|
||||
if ($self->option->labels) {
|
||||
my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
|
||||
if (defined $value_idx) {
|
||||
if ($self->option->values) {
|
||||
return $self->option->values->[$value_idx];
|
||||
}
|
||||
return $value_idx;
|
||||
}
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use Wx qw(:misc :colour);
|
||||
use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
|
||||
$self->_string_to_colour($self->option->default), wxDefaultPosition,
|
||||
$self->_default_size);
|
||||
$self->wxWindow($field);
|
||||
|
||||
EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->wxWindow->SetColour($self->_string_to_colour($value));
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
|
||||
}
|
||||
|
||||
sub _string_to_colour {
|
||||
my ($self, $string) = @_;
|
||||
|
||||
$string =~ s/^#//;
|
||||
# If the color is in an invalid format, set it to white.
|
||||
$string = 'FFFFFF' if ($string !~ m/^[[:xdigit:]]{6}/);
|
||||
return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::wxSizer;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field';
|
||||
|
||||
has 'wxSizer' => (is => 'rw'); # wxSizer object
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::Point;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
|
||||
|
||||
has 'x_textctrl' => (is => 'rw');
|
||||
has 'y_textctrl' => (is => 'rw');
|
||||
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
use Wx qw(:misc :sizer);
|
||||
use Wx::Event qw(EVT_TEXT);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->wxSizer($sizer);
|
||||
|
||||
my $field_size = Wx::Size->new(40, -1);
|
||||
|
||||
$self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
|
||||
$self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
|
||||
|
||||
my @items = (
|
||||
Wx::StaticText->new($self->parent, -1, "x:"),
|
||||
$self->x_textctrl,
|
||||
Wx::StaticText->new($self->parent, -1, " y:"),
|
||||
$self->y_textctrl,
|
||||
);
|
||||
$sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for @items;
|
||||
|
||||
if ($self->option->tooltip) {
|
||||
foreach my $item (@items) {
|
||||
$item->SetToolTipString($self->option->tooltip)
|
||||
if $item->can('SetToolTipString');
|
||||
}
|
||||
}
|
||||
|
||||
EVT_TEXT($self->parent, $_, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
}) for $self->x_textctrl, $self->y_textctrl;
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->x_textctrl->SetValue($value->[X]);
|
||||
$self->y_textctrl->SetValue($value->[Y]);
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
|
||||
return [
|
||||
$self->x_textctrl->GetValue,
|
||||
$self->y_textctrl->GetValue,
|
||||
];
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->x_textctrl->Enable;
|
||||
$self->y_textctrl->Enable;
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->x_textctrl->Disable;
|
||||
$self->y_textctrl->Disable;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::Slider;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
|
||||
|
||||
has 'scale' => (is => 'rw', default => sub { 10 });
|
||||
has 'slider' => (is => 'rw');
|
||||
has 'textctrl' => (is => 'rw');
|
||||
|
||||
use Wx qw(:misc :sizer);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_TEXT EVT_KILL_FOCUS);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->wxSizer($sizer);
|
||||
|
||||
my $slider = Wx::Slider->new(
|
||||
$self->parent, -1,
|
||||
($self->option->default // $self->option->min) * $self->scale,
|
||||
($self->option->min // 0) * $self->scale,
|
||||
($self->option->max // 100) * $self->scale,
|
||||
wxDefaultPosition,
|
||||
[ $self->option->width // -1, $self->option->height // -1 ],
|
||||
);
|
||||
$self->slider($slider);
|
||||
|
||||
my $textctrl = Wx::TextCtrl->new($self->parent, -1, $slider->GetValue/$self->scale,
|
||||
wxDefaultPosition, [50,-1]);
|
||||
$self->textctrl($textctrl);
|
||||
|
||||
$sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0);
|
||||
$sizer->Add($textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
||||
EVT_SLIDER($self->parent, $slider, sub {
|
||||
if (! $self->disable_change_event) {
|
||||
# wxTextCtrl::SetLabel() does not work on Linux, use wxTextCtrl::SetValue() instead
|
||||
$self->textctrl->SetValue($self->get_value);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
}
|
||||
});
|
||||
EVT_TEXT($self->parent, $textctrl, sub {
|
||||
my $value = $textctrl->GetValue;
|
||||
if ($value =~ /^-?\d+(\.\d*)?$/) {
|
||||
$self->disable_change_event(1);
|
||||
$self->slider->SetValue($value*$self->scale);
|
||||
$self->disable_change_event(0);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
}
|
||||
});
|
||||
EVT_KILL_FOCUS($textctrl, sub {
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->slider->SetValue($value*$self->scale);
|
||||
$self->textctrl->SetLabel($self->get_value);
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->slider->GetValue/$self->scale;
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->slider->Enable;
|
||||
$self->textctrl->Enable;
|
||||
$self->textctrl->SetEditable(1);
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->slider->Disable;
|
||||
$self->textctrl->Disable;
|
||||
$self->textctrl->SetEditable(0);
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,372 +0,0 @@
|
|||
# 2D preview on the platter.
|
||||
# 3D objects are visualized by their convex hulls.
|
||||
|
||||
package Slic3r::GUI::Plater::2D;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl);
|
||||
use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $size, $objects, $model, $config) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
|
||||
# This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
|
||||
$self->{objects} = $objects;
|
||||
$self->{model} = $model;
|
||||
$self->{config} = $config;
|
||||
$self->{on_select_object} = sub {};
|
||||
$self->{on_double_click} = sub {};
|
||||
$self->{on_right_click} = sub {};
|
||||
$self->{on_instances_moved} = sub {};
|
||||
|
||||
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
|
||||
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
|
||||
$self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID);
|
||||
$self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT);
|
||||
$self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID);
|
||||
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
|
||||
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
|
||||
|
||||
$self->{user_drawn_background} = $^O ne 'darwin';
|
||||
|
||||
EVT_PAINT($self, \&repaint);
|
||||
EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
|
||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||
EVT_SIZE($self, sub {
|
||||
$self->update_bed_size;
|
||||
$self->Refresh;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub on_select_object {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_select_object} = $cb;
|
||||
}
|
||||
|
||||
sub on_double_click {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_double_click} = $cb;
|
||||
}
|
||||
|
||||
sub on_right_click {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_right_click} = $cb;
|
||||
}
|
||||
|
||||
sub on_instances_moved {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_instances_moved} = $cb;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::AutoBufferedPaintDC->new($self);
|
||||
my $size = $self->GetSize;
|
||||
my @size = ($size->GetWidth, $size->GetHeight);
|
||||
|
||||
if ($self->{user_drawn_background}) {
|
||||
# On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
# On MacOS the background is erased, on Windows the background is not erased
|
||||
# and on Linux/GTK the background is erased to gray color.
|
||||
# Fill DC with the background on Windows & Linux/GTK.
|
||||
my $brush_background = Wx::Brush->new(Wx::wxWHITE, wxSOLID);
|
||||
$dc->SetPen(wxWHITE_PEN);
|
||||
$dc->SetBrush($brush_background);
|
||||
my $rect = $self->GetUpdateRegion()->GetBox();
|
||||
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
|
||||
}
|
||||
|
||||
# draw grid
|
||||
$dc->SetPen($self->{grid_pen});
|
||||
$dc->DrawLine(map @$_, @$_) for @{$self->{grid}};
|
||||
|
||||
# draw bed
|
||||
{
|
||||
$dc->SetPen($self->{print_center_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0);
|
||||
}
|
||||
|
||||
# draw print center
|
||||
if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) {
|
||||
my $center = $self->unscaled_point_to_pixel($self->{print_center});
|
||||
$dc->SetPen($self->{print_center_pen});
|
||||
$dc->DrawLine($center->[X], 0, $center->[X], $size[Y]);
|
||||
$dc->DrawLine(0, $center->[Y], $size[X], $center->[Y]);
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel("X = " . sprintf('%.0f', $self->{print_center}->[X]), Wx::Rect->new(0, 0, $center->[X]*2, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
|
||||
$dc->DrawRotatedText("Y = " . sprintf('%.0f', $self->{print_center}->[Y]), 0, $center->[Y]+15, 90);
|
||||
}
|
||||
|
||||
# draw frame
|
||||
if (0) {
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawRectangle(0, 0, @size);
|
||||
}
|
||||
|
||||
# draw text if plate is empty
|
||||
if (!@{$self->{objects}}) {
|
||||
$dc->SetTextForeground(Wx::Colour->new(150,50,50));
|
||||
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel(
|
||||
join('-', +(localtime)[3,4]) eq '13-8'
|
||||
? L('What do you want to print today? ™') # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
|
||||
: L('Drag your objects here'),
|
||||
Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
# draw thumbnails
|
||||
$dc->SetPen(wxBLACK_PEN);
|
||||
$self->clean_instance_thumbnails;
|
||||
for my $obj_idx (0 .. $#{$self->{objects}}) {
|
||||
my $object = $self->{objects}[$obj_idx];
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
next unless defined $object->thumbnail;
|
||||
for my $instance_idx (0 .. $#{$model_object->instances}) {
|
||||
my $instance = $model_object->instances->[$instance_idx];
|
||||
next if !defined $object->transformed_thumbnail;
|
||||
|
||||
my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates
|
||||
$thumbnail->translate(map scale($_), @{$instance->offset});
|
||||
|
||||
$object->instance_thumbnails->[$instance_idx] = $thumbnail;
|
||||
|
||||
if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
|
||||
$dc->SetBrush($self->{dragged_brush});
|
||||
} elsif ($object->selected) {
|
||||
$dc->SetBrush($self->{selected_brush});
|
||||
} else {
|
||||
$dc->SetBrush($self->{objects_brush});
|
||||
}
|
||||
foreach my $expolygon (@$thumbnail) {
|
||||
foreach my $points (@{$expolygon->pp}) {
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($points, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (0) {
|
||||
# draw bounding box for debugging purposes
|
||||
my $bb = $model_object->instance_bounding_box($instance_idx);
|
||||
$bb->scale($self->{scaling_factor});
|
||||
# no need to translate by instance offset because instance_bounding_box() does that
|
||||
my $points = $bb->polygon->pp;
|
||||
$dc->SetPen($self->{clearance_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->_y($points), 0, 0);
|
||||
}
|
||||
|
||||
# if sequential printing is enabled and we have more than one object, draw clearance area
|
||||
if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) {
|
||||
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{clearance_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($clearance, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# draw skirt
|
||||
if (@{$self->{objects}} && $self->{config}->skirts) {
|
||||
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}};
|
||||
if (@points >= 3) {
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{skirt_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
}
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
my $pos = $event->GetPosition;
|
||||
my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]]
|
||||
if ($event->ButtonDown) {
|
||||
$self->{on_select_object}->(undef);
|
||||
# traverse objects and instances in reverse order, so that if they're overlapping
|
||||
# we get the one that gets drawn last, thus on top (as user expects that to move)
|
||||
OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) {
|
||||
my $object = $self->{objects}->[$obj_idx];
|
||||
for my $instance_idx (reverse 0 .. $#{ $object->instance_thumbnails }) {
|
||||
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
|
||||
if (defined first { $_->contour->contains_point($point) } @$thumbnail) {
|
||||
$self->{on_select_object}->($obj_idx);
|
||||
|
||||
if ($event->LeftDown) {
|
||||
# start dragging
|
||||
my $instance = $self->{model}->objects->[$obj_idx]->instances->[$instance_idx];
|
||||
my $instance_origin = [ map scale($_), @{$instance->offset} ];
|
||||
$self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units
|
||||
$point->x - $instance_origin->[X],
|
||||
$point->y - $instance_origin->[Y], #-
|
||||
];
|
||||
$self->{drag_object} = [ $obj_idx, $instance_idx ];
|
||||
} elsif ($event->RightDown) {
|
||||
$self->{on_right_click}->($pos->x, $pos->y);
|
||||
}
|
||||
|
||||
last OBJECTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->Refresh;
|
||||
} elsif ($event->LeftUp) {
|
||||
if ($self->{drag_object}) {
|
||||
$self->{on_instances_moved}->();
|
||||
}
|
||||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
$self->SetCursor(wxSTANDARD_CURSOR);
|
||||
} elsif ($event->LeftDClick) {
|
||||
$self->{on_double_click}->();
|
||||
} elsif ($event->Dragging) {
|
||||
return if !$self->{drag_start_pos}; # concurrency problems
|
||||
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
$model_object->instances->[$instance_idx]->set_offset(
|
||||
Slic3r::Pointf->new(
|
||||
unscale($point->[X] - $self->{drag_start_pos}[X]),
|
||||
unscale($point->[Y] - $self->{drag_start_pos}[Y]),
|
||||
));
|
||||
$self->Refresh;
|
||||
} elsif ($event->Moving) {
|
||||
my $cursor = wxSTANDARD_CURSOR;
|
||||
if (defined first { $_->contour->contains_point($point) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) {
|
||||
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
|
||||
}
|
||||
$self->SetCursor($cursor);
|
||||
}
|
||||
}
|
||||
|
||||
sub update_bed_size {
|
||||
my ($self) = @_;
|
||||
|
||||
# when the canvas is not rendered yet, its GetSize() method returns 0,0
|
||||
my $canvas_size = $self->GetSize;
|
||||
my ($canvas_w, $canvas_h) = ($canvas_size->GetWidth, $canvas_size->GetHeight);
|
||||
return if $canvas_w == 0;
|
||||
|
||||
# get bed shape polygon
|
||||
$self->{bed_polygon} = my $polygon = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
|
||||
my $bb = $polygon->bounding_box;
|
||||
my $size = $bb->size;
|
||||
|
||||
# calculate the scaling factor needed for constraining print bed area inside preview
|
||||
# scaling_factor is expressed in pixel / mm
|
||||
$self->{scaling_factor} = min($canvas_w / unscale($size->x), $canvas_h / unscale($size->y)); #)
|
||||
|
||||
# calculate the displacement needed to center bed
|
||||
$self->{bed_origin} = [
|
||||
$canvas_w/2 - (unscale($bb->x_max + $bb->x_min)/2 * $self->{scaling_factor}),
|
||||
$canvas_h - ($canvas_h/2 - (unscale($bb->y_max + $bb->y_min)/2 * $self->{scaling_factor})),
|
||||
];
|
||||
|
||||
# calculate print center
|
||||
my $center = $bb->center;
|
||||
$self->{print_center} = [ unscale($center->x), unscale($center->y) ]; #))
|
||||
|
||||
# cache bed contours and grid
|
||||
{
|
||||
my $step = scale 10; # 1cm grid
|
||||
my @polylines = ();
|
||||
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
|
||||
push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]);
|
||||
}
|
||||
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
|
||||
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$polygon])};
|
||||
$self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
|
||||
}
|
||||
}
|
||||
|
||||
sub clean_instance_thumbnails {
|
||||
my ($self) = @_;
|
||||
|
||||
foreach my $object (@{ $self->{objects} }) {
|
||||
@{ $object->instance_thumbnails } = ();
|
||||
}
|
||||
}
|
||||
|
||||
# convert a model coordinate into a pixel coordinate
|
||||
sub unscaled_point_to_pixel {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $canvas_height = $self->GetSize->GetHeight;
|
||||
my $zero = $self->{bed_origin};
|
||||
return [
|
||||
$point->[X] * $self->{scaling_factor} + $zero->[X],
|
||||
$canvas_height - $point->[Y] * $self->{scaling_factor} + ($zero->[Y] - $canvas_height),
|
||||
];
|
||||
}
|
||||
|
||||
sub scaled_points_to_pixel {
|
||||
my ($self, $points, $unscale) = @_;
|
||||
|
||||
my $result = [];
|
||||
foreach my $point (@$points) {
|
||||
$point = [ map unscale($_), @$point ] if $unscale;
|
||||
push @$result, $self->unscaled_point_to_pixel($point);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub point_to_model_units {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $zero = $self->{bed_origin};
|
||||
return Slic3r::Point->new(
|
||||
scale ($point->[X] - $zero->[X]) / $self->{scaling_factor},
|
||||
scale ($zero->[Y] - $point->[Y]) / $self->{scaling_factor},
|
||||
);
|
||||
}
|
||||
|
||||
sub reload_scene {
|
||||
my ($self, $force) = @_;
|
||||
|
||||
if (! $self->IsShown && ! $force) {
|
||||
$self->{reload_delayed} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{reload_delayed} = 0;
|
||||
|
||||
foreach my $obj_idx (0..$#{$self->{model}->objects}) {
|
||||
my $plater_object = $self->{objects}[$obj_idx];
|
||||
next if $plater_object->thumbnail;
|
||||
# The thumbnail is not valid, update it with a convex hull of an object.
|
||||
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
|
||||
$plater_object->make_thumbnail($self->{model}, $obj_idx);
|
||||
$plater_object->transform_thumbnail($self->{model}, $obj_idx);
|
||||
}
|
||||
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
# Called by the Platter wxNotebook when this page is activated.
|
||||
sub OnActivate {
|
||||
my ($self) = @_;
|
||||
$self->reload_scene(1) if ($self->{reload_delayed});
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,916 +0,0 @@
|
|||
# 2D preview of the tool paths of a single layer, using a thin line.
|
||||
# OpenGL is used to render the paths.
|
||||
# Vojtech also added a 2D simulation of under/over extrusion in a single layer.
|
||||
|
||||
package Slic3r::GUI::Plater::2DToolpaths;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Print::State ':steps';
|
||||
use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxWANTS_CHARS);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(print enabled));
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $print) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
|
||||
$self->SetBackgroundColour(wxWHITE);
|
||||
|
||||
# init GUI elements
|
||||
my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print);
|
||||
my $slider = $self->{slider} = Wx::Slider->new(
|
||||
$self, -1,
|
||||
0, # default
|
||||
0, # min
|
||||
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
|
||||
# will skip drawing the slider if max <= min:
|
||||
1, # max
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxVERTICAL | wxSL_INVERSE,
|
||||
);
|
||||
my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
|
||||
$vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
|
||||
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
EVT_SLIDER($self, $slider, sub {
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue])
|
||||
if $self->enabled;
|
||||
});
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($s, $event) = @_;
|
||||
if ($event->HasModifiers) {
|
||||
$event->Skip;
|
||||
} else {
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($key == ord('D') || $key == WXK_LEFT) {
|
||||
# Keys: 'D' or WXK_LEFT
|
||||
$slider->SetValue($slider->GetValue - 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key == ord('U') || $key == WXK_RIGHT) {
|
||||
# Keys: 'U' or WXK_RIGHT
|
||||
$slider->SetValue($slider->GetValue + 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key >= ord('1') && $key <= ord('3')) {
|
||||
# Keys: '1' to '3'
|
||||
$canvas->set_simulation_mode($key - ord('1'));
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
# init print
|
||||
$self->{print} = $print;
|
||||
$self->reload_print;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reload_print {
|
||||
my ($self) = @_;
|
||||
|
||||
# we require that there's at least one object and the posSlice step
|
||||
# is performed on all of them (this ensures that _shifted_copies was
|
||||
# populated and we know the number of layers)
|
||||
if (!$self->print->object_step_done(STEP_SLICE)) {
|
||||
$self->enabled(0);
|
||||
$self->{slider}->Hide;
|
||||
$self->{canvas}->Refresh; # clears canvas
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{canvas}->bb($self->print->total_bounding_box);
|
||||
$self->{canvas}->_dirty(1);
|
||||
|
||||
my %z = (); # z => 1
|
||||
foreach my $object (@{$self->{print}->objects}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
$z{$layer->print_z} = 1;
|
||||
}
|
||||
}
|
||||
$self->enabled(1);
|
||||
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
|
||||
$self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1);
|
||||
if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) {
|
||||
$self->set_z($self->{layers_z}[$z_idx]);
|
||||
} else {
|
||||
$self->{slider}->SetValue(0);
|
||||
$self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}};
|
||||
}
|
||||
$self->{slider}->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub set_z {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
return if !$self->enabled;
|
||||
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
|
||||
$self->{canvas}->set_z($z);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::Plater::2DToolpaths::Canvas;
|
||||
|
||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
||||
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
|
||||
use base qw(Wx::GLCanvas Class::Accessor);
|
||||
use Wx::GLCanvas qw(:all);
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::Geometry qw(scale epsilon X Y);
|
||||
use Slic3r::Print::State ':steps';
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(
|
||||
print z layers color init
|
||||
bb
|
||||
_camera_bb
|
||||
_dirty
|
||||
_zoom
|
||||
_camera_target
|
||||
_drag_start_xy
|
||||
_texture_name
|
||||
_texture_size
|
||||
_extrusion_simulator
|
||||
_simulation_mode
|
||||
));
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
{
|
||||
no warnings 'redefine';
|
||||
*OpenGL::Array::CLONE_SKIP = sub { 1 };
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $print) = @_;
|
||||
|
||||
my $self = (Wx::wxVERSION >= 3.000003) ?
|
||||
# The wxWidgets 3.0.3-beta have a bug, they crash with NULL attribute list.
|
||||
$class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
|
||||
[WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0]) :
|
||||
$class->SUPER::new($parent);
|
||||
# Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
|
||||
$self->GetContext();
|
||||
$self->print($print);
|
||||
$self->_zoom(1);
|
||||
|
||||
# 2D point in model space
|
||||
$self->_camera_target(Slic3r::Pointf->new(0,0));
|
||||
|
||||
# Texture for the extrusion simulator. The texture will be allocated / reallocated on Resize.
|
||||
$self->_texture_name(0);
|
||||
$self->_texture_size(Slic3r::Point->new(0,0));
|
||||
$self->_extrusion_simulator(Slic3r::ExtrusionSimulator->new());
|
||||
$self->_simulation_mode(0);
|
||||
|
||||
EVT_PAINT($self, sub {
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
$self->Render($dc);
|
||||
});
|
||||
EVT_SIZE($self, sub { $self->_dirty(1) });
|
||||
EVT_IDLE($self, sub {
|
||||
return unless $self->_dirty;
|
||||
return if !$self->IsShownOnScreen;
|
||||
$self->Resize;
|
||||
$self->Refresh;
|
||||
});
|
||||
EVT_MOUSEWHEEL($self, sub {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $old_zoom = $self->_zoom;
|
||||
|
||||
# Calculate the zoom delta and apply it to the current zoom factor
|
||||
my $zoom = -$e->GetWheelRotation() / $e->GetWheelDelta();
|
||||
$zoom = max(min($zoom, 4), -4);
|
||||
$zoom /= 10;
|
||||
$self->_zoom($self->_zoom / (1-$zoom));
|
||||
$self->_zoom(1.25) if $self->_zoom > 1.25; # prevent from zooming out too much
|
||||
|
||||
{
|
||||
# In order to zoom around the mouse point we need to translate
|
||||
# the camera target. This math is almost there but not perfect yet...
|
||||
my $camera_bb_size = $self->_camera_bb->size;
|
||||
my $size = Slic3r::Pointf->new($self->GetSizeWH);
|
||||
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
|
||||
|
||||
# calculate the zooming center in pixel coordinates relative to the viewport center
|
||||
my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2); #-
|
||||
|
||||
# calculate where this point will end up after applying the new zoom
|
||||
my $vec2 = $vec->clone;
|
||||
$vec2->scale($old_zoom / $self->_zoom);
|
||||
|
||||
# move the camera target by the difference of the two positions
|
||||
$self->_camera_target->translate(
|
||||
-($vec->x - $vec2->x) * $camera_bb_size->x / $size->x,
|
||||
($vec->y - $vec2->y) * $camera_bb_size->y / $size->y, #//
|
||||
);
|
||||
}
|
||||
|
||||
$self->_dirty(1);
|
||||
});
|
||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
|
||||
# Deallocate the OpenGL resources.
|
||||
my $context = $self->GetContext;
|
||||
if ($context and $self->texture_id) {
|
||||
$self->SetCurrent($context);
|
||||
glDeleteTextures(1, ($self->texture_id));
|
||||
$self->SetCurrent(0);
|
||||
$self->texture_id(0);
|
||||
$self->texture_size(new Slic3r::Point(0, 0));
|
||||
}
|
||||
return $self->SUPER::Destroy;
|
||||
}
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
|
||||
if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
|
||||
# wxMSW and Linux needs focus in order to catch key events
|
||||
$self->SetFocus;
|
||||
} elsif ($e->Dragging) {
|
||||
if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) {
|
||||
# if dragging, translate view
|
||||
|
||||
if (defined $self->_drag_start_xy) {
|
||||
my $move = $self->_drag_start_xy->vector_to($pos); # in pixels
|
||||
|
||||
# get viewport and camera size in order to convert pixel to model units
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
my $camera_bb_size = $self->_camera_bb->size;
|
||||
|
||||
# compute translation in model units
|
||||
$self->_camera_target->translate(
|
||||
-$move->x * $camera_bb_size->x / $x,
|
||||
$move->y * $camera_bb_size->y / $y, # /**
|
||||
);
|
||||
|
||||
$self->_dirty(1);
|
||||
}
|
||||
$self->_drag_start_xy($pos);
|
||||
}
|
||||
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
|
||||
$self->_drag_start_xy(undef);
|
||||
} else {
|
||||
$e->Skip();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_z {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
my $print = $self->print;
|
||||
|
||||
# can we have interlaced layers?
|
||||
my $interlaced = (defined first { $_->config->support_material } @{$print->objects})
|
||||
|| (defined first { $_->config->infill_every_layers > 1 } @{$print->regions});
|
||||
|
||||
my $max_layer_height = $print->max_allowed_layer_height;
|
||||
|
||||
my @layers = ();
|
||||
foreach my $object (@{$print->objects}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
if ($interlaced) {
|
||||
push @layers, $layer
|
||||
if $z > ($layer->print_z - $max_layer_height - epsilon)
|
||||
&& $z <= $layer->print_z + epsilon;
|
||||
} else {
|
||||
push @layers, $layer if abs($layer->print_z - $z) < epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# reverse layers so that we draw the lowermost (i.e. current) on top
|
||||
$self->z($z);
|
||||
$self->layers([ reverse @layers ]);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
sub set_simulation_mode
|
||||
{
|
||||
my ($self, $mode) = @_;
|
||||
$self->_simulation_mode($mode);
|
||||
$self->_dirty(1);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
sub Render {
|
||||
my ($self, $dc) = @_;
|
||||
|
||||
# prevent calling SetCurrent() when window is not shown yet
|
||||
return unless $self->IsShownOnScreen;
|
||||
return unless my $context = $self->GetContext;
|
||||
$self->SetCurrent($context);
|
||||
$self->InitGL;
|
||||
|
||||
glClearColor(1, 1, 1, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (!$self->GetParent->enabled || !$self->layers) {
|
||||
$self->SwapBuffers;
|
||||
return;
|
||||
}
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
if ($self->_simulation_mode and $self->_texture_name and $self->_texture_size->x() > 0 and $self->_texture_size->y() > 0) {
|
||||
$self->_simulate_extrusion();
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_REPLACE);
|
||||
glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexImage2D_c(GL_TEXTURE_2D,
|
||||
0, # level (0 normal, heighr is form mip-mapping)
|
||||
GL_RGBA, # internal format
|
||||
$self->_texture_size->x(), $self->_texture_size->y(),
|
||||
0, # border
|
||||
GL_RGBA, # format RGBA color data
|
||||
GL_UNSIGNED_BYTE, # unsigned byte data
|
||||
$self->_extrusion_simulator->image_ptr()); # ptr to texture data
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glOrtho(0, 1, 0, 1, 0, 1);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0, 0);
|
||||
glVertex2f(0, 0);
|
||||
glTexCoord2f($x/$self->_texture_size->x(), 0);
|
||||
glVertex2f(1, 0);
|
||||
glTexCoord2f($x/$self->_texture_size->x(), $y/$self->_texture_size->y());
|
||||
glVertex2f(1, 1);
|
||||
glTexCoord2f(0, $y/$self->_texture_size->y());
|
||||
glVertex2f(0, 1);
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
# anti-alias
|
||||
if (0) {
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
|
||||
glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
|
||||
}
|
||||
|
||||
# Tesselator triangulates polygons with holes on the fly for the rendering purposes only.
|
||||
my $tess;
|
||||
if ($self->_simulation_mode() == 0 and !(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) {
|
||||
# We can't use the GLU tesselator on MSW with older OpenGL versions
|
||||
# because of an upstream bug:
|
||||
# http://sourceforge.net/p/pogl/bugs/16/
|
||||
$tess = gluNewTess();
|
||||
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
|
||||
}
|
||||
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
my $object = $layer->object;
|
||||
|
||||
# only draw the slice for the current layer
|
||||
next unless abs($layer->print_z - $self->z) < epsilon;
|
||||
|
||||
# draw slice contour
|
||||
glLineWidth(1);
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
glPushMatrix();
|
||||
glTranslatef(@$copy, 0);
|
||||
|
||||
foreach my $slice (@{$layer->slices}) {
|
||||
glColor3f(0.95, 0.95, 0.95);
|
||||
|
||||
if ($tess) {
|
||||
gluTessBeginPolygon($tess);
|
||||
foreach my $polygon (@$slice) {
|
||||
gluTessBeginContour($tess);
|
||||
gluTessVertex_p($tess, @$_, 0) for @$polygon;
|
||||
gluTessEndContour($tess);
|
||||
}
|
||||
gluTessEndPolygon($tess);
|
||||
}
|
||||
|
||||
glColor3f(0.9, 0.9, 0.9);
|
||||
foreach my $polygon (@$slice) {
|
||||
foreach my $line (@{$polygon->lines}) {
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
my $skirt_drawn = 0;
|
||||
my $brim_drawn = 0;
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
my $object = $layer->object;
|
||||
my $print_z = $layer->print_z;
|
||||
|
||||
# draw brim
|
||||
if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw(undef, $print_z, $_) for @{$self->print->brim};
|
||||
$brim_drawn = 1;
|
||||
}
|
||||
if ($self->print->step_done(STEP_SKIRT)
|
||||
&& ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
|
||||
&& !$skirt_drawn) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
|
||||
$skirt_drawn = 1;
|
||||
}
|
||||
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
$self->color([0.7, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
|
||||
}
|
||||
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
$self->color([0, 0, 0.7]);
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
|
||||
}
|
||||
}
|
||||
|
||||
if ($object->step_done(STEP_SUPPORTMATERIAL)) {
|
||||
if ($layer->isa('Slic3r::Layer::Support')) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gluDeleteTess($tess) if $tess;
|
||||
$self->SwapBuffers;
|
||||
}
|
||||
|
||||
sub _draw {
|
||||
my ($self, $object, $print_z, $path) = @_;
|
||||
|
||||
my @paths = ($path->isa('Slic3r::ExtrusionLoop') || $path->isa('Slic3r::ExtrusionMultiPath'))
|
||||
? @$path
|
||||
: ($path);
|
||||
|
||||
$self->_draw_path($object, $print_z, $_) for @paths;
|
||||
}
|
||||
|
||||
sub _draw_path {
|
||||
my ($self, $object, $print_z, $path) = @_;
|
||||
|
||||
return if $print_z - $path->height > $self->z - epsilon;
|
||||
|
||||
if (abs($print_z - $self->z) < epsilon) {
|
||||
glColor3f(@{$self->color});
|
||||
} else {
|
||||
glColor3f(0.8, 0.8, 0.8);
|
||||
}
|
||||
|
||||
glLineWidth(1);
|
||||
|
||||
if (defined $object) {
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
glPushMatrix();
|
||||
glTranslatef(@$copy, 0);
|
||||
foreach my $line (@{$path->polyline->lines}) {
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
} else {
|
||||
foreach my $line (@{$path->polyline->lines}) {
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _simulate_extrusion {
|
||||
my ($self) = @_;
|
||||
$self->_extrusion_simulator->reset_accumulator();
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
if (abs($layer->print_z - $self->z) < epsilon) {
|
||||
my $object = $layer->object;
|
||||
my @shifts = (defined $object) ? @{$object->_shifted_copies} : (Slic3r::Point->new(0, 0));
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
my @extrusions = ();
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
push @extrusions, @$_ for @{$layerm->perimeters};
|
||||
}
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
push @extrusions, @$_ for @{$layerm->fills};
|
||||
}
|
||||
foreach my $extrusion_entity (@extrusions) {
|
||||
my @paths = ($extrusion_entity->isa('Slic3r::ExtrusionLoop') || $extrusion_entity->isa('Slic3r::ExtrusionMultiPath'))
|
||||
? @$extrusion_entity
|
||||
: ($extrusion_entity);
|
||||
foreach my $path (@paths) {
|
||||
print "width: ", $path->width,
|
||||
" height: ", $path->height,
|
||||
" mm3_per_mm: ", $path->mm3_per_mm,
|
||||
" height2: ", $path->mm3_per_mm / $path->height,
|
||||
"\n";
|
||||
$self->_extrusion_simulator->extrude_to_accumulator($path, $_, $self->_simulation_mode()) for @shifts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->_extrusion_simulator->evaluate_accumulator($self->_simulation_mode());
|
||||
}
|
||||
|
||||
sub InitGL {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->init;
|
||||
return unless $self->GetContext;
|
||||
|
||||
my $texture_id = 0;
|
||||
($texture_id) = glGenTextures_p(1);
|
||||
$self->_texture_name($texture_id);
|
||||
$self->init(1);
|
||||
}
|
||||
|
||||
sub GetContext {
|
||||
my ($self) = @_;
|
||||
return $self->{context} ||= Wx::GLContext->new($self);
|
||||
}
|
||||
|
||||
sub SetCurrent {
|
||||
my ($self, $context) = @_;
|
||||
return $self->SUPER::SetCurrent($context);
|
||||
}
|
||||
|
||||
sub Resize {
|
||||
my ($self) = @_;
|
||||
|
||||
return unless $self->GetContext;
|
||||
return unless $self->bb;
|
||||
$self->_dirty(0);
|
||||
|
||||
$self->SetCurrent($self->GetContext);
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
|
||||
if ($self->_texture_size->x() < $x or $self->_texture_size->y() < $y) {
|
||||
# Allocate a large enough OpenGL texture with power of 2 dimensions.
|
||||
$self->_texture_size->set_x(1) if ($self->_texture_size->x() == 0);
|
||||
$self->_texture_size->set_y(1) if ($self->_texture_size->y() == 0);
|
||||
$self->_texture_size->set_x($self->_texture_size->x() * 2) while ($self->_texture_size->x() < $x);
|
||||
$self->_texture_size->set_y($self->_texture_size->y() * 2) while ($self->_texture_size->y() < $y);
|
||||
#print "screen size ", $x, "x", $y;
|
||||
#print "texture size ", $self->_texture_size->x(), "x", $self->_texture_size->y();
|
||||
# Initialize an empty texture.
|
||||
glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
|
||||
if (1) {
|
||||
glTexImage2D_c(GL_TEXTURE_2D,
|
||||
0, # level (0 normal, heighr is form mip-mapping)
|
||||
GL_RGBA, # internal format
|
||||
$self->_texture_size->x(), $self->_texture_size->y(),
|
||||
0, # border
|
||||
GL_RGBA, # format RGBA color data
|
||||
GL_UNSIGNED_BYTE, # unsigned byte data
|
||||
0); # ptr to texture data
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
$self->_extrusion_simulator->set_image_size($self->_texture_size);
|
||||
}
|
||||
$self->_extrusion_simulator->set_viewport(Slic3r::Geometry::BoundingBox->new_from_points(
|
||||
[Slic3r::Point->new(0, 0), Slic3r::Point->new($x, $y)]));
|
||||
|
||||
glViewport(0, 0, $x, $y);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
my $bb = $self->bb->clone;
|
||||
|
||||
# rescale in dependence of window aspect ratio
|
||||
my $bb_size = $bb->size;
|
||||
my $ratio_x = ($x != 0.0) ? $bb_size->x / $x : 1.0;
|
||||
my $ratio_y = ($y != 0.0) ? $bb_size->y / $y : 1.0;
|
||||
|
||||
if ($ratio_y < $ratio_x) {
|
||||
if ($ratio_y != 0.0) {
|
||||
my $new_size_y = $bb_size->y * $ratio_x / $ratio_y;
|
||||
my $half_delta_size_y = 0.5 * ($new_size_y - $bb_size->y);
|
||||
$bb->set_y_min($bb->y_min - $half_delta_size_y);
|
||||
$bb->set_y_max($bb->y_max + $half_delta_size_y);
|
||||
}
|
||||
} elsif ($ratio_x < $ratio_y) {
|
||||
if ($ratio_x != 0.0) {
|
||||
my $new_size_x = $bb_size->x * $ratio_y / $ratio_x;
|
||||
my $half_delta_size_x = 0.5 * ($new_size_x - $bb_size->x);
|
||||
$bb->set_x_min($bb->x_min - $half_delta_size_x);
|
||||
$bb->set_x_max($bb->x_max + $half_delta_size_x);
|
||||
}
|
||||
}
|
||||
|
||||
# center bounding box around origin before scaling it
|
||||
my $bb_center = $bb->center;
|
||||
$bb->translate(@{$bb_center->negative});
|
||||
|
||||
# scale bounding box according to zoom factor
|
||||
$bb->scale($self->_zoom);
|
||||
|
||||
# reposition bounding box around original center
|
||||
$bb->translate(@{$bb_center});
|
||||
|
||||
# translate camera
|
||||
$bb->translate(@{$self->_camera_target});
|
||||
|
||||
# # keep camera_bb within total bb
|
||||
# # (i.e. prevent user from panning outside the bounding box)
|
||||
# {
|
||||
# my @translate = (0,0);
|
||||
# if ($bb->x_min < $self->bb->x_min) {
|
||||
# $translate[X] += $self->bb->x_min - $bb->x_min;
|
||||
# }
|
||||
# if ($bb->y_min < $self->bb->y_min) {
|
||||
# $translate[Y] += $self->bb->y_min - $bb->y_min;
|
||||
# }
|
||||
# if ($bb->x_max > $self->bb->x_max) {
|
||||
# $translate[X] -= $bb->x_max - $self->bb->x_max;
|
||||
# }
|
||||
# if ($bb->y_max > $self->bb->y_max) {
|
||||
# $translate[Y] -= $bb->y_max - $self->bb->y_max;
|
||||
# }
|
||||
# $self->_camera_target->translate(@translate);
|
||||
# $bb->translate(@translate);
|
||||
# }
|
||||
|
||||
# save camera
|
||||
$self->_camera_bb($bb);
|
||||
|
||||
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
|
||||
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
|
||||
# adjust Y
|
||||
my $new_y = $y * ($x2 - $x1) / $x;
|
||||
$y1 = ($y2 + $y1)/2 - $new_y/2;
|
||||
$y2 = $y1 + $new_y;
|
||||
} else {
|
||||
my $new_x = $x * ($y2 - $y1) / $y;
|
||||
$x1 = ($x2 + $x1)/2 - $new_x/2;
|
||||
$x2 = $x1 + $new_x;
|
||||
}
|
||||
glOrtho($x1, $x2, $y1, $y2, 0, 1);
|
||||
|
||||
# Set the adjusted bounding box at the extrusion simulator.
|
||||
#print "Scene bbox ", $bb->x_min, ",", $bb->y_min, " ", $bb->x_max, ",", $bb->y_max, "\n";
|
||||
#print "Setting simulator bbox ", $x1, ",", $y1, " ", $x2, ",", $y2, "\n";
|
||||
$self->_extrusion_simulator->set_bounding_box(
|
||||
Slic3r::Geometry::BoundingBox->new_from_points(
|
||||
[Slic3r::Point->new($x1, $y1), Slic3r::Point->new($x2, $y2)]));
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
}
|
||||
|
||||
# Thick line drawing is not used anywhere. Probably not tested?
|
||||
sub line {
|
||||
my (
|
||||
$x1, $y1, $x2, $y2, # coordinates of the line
|
||||
$w, # width/thickness of the line in pixel
|
||||
$Cr, $Cg, $Cb, # RGB color components
|
||||
$Br, $Bg, $Bb, # color of background when alphablend=false
|
||||
# Br=alpha of color when alphablend=true
|
||||
$alphablend, # use alpha blend or not
|
||||
) = @_;
|
||||
|
||||
my $t;
|
||||
my $R;
|
||||
my $f = $w - int($w);
|
||||
my $A;
|
||||
|
||||
if ($alphablend) {
|
||||
$A = $Br;
|
||||
} else {
|
||||
$A = 1;
|
||||
}
|
||||
|
||||
# determine parameters t,R
|
||||
if ($w >= 0 && $w < 1) {
|
||||
$t = 0.05; $R = 0.48 + 0.32 * $f;
|
||||
if (!$alphablend) {
|
||||
$Cr += 0.88 * (1-$f);
|
||||
$Cg += 0.88 * (1-$f);
|
||||
$Cb += 0.88 * (1-$f);
|
||||
$Cr = 1.0 if ($Cr > 1.0);
|
||||
$Cg = 1.0 if ($Cg > 1.0);
|
||||
$Cb = 1.0 if ($Cb > 1.0);
|
||||
} else {
|
||||
$A *= $f;
|
||||
}
|
||||
} elsif ($w >= 1.0 && $w < 2.0) {
|
||||
$t = 0.05 + $f*0.33; $R = 0.768 + 0.312*$f;
|
||||
} elsif ($w >= 2.0 && $w < 3.0) {
|
||||
$t = 0.38 + $f*0.58; $R = 1.08;
|
||||
} elsif ($w >= 3.0 && $w < 4.0) {
|
||||
$t = 0.96 + $f*0.48; $R = 1.08;
|
||||
} elsif ($w >= 4.0 && $w < 5.0) {
|
||||
$t= 1.44 + $f*0.46; $R = 1.08;
|
||||
} elsif ($w >= 5.0 && $w < 6.0) {
|
||||
$t= 1.9 + $f*0.6; $R = 1.08;
|
||||
} elsif ($w >= 6.0) {
|
||||
my $ff = $w - 6.0;
|
||||
$t = 2.5 + $ff*0.50; $R = 1.08;
|
||||
}
|
||||
#printf( "w=%f, f=%f, C=%.4f\n", $w, $f, $C);
|
||||
|
||||
# determine angle of the line to horizontal
|
||||
my $tx = 0; my $ty = 0; # core thinkness of a line
|
||||
my $Rx = 0; my $Ry = 0; # fading edge of a line
|
||||
my $cx = 0; my $cy = 0; # cap of a line
|
||||
my $ALW = 0.01;
|
||||
my $dx = $x2 - $x1;
|
||||
my $dy = $y2 - $y1;
|
||||
if (abs($dx) < $ALW) {
|
||||
# vertical
|
||||
$tx = $t; $ty = 0;
|
||||
$Rx = $R; $Ry = 0;
|
||||
if ($w > 0.0 && $w < 1.0) {
|
||||
$tx *= 8;
|
||||
} elsif ($w == 1.0) {
|
||||
$tx *= 10;
|
||||
}
|
||||
} elsif (abs($dy) < $ALW) {
|
||||
#horizontal
|
||||
$tx = 0; $ty = $t;
|
||||
$Rx = 0; $Ry = $R;
|
||||
if ($w > 0.0 && $w < 1.0) {
|
||||
$ty *= 8;
|
||||
} elsif ($w == 1.0) {
|
||||
$ty *= 10;
|
||||
}
|
||||
} else {
|
||||
if ($w < 3) { # approximate to make things even faster
|
||||
my $m = $dy/$dx;
|
||||
# and calculate tx,ty,Rx,Ry
|
||||
if ($m > -0.4142 && $m <= 0.4142) {
|
||||
# -22.5 < $angle <= 22.5, approximate to 0 (degree)
|
||||
$tx = $t * 0.1; $ty = $t;
|
||||
$Rx = $R * 0.6; $Ry = $R;
|
||||
} elsif ($m > 0.4142 && $m <= 2.4142) {
|
||||
# 22.5 < $angle <= 67.5, approximate to 45 (degree)
|
||||
$tx = $t * -0.7071; $ty = $t * 0.7071;
|
||||
$Rx = $R * -0.7071; $Ry = $R * 0.7071;
|
||||
} elsif ($m > 2.4142 || $m <= -2.4142) {
|
||||
# 67.5 < $angle <= 112.5, approximate to 90 (degree)
|
||||
$tx = $t; $ty = $t*0.1;
|
||||
$Rx = $R; $Ry = $R*0.6;
|
||||
} elsif ($m > -2.4142 && $m < -0.4142) {
|
||||
# 112.5 < angle < 157.5, approximate to 135 (degree)
|
||||
$tx = $t * 0.7071; $ty = $t * 0.7071;
|
||||
$Rx = $R * 0.7071; $Ry = $R * 0.7071;
|
||||
} else {
|
||||
# error in determining angle
|
||||
printf("error in determining angle: m=%.4f\n", $m);
|
||||
}
|
||||
} else { # calculate to exact
|
||||
$dx= $y1 - $y2;
|
||||
$dy= $x2 - $x1;
|
||||
my $L = sqrt($dx*$dx + $dy*$dy);
|
||||
$dx /= $L;
|
||||
$dy /= $L;
|
||||
$cx = -0.6*$dy; $cy=0.6*$dx;
|
||||
$tx = $t*$dx; $ty = $t*$dy;
|
||||
$Rx = $R*$dx; $Ry = $R*$dy;
|
||||
}
|
||||
}
|
||||
|
||||
# draw the line by triangle strip
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
if (!$alphablend) {
|
||||
glColor3f($Br, $Bg, $Bb);
|
||||
} else {
|
||||
glColor4f($Cr, $Cg, $Cb, 0);
|
||||
}
|
||||
glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); # fading edge
|
||||
glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
|
||||
|
||||
if (!$alphablend) {
|
||||
glColor3f($Cr, $Cg, $Cb);
|
||||
} else {
|
||||
glColor4f($Cr, $Cg, $Cb, $A);
|
||||
}
|
||||
glVertex2f($x1 - $tx, $y1 - $ty); # core
|
||||
glVertex2f($x2 - $tx, $y2 - $ty);
|
||||
glVertex2f($x1 + $tx, $y1 + $ty);
|
||||
glVertex2f($x2 + $tx, $y2 + $ty);
|
||||
|
||||
if ((abs($dx) < $ALW || abs($dy) < $ALW) && $w <= 1.0) {
|
||||
# printf("skipped one fading edge\n");
|
||||
} else {
|
||||
if (!$alphablend) {
|
||||
glColor3f($Br, $Bg, $Bb);
|
||||
} else {
|
||||
glColor4f($Cr, $Cg, $Cb, 0);
|
||||
}
|
||||
glVertex2f($x1 + $tx+ $Rx, $y1 + $ty + $Ry); # fading edge
|
||||
glVertex2f($x2 + $tx+ $Rx, $y2 + $ty + $Ry);
|
||||
}
|
||||
glEnd();
|
||||
|
||||
# cap
|
||||
if ($w < 3) {
|
||||
# do not draw cap
|
||||
} else {
|
||||
# draw cap
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
if (!$alphablend) {
|
||||
glColor3f($Br, $Bg, $Bb);
|
||||
} else {
|
||||
glColor4f($Cr, $Cg, $Cb, 0);
|
||||
}
|
||||
glVertex2f($x1 - $Rx + $cx, $y1 - $Ry + $cy);
|
||||
glVertex2f($x1 + $Rx + $cx, $y1 + $Ry + $cy);
|
||||
glColor3f($Cr, $Cg, $Cb);
|
||||
glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry);
|
||||
glVertex2f($x1 + $tx + $Rx, $y1 + $ty + $Ry);
|
||||
glEnd();
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
if (!$alphablend) {
|
||||
glColor3f($Br, $Bg, $Bb);
|
||||
} else {
|
||||
glColor4f($Cr, $Cg, $Cb, 0);
|
||||
}
|
||||
glVertex2f($x2 - $Rx - $cx, $y2 - $Ry - $cy);
|
||||
glVertex2f($x2 + $Rx - $cx, $y2 + $Ry - $cy);
|
||||
glColor3f($Cr, $Cg, $Cb);
|
||||
glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
|
||||
glVertex2f($x2 + $tx + $Rx, $y2 + $ty + $Ry);
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::Plater::2DToolpaths::Dialog;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer);
|
||||
use Wx::Event qw(EVT_CLOSE);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $print) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Toolpaths", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($self, $print), 1, wxEXPAND, 0);
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
# Generate an anonymous or "lambda" 3D object. This gets used with the Add Generic option in Settings.
|
||||
#
|
||||
|
||||
package Slic3r::GUI::Plater::LambdaObjectDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL wxCB_READONLY wxTE_PROCESS_TAB);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_COMBOBOX EVT_TEXT);
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Lambda Object", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
# Note whether the window was already closed, so a pending update is not executed.
|
||||
$self->{already_closed} = 0;
|
||||
$self->{object_parameters} = {
|
||||
type => "box",
|
||||
dim => [1, 1, 1],
|
||||
cyl_r => 1,
|
||||
cyl_h => 1,
|
||||
sph_rho => 1.0,
|
||||
slab_h => 1.0,
|
||||
slab_z => 0.0,
|
||||
};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
my $button_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $button_ok = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
my $button_cancel = $self->CreateStdDialogButtonSizer(wxCANCEL);
|
||||
$button_sizer->Add($button_ok);
|
||||
$button_sizer->Add($button_cancel);
|
||||
EVT_BUTTON($self, wxID_OK, sub {
|
||||
# validate user input
|
||||
return if !$self->CanClose;
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
EVT_BUTTON($self, wxID_CANCEL, sub {
|
||||
# validate user input
|
||||
return if !$self->CanClose;
|
||||
|
||||
$self->EndModal(wxID_CANCEL);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
my $optgroup_box;
|
||||
$optgroup_box = $self->{optgroup_box} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Cube...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id == 0 || $opt_id == 1 || $opt_id == 2) {
|
||||
if (!looks_like_number($optgroup_box->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{dim}[$opt_id] = $optgroup_box->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
my @options = ("box", "slab", "cylinder", "sphere");
|
||||
$self->{type} = Wx::ComboBox->new($self, 1, "box", wxDefaultPosition, wxDefaultSize, \@options, wxCB_READONLY);
|
||||
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 0,
|
||||
label => 'L',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 1,
|
||||
label => 'W',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 2,
|
||||
label => 'H',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_cylinder;
|
||||
$optgroup_cylinder = $self->{optgroup_cylinder} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Cylinder...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'cyl_r' || $opt_id eq 'cyl_h') {
|
||||
if (!looks_like_number($optgroup_cylinder->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_cylinder->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
|
||||
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "cyl_r",
|
||||
label => 'Radius',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "cyl_h",
|
||||
label => 'Height',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_sphere;
|
||||
$optgroup_sphere = $self->{optgroup_sphere} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Sphere...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'sph_rho') {
|
||||
if (!looks_like_number($optgroup_sphere->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_sphere->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
|
||||
$optgroup_sphere->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "sph_rho",
|
||||
label => 'Rho',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_slab;
|
||||
$optgroup_slab = $self->{optgroup_slab} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Slab...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'slab_z' || $opt_id eq 'slab_h') {
|
||||
if (!looks_like_number($optgroup_slab->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_slab->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
$optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "slab_h",
|
||||
label => 'H',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "slab_z",
|
||||
label => 'Initial Z',
|
||||
type => 'f',
|
||||
default => '0',
|
||||
));
|
||||
|
||||
|
||||
EVT_COMBOBOX($self, 1, sub{
|
||||
$self->{object_parameters}->{type} = $self->{type}->GetValue();
|
||||
$self->_update_ui;
|
||||
});
|
||||
|
||||
|
||||
$self->{sizer}->Add($self->{type}, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_box->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_cylinder->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_sphere->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_slab->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($button_sizer,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->_update_ui;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->Fit($self);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
|
||||
return $self;
|
||||
}
|
||||
sub CanClose {
|
||||
return 1;
|
||||
}
|
||||
sub ObjectParameter {
|
||||
my ($self) = @_;
|
||||
return $self->{object_parameters};
|
||||
}
|
||||
|
||||
sub _update_ui {
|
||||
my ($self) = @_;
|
||||
$self->{sizer}->Hide($self->{optgroup_cylinder}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_slab}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_box}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_sphere}->sizer);
|
||||
if ($self->{type}->GetValue eq "box") {
|
||||
$self->{sizer}->Show($self->{optgroup_box}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "cylinder") {
|
||||
$self->{sizer}->Show($self->{optgroup_cylinder}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "slab") {
|
||||
$self->{sizer}->Show($self->{optgroup_slab}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "sphere") {
|
||||
$self->{sizer}->Show($self->{optgroup_sphere}->sizer);
|
||||
}
|
||||
$self->{sizer}->Fit($self);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
}
|
||||
1;
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
# Cut an object at a Z position, keep either the top or the bottom of the object.
|
||||
# This dialog gets opened with the "Cut..." button above the platter.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Geometry qw(PI X);
|
||||
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||
use List::Util qw(max);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{model_object} = $params{model_object};
|
||||
$self->{new_model_objects} = [];
|
||||
# Mark whether the mesh cut is valid.
|
||||
# If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog.
|
||||
$self->{mesh_cut_valid} = 0;
|
||||
# Note whether the window was already closed, so a pending update is not executed.
|
||||
$self->{already_closed} = 0;
|
||||
|
||||
# cut options
|
||||
$self->{cut_options} = {
|
||||
z => 0,
|
||||
keep_upper => 1,
|
||||
keep_lower => 1,
|
||||
rotate_lower => 1,
|
||||
# preview => 1,
|
||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
|
||||
preview => 0,
|
||||
};
|
||||
|
||||
my $optgroup;
|
||||
$optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Cut',
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
# There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
|
||||
# genates tens of events for a single value change.
|
||||
# Only trigger the recalculation if the value changes
|
||||
# or a live preview was activated and the mesh cut is not valid yet.
|
||||
if ($self->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) ||
|
||||
! $self->{mesh_cut_valid} && $self->_life_preview_active()) {
|
||||
$self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
|
||||
$self->{mesh_cut_valid} = 0;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_update;
|
||||
});
|
||||
}
|
||||
},
|
||||
label_width => 120,
|
||||
);
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'z',
|
||||
type => 'slider',
|
||||
label => 'Z',
|
||||
default => $self->{cut_options}{z},
|
||||
min => 0,
|
||||
max => $self->{model_object}->bounding_box->size->z,
|
||||
full_width => 1,
|
||||
));
|
||||
{
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Keep',
|
||||
);
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'keep_upper',
|
||||
type => 'bool',
|
||||
label => 'Upper part',
|
||||
default => $self->{cut_options}{keep_upper},
|
||||
));
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'keep_lower',
|
||||
type => 'bool',
|
||||
label => 'Lower part',
|
||||
default => $self->{cut_options}{keep_lower},
|
||||
));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'rotate_lower',
|
||||
label => 'Rotate lower part upwards',
|
||||
type => 'bool',
|
||||
tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
|
||||
default => $self->{cut_options}{rotate_lower},
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'preview',
|
||||
label => 'Show preview',
|
||||
type => 'bool',
|
||||
tooltip => 'If enabled, object will be cut in real time.',
|
||||
default => $self->{cut_options}{preview},
|
||||
));
|
||||
{
|
||||
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
|
||||
$cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10);
|
||||
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
|
||||
sizer => $cut_button_sizer,
|
||||
));
|
||||
}
|
||||
|
||||
# left pane with tree
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
|
||||
Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas->SetMinSize($canvas->GetSize);
|
||||
Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->{config});
|
||||
Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
|
||||
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
EVT_BUTTON($self, $self->{btn_cut}, sub {
|
||||
# Recalculate the cut if the preview was not active.
|
||||
$self->_perform_cut() unless $self->{mesh_cut_valid};
|
||||
|
||||
# Adjust position / orientation of the split object halves.
|
||||
if ($self->{new_model_objects}{lower}) {
|
||||
if ($self->{cut_options}{rotate_lower}) {
|
||||
$self->{new_model_objects}{lower}->rotate(PI, Slic3r::Pointf3->new(1,0,0));
|
||||
$self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0
|
||||
}
|
||||
}
|
||||
if ($self->{new_model_objects}{upper}) {
|
||||
$self->{new_model_objects}{upper}->center_around_origin; # align to Z = 0
|
||||
}
|
||||
|
||||
# Note that the window was already closed, so a pending update will not be executed.
|
||||
$self->{already_closed} = 1;
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->{canvas}->Destroy;
|
||||
$self->Destroy();
|
||||
});
|
||||
|
||||
EVT_CLOSE($self, sub {
|
||||
# Note that the window was already closed, so a pending update will not be executed.
|
||||
$self->{already_closed} = 1;
|
||||
$self->EndModal(wxID_CANCEL);
|
||||
$self->{canvas}->Destroy;
|
||||
$self->Destroy();
|
||||
});
|
||||
|
||||
$self->_update;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
|
||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||
sub _mesh_slice_z_pos
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor;
|
||||
}
|
||||
|
||||
# Only perform live preview if just a single part of the object shall survive.
|
||||
sub _life_preview_active
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
|
||||
}
|
||||
|
||||
# Slice the mesh, keep the top / bottom part.
|
||||
sub _perform_cut
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# Early exit. If the cut is valid, don't recalculate it.
|
||||
return if $self->{mesh_cut_valid};
|
||||
|
||||
my $z = $self->_mesh_slice_z_pos();
|
||||
|
||||
my ($new_model) = $self->{model_object}->cut($z);
|
||||
my ($upper_object, $lower_object) = @{$new_model->objects};
|
||||
$self->{new_model} = $new_model;
|
||||
$self->{new_model_objects} = {};
|
||||
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
|
||||
$self->{new_model_objects}{upper} = $upper_object;
|
||||
}
|
||||
if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
|
||||
$self->{new_model_objects}{lower} = $lower_object;
|
||||
}
|
||||
|
||||
$self->{mesh_cut_valid} = 1;
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
|
||||
# Don't update if the window was already closed.
|
||||
# We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed.
|
||||
# Probably not, but better be safe than sorry, which is espetially true on multiple platforms.
|
||||
return if $self->{already_closed};
|
||||
|
||||
# Only recalculate the cut, if the live cut preview is active.
|
||||
my $life_preview_active = $self->_life_preview_active();
|
||||
$self->_perform_cut() if $life_preview_active;
|
||||
|
||||
{
|
||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
|
||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||
my $z = $self->_mesh_slice_z_pos();
|
||||
|
||||
|
||||
# update canvas
|
||||
if ($self->{canvas}) {
|
||||
# get volumes to render
|
||||
my @objects = ();
|
||||
if ($life_preview_active) {
|
||||
push @objects, values %{$self->{new_model_objects}};
|
||||
} else {
|
||||
push @objects, $self->{model_object};
|
||||
}
|
||||
|
||||
my $z_cut = $z + $self->{model_object}->bounding_box->z_min;
|
||||
|
||||
# get section contour
|
||||
my @expolygons = ();
|
||||
foreach my $volume (@{$self->{model_object}->volumes}) {
|
||||
next if !$volume->mesh;
|
||||
next if !$volume->model_part;
|
||||
my $expp = $volume->mesh->slice([ $z_cut ])->[0];
|
||||
push @expolygons, @$expp;
|
||||
}
|
||||
foreach my $expolygon (@expolygons) {
|
||||
$self->{model_object}->instances->[0]->transform_polygon($_)
|
||||
for @$expolygon;
|
||||
$expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset });
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
|
||||
Slic3r::GUI::_3DScene::set_cutting_plane($self->{canvas}, $self->{cut_options}{z}, [@expolygons]);
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
# update controls
|
||||
{
|
||||
my $z = $self->{cut_options}{z};
|
||||
my $optgroup = $self->{optgroup};
|
||||
$optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
|
||||
$optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
|
||||
$optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower});
|
||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
|
||||
# $optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
|
||||
|
||||
# update cut button
|
||||
if (($self->{cut_options}{keep_upper} && $have_upper)
|
||||
|| ($self->{cut_options}{keep_lower} && $have_lower)) {
|
||||
$self->{btn_cut}->Enable;
|
||||
} else {
|
||||
$self->{btn_cut}->Disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub NewModelObjects {
|
||||
my ($self) = @_;
|
||||
return values %{ $self->{new_model_objects} };
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,637 +0,0 @@
|
|||
# Configuration of mesh modifiers and their parameters.
|
||||
# This panel is inserted into ObjectSettingsDialog.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use Wx qw(:misc :sizer :treectrl :button :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL
|
||||
wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN EVT_KEY_DOWN);
|
||||
use List::Util qw(max);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant ICON_OBJECT => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
use constant ICON_SUPPORT_ENFORCER => 3;
|
||||
use constant ICON_SUPPORT_BLOCKER => 4;
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
|
||||
# C++ type Slic3r::ModelObject
|
||||
my $object = $self->{model_object} = $params{model_object};
|
||||
|
||||
# Save state for sliders.
|
||||
$self->{move_options} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
$self->{last_coords} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
|
||||
# create TreeCtrl
|
||||
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
|
||||
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
|
||||
| wxTR_SINGLE);
|
||||
{
|
||||
$self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
|
||||
$tree->AssignImageList($self->{tree_icons});
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_ENFORCER
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_BLOCKER
|
||||
|
||||
my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
|
||||
$tree->SetPlData($rootId, { type => 'object' });
|
||||
}
|
||||
|
||||
# buttons
|
||||
$self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_load_lambda_modifier} = Wx::Button->new($self, -1, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
|
||||
$self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
|
||||
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG));
|
||||
|
||||
# buttons sizer
|
||||
my $buttons_sizer = Wx::GridSizer->new(2, 3);
|
||||
$buttons_sizer->Add($self->{btn_load_part}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_load_modifier}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0, wxEXPAND | wxBOTTOM, 5);
|
||||
$buttons_sizer->Add($self->{btn_delete}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_split}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
{
|
||||
my $up_down_sizer = Wx::GridSizer->new(1, 2);
|
||||
$up_down_sizer->Add($self->{btn_move_up}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
$up_down_sizer->Add($self->{btn_move_down}, 0, wxEXPAND, 5);
|
||||
$buttons_sizer->Add($up_down_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_load_lambda_modifier}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_split}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_move_up}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
# part settings panel
|
||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub {
|
||||
my ($key, $value) = @_;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->set_part_type($value) if ($key eq "part_type");
|
||||
$self->{part_settings_changed} = 1;
|
||||
$self->_update_canvas;
|
||||
});
|
||||
});
|
||||
my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
|
||||
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
|
||||
|
||||
my $optgroup_movers;
|
||||
$optgroup_movers = $self->{optgroup_movers} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Move',
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
# There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
|
||||
# genates tens of events for a single value change.
|
||||
# Only trigger the recalculation if the value changes
|
||||
# or a live preview was activated and the mesh cut is not valid yet.
|
||||
if ($self->{move_options}{$opt_id} != $optgroup_movers->get_value($opt_id)) {
|
||||
$self->{move_options}{$opt_id} = $optgroup_movers->get_value($opt_id);
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_update;
|
||||
});
|
||||
}
|
||||
},
|
||||
label_width => 20,
|
||||
);
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'x',
|
||||
type => 'slider',
|
||||
label => 'X',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->x)*4,
|
||||
max => $self->{model_object}->bounding_box->size->x*4,
|
||||
full_width => 1,
|
||||
));
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'y',
|
||||
type => 'slider',
|
||||
label => 'Y',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->y)*4,
|
||||
max => $self->{model_object}->bounding_box->size->y*4,
|
||||
full_width => 1,
|
||||
));
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'z',
|
||||
type => 'slider',
|
||||
label => 'Z',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->z)*4,
|
||||
max => $self->{model_object}->bounding_box->size->z*4,
|
||||
full_width => 1,
|
||||
));
|
||||
|
||||
# left pane with tree
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$left_sizer->Add($tree, 3, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($settings_sizer, 5, wxEXPAND | wxALL, 0);
|
||||
$left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
Slic3r::GUI::_3DScene::enable_picking($canvas, 1);
|
||||
Slic3r::GUI::_3DScene::set_select_by($canvas, 'volume');
|
||||
Slic3r::GUI::_3DScene::register_on_select_object_callback($canvas, sub {
|
||||
my ($volume_idx) = @_;
|
||||
$self->reload_tree($volume_idx);
|
||||
});
|
||||
Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
|
||||
Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
|
||||
$canvas->SetSize([500,700]);
|
||||
Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->GetParent->GetParent->{config});
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($canvas);
|
||||
Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
|
||||
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
# attach events
|
||||
EVT_TREE_ITEM_COLLAPSING($self, $tree, sub {
|
||||
my ($self, $event) = @_;
|
||||
$event->Veto;
|
||||
});
|
||||
EVT_TREE_SEL_CHANGED($self, $tree, sub {
|
||||
my ($self, $event) = @_;
|
||||
return if $self->{disable_tree_sel_changed_event};
|
||||
$self->selection_changed;
|
||||
});
|
||||
EVT_TREE_KEY_DOWN($self, $tree, \&on_tree_key_down);
|
||||
EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
|
||||
EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) });
|
||||
EVT_BUTTON($self, $self->{btn_load_lambda_modifier}, sub { $self->on_btn_lambda(1) });
|
||||
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
|
||||
EVT_BUTTON($self, $self->{btn_split}, \&on_btn_split);
|
||||
EVT_BUTTON($self, $self->{btn_move_up}, \&on_btn_move_up);
|
||||
EVT_BUTTON($self, $self->{btn_move_down}, \&on_btn_move_down);
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($canvas, $event) = @_;
|
||||
if ($event->GetKeyCode == WXK_DELETE) {
|
||||
$canvas->GetParent->on_btn_delete;
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
});
|
||||
|
||||
$self->reload_tree;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reload_tree {
|
||||
my ($self, $selected_volume_idx) = @_;
|
||||
|
||||
$selected_volume_idx //= -1;
|
||||
my $object = $self->{model_object};
|
||||
my $tree = $self->{tree};
|
||||
my $rootId = $tree->GetRootItem;
|
||||
|
||||
# despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method",
|
||||
# the MSW implementation of DeleteChildren actually calls Delete() for each item, so
|
||||
# EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this
|
||||
# subroutine is never continued; an invisible EndModal is called on the dialog causing Plater
|
||||
# to continue its logic and rescheduling the background process etc. GH #2774)
|
||||
$self->{disable_tree_sel_changed_event} = 1;
|
||||
$tree->DeleteChildren($rootId);
|
||||
$self->{disable_tree_sel_changed_event} = 0;
|
||||
|
||||
my $selectedId = $rootId;
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
my $icon =
|
||||
$volume->modifier ? ICON_MODIFIERMESH :
|
||||
$volume->support_enforcer ? ICON_SUPPORT_ENFORCER :
|
||||
$volume->support_blocker ? ICON_SUPPORT_BLOCKER :
|
||||
ICON_SOLIDMESH;
|
||||
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
|
||||
if ($volume_id == $selected_volume_idx) {
|
||||
$selectedId = $itemId;
|
||||
}
|
||||
$tree->SetPlData($itemId, {
|
||||
type => 'volume',
|
||||
volume_id => $volume_id,
|
||||
});
|
||||
}
|
||||
$tree->ExpandAll;
|
||||
|
||||
Slic3r::GUI->CallAfter(sub {
|
||||
$self->{tree}->SelectItem($selectedId);
|
||||
|
||||
# SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs,
|
||||
# but in fact it doesn't if the given item is already selected (this happens
|
||||
# on first load)
|
||||
$self->selection_changed;
|
||||
});
|
||||
}
|
||||
|
||||
sub get_selection {
|
||||
my ($self) = @_;
|
||||
|
||||
my $nodeId = $self->{tree}->GetSelection;
|
||||
if ($nodeId->IsOk) {
|
||||
return $self->{tree}->GetPlData($nodeId);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub selection_changed {
|
||||
my ($self) = @_;
|
||||
|
||||
# deselect all meshes
|
||||
if ($self->{canvas}) {
|
||||
Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas});
|
||||
}
|
||||
|
||||
# disable things as if nothing is selected
|
||||
$self->{'btn_' . $_}->Disable for (qw(delete move_up move_down split));
|
||||
$self->{settings_panel}->disable;
|
||||
$self->{settings_panel}->set_config(undef);
|
||||
|
||||
# reset move sliders
|
||||
$self->{optgroup_movers}->set_value("x", 0);
|
||||
$self->{optgroup_movers}->set_value("y", 0);
|
||||
$self->{optgroup_movers}->set_value("z", 0);
|
||||
$self->{move_options} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
$self->{last_coords} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
|
||||
if (my $itemData = $self->get_selection) {
|
||||
my ($config, @opt_keys);
|
||||
my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT;
|
||||
my $support = 0;
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
# select volume in 3D preview
|
||||
if ($self->{canvas}) {
|
||||
Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
|
||||
}
|
||||
$self->{btn_delete}->Enable;
|
||||
$self->{btn_split}->Enable;
|
||||
$self->{btn_move_up}->Enable if $itemData->{volume_id} > 0;
|
||||
$self->{btn_move_down}->Enable if $itemData->{volume_id} + 1 < $self->{model_object}->volumes_count;
|
||||
|
||||
# attach volume config to settings panel
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
|
||||
if (! $volume->model_part) {
|
||||
$self->{optgroup_movers}->enable;
|
||||
if ($volume->support_enforcer || $volume->support_blocker) {
|
||||
$support = 1;
|
||||
$type = $volume->support_enforcer ?
|
||||
Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER :
|
||||
Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER;
|
||||
} else {
|
||||
$type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER;
|
||||
}
|
||||
} else {
|
||||
$type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART;
|
||||
$self->{optgroup_movers}->disable;
|
||||
}
|
||||
$config = $volume->config;
|
||||
$self->{staticbox}->SetLabel('Part Settings');
|
||||
# get default values
|
||||
@opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys};
|
||||
} elsif ($itemData->{type} eq 'object') {
|
||||
# select nothing in 3D preview
|
||||
|
||||
# attach object config to settings panel
|
||||
$self->{optgroup_movers}->disable;
|
||||
$self->{staticbox}->SetLabel('Object Settings');
|
||||
@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
|
||||
$config = $self->{model_object}->config;
|
||||
}
|
||||
# get default values
|
||||
my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
|
||||
|
||||
# decide which settings will be shown by default
|
||||
if ($itemData->{type} eq 'object') {
|
||||
$config->set_ifndef('wipe_into_objects', 0);
|
||||
$config->set_ifndef('wipe_into_infill', 0);
|
||||
}
|
||||
|
||||
# append default extruder
|
||||
if (! $support) {
|
||||
push @opt_keys, 'extruder';
|
||||
$default_config->set('extruder', 0);
|
||||
$config->set_ifndef('extruder', 0);
|
||||
}
|
||||
$self->{settings_panel}->set_type($type);
|
||||
$self->{settings_panel}->set_default_config($default_config);
|
||||
$self->{settings_panel}->set_config($config);
|
||||
$self->{settings_panel}->set_opt_keys(\@opt_keys);
|
||||
|
||||
# disable minus icon to remove the settings
|
||||
my $fixed_options =
|
||||
($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] :
|
||||
$support ? [] : [qw(extruder)];
|
||||
$self->{settings_panel}->set_fixed_options($fixed_options);
|
||||
$self->{settings_panel}->enable;
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
|
||||
}
|
||||
|
||||
sub set_part_type
|
||||
{
|
||||
my ($self, $part_type) = @_;
|
||||
if (my $itemData = $self->get_selection) {
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER ||
|
||||
$part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) {
|
||||
$volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER);
|
||||
} elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) {
|
||||
$volume->set_support_enforcer;
|
||||
} elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) {
|
||||
$volume->set_support_blocker;
|
||||
}
|
||||
# We want the icon of the selected item to be changed as well.
|
||||
$self->reload_tree($itemData->{volume_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_load {
|
||||
my ($self, $is_modifier) = @_;
|
||||
|
||||
my @input_files = wxTheApp->open_model($self);
|
||||
foreach my $input_file (@input_files) {
|
||||
my $model = eval { Slic3r::Model->read_from_file($input_file) };
|
||||
if ($@) {
|
||||
Slic3r::GUI::show_error($self, $@);
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $object (@{$model->objects}) {
|
||||
$object->center_around_origin;
|
||||
foreach my $volume (@{$object->volumes}) {
|
||||
my $new_volume = $self->{model_object}->add_volume($volume);
|
||||
$new_volume->set_modifier($is_modifier);
|
||||
$new_volume->set_name(basename($input_file));
|
||||
|
||||
# apply the same translation we applied to the object
|
||||
my $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x;
|
||||
my $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y;
|
||||
my $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z;
|
||||
$new_volume->mesh->translate($delta_x, $delta_y, $delta_z);
|
||||
|
||||
# set a default extruder value, since user can't add it manually
|
||||
$new_volume->config->set_ifndef('extruder', 0);
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{model_object}->center_around_origin if $self->{parts_changed};
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_btn_lambda {
|
||||
my ($self, $is_modifier) = @_;
|
||||
|
||||
my $dlg = Slic3r::GUI::Plater::LambdaObjectDialog->new($self);
|
||||
if ($dlg->ShowModal() == wxID_CANCEL) {
|
||||
return;
|
||||
}
|
||||
my $params = $dlg->ObjectParameter;
|
||||
my $type = "".$params->{"type"};
|
||||
my $name = "lambda-".$params->{"type"};
|
||||
my $mesh;
|
||||
|
||||
if ($type eq "box") {
|
||||
$mesh = Slic3r::TriangleMesh::cube($params->{"dim"}[0], $params->{"dim"}[1], $params->{"dim"}[2]);
|
||||
} elsif ($type eq "cylinder") {
|
||||
$mesh = Slic3r::TriangleMesh::cylinder($params->{"cyl_r"}, $params->{"cyl_h"});
|
||||
} elsif ($type eq "sphere") {
|
||||
$mesh = Slic3r::TriangleMesh::sphere($params->{"sph_rho"});
|
||||
} elsif ($type eq "slab") {
|
||||
$mesh = Slic3r::TriangleMesh::cube($self->{model_object}->bounding_box->size->x*1.5, $self->{model_object}->bounding_box->size->y*1.5, $params->{"slab_h"});
|
||||
# box sets the base coordinate at 0,0, move to center of plate and move it up to initial_z
|
||||
$mesh->translate(-$self->{model_object}->bounding_box->size->x*1.5/2.0, -$self->{model_object}->bounding_box->size->y*1.5/2.0, $params->{"slab_z"});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
$mesh->repair;
|
||||
my $new_volume = $self->{model_object}->add_volume(mesh => $mesh);
|
||||
$new_volume->set_modifier($is_modifier);
|
||||
$new_volume->set_name($name);
|
||||
|
||||
# set a default extruder value, since user can't add it manually
|
||||
$new_volume->config->set_ifndef('extruder', 0);
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_tree_key_down {
|
||||
my ($self, $event) = @_;
|
||||
my $keycode = $event->GetKeyCode;
|
||||
# Wx >= 0.9911
|
||||
if (defined(&Wx::TreeEvent::GetKeyEvent)) {
|
||||
if ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL) {
|
||||
if ($keycode == WXK_UP) {
|
||||
$event->Skip;
|
||||
$self->on_btn_move_up;
|
||||
} elsif ($keycode == WXK_DOWN) {
|
||||
$event->Skip;
|
||||
$self->on_btn_move_down;
|
||||
}
|
||||
} elsif ($keycode == WXK_DELETE) {
|
||||
$self->on_btn_delete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_move_up {
|
||||
my ($self) = @_;
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume_id = $itemData->{volume_id};
|
||||
if ($self->{model_object}->move_volume_up($volume_id)) {
|
||||
Slic3r::GUI::_3DScene::move_volume_up($self->{canvas}, $volume_id);
|
||||
$self->{parts_changed} = 1;
|
||||
$self->reload_tree($volume_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_move_down {
|
||||
my ($self) = @_;
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume_id = $itemData->{volume_id};
|
||||
if ($self->{model_object}->move_volume_down($volume_id)) {
|
||||
Slic3r::GUI::_3DScene::move_volume_down($self->{canvas}, $volume_id);
|
||||
$self->{parts_changed} = 1;
|
||||
$self->reload_tree($volume_id + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_delete {
|
||||
my ($self) = @_;
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
|
||||
# if user is deleting the last solid part, throw error
|
||||
if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
|
||||
Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{model_object}->delete_volume($itemData->{volume_id});
|
||||
$self->{parts_changed} = 1;
|
||||
}
|
||||
|
||||
$self->{model_object}->center_around_origin if $self->{parts_changed};
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_btn_split {
|
||||
my ($self) = @_;
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
my $nozzle_dmrs = $self->GetParent->GetParent->GetParent->{config}->get('nozzle_diameter');
|
||||
$self->{parts_changed} = 1 if $volume->split(scalar(@$nozzle_dmrs)) > 1;
|
||||
}
|
||||
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub _parts_changed {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->reload_tree;
|
||||
if ($self->{canvas}) {
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
sub CanClose {
|
||||
my $self = shift;
|
||||
|
||||
return 1; # skip validation for now
|
||||
|
||||
# validate options before allowing user to dismiss the dialog
|
||||
# the validate method only works on full configs so we have
|
||||
# to merge our settings with the default ones
|
||||
my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone;
|
||||
eval {
|
||||
$config->apply($self->model_object->config);
|
||||
$config->validate;
|
||||
};
|
||||
return ! Slic3r::GUI::catch_error($self);
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
$self->{canvas}->Destroy if ($self->{canvas});
|
||||
}
|
||||
|
||||
sub PartsChanged {
|
||||
my ($self) = @_;
|
||||
return $self->{parts_changed};
|
||||
}
|
||||
|
||||
sub PartSettingsChanged {
|
||||
my ($self) = @_;
|
||||
return $self->{part_settings_changed};
|
||||
}
|
||||
|
||||
sub _update_canvas {
|
||||
my ($self) = @_;
|
||||
|
||||
if ($self->{canvas}) {
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
|
||||
# restore selection, if any
|
||||
if (my $itemData = $self->get_selection) {
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
|
||||
}
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
my ($m_x, $m_y, $m_z) = ($self->{move_options}{x}, $self->{move_options}{y}, $self->{move_options}{z});
|
||||
my ($l_x, $l_y, $l_z) = ($self->{last_coords}{x}, $self->{last_coords}{y}, $self->{last_coords}{z});
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $d = Slic3r::Pointf3->new($m_x - $l_x, $m_y - $l_y, $m_z - $l_z);
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
$volume->mesh->translate(@{$d});
|
||||
$self->{last_coords}{x} = $m_x;
|
||||
$self->{last_coords}{y} = $m_y;
|
||||
$self->{last_coords}{z} = $m_z;
|
||||
}
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
my @objects = ();
|
||||
push @objects, $self->{model_object};
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
# This dialog opens up when double clicked on an object line in the list at the right side of the platter.
|
||||
# One may load additional STLs and additional modifier STLs,
|
||||
# one may change the properties of the print per each modifier mesh or a Z-span.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
# Called with
|
||||
# %params{object} of a Perl type Slic3r::GUI::Plater::Object
|
||||
# %params{model_object} of a C++ type Slic3r::ModelObject
|
||||
sub new {
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{$_} = $params{$_} for keys %params;
|
||||
|
||||
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
||||
$self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
|
||||
$self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers");
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
EVT_BUTTON($self, wxID_OK, sub {
|
||||
# validate user input
|
||||
return if !$self->{parts}->CanClose;
|
||||
return if !$self->{layers}->CanClose;
|
||||
|
||||
# notify tabs
|
||||
$self->{layers}->Closing;
|
||||
|
||||
# save window size
|
||||
Slic3r::GUI::save_window_size($self, "object_settings");
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->{parts}->Destroy;
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($self->{tabpanel}, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
$self->Layout;
|
||||
|
||||
Slic3r::GUI::restore_window_size($self, "object_settings");
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub PartsChanged {
|
||||
my ($self) = @_;
|
||||
return $self->{parts}->PartsChanged;
|
||||
}
|
||||
|
||||
sub PartSettingsChanged {
|
||||
my ($self) = @_;
|
||||
return $self->{parts}->PartSettingsChanged || $self->{layers}->LayersChanged;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectDialog::BaseTab;
|
||||
use base 'Wx::Panel';
|
||||
|
||||
sub model_object {
|
||||
my ($self) = @_;
|
||||
# $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
|
||||
return $self->GetParent->GetParent->{model_object};
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings);
|
||||
use Wx::Grid;
|
||||
use Wx::Event qw(EVT_GRID_CELL_CHANGED);
|
||||
use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
{
|
||||
my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object.",
|
||||
wxDefaultPosition, [-1, 40]);
|
||||
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
$sizer->Add($label, 0, wxEXPAND | wxALL, 10);
|
||||
}
|
||||
|
||||
my $grid = $self->{grid} = Wx::Grid->new($self, -1, wxDefaultPosition, wxDefaultSize);
|
||||
$sizer->Add($grid, 1, wxEXPAND | wxALL, 10);
|
||||
$grid->CreateGrid(0, 3);
|
||||
$grid->DisableDragRowSize;
|
||||
$grid->HideRowLabels;
|
||||
$grid->SetColLabelValue(0, "Min Z (mm)");
|
||||
$grid->SetColLabelValue(1, "Max Z (mm)");
|
||||
$grid->SetColLabelValue(2, "Layer height (mm)");
|
||||
$grid->SetColSize($_, 135) for 0..2;
|
||||
$grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
|
||||
|
||||
# load data
|
||||
foreach my $range (@{ $self->model_object->layer_height_ranges }) {
|
||||
$grid->AppendRows(1);
|
||||
my $i = $grid->GetNumberRows-1;
|
||||
$grid->SetCellValue($i, $_, $range->[$_]) for 0..2;
|
||||
}
|
||||
$grid->AppendRows(1); # append one empty row
|
||||
|
||||
EVT_GRID_CELL_CHANGED($grid, sub {
|
||||
my ($grid, $event) = @_;
|
||||
|
||||
# remove any non-numeric character
|
||||
my $value = $grid->GetCellValue($event->GetRow, $event->GetCol);
|
||||
$value =~ s/,/./g;
|
||||
$value =~ s/[^0-9.]//g;
|
||||
$grid->SetCellValue($event->GetRow, $event->GetCol, ($event->GetCol == 2) ? $self->_clamp_layer_height($value) : $value);
|
||||
|
||||
# if there's no empty row, let's append one
|
||||
for my $i (0 .. $grid->GetNumberRows) {
|
||||
if ($i == $grid->GetNumberRows) {
|
||||
# if we're here then we found no empty row
|
||||
$grid->AppendRows(1);
|
||||
last;
|
||||
}
|
||||
if (!grep $grid->GetCellValue($i, $_), 0..2) {
|
||||
# exit loop if this row is empty
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
$self->{layers_changed} = 1;
|
||||
});
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _clamp_layer_height
|
||||
{
|
||||
my ($self, $value) = @_;
|
||||
# $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
|
||||
my $config = $self->GetParent->GetParent->{config};
|
||||
if ($value =~ /^[0-9,.E]+$/) {
|
||||
# Looks like a number. Validate the layer height.
|
||||
my $nozzle_dmrs = $config->get('nozzle_diameter');
|
||||
my $min_layer_heights = $config->get('min_layer_height');
|
||||
my $max_layer_heights = $config->get('max_layer_height');
|
||||
my $min_layer_height = 1000.;
|
||||
my $max_layer_height = 0.;
|
||||
my $max_nozzle_dmr = 0.;
|
||||
for (my $i = 0; $i < int(@{$nozzle_dmrs}); $i += 1) {
|
||||
$min_layer_height = $min_layer_heights->[$i] if ($min_layer_heights->[$i] < $min_layer_height);
|
||||
$max_layer_height = $max_layer_heights->[$i] if ($max_layer_heights->[$i] > $max_layer_height);
|
||||
$max_nozzle_dmr = $nozzle_dmrs ->[$i] if ($nozzle_dmrs ->[$i] > $max_nozzle_dmr );
|
||||
}
|
||||
$min_layer_height = 0.005 if ($min_layer_height < 0.005);
|
||||
$max_layer_height = $max_nozzle_dmr * 0.75 if ($max_layer_height == 0.);
|
||||
$max_layer_height = $max_nozzle_dmr if ($max_layer_height > $max_nozzle_dmr);
|
||||
return ($value < $min_layer_height) ? $min_layer_height :
|
||||
($value > $max_layer_height) ? $max_layer_height : $value;
|
||||
} else {
|
||||
# If an invalid numeric value has been entered, use the default layer height.
|
||||
return $config->get('layer_height');
|
||||
}
|
||||
}
|
||||
|
||||
sub CanClose {
|
||||
my $self = shift;
|
||||
|
||||
# validate ranges before allowing user to dismiss the dialog
|
||||
|
||||
foreach my $range ($self->_get_ranges) {
|
||||
my ($min, $max, $height) = @$range;
|
||||
if ($max <= $min) {
|
||||
Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
|
||||
return 0;
|
||||
}
|
||||
if ($min < 0 || $max < 0) {
|
||||
Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
|
||||
return 0;
|
||||
}
|
||||
if ($height < 0) {
|
||||
Slic3r::GUI::show_error($self, "Invalid layer height $height.");
|
||||
return 0;
|
||||
}
|
||||
# TODO: check for overlapping ranges
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub Closing {
|
||||
my $self = shift;
|
||||
|
||||
# save ranges into the plater object
|
||||
$self->model_object->set_layer_height_ranges([ $self->_get_ranges ]);
|
||||
}
|
||||
|
||||
sub _get_ranges {
|
||||
my $self = shift;
|
||||
|
||||
my @ranges = ();
|
||||
for my $i (0 .. $self->{grid}->GetNumberRows-1) {
|
||||
my ($min, $max, $height) = map $self->{grid}->GetCellValue($i, $_), 0..2;
|
||||
next if $min eq '' || $max eq '' || $height eq '';
|
||||
push @ranges, [ $min, $max, $height ];
|
||||
}
|
||||
return sort { $a->[0] <=> $b->[0] } @ranges;
|
||||
}
|
||||
|
||||
sub LayersChanged {
|
||||
my ($self) = @_;
|
||||
return $self->{layers_changed};
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
# Included in ObjectSettingsDialog -> ObjectPartsPanel.
|
||||
# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh.
|
||||
|
||||
package Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
use constant ICON_MATERIAL => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
|
||||
use constant TYPE_OBJECT => -1;
|
||||
use constant TYPE_PART => 0;
|
||||
use constant TYPE_MODIFIER => 1;
|
||||
use constant TYPE_SUPPORT_ENFORCER => 2;
|
||||
use constant TYPE_SUPPORT_BLOCKER => 3;
|
||||
|
||||
my %icons = (
|
||||
'Advanced' => 'wand.png',
|
||||
'Extruders' => 'funnel.png',
|
||||
'Extrusion Width' => 'funnel.png',
|
||||
'Infill' => 'infill.png',
|
||||
'Layers and Perimeters' => 'layers.png',
|
||||
'Skirt and brim' => 'box.png',
|
||||
'Speed' => 'time.png',
|
||||
'Speed > Acceleration' => 'time.png',
|
||||
'Support material' => 'building.png',
|
||||
);
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
# C++ class Slic3r::DynamicPrintConfig, initially empty.
|
||||
$self->{default_config} = Slic3r::Config->new;
|
||||
$self->{config} = Slic3r::Config->new;
|
||||
# On change callback.
|
||||
$self->{on_change} = $params{on_change};
|
||||
$self->{type} = TYPE_OBJECT;
|
||||
$self->{fixed_options} = {};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
|
||||
|
||||
# option selector
|
||||
{
|
||||
# create the button
|
||||
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
|
||||
EVT_LEFT_DOWN($btn, sub {
|
||||
my $menu = Wx::Menu->new;
|
||||
# create category submenus
|
||||
my %categories = (); # category => submenu
|
||||
foreach my $opt_key (@{$self->{options}}) {
|
||||
if (my $cat = $Slic3r::Config::Options->{$opt_key}{category}) {
|
||||
$categories{$cat} //= Wx::Menu->new;
|
||||
}
|
||||
}
|
||||
# append submenus to main menu
|
||||
my @categories = ('Layers and Perimeters', 'Infill', 'Support material', 'Speed', 'Extruders', 'Extrusion Width', 'Advanced');
|
||||
#foreach my $cat (sort keys %categories) {
|
||||
foreach my $cat (@categories) {
|
||||
wxTheApp->append_submenu($menu, $cat, "", $categories{$cat}, undef, $icons{$cat});
|
||||
}
|
||||
# append options to submenus
|
||||
foreach my $opt_key (@{$self->{options}}) {
|
||||
my $cat = $Slic3r::Config::Options->{$opt_key}{category} or next;
|
||||
my $cb = sub {
|
||||
$self->{config}->set($opt_key, $self->{default_config}->get($opt_key));
|
||||
$self->update_optgroup;
|
||||
$self->{on_change}->($opt_key) if $self->{on_change};
|
||||
};
|
||||
wxTheApp->append_menu_item($categories{$cat}, $self->{option_labels}{$opt_key},
|
||||
$Slic3r::Config::Options->{$opt_key}{tooltip}, $cb);
|
||||
}
|
||||
$self->PopupMenu($menu, $btn->GetPosition);
|
||||
$menu->Destroy;
|
||||
});
|
||||
|
||||
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$h_sizer->Add($btn, 0, wxALL, 0);
|
||||
$self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->SetScrollbars(0, 1, 0, 1);
|
||||
|
||||
$self->set_opt_keys($params{opt_keys}) if $params{opt_keys};
|
||||
$self->update_optgroup;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub set_default_config {
|
||||
my ($self, $config) = @_;
|
||||
$self->{default_config} = $config;
|
||||
}
|
||||
|
||||
sub set_config {
|
||||
my ($self, $config) = @_;
|
||||
$self->{config} = $config;
|
||||
$self->update_optgroup;
|
||||
}
|
||||
|
||||
sub set_opt_keys {
|
||||
my ($self, $opt_keys) = @_;
|
||||
# sort options by category+label
|
||||
$self->{option_labels} = { map { $_ => $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label} } @$opt_keys };
|
||||
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
|
||||
}
|
||||
|
||||
sub set_type {
|
||||
my ($self, $type) = @_;
|
||||
$self->{type} = $type;
|
||||
if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) {
|
||||
$self->{btn_add}->Hide;
|
||||
} else {
|
||||
$self->{btn_add}->Show;
|
||||
}
|
||||
}
|
||||
|
||||
sub set_fixed_options {
|
||||
my ($self, $opt_keys) = @_;
|
||||
$self->{fixed_options} = { map {$_ => 1} @$opt_keys };
|
||||
$self->update_optgroup;
|
||||
}
|
||||
|
||||
sub update_optgroup {
|
||||
my $self = shift;
|
||||
|
||||
$self->{options_sizer}->Clear(1);
|
||||
return if !defined $self->{config};
|
||||
|
||||
if ($self->{type} != TYPE_OBJECT) {
|
||||
my $label = Wx::StaticText->new($self, -1, "Type:"),
|
||||
my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ];
|
||||
my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY);
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($label, 1, wxEXPAND | wxALL, 5);
|
||||
$sizer->Add($field, 0, wxALL, 5);
|
||||
EVT_COMBOBOX($self, $field, sub {
|
||||
my $idx = $field->GetSelection; # get index of selected value
|
||||
$self->{on_change}->("part_type", $idx) if $self->{on_change};
|
||||
});
|
||||
$self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0);
|
||||
}
|
||||
|
||||
my %categories = ();
|
||||
if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) {
|
||||
foreach my $opt_key (@{$self->{config}->get_keys}) {
|
||||
my $category = $Slic3r::Config::Options->{$opt_key}{category};
|
||||
$categories{$category} ||= [];
|
||||
push @{$categories{$category}}, $opt_key;
|
||||
}
|
||||
}
|
||||
foreach my $category (sort keys %categories) {
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
parent => $self,
|
||||
title => $category,
|
||||
config => $self->{config},
|
||||
full_labels => 1,
|
||||
label_font => $Slic3r::GUI::small_font,
|
||||
sidetext_font => $Slic3r::GUI::small_font,
|
||||
label_width => 150,
|
||||
on_change => sub { $self->{on_change}->() if $self->{on_change} },
|
||||
extra_column => sub {
|
||||
my ($line) = @_;
|
||||
|
||||
my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line
|
||||
|
||||
# disallow deleting fixed options
|
||||
return undef if $self->{fixed_options}{$opt_key};
|
||||
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
$self->{config}->erase($opt_key);
|
||||
$self->{on_change}->() if $self->{on_change};
|
||||
wxTheApp->CallAfter(sub { $self->update_optgroup });
|
||||
});
|
||||
return $btn;
|
||||
},
|
||||
);
|
||||
foreach my $opt_key (sort @{$categories{$category}}) {
|
||||
$optgroup->append_single_option_line($opt_key);
|
||||
}
|
||||
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0);
|
||||
}
|
||||
$self->GetParent->Layout; # we need this for showing scrollbars
|
||||
}
|
||||
|
||||
# work around a wxMAC bug causing controls not being disabled when calling Disable() on a Window
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->{btn_add}->Enable;
|
||||
$self->Enable;
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->{btn_add}->Disable;
|
||||
$self->Disable;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Status bar at the bottom of the main screen.
|
||||
# Now it just implements cancel cb on perl side, every other functionality is
|
||||
# in C++
|
||||
|
||||
package Slic3r::GUI::ProgressStatusBar;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $cancel_cb;
|
||||
|
||||
sub SetCancelCallback {
|
||||
my $self = shift;
|
||||
my ($cb) = @_;
|
||||
$cancel_cb = $cb;
|
||||
$cb ? $self->ShowCancelButton : $self->HideCancelButton;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package Slic3r::GUI::SystemInfo;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id wxTheClipboard);
|
||||
use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON);
|
||||
use Wx::Html;
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my ($class, %params) = @_;
|
||||
my $self = $class->SUPER::new($params{parent}, -1, 'Slic3r Prusa Edition - System Information', wxDefaultPosition, [600, 340],
|
||||
wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxRESIZE_BORDER);
|
||||
$self->{text_info} = $params{text_info};
|
||||
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->SetSizer($vsizer);
|
||||
|
||||
# text
|
||||
my $text =
|
||||
'<html>' .
|
||||
'<body bgcolor="#ffffff" link="#808080">' .
|
||||
($params{slic3r_info} // '') .
|
||||
($params{copyright_info} // '') .
|
||||
($params{system_info} // '') .
|
||||
($params{opengl_info} // '') .
|
||||
'</body>' .
|
||||
'</html>';
|
||||
my $html = $self->{html} = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
my $size = &Wx::wxMSW ? 8 : 10;
|
||||
$html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size * 1.5, $size * 1.4, $size * 1.3, $size, $size, $size, $size]);
|
||||
$html->SetBorders(10);
|
||||
$html->SetPage($text);
|
||||
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
|
||||
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
my $btn_copy_to_clipboard = Wx::Button->new($self, -1, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize);
|
||||
$buttons->Insert(0, $btn_copy_to_clipboard, 0, wxLEFT, 5);
|
||||
EVT_BUTTON($self, $btn_copy_to_clipboard, \©_to_clipboard);
|
||||
$self->SetEscapeId(wxID_CLOSE);
|
||||
EVT_BUTTON($self, wxID_CLOSE, sub {
|
||||
$self->EndModal(wxID_CLOSE);
|
||||
$self->Close;
|
||||
});
|
||||
# $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
$vsizer->Add($buttons, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub link_clicked {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref);
|
||||
$event->Skip(0);
|
||||
}
|
||||
|
||||
sub copy_to_clipboard {
|
||||
my ($self, $event) = @_;
|
||||
my $data = $self->{text_info};
|
||||
wxTheClipboard->Open;
|
||||
wxTheClipboard->SetData(Wx::TextDataObject->new($data));
|
||||
wxTheClipboard->Close;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -122,6 +122,7 @@ sub line_intersection {
|
|||
: undef;
|
||||
}
|
||||
|
||||
# Used by test cases.
|
||||
sub collinear {
|
||||
my ($line1, $line2, $require_overlapping) = @_;
|
||||
my $intersection = _line_intersection(map @$_, @$line1, @$line2);
|
||||
|
|
@ -226,6 +227,7 @@ sub bounding_box {
|
|||
return @bb[X1,Y1,X2,Y2];
|
||||
}
|
||||
|
||||
# used by ExPolygon::size
|
||||
sub size_2D {
|
||||
my @bounding_box = bounding_box(@_);
|
||||
return (
|
||||
|
|
@ -234,6 +236,7 @@ sub size_2D {
|
|||
);
|
||||
}
|
||||
|
||||
# Used by sub collinear, which is used by test cases.
|
||||
# bounding_box_intersect($d, @a, @b)
|
||||
# Return true if the given bounding boxes @a and @b intersect
|
||||
# in $d dimensions. Used by sub collinear.
|
||||
|
|
@ -252,6 +255,7 @@ sub bounding_box_intersect {
|
|||
return 1;
|
||||
}
|
||||
|
||||
# Used by test cases.
|
||||
# this assumes a CCW rotation from $p2 to $p3 around $p1
|
||||
sub angle3points {
|
||||
my ($p1, $p2, $p3) = @_;
|
||||
|
|
|
|||
|
|
@ -1,300 +0,0 @@
|
|||
# The slicing work horse.
|
||||
# Extends C++ class Slic3r::Print
|
||||
package Slic3r::Print;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename qw(basename fileparse);
|
||||
use File::Spec;
|
||||
use List::Util qw(min max first sum);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y unscale);
|
||||
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset
|
||||
union JT_ROUND JT_SQUARE);
|
||||
use Slic3r::Print::State ':steps';
|
||||
|
||||
our $status_cb;
|
||||
|
||||
sub set_status_cb {
|
||||
my ($class, $cb) = @_;
|
||||
$status_cb = $cb;
|
||||
}
|
||||
|
||||
sub status_cb {
|
||||
return $status_cb // sub {};
|
||||
}
|
||||
|
||||
sub size {
|
||||
my $self = shift;
|
||||
return $self->bounding_box->size;
|
||||
}
|
||||
|
||||
# Slicing process, running at a background thread.
|
||||
sub process {
|
||||
my ($self) = @_;
|
||||
|
||||
Slic3r::trace(3, "Staring the slicing process.");
|
||||
$_->make_perimeters for @{$self->objects};
|
||||
|
||||
$self->status_cb->(70, "Infilling layers");
|
||||
$_->infill for @{$self->objects};
|
||||
|
||||
$_->generate_support_material for @{$self->objects};
|
||||
$self->make_skirt;
|
||||
$self->make_brim; # must come after make_skirt
|
||||
$self->make_wipe_tower;
|
||||
|
||||
# time to make some statistics
|
||||
if (0) {
|
||||
eval "use Devel::Size";
|
||||
print "MEMORY USAGE:\n";
|
||||
printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
|
||||
printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
|
||||
printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
|
||||
printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
|
||||
printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
|
||||
printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
|
||||
}
|
||||
if (0) {
|
||||
eval "use Slic3r::Test::SectionCut";
|
||||
Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg");
|
||||
}
|
||||
Slic3r::trace(3, "Slicing process finished.")
|
||||
}
|
||||
|
||||
# G-code export process, running at a background thread.
|
||||
# The export_gcode may die for various reasons (fails to process output_filename_format,
|
||||
# write error into the G-code, cannot execute post-processing scripts).
|
||||
# It is up to the caller to show an error message.
|
||||
sub export_gcode {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
# prerequisites
|
||||
$self->process;
|
||||
|
||||
# output everything to a G-code file
|
||||
# The following call may die if the output_filename_format template substitution fails.
|
||||
my $output_file = $self->output_filepath($params{output_file} // '');
|
||||
$self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
|
||||
|
||||
# The following line may die for multiple reasons.
|
||||
my $gcode = Slic3r::GCode->new;
|
||||
if (defined $params{gcode_preview_data}) {
|
||||
$gcode->do_export_w_preview($self, $output_file, $params{gcode_preview_data});
|
||||
} else {
|
||||
$gcode->do_export($self, $output_file);
|
||||
}
|
||||
|
||||
# run post-processing scripts
|
||||
if (@{$self->config->post_process}) {
|
||||
$self->status_cb->(95, "Running post-processing scripts");
|
||||
$self->config->setenv;
|
||||
for my $script (@{$self->config->post_process}) {
|
||||
# Ignore empty post processing script lines.
|
||||
next if $script =~ /^\s*$/;
|
||||
Slic3r::debugf " '%s' '%s'\n", $script, $output_file;
|
||||
# -x doesn't return true on Windows except for .exe files
|
||||
if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) {
|
||||
die "The configured post-processing script is not executable: check permissions. ($script)\n";
|
||||
}
|
||||
if ($^O eq 'MSWin32' && $script =~ /\.[pP][lL]/) {
|
||||
# The current process (^X) may be slic3r.exe or slic3r-console.exe.
|
||||
# Replace it with the current perl interpreter.
|
||||
my($filename, $directories, $suffix) = fileparse($^X);
|
||||
$filename =~ s/^slic3r.*$/perl5\.24\.0\.exe/;
|
||||
my $interpreter = $directories . $filename;
|
||||
system($interpreter, $script, $output_file);
|
||||
} else {
|
||||
system($script, $output_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub export_png {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my @sobjects = @{$self->objects};
|
||||
my $objnum = scalar @sobjects;
|
||||
for(my $oi = 0; $oi < $objnum; $oi++)
|
||||
{
|
||||
$sobjects[$oi]->slice;
|
||||
$self->status_cb->(($oi + 1)*100/$objnum - 1, "Slicing...");
|
||||
}
|
||||
|
||||
my $fh = $params{output_file};
|
||||
$self->status_cb->(90, "Exporting zipped archive...");
|
||||
$self->print_to_png($fh);
|
||||
$self->status_cb->(100, "Done.");
|
||||
}
|
||||
|
||||
# Export SVG slices for the offline SLA printing.
|
||||
# The export_svg is expected to be executed inside an eval block.
|
||||
sub export_svg {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my @sobjects = @{$self->objects};
|
||||
my $objnum = scalar @sobjects;
|
||||
for(my $oi = 0; $oi < $objnum; $oi++)
|
||||
{
|
||||
$sobjects[$oi]->slice;
|
||||
$self->status_cb->(($oi + 1)*100/$objnum - 1, "Slicing...");
|
||||
}
|
||||
|
||||
my $fh = $params{output_fh};
|
||||
if (!$fh) {
|
||||
# The following line may die if the output_filename_format template substitution fails.
|
||||
my $output_file = $self->output_filepath($params{output_file});
|
||||
$output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/;
|
||||
Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
|
||||
print "Exporting to $output_file..." unless $params{quiet};
|
||||
}
|
||||
|
||||
my $print_bb = $self->bounding_box;
|
||||
my $print_size = $print_bb->size;
|
||||
print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg width="%s" height="%s" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:slic3r="http://slic3r.org/namespaces/slic3r">
|
||||
<!--
|
||||
Generated using Slic3r $Slic3r::VERSION
|
||||
http://slic3r.org/
|
||||
-->
|
||||
EOF
|
||||
|
||||
my $print_polygon = sub {
|
||||
my ($polygon, $type) = @_;
|
||||
printf $fh qq{ <polygon slic3r:type="%s" points="%s" style="fill: %s" />\n},
|
||||
$type, (join ' ', map { join ',', map unscale $_, @$_ } @$polygon),
|
||||
($type eq 'contour' ? 'white' : 'black');
|
||||
};
|
||||
|
||||
my @layers = sort { $a->print_z <=> $b->print_z }
|
||||
map { @{$_->layers}, @{$_->support_layers} }
|
||||
@{$self->objects};
|
||||
|
||||
my $layer_id = -1;
|
||||
my @previous_layer_slices = ();
|
||||
for my $layer (@layers) {
|
||||
$layer_id++;
|
||||
if ($layer->slice_z == -1) {
|
||||
printf $fh qq{ <g id="layer%d">\n}, $layer_id;
|
||||
} else {
|
||||
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, unscale($layer->slice_z);
|
||||
}
|
||||
|
||||
my @current_layer_slices = ();
|
||||
# sort slices so that the outermost ones come first
|
||||
my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices};
|
||||
foreach my $copy (@{$layer->object->_shifted_copies}) {
|
||||
foreach my $slice (@slices) {
|
||||
my $expolygon = $slice->clone;
|
||||
$expolygon->translate(@$copy);
|
||||
$expolygon->translate(-$print_bb->x_min, -$print_bb->y_min);
|
||||
$print_polygon->($expolygon->contour, 'contour');
|
||||
$print_polygon->($_, 'hole') for @{$expolygon->holes};
|
||||
push @current_layer_slices, $expolygon;
|
||||
}
|
||||
}
|
||||
# generate support material
|
||||
if ($self->has_support_material && $layer->id > 0) {
|
||||
my (@supported_slices, @unsupported_slices) = ();
|
||||
foreach my $expolygon (@current_layer_slices) {
|
||||
my $intersection = intersection_ex(
|
||||
[ map @$_, @previous_layer_slices ],
|
||||
[ @$expolygon ],
|
||||
);
|
||||
@$intersection
|
||||
? push @supported_slices, $expolygon
|
||||
: push @unsupported_slices, $expolygon;
|
||||
}
|
||||
my @supported_points = map @$_, @$_, @supported_slices;
|
||||
foreach my $expolygon (@unsupported_slices) {
|
||||
# look for the nearest point to this island among all
|
||||
# supported points
|
||||
my $contour = $expolygon->contour;
|
||||
my $support_point = $contour->first_point->nearest_point(\@supported_points)
|
||||
or next;
|
||||
my $anchor_point = $support_point->nearest_point([ @$contour ]);
|
||||
printf $fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n},
|
||||
map @$_, $support_point, $anchor_point;
|
||||
}
|
||||
}
|
||||
print $fh qq{ </g>\n};
|
||||
@previous_layer_slices = @current_layer_slices;
|
||||
}
|
||||
|
||||
print $fh "</svg>\n";
|
||||
close $fh;
|
||||
print "Done.\n" unless $params{quiet};
|
||||
}
|
||||
|
||||
sub make_skirt {
|
||||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$_->make_perimeters for @{$self->objects};
|
||||
$_->infill for @{$self->objects};
|
||||
$_->generate_support_material for @{$self->objects};
|
||||
|
||||
return if $self->step_done(STEP_SKIRT);
|
||||
|
||||
$self->set_step_started(STEP_SKIRT);
|
||||
$self->skirt->clear;
|
||||
if ($self->has_skirt) {
|
||||
$self->status_cb->(88, "Generating skirt");
|
||||
$self->_make_skirt();
|
||||
}
|
||||
$self->set_step_done(STEP_SKIRT);
|
||||
}
|
||||
|
||||
sub make_brim {
|
||||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$_->make_perimeters for @{$self->objects};
|
||||
$_->infill for @{$self->objects};
|
||||
$_->generate_support_material for @{$self->objects};
|
||||
$self->make_skirt;
|
||||
|
||||
return if $self->step_done(STEP_BRIM);
|
||||
|
||||
$self->set_step_started(STEP_BRIM);
|
||||
# since this method must be idempotent, we clear brim paths *before*
|
||||
# checking whether we need to generate them
|
||||
$self->brim->clear;
|
||||
if ($self->config->brim_width > 0) {
|
||||
$self->status_cb->(88, "Generating brim");
|
||||
$self->_make_brim;
|
||||
}
|
||||
|
||||
$self->set_step_done(STEP_BRIM);
|
||||
}
|
||||
|
||||
sub make_wipe_tower {
|
||||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$_->make_perimeters for @{$self->objects};
|
||||
$_->infill for @{$self->objects};
|
||||
$_->generate_support_material for @{$self->objects};
|
||||
$self->make_skirt;
|
||||
$self->make_brim;
|
||||
|
||||
return if $self->step_done(STEP_WIPE_TOWER);
|
||||
|
||||
$self->set_step_started(STEP_WIPE_TOWER);
|
||||
$self->_clear_wipe_tower;
|
||||
if ($self->has_wipe_tower) {
|
||||
# $self->status_cb->(95, "Generating wipe tower");
|
||||
$self->_make_wipe_tower;
|
||||
}
|
||||
$self->set_step_done(STEP_WIPE_TOWER);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -21,95 +21,4 @@ sub support_layers {
|
|||
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
|
||||
}
|
||||
|
||||
# 1) Decides Z positions of the layers,
|
||||
# 2) Initializes layers and their regions
|
||||
# 3) Slices the object meshes
|
||||
# 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
|
||||
# 5) Applies size compensation (offsets the slices in XY plane)
|
||||
# 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
|
||||
# Resulting expolygons of layer regions are marked as Internal.
|
||||
#
|
||||
# this should be idempotent
|
||||
sub slice {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->step_done(STEP_SLICE);
|
||||
$self->set_step_started(STEP_SLICE);
|
||||
# $self->print->status_cb->(10, "Processing triangulated mesh");
|
||||
|
||||
$self->_slice;
|
||||
|
||||
my $warning = $self->_fix_slicing_errors;
|
||||
warn $warning if (defined($warning) && $warning ne '');
|
||||
|
||||
# simplify slices if required
|
||||
$self->_simplify_slices(scale($self->print->config->resolution))
|
||||
if ($self->print->config->resolution);
|
||||
|
||||
die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"
|
||||
if !@{$self->layers};
|
||||
|
||||
$self->set_step_done(STEP_SLICE);
|
||||
}
|
||||
|
||||
# 1) Merges typed region slices into stInternal type.
|
||||
# 2) Increases an "extra perimeters" counter at region slices where needed.
|
||||
# 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
|
||||
sub make_perimeters {
|
||||
my ($self) = @_;
|
||||
|
||||
# prerequisites
|
||||
$self->slice;
|
||||
|
||||
if (! $self->step_done(STEP_PERIMETERS)) {
|
||||
$self->print->status_cb->(20, "Generating perimeters");
|
||||
$self->_make_perimeters;
|
||||
}
|
||||
}
|
||||
|
||||
sub prepare_infill {
|
||||
my ($self) = @_;
|
||||
|
||||
# prerequisites
|
||||
$self->make_perimeters;
|
||||
|
||||
return if $self->step_done(STEP_PREPARE_INFILL);
|
||||
$self->set_step_started(STEP_PREPARE_INFILL);
|
||||
$self->print->status_cb->(30, "Preparing infill");
|
||||
|
||||
$self->_prepare_infill;
|
||||
|
||||
$self->set_step_done(STEP_PREPARE_INFILL);
|
||||
}
|
||||
|
||||
sub infill {
|
||||
my ($self) = @_;
|
||||
|
||||
# prerequisites
|
||||
$self->prepare_infill;
|
||||
$self->_infill;
|
||||
}
|
||||
|
||||
sub generate_support_material {
|
||||
my $self = shift;
|
||||
|
||||
# prerequisites
|
||||
$self->slice;
|
||||
|
||||
return if $self->step_done(STEP_SUPPORTMATERIAL);
|
||||
$self->set_step_started(STEP_SUPPORTMATERIAL);
|
||||
|
||||
$self->clear_support_layers;
|
||||
|
||||
if (($self->config->support_material || $self->config->raft_layers > 0) && scalar(@{$self->layers}) > 1) {
|
||||
$self->print->status_cb->(85, "Generating support material");
|
||||
# New supports, C++ implementation.
|
||||
$self->_generate_support_material;
|
||||
}
|
||||
|
||||
$self->set_step_done(STEP_SUPPORTMATERIAL);
|
||||
my $stats = sprintf "Weight: %.1fg, Cost: %.1f" , $self->print->total_weight, $self->print->total_cost;
|
||||
$self->print->status_cb->(85, $stats);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -38,11 +38,6 @@ has 'duplicate_grid' => (
|
|||
default => sub { [1,1] },
|
||||
);
|
||||
|
||||
has 'status_cb' => (
|
||||
is => 'rw',
|
||||
default => sub { sub {} },
|
||||
);
|
||||
|
||||
has 'print_center' => (
|
||||
is => 'rw',
|
||||
default => sub { Slic3r::Pointf->new(100,100) },
|
||||
|
|
@ -90,35 +85,10 @@ sub set_model {
|
|||
}
|
||||
}
|
||||
|
||||
sub _before_export {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_print->set_status_cb($self->status_cb);
|
||||
$self->_print->validate;
|
||||
}
|
||||
|
||||
sub _after_export {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_print->set_status_cb(undef);
|
||||
}
|
||||
|
||||
sub export_gcode {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_before_export;
|
||||
$self->_print->export_gcode(output_file => $self->output_file);
|
||||
$self->_after_export;
|
||||
}
|
||||
|
||||
sub export_svg {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_before_export;
|
||||
|
||||
$self->_print->export_svg(output_file => $self->output_file);
|
||||
|
||||
$self->_after_export;
|
||||
$self->_print->validate;
|
||||
$self->_print->export_gcode($self->output_file // '');
|
||||
}
|
||||
|
||||
sub export_png {
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
package Slic3r::SVG;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use SVG;
|
||||
|
||||
use constant X => 0;
|
||||
use constant Y => 1;
|
||||
|
||||
our $filltype = 'evenodd';
|
||||
|
||||
sub factor {
|
||||
return &Slic3r::SCALING_FACTOR * 10;
|
||||
}
|
||||
|
||||
sub svg {
|
||||
my $svg = SVG->new(width => 200 * 10, height => 200 * 10);
|
||||
my $marker_end = $svg->marker(
|
||||
id => "endArrow",
|
||||
viewBox => "0 0 10 10",
|
||||
refX => "1",
|
||||
refY => "5",
|
||||
markerUnits => "strokeWidth",
|
||||
orient => "auto",
|
||||
markerWidth => "10",
|
||||
markerHeight => "8",
|
||||
);
|
||||
$marker_end->polyline(
|
||||
points => "0,0 10,5 0,10 1,5",
|
||||
fill => "darkblue",
|
||||
);
|
||||
|
||||
return $svg;
|
||||
}
|
||||
|
||||
sub output {
|
||||
my ($filename, @things) = @_;
|
||||
|
||||
my $svg = svg();
|
||||
my $arrows = 1;
|
||||
|
||||
while (my $type = shift @things) {
|
||||
my $value = shift @things;
|
||||
|
||||
if ($type eq 'no_arrows') {
|
||||
$arrows = 0;
|
||||
} elsif ($type =~ /^(?:(.+?)_)?expolygons$/) {
|
||||
my $colour = $1;
|
||||
$value = [ map $_->pp, @$value ];
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => 0,
|
||||
'stroke' => $colour || 'black',
|
||||
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
|
||||
'fill-type' => $filltype,
|
||||
},
|
||||
);
|
||||
foreach my $expolygon (@$value) {
|
||||
my $points = join ' ', map "M $_ z", map join(" ", reverse map $_->[0]*factor() . " " . $_->[1]*factor(), @$_), @$expolygon;
|
||||
$g->path(
|
||||
d => $points,
|
||||
);
|
||||
}
|
||||
} elsif ($type =~ /^(?:(.+?)_)?(polygon|polyline)s$/) {
|
||||
my ($colour, $method) = ($1, $2);
|
||||
$value = [ map $_->pp, @$value ];
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => ($method eq 'polyline') ? 1 : 0,
|
||||
'stroke' => $colour || 'black',
|
||||
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
|
||||
},
|
||||
);
|
||||
foreach my $polygon (@$value) {
|
||||
my $path = $svg->get_path(
|
||||
'x' => [ map($_->[X] * factor(), @$polygon) ],
|
||||
'y' => [ map($_->[Y] * factor(), @$polygon) ],
|
||||
-type => 'polygon',
|
||||
);
|
||||
$g->$method(
|
||||
%$path,
|
||||
'marker-end' => !$arrows ? "" : "url(#endArrow)",
|
||||
);
|
||||
}
|
||||
} elsif ($type =~ /^(?:(.+?)_)?points$/) {
|
||||
my $colour = $1 // 'black';
|
||||
my $r = $colour eq 'black' ? 1 : 3;
|
||||
$value = [ map $_->pp, @$value ];
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => 2,
|
||||
'stroke' => $colour,
|
||||
'fill' => $colour,
|
||||
},
|
||||
);
|
||||
foreach my $point (@$value) {
|
||||
$g->circle(
|
||||
cx => $point->[X] * factor(),
|
||||
cy => $point->[Y] * factor(),
|
||||
r => $r,
|
||||
);
|
||||
}
|
||||
} elsif ($type =~ /^(?:(.+?)_)?lines$/) {
|
||||
my $colour = $1;
|
||||
$value = [ map $_->pp, @$value ];
|
||||
|
||||
my $g = $svg->group(
|
||||
style => {
|
||||
'stroke-width' => 2,
|
||||
},
|
||||
);
|
||||
foreach my $line (@$value) {
|
||||
$g->line(
|
||||
x1 => $line->[0][X] * factor(),
|
||||
y1 => $line->[0][Y] * factor(),
|
||||
x2 => $line->[1][X] * factor(),
|
||||
y2 => $line->[1][Y] * factor(),
|
||||
style => {
|
||||
'stroke' => $colour || 'black',
|
||||
},
|
||||
'marker-end' => !$arrows ? "" : "url(#endArrow)",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_svg($svg, $filename);
|
||||
}
|
||||
|
||||
sub write_svg {
|
||||
my ($svg, $filename) = @_;
|
||||
|
||||
Slic3r::open(\my $fh, '>', $filename);
|
||||
print $fh $svg->xmlify;
|
||||
close $fh;
|
||||
printf "SVG written to %s\n", $filename;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -208,8 +208,9 @@ sub gcode {
|
|||
my $gcode_temp_path = abs_path($0) . '.gcode.temp';
|
||||
# Remove the existing temp file.
|
||||
unlink $gcode_temp_path;
|
||||
$print->set_status_silent;
|
||||
$print->process;
|
||||
$print->export_gcode(output_file => $gcode_temp_path, quiet => 1);
|
||||
$print->export_gcode($gcode_temp_path);
|
||||
# Read the temoprary G-code file.
|
||||
my $gcode;
|
||||
{
|
||||
|
|
|
|||
642
serial.txt
Normal file
642
serial.txt
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
<< start
|
||||
<< echo: 3.1.1-RC5-150z
|
||||
<< echo: Last Updated: Feb 7 2018 15:28:23 | Author: (none, default config)
|
||||
<< Compiled: Feb 7 2018
|
||||
<< echo: Free Memory: 1777 PlannerBufferBytes: 1312
|
||||
<< echo:Hardcoded Default Settings Loaded
|
||||
<< adc_init
|
||||
>> N0 M105*39
|
||||
<< CrashDetect ENABLED!
|
||||
<< tmc2130_init(), mode=NORMAL
|
||||
<< PAT9125_init:1
|
||||
<< FSensor
|
||||
<< ENABLED
|
||||
<< echo:SD card ok
|
||||
<< echo:busy: processing
|
||||
<< Error:Line Number is not Last Line Number+1, Last Line: 0
|
||||
<< Resend: 1
|
||||
<< ok
|
||||
>> N1 M107*36
|
||||
<< ok
|
||||
>> N2 M115 U3.1.1-RC5*107
|
||||
<< ok
|
||||
>> N3 M201 X1000 Y1000 Z200 E5000*10
|
||||
<< ok
|
||||
>> N4 M203 X200 Y200 Z12 E120*8
|
||||
<< ok
|
||||
>> N5 M204 S1250 T1250*39
|
||||
<< ok
|
||||
>> N6 M205 X10 Y10 Z0.4 E2.5*63
|
||||
<< ok
|
||||
>> N7 M205 S0 T0*36
|
||||
<< ok
|
||||
>> N8 M83*16
|
||||
<< ok
|
||||
>> N9 M104 S215*106
|
||||
<< ok
|
||||
>> N10 M140 S60*98
|
||||
<< ok
|
||||
>> N11 M190 S60*110
|
||||
<< T:158.08 E:0 B:57.1
|
||||
<< T:157.04 E:0 B:57.1
|
||||
<< T:156.77 E:0 B:56.9
|
||||
<< T:156.97 E:0 B:57.0
|
||||
<< T:158.14 E:0 B:57.0
|
||||
<< T:159.62 E:0 B:56.9
|
||||
<< T:161.25 E:0 B:56.8
|
||||
<< T:163.64 E:0 B:56.8
|
||||
<< T:165.94 E:0 B:56.7
|
||||
<< T:168.40 E:0 B:56.8
|
||||
<< T:170.79 E:0 B:56.7
|
||||
<< T:173.68 E:0 B:56.7
|
||||
<< T:175.53 E:0 B:56.6
|
||||
<< T:178.40 E:0 B:56.6
|
||||
<< T:180.94 E:0 B:56.5
|
||||
<< T:183.92 E:0 B:56.4
|
||||
<< T:186.73 E:0 B:56.4
|
||||
<< T:189.20 E:0 B:56.4
|
||||
<< T:191.32 E:0 B:56.3
|
||||
<< T:193.91 E:0 B:56.3
|
||||
<< T:196.38 E:0 B:56.2
|
||||
<< T:198.75 E:0 B:56.2
|
||||
<< T:201.65 E:0 B:56.3
|
||||
<< T:203.57 E:0 B:56.4
|
||||
<< T:206.38 E:0 B:56.5
|
||||
<< T:208.71 E:0 B:56.6
|
||||
<< T:211.04 E:0 B:56.6
|
||||
<< T:212.86 E:0 B:56.8
|
||||
<< T:214.84 E:0 B:57.0
|
||||
<< T:215.52 E:0 B:57.2
|
||||
<< T:215.78 E:0 B:57.4
|
||||
<< T:216.30 E:0 B:57.6
|
||||
<< T:216.51 E:0 B:57.7
|
||||
<< T:215.73 E:0 B:58.0
|
||||
<< T:215.47 E:0 B:58.2
|
||||
<< T:214.95 E:0 B:58.5
|
||||
<< T:214.22 E:0 B:58.7
|
||||
<< T:213.65 E:0 B:59.0
|
||||
<< T:212.24 E:0 B:59.2
|
||||
<< T:212.14 E:0 B:59.4
|
||||
<< T:212.03 E:0 B:59.7
|
||||
<< T:211.51 E:0 B:59.8
|
||||
<< ok
|
||||
>> N12 M105*20
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N13 M105*21
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N14 M105*18
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N15 M105*19
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N16 M105*16
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N17 M105*17
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N18 M105*30
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N19 M105*31
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N20 M105*21
|
||||
<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2
|
||||
>> N21 M109 S215*93
|
||||
<< T:211.3 E:0 W:?
|
||||
<< T:211.8 E:0 W:?
|
||||
<< T:211.8 E:0 W:?
|
||||
<< T:212.1 E:0 W:?
|
||||
<< T:212.4 E:0 W:?
|
||||
<< T:213.3 E:0 W:?
|
||||
<< T:213.3 E:0 W:?
|
||||
<< T:213.8 E:0 W:?
|
||||
<< T:214.1 E:0 W:2
|
||||
<< T:214.1 E:0 W:1
|
||||
<< T:214.2 E:0 W:0
|
||||
<< ok
|
||||
>> N22 M105*23
|
||||
<< ok T:214.3 /215.0 B:60.8 /60.0 T0:214.3 /215.0 @:20 B@:7 P:46.4 A:36.0
|
||||
>> N23 M105*22
|
||||
<< ok T:214.3 /215.0 B:60.8 /60.0 T0:214.3 /215.0 @:20 B@:7 P:46.4 A:36.0
|
||||
>> N24 G28 W*82
|
||||
<< 0 step=62 mscnt= 993
|
||||
<< tmc2130_goto_step 0 0 2 1000
|
||||
<< step 61 mscnt = 984
|
||||
<< dir=0 steps=-61
|
||||
<< dir=1 steps=61
|
||||
<< dir=0 steps=3
|
||||
<< cnt 2 step 61 mscnt = 986
|
||||
<< cnt 1 step 62 mscnt = 1005
|
||||
<< cnt 0 step 63 mscnt = 1021
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< 0 step=34 mscnt= 547
|
||||
<< tmc2130_goto_step 1 0 2 1000
|
||||
<< step 34 mscnt = 552
|
||||
<< dir=1 steps=-34
|
||||
<< dir=0 steps=34
|
||||
<< dir=1 steps=30
|
||||
<< cnt 29 step 34 mscnt = 554
|
||||
<< cnt 28 step 35 mscnt = 572
|
||||
<< cnt 27 step 36 mscnt = 588
|
||||
<< cnt 26 step 37 mscnt = 604
|
||||
<< cnt 25 step 38 mscnt = 620
|
||||
<< cnt 24 step 39 mscnt = 637
|
||||
<< cnt 23 step 40 mscnt = 653
|
||||
<< cnt 22 step 41 mscnt = 668
|
||||
<< cnt 21 step 42 mscnt = 684
|
||||
<< cnt 20 step 43 mscnt = 701
|
||||
<< cnt 19 step 44 mscnt = 717
|
||||
<< cnt 18 step 45 mscnt = 733
|
||||
<< cnt 17 step 46 mscnt = 748
|
||||
<< cnt 16 step 47 mscnt = 765
|
||||
<< cnt 15 step 48 mscnt = 780
|
||||
<< cnt 14 step 49 mscnt = 796
|
||||
<< cnt 13 step 50 mscnt = 812
|
||||
<< cnt 12 step 51 mscnt = 828
|
||||
<< cnt 11 step 52 mscnt = 844
|
||||
<< cnt 10 step 53 mscnt = 860
|
||||
<< cnt 9 step 54 mscnt = 876
|
||||
<< cnt 8 step 55 mscnt = 893
|
||||
<< cnt 7 step 56 mscnt = 909
|
||||
<< cnt 6 step 57 mscnt = 925
|
||||
<< cnt 5 step 58 mscnt = 941
|
||||
<< cnt 4 step 59 mscnt = 956
|
||||
<< cnt 3 step 60 mscnt = 972
|
||||
<< cnt 2 step 61 mscnt = 988
|
||||
<< cnt 1 step 62 mscnt = 1005
|
||||
<< cnt 0 step 63 mscnt = 1021
|
||||
<< echo:busy: processing
|
||||
<< ok
|
||||
>> N25 M105*16
|
||||
<< ok T:213.1 /215.0 B:60.8 /60.0 T0:213.1 /215.0 @:44 B@:35 P:46.5 A:35.6
|
||||
>> N26 G80*37
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< ok
|
||||
>> N27 M105*18
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N28 M105*29
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N29 M105*28
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N30 M105*20
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N31 M105*21
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N32 M105*22
|
||||
<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7
|
||||
>> N33 G1 Y-3.0 F1000.0*24
|
||||
<< ok
|
||||
>> N34 G92 E0.0*110
|
||||
<< ok
|
||||
>> N35 G1 X60.0 E9.0 F1000.0*101
|
||||
<< ok
|
||||
>> N36 G1 X100.0 E12.5 F1000.0*110
|
||||
<< ok
|
||||
>> N37 G92 E0.0*109
|
||||
<< ok
|
||||
>> N38 M221 S95*102
|
||||
<< ok
|
||||
>> N39 M900 K30*120
|
||||
<< Invalid M code.
|
||||
<< ok
|
||||
>> N40 G21*46
|
||||
<< ok
|
||||
>> N41 G90*37
|
||||
<< ok
|
||||
>> N42 M83*46
|
||||
<< ok
|
||||
>> N43 G1 E-0.80000 F2100.00000*10
|
||||
<< ok
|
||||
>> N44 G1 Z0.600 F10200.000*1
|
||||
<< ok
|
||||
>> N45 G1 X112.437 Y93.991 F10200.000*106
|
||||
<< ok
|
||||
>> N46 G1 Z0.200 F10200.000*7
|
||||
<< ok
|
||||
>> N47 G1 E0.80000 F2100.00000*35
|
||||
<< ok
|
||||
>> N48 M204 S1000*107
|
||||
<< ok
|
||||
>> N49 G1 F1800*122
|
||||
<< ok
|
||||
>> N50 G1 X112.930 Y93.183 E0.02968*106
|
||||
<< ok
|
||||
>> N51 G1 X113.335 Y92.806 E0.01733*99
|
||||
<< ok
|
||||
>> N52 G1 X113.810 Y92.516 E0.01745*97
|
||||
<< ok
|
||||
>> N53 G1 X114.334 Y92.328 E0.01745*97
|
||||
<< ok
|
||||
>> N54 G1 X114.885 Y92.248 E0.01745*96
|
||||
<< ok
|
||||
>> N55 M105*23
|
||||
<< ok T:214.9 /215.0 B:60.8 /60.0 T0:214.9 /215.0 @:30 B@:28 P:46.2 A:35.7
|
||||
>> N56 G1 X135.004 Y92.246 E0.63084*96
|
||||
<< ok
|
||||
>> N57 G1 X136.005 Y92.436 E0.03195*101
|
||||
<< ok
|
||||
>> N58 G1 X136.866 Y92.974 E0.03183*107
|
||||
<< ok
|
||||
>> N59 G1 X137.473 Y93.788 E0.03183*111
|
||||
<< ok
|
||||
>> N60 G1 X137.745 Y94.770 E0.03195*100
|
||||
<< ok
|
||||
>> N61 G1 X137.753 Y115.086 E0.63700*88
|
||||
<< ok
|
||||
>> N62 G1 X137.563 Y116.009 E0.02955*87
|
||||
<< ok
|
||||
>> N63 G1 X137.070 Y116.817 E0.02968*88
|
||||
<< ok
|
||||
>> N64 G1 X136.646 Y117.208 E0.01809*93
|
||||
<< ok
|
||||
>> N65 G1 X136.149 Y117.503 E0.01809*88
|
||||
<< ok
|
||||
>> N66 G1 X135.603 Y117.687 E0.01809*94
|
||||
<< ok
|
||||
>> N67 G1 X135.029 Y117.754 E0.01809*94
|
||||
<< ok
|
||||
>> N68 G1 X114.914 Y117.753 E0.63071*81
|
||||
<< ok
|
||||
>> N69 G1 X113.991 Y117.563 E0.02955*83
|
||||
<< ok
|
||||
>> N70 G1 X113.183 Y117.070 E0.02968*89
|
||||
<< ok
|
||||
>> N71 G1 X112.792 Y116.646 E0.01809*88
|
||||
<< ok
|
||||
>> N72 M105*18
|
||||
<< ok T:214.0 /215.0 B:60.8 /60.0 T0:214.0 /215.0 @:45 B@:16 P:46.2 A:35.5
|
||||
>> N73 G1 X112.497 Y116.149 E0.01809*84
|
||||
<< ok
|
||||
>> N74 G1 X112.313 Y115.603 E0.01809*82
|
||||
<< ok
|
||||
>> N75 G1 X112.246 Y115.029 E0.01809*92
|
||||
<< ok
|
||||
>> N76 G1 X112.247 Y94.914 E0.63071*98
|
||||
<< ok
|
||||
>> N77 G1 X112.425 Y94.050 E0.02767*111
|
||||
<< ok
|
||||
>> N78 G1 F8160*126
|
||||
<< ok
|
||||
>> N79 G1 X112.930 Y93.183 E-0.24526*78
|
||||
<< ok
|
||||
>> N80 G1 F8160*121
|
||||
<< ok
|
||||
>> N81 G1 X113.335 Y92.806 E-0.13510*67
|
||||
<< ok
|
||||
>> N82 G1 F8160*123
|
||||
<< ok
|
||||
>> N83 G1 X113.810 Y92.516 E-0.13609*74
|
||||
<< ok
|
||||
>> N84 G1 F8160*125
|
||||
<< ok
|
||||
>> N85 G1 X114.334 Y92.328 E-0.13609*77
|
||||
<< ok
|
||||
>> N86 G1 F8160*127
|
||||
<< ok
|
||||
>> N87 G1 X114.769 Y92.265 E-0.10746*66
|
||||
<< ok
|
||||
>> N88 G1 E-0.04000 F2100.00000*1
|
||||
<< ok
|
||||
>> N89 G1 Z0.800 F10200.000*14
|
||||
<< ok
|
||||
>> N90 G1 X113.989 Y92.849 F10200.000*110
|
||||
<< ok
|
||||
>> N91 G1 Z0.200 F10200.000*13
|
||||
<< ok
|
||||
>> N92 G1 E0.80000 F2100.00000*43
|
||||
<< ok
|
||||
>> N93 G1 F1800*125
|
||||
<< ok
|
||||
>> N94 G1 X114.911 Y92.625 E0.02977*99
|
||||
<< ok
|
||||
>> N95 G1 X135.004 Y92.623 E0.62999*108
|
||||
<< ok
|
||||
>> N96 G1 X135.871 Y92.788 E0.02767*108
|
||||
<< ok
|
||||
>> N97 G1 X136.617 Y93.258 E0.02767*105
|
||||
<< ok
|
||||
>> N98 G1 X137.141 Y93.968 E0.02767*107
|
||||
<< ok
|
||||
>> N99 G1 X137.371 Y94.824 E0.02778*107
|
||||
<< ok
|
||||
>> N100 G1 X137.376 Y115.065 E0.63464*97
|
||||
<< ok
|
||||
>> N101 G1 X137.209 Y115.878 E0.02602*104
|
||||
<< ok
|
||||
>> N102 G1 X136.773 Y116.584 E0.02602*111
|
||||
<< ok
|
||||
>> N103 G1 X136.407 Y116.916 E0.01550*110
|
||||
<< ok
|
||||
>> N104 G1 X135.980 Y117.166 E0.01550*102
|
||||
<< ok
|
||||
>> N105 G1 X135.511 Y117.321 E0.01550*98
|
||||
<< ok
|
||||
>> N106 G1 X135.020 Y117.377 E0.01550*101
|
||||
<< ok
|
||||
>> N107 G1 X114.935 Y117.376 E0.62975*101
|
||||
<< ok
|
||||
>> N108 G1 X114.122 Y117.209 E0.02602*100
|
||||
<< ok
|
||||
>> N109 G1 X113.416 Y116.773 E0.02602*105
|
||||
<< ok
|
||||
>> N110 G1 X113.084 Y116.407 E0.01550*105
|
||||
<< ok
|
||||
>> N111 G1 X112.834 Y115.980 E0.01550*107
|
||||
<< ok
|
||||
>> N112 G1 X112.679 Y115.511 E0.01550*107
|
||||
<< ok
|
||||
>> N113 G1 X112.623 Y115.020 E0.01550*98
|
||||
<< ok
|
||||
>> N114 G1 X112.624 Y94.935 E0.62975*89
|
||||
<< ok
|
||||
>> N115 G1 X112.791 Y94.122 E0.02602*80
|
||||
<< ok
|
||||
>> N116 G1 X113.227 Y93.416 E0.02602*95
|
||||
<< ok
|
||||
>> N117 G1 X113.940 Y92.885 E0.02789*81
|
||||
<< ok
|
||||
>> N118 G1 F8160*73
|
||||
<< ok
|
||||
>> N119 G1 X114.911 Y92.625 E-0.24574*113
|
||||
<< ok
|
||||
>> N120 G1 F8160*66
|
||||
<< ok
|
||||
>> N121 G1 X117.015 Y92.624 E-0.51426*113
|
||||
<< ok
|
||||
>> N122 G1 E-0.04000 F2100.00000*48
|
||||
<< ok
|
||||
>> N123 G1 Z0.800 F10200.000*63
|
||||
<< ok
|
||||
>> N124 G1 X115.587 Y95.587 F10200.000*92
|
||||
<< ok
|
||||
>> N125 M105*33
|
||||
<< ok T:214.2 /215.0 B:60.7 /60.0 T0:214.2 /215.0 @:41 B@:24 P:46.2 A:36.0
|
||||
>> N126 G1 Z0.200 F10200.000*48
|
||||
<< ok
|
||||
>> N127 G1 E0.80000 F2100.00000*20
|
||||
<< ok
|
||||
>> N128 G1 F1800*76
|
||||
<< ok
|
||||
>> N129 G1 X134.413 Y95.587 E0.59027*87
|
||||
<< ok
|
||||
>> N130 G1 X134.413 Y114.413 E0.59027*107
|
||||
<< ok
|
||||
>> N131 G1 X115.587 Y114.413 E0.59027*101
|
||||
<< ok
|
||||
>> N132 G1 X115.587 Y95.647 E0.58839*91
|
||||
<< ok
|
||||
>> N133 G1 X115.210 Y95.210 F10200.000*90
|
||||
<< ok
|
||||
>> N134 G1 F1800*65
|
||||
<< ok
|
||||
>> N135 G1 X134.790 Y95.210 E0.61392*93
|
||||
<< ok
|
||||
>> N136 G1 X134.790 Y114.790 E0.61392*107
|
||||
<< ok
|
||||
>> N137 G1 X115.210 Y114.790 E0.61392*100
|
||||
<< ok
|
||||
>> N138 G1 X115.210 Y95.270 E0.61204*86
|
||||
<< ok
|
||||
>> N139 G1 X115.596 Y95.314 F10200.000*92
|
||||
<< ok
|
||||
>> N140 G1 F8160*68
|
||||
<< ok
|
||||
>> N141 G1 X118.319 Y95.260 E-0.76000*113
|
||||
<< ok
|
||||
>> N142 G1 E-0.04000 F2100.00000*54
|
||||
<< ok
|
||||
>> N143 G1 Z0.800 F10200.000*57
|
||||
<< ok
|
||||
>> N144 G1 X115.700 Y113.527 F10200.000*98
|
||||
<< ok
|
||||
>> N145 G1 Z0.200 F10200.000*53
|
||||
<< ok
|
||||
>> N146 G1 E0.80000 F2100.00000*19
|
||||
<< ok
|
||||
>> N147 G1 F1800*69
|
||||
<< ok
|
||||
>> N148 G1 X116.303 Y114.130 E0.02708*98
|
||||
<< ok
|
||||
>> N149 G1 X116.843 Y114.130 E0.01716*96
|
||||
<< ok
|
||||
>> N150 G1 X115.870 Y113.157 E0.04372*110
|
||||
<< ok
|
||||
>> N151 G1 X115.870 Y112.617 E0.01716*110
|
||||
<< ok
|
||||
>> N152 G1 X117.383 Y114.130 E0.06799*108
|
||||
<< ok
|
||||
>> N153 G1 X117.924 Y114.130 E0.01716*106
|
||||
<< ok
|
||||
>> N154 M105*39
|
||||
<< ok T:215.1 /215.0 B:60.7 /60.0 T0:215.1 /215.0 @:29 B@:12 P:46.2 A:35.7
|
||||
>> N155 G1 X115.870 Y112.076 E0.09225*102
|
||||
<< ok
|
||||
>> N156 G1 X115.870 Y111.536 E0.01716*106
|
||||
<< ok
|
||||
>> N157 G1 X118.464 Y114.130 E0.11652*104
|
||||
<< ok
|
||||
>> N158 G1 X119.004 Y114.130 E0.01716*100
|
||||
<< ok
|
||||
>> N159 G1 X115.870 Y110.996 E0.14079*104
|
||||
<< ok
|
||||
>> N160 G1 X115.870 Y110.456 E0.01716*105
|
||||
<< ok
|
||||
>> N161 G1 X119.544 Y114.130 E0.16505*105
|
||||
<< ok
|
||||
>> N162 G1 X120.085 Y114.130 E0.01716*110
|
||||
<< ok
|
||||
>> N163 G1 X115.870 Y109.915 E0.18932*104
|
||||
<< ok
|
||||
>> N164 G1 X115.870 Y109.375 E0.01716*99
|
||||
<< ok
|
||||
>> N165 G1 X120.625 Y114.130 E0.21358*105
|
||||
<< ok
|
||||
>> N166 G1 X121.165 Y114.130 E0.01716*100
|
||||
<< ok
|
||||
>> N167 G1 X115.870 Y108.835 E0.23785*100
|
||||
<< ok
|
||||
>> N168 G1 X115.870 Y108.295 E0.01716*97
|
||||
<< ok
|
||||
>> N169 G1 X121.705 Y114.130 E0.26212*111
|
||||
<< ok
|
||||
>> N170 G1 X122.245 Y114.130 E0.01716*97
|
||||
<< ok
|
||||
>> N171 G1 X115.870 Y107.755 E0.28638*105
|
||||
<< ok
|
||||
>> N172 G1 X115.870 Y107.214 E0.01716*108
|
||||
<< ok
|
||||
>> N173 G1 X122.786 Y114.130 E0.31065*104
|
||||
<< ok
|
||||
>> N174 G1 X123.326 Y114.130 E0.01716*96
|
||||
<< ok
|
||||
>> N175 G1 X115.870 Y106.674 E0.33491*101
|
||||
<< ok
|
||||
>> N176 G1 X115.870 Y106.134 E0.01716*104
|
||||
<< ok
|
||||
>> N177 G1 X123.866 Y114.130 E0.35918*107
|
||||
<< ok
|
||||
>> N178 G1 X124.406 Y114.130 E0.01716*110
|
||||
<< ok
|
||||
>> N179 G1 X115.870 Y105.594 E0.38344*99
|
||||
<< ok
|
||||
>> N180 G1 X115.870 Y105.054 E0.01716*101
|
||||
<< ok
|
||||
>> N181 G1 X124.946 Y114.130 E0.40771*101
|
||||
<< ok
|
||||
>> N182 G1 X125.487 Y114.130 E0.01716*99
|
||||
<< ok
|
||||
>> N183 G1 X115.870 Y104.513 E0.43198*103
|
||||
<< ok
|
||||
>> N184 G1 X115.870 Y103.973 E0.01716*107
|
||||
<< ok
|
||||
>> N185 G1 X126.027 Y114.130 E0.45624*105
|
||||
<< ok
|
||||
>> N186 G1 X126.567 Y114.130 E0.01716*107
|
||||
<< ok
|
||||
>> N187 G1 X115.870 Y103.433 E0.48051*104
|
||||
<< ok
|
||||
>> N188 G1 X115.870 Y102.893 E0.01716*105
|
||||
<< ok
|
||||
>> N189 G1 X127.107 Y114.130 E0.50477*103
|
||||
<< ok
|
||||
>> N190 M105*47
|
||||
<< ok T:215.3 /215.0 B:60.7 /60.0 T0:215.3 /215.0 @:28 B@:18 P:46.3 A:35.5
|
||||
>> N191 G1 X127.648 Y114.130 E0.01716*98
|
||||
<< ok
|
||||
>> N192 G1 X115.870 Y102.352 E0.52904*111
|
||||
<< ok
|
||||
>> N193 G1 X115.870 Y101.812 E0.01716*105
|
||||
<< ok
|
||||
>> N194 G1 X128.188 Y114.130 E0.55330*98
|
||||
<< ok
|
||||
>> N195 G1 X128.728 Y114.130 E0.01716*110
|
||||
<< ok
|
||||
>> N196 G1 X115.870 Y101.272 E0.57757*102
|
||||
<< ok
|
||||
>> N197 G1 X115.870 Y100.732 E0.01716*97
|
||||
<< ok
|
||||
>> N198 G1 X129.268 Y114.130 E0.60184*105
|
||||
<< ok
|
||||
>> N199 G1 X129.808 Y114.130 E0.01716*110
|
||||
<< ok
|
||||
>> N200 G1 X115.870 Y100.192 E0.62610*98
|
||||
<< ok
|
||||
>> N201 G1 X115.870 Y99.651 E0.01716*88
|
||||
<< ok
|
||||
>> N202 G1 X130.349 Y114.130 E0.65037*111
|
||||
<< ok
|
||||
>> N203 G1 X130.889 Y114.130 E0.01716*111
|
||||
<< ok
|
||||
>> N204 G1 X115.870 Y99.111 E0.67463*95
|
||||
<< ok
|
||||
>> N205 G1 X115.870 Y98.571 E0.01716*92
|
||||
<< ok
|
||||
>> N206 G1 X131.429 Y114.130 E0.69890*98
|
||||
<< ok
|
||||
>> N207 G1 X131.969 Y114.130 E0.01716*101
|
||||
<< ok
|
||||
>> N208 M105*45
|
||||
<< ok T:214.7 /215.0 B:60.6 /60.0 T0:214.7 /215.0 @:38 B@:12 P:46.2 A:35.9
|
||||
>> N209 G1 X115.870 Y98.031 E0.72316*81
|
||||
<< ok
|
||||
>> N210 G1 X115.870 Y97.491 E0.01716*88
|
||||
<< ok
|
||||
>> N211 G1 X132.509 Y114.130 E0.74743*105
|
||||
<< ok
|
||||
>> N212 G1 X133.050 Y114.130 E0.01716*96
|
||||
<< ok
|
||||
>> N213 M105*39
|
||||
<< ok T:215.1 /215.0 B:60.4 /60.0 T0:215.1 /215.0 @:32 B@:39 P:46.3 A:35.8
|
||||
>> N214 M105*32
|
||||
<< ok T:214.6 /215.0 B:60.5 /60.0 T0:214.6 /215.0 @:39 B@:14 P:46.3 A:36.3
|
||||
>> N215 G28*21
|
||||
<< echo:busy: processing
|
||||
<< 0 step=61 mscnt= 989
|
||||
<< tmc2130_goto_step 0 0 2 1000
|
||||
<< step 61 mscnt = 984
|
||||
<< dir=0 steps=-61
|
||||
<< dir=1 steps=61
|
||||
<< dir=0 steps=3
|
||||
<< cnt 2 step 61 mscnt = 986
|
||||
<< cnt 1 step 62 mscnt = 1004
|
||||
<< cnt 0 step 63 mscnt = 1021
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< 0 step=34 mscnt= 547
|
||||
<< tmc2130_goto_step 1 0 2 1000
|
||||
<< step 34 mscnt = 552
|
||||
<< dir=1 steps=-34
|
||||
<< dir=0 steps=34
|
||||
<< dir=1 steps=30
|
||||
<< cnt 29 step 34 mscnt = 554
|
||||
<< cnt 28 step 35 mscnt = 573
|
||||
<< cnt 27 step 36 mscnt = 589
|
||||
<< cnt 26 step 37 mscnt = 604
|
||||
<< cnt 25 step 38 mscnt = 621
|
||||
<< cnt 24 step 39 mscnt = 636
|
||||
<< cnt 23 step 40 mscnt = 652
|
||||
<< cnt 22 step 41 mscnt = 668
|
||||
<< cnt 21 step 42 mscnt = 684
|
||||
<< cnt 20 step 43 mscnt = 701
|
||||
<< cnt 19 step 44 mscnt = 717
|
||||
<< cnt 18 step 45 mscnt = 733
|
||||
<< cnt 17 step 46 mscnt = 749
|
||||
<< cnt 16 step 47 mscnt = 765
|
||||
<< cnt 15 step 48 mscnt = 781
|
||||
<< cnt 14 step 49 mscnt = 796
|
||||
<< cnt 13 step 50 mscnt = 812
|
||||
<< cnt 12 step 51 mscnt = 829
|
||||
<< cnt 11 step 52 mscnt = 844
|
||||
<< cnt 10 step 53 mscnt = 860
|
||||
<< cnt 9 step 54 mscnt = 876
|
||||
<< cnt 8 step 55 mscnt = 893
|
||||
<< cnt 7 step 56 mscnt = 909
|
||||
<< cnt 6 step 57 mscnt = 925
|
||||
<< cnt 5 step 58 mscnt = 941
|
||||
<< cnt 4 step 59 mscnt = 957
|
||||
<< cnt 3 step 60 mscnt = 973
|
||||
<< cnt 2 step 61 mscnt = 989
|
||||
<< cnt 1 step 62 mscnt = 1005
|
||||
<< cnt 0 step 63 mscnt = 1021
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< echo:busy: processing
|
||||
<< ok
|
||||
>> N216 M105*34
|
||||
<< ok T:214.8 /215.0 B:60.2 /60.0 T0:214.8 /215.0 @:33 B@:27 P:46.0 A:36.1
|
||||
>> N217 M105*35
|
||||
<< ok T:214.8 /215.0 B:60.2 /60.0 T0:214.8 /215.0 @:33 B@:27 P:46.0 A:36.1
|
||||
>> N218 M105*44
|
||||
<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1
|
||||
>> N219 M105*45
|
||||
<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1
|
||||
>> N220 M105*39
|
||||
<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1
|
||||
>> N221 M105*38
|
||||
<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1
|
||||
>> N222 M105*37
|
||||
<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1
|
||||
>> N223 M105*36
|
||||
<< ok T:214.5 /215.0 B:60.1 /60.0 T0:214.5 /215.0 @:38 B@:31 P:46.0 A:36.3
|
||||
DISCONNECTED
|
||||
21
slic3r.pl
21
slic3r.pl
|
|
@ -38,11 +38,9 @@ my %cli_options = ();
|
|||
'load=s@' => \$opt{load},
|
||||
'autosave=s' => \$opt{autosave},
|
||||
'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config},
|
||||
'no-controller' => \$opt{no_controller},
|
||||
'no-plater' => \$opt{no_plater},
|
||||
'gui-mode=s' => \$opt{obsolete_ignore_this_option_gui_mode},
|
||||
'datadir=s' => \$opt{datadir},
|
||||
'export-svg' => \$opt{export_svg},
|
||||
'export-png' => \$opt{export_png},
|
||||
'merge|m' => \$opt{merge},
|
||||
'repair' => \$opt{repair},
|
||||
|
|
@ -108,10 +106,10 @@ if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3
|
|||
{
|
||||
no warnings 'once';
|
||||
$Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // '');
|
||||
$Slic3r::GUI::no_controller = $opt{no_controller};
|
||||
$Slic3r::GUI::no_plater = $opt{no_plater};
|
||||
$Slic3r::GUI::autosave = $opt{autosave};
|
||||
}
|
||||
Slic3r::GUI::set_gui_appctl();
|
||||
$gui = Slic3r::GUI->new;
|
||||
#setlocale(LC_NUMERIC, 'C');
|
||||
$gui->{mainframe}->load_config_file($_) for @{$opt{load}};
|
||||
|
|
@ -124,6 +122,9 @@ if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3
|
|||
die $@ if $@ && $opt{gui};
|
||||
|
||||
if (@ARGV) { # slicing from command line
|
||||
Slic3r::GUI::set_cli_appctl();
|
||||
my $appctl = Slic3r::AppController->new();
|
||||
|
||||
$config->validate;
|
||||
|
||||
if ($opt{repair}) {
|
||||
|
|
@ -203,10 +204,6 @@ if (@ARGV) { # slicing from command line
|
|||
duplicate_grid => $opt{duplicate_grid} // [1,1],
|
||||
print_center => $opt{print_center} // Slic3r::Pointf->new(100,100),
|
||||
dont_arrange => $opt{dont_arrange} // 0,
|
||||
status_cb => sub {
|
||||
my ($percent, $message) = @_;
|
||||
printf "=> %s\n", $message;
|
||||
},
|
||||
output_file => $opt{output},
|
||||
);
|
||||
|
||||
|
|
@ -216,10 +213,11 @@ if (@ARGV) { # slicing from command line
|
|||
# Do the apply_config once again to validate the layer height profiles at all the newly added PrintObjects.
|
||||
$sprint->apply_config($config);
|
||||
|
||||
if ($opt{export_svg}) {
|
||||
$sprint->export_svg;
|
||||
} elsif ($opt{export_png}) {
|
||||
$sprint->export_png;
|
||||
if ($opt{export_png}) {
|
||||
# $sprint->export_png;
|
||||
$appctl->set_model($model);
|
||||
$appctl->set_print($sprint->_print);
|
||||
$appctl->print_ctl()->slice_to_png();
|
||||
} else {
|
||||
my $t0 = [gettimeofday];
|
||||
# The following call may die if the output_filename_format template substitution fails,
|
||||
|
|
@ -284,7 +282,6 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
|
|||
and [input_filename] (default: $config->{output_filename_format})
|
||||
--post-process Generated G-code will be processed with the supplied script;
|
||||
call this more than once to process through multiple scripts.
|
||||
--export-svg Export a SVG file containing slices instead of G-code.
|
||||
--export-png Export zipped PNG files containing slices instead of G-code.
|
||||
-m, --merge If multiple files are supplied, they will be composed into a single
|
||||
print rather than processed individually.
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ const std::string USAGE_STR = {
|
|||
"Usage: slabasebed stlfilename.stl"
|
||||
};
|
||||
|
||||
void confess_at(const char * /*file*/,
|
||||
int /*line*/,
|
||||
const char * /*func*/,
|
||||
const char * /*pat*/,
|
||||
...) {}
|
||||
|
||||
int main(const int argc, const char *argv[]) {
|
||||
using namespace Slic3r;
|
||||
using std::cout; using std::endl;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
using namespace Slic3r;
|
||||
|
||||
void confess_at(const char *file, int line, const char *func, const char *pat, ...){}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
|
|
|
|||
37
t/svg.t
37
t/svg.t
|
|
@ -1,37 +0,0 @@
|
|||
use Test::More tests => 2;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
use IO::Scalar;
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
eval {
|
||||
my $fh = IO::Scalar->new(\my $gcode);
|
||||
$print->print->export_svg(output_fh => $fh, quiet => 1);
|
||||
$fh->close;
|
||||
};
|
||||
die $@ if $@;
|
||||
ok !$@, 'successful SVG export';
|
||||
}
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('two_hollow_squares');
|
||||
eval {
|
||||
my $fh = IO::Scalar->new(\my $gcode);
|
||||
$print->print->export_svg(output_fh => $fh, quiet => 1);
|
||||
$fh->close;
|
||||
};
|
||||
die $@ if $@;
|
||||
ok !$@, 'successful SVG export of object with two islands';
|
||||
}
|
||||
|
||||
__END__
|
||||
35
t/threads.t
35
t/threads.t
|
|
@ -1,35 +0,0 @@
|
|||
use Test::More tests => 2;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
{
|
||||
my $thread = threads->create(sub { Slic3r::thread_cleanup(); return 1; });
|
||||
ok $thread->join, "print survives thread spawning";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $thread = threads->create(sub {
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube');
|
||||
Slic3r::Test::gcode($print);
|
||||
}
|
||||
Slic3r::thread_cleanup();
|
||||
return 1;
|
||||
});
|
||||
ok $thread->join, "process print in a separate thread";
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# This script displays 3D preview of a mesh
|
||||
|
||||
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 Slic3r;
|
||||
use Slic3r::GUI;
|
||||
use Slic3r::GUI::3DScene;
|
||||
$|++;
|
||||
|
||||
my %opt = ();
|
||||
{
|
||||
my %options = (
|
||||
'help' => sub { usage() },
|
||||
'cut=f' => \$opt{cut},
|
||||
'enable-moving' => \$opt{enable_moving},
|
||||
);
|
||||
GetOptions(%options) or usage(1);
|
||||
$ARGV[0] or usage(1);
|
||||
}
|
||||
|
||||
{
|
||||
my $model = Slic3r::Model->read_from_file($ARGV[0]);
|
||||
$_->center_around_origin for @{$model->objects}; # and align to Z = 0
|
||||
|
||||
my $app = Slic3r::ViewMesh->new;
|
||||
$app->{canvas}->enable_picking(1);
|
||||
$app->{canvas}->enable_moving($opt{enable_moving});
|
||||
$app->{canvas}->load_object($model, 0);
|
||||
$app->{canvas}->set_auto_bed_shape;
|
||||
$app->{canvas}->zoom_to_volumes;
|
||||
$app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut};
|
||||
$app->MainLoop;
|
||||
}
|
||||
|
||||
|
||||
sub usage {
|
||||
my ($exit_code) = @_;
|
||||
|
||||
print <<"EOF";
|
||||
Usage: view-mesh.pl [ OPTIONS ] file.stl
|
||||
|
||||
--help Output this usage screen and exit
|
||||
--cut Z Display the cutting plane at the given Z
|
||||
|
||||
EOF
|
||||
exit ($exit_code || 0);
|
||||
}
|
||||
|
||||
package Slic3r::ViewMesh;
|
||||
use Wx qw(:sizer);
|
||||
use base qw(Wx::App);
|
||||
|
||||
sub OnInit {
|
||||
my $self = shift;
|
||||
|
||||
my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]);
|
||||
my $panel = Wx::Panel->new($frame, -1);
|
||||
|
||||
$self->{canvas} = Slic3r::GUI::3DScene->new($panel);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($self->{canvas}, 1, wxEXPAND, 0);
|
||||
$panel->SetSizer($sizer);
|
||||
$sizer->SetSizeHints($panel);
|
||||
|
||||
$frame->Show(1);
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# This script displays 3D preview of a mesh
|
||||
|
||||
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 Slic3r;
|
||||
use Slic3r::GUI;
|
||||
use Slic3r::GUI::3DScene;
|
||||
$|++;
|
||||
|
||||
my %opt = ();
|
||||
{
|
||||
my %options = (
|
||||
'help' => sub { usage() },
|
||||
'load=s' => \$opt{load},
|
||||
'3D' => \$opt{d3},
|
||||
'duplicate=i' => \$opt{duplicate},
|
||||
);
|
||||
GetOptions(%options) or usage(1);
|
||||
$ARGV[0] or usage(1);
|
||||
}
|
||||
|
||||
{
|
||||
# load model
|
||||
my $model = Slic3r::Model->read_from_file($ARGV[0]);
|
||||
|
||||
# load config
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
if ($opt{load}) {
|
||||
$config->apply(Slic3r::Config::load($opt{load}));
|
||||
}
|
||||
|
||||
# init print
|
||||
my $sprint = Slic3r::Print::Simple->new;
|
||||
$sprint->duplicate($opt{duplicate} // 1);
|
||||
$sprint->apply_config($config);
|
||||
$sprint->set_model($model);
|
||||
$sprint->process;
|
||||
|
||||
# visualize toolpaths
|
||||
$Slic3r::ViewToolpaths::print = $sprint->_print;
|
||||
$Slic3r::ViewToolpaths::d3 = $opt{d3};
|
||||
my $app = Slic3r::ViewToolpaths->new;
|
||||
$app->MainLoop;
|
||||
}
|
||||
|
||||
|
||||
sub usage {
|
||||
my ($exit_code) = @_;
|
||||
|
||||
print <<"EOF";
|
||||
Usage: view-toolpaths.pl [ OPTIONS ] file.stl
|
||||
|
||||
--help Output this usage screen and exit
|
||||
--load CONFIG Loads the supplied config file
|
||||
|
||||
EOF
|
||||
exit ($exit_code || 0);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::ViewToolpaths;
|
||||
use Wx qw(:sizer);
|
||||
use base qw(Wx::App Class::Accessor);
|
||||
|
||||
our $print;
|
||||
our $d3;
|
||||
|
||||
sub OnInit {
|
||||
my $self = shift;
|
||||
|
||||
my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]);
|
||||
my $panel = Wx::Panel->new($frame, -1);
|
||||
|
||||
my $canvas;
|
||||
if ($d3) {
|
||||
$canvas = Slic3r::GUI::3DScene->new($panel);
|
||||
$canvas->set_bed_shape($print->config->bed_shape);
|
||||
$canvas->load_print_toolpaths($print);
|
||||
|
||||
foreach my $object (@{$print->objects}) {
|
||||
#$canvas->load_print_object_slices($object);
|
||||
$canvas->load_print_object_toolpaths($object);
|
||||
#$canvas->load_object($object->model_object);
|
||||
}
|
||||
$canvas->zoom_to_volumes;
|
||||
} else {
|
||||
$canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print);
|
||||
}
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($canvas, 1, wxEXPAND, 0);
|
||||
$panel->SetSizer($sizer);
|
||||
|
||||
$frame->Show(1);
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# This script exports experimental G-code for wireframe printing
|
||||
# (inspired by the brilliant WirePrint concept)
|
||||
|
||||
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 Slic3r;
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Geometry qw(scale unscale X Y PI);
|
||||
|
||||
my %opt = (
|
||||
step_height => 5,
|
||||
nozzle_angle => 30,
|
||||
nozzle_width => 10,
|
||||
first_layer_height => 0.3,
|
||||
);
|
||||
{
|
||||
my %options = (
|
||||
'help' => sub { usage() },
|
||||
'output|o=s' => \$opt{output_file},
|
||||
'step-height|h=f' => \$opt{step_height},
|
||||
'nozzle-angle|a=f' => \$opt{nozzle_angle},
|
||||
'nozzle-width|w=f' => \$opt{nozzle_width},
|
||||
'first-layer-height=f' => \$opt{first_layer_height},
|
||||
);
|
||||
GetOptions(%options) or usage(1);
|
||||
$opt{output_file} or usage(1);
|
||||
$ARGV[0] or usage(1);
|
||||
}
|
||||
|
||||
{
|
||||
# load model
|
||||
my $model = Slic3r::Model->read_from_file($ARGV[0]);
|
||||
$model->center_instances_around_point(Slic3r::Pointf->new(100,100));
|
||||
my $mesh = $model->mesh;
|
||||
$mesh->translate(0, 0, -$mesh->bounding_box->z_min);
|
||||
|
||||
# get slices
|
||||
my @z = ();
|
||||
my $z_max = $mesh->bounding_box->z_max;
|
||||
for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) {
|
||||
push @z, $z;
|
||||
}
|
||||
my @slices = @{$mesh->slice(\@z)};
|
||||
|
||||
my $flow = Slic3r::Flow->new(
|
||||
width => 0.35,
|
||||
height => 0.35,
|
||||
nozzle_diameter => 0.35,
|
||||
bridge => 1,
|
||||
);
|
||||
|
||||
my $config = Slic3r::Config::Print->new;
|
||||
$config->set('gcode_comments', 1);
|
||||
|
||||
open my $fh, '>', $opt{output_file};
|
||||
my $gcodegen = Slic3r::GCode->new(
|
||||
enable_loop_clipping => 0, # better bonding
|
||||
);
|
||||
$gcodegen->apply_print_config($config);
|
||||
$gcodegen->set_extruders([0]);
|
||||
print $fh $gcodegen->set_extruder(0);
|
||||
print $fh $gcodegen->writer->preamble;
|
||||
|
||||
my $e = $gcodegen->writer->extruder->e_per_mm3 * $flow->mm3_per_mm;
|
||||
|
||||
foreach my $layer_id (0..$#z) {
|
||||
my $z = $z[$layer_id];
|
||||
|
||||
foreach my $island (@{$slices[$layer_id]}) {
|
||||
foreach my $polygon (@$island) {
|
||||
if ($layer_id > 0) {
|
||||
# find the lower polygon that we want to connect to this one
|
||||
my $lower = $slices[$layer_id-1]->[0]->contour; # 't was easy, wasn't it?
|
||||
my $lower_z = $z[$layer_id-1];
|
||||
|
||||
{
|
||||
my @points = ();
|
||||
|
||||
# keep all points with strong angles
|
||||
{
|
||||
my @pp = @$polygon;
|
||||
foreach my $i (0..$#pp) {
|
||||
push @points, $pp[$i-1] if abs($pp[$i-1]->ccw_angle($pp[$i-2], $pp[$i]) - PI) > PI/3;
|
||||
}
|
||||
}
|
||||
|
||||
$polygon = Slic3r::Polygon->new(@points);
|
||||
}
|
||||
#$polygon = Slic3r::Polygon->new(@{$polygon->split_at_first_point->equally_spaced_points(scale $opt{nozzle_width})});
|
||||
|
||||
# find vertical lines
|
||||
my @vertical = ();
|
||||
foreach my $point (@{$polygon}) {
|
||||
push @vertical, Slic3r::Line->new($point->projection_onto_polygon($lower), $point);
|
||||
}
|
||||
|
||||
next if !@vertical;
|
||||
|
||||
my @points = ();
|
||||
foreach my $line (@vertical) {
|
||||
push @points, Slic3r::Pointf3->new(
|
||||
unscale($line->a->x),
|
||||
unscale($line->a->y), #))
|
||||
$lower_z,
|
||||
);
|
||||
push @points, Slic3r::Pointf3->new(
|
||||
unscale($line->b->x),
|
||||
unscale($line->b->y), #))
|
||||
$z,
|
||||
);
|
||||
}
|
||||
|
||||
# reappend first point as destination of the last diagonal segment
|
||||
push @points, Slic3r::Pointf3->new(
|
||||
unscale($vertical[0]->a->x),
|
||||
unscale($vertical[0]->a->y), #))
|
||||
$lower_z,
|
||||
);
|
||||
|
||||
# move to the position of the first vertical line
|
||||
print $fh $gcodegen->writer->travel_to_xyz(shift @points);
|
||||
|
||||
# extrude segments
|
||||
foreach my $point (@points) {
|
||||
print $fh $gcodegen->writer->extrude_to_xyz($point, $e * $gcodegen->writer->get_position->distance_to($point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print $fh $gcodegen->writer->travel_to_z($z);
|
||||
foreach my $polygon (@$island) {
|
||||
#my $polyline = $polygon->split_at_vertex(Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]));
|
||||
my $polyline = $polygon->split_at_first_point;
|
||||
print $fh $gcodegen->writer->travel_to_xy(Slic3r::Pointf->new_unscale(@{ $polyline->first_point }), "move to first contour point");
|
||||
|
||||
foreach my $line (@{$polyline->lines}) {
|
||||
my $point = Slic3r::Pointf->new_unscale(@{ $line->b });
|
||||
print $fh $gcodegen->writer->extrude_to_xy($point, $e * unscale($line->length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close $fh;
|
||||
}
|
||||
|
||||
sub usage {
|
||||
my ($exit_code) = @_;
|
||||
|
||||
print <<"EOF";
|
||||
Usage: wireframe.pl [ OPTIONS ] file.stl
|
||||
|
||||
--help Output this usage screen and exit
|
||||
--output, -o Write to the specified file
|
||||
--step-height, -h Use the specified step height
|
||||
--nozzle-angle, -a Max nozzle angle
|
||||
--nozzle-width, -w External nozzle diameter
|
||||
|
||||
EOF
|
||||
exit ($exit_code || 0);
|
||||
}
|
||||
|
||||
__END__
|
||||
|
|
@ -2,16 +2,12 @@
|
|||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Enable C11 language standard.
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# Add our own cmake module path.
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall" )
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-reorder" )
|
||||
find_package(PkgConfig REQUIRED)
|
||||
endif()
|
||||
|
||||
|
|
@ -20,6 +16,12 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX)
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals" )
|
||||
endif()
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
# On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=return-type" )
|
||||
endif()
|
||||
|
||||
|
||||
# Where all the bundled libraries reside?
|
||||
set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/)
|
||||
# For the bundled boost libraries (boost::nowide)
|
||||
|
|
@ -105,6 +107,8 @@ add_library(libslic3r STATIC
|
|||
${LIBDIR}/libslic3r/GCode/Analyzer.hpp
|
||||
${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp
|
||||
${LIBDIR}/libslic3r/GCode/CoolingBuffer.hpp
|
||||
${LIBDIR}/libslic3r/GCode/PostProcessor.cpp
|
||||
${LIBDIR}/libslic3r/GCode/PostProcessor.hpp
|
||||
${LIBDIR}/libslic3r/GCode/PressureEqualizer.cpp
|
||||
${LIBDIR}/libslic3r/GCode/PressureEqualizer.hpp
|
||||
${LIBDIR}/libslic3r/GCode/PreviewData.cpp
|
||||
|
|
@ -192,6 +196,8 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/GUI/AboutDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/AppConfig.cpp
|
||||
${LIBDIR}/slic3r/GUI/AppConfig.hpp
|
||||
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp
|
||||
${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp
|
||||
${LIBDIR}/slic3r/GUI/BitmapCache.cpp
|
||||
${LIBDIR}/slic3r/GUI/BitmapCache.hpp
|
||||
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp
|
||||
|
|
@ -222,6 +228,10 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/GUI/GUI.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Preview.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_Preview.hpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_PreviewIface.cpp
|
||||
${LIBDIR}/slic3r/GUI/GUI_PreviewIface.hpp
|
||||
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/Tab.cpp
|
||||
|
|
@ -262,6 +272,7 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp
|
||||
${LIBDIR}/slic3r/GUI/FirmwareDialog.cpp
|
||||
${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressIndicator.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressStatusBar.hpp
|
||||
${LIBDIR}/slic3r/GUI/ProgressStatusBar.cpp
|
||||
${LIBDIR}/slic3r/Utils/Http.cpp
|
||||
|
|
@ -284,7 +295,6 @@ add_library(libslic3r_gui STATIC
|
|||
${LIBDIR}/slic3r/Utils/Time.hpp
|
||||
${LIBDIR}/slic3r/Utils/HexFile.cpp
|
||||
${LIBDIR}/slic3r/Utils/HexFile.hpp
|
||||
${LIBDIR}/slic3r/ProgressIndicator.hpp
|
||||
${LIBDIR}/slic3r/AppController.hpp
|
||||
${LIBDIR}/slic3r/AppController.cpp
|
||||
${LIBDIR}/slic3r/AppControllerWx.cpp
|
||||
|
|
@ -459,9 +469,11 @@ set(XS_XSP_FILES
|
|||
${XSP_DIR}/Geometry.xsp
|
||||
${XSP_DIR}/GUI.xsp
|
||||
${XSP_DIR}/GUI_AppConfig.xsp
|
||||
${XSP_DIR}/GUI_BackgroundSlicingProcess.xsp
|
||||
${XSP_DIR}/GUI_3DScene.xsp
|
||||
${XSP_DIR}/GUI_Preset.xsp
|
||||
${XSP_DIR}/GUI_Tab.xsp
|
||||
${XSP_DIR}/GUI_Preview.xsp
|
||||
${XSP_DIR}/Layer.xsp
|
||||
${XSP_DIR}/Line.xsp
|
||||
${XSP_DIR}/Model.xsp
|
||||
|
|
@ -513,6 +525,8 @@ add_library(XS ${XS_SHARED_LIBRARY_TYPE}
|
|||
${LIBDIR}/libslic3r/utils.cpp
|
||||
${LIBDIR}/slic3r/GUI/wxPerlIface.cpp
|
||||
${LIBDIR}/perlglue.cpp
|
||||
${LIBDIR}/callback.cpp
|
||||
${LIBDIR}/callback.hpp
|
||||
${LIBDIR}/ppport.h
|
||||
${LIBDIR}/xsinit.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/xsp/my.map
|
||||
|
|
@ -600,12 +614,12 @@ if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;")
|
|||
message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
|
||||
set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32")
|
||||
set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32")
|
||||
set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT")
|
||||
endif()
|
||||
# The following line will add -fPIC on Linux to make the XS.so rellocable.
|
||||
add_definitions(${PerlEmbed_CCCDLFLAGS})
|
||||
|
|
|
|||
|
|
@ -231,23 +231,16 @@ sub new {
|
|||
);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::_3DScene::GLShader;
|
||||
sub CLONE_SKIP { 1 }
|
||||
|
||||
package Slic3r::GUI::_3DScene::GLVolume::Collection;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
sub CLONE_SKIP { 1 }
|
||||
|
||||
package Slic3r::GUI::PresetCollection;
|
||||
use overload
|
||||
'@{}' => sub { $_[0]->arrayref },
|
||||
'fallback' => 1;
|
||||
|
||||
sub CLONE_SKIP { 1 }
|
||||
|
||||
package main;
|
||||
for my $class (qw(
|
||||
Slic3r::BridgeDetector
|
||||
|
|
|
|||
155
xs/src/callback.cpp
Normal file
155
xs/src/callback.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include "callback.hpp"
|
||||
|
||||
#include <xsinit.h>
|
||||
|
||||
void PerlCallback::register_callback(void *sv)
|
||||
{
|
||||
if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV)
|
||||
croak("Not a Callback %_ for PerlFunction", (SV*)sv);
|
||||
if (m_callback)
|
||||
SvSetSV((SV*)m_callback, (SV*)sv);
|
||||
else
|
||||
m_callback = newSVsv((SV*)sv);
|
||||
}
|
||||
|
||||
void PerlCallback::deregister_callback()
|
||||
{
|
||||
if (m_callback) {
|
||||
sv_2mortal((SV*)m_callback);
|
||||
m_callback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PerlCallback::call() const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(int i) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(int i, int j) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
XPUSHs(sv_2mortal(newSViv(j)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(const std::vector<int>& ints) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
for (int i : ints)
|
||||
{
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
}
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b, double c) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
XPUSHs(sv_2mortal(newSVnv(c)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b, double c, double d) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
XPUSHs(sv_2mortal(newSVnv(c)));
|
||||
XPUSHs(sv_2mortal(newSVnv(d)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(bool b) const
|
||||
{
|
||||
call(b ? 1 : 0);
|
||||
}
|
||||
32
xs/src/callback.hpp
Normal file
32
xs/src/callback.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef slic3r_PerlCallback_hpp_
|
||||
#define slic3r_PerlCallback_hpp_
|
||||
|
||||
#include <locale>
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PerlCallback {
|
||||
public:
|
||||
PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); }
|
||||
PerlCallback() : m_callback(nullptr) {}
|
||||
~PerlCallback() { this->deregister_callback(); }
|
||||
void register_callback(void *sv);
|
||||
void deregister_callback();
|
||||
void call() const;
|
||||
void call(int i) const;
|
||||
void call(int i, int j) const;
|
||||
void call(const std::vector<int>& ints) const;
|
||||
void call(double a) const;
|
||||
void call(double a, double b) const;
|
||||
void call(double a, double b, double c) const;
|
||||
void call(double a, double b, double c, double d) const;
|
||||
void call(bool b) const;
|
||||
private:
|
||||
void *m_callback;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PerlCallback_hpp_ */
|
||||
|
|
@ -21,7 +21,7 @@ public:
|
|||
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (points.empty())
|
||||
CONFESS("Empty point set supplied to BoundingBoxBase constructor");
|
||||
throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
|
|
@ -65,7 +65,7 @@ public:
|
|||
BoundingBox3Base(const std::vector<PointClass>& points)
|
||||
{
|
||||
if (points.empty())
|
||||
CONFESS("Empty point set supplied to BoundingBox3Base constructor");
|
||||
throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
this->max = *it;
|
||||
|
|
|
|||
|
|
@ -437,7 +437,8 @@ ExPolygon::triangulate_pp(Polygons* polygons) const
|
|||
// perform triangulation
|
||||
std::list<TPPLPoly> output;
|
||||
int res = TPPLPartition().Triangulate_MONO(&input, &output);
|
||||
if (res != 1) CONFESS("Triangulation failed");
|
||||
if (res != 1)
|
||||
throw std::runtime_error("Triangulation failed");
|
||||
|
||||
// convert output polygons
|
||||
for (std::list<TPPLPoly>::iterator poly = output.begin(); poly != output.end(); ++poly) {
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ public:
|
|||
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {}
|
||||
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = rhs.polyline; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = std::move(rhs.polyline); return *this; }
|
||||
ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = rhs.polyline; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = std::move(rhs.polyline); return *this; }
|
||||
|
||||
ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
|
||||
void reverse() { this->polyline.reverse(); }
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public:
|
|||
|
||||
// Following methods shall never be called on an ExtrusionEntityCollection.
|
||||
Polyline as_polyline() const {
|
||||
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
return Polyline();
|
||||
};
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ public:
|
|||
}
|
||||
|
||||
double length() const override {
|
||||
CONFESS("Calling length() on a ExtrusionEntityCollection");
|
||||
throw std::runtime_error("Calling length() on a ExtrusionEntityCollection");
|
||||
return 0.;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
{
|
||||
// Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id;
|
||||
|
||||
double fill_density = layerm.region()->config.fill_density;
|
||||
double fill_density = layerm.region()->config().fill_density;
|
||||
Flow infill_flow = layerm.flow(frInfill);
|
||||
Flow solid_infill_flow = layerm.flow(frSolidInfill);
|
||||
Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill);
|
||||
|
|
@ -69,7 +69,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) {
|
||||
group_attrib[i].is_solid = true;
|
||||
group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width;
|
||||
group_attrib[i].pattern = surface.is_external() ? layerm.region()->config.external_fill_pattern.value : ipRectilinear;
|
||||
group_attrib[i].pattern = surface.is_external() ? layerm.region()->config().external_fill_pattern.value : ipRectilinear;
|
||||
}
|
||||
}
|
||||
// Loop through solid groups, find compatible groups and append them to this one.
|
||||
|
|
@ -152,7 +152,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
for (const Surface &surface : surfaces) {
|
||||
if (surface.surface_type == stInternalVoid)
|
||||
continue;
|
||||
InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value;
|
||||
InfillPattern fill_pattern = layerm.region()->config().fill_pattern.value;
|
||||
double density = fill_density;
|
||||
FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill :
|
||||
(surface.is_solid() ? frSolidInfill : frInfill);
|
||||
|
|
@ -161,7 +161,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
if (surface.is_solid()) {
|
||||
density = 100.;
|
||||
fill_pattern = (surface.is_external() && ! is_bridge) ?
|
||||
layerm.region()->config.external_fill_pattern.value :
|
||||
layerm.region()->config().external_fill_pattern.value :
|
||||
ipRectilinear;
|
||||
} else if (density <= 0)
|
||||
continue;
|
||||
|
|
@ -190,7 +190,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
// layer height
|
||||
Flow internal_flow = layerm.region()->flow(
|
||||
frInfill,
|
||||
layerm.layer()->object()->config.layer_height.value, // TODO: handle infill_every_layers?
|
||||
layerm.layer()->object()->config().layer_height.value, // TODO: handle infill_every_layers?
|
||||
false, // no bridge
|
||||
false, // no first layer
|
||||
-1, // auto width
|
||||
|
|
@ -205,7 +205,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
double link_max_length = 0.;
|
||||
if (! is_bridge) {
|
||||
#if 0
|
||||
link_max_length = layerm.region()->config.get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
|
||||
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
|
||||
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
|
||||
#else
|
||||
if (density > 80.) // 80%
|
||||
|
|
@ -215,7 +215,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
|
||||
f->layer_id = layerm.layer()->id();
|
||||
f->z = layerm.layer()->print_z;
|
||||
f->angle = float(Geometry::deg2rad(layerm.region()->config.fill_angle.value));
|
||||
f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
|
||||
// Maximum length of the perimeter segment linking two infill lines.
|
||||
f->link_max_length = scale_(link_max_length);
|
||||
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||
case ipArchimedeanChords: return new FillArchimedeanChords();
|
||||
case ipHilbertCurve: return new FillHilbertCurve();
|
||||
case ipOctagramSpiral: return new FillOctagramSpiral();
|
||||
default: CONFESS("unknown type"); return nullptr;
|
||||
default: throw std::invalid_argument("unknown type");;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
|
|||
{
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && bridge_flow_ratio == 0)
|
||||
CONFESS("Invalid flow height supplied to new_from_config_width()");
|
||||
throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()");
|
||||
|
||||
float w;
|
||||
if (bridge_flow_ratio > 0) {
|
||||
|
|
@ -53,7 +53,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height,
|
|||
{
|
||||
// we need layer height unless it's a bridge
|
||||
if (height <= 0 && !bridge)
|
||||
CONFESS("Invalid flow height supplied to new_from_spacing()");
|
||||
throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()");
|
||||
// Calculate width from spacing.
|
||||
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
|
||||
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
|
||||
|
|
@ -111,23 +111,23 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
|
|||
return Flow::new_from_config_width(
|
||||
frSupportMaterial,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(object->config.support_material_extrusion_width.value > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width,
|
||||
// if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
|
||||
(object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
|
||||
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
||||
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
|
||||
{
|
||||
const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width;
|
||||
const auto &width = (object->print()->config().first_layer_extrusion_width.value > 0) ? object->print()->config().first_layer_extrusion_width : object->config().support_material_extrusion_width;
|
||||
return Flow::new_from_config_width(
|
||||
frSupportMaterial,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(width.value > 0) ? width : object->config.extrusion_width,
|
||||
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)),
|
||||
(width.value > 0) ? width : object->config().extrusion_width,
|
||||
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)),
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
|
@ -137,10 +137,10 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
|
|||
return Flow::new_from_config_width(
|
||||
frSupportMaterialInterface,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(object->config.support_material_extrusion_width > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width,
|
||||
// if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config.layer_height.value),
|
||||
(object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
|
||||
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
|
||||
// bridge_flow_ratio
|
||||
0.f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1278,11 +1278,15 @@ namespace Slic3r {
|
|||
double inv_sy = 1.0 / sy;
|
||||
double inv_sz = 1.0 / sz;
|
||||
|
||||
Eigen::Matrix3d m3x3;
|
||||
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> m3x3;
|
||||
m3x3 << transform(0, 0) * inv_sx, transform(0, 1) * inv_sy, transform(0, 2) * inv_sz,
|
||||
transform(1, 0) * inv_sx, transform(1, 1) * inv_sy, transform(1, 2) * inv_sz,
|
||||
transform(2, 0) * inv_sx, transform(2, 1) * inv_sy, transform(2, 2) * inv_sz;
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Vec3d angles = m3x3.eulerAngles(2, 1, 0);
|
||||
Vec3d rotation(angles(2), angles(1), angles(0));
|
||||
#else
|
||||
Eigen::AngleAxisd rotation;
|
||||
rotation.fromRotationMatrix(m3x3);
|
||||
|
||||
|
|
@ -1291,6 +1295,7 @@ namespace Slic3r {
|
|||
return;
|
||||
|
||||
double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle();
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
instance.set_offset(offset);
|
||||
|
|
@ -1299,7 +1304,11 @@ namespace Slic3r {
|
|||
instance.offset(1) = offset_y;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
instance.scaling_factor = sx;
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance.set_rotation(rotation);
|
||||
#else
|
||||
instance.rotation = angle_z;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@
|
|||
// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
|
||||
// 1 : Introduction of amf versioning. No other change in data saved into amf files.
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// 2 : Added z component of offset
|
||||
// Added x and y components of rotation
|
||||
#else
|
||||
// 2 : Added z component of offset.
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const unsigned int VERSION_AMF = 2;
|
||||
#else
|
||||
const unsigned int VERSION_AMF = 1;
|
||||
|
|
@ -127,6 +132,10 @@ struct AMFParserContext
|
|||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
NODE_TYPE_RX, // amf/constellation/instance/rx
|
||||
NODE_TYPE_RY, // amf/constellation/instance/ry
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
NODE_TYPE_RZ, // amf/constellation/instance/rz
|
||||
NODE_TYPE_SCALE, // amf/constellation/instance/scale
|
||||
NODE_TYPE_METADATA, // anywhere under amf/*/metadata
|
||||
|
|
@ -134,7 +143,11 @@ struct AMFParserContext
|
|||
|
||||
struct Instance {
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rx_set(false), ry_set(false), rz_set(false), scale_set(false) {}
|
||||
#else
|
||||
Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
#else
|
||||
Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
|
|
@ -149,6 +162,14 @@ struct AMFParserContext
|
|||
float deltaz;
|
||||
bool deltaz_set;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Rotation around the X axis.
|
||||
float rx;
|
||||
bool rx_set;
|
||||
// Rotation around the Y axis.
|
||||
float ry;
|
||||
bool ry_set;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Rotation around the Z axis.
|
||||
float rz;
|
||||
bool rz_set;
|
||||
|
|
@ -275,6 +296,12 @@ void AMFParserContext::startElement(const char *name, const char **atts)
|
|||
else if (strcmp(name, "deltaz") == 0)
|
||||
node_type_new = NODE_TYPE_DELTAZ;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
else if (strcmp(name, "rx") == 0)
|
||||
node_type_new = NODE_TYPE_RX;
|
||||
else if (strcmp(name, "ry") == 0)
|
||||
node_type_new = NODE_TYPE_RY;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
else if (strcmp(name, "rz") == 0)
|
||||
node_type_new = NODE_TYPE_RZ;
|
||||
else if (strcmp(name, "scale") == 0)
|
||||
|
|
@ -339,7 +366,11 @@ void AMFParserContext::characters(const XML_Char *s, int len)
|
|||
if (m_path.back() == NODE_TYPE_DELTAX ||
|
||||
m_path.back() == NODE_TYPE_DELTAY ||
|
||||
m_path.back() == NODE_TYPE_DELTAZ ||
|
||||
m_path.back() == NODE_TYPE_RZ ||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_path.back() == NODE_TYPE_RX ||
|
||||
m_path.back() == NODE_TYPE_RY ||
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_path.back() == NODE_TYPE_RZ ||
|
||||
m_path.back() == NODE_TYPE_SCALE)
|
||||
#else
|
||||
if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE)
|
||||
|
|
@ -391,6 +422,20 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
m_value[0].clear();
|
||||
break;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
case NODE_TYPE_RX:
|
||||
assert(m_instance);
|
||||
m_instance->rx = float(atof(m_value[0].c_str()));
|
||||
m_instance->rx_set = true;
|
||||
m_value[0].clear();
|
||||
break;
|
||||
case NODE_TYPE_RY:
|
||||
assert(m_instance);
|
||||
m_instance->ry = float(atof(m_value[0].c_str()));
|
||||
m_instance->ry_set = true;
|
||||
m_value[0].clear();
|
||||
break;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
case NODE_TYPE_RZ:
|
||||
assert(m_instance);
|
||||
m_instance->rz = float(atof(m_value[0].c_str()));
|
||||
|
|
@ -541,12 +586,16 @@ void AMFParserContext::endDocument()
|
|||
if (instance.deltax_set && instance.deltay_set) {
|
||||
ModelInstance *mi = m_model.objects[object.second.idx]->add_instance();
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz));
|
||||
mi->set_offset(Vec3d(instance.deltax_set ? (double)instance.deltax : 0.0, instance.deltay_set ? (double)instance.deltay : 0.0, instance.deltaz_set ? (double)instance.deltaz : 0.0));
|
||||
#else
|
||||
mi->offset(0) = instance.deltax;
|
||||
mi->offset(1) = instance.deltay;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0));
|
||||
#else
|
||||
mi->rotation = instance.rz_set ? instance.rz : 0.f;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
mi->scaling_factor = instance.scale_set ? instance.scale : 1.f;
|
||||
}
|
||||
}
|
||||
|
|
@ -800,7 +849,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
for (ModelVolume *volume : object->volumes) {
|
||||
vertices_offsets.push_back(num_vertices);
|
||||
if (! volume->mesh.repaired)
|
||||
CONFESS("store_amf() requires repair()");
|
||||
throw std::runtime_error("store_amf() requires repair()");
|
||||
auto &stl = volume->mesh.stl;
|
||||
if (stl.v_shared == nullptr)
|
||||
stl_generate_shared_vertices(&stl);
|
||||
|
|
@ -850,6 +899,10 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
" <deltaz>%lf</deltaz>\n"
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
" <rx>%lf</rx>\n"
|
||||
" <ry>%lf</ry>\n"
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
" <rz>%lf</rz>\n"
|
||||
" <scale>%lf</scale>\n"
|
||||
" </instance>\n",
|
||||
|
|
@ -862,8 +915,15 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
instance->offset(0),
|
||||
instance->offset(1),
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance->get_rotation(X),
|
||||
instance->get_rotation(Y),
|
||||
instance->get_rotation(Z),
|
||||
#else
|
||||
instance->rotation,
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance->scaling_factor);
|
||||
|
||||
//FIXME missing instance->scaling_factor
|
||||
instances.append(buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,11 @@ bool load_prus(const char *path, Model *model)
|
|||
const char *zero_tag = "<zero>";
|
||||
const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
|
||||
float trafo[3][4] = { 0 };
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Vec3d instance_rotation = Vec3d::Zero();
|
||||
#else
|
||||
double instance_rotation = 0.;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
double instance_scaling_factor = 1.f;
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
Vec3d instance_offset = Vec3d::Zero();
|
||||
|
|
@ -197,10 +201,14 @@ bool load_prus(const char *path, Model *model)
|
|||
instance_scaling_factor = scale[0];
|
||||
scale[0] = scale[1] = scale[2] = 1.;
|
||||
}
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]);
|
||||
#else
|
||||
if (rotation[0] == 0. && rotation[1] == 0.) {
|
||||
instance_rotation = - rotation[2];
|
||||
rotation[2] = 0.;
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Eigen::Matrix3f mat_rot, mat_scale, mat_trafo;
|
||||
mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) *
|
||||
Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) *
|
||||
|
|
@ -366,8 +374,12 @@ bool load_prus(const char *path, Model *model)
|
|||
model_object = model->add_object(name_utf8.data(), path, std::move(mesh));
|
||||
volume = model_object->volumes.front();
|
||||
ModelInstance *instance = model_object->add_instance();
|
||||
instance->rotation = instance_rotation;
|
||||
instance->scaling_factor = instance_scaling_factor;
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance->set_rotation(instance_rotation);
|
||||
#else
|
||||
instance->rotation = instance_rotation;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
instance->scaling_factor = instance_scaling_factor;
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
instance->set_offset(instance_offset);
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/find.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
|
|
@ -337,15 +338,15 @@ std::string WipeTowerIntegration::finalize(GCode &gcodegen)
|
|||
std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject &object)
|
||||
{
|
||||
std::vector<GCode::LayerToPrint> layers_to_print;
|
||||
layers_to_print.reserve(object.layers.size() + object.support_layers.size());
|
||||
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
|
||||
|
||||
// Pair the object layers with the support layers by z.
|
||||
size_t idx_object_layer = 0;
|
||||
size_t idx_support_layer = 0;
|
||||
while (idx_object_layer < object.layers.size() || idx_support_layer < object.support_layers.size()) {
|
||||
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
|
||||
LayerToPrint layer_to_print;
|
||||
layer_to_print.object_layer = (idx_object_layer < object.layers.size()) ? object.layers[idx_object_layer ++] : nullptr;
|
||||
layer_to_print.support_layer = (idx_support_layer < object.support_layers.size()) ? object.support_layers[idx_support_layer ++] : nullptr;
|
||||
layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr;
|
||||
layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr;
|
||||
if (layer_to_print.object_layer && layer_to_print.support_layer) {
|
||||
if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
|
||||
layer_to_print.support_layer = nullptr;
|
||||
|
|
@ -417,6 +418,12 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
{
|
||||
PROFILE_CLEAR();
|
||||
|
||||
// Does the file exist? If so, we hope that it is still valid.
|
||||
if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path)))
|
||||
return;
|
||||
|
||||
print->set_started(psGCodeExport);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Exporting G-code...";
|
||||
|
||||
// Remove the old g-code if it exists.
|
||||
|
|
@ -429,27 +436,34 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
if (file == nullptr)
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
|
||||
|
||||
this->m_placeholder_parser_failed_templates.clear();
|
||||
this->_do_export(*print, file, preview_data);
|
||||
fflush(file);
|
||||
if (ferror(file)) {
|
||||
try {
|
||||
m_placeholder_parser_failed_templates.clear();
|
||||
this->_do_export(*print, file, preview_data);
|
||||
fflush(file);
|
||||
if (ferror(file)) {
|
||||
fclose(file);
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
|
||||
// Close and remove the file.
|
||||
fclose(file);
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
|
||||
throw;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
if (print->config.remaining_times.value)
|
||||
{
|
||||
if (print->config().remaining_times.value) {
|
||||
m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
if (m_silent_time_estimator_enabled)
|
||||
m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
}
|
||||
|
||||
if (! this->m_placeholder_parser_failed_templates.empty()) {
|
||||
if (! m_placeholder_parser_failed_templates.empty()) {
|
||||
// G-code export proceeded, but some of the PlaceholderParser substitutions failed.
|
||||
std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
|
||||
for (const std::string &name : this->m_placeholder_parser_failed_templates)
|
||||
for (const std::string &name : m_placeholder_parser_failed_templates)
|
||||
msg += std::string("\t") + name + "\n";
|
||||
msg += "\nPlease inspect the file ";
|
||||
msg += path_tmp + " for error messages enclosed between\n";
|
||||
|
|
@ -459,12 +473,13 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
if (boost::nowide::rename(path_tmp.c_str(), path) != 0)
|
||||
if (rename_file(path_tmp, path) != 0)
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished";
|
||||
print->set_done(psGCodeExport);
|
||||
|
||||
// Write the profiler measurements to file
|
||||
PROFILE_UPDATE();
|
||||
|
|
@ -477,66 +492,66 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
|
||||
// resets time estimators
|
||||
m_normal_time_estimator.reset();
|
||||
m_normal_time_estimator.set_dialect(print.config.gcode_flavor);
|
||||
m_silent_time_estimator_enabled = (print.config.gcode_flavor == gcfMarlin) && print.config.silent_mode;
|
||||
m_normal_time_estimator.set_dialect(print.config().gcode_flavor);
|
||||
m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode;
|
||||
|
||||
// Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
|
||||
// and let the user to enter the G-code limits into the start G-code.
|
||||
// If the following block is enabled for other firmwares than the Marlin, then the function
|
||||
// this->print_machine_envelope(file, print);
|
||||
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
|
||||
if (print.config.gcode_flavor.value == gcfMarlin) {
|
||||
m_normal_time_estimator.set_max_acceleration(print.config.machine_max_acceleration_extruding.values[0]);
|
||||
m_normal_time_estimator.set_retract_acceleration(print.config.machine_max_acceleration_retracting.values[0]);
|
||||
m_normal_time_estimator.set_minimum_feedrate(print.config.machine_min_extruding_rate.values[0]);
|
||||
m_normal_time_estimator.set_minimum_travel_feedrate(print.config.machine_min_travel_rate.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config.machine_max_acceleration_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config.machine_max_acceleration_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config.machine_max_acceleration_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config.machine_max_acceleration_e.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config.machine_max_feedrate_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config.machine_max_feedrate_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config.machine_max_feedrate_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config.machine_max_feedrate_e.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config.machine_max_jerk_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[0]);
|
||||
if (print.config().gcode_flavor.value == gcfMarlin) {
|
||||
m_normal_time_estimator.set_max_acceleration(print.config().machine_max_acceleration_extruding.values[0]);
|
||||
m_normal_time_estimator.set_retract_acceleration(print.config().machine_max_acceleration_retracting.values[0]);
|
||||
m_normal_time_estimator.set_minimum_feedrate(print.config().machine_min_extruding_rate.values[0]);
|
||||
m_normal_time_estimator.set_minimum_travel_feedrate(print.config().machine_min_travel_rate.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config().machine_max_acceleration_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config().machine_max_acceleration_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config().machine_max_acceleration_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config().machine_max_acceleration_e.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config().machine_max_feedrate_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config().machine_max_feedrate_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config().machine_max_feedrate_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config().machine_max_feedrate_e.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config().machine_max_jerk_x.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config().machine_max_jerk_y.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config().machine_max_jerk_z.values[0]);
|
||||
m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config().machine_max_jerk_e.values[0]);
|
||||
|
||||
if (m_silent_time_estimator_enabled)
|
||||
{
|
||||
m_silent_time_estimator.reset();
|
||||
m_silent_time_estimator.set_dialect(print.config.gcode_flavor);
|
||||
m_silent_time_estimator.set_max_acceleration(print.config.machine_max_acceleration_extruding.values[1]);
|
||||
m_silent_time_estimator.set_retract_acceleration(print.config.machine_max_acceleration_retracting.values[1]);
|
||||
m_silent_time_estimator.set_minimum_feedrate(print.config.machine_min_extruding_rate.values[1]);
|
||||
m_silent_time_estimator.set_minimum_travel_feedrate(print.config.machine_min_travel_rate.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config.machine_max_acceleration_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config.machine_max_acceleration_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config.machine_max_acceleration_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config.machine_max_acceleration_e.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config.machine_max_feedrate_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config.machine_max_feedrate_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config.machine_max_feedrate_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config.machine_max_feedrate_e.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config.machine_max_jerk_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[1]);
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
m_silent_time_estimator.set_dialect(print.config().gcode_flavor);
|
||||
m_silent_time_estimator.set_max_acceleration(print.config().machine_max_acceleration_extruding.values[1]);
|
||||
m_silent_time_estimator.set_retract_acceleration(print.config().machine_max_acceleration_retracting.values[1]);
|
||||
m_silent_time_estimator.set_minimum_feedrate(print.config().machine_min_extruding_rate.values[1]);
|
||||
m_silent_time_estimator.set_minimum_travel_feedrate(print.config().machine_min_travel_rate.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config().machine_max_acceleration_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config().machine_max_acceleration_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config().machine_max_acceleration_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config().machine_max_acceleration_e.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config().machine_max_feedrate_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config().machine_max_feedrate_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config().machine_max_feedrate_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config().machine_max_feedrate_e.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config().machine_max_jerk_x.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config().machine_max_jerk_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config().machine_max_jerk_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config().machine_max_jerk_e.values[1]);
|
||||
if (print.config().single_extruder_multi_material) {
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values);
|
||||
m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values);
|
||||
m_silent_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
|
||||
m_silent_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
if (print.config().single_extruder_multi_material) {
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values);
|
||||
m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values);
|
||||
m_normal_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
|
||||
m_normal_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
|
||||
}
|
||||
|
||||
// resets analyzer
|
||||
|
|
@ -552,14 +567,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// change_layer() in turn increments the progress bar status.
|
||||
m_layer_count = 0;
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
if (print.config.complete_objects.value) {
|
||||
if (print.config().complete_objects.value) {
|
||||
// Add each of the object's layers separately.
|
||||
for (auto object : printable_objects) {
|
||||
std::vector<coordf_t> zs;
|
||||
zs.reserve(object->layers.size() + object->support_layers.size());
|
||||
for (auto layer : object->layers)
|
||||
zs.reserve(object->layers().size() + object->support_layers().size());
|
||||
for (auto layer : object->layers())
|
||||
zs.push_back(layer->print_z);
|
||||
for (auto layer : object->support_layers)
|
||||
for (auto layer : object->support_layers())
|
||||
zs.push_back(layer->print_z);
|
||||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
|
||||
|
|
@ -568,18 +583,19 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// Print all objects with the same print_z together.
|
||||
std::vector<coordf_t> zs;
|
||||
for (auto object : printable_objects) {
|
||||
zs.reserve(zs.size() + object->layers.size() + object->support_layers.size());
|
||||
for (auto layer : object->layers)
|
||||
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
|
||||
for (auto layer : object->layers())
|
||||
zs.push_back(layer->print_z);
|
||||
for (auto layer : object->support_layers)
|
||||
for (auto layer : object->support_layers())
|
||||
zs.push_back(layer->print_z);
|
||||
}
|
||||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
m_enable_cooling_markers = true;
|
||||
this->apply_print_config(print.config);
|
||||
this->apply_print_config(print.config());
|
||||
this->set_extruders(print.extruders());
|
||||
|
||||
// Initialize autospeed.
|
||||
|
|
@ -587,27 +603,28 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// get the minimum cross-section used in the print
|
||||
std::vector<double> mm3_per_mm;
|
||||
for (auto object : printable_objects) {
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++region_id) {
|
||||
auto region = print.regions[region_id];
|
||||
for (auto layer : object->layers) {
|
||||
auto layerm = layer->regions[region_id];
|
||||
if (region->config.get_abs_value("perimeter_speed" ) == 0 ||
|
||||
region->config.get_abs_value("small_perimeter_speed" ) == 0 ||
|
||||
region->config.get_abs_value("external_perimeter_speed" ) == 0 ||
|
||||
region->config.get_abs_value("bridge_speed" ) == 0)
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++region_id) {
|
||||
auto region = print.regions()[region_id];
|
||||
for (auto layer : object->layers()) {
|
||||
auto layerm = layer->regions()[region_id];
|
||||
if (region->config().get_abs_value("perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("small_perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("external_perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("bridge_speed" ) == 0)
|
||||
mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
|
||||
if (region->config.get_abs_value("infill_speed" ) == 0 ||
|
||||
region->config.get_abs_value("solid_infill_speed" ) == 0 ||
|
||||
region->config.get_abs_value("top_solid_infill_speed" ) == 0 ||
|
||||
region->config.get_abs_value("bridge_speed" ) == 0)
|
||||
if (region->config().get_abs_value("infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("solid_infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("top_solid_infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("bridge_speed" ) == 0)
|
||||
mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
|
||||
}
|
||||
}
|
||||
if (object->config.get_abs_value("support_material_speed" ) == 0 ||
|
||||
object->config.get_abs_value("support_material_interface_speed" ) == 0)
|
||||
for (auto layer : object->support_layers)
|
||||
if (object->config().get_abs_value("support_material_speed" ) == 0 ||
|
||||
object->config().get_abs_value("support_material_interface_speed" ) == 0)
|
||||
for (auto layer : object->support_layers())
|
||||
mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
// filter out 0-width segments
|
||||
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
|
||||
if (! mm3_per_mm.empty()) {
|
||||
|
|
@ -616,19 +633,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// volumetric speed as the volumetric speed produced by printing the
|
||||
// smallest cross-section at the maximum speed: any larger cross-section
|
||||
// will need slower feedrates.
|
||||
m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config.max_print_speed.value;
|
||||
m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
|
||||
// limit such volumetric speed with max_volumetric_speed if set
|
||||
if (print.config.max_volumetric_speed.value > 0)
|
||||
m_volumetric_speed = std::min(m_volumetric_speed, print.config.max_volumetric_speed.value);
|
||||
if (print.config().max_volumetric_speed.value > 0)
|
||||
m_volumetric_speed = std::min(m_volumetric_speed, print.config().max_volumetric_speed.value);
|
||||
}
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
m_cooling_buffer = make_unique<CoolingBuffer>(*this);
|
||||
if (print.config.spiral_vase.value)
|
||||
m_spiral_vase = make_unique<SpiralVase>(print.config);
|
||||
if (print.config.max_volumetric_extrusion_rate_slope_positive.value > 0 ||
|
||||
print.config.max_volumetric_extrusion_rate_slope_negative.value > 0)
|
||||
m_pressure_equalizer = make_unique<PressureEqualizer>(&print.config);
|
||||
if (print.config().spiral_vase.value)
|
||||
m_spiral_vase = make_unique<SpiralVase>(print.config());
|
||||
if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 ||
|
||||
print.config().max_volumetric_extrusion_rate_slope_negative.value > 0)
|
||||
m_pressure_equalizer = make_unique<PressureEqualizer>(&print.config());
|
||||
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
|
||||
|
||||
// Write information on the generator.
|
||||
|
|
@ -636,7 +654,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// Write notes (content of the Print Settings tab -> Notes)
|
||||
{
|
||||
std::list<std::string> lines;
|
||||
boost::split(lines, print.config.notes.value, boost::is_any_of("\n"), boost::token_compress_off);
|
||||
boost::split(lines, print.config().notes.value, boost::is_any_of("\n"), boost::token_compress_off);
|
||||
for (auto line : lines) {
|
||||
// Remove the trailing '\r' from the '\r\n' sequence.
|
||||
if (! line.empty() && line.back() == '\r')
|
||||
|
|
@ -646,12 +664,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
if (! lines.empty())
|
||||
_write(file, "\n");
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = printable_objects.front();
|
||||
const double layer_height = first_object->config.layer_height.value;
|
||||
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
|
||||
auto region = print.regions[region_id];
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height);
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||
auto region = print.regions()[region_id];
|
||||
_write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
|
||||
_write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
|
||||
_write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
|
||||
|
|
@ -659,13 +679,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
_write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
|
||||
if (print.has_support_material())
|
||||
_write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
|
||||
if (print.config.first_layer_extrusion_width.value > 0)
|
||||
if (print.config().first_layer_extrusion_width.value > 0)
|
||||
_write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
|
||||
_write_format(file, "\n");
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
// adds tags for time estimators
|
||||
if (print.config.remaining_times.value)
|
||||
if (print.config().remaining_times.value)
|
||||
{
|
||||
_writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
|
||||
if (m_silent_time_estimator_enabled)
|
||||
|
|
@ -673,7 +694,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
}
|
||||
|
||||
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
|
||||
m_placeholder_parser = print.placeholder_parser;
|
||||
m_placeholder_parser = print.placeholder_parser();
|
||||
m_placeholder_parser.update_timestamp();
|
||||
|
||||
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
|
||||
|
|
@ -683,7 +704,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
unsigned int final_extruder_id = (unsigned int)-1;
|
||||
size_t initial_print_object_id = 0;
|
||||
bool has_wipe_tower = false;
|
||||
if (print.config.complete_objects.value) {
|
||||
if (print.config().complete_objects.value) {
|
||||
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
|
||||
for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) {
|
||||
tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id);
|
||||
|
|
@ -693,11 +714,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
} else {
|
||||
// Find tool ordering for all the objects at once, and the initial extruder ID.
|
||||
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
|
||||
tool_ordering = print.m_tool_ordering.empty() ?
|
||||
tool_ordering = print.wipe_tower_data().tool_ordering.empty() ?
|
||||
ToolOrdering(print, initial_extruder_id) :
|
||||
print.m_tool_ordering;
|
||||
print.wipe_tower_data().tool_ordering;
|
||||
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||
initial_extruder_id = (has_wipe_tower && ! print.config.single_extruder_multi_material_priming) ?
|
||||
initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
|
||||
// The priming towers will be skipped.
|
||||
tool_ordering.all_extruders().back() :
|
||||
// Don't skip the priming towers.
|
||||
|
|
@ -711,6 +732,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
final_extruder_id = tool_ordering.last_extruder();
|
||||
assert(final_extruder_id != (unsigned int)-1);
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||
|
||||
|
|
@ -718,7 +740,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Disable fan.
|
||||
if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id))
|
||||
if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id))
|
||||
_write(file, m_writer.set_fan(0, true));
|
||||
|
||||
// Let the start-up script prime the 1st printing tool.
|
||||
|
|
@ -729,8 +751,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
m_placeholder_parser.set("current_object_idx", 0);
|
||||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
|
||||
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming);
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
|
||||
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
|
||||
// Set extruder(s) temperature before and after start G-code.
|
||||
|
|
@ -747,49 +769,51 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// Write the custom start G-code
|
||||
_writeln(file, start_gcode);
|
||||
// Process filament-specific gcode in extruder order.
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
if (print.config().single_extruder_multi_material) {
|
||||
if (has_wipe_tower) {
|
||||
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
|
||||
} else {
|
||||
// Only initialize the initial extruder.
|
||||
_writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
|
||||
_writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
|
||||
}
|
||||
} else {
|
||||
for (const std::string &start_gcode : print.config.start_filament_gcode.values)
|
||||
_writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
|
||||
for (const std::string &start_gcode : print.config().start_filament_gcode.values)
|
||||
_writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front())));
|
||||
}
|
||||
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
|
||||
|
||||
print.throw_if_canceled();
|
||||
|
||||
// Set other general things.
|
||||
_write(file, this->preamble());
|
||||
|
||||
// Initialize a motion planner for object-to-object travel moves.
|
||||
if (print.config.avoid_crossing_perimeters.value) {
|
||||
if (print.config().avoid_crossing_perimeters.value) {
|
||||
// Collect outer contours of all objects over all layers.
|
||||
// Discard objects only containing thin walls (offset would fail on an empty polygon).
|
||||
Polygons islands;
|
||||
for (const PrintObject *object : printable_objects)
|
||||
for (const Layer *layer : object->layers)
|
||||
for (const Layer *layer : object->layers())
|
||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
||||
for (const Point © : object->_shifted_copies) {
|
||||
for (const Point © : object->copies()) {
|
||||
islands.emplace_back(expoly.contour);
|
||||
islands.back().translate(- copy);
|
||||
}
|
||||
//FIXME Mege the islands in parallel.
|
||||
m_avoid_crossing_perimeters.init_external_mp(union_ex(islands));
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
|
||||
// Calculate wiping points if needed
|
||||
if (print.config.ooze_prevention.value && ! print.config.single_extruder_multi_material) {
|
||||
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
|
||||
Points skirt_points;
|
||||
for (const ExtrusionEntity *ee : print.skirt.entities)
|
||||
for (const ExtrusionEntity *ee : print.skirt().entities)
|
||||
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
|
||||
append(skirt_points, path.polyline.points);
|
||||
if (! skirt_points.empty()) {
|
||||
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
|
||||
Polygons skirts;
|
||||
for (unsigned int extruder_id : print.extruders()) {
|
||||
const Vec2d &extruder_offset = print.config.extruder_offset.get_at(extruder_id);
|
||||
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
|
||||
Polygon s(outer_skirt);
|
||||
s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1)));
|
||||
skirts.emplace_back(std::move(s));
|
||||
|
|
@ -807,16 +831,17 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
);
|
||||
#endif
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
|
||||
if (! (has_wipe_tower && print.config.single_extruder_multi_material_priming)) {
|
||||
if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
|
||||
// Set initial extruder only after custom start G-code.
|
||||
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
|
||||
_write(file, this->set_extruder(initial_extruder_id));
|
||||
}
|
||||
|
||||
// Do all objects for each layer.
|
||||
if (print.config.complete_objects.value) {
|
||||
if (print.config().complete_objects.value) {
|
||||
// Print objects from the smallest to the tallest to avoid collisions
|
||||
// when moving onto next object starting point.
|
||||
std::vector<PrintObject*> objects(printable_objects);
|
||||
|
|
@ -824,9 +849,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
size_t finished_objects = 0;
|
||||
for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) {
|
||||
const PrintObject &object = *objects[object_id];
|
||||
for (const Point © : object._shifted_copies) {
|
||||
for (const Point © : object.copies()) {
|
||||
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
|
||||
if (object_id != initial_print_object_id || © != object._shifted_copies.data()) {
|
||||
if (object_id != initial_print_object_id || © != object.copies().data()) {
|
||||
// Don't initialize for the first object and first copy.
|
||||
tool_ordering = ToolOrdering(object, final_extruder_id);
|
||||
unsigned int new_extruder_id = tool_ordering.first_extruder();
|
||||
|
|
@ -837,6 +862,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
final_extruder_id = tool_ordering.last_extruder();
|
||||
assert(final_extruder_id != (unsigned int)-1);
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
this->set_origin(unscale(copy));
|
||||
if (finished_objects > 0) {
|
||||
// Move to the origin position for the copy we're going to print.
|
||||
|
|
@ -852,7 +878,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// another one, set first layer temperatures. This happens before the Z move
|
||||
// is triggered, so machine has more time to reach such temperatures.
|
||||
m_placeholder_parser.set("current_object_idx", int(finished_objects));
|
||||
std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id);
|
||||
std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config().between_objects_gcode.value, initial_extruder_id);
|
||||
// Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
|
||||
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
|
|
@ -866,7 +892,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
for (const LayerToPrint <p : layers_to_print) {
|
||||
std::vector<LayerToPrint> lrs;
|
||||
lrs.emplace_back(std::move(ltp));
|
||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object._shifted_copies.data());
|
||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data());
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
if (m_pressure_equalizer)
|
||||
_write(file, m_pressure_equalizer->process("", true));
|
||||
|
|
@ -882,16 +909,16 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
Points object_reference_points;
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
for (PrintObject *object : printable_objects)
|
||||
object_reference_points.push_back(object->_shifted_copies.front());
|
||||
object_reference_points.push_back(object->copies().front());
|
||||
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
|
||||
// Sort layers by Z.
|
||||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
||||
// Prusa Multi-Material wipe tower.
|
||||
if (has_wipe_tower && ! layers_to_print.empty()) {
|
||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
|
||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
|
||||
_write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
|
||||
if (print.config.single_extruder_multi_material_priming) {
|
||||
if (print.config().single_extruder_multi_material_priming) {
|
||||
_write(file, m_wipe_tower->prime(*this));
|
||||
// Verify, whether the print overaps the priming extrusions.
|
||||
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
|
||||
|
|
@ -915,6 +942,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
_write(file, "M1 S10\n");
|
||||
}
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
// Extrude the layers.
|
||||
for (auto &layer : layers_to_print) {
|
||||
|
|
@ -922,6 +950,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
if (m_wipe_tower && layer_tools.has_wipe_tower)
|
||||
m_wipe_tower->next_layer();
|
||||
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
if (m_pressure_equalizer)
|
||||
_write(file, m_pressure_equalizer->process("", true));
|
||||
|
|
@ -947,17 +976,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value));
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
if (print.config().single_extruder_multi_material) {
|
||||
// Process the end_filament_gcode for the active filament only.
|
||||
_writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id(), &config));
|
||||
_writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id(), &config));
|
||||
} else {
|
||||
for (const std::string &end_gcode : print.config.end_filament_gcode.values)
|
||||
_writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()), &config));
|
||||
for (const std::string &end_gcode : print.config().end_filament_gcode.values)
|
||||
_writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()), &config));
|
||||
}
|
||||
_writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id(), &config));
|
||||
_writeln(file, this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config));
|
||||
}
|
||||
_write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
|
||||
_write(file, m_writer.postamble());
|
||||
print.throw_if_canceled();
|
||||
|
||||
// calculates estimated printing time
|
||||
m_normal_time_estimator.calculate_time(false);
|
||||
|
|
@ -965,37 +995,30 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
m_silent_time_estimator.calculate_time(false);
|
||||
|
||||
// Get filament stats.
|
||||
print.filament_stats.clear();
|
||||
print.total_used_filament = 0.;
|
||||
print.total_extruded_volume = 0.;
|
||||
print.total_weight = 0.;
|
||||
print.total_cost = 0.;
|
||||
print.total_wipe_tower_cost = 0.;
|
||||
print.total_wipe_tower_filament = 0.;
|
||||
print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
|
||||
print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
|
||||
print.m_print_statistics.clear();
|
||||
print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
|
||||
print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
|
||||
for (const Extruder &extruder : m_writer.extruders()) {
|
||||
double used_filament = extruder.used_filament() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] : 0.f);
|
||||
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
|
||||
double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f);
|
||||
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
|
||||
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
|
||||
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
|
||||
|
||||
print.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
|
||||
print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
|
||||
_write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
|
||||
if (filament_weight > 0.) {
|
||||
print.total_weight = print.total_weight + filament_weight;
|
||||
print.m_print_statistics.total_weight = print.m_print_statistics.total_weight + filament_weight;
|
||||
_write_format(file, "; filament used = %.1lf\n", filament_weight);
|
||||
if (filament_cost > 0.) {
|
||||
print.total_cost = print.total_cost + filament_cost;
|
||||
print.m_print_statistics.total_cost = print.m_print_statistics.total_cost + filament_cost;
|
||||
_write_format(file, "; filament cost = %.1lf\n", filament_cost);
|
||||
}
|
||||
}
|
||||
print.total_used_filament += used_filament;
|
||||
print.total_extruded_volume += extruded_volume;
|
||||
print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
|
||||
print.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
|
||||
print.m_print_statistics.total_used_filament += used_filament;
|
||||
print.m_print_statistics.total_extruded_volume += extruded_volume;
|
||||
print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
|
||||
print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
|
||||
}
|
||||
_write_format(file, "; total filament cost = %.1lf\n", print.total_cost);
|
||||
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
|
||||
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
|
||||
if (m_silent_time_estimator_enabled)
|
||||
_write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
|
||||
|
|
@ -1008,6 +1031,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
if (!full_config.empty())
|
||||
_write(file, full_config);
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
// starts analizer calculations
|
||||
if (preview_data != nullptr)
|
||||
|
|
@ -1020,7 +1044,7 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std
|
|||
return m_placeholder_parser.process(templ, current_extruder_id, config_override);
|
||||
} catch (std::runtime_error &err) {
|
||||
// Collect the names of failed template substitutions for error reporting.
|
||||
this->m_placeholder_parser_failed_templates.insert(name);
|
||||
m_placeholder_parser_failed_templates.insert(name);
|
||||
// Insert the macro error message into the G-code.
|
||||
return
|
||||
std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
|
||||
|
|
@ -1087,29 +1111,29 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
|
|||
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
|
||||
void GCode::print_machine_envelope(FILE *file, Print &print)
|
||||
{
|
||||
if (print.config.gcode_flavor.value == gcfMarlin) {
|
||||
if (print.config().gcode_flavor.value == gcfMarlin) {
|
||||
fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
|
||||
int(print.config.machine_max_acceleration_x.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_y.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_z.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_e.values.front() + 0.5));
|
||||
int(print.config().machine_max_acceleration_x.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_y.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_z.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_e.values.front() + 0.5));
|
||||
fprintf(file, "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n",
|
||||
int(print.config.machine_max_feedrate_x.values.front() + 0.5),
|
||||
int(print.config.machine_max_feedrate_y.values.front() + 0.5),
|
||||
int(print.config.machine_max_feedrate_z.values.front() + 0.5),
|
||||
int(print.config.machine_max_feedrate_e.values.front() + 0.5));
|
||||
int(print.config().machine_max_feedrate_x.values.front() + 0.5),
|
||||
int(print.config().machine_max_feedrate_y.values.front() + 0.5),
|
||||
int(print.config().machine_max_feedrate_z.values.front() + 0.5),
|
||||
int(print.config().machine_max_feedrate_e.values.front() + 0.5));
|
||||
fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
|
||||
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_retracting.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5));
|
||||
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5));
|
||||
fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
|
||||
print.config.machine_max_jerk_x.values.front(),
|
||||
print.config.machine_max_jerk_y.values.front(),
|
||||
print.config.machine_max_jerk_z.values.front(),
|
||||
print.config.machine_max_jerk_e.values.front());
|
||||
print.config().machine_max_jerk_x.values.front(),
|
||||
print.config().machine_max_jerk_y.values.front(),
|
||||
print.config().machine_max_jerk_z.values.front(),
|
||||
print.config().machine_max_jerk_e.values.front());
|
||||
fprintf(file, "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n",
|
||||
int(print.config.machine_min_extruding_rate.values.front() + 0.5),
|
||||
int(print.config.machine_min_travel_rate.values.front() + 0.5));
|
||||
int(print.config().machine_min_extruding_rate.values.front() + 0.5),
|
||||
int(print.config().machine_min_travel_rate.values.front() + 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1120,7 +1144,7 @@ void GCode::print_machine_envelope(FILE *file, Print &print)
|
|||
void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
// Initial bed temperature based on the first extruder.
|
||||
int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id);
|
||||
int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
int temp_by_gcode = -1;
|
||||
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
|
||||
|
|
@ -1143,23 +1167,23 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c
|
|||
int temp_by_gcode = -1;
|
||||
if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
|
||||
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
|
||||
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
|
||||
temp = temp_by_gcode;
|
||||
m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id);
|
||||
} else {
|
||||
// Custom G-code does not set the extruder temperature. Do it now.
|
||||
if (print.config.single_extruder_multi_material.value) {
|
||||
if (print.config().single_extruder_multi_material.value) {
|
||||
// Set temperature of the first printing extruder only.
|
||||
int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
if (temp > 0)
|
||||
_write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id));
|
||||
} else {
|
||||
// Set temperatures of all the printing extruders.
|
||||
for (unsigned int tool_id : print.extruders()) {
|
||||
int temp = print.config.first_layer_temperature.get_at(tool_id);
|
||||
if (print.config.ooze_prevention.value)
|
||||
temp += print.config.standby_temperature_delta.value;
|
||||
int temp = print.config().first_layer_temperature.get_at(tool_id);
|
||||
if (print.config().ooze_prevention.value)
|
||||
temp += print.config().standby_temperature_delta.value;
|
||||
if (temp > 0)
|
||||
_write(file, m_writer.set_temperature(temp, wait, tool_id));
|
||||
}
|
||||
|
|
@ -1232,15 +1256,15 @@ void GCode::process_layer(
|
|||
unsigned int first_extruder_id = layer_tools.extruders.front();
|
||||
|
||||
// Initialize config with the 1st object to be printed at this layer.
|
||||
m_config.apply(layer.object()->config, true);
|
||||
m_config.apply(layer.object()->config(), true);
|
||||
|
||||
// Check whether it is possible to apply the spiral vase logic for this layer.
|
||||
// Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
|
||||
if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) {
|
||||
bool enable = (layer.id() > 0 || print.config.brim_width.value == 0.) && (layer.id() >= print.config.skirt_height.value && ! print.has_infinite_skirt());
|
||||
bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt());
|
||||
if (enable) {
|
||||
for (const LayerRegion *layer_region : layer.regions)
|
||||
if (layer_region->region()->config.bottom_solid_layers.value > layer.id() ||
|
||||
for (const LayerRegion *layer_region : layer.regions())
|
||||
if (layer_region->region()->config().bottom_solid_layers.value > layer.id() ||
|
||||
layer_region->perimeters.items_count() > 1 ||
|
||||
layer_region->fills.items_count() > 0) {
|
||||
enable = false;
|
||||
|
|
@ -1255,22 +1279,22 @@ void GCode::process_layer(
|
|||
std::string gcode;
|
||||
|
||||
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
|
||||
if (! print.config.before_layer_gcode.value.empty()) {
|
||||
if (! print.config().before_layer_gcode.value.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
gcode += this->placeholder_parser_process("before_layer_gcode",
|
||||
print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
gcode += this->change_layer(print_z); // this will increase m_layer_index
|
||||
m_layer = &layer;
|
||||
if (! print.config.layer_gcode.value.empty()) {
|
||||
if (! print.config().layer_gcode.value.empty()) {
|
||||
DynamicConfig config;
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
|
||||
gcode += this->placeholder_parser_process("layer_gcode",
|
||||
print.config.layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
print.config().layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
|
|
@ -1278,14 +1302,14 @@ void GCode::process_layer(
|
|||
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
|
||||
// first_layer_temperature vs. temperature settings.
|
||||
for (const Extruder &extruder : m_writer.extruders()) {
|
||||
if (print.config.single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id())
|
||||
if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id())
|
||||
// In single extruder multi material mode, set the temperature for the current extruder only.
|
||||
continue;
|
||||
int temperature = print.config.temperature.get_at(extruder.id());
|
||||
if (temperature > 0 && temperature != print.config.first_layer_temperature.get_at(extruder.id()))
|
||||
int temperature = print.config().temperature.get_at(extruder.id());
|
||||
if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id()))
|
||||
gcode += m_writer.set_temperature(temperature, false, extruder.id());
|
||||
}
|
||||
gcode += m_writer.set_bed_temperature(print.config.bed_temperature.get_at(first_extruder_id));
|
||||
gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
|
||||
// Mark the temperature transition from 1st to 2nd layer to be finished.
|
||||
m_second_layer_things_done = true;
|
||||
}
|
||||
|
|
@ -1293,9 +1317,9 @@ void GCode::process_layer(
|
|||
// Extrude skirt at the print_z of the raft layers and normal object layers
|
||||
// not at the print_z of the interlaced support material layers.
|
||||
bool extrude_skirt =
|
||||
! print.skirt.entities.empty() &&
|
||||
! print.skirt().entities.empty() &&
|
||||
// Not enough skirt layers printed yet.
|
||||
(m_skirt_done.size() < print.config.skirt_height.value || print.has_infinite_skirt()) &&
|
||||
(m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) &&
|
||||
// This print_z has not been extruded yet
|
||||
(m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON &&
|
||||
// and this layer is the 1st layer, or it is an object layer, or it is a raft layer.
|
||||
|
|
@ -1317,7 +1341,7 @@ void GCode::process_layer(
|
|||
extruder_ids.front() = first_extruder_id;
|
||||
break;
|
||||
}
|
||||
size_t n_loops = print.skirt.entities.size();
|
||||
size_t n_loops = print.skirt().entities.size();
|
||||
if (n_loops <= extruder_ids.size()) {
|
||||
for (size_t i = 0; i < n_loops; ++i)
|
||||
skirt_loops_per_extruder[extruder_ids[i]] = std::pair<size_t, size_t>(i, i + 1);
|
||||
|
|
@ -1336,7 +1360,7 @@ void GCode::process_layer(
|
|||
}
|
||||
} else
|
||||
// Extrude all skirts with the current extruder.
|
||||
skirt_loops_per_extruder[first_extruder_id] = std::pair<size_t, size_t>(0, print.config.skirts.value);
|
||||
skirt_loops_per_extruder[first_extruder_id] = std::pair<size_t, size_t>(0, print.config().skirts.value);
|
||||
}
|
||||
|
||||
// Group extrusions by an extruder, then by an object, an island and a region.
|
||||
|
|
@ -1350,22 +1374,22 @@ void GCode::process_layer(
|
|||
bool has_support = role == erMixed || role == erSupportMaterial;
|
||||
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
||||
// Extruder ID of the support base. -1 if "don't care".
|
||||
unsigned int support_extruder = object.config.support_material_extruder.value - 1;
|
||||
unsigned int support_extruder = object.config().support_material_extruder.value - 1;
|
||||
// Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
||||
bool support_dontcare = object.config.support_material_extruder.value == 0;
|
||||
bool support_dontcare = object.config().support_material_extruder.value == 0;
|
||||
// Extruder ID of the support interface. -1 if "don't care".
|
||||
unsigned int interface_extruder = object.config.support_material_interface_extruder.value - 1;
|
||||
unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1;
|
||||
// Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
|
||||
bool interface_dontcare = object.config.support_material_interface_extruder.value == 0;
|
||||
bool interface_dontcare = object.config().support_material_interface_extruder.value == 0;
|
||||
if (support_dontcare || interface_dontcare) {
|
||||
// Some support will be printed with "don't care" material, preferably non-soluble.
|
||||
// Is the current extruder assigned a soluble filament?
|
||||
unsigned int dontcare_extruder = first_extruder_id;
|
||||
if (print.config.filament_soluble.get_at(dontcare_extruder)) {
|
||||
if (print.config().filament_soluble.get_at(dontcare_extruder)) {
|
||||
// The last extruder printed on the previous layer extrudes soluble filament.
|
||||
// Try to find a non-soluble extruder on the same layer.
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
if (! print.config.filament_soluble.get_at(extruder_id)) {
|
||||
if (! print.config().filament_soluble.get_at(extruder_id)) {
|
||||
dontcare_extruder = extruder_id;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1412,11 +1436,11 @@ void GCode::process_layer(
|
|||
layer.slices.expolygons[i].contour.contains(point);
|
||||
};
|
||||
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
|
||||
const LayerRegion *layerm = layer.regions[region_id];
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||
const LayerRegion *layerm = layer.regions()[region_id];
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
const PrintRegion ®ion = *print.regions[region_id];
|
||||
const PrintRegion ®ion = *print.regions()[region_id];
|
||||
|
||||
|
||||
// Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
|
||||
|
|
@ -1433,11 +1457,14 @@ void GCode::process_layer(
|
|||
continue;
|
||||
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
||||
int correct_extruder_id = Print::get_extruder(*fill, region); entity_type=="infills" ? std::max<int>(0, (is_solid_infill(fill->entities.front()->role()) ? region.config.solid_infill_extruder : region.config.infill_extruder) - 1) :
|
||||
std::max<int>(region.config.perimeter_extruder.value - 1, 0);
|
||||
int correct_extruder_id = Print::get_extruder(*fill, region);
|
||||
//FIXME what is this?
|
||||
entity_type=="infills" ?
|
||||
std::max<int>(0, (is_solid_infill(fill->entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
|
||||
std::max<int>(region.config().perimeter_extruder.value - 1, 0);
|
||||
|
||||
// Let's recover vector of extruder overrides:
|
||||
const ExtruderPerCopy* entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(fill, correct_extruder_id, layer_to_print.object()->_shifted_copies.size());
|
||||
const ExtruderPerCopy* entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(fill, correct_extruder_id, layer_to_print.object()->copies().size());
|
||||
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
|
||||
for (unsigned int extruder : layer_tools.extruders)
|
||||
|
|
@ -1459,8 +1486,8 @@ void GCode::process_layer(
|
|||
// fill->first_point fits inside ith slice
|
||||
point_inside_surface(i, fill->first_point())) {
|
||||
if (islands[i].by_region.empty())
|
||||
islands[i].by_region.assign(print.regions.size(), ObjectByExtruder::Island::Region());
|
||||
islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->_shifted_copies.size());
|
||||
islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
|
||||
islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1494,7 +1521,7 @@ void GCode::process_layer(
|
|||
Flow skirt_flow = print.skirt_flow();
|
||||
for (size_t i = loops.first; i < loops.second; ++ i) {
|
||||
// Adjust flow according to this layer's layer height.
|
||||
ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt.entities[i]);
|
||||
ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
|
||||
Flow layer_skirt_flow(skirt_flow);
|
||||
layer_skirt_flow.height = (float)skirt_height;
|
||||
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
|
||||
|
|
@ -1515,7 +1542,7 @@ void GCode::process_layer(
|
|||
if (! m_brim_done) {
|
||||
this->set_origin(0., 0.);
|
||||
m_avoid_crossing_perimeters.use_external_mp = true;
|
||||
for (const ExtrusionEntity *ee : print.brim.entities)
|
||||
for (const ExtrusionEntity *ee : print.brim().entities)
|
||||
gcode += this->extrude_loop(*dynamic_cast<const ExtrusionLoop*>(ee), "brim", m_config.support_material_speed.value);
|
||||
m_brim_done = true;
|
||||
m_avoid_crossing_perimeters.use_external_mp = false;
|
||||
|
|
@ -1527,7 +1554,6 @@ void GCode::process_layer(
|
|||
auto objects_by_extruder_it = by_extruder.find(extruder_id);
|
||||
if (objects_by_extruder_it == by_extruder.end())
|
||||
continue;
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||
for (int print_wipe_extrusions=const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden(); print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
||||
if (print_wipe_extrusions == 0)
|
||||
|
|
@ -1540,15 +1566,15 @@ void GCode::process_layer(
|
|||
// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
|
||||
continue;
|
||||
|
||||
m_config.apply(print_object->config, true);
|
||||
m_config.apply(print_object->config(), true);
|
||||
m_layer = layers[layer_id].layer();
|
||||
if (m_config.avoid_crossing_perimeters)
|
||||
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
|
||||
Points copies;
|
||||
if (single_object_idx == size_t(-1))
|
||||
copies = print_object->_shifted_copies;
|
||||
copies = print_object->copies();
|
||||
else
|
||||
copies.push_back(print_object->_shifted_copies[single_object_idx]);
|
||||
copies.push_back(print_object->copies()[single_object_idx]);
|
||||
// Sort the copies by the closest point starting with the current print position.
|
||||
|
||||
unsigned int copy_id = 0;
|
||||
|
|
@ -1569,7 +1595,7 @@ void GCode::process_layer(
|
|||
for (ObjectByExtruder::Island &island : object_by_extruder.islands) {
|
||||
const auto& by_region_specific = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden() ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region;
|
||||
|
||||
if (print.config.infill_first) {
|
||||
if (print.config().infill_first) {
|
||||
gcode += this->extrude_infill(print, by_region_specific);
|
||||
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
|
||||
} else {
|
||||
|
|
@ -1612,14 +1638,14 @@ void GCode::apply_print_config(const PrintConfig &print_config)
|
|||
|
||||
void GCode::append_full_config(const Print& print, std::string& str)
|
||||
{
|
||||
const StaticPrintConfig *configs[] = { static_cast<const GCodeConfig*>(&print.config), &print.default_object_config, &print.default_region_config };
|
||||
const StaticPrintConfig *configs[] = { static_cast<const GCodeConfig*>(&print.config()), &print.default_object_config(), &print.default_region_config() };
|
||||
for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) {
|
||||
const StaticPrintConfig *cfg = configs[i];
|
||||
for (const std::string &key : cfg->keys())
|
||||
if (key != "compatible_printers")
|
||||
str += "; " + key + " = " + cfg->serialize(key) + "\n";
|
||||
}
|
||||
const DynamicConfig &full_config = print.placeholder_parser.config();
|
||||
const DynamicConfig &full_config = print.placeholder_parser().config();
|
||||
for (const char *key : {
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id",
|
||||
"printer_model", "printer_variant", "default_print_profile", "default_filament_profile",
|
||||
|
|
@ -2178,7 +2204,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
|
|||
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
|
||||
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
|
||||
else {
|
||||
CONFESS("Invalid argument supplied to extrude()");
|
||||
throw std::invalid_argument("Invalid argument supplied to extrude()");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -2202,7 +2228,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
|
|||
{
|
||||
std::string gcode;
|
||||
for (const ObjectByExtruder::Island::Region ®ion : by_region) {
|
||||
m_config.apply(print.regions[®ion - &by_region.front()]->config);
|
||||
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
||||
for (ExtrusionEntity *ee : region.perimeters.entities)
|
||||
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
|
||||
}
|
||||
|
|
@ -2214,7 +2240,7 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
|
|||
{
|
||||
std::string gcode;
|
||||
for (const ObjectByExtruder::Island::Region ®ion : by_region) {
|
||||
m_config.apply(print.regions[®ion - &by_region.front()]->config);
|
||||
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
||||
ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false);
|
||||
for (ExtrusionEntity *fill : chained.entities) {
|
||||
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
|
||||
|
|
@ -2363,7 +2389,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||
} else if (path.role() == erGapFill) {
|
||||
speed = m_config.get_abs_value("gap_fill_speed");
|
||||
} else {
|
||||
CONFESS("Invalid speed");
|
||||
throw std::invalid_argument("Invalid speed");
|
||||
}
|
||||
}
|
||||
if (this->on_first_layer())
|
||||
|
|
@ -2689,7 +2715,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const std::string& type, co
|
|||
}
|
||||
else
|
||||
if (type != "infills") {
|
||||
CONFESS("Unknown parameter!");
|
||||
throw std::invalid_argument("Unknown parameter!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,8 @@ public:
|
|||
{}
|
||||
~GCode() {}
|
||||
|
||||
// throws std::runtime_exception
|
||||
// throws std::runtime_exception on error,
|
||||
// throws CanceledException through print->throw_if_canceled().
|
||||
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
|
||||
|
||||
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
|
||||
|
|
@ -164,6 +165,7 @@ public:
|
|||
const Layer* layer() const { return m_layer; }
|
||||
GCodeWriter& writer() { return m_writer; }
|
||||
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
|
||||
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
|
||||
// Process a template through the placeholder parser, collect error messages to be reported
|
||||
// inside the generated string and after the G-code export finishes.
|
||||
std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
|
||||
|
|
|
|||
60
xs/src/libslic3r/GCode/PostProcessor.cpp
Normal file
60
xs/src/libslic3r/GCode/PostProcessor.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include "PostProcessor.hpp"
|
||||
|
||||
#if 1
|
||||
//#ifdef WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//FIXME Ignore until we include boost::process
|
||||
void run_post_process_scripts(const std::string &path, const PrintConfig &config)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#else
|
||||
|
||||
#include <boost/process/system.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void run_post_process_scripts(const std::string &path, const PrintConfig &config)
|
||||
{
|
||||
if (config.post_process.values.empty())
|
||||
return;
|
||||
config.setenv_();
|
||||
for (std::string script: config.post_process.values) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
if (! boost::filesystem::exists(boost::filesystem::path(path)))
|
||||
throw std::runtime_exception(std::string("The configured post-processing script does not exist: ") + path);
|
||||
#ifndef WIN32
|
||||
file_status fs = boost::filesystem::status(path);
|
||||
//FIXME test if executible by the effective UID / GID.
|
||||
// throw std::runtime_exception(std::string("The configured post-processing script is not executable: check permissions. ") + path));
|
||||
#endif
|
||||
int result = 0;
|
||||
#ifdef WIN32
|
||||
if (boost::iends_with(file, ".gcode")) {
|
||||
// The current process may be slic3r.exe or slic3r-console.exe.
|
||||
// Find the path of the process:
|
||||
wchar_t wpath_exe[_MAX_PATH + 1];
|
||||
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
|
||||
boost::filesystem::path path_exe(wpath_exe);
|
||||
// Replace it with the current perl interpreter.
|
||||
result = boost::process::system((path_exe.parent_path() / "perl5.24.0.exe").string(), script, output_file);
|
||||
} else
|
||||
#else
|
||||
result = boost::process::system(script, output_file);
|
||||
#endif
|
||||
if (result < 0)
|
||||
BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned.";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
15
xs/src/libslic3r/GCode/PostProcessor.hpp
Normal file
15
xs/src/libslic3r/GCode/PostProcessor.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef slic3r_GCode_PostProcessor_hpp_
|
||||
#define slic3r_GCode_PostProcessor_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
extern void run_post_process_scripts(const std::string &path, const PrintConfig &config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PostProcessor_hpp_ */
|
||||
|
|
@ -49,10 +49,10 @@ void PressureEqualizer::reset()
|
|||
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
|
||||
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
|
||||
// Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
|
||||
m_max_volumetric_extrusion_rate_slope_positive = (this->m_config == NULL) ? 6480.f :
|
||||
this->m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
|
||||
m_max_volumetric_extrusion_rate_slope_negative = (this->m_config == NULL) ? 6480.f :
|
||||
this->m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;
|
||||
m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f :
|
||||
m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
|
||||
m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f :
|
||||
m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;
|
||||
|
||||
for (size_t i = 0; i < numExtrusionRoles; ++ i) {
|
||||
m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
|
||||
|
|
@ -171,7 +171,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
|
|||
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
|
||||
line += strlen(EXTRUSION_ROLE_TAG);
|
||||
int role = atoi(line);
|
||||
this->m_current_extrusion_role = ExtrusionRole(role);
|
||||
m_current_extrusion_role = ExtrusionRole(role);
|
||||
++ line_idx;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,25 +93,25 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent
|
|||
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
|
||||
if (extrusion_entity_collection != nullptr)
|
||||
return extrusionentity_extents(*extrusion_entity_collection);
|
||||
CONFESS("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
return BoundingBoxf();
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_extrusions_extents(const Print &print)
|
||||
{
|
||||
BoundingBoxf bbox(extrusionentity_extents(print.brim));
|
||||
bbox.merge(extrusionentity_extents(print.skirt));
|
||||
BoundingBoxf bbox(extrusionentity_extents(print.brim()));
|
||||
bbox.merge(extrusionentity_extents(print.skirt()));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
for (const Layer *layer : print_object.layers) {
|
||||
for (const Layer *layer : print_object.layers()) {
|
||||
if (layer->print_z > max_print_z)
|
||||
break;
|
||||
BoundingBoxf bbox_this;
|
||||
for (const LayerRegion *layerm : layer->regions) {
|
||||
for (const LayerRegion *layerm : layer->regions()) {
|
||||
bbox_this.merge(extrusionentity_extents(layerm->perimeters));
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities)
|
||||
// fill represents infill extrusions of a single island.
|
||||
|
|
@ -121,7 +121,7 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
|
|||
if (support_layer)
|
||||
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
|
||||
bbox_this.merge(extrusionentity_extents(extrusion_entity));
|
||||
for (const Point &offset : print_object._shifted_copies) {
|
||||
for (const Point &offset : print_object.copies()) {
|
||||
BoundingBoxf bbox_translated(bbox_this);
|
||||
bbox_translated.translate(unscale(offset));
|
||||
bbox.merge(bbox_translated);
|
||||
|
|
@ -137,11 +137,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
|||
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
|
||||
// We need to get position and angle of the wipe tower to transform them to actual position.
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value) *
|
||||
Eigen::Rotation2Dd(print.config.wipe_tower_rotation_angle.value);
|
||||
Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
|
||||
Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value);
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.m_wipe_tower_tool_changes) {
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
|
||||
break;
|
||||
for (const WipeTower::ToolChangeResult &tcr : tool_changes) {
|
||||
|
|
@ -164,8 +164,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
|||
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
if (print.m_wipe_tower_priming) {
|
||||
const WipeTower::ToolChangeResult &tcr = *print.m_wipe_tower_priming.get();
|
||||
if (print.wipe_tower_data().priming != nullptr) {
|
||||
const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming;
|
||||
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
|
||||
const WipeTower::Extrusion &e = tcr.extrusions[i];
|
||||
if (e.width > 0) {
|
||||
|
|
|
|||
|
|
@ -34,19 +34,19 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
|
|||
|
||||
|
||||
// For the use case when each object is printed separately
|
||||
// (print.config.complete_objects is true).
|
||||
// (print.config().complete_objects is true).
|
||||
ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material)
|
||||
{
|
||||
if (object.layers.empty())
|
||||
if (object.layers().empty())
|
||||
return;
|
||||
|
||||
// Initialize the print layers for just a single object.
|
||||
{
|
||||
std::vector<coordf_t> zs;
|
||||
zs.reserve(zs.size() + object.layers.size() + object.support_layers.size());
|
||||
for (auto layer : object.layers)
|
||||
zs.reserve(zs.size() + object.layers().size() + object.support_layers().size());
|
||||
for (auto layer : object.layers())
|
||||
zs.emplace_back(layer->print_z);
|
||||
for (auto layer : object.support_layers)
|
||||
for (auto layer : object.support_layers())
|
||||
zs.emplace_back(layer->print_z);
|
||||
this->initialize_layers(zs);
|
||||
}
|
||||
|
|
@ -57,16 +57,16 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
|
|||
// Reorder the extruders to minimize tool switches.
|
||||
this->reorder_extruders(first_extruder);
|
||||
|
||||
this->fill_wipe_tower_partitions(object.print()->config, object.layers.front()->print_z - object.layers.front()->height);
|
||||
this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height);
|
||||
|
||||
this->collect_extruder_statistics(prime_multi_material);
|
||||
}
|
||||
|
||||
// For the use case when all objects are printed at once.
|
||||
// (print.config.complete_objects is false).
|
||||
// (print.config().complete_objects is false).
|
||||
ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material)
|
||||
{
|
||||
m_print_config_ptr = &print.config;
|
||||
m_print_config_ptr = &print.config();
|
||||
|
||||
PrintObjectPtrs objects = print.get_printable_objects();
|
||||
// Initialize the print layers for all objects and all layers.
|
||||
|
|
@ -74,13 +74,13 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
|||
{
|
||||
std::vector<coordf_t> zs;
|
||||
for (auto object : objects) {
|
||||
zs.reserve(zs.size() + object->layers.size() + object->support_layers.size());
|
||||
for (auto layer : object->layers)
|
||||
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
|
||||
for (auto layer : object->layers())
|
||||
zs.emplace_back(layer->print_z);
|
||||
for (auto layer : object->support_layers)
|
||||
for (auto layer : object->support_layers())
|
||||
zs.emplace_back(layer->print_z);
|
||||
if (! object->layers.empty())
|
||||
object_bottom_z = object->layers.front()->print_z - object->layers.front()->height;
|
||||
if (! object->layers().empty())
|
||||
object_bottom_z = object->layers().front()->print_z - object->layers().front()->height;
|
||||
}
|
||||
this->initialize_layers(zs);
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
|||
// Reorder the extruders to minimize tool switches.
|
||||
this->reorder_extruders(first_extruder);
|
||||
|
||||
this->fill_wipe_tower_partitions(print.config, object_bottom_z);
|
||||
this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
|
||||
|
||||
this->collect_extruder_statistics(prime_multi_material);
|
||||
}
|
||||
|
|
@ -133,13 +133,13 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
|
|||
void ToolOrdering::collect_extruders(const PrintObject &object)
|
||||
{
|
||||
// Collect the support extruders.
|
||||
for (auto support_layer : object.support_layers) {
|
||||
for (auto support_layer : object.support_layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
|
||||
ExtrusionRole role = support_layer->support_fills.role();
|
||||
bool has_support = role == erMixed || role == erSupportMaterial;
|
||||
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
|
||||
unsigned int extruder_support = object.config.support_material_extruder.value;
|
||||
unsigned int extruder_interface = object.config.support_material_interface_extruder.value;
|
||||
unsigned int extruder_support = object.config().support_material_extruder.value;
|
||||
unsigned int extruder_interface = object.config().support_material_interface_extruder.value;
|
||||
if (has_support)
|
||||
layer_tools.extruders.push_back(extruder_support);
|
||||
if (has_interface)
|
||||
|
|
@ -148,14 +148,14 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
|
|||
layer_tools.has_support = true;
|
||||
}
|
||||
// Collect the object extruders.
|
||||
for (auto layer : object.layers) {
|
||||
for (auto layer : object.layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
|
||||
// What extruders are required to print this object layer?
|
||||
for (size_t region_id = 0; region_id < object.print()->regions.size(); ++ region_id) {
|
||||
const LayerRegion *layerm = (region_id < layer->regions.size()) ? layer->regions[region_id] : nullptr;
|
||||
for (size_t region_id = 0; region_id < object.print()->regions().size(); ++ region_id) {
|
||||
const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
const PrintRegion ®ion = *object.print()->regions[region_id];
|
||||
const PrintRegion ®ion = *object.print()->regions()[region_id];
|
||||
|
||||
if (! layerm->perimeters.entities.empty()) {
|
||||
bool something_nonoverriddable = true;
|
||||
|
|
@ -170,7 +170,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
|
|||
}
|
||||
|
||||
if (something_nonoverriddable)
|
||||
layer_tools.extruders.push_back(region.config.perimeter_extruder.value);
|
||||
layer_tools.extruders.push_back(region.config().perimeter_extruder.value);
|
||||
|
||||
layer_tools.has_object = true;
|
||||
}
|
||||
|
|
@ -197,9 +197,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
|
|||
if (something_nonoverriddable || !m_print_config_ptr)
|
||||
{
|
||||
if (has_solid_infill)
|
||||
layer_tools.extruders.push_back(region.config.solid_infill_extruder);
|
||||
layer_tools.extruders.push_back(region.config().solid_infill_extruder);
|
||||
if (has_infill)
|
||||
layer_tools.extruders.push_back(region.config.infill_extruder);
|
||||
layer_tools.extruders.push_back(region.config().infill_extruder);
|
||||
}
|
||||
if (has_solid_infill || has_infill)
|
||||
layer_tools.has_object = true;
|
||||
|
|
@ -430,10 +430,10 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con
|
|||
if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region)))
|
||||
return false;
|
||||
|
||||
if (object.config.wipe_into_objects)
|
||||
if (object.config().wipe_into_objects)
|
||||
return true;
|
||||
|
||||
if (!region.config.wipe_into_infill || eec.role() != erInternalInfill)
|
||||
if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -447,12 +447,12 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
const LayerTools& lt = *m_layer_tools;
|
||||
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
|
||||
|
||||
if (print.config.filament_soluble.get_at(old_extruder) || print.config.filament_soluble.get_at(new_extruder))
|
||||
if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
|
||||
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
|
||||
|
||||
// we will sort objects so that dedicated for wiping are at the beginning:
|
||||
PrintObjectPtrs object_list = print.get_printable_objects();
|
||||
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config.wipe_into_objects; });
|
||||
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; });
|
||||
|
||||
// We will now iterate through
|
||||
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
|
||||
|
|
@ -462,7 +462,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
bool perimeters_done = false;
|
||||
|
||||
for (int i=0 ; i<(int)object_list.size() + (perimeters_done ? 0 : 1); ++i) {
|
||||
if (!perimeters_done && (i==(int)object_list.size() || !object_list[i]->config.wipe_into_objects)) { // we passed the last dedicated object in list
|
||||
if (!perimeters_done && (i==(int)object_list.size() || !object_list[i]->config().wipe_into_objects)) { // we passed the last dedicated object in list
|
||||
perimeters_done = true;
|
||||
i=-1; // let's go from the start again
|
||||
continue;
|
||||
|
|
@ -471,26 +471,26 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
const auto& object = object_list[i];
|
||||
|
||||
// Finds this layer:
|
||||
auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
|
||||
if (this_layer_it == object->layers.end())
|
||||
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
|
||||
if (this_layer_it == object->layers().end())
|
||||
continue;
|
||||
const Layer* this_layer = *this_layer_it;
|
||||
unsigned int num_of_copies = object->_shifted_copies.size();
|
||||
unsigned int num_of_copies = object->copies().size();
|
||||
|
||||
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
|
||||
|
||||
for (size_t region_id = 0; region_id < object->print()->regions.size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions[region_id];
|
||||
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions()[region_id];
|
||||
|
||||
if (!region.config.wipe_into_infill && !object->config.wipe_into_objects)
|
||||
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
|
||||
continue;
|
||||
|
||||
|
||||
if ((!print.config.infill_first ? perimeters_done : !perimeters_done) || (!object->config.wipe_into_objects && region.config.wipe_into_infill)) {
|
||||
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->fills.entities) { // iterate through all infill Collections
|
||||
if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) {
|
||||
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
|
||||
if (!is_overriddable(*fill, print.config, *object, region))
|
||||
if (!is_overriddable(*fill, print.config(), *object, region))
|
||||
continue;
|
||||
|
||||
// What extruder would this normally be printed with?
|
||||
|
|
@ -499,10 +499,10 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
if (volume_to_wipe<=0)
|
||||
continue;
|
||||
|
||||
if (!object->config.wipe_into_objects && !print.config.infill_first && region.config.wipe_into_infill)
|
||||
if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
|
||||
// In this case we must check that the original extruder is used on this layer before the one we are overridding
|
||||
// (and the perimeters will be finished before the infill is printed):
|
||||
if (!lt.is_extruder_order(region.config.perimeter_extruder - 1, new_extruder))
|
||||
if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
|
||||
continue;
|
||||
|
||||
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder
|
||||
|
|
@ -513,11 +513,11 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
}
|
||||
|
||||
// Now the same for perimeters - see comments above for explanation:
|
||||
if (object->config.wipe_into_objects && (print.config.infill_first ? perimeters_done : !perimeters_done))
|
||||
if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done))
|
||||
{
|
||||
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->perimeters.entities) {
|
||||
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
if (!is_overriddable(*fill, print.config, *object, region))
|
||||
if (!is_overriddable(*fill, print.config(), *object, region))
|
||||
continue;
|
||||
|
||||
if (volume_to_wipe<=0)
|
||||
|
|
@ -544,29 +544,29 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
||||
{
|
||||
const LayerTools& lt = *m_layer_tools;
|
||||
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config);
|
||||
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config);
|
||||
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
|
||||
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
|
||||
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
for (const PrintObject* object : printable_objects) {
|
||||
// Finds this layer:
|
||||
auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
|
||||
if (this_layer_it == object->layers.end())
|
||||
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
|
||||
if (this_layer_it == object->layers().end())
|
||||
continue;
|
||||
const Layer* this_layer = *this_layer_it;
|
||||
unsigned int num_of_copies = object->_shifted_copies.size();
|
||||
unsigned int num_of_copies = object->copies().size();
|
||||
|
||||
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
|
||||
for (size_t region_id = 0; region_id < object->print()->regions.size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions[region_id];
|
||||
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions()[region_id];
|
||||
|
||||
if (!region.config.wipe_into_infill && !object->config.wipe_into_objects)
|
||||
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
|
||||
continue;
|
||||
|
||||
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->fills.entities) { // iterate through all infill Collections
|
||||
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
|
||||
if (!is_overriddable(*fill, print.config, *object, region)
|
||||
if (!is_overriddable(*fill, print.config(), *object, region)
|
||||
|| is_entity_overridden(fill, copy) )
|
||||
continue;
|
||||
|
||||
|
|
@ -574,12 +574,12 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
// printed before its perimeter, or not be printed at all (in case its original extruder has
|
||||
// not been added to LayerTools
|
||||
// Either way, we will now force-override it with something suitable:
|
||||
if (print.config.infill_first
|
||||
|| object->config.wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||
|| lt.is_extruder_order(region.config.perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config.infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
|
||||
if (print.config().infill_first
|
||||
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||
|| lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
|
||||
)
|
||||
set_extruder_override(fill, copy, (print.config.infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
else {
|
||||
// In this case we can (and should) leave it to be printed normally.
|
||||
// Force overriding would mean it gets printed before its perimeter.
|
||||
|
|
@ -587,13 +587,13 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
}
|
||||
|
||||
// Now the same for perimeters - see comments above for explanation:
|
||||
for (const ExtrusionEntity* ee : this_layer->regions[region_id]->perimeters.entities) { // iterate through all perimeter Collections
|
||||
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
if (!is_overriddable(*fill, print.config, *object, region)
|
||||
if (!is_overriddable(*fill, print.config(), *object, region)
|
||||
|| is_entity_overridden(fill, copy) )
|
||||
continue;
|
||||
|
||||
set_extruder_override(fill, copy, (print.config.infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
|
||||
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1166,7 +1166,6 @@ void WipeTowerPrusaMM::save_on_last_wipe()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
||||
// Resulting ToolChangeResults are appended into vector "result"
|
||||
void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
||||
|
|
@ -1256,6 +1255,4 @@ void WipeTowerPrusaMM::make_wipe_tower_square()
|
|||
lay.extra_spacing = lay.depth / lay.toolchanges_depth();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
|
|||
(cmd_len == 3 && command.first[1] == '9' && command.first[2] == '2')) {
|
||||
for (size_t i = 0; i < NUM_AXES; ++ i)
|
||||
if (gline.has(Axis(i)))
|
||||
this->m_position[i] = gline.value(Axis(i));
|
||||
m_position[i] = gline.value(Axis(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "GCodeTimeEstimator.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include <boost/bind.hpp>
|
||||
#include <cmath>
|
||||
|
||||
|
|
@ -367,8 +368,7 @@ namespace Slic3r {
|
|||
fclose(out);
|
||||
in.close();
|
||||
|
||||
boost::nowide::remove(filename.c_str());
|
||||
if (boost::nowide::rename(path_tmp.c_str(), filename.c_str()) != 0)
|
||||
if (rename_file(path_tmp, filename) != 0)
|
||||
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
|
||||
"Is " + path_tmp + " locked?" + '\n');
|
||||
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo
|
|||
size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
|
||||
size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1)));
|
||||
if (num_parts > cellw * cellh)
|
||||
CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", num_parts);
|
||||
throw std::invalid_argument(PRINTF_ZU " parts won't fit in your print area!\n", num_parts);
|
||||
|
||||
// Get a bounding box of cellw x cellh cells, centered at the center of the bed.
|
||||
Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);
|
||||
|
|
|
|||
|
|
@ -12,24 +12,24 @@ namespace Slic3r {
|
|||
Layer::~Layer()
|
||||
{
|
||||
this->lower_layer = this->upper_layer = nullptr;
|
||||
for (LayerRegion *region : this->regions)
|
||||
for (LayerRegion *region : m_regions)
|
||||
delete region;
|
||||
this->regions.clear();
|
||||
m_regions.clear();
|
||||
}
|
||||
|
||||
LayerRegion* Layer::add_region(PrintRegion* print_region)
|
||||
{
|
||||
this->regions.emplace_back(new LayerRegion(this, print_region));
|
||||
return this->regions.back();
|
||||
m_regions.emplace_back(new LayerRegion(this, print_region));
|
||||
return m_regions.back();
|
||||
}
|
||||
|
||||
// merge all regions' slices to get islands
|
||||
void Layer::make_slices()
|
||||
{
|
||||
ExPolygons slices;
|
||||
if (this->regions.size() == 1) {
|
||||
if (m_regions.size() == 1) {
|
||||
// optimization: if we only have one region, take its slices
|
||||
slices = this->regions.front()->slices;
|
||||
slices = m_regions.front()->slices;
|
||||
} else {
|
||||
Polygons slices_p;
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
|
|
@ -58,10 +58,10 @@ void Layer::make_slices()
|
|||
|
||||
void Layer::merge_slices()
|
||||
{
|
||||
if (this->regions.size() == 1) {
|
||||
if (m_regions.size() == 1) {
|
||||
// Optimization, also more robust. Don't merge classified pieces of layerm->slices,
|
||||
// but use the non-split islands of a layer. For a single region print, these shall be equal.
|
||||
this->regions.front()->slices.set(this->slices.expolygons, stInternal);
|
||||
m_regions.front()->slices.set(this->slices.expolygons, stInternal);
|
||||
} else {
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
|
|
@ -81,18 +81,18 @@ void Layer::make_perimeters()
|
|||
std::set<size_t> done;
|
||||
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
size_t region_id = layerm - this->regions.begin();
|
||||
size_t region_id = layerm - m_regions.begin();
|
||||
if (done.find(region_id) != done.end()) continue;
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
|
||||
done.insert(region_id);
|
||||
const PrintRegionConfig &config = (*layerm)->region()->config;
|
||||
const PrintRegionConfig &config = (*layerm)->region()->config();
|
||||
|
||||
// find compatible regions
|
||||
LayerRegionPtrs layerms;
|
||||
layerms.push_back(*layerm);
|
||||
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != this->regions.end(); ++it) {
|
||||
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) {
|
||||
LayerRegion* other_layerm = *it;
|
||||
const PrintRegionConfig &other_config = other_layerm->region()->config;
|
||||
const PrintRegionConfig &other_config = other_layerm->region()->config();
|
||||
|
||||
if (config.perimeter_extruder == other_config.perimeter_extruder
|
||||
&& config.perimeters == other_config.perimeters
|
||||
|
|
@ -104,7 +104,7 @@ void Layer::make_perimeters()
|
|||
&& config.thin_walls == other_config.thin_walls
|
||||
&& config.external_perimeters_first == other_config.external_perimeters_first) {
|
||||
layerms.push_back(other_layerm);
|
||||
done.insert(it - this->regions.begin());
|
||||
done.insert(it - m_regions.begin());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,13 +150,12 @@ void Layer::make_fills()
|
|||
#ifdef SLIC3R_DEBUG
|
||||
printf("Making fills for layer " PRINTF_ZU "\n", this->id());
|
||||
#endif
|
||||
for (LayerRegionPtrs::iterator it_layerm = regions.begin(); it_layerm != regions.end(); ++ it_layerm) {
|
||||
LayerRegion &layerm = *(*it_layerm);
|
||||
layerm.fills.clear();
|
||||
make_fill(layerm, layerm.fills);
|
||||
for (LayerRegion *layerm : m_regions) {
|
||||
layerm->fills.clear();
|
||||
make_fill(*layerm, layerm->fills);
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < layerm.fills.entities.size(); ++ i)
|
||||
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm.fills.entities[i]) != NULL);
|
||||
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -164,18 +163,18 @@ void Layer::make_fills()
|
|||
void Layer::export_region_slices_to_svg(const char *path) const
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region)
|
||||
for (Surfaces::const_iterator surface = (*region)->slices.surfaces.begin(); surface != (*region)->slices.surfaces.end(); ++surface)
|
||||
bbox.merge(get_extents(surface->expolygon));
|
||||
for (const auto *region : m_regions)
|
||||
for (const auto &surface : region->slices.surfaces)
|
||||
bbox.merge(get_extents(surface.expolygon));
|
||||
Point legend_size = export_surface_type_legend_to_svg_box_size();
|
||||
Point legend_pos(bbox.min(0), bbox.max(1));
|
||||
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
||||
|
||||
SVG svg(path, bbox);
|
||||
const float transparency = 0.5f;
|
||||
for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region)
|
||||
for (Surfaces::const_iterator surface = (*region)->slices.surfaces.begin(); surface != (*region)->slices.surfaces.end(); ++surface)
|
||||
svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
|
||||
for (const auto *region : m_regions)
|
||||
for (const auto &surface : region->slices.surfaces)
|
||||
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
|
||||
export_surface_type_legend_to_svg(svg, legend_pos);
|
||||
svg.Close();
|
||||
}
|
||||
|
|
@ -190,18 +189,18 @@ void Layer::export_region_slices_to_svg_debug(const char *name) const
|
|||
void Layer::export_region_fill_surfaces_to_svg(const char *path) const
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region)
|
||||
for (Surfaces::const_iterator surface = (*region)->fill_surfaces.surfaces.begin(); surface != (*region)->fill_surfaces.surfaces.end(); ++surface)
|
||||
bbox.merge(get_extents(surface->expolygon));
|
||||
for (const auto *region : m_regions)
|
||||
for (const auto &surface : region->slices.surfaces)
|
||||
bbox.merge(get_extents(surface.expolygon));
|
||||
Point legend_size = export_surface_type_legend_to_svg_box_size();
|
||||
Point legend_pos(bbox.min(0), bbox.max(1));
|
||||
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
||||
|
||||
SVG svg(path, bbox);
|
||||
const float transparency = 0.5f;
|
||||
for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region)
|
||||
for (Surfaces::const_iterator surface = (*region)->fill_surfaces.surfaces.begin(); surface != (*region)->fill_surfaces.surfaces.end(); ++surface)
|
||||
svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
|
||||
for (const auto *region : m_regions)
|
||||
for (const auto &surface : region->slices.surfaces)
|
||||
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
|
||||
export_surface_type_legend_to_svg(svg, legend_pos);
|
||||
svg.Close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,87 +15,93 @@ class Layer;
|
|||
class PrintRegion;
|
||||
class PrintObject;
|
||||
|
||||
// TODO: make stuff private
|
||||
class LayerRegion
|
||||
{
|
||||
friend class Layer;
|
||||
|
||||
public:
|
||||
Layer* layer() { return this->_layer; }
|
||||
const Layer* layer() const { return this->_layer; }
|
||||
PrintRegion* region() { return this->_region; }
|
||||
const PrintRegion* region() const { return this->_region; }
|
||||
Layer* layer() { return m_layer; }
|
||||
const Layer* layer() const { return m_layer; }
|
||||
PrintRegion* region() { return m_region; }
|
||||
const PrintRegion* region() const { return m_region; }
|
||||
|
||||
// Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal.
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection slices;
|
||||
|
||||
// collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
ExtrusionEntityCollection thin_fills;
|
||||
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
ExPolygons fill_expolygons;
|
||||
// collection of surfaces for infill generation
|
||||
SurfaceCollection fill_surfaces;
|
||||
// Collection of extrusion paths/loops filling gaps.
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
ExtrusionEntityCollection thin_fills;
|
||||
|
||||
// Collection of expolygons representing the bridged areas (thus not needing support material).
|
||||
//FIXME Not used as of now.
|
||||
// Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces).
|
||||
// While not necessary, the memory consumption is meager and it speeds up calculation.
|
||||
// The perimeter_surfaces keep the IDs of the slices (top/bottom/)
|
||||
SurfaceCollection perimeter_surfaces;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
Polygons bridged;
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
PolylineCollection unsupported_bridge_edges;
|
||||
|
||||
// Ordered collection of extrusion paths/loops to build all perimeters.
|
||||
// This collection contains only ExtrusionEntityCollection objects.
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection perimeters;
|
||||
// Ordered collection of extrusion paths to fill surfaces.
|
||||
// This collection contains only ExtrusionEntityCollection objects.
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection fills;
|
||||
|
||||
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
|
||||
void process_external_surfaces(const Layer* lower_layer);
|
||||
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
|
||||
void process_external_surfaces(const Layer* lower_layer);
|
||||
double infill_area_threshold() const;
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); }
|
||||
bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); }
|
||||
|
||||
protected:
|
||||
friend class Layer;
|
||||
|
||||
LayerRegion(Layer *layer, PrintRegion *region) : m_layer(layer), m_region(region) {}
|
||||
~LayerRegion() {}
|
||||
|
||||
private:
|
||||
Layer *_layer;
|
||||
PrintRegion *_region;
|
||||
|
||||
LayerRegion(Layer *layer, PrintRegion *region) : _layer(layer), _region(region) {}
|
||||
~LayerRegion() {}
|
||||
Layer *m_layer;
|
||||
PrintRegion *m_region;
|
||||
};
|
||||
|
||||
|
||||
typedef std::vector<LayerRegion*> LayerRegionPtrs;
|
||||
|
||||
class Layer {
|
||||
friend class PrintObject;
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
size_t id() const { return this->_id; }
|
||||
void set_id(size_t id) { this->_id = id; }
|
||||
PrintObject* object() { return this->_object; }
|
||||
const PrintObject* object() const { return this->_object; }
|
||||
size_t id() const { return m_id; }
|
||||
void set_id(size_t id) { m_id = id; }
|
||||
PrintObject* object() { return m_object; }
|
||||
const PrintObject* object() const { return m_object; }
|
||||
|
||||
Layer *upper_layer;
|
||||
Layer *lower_layer;
|
||||
LayerRegionPtrs regions;
|
||||
bool slicing_errors;
|
||||
coordf_t slice_z; // Z used for slicing in unscaled coordinates
|
||||
coordf_t print_z; // Z used for printing in unscaled coordinates
|
||||
coordf_t height; // layer height in unscaled coordinates
|
||||
Layer *upper_layer;
|
||||
Layer *lower_layer;
|
||||
bool slicing_errors;
|
||||
coordf_t slice_z; // Z used for slicing in unscaled coordinates
|
||||
coordf_t print_z; // Z used for printing in unscaled coordinates
|
||||
coordf_t height; // layer height in unscaled coordinates
|
||||
|
||||
// collection of expolygons generated by slicing the original geometry;
|
||||
// also known as 'islands' (all regions and surface types are merged here)
|
||||
|
|
@ -103,57 +109,64 @@ public:
|
|||
// order will be recovered by the G-code generator.
|
||||
ExPolygonCollection slices;
|
||||
|
||||
size_t region_count() const { return this->regions.size(); }
|
||||
const LayerRegion* get_region(int idx) const { return this->regions.at(idx); }
|
||||
LayerRegion* get_region(int idx) { return this->regions.at(idx); }
|
||||
LayerRegion* add_region(PrintRegion* print_region);
|
||||
size_t region_count() const { return m_regions.size(); }
|
||||
const LayerRegion* get_region(int idx) const { return m_regions.at(idx); }
|
||||
LayerRegion* get_region(int idx) { return m_regions[idx]; }
|
||||
LayerRegion* add_region(PrintRegion* print_region);
|
||||
const LayerRegionPtrs& regions() const { return m_regions; }
|
||||
|
||||
void make_slices();
|
||||
void merge_slices();
|
||||
void make_slices();
|
||||
void merge_slices();
|
||||
template <class T> bool any_internal_region_slice_contains(const T &item) const {
|
||||
for (const LayerRegion *layerm : this->regions) if (layerm->slices.any_internal_contains(item)) return true;
|
||||
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true;
|
||||
return false;
|
||||
}
|
||||
template <class T> bool any_bottom_region_slice_contains(const T &item) const {
|
||||
for (const LayerRegion *layerm : this->regions) if (layerm->slices.any_bottom_contains(item)) return true;
|
||||
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true;
|
||||
return false;
|
||||
}
|
||||
void make_perimeters();
|
||||
void make_fills();
|
||||
void make_perimeters();
|
||||
void make_fills();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
virtual bool has_extrusions() const { for (auto layerm : this->regions) if (layerm->has_extrusions()) return true; return false; }
|
||||
virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; }
|
||||
|
||||
protected:
|
||||
size_t _id; // sequential number of layer, 0-based
|
||||
PrintObject *_object;
|
||||
friend class PrintObject;
|
||||
|
||||
Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false),
|
||||
slice_z(slice_z), print_z(print_z), height(height),
|
||||
_id(id), _object(object) {}
|
||||
m_id(id), m_object(object) {}
|
||||
virtual ~Layer();
|
||||
|
||||
private:
|
||||
// sequential number of layer, 0-based
|
||||
size_t m_id;
|
||||
PrintObject *m_object;
|
||||
LayerRegionPtrs m_regions;
|
||||
};
|
||||
|
||||
class SupportLayer : public Layer {
|
||||
friend class PrintObject;
|
||||
|
||||
class SupportLayer : public Layer
|
||||
{
|
||||
public:
|
||||
// Polygons covered by the supports: base, interface and contact areas.
|
||||
ExPolygonCollection support_islands;
|
||||
ExPolygonCollection support_islands;
|
||||
// Extrusion paths for the support base and for the support interface and contacts.
|
||||
ExtrusionEntityCollection support_fills;
|
||||
ExtrusionEntityCollection support_fills;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
virtual bool has_extrusions() const { return ! support_fills.empty(); }
|
||||
virtual bool has_extrusions() const { return ! support_fills.empty(); }
|
||||
|
||||
protected:
|
||||
friend class PrintObject;
|
||||
|
||||
//protected:
|
||||
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
|
||||
// between the raft and the object first layer.
|
||||
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ namespace Slic3r {
|
|||
|
||||
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
|
||||
{
|
||||
return this->_region->flow(
|
||||
return m_region->flow(
|
||||
role,
|
||||
this->_layer->height,
|
||||
m_layer->height,
|
||||
bridge,
|
||||
this->_layer->id() == 0,
|
||||
m_layer->id() == 0,
|
||||
width,
|
||||
*this->_layer->object()
|
||||
*m_layer->object()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -60,9 +60,9 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
|
|||
&slices,
|
||||
this->layer()->height,
|
||||
this->flow(frPerimeter),
|
||||
&this->region()->config,
|
||||
&this->layer()->object()->config,
|
||||
&this->layer()->object()->print()->config,
|
||||
&this->region()->config(),
|
||||
&this->layer()->object()->config(),
|
||||
&this->layer()->object()->print()->config(),
|
||||
|
||||
// output:
|
||||
&this->perimeters,
|
||||
|
|
@ -115,7 +115,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
{
|
||||
// bottom_polygons are used to trim inflated top surfaces.
|
||||
fill_boundaries.reserve(number_polygons(surfaces));
|
||||
bool has_infill = this->region()->config.fill_density.value > 0.;
|
||||
bool has_infill = this->region()->config().fill_density.value > 0.;
|
||||
for (const Surface &surface : this->fill_surfaces.surfaces) {
|
||||
if (surface.surface_type == stTop) {
|
||||
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
|
||||
|
|
@ -258,9 +258,9 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
#ifdef SLIC3R_DEBUG
|
||||
printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id());
|
||||
#endif
|
||||
if (bd.detect_angle(Geometry::deg2rad(this->region()->config.bridge_angle.value))) {
|
||||
if (bd.detect_angle(Geometry::deg2rad(this->region()->config().bridge_angle.value))) {
|
||||
bridges[idx_last].bridge_angle = bd.angle;
|
||||
if (this->layer()->object()->config.support_material) {
|
||||
if (this->layer()->object()->config().support_material) {
|
||||
polygons_append(this->bridged, bd.coverage());
|
||||
this->unsupported_bridge_edges.append(bd.unsupported_edges());
|
||||
}
|
||||
|
|
@ -350,13 +350,13 @@ void LayerRegion::prepare_fill_surfaces()
|
|||
the only meaningful information returned by psPerimeters. */
|
||||
|
||||
// if no solid layers are requested, turn top/bottom surfaces to internal
|
||||
if (this->region()->config.top_solid_layers == 0) {
|
||||
if (this->region()->config().top_solid_layers == 0) {
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
|
||||
if (surface->surface_type == stTop)
|
||||
surface->surface_type = (this->layer()->object()->config.infill_only_where_needed) ?
|
||||
surface->surface_type = (this->layer()->object()->config().infill_only_where_needed) ?
|
||||
stInternalVoid : stInternal;
|
||||
}
|
||||
if (this->region()->config.bottom_solid_layers == 0) {
|
||||
if (this->region()->config().bottom_solid_layers == 0) {
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge)
|
||||
surface->surface_type = stInternal;
|
||||
|
|
@ -364,9 +364,9 @@ void LayerRegion::prepare_fill_surfaces()
|
|||
}
|
||||
|
||||
// turn too small internal regions into solid regions according to the user setting
|
||||
if (this->region()->config.fill_density.value > 0) {
|
||||
if (this->region()->config().fill_density.value > 0) {
|
||||
// scaling an area requires two calls!
|
||||
double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value));
|
||||
double min_area = scale_(scale_(this->region()->config().solid_infill_below_area.value));
|
||||
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
|
||||
if (surface->surface_type == stInternal && surface->area() <= min_area)
|
||||
surface->surface_type = stInternalSolid;
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
|
|||
Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size()));
|
||||
Pointfs positions;
|
||||
if (! _arrange(model_sizes, dist, bb, positions))
|
||||
CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n");
|
||||
throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n");
|
||||
|
||||
// note that this will leave the object count unaltered
|
||||
|
||||
|
|
@ -671,7 +671,8 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
|
|||
BoundingBoxf3 bb;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part()) {
|
||||
if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances");
|
||||
if (this->instances.empty())
|
||||
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
|
||||
bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
|
||||
}
|
||||
return bb;
|
||||
|
|
@ -885,6 +886,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
return;
|
||||
}
|
||||
|
||||
// Called by Print::validate() from the UI thread.
|
||||
void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
|
||||
{
|
||||
for (const ModelVolume* vol : this->volumes)
|
||||
|
|
@ -989,8 +991,6 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
|
|||
ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
|
||||
{
|
||||
// Legacy support
|
||||
if (s == "0")
|
||||
return MODEL_PART;
|
||||
if (s == "1")
|
||||
return PARAMETER_MODIFIER;
|
||||
// New type (supporting the support enforcers & blockers)
|
||||
|
|
@ -1002,6 +1002,9 @@ ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
|
|||
return SUPPORT_ENFORCER;
|
||||
if (s == "SupportBlocker")
|
||||
return SUPPORT_BLOCKER;
|
||||
assert(s == "0");
|
||||
// Default value if invalud type string received.
|
||||
return MODEL_PART;
|
||||
}
|
||||
|
||||
std::string ModelVolume::type_to_string(const Type t)
|
||||
|
|
@ -1051,6 +1054,29 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
return idx;
|
||||
}
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
void ModelInstance::set_rotation(const Vec3d& rotation)
|
||||
{
|
||||
set_rotation(X, rotation(0));
|
||||
set_rotation(Y, rotation(1));
|
||||
set_rotation(Z, rotation(2));
|
||||
}
|
||||
|
||||
void ModelInstance::set_rotation(Axis axis, double rotation)
|
||||
{
|
||||
static const double TWO_PI = 2.0 * (double)PI;
|
||||
while (rotation < 0.0)
|
||||
{
|
||||
rotation += TWO_PI;
|
||||
}
|
||||
while (TWO_PI < rotation)
|
||||
{
|
||||
rotation -= TWO_PI;
|
||||
}
|
||||
m_rotation(axis) = rotation;
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
|
||||
{
|
||||
mesh->transform(world_matrix(dont_translate).cast<float>());
|
||||
|
|
@ -1095,7 +1121,12 @@ Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
|
|||
|
||||
void ModelInstance::transform_polygon(Polygon* polygon) const
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// CHECK_ME -> Is the following correct or it should take in account all three rotations ?
|
||||
polygon->rotate(this->m_rotation(2)); // rotate around polygon origin
|
||||
#else
|
||||
polygon->rotate(this->rotation); // rotate around polygon origin
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
polygon->scale(this->scaling_factor); // scale around polygon origin
|
||||
}
|
||||
|
||||
|
|
@ -1111,7 +1142,15 @@ Transform3d ModelInstance::world_matrix(bool dont_translate, bool dont_rotate, b
|
|||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
|
||||
if (!dont_rotate)
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
{
|
||||
m.rotate(Eigen::AngleAxisd(m_rotation(2), Vec3d::UnitZ()));
|
||||
m.rotate(Eigen::AngleAxisd(m_rotation(1), Vec3d::UnitY()));
|
||||
m.rotate(Eigen::AngleAxisd(m_rotation(0), Vec3d::UnitX()));
|
||||
}
|
||||
#else
|
||||
m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ()));
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
if (!dont_scale)
|
||||
m.scale(scaling_factor);
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ public:
|
|||
void cut(coordf_t z, Model* model) const;
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
|
||||
// Called by Print::validate() from the UI thread.
|
||||
void check_instances_print_volume_state(const BoundingBoxf3& print_volume);
|
||||
|
||||
// Print object statistics to console.
|
||||
|
|
@ -249,11 +250,16 @@ public:
|
|||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
private:
|
||||
Vec3d m_offset; // in unscaled coordinates
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
public:
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
|
||||
#if !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
double rotation; // Rotation around the Z axis, in radians around mesh center point
|
||||
#endif // !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
double scaling_factor;
|
||||
#if !ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
Vec2d offset; // in unscaled coordinates
|
||||
|
|
@ -272,6 +278,14 @@ public:
|
|||
void set_offset(Axis axis, double offset) { m_offset(axis) = offset; }
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const Vec3d& get_rotation() const { return m_rotation; }
|
||||
double get_rotation(Axis axis) const { return m_rotation(axis); }
|
||||
|
||||
void set_rotation(const Vec3d& rotation);
|
||||
void set_rotation(Axis axis, double rotation);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
// To be called on an external mesh
|
||||
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
|
||||
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
|
||||
|
|
@ -292,9 +306,15 @@ private:
|
|||
ModelObject* object;
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
ModelInstance(ModelObject *object) : m_rotation(Vec3d::Zero()), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {}
|
||||
ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
m_rotation(other.m_rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {}
|
||||
#else
|
||||
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {}
|
||||
ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
rotation(other.rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
#else
|
||||
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {}
|
||||
ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
|
|
|
|||
|
|
@ -292,59 +292,59 @@ protected:
|
|||
using Distance = TCoord<PointImpl>;
|
||||
using Pile = sl::Shapes<PolygonImpl>;
|
||||
|
||||
Packer pck_;
|
||||
PConfig pconf_; // Placement configuration
|
||||
double bin_area_;
|
||||
SpatIndex rtree_;
|
||||
SpatIndex smallsrtree_;
|
||||
double norm_;
|
||||
Pile merged_pile_;
|
||||
Box pilebb_;
|
||||
ItemGroup remaining_;
|
||||
ItemGroup items_;
|
||||
Packer m_pck;
|
||||
PConfig m_pconf; // Placement configuration
|
||||
double m_bin_area;
|
||||
SpatIndex m_rtree;
|
||||
SpatIndex m_smallsrtree;
|
||||
double m_norm;
|
||||
Pile m_merged_pile;
|
||||
Box m_pilebb;
|
||||
ItemGroup m_remaining;
|
||||
ItemGroup m_items;
|
||||
public:
|
||||
|
||||
_ArrBase(const TBin& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind,
|
||||
std::function<bool(void)> stopcond):
|
||||
pck_(bin, dist), bin_area_(sl::area(bin)),
|
||||
norm_(std::sqrt(sl::area(bin)))
|
||||
m_pck(bin, dist), m_bin_area(sl::area(bin)),
|
||||
m_norm(std::sqrt(sl::area(bin)))
|
||||
{
|
||||
fillConfig(pconf_);
|
||||
fillConfig(m_pconf);
|
||||
|
||||
pconf_.before_packing =
|
||||
m_pconf.before_packing =
|
||||
[this](const Pile& merged_pile, // merged pile
|
||||
const ItemGroup& items, // packed items
|
||||
const ItemGroup& remaining) // future items to be packed
|
||||
{
|
||||
items_ = items;
|
||||
merged_pile_ = merged_pile;
|
||||
remaining_ = remaining;
|
||||
m_items = items;
|
||||
m_merged_pile = merged_pile;
|
||||
m_remaining = remaining;
|
||||
|
||||
pilebb_ = sl::boundingBox(merged_pile);
|
||||
m_pilebb = sl::boundingBox(merged_pile);
|
||||
|
||||
rtree_.clear();
|
||||
smallsrtree_.clear();
|
||||
m_rtree.clear();
|
||||
m_smallsrtree.clear();
|
||||
|
||||
// We will treat big items (compared to the print bed) differently
|
||||
auto isBig = [this](double a) {
|
||||
return a/bin_area_ > BIG_ITEM_TRESHOLD ;
|
||||
return a/m_bin_area > BIG_ITEM_TRESHOLD ;
|
||||
};
|
||||
|
||||
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
||||
Item& itm = items[idx];
|
||||
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
|
||||
smallsrtree_.insert({itm.boundingBox(), idx});
|
||||
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
|
||||
m_smallsrtree.insert({itm.boundingBox(), idx});
|
||||
}
|
||||
};
|
||||
|
||||
pck_.progressIndicator(progressind);
|
||||
pck_.stopCondition(stopcond);
|
||||
m_pck.progressIndicator(progressind);
|
||||
m_pck.stopCondition(stopcond);
|
||||
}
|
||||
|
||||
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
|
||||
rtree_.clear();
|
||||
return pck_.executeIndexed(std::forward<Args>(args)...);
|
||||
m_rtree.clear();
|
||||
return m_pck.executeIndexed(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -358,18 +358,18 @@ public:
|
|||
_ArrBase<Box>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
|
||||
pconf_.object_function = [this, bin] (const Item &item) {
|
||||
m_pconf.object_function = [this, bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
merged_pile_,
|
||||
pilebb_,
|
||||
items_,
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
auto& fullbb = std::get<1>(result);
|
||||
|
|
@ -381,7 +381,7 @@ public:
|
|||
return score;
|
||||
};
|
||||
|
||||
pck_.configure(pconf_);
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -396,27 +396,27 @@ public:
|
|||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
|
||||
|
||||
pconf_.object_function = [this, &bin] (const Item &item) {
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto result = objfunc(bin.center(),
|
||||
merged_pile_,
|
||||
pilebb_,
|
||||
items_,
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
|
||||
auto isBig = [this](const Item& itm) {
|
||||
return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ;
|
||||
return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ;
|
||||
};
|
||||
|
||||
if(isBig(item)) {
|
||||
auto mp = merged_pile_;
|
||||
auto mp = m_merged_pile;
|
||||
mp.push_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(mp);
|
||||
double miss = Placer::overfit(chull, bin);
|
||||
|
|
@ -427,7 +427,7 @@ public:
|
|||
return score;
|
||||
};
|
||||
|
||||
pck_.configure(pconf_);
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -439,25 +439,25 @@ public:
|
|||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
|
||||
{
|
||||
pconf_.object_function = [this, &bin] (const Item &item) {
|
||||
m_pconf.object_function = [this, &bin] (const Item &item) {
|
||||
|
||||
auto binbb = sl::boundingBox(bin);
|
||||
auto result = objfunc(binbb.center(),
|
||||
merged_pile_,
|
||||
pilebb_,
|
||||
items_,
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
bin_area_,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
m_bin_area,
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
double score = std::get<0>(result);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
pck_.configure(pconf_);
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -469,22 +469,22 @@ public:
|
|||
std::function<bool(void)> stopcond):
|
||||
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
|
||||
{
|
||||
this->pconf_.object_function = [this] (const Item &item) {
|
||||
this->m_pconf.object_function = [this] (const Item &item) {
|
||||
|
||||
auto result = objfunc({0, 0},
|
||||
merged_pile_,
|
||||
pilebb_,
|
||||
items_,
|
||||
m_merged_pile,
|
||||
m_pilebb,
|
||||
m_items,
|
||||
item,
|
||||
0,
|
||||
norm_,
|
||||
rtree_,
|
||||
smallsrtree_,
|
||||
remaining_);
|
||||
m_norm,
|
||||
m_rtree,
|
||||
m_smallsrtree,
|
||||
m_remaining);
|
||||
return std::get<0>(result);
|
||||
};
|
||||
|
||||
this->pck_.configure(pconf_);
|
||||
this->m_pck.configure(m_pconf);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -527,14 +527,19 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
|||
|
||||
// Invalid geometries would throw exceptions when arranging
|
||||
if(item.vertexCount() > 3) {
|
||||
item.rotation(objinst->rotation);
|
||||
item.translation( {
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
ClipperLib::cInt(objinst->get_offset(X) / SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->get_offset(Y) / SCALING_FACTOR)
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// CHECK_ME -> is the following correct or it should take in account all three rotations ?
|
||||
item.rotation(objinst->get_rotation(Z));
|
||||
#else
|
||||
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
|
||||
item.rotation(objinst->rotation);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
item.translation({
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
|
||||
#else
|
||||
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
});
|
||||
ret.emplace_back(objinst, item);
|
||||
|
|
@ -668,18 +673,25 @@ void applyResult(
|
|||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the tranformation data from the item object and scale it
|
||||
// Get the transformation data from the item object and scale it
|
||||
// appropriately
|
||||
auto off = item.translation();
|
||||
Radians rot = item.rotation();
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
Vec3d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR, 0.0);
|
||||
Vec3d foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR,
|
||||
0.0);
|
||||
#else
|
||||
Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
|
||||
// write the tranformation data into the model instance
|
||||
// write the transformation data into the model instance
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
inst_ptr->set_rotation(Vec3d(0.0, 0.0, rot));
|
||||
#else
|
||||
inst_ptr->rotation = rot;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
#if ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
inst_ptr->set_offset(foff);
|
||||
#else
|
||||
|
|
@ -695,7 +707,7 @@ void applyResult(
|
|||
* The arrangement considers multiple bins (aka. print beds) for placing all
|
||||
* the items provided in the model argument. If the items don't fit on one
|
||||
* print bed, the remaining will be placed onto newly created print beds.
|
||||
* The first_bin_only parameter, if set to true, disables this behaviour and
|
||||
* The first_bin_only parameter, if set to true, disables this behavior and
|
||||
* makes sure that only one print bed is filled and the remaining items will be
|
||||
* untouched. When set to false, the items which could not fit onto the
|
||||
* print bed will be placed next to the print bed so the user should see a
|
||||
|
|
@ -741,6 +753,7 @@ bool arrange(Model &model, coordf_t min_obj_distance,
|
|||
|
||||
IndexedPackGroup result;
|
||||
|
||||
// If there is no hint about the shape, we will try to guess
|
||||
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
|
||||
|
||||
BoundingBox bbb(bed);
|
||||
|
|
|
|||
|
|
@ -37,30 +37,30 @@ typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
|
|||
class PerimeterGenerator {
|
||||
public:
|
||||
// Inputs:
|
||||
const SurfaceCollection* slices;
|
||||
const ExPolygonCollection* lower_slices;
|
||||
double layer_height;
|
||||
int layer_id;
|
||||
Flow perimeter_flow;
|
||||
Flow ext_perimeter_flow;
|
||||
Flow overhang_flow;
|
||||
Flow solid_infill_flow;
|
||||
PrintRegionConfig* config;
|
||||
PrintObjectConfig* object_config;
|
||||
PrintConfig* print_config;
|
||||
const SurfaceCollection *slices;
|
||||
const ExPolygonCollection *lower_slices;
|
||||
double layer_height;
|
||||
int layer_id;
|
||||
Flow perimeter_flow;
|
||||
Flow ext_perimeter_flow;
|
||||
Flow overhang_flow;
|
||||
Flow solid_infill_flow;
|
||||
const PrintRegionConfig *config;
|
||||
const PrintObjectConfig *object_config;
|
||||
const PrintConfig *print_config;
|
||||
// Outputs:
|
||||
ExtrusionEntityCollection* loops;
|
||||
ExtrusionEntityCollection* gap_fill;
|
||||
SurfaceCollection* fill_surfaces;
|
||||
ExtrusionEntityCollection *loops;
|
||||
ExtrusionEntityCollection *gap_fill;
|
||||
SurfaceCollection *fill_surfaces;
|
||||
|
||||
PerimeterGenerator(
|
||||
// Input:
|
||||
const SurfaceCollection* slices,
|
||||
double layer_height,
|
||||
Flow flow,
|
||||
PrintRegionConfig* config,
|
||||
PrintObjectConfig* object_config,
|
||||
PrintConfig* print_config,
|
||||
const PrintRegionConfig* config,
|
||||
const PrintObjectConfig* object_config,
|
||||
const PrintConfig* print_config,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection* loops,
|
||||
|
|
@ -78,15 +78,13 @@ public:
|
|||
void process();
|
||||
|
||||
private:
|
||||
double _ext_mm3_per_mm;
|
||||
double _mm3_per_mm;
|
||||
double _mm3_per_mm_overhang;
|
||||
Polygons _lower_slices_p;
|
||||
double _ext_mm3_per_mm;
|
||||
double _mm3_per_mm;
|
||||
double _mm3_per_mm_overhang;
|
||||
Polygons _lower_slices_p;
|
||||
|
||||
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops,
|
||||
ThickPolylines &thin_walls) const;
|
||||
ExtrusionEntityCollection _variable_width
|
||||
(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
|
||||
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const;
|
||||
ExtrusionEntityCollection _variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ PlaceholderParser::PlaceholderParser()
|
|||
this->update_timestamp();
|
||||
}
|
||||
|
||||
void PlaceholderParser::update_timestamp()
|
||||
void PlaceholderParser::update_timestamp(DynamicConfig &config)
|
||||
{
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
|
@ -84,14 +84,14 @@ void PlaceholderParser::update_timestamp()
|
|||
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour;
|
||||
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min;
|
||||
ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec;
|
||||
this->set("timestamp", ss.str());
|
||||
config.set_key_value("timestamp", new ConfigOptionString(ss.str()));
|
||||
}
|
||||
this->set("year", 1900 + timeinfo->tm_year);
|
||||
this->set("month", 1 + timeinfo->tm_mon);
|
||||
this->set("day", timeinfo->tm_mday);
|
||||
this->set("hour", timeinfo->tm_hour);
|
||||
this->set("minute", timeinfo->tm_min);
|
||||
this->set("second", timeinfo->tm_sec);
|
||||
config.set_key_value("year", new ConfigOptionInt(1900 + timeinfo->tm_year));
|
||||
config.set_key_value("month", new ConfigOptionInt(1 + timeinfo->tm_mon));
|
||||
config.set_key_value("day", new ConfigOptionInt(timeinfo->tm_mday));
|
||||
config.set_key_value("hour", new ConfigOptionInt(timeinfo->tm_hour));
|
||||
config.set_key_value("minute", new ConfigOptionInt(timeinfo->tm_min));
|
||||
config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec));
|
||||
}
|
||||
|
||||
// Scalar configuration values are stored into m_single,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class PlaceholderParser
|
|||
public:
|
||||
PlaceholderParser();
|
||||
|
||||
void update_timestamp();
|
||||
void apply_config(const DynamicPrintConfig &config);
|
||||
void apply_env_variables();
|
||||
|
||||
|
|
@ -37,6 +36,11 @@ public:
|
|||
// Throws std::runtime_error on syntax or runtime error.
|
||||
static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
|
||||
|
||||
// Update timestamp, year, month, day, hour, minute, second variables at the provided config.
|
||||
static void update_timestamp(DynamicConfig &config);
|
||||
// Update timestamp, year, month, day, hour, minute, second variables at m_config.
|
||||
void update_timestamp() { update_timestamp(m_config); }
|
||||
|
||||
private:
|
||||
DynamicConfig m_config;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Polygon::split_at_vertex(const Point &point) const
|
|||
for (const Point &pt : this->points)
|
||||
if (pt == point)
|
||||
return this->split_at_index(&pt - &this->points.front());
|
||||
CONFESS("Point not found");
|
||||
throw std::invalid_argument("Point not found");
|
||||
return Polyline();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ Polyline::operator Polylines() const
|
|||
|
||||
Polyline::operator Line() const
|
||||
{
|
||||
if (this->points.size() > 2) CONFESS("Can't convert polyline with more than two points to a line");
|
||||
if (this->points.size() > 2)
|
||||
throw std::invalid_argument("Can't convert polyline with more than two points to a line");
|
||||
return Line(this->points.front(), this->points.back());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ Polylines PolylineCollection::_chained_path_from(
|
|||
|
||||
Point PolylineCollection::leftmost_point(const Polylines &polylines)
|
||||
{
|
||||
if (polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection");
|
||||
if (polylines.empty())
|
||||
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
|
||||
Polylines::const_iterator it = polylines.begin();
|
||||
Point p = it->leftmost_point();
|
||||
for (++ it; it != polylines.end(); ++it) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,9 +2,11 @@
|
|||
#define slic3r_Print_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
|
@ -17,56 +19,110 @@
|
|||
#include "GCode/WipeTower.hpp"
|
||||
|
||||
#include "tbb/atomic.h"
|
||||
// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros.
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include "tbb/mutex.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class ModelObject;
|
||||
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum PrintStep {
|
||||
psSkirt, psBrim, psWipeTower, psCount,
|
||||
psSkirt, psBrim, psWipeTower, psGCodeExport, psCount,
|
||||
};
|
||||
enum PrintObjectStep {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
posInfill, posSupportMaterial, posCount,
|
||||
};
|
||||
|
||||
class CanceledException : public std::exception {
|
||||
public:
|
||||
const char* what() const throw() { return "Background processing has been canceled"; }
|
||||
};
|
||||
|
||||
// To be instantiated over PrintStep or PrintObjectStep enums.
|
||||
template <class StepType, size_t COUNT>
|
||||
class PrintState
|
||||
{
|
||||
public:
|
||||
PrintState() { memset(state, 0, sizeof(state)); }
|
||||
PrintState() { for (size_t i = 0; i < COUNT; ++ i) m_state[i].store(INVALID, std::memory_order_relaxed); }
|
||||
|
||||
enum State {
|
||||
INVALID,
|
||||
STARTED,
|
||||
DONE,
|
||||
};
|
||||
State state[COUNT];
|
||||
|
||||
bool is_started(StepType step) const { return this->state[step] == STARTED; }
|
||||
bool is_done(StepType step) const { return this->state[step] == DONE; }
|
||||
void set_started(StepType step) { this->state[step] = STARTED; }
|
||||
void set_done(StepType step) { this->state[step] = DONE; }
|
||||
bool invalidate(StepType step) {
|
||||
bool invalidated = this->state[step] != INVALID;
|
||||
this->state[step] = INVALID;
|
||||
// With full memory barrier.
|
||||
bool is_done(StepType step) const { return m_state[step] == DONE; }
|
||||
|
||||
// Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being
|
||||
// modified by the UI thread.
|
||||
// This is necessary to block until the Print::apply_config() updates its state, which may
|
||||
// influence the processing step being entered.
|
||||
void set_started(StepType step, tbb::mutex &mtx) {
|
||||
mtx.lock();
|
||||
m_state[step].store(STARTED, std::memory_order_relaxed);
|
||||
mtx.unlock();
|
||||
}
|
||||
|
||||
// Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being
|
||||
// modified by the UI thread.
|
||||
void set_done(StepType step, tbb::mutex &mtx) {
|
||||
mtx.lock();
|
||||
m_state[step].store(DONE, std::memory_order_relaxed);
|
||||
mtx.unlock();
|
||||
}
|
||||
|
||||
// Make the step invalid.
|
||||
// The provided mutex should be locked at this point, guarding access to m_state.
|
||||
// In case the step has already been entered or finished, cancel the background
|
||||
// processing by calling the cancel callback.
|
||||
template<typename CancelationCallback>
|
||||
bool invalidate(StepType step, tbb::mutex &mtx, CancelationCallback &cancel) {
|
||||
bool invalidated = m_state[step].load(std::memory_order_relaxed) != INVALID;
|
||||
if (invalidated) {
|
||||
#if 0
|
||||
if (mtx.state != mtx.HELD) {
|
||||
printf("Not held!\n");
|
||||
}
|
||||
#endif
|
||||
mtx.unlock();
|
||||
cancel();
|
||||
mtx.lock();
|
||||
}
|
||||
return invalidated;
|
||||
}
|
||||
bool invalidate_all() {
|
||||
|
||||
// Make all steps invalid.
|
||||
// The provided mutex should be locked at this point, guarding access to m_state.
|
||||
// In case any step has already been entered or finished, cancel the background
|
||||
// processing by calling the cancel callback.
|
||||
template<typename CancelationCallback>
|
||||
bool invalidate_all(tbb::mutex &mtx, CancelationCallback &cancel) {
|
||||
bool invalidated = false;
|
||||
for (size_t i = 0; i < COUNT; ++ i)
|
||||
if (this->state[i] != INVALID) {
|
||||
invalidated = true;
|
||||
break;
|
||||
if (m_state[i].load(std::memory_order_relaxed) != INVALID) {
|
||||
if (! invalidated) {
|
||||
mtx.unlock();
|
||||
cancel();
|
||||
mtx.lock();
|
||||
invalidated = true;
|
||||
}
|
||||
m_state[i].store(INVALID, std::memory_order_relaxed);
|
||||
}
|
||||
memset(state, 0, sizeof(state));
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<State> m_state[COUNT];
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
|
|
@ -75,20 +131,27 @@ class PrintRegion
|
|||
{
|
||||
friend class Print;
|
||||
|
||||
// Methods NOT modifying the PrintRegion's state:
|
||||
public:
|
||||
PrintRegionConfig config;
|
||||
|
||||
Print* print() { return this->_print; }
|
||||
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
|
||||
const Print* print() const { return m_print; }
|
||||
const PrintRegionConfig& config() const { return m_config; }
|
||||
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
|
||||
// Average diameter of nozzles participating on extruding this region.
|
||||
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
|
||||
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
|
||||
// Average diameter of nozzles participating on extruding this region.
|
||||
coordf_t bridging_height_avg(const PrintConfig &print_config) const;
|
||||
|
||||
// Methods modifying the PrintRegion's state:
|
||||
public:
|
||||
Print* print() { return m_print; }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
|
||||
private:
|
||||
Print* _print;
|
||||
Print *m_print;
|
||||
PrintRegionConfig m_config;
|
||||
|
||||
PrintRegion(Print* print) : _print(print) {}
|
||||
PrintRegion(Print* print) : m_print(print) {}
|
||||
PrintRegion(Print* print, const PrintRegionConfig &config) : m_print(print), m_config(config) {}
|
||||
~PrintRegion() {}
|
||||
};
|
||||
|
||||
|
|
@ -104,43 +167,35 @@ class PrintObject
|
|||
public:
|
||||
// vector of (vectors of volume ids), indexed by region_id
|
||||
std::vector<std::vector<int>> region_volumes;
|
||||
PrintObjectConfig config;
|
||||
t_layer_height_ranges layer_height_ranges;
|
||||
t_layer_height_ranges layer_height_ranges;
|
||||
|
||||
// Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
|
||||
// The pairs of <z, layer_height> are packed into a 1D array to simplify handling by the Perl XS.
|
||||
// layer_height_profile must not be set by the background thread.
|
||||
std::vector<coordf_t> layer_height_profile;
|
||||
std::vector<coordf_t> layer_height_profile;
|
||||
// There is a layer_height_profile at both PrintObject and ModelObject. The layer_height_profile at the ModelObject
|
||||
// is used for interactive editing and for loading / storing into a project file (AMF file as of today).
|
||||
// This flag indicates that the layer_height_profile at the UI has been updated, therefore the backend needs to get it.
|
||||
// This flag is necessary as we cannot safely clear the layer_height_profile if the background calculation is running.
|
||||
bool layer_height_profile_valid;
|
||||
bool layer_height_profile_valid;
|
||||
|
||||
// this is set to true when LayerRegion->slices is split in top/internal/bottom
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool typed_slices;
|
||||
bool typed_slices;
|
||||
|
||||
Vec3crd size; // XYZ in scaled coordinates
|
||||
Vec3crd size; // XYZ in scaled coordinates
|
||||
|
||||
// scaled coordinates to add to copies (to compensate for the alignment
|
||||
// operated when creating the object but still preserving a coherent API
|
||||
// for external callers)
|
||||
Point _copies_shift;
|
||||
Print* print() { return m_print; }
|
||||
const Print* print() const { return m_print; }
|
||||
ModelObject* model_object() { return m_model_object; }
|
||||
const ModelObject* model_object() const { return m_model_object; }
|
||||
const PrintObjectConfig& config() const { return m_config; }
|
||||
void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
const LayerPtrs& layers() const { return m_layers; }
|
||||
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
|
||||
|
||||
// Slic3r::Point objects in scaled G-code coordinates in our coordinates
|
||||
Points _shifted_copies;
|
||||
|
||||
LayerPtrs layers;
|
||||
SupportLayerPtrs support_layers;
|
||||
PrintState<PrintObjectStep, posCount> state;
|
||||
|
||||
Print* print() { return this->_print; }
|
||||
const Print* print() const { return this->_print; }
|
||||
ModelObject* model_object() { return this->_model_object; }
|
||||
const ModelObject* model_object() const { return this->_model_object; }
|
||||
|
||||
const Points& copies() const { return this->_copies; }
|
||||
const Points& copies() const { return m_copies; }
|
||||
bool add_copy(const Vec2d &point);
|
||||
bool delete_last_copy();
|
||||
bool delete_all_copies() { return this->set_copies(Points()); }
|
||||
|
|
@ -159,24 +214,26 @@ public:
|
|||
// this value is not supposed to be compared with Layer::id
|
||||
// since they have different semantics.
|
||||
size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
|
||||
size_t layer_count() const { return this->layers.size(); }
|
||||
size_t layer_count() const { return m_layers.size(); }
|
||||
void clear_layers();
|
||||
Layer* get_layer(int idx) { return this->layers.at(idx); }
|
||||
const Layer* get_layer(int idx) const { return this->layers.at(idx); }
|
||||
Layer* get_layer(int idx) { return m_layers[idx]; }
|
||||
const Layer* get_layer(int idx) const { return m_layers[idx]; }
|
||||
|
||||
// print_z: top of the layer; slice_z: center of the layer.
|
||||
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
|
||||
|
||||
size_t support_layer_count() const { return this->support_layers.size(); }
|
||||
size_t support_layer_count() const { return m_support_layers.size(); }
|
||||
void clear_support_layers();
|
||||
SupportLayer* get_support_layer(int idx) { return this->support_layers.at(idx); }
|
||||
SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; }
|
||||
SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z);
|
||||
SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
|
||||
void delete_support_layer(int idx);
|
||||
|
||||
// methods for handling state
|
||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
bool invalidate_step(PrintObjectStep step);
|
||||
bool invalidate_all_steps() { return this->state.invalidate_all(); }
|
||||
bool invalidate_all_steps();
|
||||
bool is_step_done(PrintObjectStep step) const { return m_state.is_done(step); }
|
||||
|
||||
// To be used over the layer_height_profile of both the PrintObject and ModelObject
|
||||
// to initialize the height profile with the height ranges.
|
||||
|
|
@ -196,168 +253,282 @@ public:
|
|||
// (layer height, first layer height, raft settings, print nozzle diameter etc).
|
||||
SlicingParameters slicing_parameters() const;
|
||||
|
||||
void _slice();
|
||||
std::string _fix_slicing_errors();
|
||||
void _simplify_slices(double distance);
|
||||
void _prepare_infill();
|
||||
bool has_support_material() const;
|
||||
void detect_surfaces_type();
|
||||
void process_external_surfaces();
|
||||
void discover_vertical_shells();
|
||||
void bridge_over_infill();
|
||||
void _make_perimeters();
|
||||
void _infill();
|
||||
void clip_fill_surfaces();
|
||||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
|
||||
bool is_printable() const { return !this->_shifted_copies.empty(); }
|
||||
// Called when slicing to SVG (see Print.pm sub export_svg), and used by perimeters.t
|
||||
void slice();
|
||||
|
||||
// Helpers to slice support enforcer / blocker meshes by the support generator.
|
||||
std::vector<ExPolygons> slice_support_enforcers() const;
|
||||
std::vector<ExPolygons> slice_support_blockers() const;
|
||||
|
||||
private:
|
||||
Print* _print;
|
||||
ModelObject* _model_object;
|
||||
Points _copies; // Slic3r::Point objects in scaled G-code coordinates
|
||||
void make_perimeters();
|
||||
void prepare_infill();
|
||||
void infill();
|
||||
void generate_support_material();
|
||||
|
||||
void _slice();
|
||||
std::string _fix_slicing_errors();
|
||||
void _simplify_slices(double distance);
|
||||
void _make_perimeters();
|
||||
bool has_support_material() const;
|
||||
void detect_surfaces_type();
|
||||
void process_external_surfaces();
|
||||
void discover_vertical_shells();
|
||||
void bridge_over_infill();
|
||||
void clip_fill_surfaces();
|
||||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
|
||||
bool is_printable() const { return ! m_copies.empty(); }
|
||||
|
||||
Print *m_print;
|
||||
ModelObject *m_model_object;
|
||||
PrintObjectConfig m_config;
|
||||
// Slic3r::Point objects in scaled G-code coordinates
|
||||
Points m_copies;
|
||||
// scaled coordinates to add to copies (to compensate for the alignment
|
||||
// operated when creating the object but still preserving a coherent API
|
||||
// for external callers)
|
||||
Point m_copies_shift;
|
||||
|
||||
LayerPtrs m_layers;
|
||||
SupportLayerPtrs m_support_layers;
|
||||
|
||||
PrintState<PrintObjectStep, posCount> m_state;
|
||||
|
||||
// TODO: call model_object->get_bounding_box() instead of accepting
|
||||
// parameter
|
||||
PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox);
|
||||
~PrintObject() {}
|
||||
|
||||
void set_started(PrintObjectStep step);
|
||||
void set_done(PrintObjectStep step);
|
||||
std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
|
||||
std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
|
||||
};
|
||||
|
||||
struct WipeTowerData
|
||||
{
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
|
||||
// Cache it here, so it does not need to be recalculated during the G-code generation.
|
||||
ToolOrdering tool_ordering;
|
||||
// Cache of tool changes per print layer.
|
||||
std::unique_ptr<WipeTower::ToolChangeResult> priming;
|
||||
std::vector<std::vector<WipeTower::ToolChangeResult>> tool_changes;
|
||||
std::unique_ptr<WipeTower::ToolChangeResult> final_purge;
|
||||
std::vector<float> used_filament;
|
||||
int number_of_toolchanges;
|
||||
|
||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||
float depth;
|
||||
|
||||
void clear() {
|
||||
tool_ordering.clear();
|
||||
priming.reset(nullptr);
|
||||
tool_changes.clear();
|
||||
final_purge.reset(nullptr);
|
||||
used_filament.clear();
|
||||
number_of_toolchanges = -1;
|
||||
depth = 0.f;
|
||||
}
|
||||
};
|
||||
|
||||
struct PrintStatistics
|
||||
{
|
||||
PrintStatistics() { clear(); }
|
||||
std::string estimated_normal_print_time;
|
||||
std::string estimated_silent_print_time;
|
||||
double total_used_filament;
|
||||
double total_extruded_volume;
|
||||
double total_cost;
|
||||
double total_weight;
|
||||
double total_wipe_tower_cost;
|
||||
double total_wipe_tower_filament;
|
||||
std::map<size_t, float> filament_stats;
|
||||
|
||||
void clear() {
|
||||
estimated_normal_print_time.clear();
|
||||
estimated_silent_print_time.clear();
|
||||
total_used_filament = 0.;
|
||||
total_extruded_volume = 0.;
|
||||
total_cost = 0.;
|
||||
total_weight = 0.;
|
||||
total_wipe_tower_cost = 0.;
|
||||
total_wipe_tower_filament = 0.;
|
||||
filament_stats.clear();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<PrintObject*> PrintObjectPtrs;
|
||||
typedef std::vector<PrintRegion*> PrintRegionPtrs;
|
||||
|
||||
class ProgressIndicator;
|
||||
using ProgressIndicatorPtr = std::shared_ptr<ProgressIndicator>;
|
||||
|
||||
// The complete print tray with possibly multiple objects.
|
||||
class Print
|
||||
{
|
||||
public:
|
||||
PrintConfig config;
|
||||
PrintObjectConfig default_object_config;
|
||||
PrintRegionConfig default_region_config;
|
||||
PrintObjectPtrs objects;
|
||||
PrintRegionPtrs regions;
|
||||
PlaceholderParser placeholder_parser;
|
||||
|
||||
// TODO: status_cb
|
||||
ProgressIndicatorPtr progressindicator;
|
||||
|
||||
std::string estimated_normal_print_time;
|
||||
std::string estimated_silent_print_time;
|
||||
double total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament;
|
||||
std::map<size_t, float> filament_stats;
|
||||
PrintState<PrintStep, psCount> state;
|
||||
|
||||
// ordered collections of extrusion paths to build skirt loops and brim
|
||||
ExtrusionEntityCollection skirt, brim;
|
||||
|
||||
Print() : total_used_filament(0), total_extruded_volume(0) { restart(); }
|
||||
Print() { restart(); }
|
||||
~Print() { clear_objects(); }
|
||||
|
||||
// methods for handling objects
|
||||
void clear_objects();
|
||||
PrintObject* get_object(size_t idx) { return objects.at(idx); }
|
||||
const PrintObject* get_object(size_t idx) const { return objects.at(idx); }
|
||||
|
||||
void delete_object(size_t idx);
|
||||
void reload_object(size_t idx);
|
||||
bool reload_model_instances();
|
||||
// Methods, which change the state of Print / PrintObject / PrintRegion.
|
||||
// The following methods are synchronized with process() and export_gcode(),
|
||||
// so that process() and export_gcode() may be called from a background thread.
|
||||
// In case the following methods need to modify data processed by process() or export_gcode(),
|
||||
// a cancellation callback is executed to stop the background processing before the operation.
|
||||
void clear_objects();
|
||||
void delete_object(size_t idx);
|
||||
void reload_object(size_t idx);
|
||||
bool reload_model_instances();
|
||||
void add_model_object(ModelObject* model_object, int idx = -1);
|
||||
bool apply_config(DynamicPrintConfig config);
|
||||
void process();
|
||||
void export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
|
||||
// SLA export, temporary.
|
||||
void export_png(const std::string &dirpath);
|
||||
|
||||
PrintObjectPtrs get_printable_objects() const;
|
||||
|
||||
// methods for handling regions
|
||||
PrintRegion* get_region(size_t idx) { return regions.at(idx); }
|
||||
const PrintRegion* get_region(size_t idx) const { return regions.at(idx); }
|
||||
PrintRegion* add_region();
|
||||
|
||||
// methods for handling state
|
||||
bool invalidate_step(PrintStep step);
|
||||
bool invalidate_all_steps() { return this->state.invalidate_all(); }
|
||||
bool step_done(PrintObjectStep step) const;
|
||||
|
||||
void add_model_object(ModelObject* model_object, int idx = -1);
|
||||
bool apply_config(DynamicPrintConfig config);
|
||||
float get_wipe_tower_depth() const { return m_wipe_tower_depth; }
|
||||
bool has_infinite_skirt() const;
|
||||
bool has_skirt() const;
|
||||
bool is_step_done(PrintStep step) const { return m_state.is_done(step); }
|
||||
bool is_step_done(PrintObjectStep step) const;
|
||||
|
||||
bool has_infinite_skirt() const;
|
||||
bool has_skirt() const;
|
||||
PrintObjectPtrs get_printable_objects() const;
|
||||
float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; }
|
||||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
std::string validate() const;
|
||||
BoundingBox bounding_box() const;
|
||||
BoundingBox total_bounding_box() const;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
std::string validate() const;
|
||||
BoundingBox bounding_box() const;
|
||||
BoundingBox total_bounding_box() const;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
|
||||
std::vector<unsigned int> object_extruders() const;
|
||||
std::vector<unsigned int> support_material_extruders() const;
|
||||
std::vector<unsigned int> extruders() const;
|
||||
void _simplify_slices(double distance);
|
||||
double max_allowed_layer_height() const;
|
||||
bool has_support_material() const;
|
||||
void auto_assign_extruders(ModelObject* model_object) const;
|
||||
double max_allowed_layer_height() const;
|
||||
bool has_support_material() const;
|
||||
// Make sure the background processing has no access to this model_object during this call!
|
||||
void auto_assign_extruders(ModelObject* model_object) const;
|
||||
|
||||
const PrintConfig& config() const { return m_config; }
|
||||
const PrintObjectConfig& default_object_config() const { return m_default_object_config; }
|
||||
const PrintRegionConfig& default_region_config() const { return m_default_region_config; }
|
||||
const PrintObjectPtrs& objects() const { return m_objects; }
|
||||
const PrintObject* get_object(int idx) const { return m_objects[idx]; }
|
||||
const PrintRegionPtrs& regions() const { return m_regions; }
|
||||
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
|
||||
|
||||
// Returns extruder this eec should be printed with, according to PrintRegion config:
|
||||
static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion);
|
||||
|
||||
void _make_skirt();
|
||||
void _make_brim();
|
||||
const ExtrusionEntityCollection& skirt() const { return m_skirt; }
|
||||
const ExtrusionEntityCollection& brim() const { return m_brim; }
|
||||
|
||||
const PrintStatistics& print_statistics() const { return m_print_statistics; }
|
||||
|
||||
// Wipe tower support.
|
||||
bool has_wipe_tower() const;
|
||||
void _clear_wipe_tower();
|
||||
void _make_wipe_tower();
|
||||
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
|
||||
// Cache it here, so it does not need to be recalculated during the G-code generation.
|
||||
ToolOrdering m_tool_ordering;
|
||||
// Cache of tool changes per print layer.
|
||||
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_priming;
|
||||
std::vector<std::vector<WipeTower::ToolChangeResult>> m_wipe_tower_tool_changes;
|
||||
std::unique_ptr<WipeTower::ToolChangeResult> m_wipe_tower_final_purge;
|
||||
std::vector<float> m_wipe_tower_used_filament;
|
||||
int m_wipe_tower_number_of_toolchanges = -1;
|
||||
bool has_wipe_tower() const;
|
||||
const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; }
|
||||
|
||||
std::string output_filename();
|
||||
std::string output_filepath(const std::string &path);
|
||||
std::string output_filename() const;
|
||||
std::string output_filepath(const std::string &path) const;
|
||||
|
||||
// Calls a registered callback to update the status.
|
||||
void set_status(int percent, const std::string &message);
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void cancel() { m_canceled = true; }
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void restart() { m_canceled = false; }
|
||||
typedef std::function<void(int, const std::string&)> status_callback_type;
|
||||
// Default status console print out in the form of percent => message.
|
||||
void set_status_default() { m_status_callback = nullptr; }
|
||||
// No status output or callback whatsoever, useful mostly for automatic tests.
|
||||
void set_status_silent() { m_status_callback = [](int, const std::string&){}; }
|
||||
// Register a custom status callback.
|
||||
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
|
||||
// Calls a registered callback to update the status, or print out the default message.
|
||||
void set_status(int percent, const std::string &message) {
|
||||
if (m_status_callback) m_status_callback(percent, message);
|
||||
else printf("%d => %s\n", percent, message.c_str());
|
||||
}
|
||||
|
||||
typedef std::function<void()> cancel_callback_type;
|
||||
// Various methods will call this callback to stop the background processing (the Print::process() call)
|
||||
// in case a successive change of the Print / PrintObject / PrintRegion instances changed
|
||||
// the state of the finished or running calculations.
|
||||
void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; }
|
||||
// Has the calculation been canceled?
|
||||
bool canceled() { return m_canceled; }
|
||||
bool canceled() const { return m_canceled; }
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void cancel() { m_canceled = true; }
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void restart() { m_canceled = false; }
|
||||
|
||||
void print_to_png(std::string dirpath);
|
||||
// Accessed by SupportMaterial
|
||||
const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; }
|
||||
|
||||
protected:
|
||||
void set_started(PrintStep step) { m_state.set_started(step, m_mutex); throw_if_canceled(); }
|
||||
void set_done(PrintStep step) { m_state.set_done(step, m_mutex); throw_if_canceled(); }
|
||||
bool invalidate_step(PrintStep step);
|
||||
bool invalidate_all_steps() { return m_state.invalidate_all(m_mutex, m_cancel_callback); }
|
||||
|
||||
// methods for handling regions
|
||||
PrintRegion* get_region(size_t idx) { return m_regions[idx]; }
|
||||
PrintRegion* add_region();
|
||||
PrintRegion* add_region(const PrintRegionConfig &config);
|
||||
|
||||
private:
|
||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
|
||||
|
||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
|
||||
// If the background processing stop was requested, throw CanceledException.
|
||||
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
|
||||
void throw_if_canceled() const { if (m_canceled) throw CanceledException(); }
|
||||
|
||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||
float m_wipe_tower_depth = 0.f;
|
||||
void _make_skirt();
|
||||
void _make_brim();
|
||||
void _make_wipe_tower();
|
||||
void _simplify_slices(double distance);
|
||||
|
||||
PrintState<PrintStep, psCount> m_state;
|
||||
// Mutex used for synchronization of the worker thread with the UI thread:
|
||||
// The mutex will be used to guard the worker thread against entering a stage
|
||||
// while the data influencing the stage is modified.
|
||||
mutable tbb::mutex m_mutex;
|
||||
|
||||
// Has the calculation been canceled?
|
||||
tbb::atomic<bool> m_canceled;
|
||||
tbb::atomic<bool> m_canceled;
|
||||
// Callback to be evoked regularly to update state of the UI thread.
|
||||
status_callback_type m_status_callback;
|
||||
|
||||
// Callback to be evoked to stop the background processing before a state is updated.
|
||||
cancel_callback_type m_cancel_callback = [](){};
|
||||
|
||||
PrintConfig m_config;
|
||||
PrintObjectConfig m_default_object_config;
|
||||
PrintRegionConfig m_default_region_config;
|
||||
PrintObjectPtrs m_objects;
|
||||
PrintRegionPtrs m_regions;
|
||||
PlaceholderParser m_placeholder_parser;
|
||||
|
||||
// Ordered collections of extrusion paths to build skirt loops and brim.
|
||||
ExtrusionEntityCollection m_skirt;
|
||||
ExtrusionEntityCollection m_brim;
|
||||
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
WipeTowerData m_wipe_tower_data;
|
||||
|
||||
// Estimated print time, filament consumed.
|
||||
PrintStatistics m_print_statistics;
|
||||
|
||||
// To allow GCode to set the Print's GCodeExport step status.
|
||||
friend class GCode;
|
||||
// Allow PrintObject to access m_mutex and m_cancel_callback.
|
||||
friend class PrintObject;
|
||||
};
|
||||
|
||||
|
||||
#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)
|
||||
#define FOREACH_REGION(print, region) FOREACH_BASE(PrintRegionPtrs, (print)->regions, region)
|
||||
#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->objects, object)
|
||||
#define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->layers, layer)
|
||||
#define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->regions, layerm)
|
||||
#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->m_objects, object)
|
||||
#define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->m_layers, layer)
|
||||
#define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->m_regions, layerm)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Speed for printing bridges.");
|
||||
def->sidetext = L("mm/s");
|
||||
def->cli = "bridge-speed=f";
|
||||
def->aliases.push_back("bridge_feed_rate");
|
||||
def->aliases = { "bridge_feed_rate" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloat(60);
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Distance used for the auto-arrange feature of the plater.");
|
||||
def->sidetext = L("mm");
|
||||
def->cli = "duplicate-distance=f";
|
||||
def->aliases.push_back("multiply_distance");
|
||||
def->aliases = { "multiply_distance" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloat(6);
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_labels.push_back(L("Archimedean Chords"));
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
// solid_fill_pattern is an obsolete equivalent to external_fill_pattern.
|
||||
def->aliases.push_back("solid_fill_pattern");
|
||||
def->aliases = { "solid_fill_pattern" };
|
||||
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
|
||||
|
||||
def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
|
||||
|
|
@ -923,8 +923,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Speed for printing the internal fill. Set to zero for auto.");
|
||||
def->sidetext = L("mm/s");
|
||||
def->cli = "infill-speed=f";
|
||||
def->aliases.push_back("print_feed_rate");
|
||||
def->aliases.push_back("infill_feed_rate");
|
||||
def->aliases = { "print_feed_rate", "infill_feed_rate" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloat(80);
|
||||
|
||||
|
|
@ -1272,7 +1271,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->category = L("Extruders");
|
||||
def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1.");
|
||||
def->cli = "perimeter-extruder=i";
|
||||
def->aliases.push_back("perimeters_extruder");
|
||||
def->aliases = { "perimeters_extruder" };
|
||||
def->min = 1;
|
||||
def->default_value = new ConfigOptionInt(1);
|
||||
|
||||
|
|
@ -1285,7 +1284,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"If expressed as percentage (for example 200%) it will be computed over layer height.");
|
||||
def->sidetext = L("mm or % (leave 0 for default)");
|
||||
def->cli = "perimeter-extrusion-width=s";
|
||||
def->aliases.push_back("perimeters_extrusion_width");
|
||||
def->aliases = { "perimeters_extrusion_width" };
|
||||
def->default_value = new ConfigOptionFloatOrPercent(0, false);
|
||||
|
||||
def = this->add("perimeter_speed", coFloat);
|
||||
|
|
@ -1294,7 +1293,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto.");
|
||||
def->sidetext = L("mm/s");
|
||||
def->cli = "perimeter-speed=f";
|
||||
def->aliases.push_back("perimeter_feed_rate");
|
||||
def->aliases = { "perimeter_feed_rate" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloat(60);
|
||||
|
||||
|
|
@ -1307,7 +1306,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"if the Extra Perimeters option is enabled.");
|
||||
def->sidetext = L("(minimum)");
|
||||
def->cli = "perimeters=i";
|
||||
def->aliases.push_back("perimeter_offsets");
|
||||
def->aliases = { "perimeter_offsets" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionInt(3);
|
||||
|
||||
|
|
@ -1635,7 +1634,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s or %");
|
||||
def->cli = "solid-infill-speed=s";
|
||||
def->ratio_over = "infill_speed";
|
||||
def->aliases.push_back("solid_infill_feed_rate");
|
||||
def->aliases = { "solid_infill_feed_rate" };
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloatOrPercent(20, false);
|
||||
|
||||
|
|
@ -1989,7 +1988,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
|
||||
def->sidetext = L("mm/s");
|
||||
def->cli = "travel-speed=f";
|
||||
def->aliases.push_back("travel_feed_rate");
|
||||
def->aliases = { "travel_feed_rate" };
|
||||
def->min = 1;
|
||||
def->default_value = new ConfigOptionFloat(130);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <wx/stdstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "Rasterizer/Rasterizer.hpp"
|
||||
|
|
@ -32,14 +28,14 @@ enum class FilePrinterFormat {
|
|||
* different implementations of this class template for each supported format.
|
||||
*
|
||||
*/
|
||||
template<FilePrinterFormat format>
|
||||
template<FilePrinterFormat format, class LayerFormat = void>
|
||||
class FilePrinter {
|
||||
public:
|
||||
|
||||
void printConfig(const Print&);
|
||||
void print_config(const Print&);
|
||||
|
||||
// Draw an ExPolygon which is a polygon inside a slice on the specified layer.
|
||||
void drawPolygon(const ExPolygon& p, unsigned lyr);
|
||||
void draw_polygon(const ExPolygon& p, unsigned lyr);
|
||||
|
||||
// Tell the printer how many layers should it consider.
|
||||
void layers(unsigned layernum);
|
||||
|
|
@ -51,32 +47,57 @@ public:
|
|||
* specified layer number than an appropriate number of layers will be
|
||||
* allocated in the printer.
|
||||
*/
|
||||
void beginLayer(unsigned layer);
|
||||
void begin_layer(unsigned layer);
|
||||
|
||||
// Allocate a new layer on top of the last and switch to it.
|
||||
void beginLayer();
|
||||
void begin_layer();
|
||||
|
||||
/*
|
||||
* Finish the selected layer. It means that no drawing is allowed on that
|
||||
* layer anymore. This fact can be used to prepare the file system output
|
||||
* data like png comprimation and so on.
|
||||
*/
|
||||
void finishLayer(unsigned layer);
|
||||
void finish_layer(unsigned layer);
|
||||
|
||||
// Finish the top layer.
|
||||
void finishLayer();
|
||||
void finish_layer();
|
||||
|
||||
// Save all the layers into the file (or dir) specified in the path argument
|
||||
void save(const std::string& path);
|
||||
|
||||
// Save only the selected layer to the file specified in path argument.
|
||||
void saveLayer(unsigned lyr, const std::string& path);
|
||||
void save_layer(unsigned lyr, const std::string& path);
|
||||
};
|
||||
|
||||
template<class T = void> struct VeryFalse { static const bool value = false; };
|
||||
|
||||
// This has to be explicitly implemented in the gui layer or a default zlib
|
||||
// based implementation is needed.
|
||||
template<class Backend> class LayerWriter {
|
||||
public:
|
||||
|
||||
LayerWriter(const std::string& /*zipfile_path*/) {
|
||||
static_assert(VeryFalse<Backend>::value,
|
||||
"No layer writer implementation provided!");
|
||||
}
|
||||
|
||||
void next_entry(const std::string& /*fname*/) {}
|
||||
|
||||
std::string get_name() { return ""; }
|
||||
|
||||
bool is_ok() { return false; }
|
||||
|
||||
template<class T> LayerWriter& operator<<(const T& /*arg*/) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void close() {}
|
||||
};
|
||||
|
||||
// Implementation for PNG raster output
|
||||
// Be aware that if a large number of layers are allocated, it can very well
|
||||
// exhaust the available memory especially on 32 bit platform.
|
||||
template<> class FilePrinter<FilePrinterFormat::PNG> {
|
||||
template<class LyrFormat> class FilePrinter<FilePrinterFormat::PNG, LyrFormat> {
|
||||
|
||||
struct Layer {
|
||||
Raster first;
|
||||
|
|
@ -91,22 +112,22 @@ template<> class FilePrinter<FilePrinterFormat::PNG> {
|
|||
|
||||
// We will save the compressed PNG data into stringstreams which can be done
|
||||
// in parallel. Later we can write every layer to the disk sequentially.
|
||||
std::vector<Layer> layers_rst_;
|
||||
Raster::Resolution res_;
|
||||
Raster::PixelDim pxdim_;
|
||||
const Print *print_ = nullptr;
|
||||
double exp_time_s_ = .0, exp_time_first_s_ = .0;
|
||||
std::vector<Layer> m_layers_rst;
|
||||
Raster::Resolution m_res;
|
||||
Raster::PixelDim m_pxdim;
|
||||
const Print *m_print = nullptr;
|
||||
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) {
|
||||
double layer_height = print_?
|
||||
print_->default_object_config.layer_height.getFloat() :
|
||||
double layer_height = m_print?
|
||||
m_print->default_object_config().layer_height.getFloat() :
|
||||
0.05;
|
||||
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
|
||||
auto expt_str = to_string(exp_time_s_);
|
||||
auto expt_first_str = to_string(exp_time_first_s_);
|
||||
auto expt_str = to_string(m_exp_time_s);
|
||||
auto expt_first_str = to_string(m_exp_time_first_s);
|
||||
auto stepnum_str = to_string(static_cast<unsigned>(800*layer_height));
|
||||
auto layerh_str = to_string(layer_height);
|
||||
|
||||
|
|
@ -134,92 +155,84 @@ public:
|
|||
inline FilePrinter(double width_mm, double height_mm,
|
||||
unsigned width_px, unsigned height_px,
|
||||
double exp_time, double exp_time_first):
|
||||
res_(width_px, height_px),
|
||||
pxdim_(width_mm/width_px, height_mm/height_px),
|
||||
exp_time_s_(exp_time),
|
||||
exp_time_first_s_(exp_time_first)
|
||||
m_res(width_px, height_px),
|
||||
m_pxdim(width_mm/width_px, height_mm/height_px),
|
||||
m_exp_time_s(exp_time),
|
||||
m_exp_time_first_s(exp_time_first)
|
||||
{
|
||||
}
|
||||
|
||||
FilePrinter(const FilePrinter& ) = delete;
|
||||
FilePrinter(FilePrinter&& m):
|
||||
layers_rst_(std::move(m.layers_rst_)),
|
||||
res_(m.res_),
|
||||
pxdim_(m.pxdim_) {}
|
||||
m_layers_rst(std::move(m.m_layers_rst)),
|
||||
m_res(m.m_res),
|
||||
m_pxdim(m.m_pxdim) {}
|
||||
|
||||
inline void layers(unsigned cnt) { if(cnt > 0) layers_rst_.resize(cnt); }
|
||||
inline unsigned layers() const { return layers_rst_.size(); }
|
||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||
|
||||
void printConfig(const Print& printconf) { print_ = &printconf; }
|
||||
void print_config(const Print& printconf) { m_print = &printconf; }
|
||||
|
||||
inline void drawPolygon(const ExPolygon& p, unsigned lyr) {
|
||||
assert(lyr < layers_rst_.size());
|
||||
layers_rst_[lyr].first.draw(p);
|
||||
inline void draw_polygon(const ExPolygon& p, unsigned lyr) {
|
||||
assert(lyr < m_layers_rst.size());
|
||||
m_layers_rst[lyr].first.draw(p);
|
||||
}
|
||||
|
||||
inline void beginLayer(unsigned lyr) {
|
||||
if(layers_rst_.size() <= lyr) layers_rst_.resize(lyr+1);
|
||||
layers_rst_[lyr].first.reset(res_, pxdim_, ORIGIN);
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].first.reset(m_res, m_pxdim, ORIGIN);
|
||||
}
|
||||
|
||||
inline void beginLayer() {
|
||||
layers_rst_.emplace_back();
|
||||
layers_rst_.front().first.reset(res_, pxdim_, ORIGIN);
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().first.reset(m_res, m_pxdim, ORIGIN);
|
||||
}
|
||||
|
||||
inline void finishLayer(unsigned lyr_id) {
|
||||
assert(lyr_id < layers_rst_.size());
|
||||
layers_rst_[lyr_id].first.save(layers_rst_[lyr_id].second,
|
||||
inline void finish_layer(unsigned lyr_id) {
|
||||
assert(lyr_id < m_layers_rst.size());
|
||||
m_layers_rst[lyr_id].first.save(m_layers_rst[lyr_id].second,
|
||||
Raster::Compression::PNG);
|
||||
layers_rst_[lyr_id].first.reset();
|
||||
m_layers_rst[lyr_id].first.reset();
|
||||
}
|
||||
|
||||
inline void finishLayer() {
|
||||
if(!layers_rst_.empty()) {
|
||||
layers_rst_.back().first.save(layers_rst_.back().second,
|
||||
inline void finish_layer() {
|
||||
if(!m_layers_rst.empty()) {
|
||||
m_layers_rst.back().first.save(m_layers_rst.back().second,
|
||||
Raster::Compression::PNG);
|
||||
layers_rst_.back().first.reset();
|
||||
m_layers_rst.back().first.reset();
|
||||
}
|
||||
}
|
||||
|
||||
inline void save(const std::string& path) {
|
||||
try {
|
||||
LayerWriter<LyrFormat> writer(path);
|
||||
|
||||
wxFileName filepath(path);
|
||||
std::string project = writer.get_name();
|
||||
|
||||
wxFFileOutputStream zipfile(path);
|
||||
writer.next_entry("config.ini");
|
||||
writer << createIniContent(project);
|
||||
|
||||
std::string project = filepath.GetName().ToStdString();
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++) {
|
||||
if(m_layers_rst[i].second.rdbuf()->in_avail() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
writer.next_entry(zfilename);
|
||||
writer << m_layers_rst[i].second.rdbuf();
|
||||
m_layers_rst[i].second.str("");
|
||||
}
|
||||
}
|
||||
|
||||
if(!zipfile.IsOk()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create zip file for layers! "
|
||||
<< path;
|
||||
writer.close();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
return;
|
||||
}
|
||||
|
||||
wxZipOutputStream zipstream(zipfile);
|
||||
wxStdOutputStream pngstream(zipstream);
|
||||
|
||||
zipstream.PutNextEntry("config.ini");
|
||||
pngstream << createIniContent(project);
|
||||
|
||||
for(unsigned i = 0; i < layers_rst_.size(); i++) {
|
||||
if(layers_rst_[i].second.rdbuf()->in_avail() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
zipstream.PutNextEntry(zfilename);
|
||||
pngstream << layers_rst_[i].second.rdbuf();
|
||||
layers_rst_[i].second.str("");
|
||||
}
|
||||
}
|
||||
|
||||
zipstream.Close();
|
||||
zipfile.Close();
|
||||
}
|
||||
|
||||
void saveLayer(unsigned lyr, const std::string& path) {
|
||||
void save_layer(unsigned lyr, const std::string& path) {
|
||||
unsigned i = lyr;
|
||||
assert(i < layers_rst_.size());
|
||||
assert(i < m_layers_rst.size());
|
||||
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", lyr);
|
||||
|
|
@ -227,23 +240,23 @@ public:
|
|||
|
||||
std::fstream out(loc, std::fstream::out | std::fstream::binary);
|
||||
if(out.good()) {
|
||||
layers_rst_[i].first.save(out, Raster::Compression::PNG);
|
||||
m_layers_rst[i].first.save(out, Raster::Compression::PNG);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create file for layer";
|
||||
}
|
||||
|
||||
out.close();
|
||||
layers_rst_[i].first.reset();
|
||||
m_layers_rst[i].first.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Let's shadow this eigen interface
|
||||
inline coord_t px(const Point& p) { return p(0); }
|
||||
inline coord_t py(const Point& p) { return p(1); }
|
||||
inline coord_t px(const Point& p) { return p(0); }
|
||||
inline coord_t py(const Point& p) { return p(1); }
|
||||
inline coordf_t px(const Vec2d& p) { return p(0); }
|
||||
inline coordf_t py(const Vec2d& p) { return p(1); }
|
||||
|
||||
template<FilePrinterFormat format, class...Args>
|
||||
template<FilePrinterFormat format, class LayerFormat, class...Args>
|
||||
void print_to(Print& print,
|
||||
std::string dirpath,
|
||||
double width_mm,
|
||||
|
|
@ -258,16 +271,18 @@ void print_to(Print& print,
|
|||
// rasterized to the same image.
|
||||
std::map<long long, LayerPtrs> layers;
|
||||
|
||||
auto& objects = print.objects;
|
||||
auto& objects = print.objects();
|
||||
|
||||
// Merge the sliced layers with the support layers
|
||||
std::for_each(objects.begin(), objects.end(), [&layers](PrintObject *o) {
|
||||
for(auto l : o->layers) {
|
||||
std::for_each(objects.cbegin(), objects.cend(),
|
||||
[&layers](const PrintObject *o)
|
||||
{
|
||||
for(const auto l : o->layers()) {
|
||||
auto& lyrs = layers[static_cast<long long>(scale_(l->print_z))];
|
||||
lyrs.push_back(l);
|
||||
}
|
||||
|
||||
for(auto l : o->support_layers) {
|
||||
for(const auto l : o->support_layers()) {
|
||||
auto& lyrs = layers[static_cast<long long>(scale_(l->print_z))];
|
||||
lyrs.push_back(l);
|
||||
}
|
||||
|
|
@ -288,10 +303,10 @@ void print_to(Print& print,
|
|||
auto cy = scale_(height_mm)/2 - (py(print_bb.center()) - py(print_bb.min));
|
||||
|
||||
// Create the actual printer, forward any additional arguments to it.
|
||||
FilePrinter<format> printer(width_mm, height_mm,
|
||||
std::forward<Args>(args)...);
|
||||
FilePrinter<format, LayerFormat> printer(width_mm, height_mm,
|
||||
std::forward<Args>(args)...);
|
||||
|
||||
printer.printConfig(print);
|
||||
printer.print_config(print);
|
||||
|
||||
printer.layers(layers.size()); // Allocate space for all the layers
|
||||
|
||||
|
|
@ -303,18 +318,16 @@ void print_to(Print& print,
|
|||
keys.reserve(layers.size());
|
||||
for(auto& e : layers) keys.push_back(e.first);
|
||||
|
||||
int initstatus = print.progressindicator? print.progressindicator->state()
|
||||
: 0;
|
||||
print.set_status(initstatus, jobdesc);
|
||||
print.set_status(0, jobdesc);
|
||||
|
||||
// Method that prints one layer
|
||||
auto process_layer = [&layers, &keys, &printer, &st_prev, &m,
|
||||
&jobdesc, print_bb, dir, cx, cy, &print, initstatus]
|
||||
&jobdesc, print_bb, dir, cx, cy, &print]
|
||||
(unsigned layer_id)
|
||||
{
|
||||
LayerPtrs lrange = layers[keys[layer_id]];
|
||||
|
||||
printer.beginLayer(layer_id); // Switch to the appropriate layer
|
||||
printer.begin_layer(layer_id); // Switch to the appropriate layer
|
||||
|
||||
for(Layer *lp : lrange) {
|
||||
Layer& l = *lp;
|
||||
|
|
@ -329,21 +342,14 @@ void print_to(Print& print,
|
|||
});
|
||||
|
||||
// Draw all the polygons in the slice to the actual layer.
|
||||
std::for_each(l.object()->_shifted_copies.begin(),
|
||||
l.object()->_shifted_copies.end(),
|
||||
[&] (Point d)
|
||||
{
|
||||
std::for_each(slices.expolygons.begin(),
|
||||
slices.expolygons.end(),
|
||||
[&] (ExPolygon slice)
|
||||
{
|
||||
for (const Point &d : l.object()->copies())
|
||||
for (ExPolygon slice : slices.expolygons) {
|
||||
slice.translate(px(d), py(d));
|
||||
slice.translate(-px(print_bb.min) + cx,
|
||||
-py(print_bb.min) + cy);
|
||||
|
||||
printer.drawPolygon(slice, layer_id);
|
||||
});
|
||||
});
|
||||
printer.draw_polygon(slice, layer_id);
|
||||
}
|
||||
|
||||
/*if(print.has_support_material() && layer_id > 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "support material for layer "
|
||||
|
|
@ -355,12 +361,12 @@ void print_to(Print& print,
|
|||
|
||||
}
|
||||
|
||||
printer.finishLayer(layer_id); // Finish the layer for later saving it.
|
||||
printer.finish_layer(layer_id); // Finish the layer for later saving it.
|
||||
|
||||
auto st = static_cast<int>(layer_id*80.0/layers.size());
|
||||
m.lock();
|
||||
if( st - st_prev > 10) {
|
||||
print.set_status(initstatus + st, jobdesc);
|
||||
print.set_status(st, jobdesc);
|
||||
st_prev = st;
|
||||
}
|
||||
m.unlock();
|
||||
|
|
@ -379,9 +385,9 @@ void print_to(Print& print,
|
|||
// print.set_status(100, jobdesc);
|
||||
|
||||
// Save the print into the file system.
|
||||
print.set_status(initstatus + 90, "Writing layers to disk");
|
||||
print.set_status(90, "Writing layers to disk");
|
||||
printer.save(dir);
|
||||
print.set_status(initstatus + 100, "Writing layers completed");
|
||||
print.set_status(100, "Writing layers completed");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
Flow
|
||||
PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
|
||||
Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
|
||||
{
|
||||
ConfigOptionFloatOrPercent config_width;
|
||||
if (width != -1) {
|
||||
|
|
@ -13,53 +12,53 @@ PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_la
|
|||
} else {
|
||||
// otherwise, get extrusion width from configuration
|
||||
// (might be an absolute value, or a percent value, or zero for auto)
|
||||
if (first_layer && this->_print->config.first_layer_extrusion_width.value > 0) {
|
||||
config_width = this->_print->config.first_layer_extrusion_width;
|
||||
if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
|
||||
config_width = m_print->config().first_layer_extrusion_width;
|
||||
} else if (role == frExternalPerimeter) {
|
||||
config_width = this->config.external_perimeter_extrusion_width;
|
||||
config_width = m_config.external_perimeter_extrusion_width;
|
||||
} else if (role == frPerimeter) {
|
||||
config_width = this->config.perimeter_extrusion_width;
|
||||
config_width = m_config.perimeter_extrusion_width;
|
||||
} else if (role == frInfill) {
|
||||
config_width = this->config.infill_extrusion_width;
|
||||
config_width = m_config.infill_extrusion_width;
|
||||
} else if (role == frSolidInfill) {
|
||||
config_width = this->config.solid_infill_extrusion_width;
|
||||
config_width = m_config.solid_infill_extrusion_width;
|
||||
} else if (role == frTopSolidInfill) {
|
||||
config_width = this->config.top_infill_extrusion_width;
|
||||
config_width = m_config.top_infill_extrusion_width;
|
||||
} else {
|
||||
CONFESS("Unknown role");
|
||||
throw std::invalid_argument("Unknown role");
|
||||
}
|
||||
}
|
||||
if (config_width.value == 0) {
|
||||
config_width = object.config.extrusion_width;
|
||||
config_width = object.config().extrusion_width;
|
||||
}
|
||||
|
||||
// get the configured nozzle_diameter for the extruder associated
|
||||
// to the flow role requested
|
||||
size_t extruder = 0; // 1-based
|
||||
if (role == frPerimeter || role == frExternalPerimeter) {
|
||||
extruder = this->config.perimeter_extruder;
|
||||
extruder = m_config.perimeter_extruder;
|
||||
} else if (role == frInfill) {
|
||||
extruder = this->config.infill_extruder;
|
||||
extruder = m_config.infill_extruder;
|
||||
} else if (role == frSolidInfill || role == frTopSolidInfill) {
|
||||
extruder = this->config.solid_infill_extruder;
|
||||
extruder = m_config.solid_infill_extruder;
|
||||
} else {
|
||||
CONFESS("Unknown role $role");
|
||||
throw std::invalid_argument("Unknown role");
|
||||
}
|
||||
double nozzle_diameter = this->_print->config.nozzle_diameter.get_at(extruder-1);
|
||||
double nozzle_diameter = m_print->config().nozzle_diameter.get_at(extruder-1);
|
||||
|
||||
return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)this->config.bridge_flow_ratio : 0.0);
|
||||
return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0);
|
||||
}
|
||||
|
||||
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
|
||||
{
|
||||
return (print_config.nozzle_diameter.get_at(this->config.perimeter_extruder.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(this->config.infill_extruder.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.;
|
||||
return (print_config.nozzle_diameter.get_at(m_config.perimeter_extruder.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.infill_extruder.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.solid_infill_extruder.value - 1)) / 3.;
|
||||
}
|
||||
|
||||
coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
|
||||
{
|
||||
return this->nozzle_dmr_avg(print_config) * sqrt(this->config.bridge_flow_ratio.value);
|
||||
return this->nozzle_dmr_avg(print_config) * sqrt(m_config.bridge_flow_ratio.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,37 +37,37 @@ public:
|
|||
using Origin = Raster::Origin;
|
||||
|
||||
private:
|
||||
Raster::Resolution resolution_;
|
||||
Raster::PixelDim pxdim_;
|
||||
TBuffer buf_;
|
||||
TRawBuffer rbuf_;
|
||||
TPixelRenderer pixfmt_;
|
||||
TRawRenderer raw_renderer_;
|
||||
TRendererAA renderer_;
|
||||
Origin o_;
|
||||
std::function<void(agg::path_storage&)> flipy_ = [](agg::path_storage&) {};
|
||||
Raster::Resolution m_resolution;
|
||||
Raster::PixelDim m_pxdim;
|
||||
TBuffer m_buf;
|
||||
TRawBuffer m_rbuf;
|
||||
TPixelRenderer m_pixfmt;
|
||||
TRawRenderer m_raw_renderer;
|
||||
TRendererAA m_renderer;
|
||||
Origin m_o;
|
||||
std::function<void(agg::path_storage&)> m_flipy = [](agg::path_storage&) {};
|
||||
public:
|
||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
||||
Origin o):
|
||||
resolution_(res), pxdim_(pd),
|
||||
buf_(res.pixels()),
|
||||
rbuf_(reinterpret_cast<TPixelRenderer::value_type*>(buf_.data()),
|
||||
m_resolution(res), m_pxdim(pd),
|
||||
m_buf(res.pixels()),
|
||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
||||
res.width_px, res.height_px,
|
||||
res.width_px*TPixelRenderer::num_components),
|
||||
pixfmt_(rbuf_),
|
||||
raw_renderer_(pixfmt_),
|
||||
renderer_(raw_renderer_),
|
||||
o_(o)
|
||||
m_pixfmt(m_rbuf),
|
||||
m_raw_renderer(m_pixfmt),
|
||||
m_renderer(m_raw_renderer),
|
||||
m_o(o)
|
||||
{
|
||||
renderer_.color(ColorWhite);
|
||||
m_renderer.color(ColorWhite);
|
||||
|
||||
// If we would like to play around with gamma
|
||||
// ras.gamma(agg::gamma_power(1.0));
|
||||
|
||||
clear();
|
||||
|
||||
if(o_ == Origin::TOP_LEFT) flipy_ = [this](agg::path_storage& path) {
|
||||
path.flip_y(0, resolution_.height_px);
|
||||
if(m_o == Origin::TOP_LEFT) m_flipy = [this](agg::path_storage& path) {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -76,35 +76,35 @@ public:
|
|||
agg::scanline_p8 scanlines;
|
||||
|
||||
auto&& path = to_path(poly.contour);
|
||||
flipy_(path);
|
||||
m_flipy(path);
|
||||
ras.add_path(path);
|
||||
|
||||
for(auto h : poly.holes) {
|
||||
auto&& holepath = to_path(h);
|
||||
flipy_(holepath);
|
||||
m_flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
agg::render_scanlines(ras, scanlines, renderer_);
|
||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
raw_renderer_.clear(ColorBlack);
|
||||
m_raw_renderer.clear(ColorBlack);
|
||||
}
|
||||
|
||||
inline TBuffer& buffer() { return buf_; }
|
||||
inline TBuffer& buffer() { return m_buf; }
|
||||
|
||||
inline const Raster::Resolution resolution() { return resolution_; }
|
||||
inline const Raster::Resolution resolution() { return m_resolution; }
|
||||
|
||||
inline Origin origin() const /*noexcept*/ { return o_; }
|
||||
inline Origin origin() const /*noexcept*/ { return m_o; }
|
||||
|
||||
private:
|
||||
double getPx(const Point& p) {
|
||||
return p(0) * SCALING_FACTOR/pxdim_.w_mm;
|
||||
return p(0) * SCALING_FACTOR/m_pxdim.w_mm;
|
||||
}
|
||||
|
||||
double getPy(const Point& p) {
|
||||
return p(1) * SCALING_FACTOR/pxdim_.h_mm;
|
||||
return p(1) * SCALING_FACTOR/m_pxdim.h_mm;
|
||||
}
|
||||
|
||||
agg::path_storage to_path(const Polygon& poly) {
|
||||
|
|
@ -124,57 +124,57 @@ const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
|||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
||||
|
||||
Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o):
|
||||
impl_(new Impl(r, pd, o)) {}
|
||||
m_impl(new Impl(r, pd, o)) {}
|
||||
|
||||
Raster::Raster() {}
|
||||
|
||||
Raster::~Raster() {}
|
||||
|
||||
Raster::Raster(Raster &&m):
|
||||
impl_(std::move(m.impl_)) {}
|
||||
m_impl(std::move(m.m_impl)) {}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd)
|
||||
{
|
||||
// Free up the unnecessary memory and make sure it stays clear after
|
||||
// an exception
|
||||
auto o = impl_? impl_->origin() : Origin::TOP_LEFT;
|
||||
auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT;
|
||||
reset(r, pd, o);
|
||||
}
|
||||
|
||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||
Raster::Origin o)
|
||||
{
|
||||
impl_.reset();
|
||||
impl_.reset(new Impl(r, pd, o));
|
||||
m_impl.reset();
|
||||
m_impl.reset(new Impl(r, pd, o));
|
||||
}
|
||||
|
||||
void Raster::reset()
|
||||
{
|
||||
impl_.reset();
|
||||
m_impl.reset();
|
||||
}
|
||||
|
||||
Raster::Resolution Raster::resolution() const
|
||||
{
|
||||
if(impl_) return impl_->resolution();
|
||||
if(m_impl) return m_impl->resolution();
|
||||
|
||||
return Resolution(0, 0);
|
||||
}
|
||||
|
||||
void Raster::clear()
|
||||
{
|
||||
assert(impl_);
|
||||
impl_->clear();
|
||||
assert(m_impl);
|
||||
m_impl->clear();
|
||||
}
|
||||
|
||||
void Raster::draw(const ExPolygon &poly)
|
||||
{
|
||||
assert(impl_);
|
||||
impl_->draw(poly);
|
||||
assert(m_impl);
|
||||
m_impl->draw(poly);
|
||||
}
|
||||
|
||||
void Raster::save(std::ostream& stream, Compression comp)
|
||||
{
|
||||
assert(impl_);
|
||||
assert(m_impl);
|
||||
switch(comp) {
|
||||
case Compression::PNG: {
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ void Raster::save(std::ostream& stream, Compression comp)
|
|||
|
||||
wr.write_info();
|
||||
|
||||
auto& b = impl_->buffer();
|
||||
auto& b = m_impl->buffer();
|
||||
auto ptr = reinterpret_cast<png::byte*>( b.data() );
|
||||
unsigned stride =
|
||||
sizeof(Impl::TBuffer::value_type) * resolution().width_px;
|
||||
|
|
@ -201,12 +201,12 @@ void Raster::save(std::ostream& stream, Compression comp)
|
|||
}
|
||||
case Compression::RAW: {
|
||||
stream << "P5 "
|
||||
<< impl_->resolution().width_px << " "
|
||||
<< impl_->resolution().height_px << " "
|
||||
<< m_impl->resolution().width_px << " "
|
||||
<< m_impl->resolution().height_px << " "
|
||||
<< "255 ";
|
||||
|
||||
stream.write(reinterpret_cast<const char*>(impl_->buffer().data()),
|
||||
impl_->buffer().size()*sizeof(Impl::TBuffer::value_type));
|
||||
stream.write(reinterpret_cast<const char*>(m_impl->buffer().data()),
|
||||
m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class ExPolygon;
|
|||
*/
|
||||
class Raster {
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
/// Supported compression types
|
||||
|
|
@ -65,7 +65,7 @@ public:
|
|||
|
||||
/**
|
||||
* Release the allocated resources. Drawing in this state ends in
|
||||
* unspecified behaviour.
|
||||
* unspecified behavior.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ void ground_layer(const TriangleMesh &mesh, ExPolygons &output, float h)
|
|||
|
||||
std::vector<ExPolygons> tmp;
|
||||
|
||||
slicer.slice({h}, &tmp);
|
||||
slicer.slice({h}, &tmp, [](){});
|
||||
|
||||
output = tmp.front();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ void export_print_z_polygons_and_extrusions_to_svg(
|
|||
|
||||
PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) :
|
||||
m_object (object),
|
||||
m_print_config (&object->print()->config),
|
||||
m_object_config (&object->config),
|
||||
m_print_config (&object->print()->config()),
|
||||
m_object_config (&object->config()),
|
||||
m_slicing_params (slicing_params),
|
||||
m_first_layer_flow (support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height))),
|
||||
m_support_material_flow (support_material_flow(object, float(slicing_params.layer_height))),
|
||||
|
|
@ -164,7 +164,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
|
|||
coordf_t external_perimeter_width = 0.;
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
if (! object->region_volumes[region_id].empty()) {
|
||||
const PrintRegionConfig &config = object->print()->get_region(region_id)->config;
|
||||
const PrintRegionConfig &config = object->print()->get_region(region_id)->config();
|
||||
coordf_t width = config.external_perimeter_extrusion_width.get_abs_value(slicing_params.layer_height);
|
||||
if (width <= 0.)
|
||||
width = m_print_config->nozzle_diameter.get_at(config.perimeter_extruder-1);
|
||||
|
|
@ -226,7 +226,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||
|
||||
coordf_t max_object_layer_height = 0.;
|
||||
for (size_t i = 0; i < object.layer_count(); ++ i)
|
||||
max_object_layer_height = std::max(max_object_layer_height, object.layers[i]->height);
|
||||
max_object_layer_height = std::max(max_object_layer_height, object.layers()[i]->height);
|
||||
|
||||
// Layer instances will be allocated by std::deque and they will be kept until the end of this function call.
|
||||
// The layers will be referenced by various LayersPtr (of type std::vector<Layer*>)
|
||||
|
|
@ -266,9 +266,9 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||
layer_support_areas);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
for (size_t layer_id = 0; layer_id < object.layers.size(); ++ layer_id)
|
||||
for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id)
|
||||
Slic3r::SVG::export_expolygons(
|
||||
debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers[layer_id]->print_z),
|
||||
debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z),
|
||||
union_ex(layer_support_areas[layer_id], false));
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
|
|
@ -364,7 +364,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||
// Sort the layers lexicographically by a raising print_z and a decreasing height.
|
||||
std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare());
|
||||
int layer_id = 0;
|
||||
assert(object.support_layers.empty());
|
||||
assert(object.support_layers().empty());
|
||||
for (int i = 0; i < int(layers_sorted.size());) {
|
||||
// Find the last layer with roughly the same print_z, find the minimum layer height of all.
|
||||
// Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should.
|
||||
|
|
@ -430,14 +430,14 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
|
|||
{
|
||||
// 1) Count the new polygons first.
|
||||
size_t n_polygons_new = 0;
|
||||
for (const LayerRegion *region : layer.regions)
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
for (const Surface &surface : region->slices.surfaces)
|
||||
if (surface.surface_type == surface_type)
|
||||
n_polygons_new += surface.expolygon.holes.size() + 1;
|
||||
// 2) Collect the new polygons.
|
||||
Polygons out;
|
||||
out.reserve(n_polygons_new);
|
||||
for (const LayerRegion *region : layer.regions)
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
for (const Surface &surface : region->slices.surfaces)
|
||||
if (surface.surface_type == surface_type)
|
||||
polygons_append(out, surface.expolygon);
|
||||
|
|
@ -679,7 +679,7 @@ namespace SupportMaterialInternal {
|
|||
}
|
||||
static bool has_bridging_extrusions(const Layer &layer)
|
||||
{
|
||||
for (const LayerRegion *region : layer.regions) {
|
||||
for (const LayerRegion *region : layer.regions()) {
|
||||
if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters))
|
||||
return true;
|
||||
if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills))
|
||||
|
|
@ -742,7 +742,7 @@ namespace SupportMaterialInternal {
|
|||
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
|
||||
Polygons lower_grown_slices = offset(lower_layer_polygons,
|
||||
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))),
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1))),
|
||||
SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
// Collect perimeters of this layer.
|
||||
//FIXME split_at_first_point() could split a bridge mid-way
|
||||
|
|
@ -825,9 +825,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
std::vector<Polygons> buildplate_covered;
|
||||
if (buildplate_only) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() - collecting regions covering the print bed.";
|
||||
buildplate_covered.assign(object.layers.size(), Polygons());
|
||||
for (size_t layer_id = 1; layer_id < object.layers.size(); ++ layer_id) {
|
||||
const Layer &lower_layer = *object.layers[layer_id-1];
|
||||
buildplate_covered.assign(object.layers().size(), Polygons());
|
||||
for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) {
|
||||
const Layer &lower_layer = *object.layers()[layer_id-1];
|
||||
// Merge the new slices with the preceding slices.
|
||||
// Apply the safety offset to the newly added polygons, so they will connect
|
||||
// with the polygons collected before,
|
||||
|
|
@ -856,7 +856,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
(const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id)
|
||||
{
|
||||
const Layer &layer = *object.layers[layer_id];
|
||||
const Layer &layer = *object.layers()[layer_id];
|
||||
|
||||
// Detect overhangs and contact areas needed to support them.
|
||||
// Collect overhangs and contacts of all regions of this layer supported by the layer immediately below.
|
||||
|
|
@ -864,7 +864,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
Polygons contact_polygons;
|
||||
Polygons slices_margin_cached;
|
||||
float slices_margin_cached_offset = -1.;
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers[layer_id-1]->slices.expolygons);
|
||||
Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons);
|
||||
// Offset of the lower layer, to trim the support polygons with to calculate dense supports.
|
||||
float no_interface_offset = 0.f;
|
||||
if (layer_id == 0) {
|
||||
|
|
@ -876,14 +876,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
contact_polygons = offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN));
|
||||
} else {
|
||||
// Generate overhang / contact_polygons for non-raft layers.
|
||||
const Layer &lower_layer = *object.layers[layer_id-1];
|
||||
for (LayerRegion *layerm : layer.regions) {
|
||||
const Layer &lower_layer = *object.layers()[layer_id-1];
|
||||
for (LayerRegion *layerm : layer.regions()) {
|
||||
// Extrusion width accounts for the roundings of the extrudates.
|
||||
// It is the maximum widh of the extrudate.
|
||||
float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
|
||||
no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
|
||||
float lower_layer_offset =
|
||||
(layer_id < this->m_object_config->support_material_enforce_layers.value) ?
|
||||
(layer_id < m_object_config->support_material_enforce_layers.value) ?
|
||||
// Enforce a full possible support, ignore the overhang angle.
|
||||
0.f :
|
||||
(threshold_rad > 0. ?
|
||||
|
|
@ -1041,8 +1041,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
// Align the contact surface height with a layer immediately below the supported layer.
|
||||
// Interface layer will be synchronized with the object.
|
||||
new_layer.print_z = layer.print_z - layer.height;
|
||||
new_layer.height = object.layers[layer_id - 1]->height;
|
||||
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z;
|
||||
new_layer.height = object.layers()[layer_id - 1]->height;
|
||||
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
|
||||
} else {
|
||||
new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
|
||||
new_layer.bottom_z = new_layer.print_z;
|
||||
|
|
@ -1068,9 +1068,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
// it will support layers printed with a bridging flow.
|
||||
if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
|
||||
coordf_t bridging_height = 0.;
|
||||
for (const LayerRegion *region : layer.regions)
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
bridging_height += region->region()->bridging_height_avg(*m_print_config);
|
||||
bridging_height /= coordf_t(layer.regions.size());
|
||||
bridging_height /= coordf_t(layer.regions().size());
|
||||
coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
|
||||
if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
|
||||
// Not below the first layer height means this layer is printable.
|
||||
|
|
@ -1107,15 +1107,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
// 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells.
|
||||
new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true));
|
||||
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
|
||||
if (layer_id == 0) {
|
||||
if (layer_id == 0 || m_slicing_params.soluble_interface) {
|
||||
// if (no_interface_offset == 0.f) {
|
||||
new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true);
|
||||
} else {
|
||||
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
|
||||
Polygons dense_interface_polygons = diff(overhang_polygons,
|
||||
offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (! dense_interface_polygons.empty()) {
|
||||
//FIXME do it for non-soluble support interfaces only.
|
||||
//FIXME do it for the bridges only?
|
||||
SupportGridPattern support_grid_pattern(
|
||||
// Support islands, to be stretched into a grid.
|
||||
|
|
@ -1297,13 +1297,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
// If the layer is extruded with no bridging flow, support just the normal extrusions.
|
||||
layer_new.height = m_slicing_params.soluble_interface ?
|
||||
// Align the interface layer with the object's layer height.
|
||||
object.layers[layer_id + 1]->height :
|
||||
object.layers()[layer_id + 1]->height :
|
||||
// Place a bridge flow interface layer over the top surface.
|
||||
//FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
|
||||
// According to Jindrich the bottom surfaces work well.
|
||||
//FIXME test the bridging flow instead?
|
||||
m_support_material_interface_flow.nozzle_diameter;
|
||||
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z :
|
||||
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
|
||||
layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
|
||||
layer_new.bottom_z = layer.print_z;
|
||||
layer_new.idx_object_layer_below = layer_id;
|
||||
|
|
@ -1324,7 +1324,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
if (diff > 0.) {
|
||||
// The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer.
|
||||
assert(diff < layer_new.height + EPSILON);
|
||||
assert(layer_new.height - diff >= this->m_support_layer_height_min - EPSILON);
|
||||
assert(layer_new.height - diff >= m_support_layer_height_min - EPSILON);
|
||||
layer_new.print_z = top_contacts[top_idx]->print_z;
|
||||
layer_new.height -= diff;
|
||||
} else {
|
||||
|
|
@ -1347,7 +1347,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
//FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
|
||||
touching = offset(touching, float(SCALED_EPSILON));
|
||||
for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
|
||||
const Layer &layer_above = *object.layers[layer_id_above];
|
||||
const Layer &layer_above = *object.layers()[layer_id_above];
|
||||
if (layer_above.print_z > layer_new.print_z - EPSILON)
|
||||
break;
|
||||
if (! layer_support_areas[layer_id_above].empty()) {
|
||||
|
|
@ -1578,7 +1578,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
// Verify that the extremes are separated by m_support_layer_height_min.
|
||||
for (size_t i = 1; i < extremes.size(); ++ i) {
|
||||
assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. ||
|
||||
extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > this->m_support_layer_height_min - EPSILON);
|
||||
extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_layer_height_min - EPSILON);
|
||||
assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. ||
|
||||
extremes[i]->layer_type == extremes[i-1]->layer_type ||
|
||||
(extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact));
|
||||
|
|
@ -1602,7 +1602,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
// This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers().
|
||||
assert(extr2->layer_type == sltTopContact);
|
||||
assert(extr2->bottom_z == m_slicing_params.first_print_layer_height);
|
||||
assert(extr2->print_z >= m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON);
|
||||
assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_layer_height_min - EPSILON);
|
||||
if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) {
|
||||
MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
|
||||
layer_new.bottom_z = 0.;
|
||||
|
|
@ -1642,7 +1642,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
if (synchronize) {
|
||||
// Emit support layers synchronized with the object layers.
|
||||
// Find the first object layer, which has its print_z in this support Z range.
|
||||
while (idx_layer_object < object.layers.size() && object.layers[idx_layer_object]->print_z < extr1z + EPSILON)
|
||||
while (idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr1z + EPSILON)
|
||||
++ idx_layer_object;
|
||||
if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) {
|
||||
// Insert one base support layer below the object.
|
||||
|
|
@ -1653,11 +1653,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
intermediate_layers.push_back(&layer_new);
|
||||
}
|
||||
// Emit all intermediate support layers synchronized with object layers up to extr2z.
|
||||
for (; idx_layer_object < object.layers.size() && object.layers[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) {
|
||||
for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) {
|
||||
MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
|
||||
layer_new.print_z = object.layers[idx_layer_object]->print_z;
|
||||
layer_new.height = object.layers[idx_layer_object]->height;
|
||||
layer_new.bottom_z = (idx_layer_object > 0) ? object.layers[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height);
|
||||
layer_new.print_z = object.layers()[idx_layer_object]->print_z;
|
||||
layer_new.height = object.layers()[idx_layer_object]->height;
|
||||
layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height);
|
||||
assert(intermediate_layers.empty() || intermediate_layers.back()->print_z < layer_new.print_z + EPSILON);
|
||||
intermediate_layers.push_back(&layer_new);
|
||||
}
|
||||
|
|
@ -1667,10 +1667,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int
|
|||
assert(n_layers_extra > 0);
|
||||
coordf_t step = dist / coordf_t(n_layers_extra);
|
||||
if (extr1 != nullptr && extr1->layer_type == sltTopContact &&
|
||||
extr1->print_z + this->m_support_layer_height_min > extr1->bottom_z + step) {
|
||||
extr1->print_z + m_support_layer_height_min > extr1->bottom_z + step) {
|
||||
// The bottom extreme is a bottom of a top surface. Ensure that the gap
|
||||
// between the 1st intermediate layer print_z and extr1->print_z is not too small.
|
||||
assert(extr1->bottom_z + this->m_support_layer_height_min < extr1->print_z + EPSILON);
|
||||
assert(extr1->bottom_z + m_support_layer_height_min < extr1->print_z + EPSILON);
|
||||
// Generate the first intermediate layer.
|
||||
MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
|
||||
layer_new.bottom_z = extr1->bottom_z;
|
||||
|
|
@ -1764,7 +1764,7 @@ void PrintObjectSupportMaterial::generate_base_layers(
|
|||
Polygons polygons_new;
|
||||
|
||||
// Use the precomputed layer_support_areas.
|
||||
idx_object_layer_above = std::max(0, idx_lower_or_equal(object.layers, idx_object_layer_above,
|
||||
idx_object_layer_above = std::max(0, idx_lower_or_equal(object.layers(), idx_object_layer_above,
|
||||
[&layer_intermediate](const Layer *layer){ return layer->print_z <= layer_intermediate.print_z + EPSILON; }));
|
||||
polygons_new = layer_support_areas[idx_object_layer_above];
|
||||
|
||||
|
|
@ -1900,23 +1900,23 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
|||
// Find the overlapping object layers including the extra above / below gap.
|
||||
coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON;
|
||||
idx_object_layer_overlapping = idx_higher_or_equal(
|
||||
object.layers, idx_object_layer_overlapping,
|
||||
object.layers(), idx_object_layer_overlapping,
|
||||
[z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; });
|
||||
// Collect all the object layers intersecting with this layer.
|
||||
Polygons polygons_trimming;
|
||||
size_t i = idx_object_layer_overlapping;
|
||||
for (; i < object.layers.size(); ++ i) {
|
||||
const Layer &object_layer = *object.layers[i];
|
||||
for (; i < object.layers().size(); ++ i) {
|
||||
const Layer &object_layer = *object.layers()[i];
|
||||
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
|
||||
break;
|
||||
polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
}
|
||||
if (! this->m_slicing_params.soluble_interface) {
|
||||
if (! m_slicing_params.soluble_interface) {
|
||||
// Collect all bottom surfaces, which will be extruded with a bridging flow.
|
||||
for (; i < object.layers.size(); ++ i) {
|
||||
const Layer &object_layer = *object.layers[i];
|
||||
for (; i < object.layers().size(); ++ i) {
|
||||
const Layer &object_layer = *object.layers()[i];
|
||||
bool some_region_overlaps = false;
|
||||
for (LayerRegion *region : object_layer.regions) {
|
||||
for (LayerRegion *region : object_layer.regions()) {
|
||||
coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config);
|
||||
if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
|
||||
break;
|
||||
|
|
@ -1924,7 +1924,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
|||
polygons_append(polygons_trimming,
|
||||
offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
|
||||
gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (region->region()->config.overhangs.value)
|
||||
if (region->region()->config().overhangs.value)
|
||||
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
|
||||
}
|
||||
if (! some_region_overlaps)
|
||||
|
|
@ -2022,7 +2022,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
|
|||
// Expand the bases of the support columns in the 1st layer.
|
||||
columns_base->polygons = diff(
|
||||
offset(columns_base->polygons, inflate_factor_1st_layer),
|
||||
offset(m_object->layers.front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (contacts != nullptr)
|
||||
columns_base->polygons = diff(columns_base->polygons, interface_polygons);
|
||||
}
|
||||
|
|
@ -2168,8 +2168,8 @@ struct MyLayerExtruded
|
|||
else
|
||||
*m_polygons_to_extrude = std::move(polygons);
|
||||
}
|
||||
Polygons& polygons_to_extrude() { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; }
|
||||
const Polygons& polygons_to_extrude() const { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; }
|
||||
Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; }
|
||||
const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; }
|
||||
|
||||
bool could_merge(const MyLayerExtruded &other) const {
|
||||
return ! this->empty() && ! other.empty() &&
|
||||
|
|
@ -2182,21 +2182,21 @@ struct MyLayerExtruded
|
|||
assert(this->could_merge(other));
|
||||
// 1) Merge the rest polygons to extrude, if there are any.
|
||||
if (other.m_polygons_to_extrude != nullptr) {
|
||||
if (this->m_polygons_to_extrude == nullptr) {
|
||||
if (m_polygons_to_extrude == nullptr) {
|
||||
// This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet).
|
||||
assert(this->extrusions.empty());
|
||||
this->m_polygons_to_extrude = new Polygons(this->layer->polygons);
|
||||
m_polygons_to_extrude = new Polygons(this->layer->polygons);
|
||||
}
|
||||
Slic3r::polygons_append(*this->m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude));
|
||||
*this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true);
|
||||
Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude));
|
||||
*m_polygons_to_extrude = union_(*m_polygons_to_extrude, true);
|
||||
delete other.m_polygons_to_extrude;
|
||||
other.m_polygons_to_extrude = nullptr;
|
||||
} else if (this->m_polygons_to_extrude != nullptr) {
|
||||
} else if (m_polygons_to_extrude != nullptr) {
|
||||
assert(other.m_polygons_to_extrude == nullptr);
|
||||
// The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet).
|
||||
assert(other.extrusions.empty());
|
||||
Slic3r::polygons_append(*this->m_polygons_to_extrude, other.layer->polygons);
|
||||
*this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true);
|
||||
Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons);
|
||||
*m_polygons_to_extrude = union_(*m_polygons_to_extrude, true);
|
||||
}
|
||||
// 2) Merge the extrusions.
|
||||
this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end());
|
||||
|
|
@ -2618,7 +2618,10 @@ void modulate_extrusion_by_overlapping_layers(
|
|||
(fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
|
||||
}
|
||||
private:
|
||||
ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {}
|
||||
ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::vector<ExtrusionPathFragment> &m_path_fragments;
|
||||
};
|
||||
const coord_t search_radius = 7;
|
||||
|
|
@ -2794,7 +2797,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
|
||||
{
|
||||
assert(support_layer_id < raft_layers.size());
|
||||
SupportLayer &support_layer = *object.support_layers[support_layer_id];
|
||||
SupportLayer &support_layer = *object.support_layers()[support_layer_id];
|
||||
assert(support_layer.support_fills.entities.empty());
|
||||
MyLayer &raft_layer = *raft_layers[support_layer_id];
|
||||
|
||||
|
|
@ -2890,9 +2893,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
MyLayerExtruded interface_layer;
|
||||
std::vector<LayerCacheItem> overlaps;
|
||||
};
|
||||
std::vector<LayerCache> layer_caches(object.support_layers.size(), LayerCache());
|
||||
std::vector<LayerCache> layer_caches(object.support_layers().size(), LayerCache());
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers.size()),
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers().size()),
|
||||
[this, &object, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &layer_caches, &loop_interface_processor,
|
||||
infill_pattern, &bbox_object, support_density, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
|
|
@ -2907,7 +2910,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
filler_support->set_bounding_box(bbox_object);
|
||||
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
|
||||
{
|
||||
SupportLayer &support_layer = *object.support_layers[support_layer_id];
|
||||
SupportLayer &support_layer = *object.support_layers()[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
|
||||
// Find polygons with the same print_z.
|
||||
|
|
@ -3105,11 +3108,11 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
});
|
||||
|
||||
// Now modulate the support layer height in parallel.
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers.size()),
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers().size()),
|
||||
[this, &object, &layer_caches]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) {
|
||||
SupportLayer &support_layer = *object.support_layers[support_layer_id];
|
||||
SupportLayer &support_layer = *object.support_layers()[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
|
||||
modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping);
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ private:
|
|||
|
||||
// Produce the actual G-code.
|
||||
void generate_toolpaths(
|
||||
const PrintObject &object,
|
||||
const PrintObject &object,
|
||||
const MyLayersPtr &raft_layers,
|
||||
const MyLayersPtr &bottom_contacts,
|
||||
const MyLayersPtr &top_contacts,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
|
||||
// Add z coordinate to model instances' offset
|
||||
#define ENABLE_MODELINSTANCE_3D_OFFSET (1 && ENABLE_1_42_0)
|
||||
// Add double click on gizmo grabbers to reset transformation components to their default value
|
||||
#define ENABLE_GIZMOS_RESET (1 && ENABLE_1_42_0)
|
||||
// Add x and y rotation components to model instances' offset
|
||||
#define ENABLE_MODELINSTANCE_3D_ROTATION (1 && ENABLE_MODELINSTANCE_3D_OFFSET)
|
||||
|
||||
#endif // _technologies_h_
|
||||
|
||||
|
|
|
|||
|
|
@ -308,7 +308,8 @@ void TriangleMesh::rotate(double angle, Point* center)
|
|||
bool TriangleMesh::has_multiple_patches() const
|
||||
{
|
||||
// we need neighbors
|
||||
if (!this->repaired) CONFESS("split() requires repair()");
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
|
||||
if (this->stl.stats.number_of_facets == 0)
|
||||
return false;
|
||||
|
|
@ -338,7 +339,8 @@ bool TriangleMesh::has_multiple_patches() const
|
|||
size_t TriangleMesh::number_of_patches() const
|
||||
{
|
||||
// we need neighbors
|
||||
if (!this->repaired) CONFESS("split() requires repair()");
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
|
||||
if (this->stl.stats.number_of_facets == 0)
|
||||
return false;
|
||||
|
|
@ -382,7 +384,7 @@ TriangleMeshPtrs TriangleMesh::split() const
|
|||
|
||||
// we need neighbors
|
||||
if (!this->repaired)
|
||||
CONFESS("split() requires repair()");
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
|
||||
// loop while we have remaining facets
|
||||
for (;;) {
|
||||
|
|
@ -643,10 +645,11 @@ void TriangleMesh::require_shared_vertices()
|
|||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
|
||||
}
|
||||
|
||||
TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
|
||||
mesh(_mesh)
|
||||
void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
|
||||
{
|
||||
mesh = _mesh;
|
||||
_mesh->require_shared_vertices();
|
||||
throw_on_cancel();
|
||||
facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);
|
||||
v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices);
|
||||
// Scale the copied vertices.
|
||||
|
|
@ -683,6 +686,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
|
|||
e2f.face_edge = - e2f.face_edge;
|
||||
}
|
||||
}
|
||||
throw_on_cancel();
|
||||
std::sort(edges_map.begin(), edges_map.end());
|
||||
|
||||
// Assign a unique common edge id to touching triangle edges.
|
||||
|
|
@ -722,10 +726,12 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
|
|||
edge_j.face = -1;
|
||||
}
|
||||
++ num_edges;
|
||||
if ((i & 0x0ffff) == 0)
|
||||
throw_on_cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
|
||||
|
||||
|
|
@ -762,13 +768,17 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||
boost::mutex lines_mutex;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0,this->mesh->stl.stats.number_of_facets),
|
||||
[&lines, &lines_mutex, &z, this](const tbb::blocked_range<int>& range) {
|
||||
for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx)
|
||||
[&lines, &lines_mutex, &z, throw_on_cancel, this](const tbb::blocked_range<int>& range) {
|
||||
for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) {
|
||||
if ((facet_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel();
|
||||
this->_slice_do(facet_idx, &lines, &lines_mutex, z);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
throw_on_cancel();
|
||||
|
||||
// v_scaled_shared could be freed here
|
||||
|
||||
// build loops
|
||||
|
|
@ -776,9 +786,12 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&lines, &layers, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx)
|
||||
[&lines, &layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
|
||||
if ((line_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel();
|
||||
this->make_loops(lines[line_idx], &(*layers)[line_idx]);
|
||||
}
|
||||
}
|
||||
);
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished";
|
||||
|
|
@ -877,24 +890,25 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
|
|||
}
|
||||
}
|
||||
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
{
|
||||
std::vector<Polygons> layers_p;
|
||||
this->slice(z, &layers_p);
|
||||
this->slice(z, &layers_p, throw_on_cancel);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
|
||||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&layers_p, layers, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
|
||||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&layers_p, layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]);
|
||||
#endif
|
||||
this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]);
|
||||
}
|
||||
});
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end";
|
||||
throw_on_cancel();
|
||||
this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]);
|
||||
}
|
||||
});
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end";
|
||||
}
|
||||
|
||||
// Return true, if the facet has been sliced and line_out has been filled.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "libslic3r.h"
|
||||
#include <admesh/stl.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <boost/thread.hpp>
|
||||
#include "BoundingBox.hpp"
|
||||
|
|
@ -156,9 +157,13 @@ typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
|
|||
class TriangleMeshSlicer
|
||||
{
|
||||
public:
|
||||
TriangleMeshSlicer(TriangleMesh* _mesh);
|
||||
void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const;
|
||||
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const;
|
||||
typedef std::function<void()> throw_on_cancel_callback_type;
|
||||
TriangleMeshSlicer() : mesh(nullptr) {}
|
||||
// Not quite nice, but the constructor and init() methods require non-const mesh pointer to be able to call mesh->require_shared_vertices()
|
||||
TriangleMeshSlicer(TriangleMesh* mesh) { this->init(mesh, [](){}); }
|
||||
void init(TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel);
|
||||
void slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
enum FacetSliceType {
|
||||
NoSlice = 0,
|
||||
Slicing = 1,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@ extern local_encoded_string encode_path(const char *src);
|
|||
extern std::string decode_path(const char *src);
|
||||
extern std::string normalize_utf8_nfc(const char *src);
|
||||
|
||||
// Safely rename a file even if the target exists.
|
||||
// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
|
||||
// for a short while, so the file may not be movable. Retry while we see recoverable errors.
|
||||
extern int rename_file(const std::string &from, const std::string &to);
|
||||
|
||||
// Copy a file, adjust the access attributes, so that the target is writable.
|
||||
extern int copy_file(const std::string &from, const std::string &to);
|
||||
|
||||
// File path / name / extension splitting utilities, working with UTF-8,
|
||||
// to be published to Perl.
|
||||
namespace PerlUtils {
|
||||
|
|
@ -66,6 +74,20 @@ inline std::string header_slic3r_generated() { return std::string("generated by
|
|||
// getpid platform wrapper
|
||||
extern unsigned get_current_pid();
|
||||
|
||||
template <typename Real>
|
||||
Real round_nearest(Real value, unsigned int decimals)
|
||||
{
|
||||
Real res = (Real)0;
|
||||
if (decimals == 0)
|
||||
res = ::round(value);
|
||||
else
|
||||
{
|
||||
Real power = ::pow((Real)10, (int)decimals);
|
||||
res = ::round(value * power + (Real)0.5) / power;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Compute the next highest power of 2 of 32-bit v
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||
template<typename T>
|
||||
|
|
@ -87,26 +109,6 @@ inline T next_highest_power_of_2(T v)
|
|||
|
||||
extern std::string xml_escape(std::string text);
|
||||
|
||||
class PerlCallback {
|
||||
public:
|
||||
PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); }
|
||||
PerlCallback() : m_callback(nullptr) {}
|
||||
~PerlCallback() { this->deregister_callback(); }
|
||||
void register_callback(void *sv);
|
||||
void deregister_callback();
|
||||
void call() const;
|
||||
void call(int i) const;
|
||||
void call(int i, int j) const;
|
||||
void call(const std::vector<int>& ints) const;
|
||||
void call(double a) const;
|
||||
void call(double a, double b) const;
|
||||
void call(double a, double b, double c) const;
|
||||
void call(double a, double b, double c, double d) const;
|
||||
void call(bool b) const;
|
||||
private:
|
||||
void *m_callback;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Utils_hpp_
|
||||
|
|
|
|||
|
|
@ -48,14 +48,6 @@ typedef double coordf_t;
|
|||
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
||||
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||
#define SCALED_EPSILON scale_(EPSILON)
|
||||
/* Implementation of CONFESS("foo"): */
|
||||
#ifdef _MSC_VER
|
||||
#define CONFESS(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
|
||||
#else
|
||||
#define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#endif
|
||||
void confess_at(const char *file, int line, const char *func, const char *pat, ...);
|
||||
/* End implementation of CONFESS("foo"): */
|
||||
|
||||
// Which C++ version is supported?
|
||||
// For example, could optimized functions with move semantics be used?
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/integration/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
|
||||
|
|
@ -149,188 +152,88 @@ const std::string& data_dir()
|
|||
return g_data_dir;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#include <xsinit.h>
|
||||
|
||||
void
|
||||
confess_at(const char *file, int line, const char *func,
|
||||
const char *pat, ...)
|
||||
// borrowed from LVVM lib/Support/Windows/Path.inc
|
||||
int rename_file(const std::string &from, const std::string &to)
|
||||
{
|
||||
#ifdef SLIC3RXS
|
||||
va_list args;
|
||||
SV *error_sv = newSVpvf("Error in function %s at %s:%d: ", func,
|
||||
file, line);
|
||||
int ec = 0;
|
||||
|
||||
va_start(args, pat);
|
||||
sv_vcatpvf(error_sv, pat, &args);
|
||||
va_end(args);
|
||||
#ifdef _WIN32
|
||||
|
||||
sv_catpvn(error_sv, "\n\t", 2);
|
||||
// Convert to utf-16.
|
||||
std::wstring wide_from = boost::nowide::widen(from);
|
||||
std::wstring wide_to = boost::nowide::widen(to);
|
||||
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs( sv_2mortal(error_sv) );
|
||||
PUTBACK;
|
||||
call_pv("Carp::confess", G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
#endif
|
||||
}
|
||||
// Retry while we see recoverable errors.
|
||||
// System scanners (eg. indexer) might open the source file when it is written
|
||||
// and closed.
|
||||
bool TryReplace = true;
|
||||
|
||||
void PerlCallback::register_callback(void *sv)
|
||||
{
|
||||
if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV)
|
||||
croak("Not a Callback %_ for PerlFunction", (SV*)sv);
|
||||
if (m_callback)
|
||||
SvSetSV((SV*)m_callback, (SV*)sv);
|
||||
else
|
||||
m_callback = newSVsv((SV*)sv);
|
||||
}
|
||||
|
||||
void PerlCallback::deregister_callback()
|
||||
{
|
||||
if (m_callback) {
|
||||
sv_2mortal((SV*)m_callback);
|
||||
m_callback = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PerlCallback::call() const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(int i) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(int i, int j) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
XPUSHs(sv_2mortal(newSViv(j)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(const std::vector<int>& ints) const
|
||||
{
|
||||
if (! m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
for (int i : ints)
|
||||
{
|
||||
XPUSHs(sv_2mortal(newSViv(i)));
|
||||
// This loop may take more than 2000 x 1ms to finish.
|
||||
for (int i = 0; i < 2000; ++ i) {
|
||||
if (i > 0)
|
||||
// Sleep 1ms
|
||||
::Sleep(1);
|
||||
if (TryReplace) {
|
||||
// Try ReplaceFile first, as it is able to associate a new data stream
|
||||
// with the destination even if the destination file is currently open.
|
||||
if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL))
|
||||
return 0;
|
||||
DWORD ReplaceError = ::GetLastError();
|
||||
ec = -1; // ReplaceError
|
||||
// If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
|
||||
// ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
|
||||
if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
|
||||
ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
|
||||
TryReplace = false;
|
||||
continue;
|
||||
}
|
||||
// If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
|
||||
// using ReplaceFileW().
|
||||
if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
|
||||
continue;
|
||||
// We get ERROR_FILE_NOT_FOUND if the destination file is missing.
|
||||
// MoveFileEx can handle this case.
|
||||
if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION)
|
||||
break;
|
||||
}
|
||||
if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
|
||||
return 0;
|
||||
DWORD MoveError = ::GetLastError();
|
||||
ec = -1; // MoveError
|
||||
if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION)
|
||||
break;
|
||||
}
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
|
||||
#else
|
||||
|
||||
boost::nowide::remove(to.c_str());
|
||||
ec = boost::nowide::rename(from.c_str(), to.c_str());
|
||||
|
||||
#endif
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a) const
|
||||
int copy_file(const std::string &from, const std::string &to)
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
const boost::filesystem::path source(from);
|
||||
const boost::filesystem::path target(to);
|
||||
static const auto perms = boost::filesystem::owner_read | boost::filesystem::owner_write | boost::filesystem::group_read | boost::filesystem::others_read; // aka 644
|
||||
|
||||
// Make sure the file has correct permission both before and after we copy over it.
|
||||
try {
|
||||
if (boost::filesystem::exists(target))
|
||||
boost::filesystem::permissions(target, perms);
|
||||
boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists);
|
||||
boost::filesystem::permissions(target, perms);
|
||||
} catch (std::exception & /* ex */) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b, double c) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
XPUSHs(sv_2mortal(newSVnv(c)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(double a, double b, double c, double d) const
|
||||
{
|
||||
if (!m_callback)
|
||||
return;
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVnv(a)));
|
||||
XPUSHs(sv_2mortal(newSVnv(b)));
|
||||
XPUSHs(sv_2mortal(newSVnv(c)));
|
||||
XPUSHs(sv_2mortal(newSVnv(d)));
|
||||
PUTBACK;
|
||||
perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
void PerlCallback::call(bool b) const
|
||||
{
|
||||
call(b ? 1 : 0);
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef NOMINMAX
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
|
|||
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
|
||||
REGISTER_CLASS(TriangleMesh, "TriangleMesh");
|
||||
REGISTER_CLASS(AppConfig, "GUI::AppConfig");
|
||||
REGISTER_CLASS(BackgroundSlicingProcess, "GUI::BackgroundSlicingProcess");
|
||||
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
|
||||
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
|
||||
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
|
||||
|
|
@ -62,6 +63,7 @@ REGISTER_CLASS(Preset, "GUI::Preset");
|
|||
REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
|
||||
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
|
||||
REGISTER_CLASS(TabIface, "GUI::Tab");
|
||||
REGISTER_CLASS(PreviewIface, "GUI::Preview");
|
||||
REGISTER_CLASS(ProgressStatusBar, "GUI::ProgressStatusBar");
|
||||
REGISTER_CLASS(PresetUpdater, "PresetUpdater");
|
||||
REGISTER_CLASS(AppController, "AppController");
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@
|
|||
#include <ModelArrange.hpp>
|
||||
#include <slic3r/GUI/PresetBundle.hpp>
|
||||
|
||||
#include <Geometry.hpp>
|
||||
#include <PrintConfig.hpp>
|
||||
#include <Print.hpp>
|
||||
#include <PrintExport.hpp>
|
||||
#include <Geometry.hpp>
|
||||
#include <Model.hpp>
|
||||
#include <Utils.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppControllerBoilerplate::PriData {
|
||||
class AppControllerGui::PriData {
|
||||
public:
|
||||
std::mutex m;
|
||||
std::thread::id ui_thread;
|
||||
|
|
@ -27,39 +29,234 @@ public:
|
|||
inline explicit PriData(std::thread::id uit): ui_thread(uit) {}
|
||||
};
|
||||
|
||||
AppControllerBoilerplate::AppControllerBoilerplate()
|
||||
:pri_data_(new PriData(std::this_thread::get_id())) {}
|
||||
AppControllerGui::AppControllerGui()
|
||||
:m_pri_data(new PriData(std::this_thread::get_id())) {}
|
||||
|
||||
AppControllerBoilerplate::~AppControllerBoilerplate() {
|
||||
pri_data_.reset();
|
||||
AppControllerGui::~AppControllerGui() {
|
||||
m_pri_data.reset();
|
||||
}
|
||||
|
||||
bool AppControllerBoilerplate::is_main_thread() const
|
||||
bool AppControllerGui::is_main_thread() const
|
||||
{
|
||||
return pri_data_->ui_thread == std::this_thread::get_id();
|
||||
return m_pri_data->ui_thread == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
namespace GUI {
|
||||
PresetBundle* get_preset_bundle();
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr
|
||||
AppControllerBoilerplate::global_progress_indicator() {
|
||||
static const PrintObjectStep STEP_SLICE = posSlice;
|
||||
static const PrintObjectStep STEP_PERIMETERS = posPerimeters;
|
||||
static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill;
|
||||
static const PrintObjectStep STEP_INFILL = posInfill;
|
||||
static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial;
|
||||
static const PrintStep STEP_SKIRT = psSkirt;
|
||||
static const PrintStep STEP_BRIM = psBrim;
|
||||
static const PrintStep STEP_WIPE_TOWER = psWipeTower;
|
||||
|
||||
ProgresIndicatorPtr AppControllerGui::global_progress_indicator() {
|
||||
ProgresIndicatorPtr ret;
|
||||
|
||||
pri_data_->m.lock();
|
||||
ret = global_progressind_;
|
||||
pri_data_->m.unlock();
|
||||
m_pri_data->m.lock();
|
||||
ret = m_global_progressind;
|
||||
m_pri_data->m.unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AppControllerBoilerplate::global_progress_indicator(
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr gpri)
|
||||
void AppControllerGui::global_progress_indicator(ProgresIndicatorPtr gpri)
|
||||
{
|
||||
pri_data_->m.lock();
|
||||
global_progressind_ = gpri;
|
||||
pri_data_->m.unlock();
|
||||
m_pri_data->m.lock();
|
||||
m_global_progressind = gpri;
|
||||
m_pri_data->m.unlock();
|
||||
}
|
||||
|
||||
PrintController::PngExportData
|
||||
PrintController::query_png_export_data(const DynamicPrintConfig& conf)
|
||||
{
|
||||
PngExportData ret;
|
||||
|
||||
auto c = GUI::get_appctl();
|
||||
auto zippath = c->query_destination_path("Output zip file", "*.zip",
|
||||
"export-png",
|
||||
"out");
|
||||
|
||||
ret.zippath = zippath;
|
||||
|
||||
ret.width_mm = conf.opt_float("display_width");
|
||||
ret.height_mm = conf.opt_float("display_height");
|
||||
|
||||
ret.width_px = conf.opt_int("display_pixels_x");
|
||||
ret.height_px = conf.opt_int("display_pixels_y");
|
||||
|
||||
auto opt_corr = conf.opt<ConfigOptionFloats>("printer_correction");
|
||||
|
||||
if(opt_corr) {
|
||||
ret.corr_x = opt_corr->values[0];
|
||||
ret.corr_y = opt_corr->values[1];
|
||||
ret.corr_z = opt_corr->values[2];
|
||||
}
|
||||
|
||||
ret.exp_time_first_s = conf.opt_float("initial_exposure_time");
|
||||
ret.exp_time_s = conf.opt_float("exposure_time");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PrintController::slice(ProgresIndicatorPtr pri)
|
||||
{
|
||||
m_print->set_status_callback([pri](int st, const std::string& msg){
|
||||
pri->update(unsigned(st), msg);
|
||||
});
|
||||
|
||||
m_print->process();
|
||||
}
|
||||
|
||||
void PrintController::slice()
|
||||
{
|
||||
auto ctl = GUI::get_appctl();
|
||||
auto pri = ctl->global_progress_indicator();
|
||||
if(!pri) pri = ctl->create_progress_indicator(100, L("Slicing"));
|
||||
slice(pri);
|
||||
}
|
||||
|
||||
template<> class LayerWriter<Zipper> {
|
||||
Zipper m_zip;
|
||||
public:
|
||||
|
||||
inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {}
|
||||
|
||||
inline void next_entry(const std::string& fname) { m_zip.next_entry(fname); }
|
||||
|
||||
inline std::string get_name() const { return m_zip.get_name(); }
|
||||
|
||||
template<class T> inline LayerWriter& operator<<(const T& arg) {
|
||||
m_zip.stream() << arg; return *this;
|
||||
}
|
||||
|
||||
inline void close() { m_zip.close(); }
|
||||
};
|
||||
|
||||
void PrintController::slice_to_png()
|
||||
{
|
||||
using Pointf3 = Vec3d;
|
||||
|
||||
auto ctl = GUI::get_appctl();
|
||||
auto presetbundle = GUI::get_preset_bundle();
|
||||
|
||||
assert(presetbundle);
|
||||
|
||||
// FIXME: this crashes in command line mode
|
||||
auto pt = presetbundle->printers.get_selected_preset().printer_technology();
|
||||
if(pt != ptSLA) {
|
||||
ctl->report_issue(IssueType::ERR, L("Printer technology is not SLA!"),
|
||||
L("Error"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto conf = presetbundle->full_config();
|
||||
conf.validate();
|
||||
|
||||
auto exd = query_png_export_data(conf);
|
||||
if(exd.zippath.empty()) return;
|
||||
|
||||
Print *print = m_print;
|
||||
|
||||
try {
|
||||
print->apply_config(conf);
|
||||
print->validate();
|
||||
} catch(std::exception& e) {
|
||||
ctl->report_issue(IssueType::ERR, e.what(), "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: copy the model and work with the copy only
|
||||
bool correction = false;
|
||||
if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) {
|
||||
correction = true;
|
||||
// print->invalidate_all_steps();
|
||||
|
||||
// for(auto po : print->objects) {
|
||||
// po->model_object()->scale(
|
||||
// Pointf3(exd.corr_x, exd.corr_y, exd.corr_z)
|
||||
// );
|
||||
// po->model_object()->invalidate_bounding_box();
|
||||
// po->reload_model_instances();
|
||||
// po->invalidate_all_steps();
|
||||
// }
|
||||
}
|
||||
|
||||
// Turn back the correction scaling on the model.
|
||||
auto scale_back = [this, print, correction, exd]() {
|
||||
if(correction) { // scale the model back
|
||||
// print->invalidate_all_steps();
|
||||
// for(auto po : print->objects) {
|
||||
// po->model_object()->scale(
|
||||
// Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z)
|
||||
// );
|
||||
// po->model_object()->invalidate_bounding_box();
|
||||
// po->reload_model_instances();
|
||||
// po->invalidate_all_steps();
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
auto print_bb = print->bounding_box();
|
||||
Vec2d punsc = unscale(print_bb.size());
|
||||
|
||||
// If the print does not fit into the print area we should cry about it.
|
||||
if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << L("Print will not fit and will be truncated!") << "\n"
|
||||
<< L("Width needed: ") << px(punsc) << " mm\n"
|
||||
<< L("Height needed: ") << py(punsc) << " mm\n";
|
||||
|
||||
if(!ctl->report_issue(IssueType::WARN_Q, ss.str(), L("Warning"))) {
|
||||
scale_back();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto pri = ctl->create_progress_indicator(
|
||||
200, L("Slicing to zipped png files..."));
|
||||
|
||||
pri->on_cancel([&print](){ print->cancel(); });
|
||||
|
||||
try {
|
||||
pri->update(0, L("Slicing..."));
|
||||
slice(pri);
|
||||
} catch (std::exception& e) {
|
||||
ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
scale_back();
|
||||
if(print->canceled()) print->restart();
|
||||
return;
|
||||
}
|
||||
|
||||
auto initstate = unsigned(pri->state());
|
||||
print->set_status_callback([pri, initstate](int st, const std::string& msg)
|
||||
{
|
||||
pri->update(initstate + unsigned(st), msg);
|
||||
});
|
||||
|
||||
try {
|
||||
print_to<FilePrinterFormat::PNG, Zipper>( *print, exd.zippath,
|
||||
exd.width_mm, exd.height_mm,
|
||||
exd.width_px, exd.height_px,
|
||||
exd.exp_time_s, exd.exp_time_first_s);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred"));
|
||||
}
|
||||
|
||||
scale_back();
|
||||
if(print->canceled()) print->restart();
|
||||
print->set_status_default();
|
||||
}
|
||||
|
||||
const PrintConfig &PrintController::config() const
|
||||
{
|
||||
return m_print->config();
|
||||
}
|
||||
|
||||
void ProgressIndicator::message_fmt(
|
||||
|
|
@ -89,82 +286,81 @@ void ProgressIndicator::message_fmt(
|
|||
message(ss.str());
|
||||
}
|
||||
|
||||
const PrintConfig &PrintController::config() const
|
||||
{
|
||||
return print_->config;
|
||||
}
|
||||
|
||||
void AppController::arrange_model()
|
||||
{
|
||||
auto ftr = std::async(
|
||||
supports_asynch()? std::launch::async : std::launch::deferred,
|
||||
[this]()
|
||||
{
|
||||
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
|
||||
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : model_->objects) count += obj->instances.size();
|
||||
auto ctl = GUI::get_appctl();
|
||||
|
||||
auto pind = global_progress_indicator();
|
||||
if(m_arranging.load()) return;
|
||||
|
||||
float pmax = 1.0;
|
||||
// to prevent UI reentrancies
|
||||
m_arranging.store(true);
|
||||
|
||||
if(pind) {
|
||||
pmax = pind->max();
|
||||
unsigned count = 0;
|
||||
for(auto obj : m_model->objects) count += obj->instances.size();
|
||||
|
||||
// Set the range of the progress to the object count
|
||||
pind->max(count);
|
||||
auto pind = ctl->global_progress_indicator();
|
||||
|
||||
}
|
||||
float pmax = 1.0;
|
||||
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
if(pind) {
|
||||
pmax = pind->max();
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
// Set the range of the progress to the object count
|
||||
pind->max(count);
|
||||
|
||||
auto& bedpoints = print_ctl()->config().bed_shape.values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints)
|
||||
bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
if(pind) pind->update(0, L("Arranging objects..."));
|
||||
|
||||
try {
|
||||
arr::BedShapeHint hint;
|
||||
// TODO: from Sasha from GUI
|
||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||
|
||||
//FIXME merge error
|
||||
/*
|
||||
arr::arrange(*model_,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[pind, count](unsigned rem) {
|
||||
if(pind)
|
||||
pind->update(count - rem, L("Arranging objects..."));
|
||||
});
|
||||
*/
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
report_issue(IssueType::ERR,
|
||||
L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."),
|
||||
L("Exception occurred"));
|
||||
}
|
||||
|
||||
// Restore previous max value
|
||||
if(pind) {
|
||||
pind->max(pmax);
|
||||
pind->update(0, L("Arranging done."));
|
||||
}
|
||||
});
|
||||
|
||||
while( ftr.wait_for(std::chrono::milliseconds(10))
|
||||
!= std::future_status::ready) {
|
||||
process_events();
|
||||
pind->on_cancel([this](){
|
||||
m_arranging.store(false);
|
||||
});
|
||||
}
|
||||
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
auto& bedpoints = print_ctl()->config().bed_shape.values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints)
|
||||
bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
if(pind) pind->update(0, L("Arranging objects..."));
|
||||
|
||||
try {
|
||||
arr::BedShapeHint hint;
|
||||
// TODO: from Sasha from GUI
|
||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||
|
||||
arr::arrange(*m_model,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[this, pind, &ctl, count](unsigned rem) {
|
||||
if(pind)
|
||||
pind->update(count - rem, L("Arranging objects..."));
|
||||
|
||||
ctl->process_events();
|
||||
}, [this] () { return !m_arranging.load(); });
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
ctl->report_issue(IssueType::ERR,
|
||||
L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."),
|
||||
L("Exception occurred"));
|
||||
}
|
||||
|
||||
// Restore previous max value
|
||||
if(pind) {
|
||||
pind->max(pmax);
|
||||
pind->update(0, m_arranging.load() ? L("Arranging done.") :
|
||||
L("Arranging canceled."));
|
||||
|
||||
pind->on_cancel(/*remove cancel function*/);
|
||||
}
|
||||
|
||||
m_arranging.store(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#include <atomic>
|
||||
#include <iostream>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
#include "GUI/ProgressIndicator.hpp"
|
||||
|
||||
#include <PrintConfig.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
@ -18,6 +20,21 @@ class PrintConfig;
|
|||
class ProgressStatusBar;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
/// A Progress indicator object smart pointer
|
||||
using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
|
||||
|
||||
using FilePath = std::string;
|
||||
using FilePathList = std::vector<FilePath>;
|
||||
|
||||
/// Common runtime issue types
|
||||
enum class IssueType {
|
||||
INFO,
|
||||
WARN,
|
||||
WARN_Q, // Warning with a question to continue
|
||||
ERR,
|
||||
FATAL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A boilerplate class for creating application logic. It should provide
|
||||
* features as issue reporting and progress indication, etc...
|
||||
|
|
@ -30,34 +47,12 @@ class DynamicPrintConfig;
|
|||
* UI toolkit dependencies. We can implement it with any UI framework or make it
|
||||
* a cli client.
|
||||
*/
|
||||
class AppControllerBoilerplate {
|
||||
class AppControllerBase {
|
||||
public:
|
||||
|
||||
/// A Progress indicator object smart pointer
|
||||
using ProgresIndicatorPtr = std::shared_ptr<ProgressIndicator>;
|
||||
using Ptr = std::shared_ptr<AppControllerBase>;
|
||||
|
||||
private:
|
||||
class PriData; // Some structure to store progress indication data
|
||||
|
||||
// Pimpl data for thread safe progress indication features
|
||||
std::unique_ptr<PriData> pri_data_;
|
||||
|
||||
public:
|
||||
|
||||
AppControllerBoilerplate();
|
||||
~AppControllerBoilerplate();
|
||||
|
||||
using Path = std::string;
|
||||
using PathList = std::vector<Path>;
|
||||
|
||||
/// Common runtime issue types
|
||||
enum class IssueType {
|
||||
INFO,
|
||||
WARN,
|
||||
WARN_Q, // Warning with a question to continue
|
||||
ERR,
|
||||
FATAL
|
||||
};
|
||||
inline virtual ~AppControllerBase() {}
|
||||
|
||||
/**
|
||||
* @brief Query some paths from the user.
|
||||
|
|
@ -65,25 +60,30 @@ public:
|
|||
* It should display a file chooser dialog in case of a UI application.
|
||||
* @param title Title of a possible query dialog.
|
||||
* @param extensions Recognized file extensions.
|
||||
* @return Returns a list of paths choosed by the user.
|
||||
* @return Returns a list of paths chosen by the user.
|
||||
*/
|
||||
PathList query_destination_paths(
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions) const;
|
||||
const std::string& extensions,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but works for directories only.
|
||||
*/
|
||||
PathList query_destination_dirs(
|
||||
const std::string& title) const;
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& title,
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Same as query_destination_paths but returns only one path.
|
||||
*/
|
||||
Path query_destination_path(
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& hint = "") const;
|
||||
const std::string& functionid = "",
|
||||
const std::string& hint = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Report an issue to the user be it fatal or recoverable.
|
||||
|
|
@ -95,12 +95,9 @@ public:
|
|||
* @param brief A very brief description. Can be used for message dialog
|
||||
* title.
|
||||
*/
|
||||
bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief);
|
||||
|
||||
bool report_issue(IssueType issuetype,
|
||||
const std::string& description);
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief) = 0;
|
||||
|
||||
/**
|
||||
* @brief Return the global progress indicator for the current controller.
|
||||
|
|
@ -108,9 +105,9 @@ public:
|
|||
*
|
||||
* Only one thread should use the global indicator at a time.
|
||||
*/
|
||||
ProgresIndicatorPtr global_progress_indicator();
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() = 0;
|
||||
|
||||
void global_progress_indicator(ProgresIndicatorPtr gpri);
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) = 0;
|
||||
|
||||
/**
|
||||
* @brief A predicate telling the caller whether it is the thread that
|
||||
|
|
@ -120,7 +117,7 @@ public:
|
|||
* @return Return true for the same caller thread that created this
|
||||
* object and false for every other.
|
||||
*/
|
||||
bool is_main_thread() const;
|
||||
virtual bool is_main_thread() const = 0;
|
||||
|
||||
/**
|
||||
* @brief The frontend supports asynch execution.
|
||||
|
|
@ -136,11 +133,9 @@ public:
|
|||
* @return true if a job or method can be executed asynchronously, false
|
||||
* otherwise.
|
||||
*/
|
||||
bool supports_asynch() const;
|
||||
virtual bool supports_asynch() const = 0;
|
||||
|
||||
void process_events();
|
||||
|
||||
protected:
|
||||
virtual void process_events() = 0;
|
||||
|
||||
/**
|
||||
* @brief Create a new progress indicator and return a smart pointer to it.
|
||||
|
|
@ -149,29 +144,194 @@ protected:
|
|||
* @param firstmsg The message for the first subtask to be displayed.
|
||||
* @return Smart pointer to the created object.
|
||||
*/
|
||||
ProgresIndicatorPtr create_progress_indicator(
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const;
|
||||
const std::string& firstmsg = "") const = 0;
|
||||
};
|
||||
|
||||
ProgresIndicatorPtr create_progress_indicator(
|
||||
/**
|
||||
* @brief Implementation of AppControllerBase for the GUI app
|
||||
*/
|
||||
class AppControllerGui: public AppControllerBase {
|
||||
private:
|
||||
class PriData; // Some structure to store progress indication data
|
||||
|
||||
// Pimpl data for thread safe progress indication features
|
||||
std::unique_ptr<PriData> m_pri_data;
|
||||
|
||||
public:
|
||||
|
||||
AppControllerGui();
|
||||
|
||||
virtual ~AppControllerGui();
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& title,
|
||||
const std::string& extensions,
|
||||
const std::string& functionid,
|
||||
const std::string& hint) const override;
|
||||
|
||||
virtual bool report_issue(IssueType issuetype,
|
||||
const std::string& description,
|
||||
const std::string& brief = std::string()) override;
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override;
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr gpri) override;
|
||||
|
||||
virtual bool is_main_thread() const override;
|
||||
|
||||
virtual bool supports_asynch() const override;
|
||||
|
||||
virtual void process_events() override;
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title) const;
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const override;
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr global_progressind_;
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class AppControllerCli: public AppControllerBase {
|
||||
|
||||
class CliProgress : public ProgressIndicator {
|
||||
std::string m_msg, m_title;
|
||||
public:
|
||||
virtual void message(const std::string& msg) override {
|
||||
m_msg = msg;
|
||||
}
|
||||
|
||||
virtual void title(const std::string& title) override {
|
||||
m_title = title;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
AppControllerCli() {
|
||||
std::cout << "Cli AppController ready..." << std::endl;
|
||||
m_global_progressind = std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
virtual ~AppControllerCli() {}
|
||||
|
||||
virtual FilePathList query_destination_paths(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePathList query_destination_dirs(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return {}; }
|
||||
|
||||
virtual FilePath query_destination_path(
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*extensions*/,
|
||||
const std::string& /*functionid*/,
|
||||
const std::string& /*hint*/) const override { return "out.zip"; }
|
||||
|
||||
virtual bool report_issue(IssueType /*issuetype*/,
|
||||
const std::string& description,
|
||||
const std::string& brief) override {
|
||||
std::cerr << brief << ": " << description << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ProgresIndicatorPtr global_progress_indicator() override {
|
||||
return m_global_progressind;
|
||||
}
|
||||
|
||||
virtual void global_progress_indicator(ProgresIndicatorPtr) override {}
|
||||
|
||||
virtual bool is_main_thread() const override { return true; }
|
||||
|
||||
virtual bool supports_asynch() const override { return false; }
|
||||
|
||||
virtual void process_events() override {}
|
||||
|
||||
virtual ProgresIndicatorPtr create_progress_indicator(
|
||||
unsigned /*statenum*/,
|
||||
const std::string& /*title*/,
|
||||
const std::string& /*firstmsg*/) const override {
|
||||
return std::make_shared<CliProgress>();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// This is a global progress indicator placeholder. In the Slic3r UI it can
|
||||
// contain the progress indicator on the statusbar.
|
||||
ProgresIndicatorPtr m_global_progressind;
|
||||
};
|
||||
|
||||
class Zipper {
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
public:
|
||||
|
||||
Zipper(const std::string& zipfilepath);
|
||||
~Zipper();
|
||||
|
||||
void next_entry(const std::string& fname);
|
||||
|
||||
std::string get_name() const;
|
||||
|
||||
std::ostream& stream();
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of the printing logic.
|
||||
*/
|
||||
class PrintController: public AppControllerBoilerplate {
|
||||
Print *print_ = nullptr;
|
||||
class PrintController {
|
||||
Print *m_print = nullptr;
|
||||
std::function<void()> m_rempools;
|
||||
protected:
|
||||
|
||||
// Data structure with the png export input data
|
||||
struct PngExportData {
|
||||
std::string zippath; // output zip file
|
||||
unsigned long width_px = 1440; // resolution - rows
|
||||
unsigned long height_px = 2560; // resolution columns
|
||||
double width_mm = 68.0, height_mm = 120.0; // dimensions in mm
|
||||
double exp_time_first_s = 35.0; // first exposure time
|
||||
double exp_time_s = 8.0; // global exposure time
|
||||
double corr_x = 1.0; // offsetting in x
|
||||
double corr_y = 1.0; // offsetting in y
|
||||
double corr_z = 1.0; // offsetting in y
|
||||
};
|
||||
|
||||
// Should display a dialog with the input fields for printing to png
|
||||
PngExportData query_png_export_data(const DynamicPrintConfig&);
|
||||
|
||||
// The previous export data, to pre-populate the dialog
|
||||
PngExportData m_prev_expdata;
|
||||
|
||||
void slice(ProgresIndicatorPtr pri);
|
||||
|
||||
public:
|
||||
|
||||
// Must be public for perl to use it
|
||||
explicit inline PrintController(Print *print): print_(print) {}
|
||||
explicit inline PrintController(Print *print): m_print(print) {}
|
||||
|
||||
PrintController(const PrintController&) = delete;
|
||||
PrintController(PrintController&&) = delete;
|
||||
|
|
@ -182,9 +342,15 @@ public:
|
|||
return PrintController::Ptr( new PrintController(print) );
|
||||
}
|
||||
|
||||
//FIXME Vojtech: Merging error
|
||||
void slice() {}
|
||||
void slice_to_png() {}
|
||||
/**
|
||||
* @brief Slice the loaded print scene.
|
||||
*/
|
||||
void slice();
|
||||
|
||||
/**
|
||||
* @brief Slice the print into zipped png files.
|
||||
*/
|
||||
void slice_to_png();
|
||||
|
||||
const PrintConfig& config() const;
|
||||
};
|
||||
|
|
@ -192,9 +358,10 @@ public:
|
|||
/**
|
||||
* @brief Top level controller.
|
||||
*/
|
||||
class AppController: public AppControllerBoilerplate {
|
||||
Model *model_ = nullptr;
|
||||
class AppController {
|
||||
Model *m_model = nullptr;
|
||||
PrintController::Ptr printctl;
|
||||
std::atomic<bool> m_arranging;
|
||||
public:
|
||||
|
||||
/**
|
||||
|
|
@ -211,7 +378,7 @@ public:
|
|||
* @param model A raw pointer to the model object. This can be used from
|
||||
* perl.
|
||||
*/
|
||||
void set_model(Model *model) { model_ = model; }
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
/**
|
||||
* @brief Set the print object from perl.
|
||||
|
|
@ -237,8 +404,7 @@ public:
|
|||
* @param gauge_id The ID of the gague widget of the status bar.
|
||||
* @param statusbar_id The ID of the status bar.
|
||||
*/
|
||||
void set_global_progress_indicator(unsigned gauge_id,
|
||||
unsigned statusbar_id);
|
||||
void set_global_progress_indicator(ProgressStatusBar *prs);
|
||||
|
||||
void arrange_model();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
#include "AppController.hpp"
|
||||
|
||||
#include <wx/stdstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#include <thread>
|
||||
#include <future>
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <slic3r/GUI/ProgressStatusBar.hpp>
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/filedlg.h>
|
||||
|
|
@ -20,40 +25,43 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
bool AppControllerBoilerplate::supports_asynch() const
|
||||
bool AppControllerGui::supports_asynch() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppControllerBoilerplate::process_events()
|
||||
void AppControllerGui::process_events()
|
||||
{
|
||||
wxSafeYield();
|
||||
wxYieldIfNeeded();
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::PathList
|
||||
AppControllerBoilerplate::query_destination_paths(
|
||||
FilePathList AppControllerGui::query_destination_paths(
|
||||
const std::string &title,
|
||||
const std::string &extensions) const
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
dlg.SetWildcard(extensions);
|
||||
|
||||
dlg.ShowModal();
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
wxArrayString paths;
|
||||
dlg.GetPaths(paths);
|
||||
FilePathList ret;
|
||||
|
||||
PathList ret(paths.size(), "");
|
||||
for(auto& p : paths) ret.push_back(p.ToStdString());
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
wxArrayString paths;
|
||||
dlg.GetPaths(paths);
|
||||
for(auto& p : paths) ret.push_back(p.ToStdString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::Path
|
||||
AppControllerBoilerplate::query_destination_path(
|
||||
FilePath AppControllerGui::query_destination_path(
|
||||
const std::string &title,
|
||||
const std::string &extensions,
|
||||
const std::string &/*functionid*/,
|
||||
const std::string& hint) const
|
||||
{
|
||||
wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) );
|
||||
|
|
@ -61,16 +69,16 @@ AppControllerBoilerplate::query_destination_path(
|
|||
|
||||
dlg.SetFilename(hint);
|
||||
|
||||
Path ret;
|
||||
FilePath ret;
|
||||
|
||||
if(dlg.ShowModal() == wxID_OK) {
|
||||
ret = Path(dlg.GetPath());
|
||||
ret = FilePath(dlg.GetPath());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AppControllerBoilerplate::report_issue(IssueType issuetype,
|
||||
bool AppControllerGui::report_issue(IssueType issuetype,
|
||||
const std::string &description,
|
||||
const std::string &brief)
|
||||
{
|
||||
|
|
@ -88,14 +96,52 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype,
|
|||
return ret != wxCANCEL;
|
||||
}
|
||||
|
||||
bool AppControllerBoilerplate::report_issue(
|
||||
AppControllerBoilerplate::IssueType issuetype,
|
||||
const std::string &description)
|
||||
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
|
||||
|
||||
struct Zipper::Impl {
|
||||
wxFileName fpath;
|
||||
wxFFileOutputStream zipfile;
|
||||
wxZipOutputStream zipstream;
|
||||
wxStdOutputStream pngstream;
|
||||
|
||||
Impl(const std::string& zipfile_path):
|
||||
fpath(zipfile_path),
|
||||
zipfile(zipfile_path),
|
||||
zipstream(zipfile),
|
||||
pngstream(zipstream)
|
||||
{
|
||||
if(!zipfile.IsOk())
|
||||
throw std::runtime_error(L("Cannot create zip file."));
|
||||
}
|
||||
};
|
||||
|
||||
Zipper::Zipper(const std::string &zipfilepath)
|
||||
{
|
||||
return report_issue(issuetype, description, std::string());
|
||||
m_impl.reset(new Impl(zipfilepath));
|
||||
}
|
||||
|
||||
wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent);
|
||||
Zipper::~Zipper() {}
|
||||
|
||||
void Zipper::next_entry(const std::string &fname)
|
||||
{
|
||||
m_impl->zipstream.PutNextEntry(fname);
|
||||
}
|
||||
|
||||
std::string Zipper::get_name() const
|
||||
{
|
||||
return m_impl->fpath.GetName().ToStdString();
|
||||
}
|
||||
|
||||
std::ostream &Zipper::stream()
|
||||
{
|
||||
return m_impl->pngstream;
|
||||
}
|
||||
|
||||
void Zipper::close()
|
||||
{
|
||||
m_impl->zipstream.Close();
|
||||
m_impl->zipfile.Close();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -106,49 +152,52 @@ namespace {
|
|||
class GuiProgressIndicator:
|
||||
public ProgressIndicator, public wxEvtHandler {
|
||||
|
||||
wxProgressDialog gauge_;
|
||||
wxProgressDialog m_gauge;
|
||||
using Base = ProgressIndicator;
|
||||
wxString message_;
|
||||
int range_; wxString title_;
|
||||
bool is_asynch_ = false;
|
||||
wxString m_message;
|
||||
int m_range; wxString m_title;
|
||||
bool m_is_asynch = false;
|
||||
|
||||
const int id_ = wxWindow::NewControlId();
|
||||
const int m_id = wxWindow::NewControlId();
|
||||
|
||||
// status update handler
|
||||
void _state( wxCommandEvent& evt) {
|
||||
unsigned st = evt.GetInt();
|
||||
message_ = evt.GetString();
|
||||
m_message = evt.GetString();
|
||||
_state(st);
|
||||
}
|
||||
|
||||
// Status update implementation
|
||||
void _state( unsigned st) {
|
||||
if(!gauge_.IsShown()) gauge_.ShowModal();
|
||||
if(!m_gauge.IsShown()) m_gauge.ShowModal();
|
||||
Base::state(st);
|
||||
gauge_.Update(static_cast<int>(st), message_);
|
||||
if(!m_gauge.Update(static_cast<int>(st), m_message)) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Setting whether it will be used from the UI thread or some worker thread
|
||||
inline void asynch(bool is) { is_asynch_ = is; }
|
||||
inline void asynch(bool is) { m_is_asynch = is; }
|
||||
|
||||
/// Get the mode of parallel operation.
|
||||
inline bool asynch() const { return is_asynch_; }
|
||||
inline bool asynch() const { return m_is_asynch; }
|
||||
|
||||
inline GuiProgressIndicator(int range, const wxString& title,
|
||||
const wxString& firstmsg) :
|
||||
gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(),
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE),
|
||||
message_(firstmsg),
|
||||
range_(range), title_(title)
|
||||
m_gauge(title, firstmsg, range, wxTheApp->GetTopWindow(),
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT),
|
||||
|
||||
m_message(firstmsg),
|
||||
m_range(range), m_title(title)
|
||||
{
|
||||
Base::max(static_cast<float>(range));
|
||||
Base::states(static_cast<unsigned>(range));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&GuiProgressIndicator::_state,
|
||||
this, id_);
|
||||
this, m_id);
|
||||
}
|
||||
|
||||
virtual void state(float val) override {
|
||||
|
|
@ -157,33 +206,32 @@ public:
|
|||
|
||||
void state(unsigned st) {
|
||||
// send status update event
|
||||
if(is_asynch_) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
|
||||
if(m_is_asynch) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, m_id);
|
||||
evt->SetInt(st);
|
||||
evt->SetString(message_);
|
||||
evt->SetString(m_message);
|
||||
wxQueueEvent(this, evt);
|
||||
} else _state(st);
|
||||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
message_ = _(msg);
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void messageFmt(const std::string& fmt, ...) {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
message_ = wxString::Format(_(fmt), arglist);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & title) override {
|
||||
title_ = _(title);
|
||||
m_title = _(title);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr
|
||||
AppControllerBoilerplate::create_progress_indicator(
|
||||
ProgresIndicatorPtr AppControllerGui::create_progress_indicator(
|
||||
unsigned statenum,
|
||||
const std::string& title,
|
||||
const std::string& firstmsg) const
|
||||
|
|
@ -198,40 +246,23 @@ AppControllerBoilerplate::create_progress_indicator(
|
|||
return pri;
|
||||
}
|
||||
|
||||
AppControllerBoilerplate::ProgresIndicatorPtr
|
||||
AppControllerBoilerplate::create_progress_indicator(
|
||||
unsigned statenum, const std::string &title) const
|
||||
{
|
||||
return create_progress_indicator(statenum, title, std::string());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A wrapper progress indicator class around the statusbar created in perl.
|
||||
class Wrapper: public ProgressIndicator, public wxEvtHandler {
|
||||
wxGauge *gauge_;
|
||||
wxStatusBar *stbar_;
|
||||
ProgressStatusBar *m_sbar;
|
||||
using Base = ProgressIndicator;
|
||||
wxString message_;
|
||||
AppControllerBoilerplate& ctl_;
|
||||
wxString m_message;
|
||||
AppControllerBase& m_ctl;
|
||||
|
||||
void showProgress(bool show = true) {
|
||||
gauge_->Show(show);
|
||||
m_sbar->show_progress(show);
|
||||
}
|
||||
|
||||
void _state(unsigned st) {
|
||||
if( st <= ProgressIndicator::max() ) {
|
||||
Base::state(st);
|
||||
|
||||
if(!gauge_->IsShown()) showProgress(true);
|
||||
|
||||
stbar_->SetStatusText(message_);
|
||||
if(static_cast<long>(st) == gauge_->GetRange()) {
|
||||
gauge_->SetValue(0);
|
||||
showProgress(false);
|
||||
} else {
|
||||
gauge_->SetValue(static_cast<int>(st));
|
||||
}
|
||||
m_sbar->set_status_text(m_message);
|
||||
m_sbar->set_progress(st);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,12 +275,12 @@ class Wrapper: public ProgressIndicator, public wxEvtHandler {
|
|||
|
||||
public:
|
||||
|
||||
inline Wrapper(wxGauge *gauge, wxStatusBar *stbar,
|
||||
AppControllerBoilerplate& ctl):
|
||||
gauge_(gauge), stbar_(stbar), ctl_(ctl)
|
||||
inline Wrapper(ProgressStatusBar *sbar,
|
||||
AppControllerBase& ctl):
|
||||
m_sbar(sbar), m_ctl(ctl)
|
||||
{
|
||||
Base::max(static_cast<float>(gauge->GetRange()));
|
||||
Base::states(static_cast<unsigned>(gauge->GetRange()));
|
||||
Base::max(static_cast<float>(m_sbar->get_range()));
|
||||
Base::states(static_cast<unsigned>(m_sbar->get_range()));
|
||||
|
||||
Bind(PROGRESS_STATUS_UPDATE_EVENT,
|
||||
&Wrapper::_state,
|
||||
|
|
@ -262,13 +293,13 @@ public:
|
|||
|
||||
virtual void max(float val) override {
|
||||
if(val > 1.0) {
|
||||
gauge_->SetRange(static_cast<int>(val));
|
||||
m_sbar->set_range(static_cast<int>(val));
|
||||
ProgressIndicator::max(val);
|
||||
}
|
||||
}
|
||||
|
||||
void state(unsigned st) {
|
||||
if(!ctl_.is_main_thread()) {
|
||||
if(!m_ctl.is_main_thread()) {
|
||||
auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_);
|
||||
evt->SetInt(st);
|
||||
wxQueueEvent(this, evt);
|
||||
|
|
@ -278,30 +309,32 @@ public:
|
|||
}
|
||||
|
||||
virtual void message(const std::string & msg) override {
|
||||
message_ = _(msg);
|
||||
m_message = _(msg);
|
||||
}
|
||||
|
||||
virtual void message_fmt(const std::string& fmt, ...) override {
|
||||
va_list arglist;
|
||||
va_start(arglist, fmt);
|
||||
message_ = wxString::Format(_(fmt), arglist);
|
||||
m_message = wxString::Format(_(fmt), arglist);
|
||||
va_end(arglist);
|
||||
}
|
||||
|
||||
virtual void title(const std::string & /*title*/) override {}
|
||||
|
||||
virtual void on_cancel(CancelFn fn) override {
|
||||
m_sbar->set_cancel_callback(fn);
|
||||
Base::on_cancel(fn);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
void AppController::set_global_progress_indicator(
|
||||
unsigned gid,
|
||||
unsigned sid)
|
||||
void AppController::set_global_progress_indicator(ProgressStatusBar *prsb)
|
||||
{
|
||||
wxGauge* gauge = dynamic_cast<wxGauge*>(wxWindow::FindWindowById(gid));
|
||||
wxStatusBar* sb = dynamic_cast<wxStatusBar*>(wxWindow::FindWindowById(sid));
|
||||
|
||||
if(gauge && sb) {
|
||||
global_progressind_ = std::make_shared<Wrapper>(gauge, sb, *this);
|
||||
if(prsb) {
|
||||
auto ctl = GUI::get_appctl();
|
||||
ctl->global_progress_indicator(std::make_shared<Wrapper>(prsb, *ctl));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,11 @@ const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
|
|||
|
||||
GLVolume::GLVolume(float r, float g, float b, float a)
|
||||
: m_offset(Vec3d::Zero())
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
, m_rotation(Vec3d::Zero())
|
||||
#else
|
||||
, m_rotation(0.0)
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
, m_scaling_factor(1.0)
|
||||
, m_world_matrix(Transform3f::Identity())
|
||||
, m_world_matrix_dirty(true)
|
||||
|
|
@ -255,7 +259,24 @@ void GLVolume::set_render_color()
|
|||
set_render_color(color, 4);
|
||||
}
|
||||
|
||||
double GLVolume::get_rotation()
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const Vec3d& GLVolume::get_rotation() const
|
||||
{
|
||||
return m_rotation;
|
||||
}
|
||||
|
||||
void GLVolume::set_rotation(const Vec3d& rotation)
|
||||
{
|
||||
if (m_rotation != rotation)
|
||||
{
|
||||
m_rotation = rotation;
|
||||
m_world_matrix_dirty = true;
|
||||
m_transformed_bounding_box_dirty = true;
|
||||
m_transformed_convex_hull_bounding_box_dirty = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
double GLVolume::get_rotation() const
|
||||
{
|
||||
return m_rotation;
|
||||
}
|
||||
|
|
@ -270,6 +291,7 @@ void GLVolume::set_rotation(double rotation)
|
|||
m_transformed_convex_hull_bounding_box_dirty = true;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
const Vec3d& GLVolume::get_offset() const
|
||||
{
|
||||
|
|
@ -327,7 +349,13 @@ const Transform3f& GLVolume::world_matrix() const
|
|||
{
|
||||
m_world_matrix = Transform3f::Identity();
|
||||
m_world_matrix.translate(m_offset.cast<float>());
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_world_matrix.rotate(Eigen::AngleAxisf((float)m_rotation(2), Vec3f::UnitZ()));
|
||||
m_world_matrix.rotate(Eigen::AngleAxisf((float)m_rotation(1), Vec3f::UnitY()));
|
||||
m_world_matrix.rotate(Eigen::AngleAxisf((float)m_rotation(0), Vec3f::UnitX()));
|
||||
#else
|
||||
m_world_matrix.rotate(Eigen::AngleAxisf((float)m_rotation, Vec3f::UnitZ()));
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_world_matrix.scale((float)m_scaling_factor);
|
||||
m_world_matrix_dirty = false;
|
||||
}
|
||||
|
|
@ -403,7 +431,13 @@ void GLVolume::render() const
|
|||
::glCullFace(GL_BACK);
|
||||
::glPushMatrix();
|
||||
::glTranslated(m_offset(0), m_offset(1), m_offset(2));
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glRotated(m_rotation(2) * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
::glRotated(m_rotation(1) * 180.0 / (double)PI, 0.0, 1.0, 0.0);
|
||||
::glRotated(m_rotation(0) * 180.0 / (double)PI, 1.0, 0.0, 0.0);
|
||||
#else
|
||||
::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor);
|
||||
if (this->indexed_vertex_array.indexed())
|
||||
this->indexed_vertex_array.render(this->tverts_range, this->qverts_range);
|
||||
|
|
@ -529,7 +563,13 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c
|
|||
|
||||
::glPushMatrix();
|
||||
::glTranslated(m_offset(0), m_offset(1), m_offset(2));
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glRotated(m_rotation(2) * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
::glRotated(m_rotation(1) * 180.0 / (double)PI, 0.0, 1.0, 0.0);
|
||||
::glRotated(m_rotation(0) * 180.0 / (double)PI, 1.0, 0.0, 0.0);
|
||||
#else
|
||||
::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor);
|
||||
|
||||
if (n_triangles > 0)
|
||||
|
|
@ -574,7 +614,13 @@ void GLVolume::render_legacy() const
|
|||
|
||||
::glPushMatrix();
|
||||
::glTranslated(m_offset(0), m_offset(1), m_offset(2));
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glRotated(m_rotation(2) * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
::glRotated(m_rotation(1) * 180.0 / (double)PI, 0.0, 1.0, 0.0);
|
||||
::glRotated(m_rotation(0) * 180.0 / (double)PI, 1.0, 0.0, 0.0);
|
||||
#else
|
||||
::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor);
|
||||
|
||||
if (n_triangles > 0)
|
||||
|
|
@ -591,7 +637,7 @@ double GLVolume::layer_height_texture_z_to_row_id() const
|
|||
return (this->layer_height_texture.get() == nullptr) ? 0.0 : double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * this->layer_height_texture_data.print_object->model_object()->bounding_box().max(2));
|
||||
}
|
||||
|
||||
void GLVolume::generate_layer_height_texture(PrintObject *print_object, bool force)
|
||||
void GLVolume::generate_layer_height_texture(const PrintObject *print_object, bool force)
|
||||
{
|
||||
LayersTexture *tex = this->layer_height_texture.get();
|
||||
if (tex == nullptr)
|
||||
|
|
@ -599,7 +645,7 @@ void GLVolume::generate_layer_height_texture(PrintObject *print_object, bool for
|
|||
return;
|
||||
|
||||
// Always try to update the layer height profile.
|
||||
bool update = print_object->update_layer_height_profile(print_object->model_object()->layer_height_profile) || force;
|
||||
bool update = print_object->update_layer_height_profile(const_cast<ModelObject*>(print_object->model_object())->layer_height_profile) || force;
|
||||
// Update if the layer height profile was changed, or when the texture is not valid.
|
||||
if (! update && ! tex->data.empty() && tex->cells > 0)
|
||||
// Texture is valid, don't update.
|
||||
|
|
@ -698,7 +744,11 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
#else
|
||||
v.set_offset(Vec3d(instance->offset(0), instance->offset(1), 0.0));
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
v.set_rotation(instance->get_rotation());
|
||||
#else
|
||||
v.set_rotation(instance->rotation);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
v.set_scaling_factor(instance->scaling_factor);
|
||||
}
|
||||
}
|
||||
|
|
@ -1687,7 +1737,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity,
|
|||
if (extrusion_entity_collection != nullptr)
|
||||
extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume);
|
||||
else {
|
||||
CONFESS("Unexpected extrusion_entity type in to_verts()");
|
||||
throw std::runtime_error("Unexpected extrusion_entity type in to_verts()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2067,12 +2117,30 @@ void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, vo
|
|||
|
||||
void _3DScene::register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback)
|
||||
{
|
||||
#if !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
s_canvas_mgr.register_on_gizmo_rotate_callback(canvas, callback);
|
||||
#endif // !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
void _3DScene::register_on_gizmo_rotate_3D_callback(wxGLCanvas* canvas, void* callback)
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
s_canvas_mgr.register_on_gizmo_rotate_3D_callback(canvas, callback);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
void _3DScene::register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback)
|
||||
{
|
||||
#if !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
s_canvas_mgr.register_on_gizmo_flatten_callback(canvas, callback);
|
||||
#endif // !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
void _3DScene::register_on_gizmo_flatten_3D_callback(wxGLCanvas* canvas, void* callback)
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
s_canvas_mgr.register_on_gizmo_flatten_3D_callback(canvas, callback);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
void _3DScene::register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback)
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class GLVolume {
|
|||
// ID of the shader used to render with the layer height texture
|
||||
unsigned int shader_id;
|
||||
// The print object to update when generating the layer height texture
|
||||
PrintObject* print_object;
|
||||
const PrintObject* print_object;
|
||||
|
||||
float z_cursor_relative;
|
||||
float edit_band_width;
|
||||
|
|
@ -256,8 +256,13 @@ public:
|
|||
private:
|
||||
// Offset of the volume to be rendered.
|
||||
Vec3d m_offset;
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Rotation around three axes of the volume to be rendered.
|
||||
Vec3d m_rotation;
|
||||
#else
|
||||
// Rotation around Z axis of the volume to be rendered.
|
||||
double m_rotation;
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Scale factor of the volume to be rendered.
|
||||
double m_scaling_factor;
|
||||
// World matrix of the volume to be rendered.
|
||||
|
|
@ -327,8 +332,13 @@ public:
|
|||
// Sets render color in dependence of current state
|
||||
void set_render_color();
|
||||
|
||||
double get_rotation();
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const Vec3d& get_rotation() const;
|
||||
void set_rotation(const Vec3d& rotation);
|
||||
#else
|
||||
double get_rotation() const;
|
||||
void set_rotation(double rotation);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
const Vec3d& get_offset() const;
|
||||
void set_offset(const Vec3d& offset);
|
||||
|
|
@ -382,9 +392,9 @@ public:
|
|||
(void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4);
|
||||
}
|
||||
double layer_height_texture_z_to_row_id() const;
|
||||
void generate_layer_height_texture(PrintObject *print_object, bool force);
|
||||
void generate_layer_height_texture(const PrintObject *print_object, bool force);
|
||||
|
||||
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, PrintObject* print_object, float z_cursor_relative, float edit_band_width)
|
||||
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, const PrintObject* print_object, float z_cursor_relative, float edit_band_width)
|
||||
{
|
||||
layer_height_texture_data.texture_id = texture_id;
|
||||
layer_height_texture_data.shader_id = shader_id;
|
||||
|
|
@ -558,7 +568,9 @@ public:
|
|||
static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_gizmo_rotate_3D_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_gizmo_flatten_3D_callback(wxGLCanvas* canvas, void* callback);
|
||||
static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback);
|
||||
|
||||
static void register_action_add_callback(wxGLCanvas* canvas, void* callback);
|
||||
|
|
|
|||
167
xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
167
xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "GUI.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
#include "../../libslic3r/GCode/PostProcessor.hpp"
|
||||
|
||||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
extern wxPanel *g_wxPlater;
|
||||
};
|
||||
|
||||
BackgroundSlicingProcess::BackgroundSlicingProcess()
|
||||
{
|
||||
m_temp_output_path = wxStandardPaths::Get().GetTempDir().utf8_str().data();
|
||||
m_temp_output_path += (boost::format(".%1%.gcode") % get_current_pid()).str();
|
||||
}
|
||||
|
||||
BackgroundSlicingProcess::~BackgroundSlicingProcess()
|
||||
{
|
||||
this->stop();
|
||||
this->join_background_thread();
|
||||
boost::nowide::remove(m_temp_output_path.c_str());
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::thread_proc()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
// Let the caller know we are ready to run the background processing task.
|
||||
m_state = STATE_IDLE;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
for (;;) {
|
||||
assert(m_state == STATE_IDLE || m_state == STATE_CANCELED || m_state == STATE_FINISHED);
|
||||
// Wait until a new task is ready to be executed, or this thread should be finished.
|
||||
lck.lock();
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
|
||||
if (m_state == STATE_EXIT)
|
||||
// Exiting this thread.
|
||||
break;
|
||||
// Process the background slicing task.
|
||||
m_state = STATE_RUNNING;
|
||||
lck.unlock();
|
||||
std::string error;
|
||||
try {
|
||||
assert(m_print != nullptr);
|
||||
m_print->process();
|
||||
if (! m_print->canceled()) {
|
||||
wxQueueEvent(GUI::g_wxPlater, new wxCommandEvent(m_event_sliced_id));
|
||||
m_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
if (! m_print->canceled() && ! m_output_path.empty()) {
|
||||
if (copy_file(m_temp_output_path, m_output_path) != 0)
|
||||
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
|
||||
m_print->set_status(95, "Running post-processing scripts");
|
||||
run_post_process_scripts(m_output_path, m_print->config());
|
||||
}
|
||||
}
|
||||
} catch (CanceledException &ex) {
|
||||
// Canceled, this is all right.
|
||||
assert(m_print->canceled());
|
||||
} catch (std::exception &ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown C++ exception.";
|
||||
}
|
||||
lck.lock();
|
||||
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
|
||||
wxCommandEvent evt(m_event_finished_id);
|
||||
evt.SetString(error);
|
||||
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
|
||||
wxQueueEvent(GUI::g_wxPlater, evt.Clone());
|
||||
m_print->restart();
|
||||
lck.unlock();
|
||||
// Let the UI thread wake up if it is waiting for the background task to finish.
|
||||
m_condition.notify_one();
|
||||
// Let the UI thread see the result.
|
||||
}
|
||||
m_state = STATE_EXITED;
|
||||
lck.unlock();
|
||||
// End of the background processing thread. The UI thread should join m_thread now.
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::join_background_thread()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// Worker thread has not been started yet.
|
||||
assert(! m_thread.joinable());
|
||||
} else {
|
||||
assert(m_state == STATE_IDLE);
|
||||
assert(m_thread.joinable());
|
||||
// Notify the worker thread to exit.
|
||||
m_state = STATE_EXIT;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
// Wait until the worker thread exits.
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::start()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// The worker thread is not running yet. Start it.
|
||||
assert(! m_thread.joinable());
|
||||
m_thread = std::thread([this]{this->thread_proc();});
|
||||
// Wait until the worker thread is ready to execute the background processing task.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
|
||||
}
|
||||
assert(m_state == STATE_IDLE || this->running());
|
||||
if (this->running())
|
||||
// The background processing thread is already running.
|
||||
return false;
|
||||
if (! this->idle())
|
||||
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
|
||||
m_state = STATE_STARTED;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::stop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
this->m_output_path.clear();
|
||||
return false;
|
||||
}
|
||||
assert(this->running());
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
m_print->cancel();
|
||||
// Wait until the background processing stops by being canceled.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
|
||||
// In the "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
} else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) {
|
||||
// In the "Finished" or "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
}
|
||||
this->m_output_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
bool BackgroundSlicingProcess::apply_config(const DynamicPrintConfig &config)
|
||||
{
|
||||
this->stop();
|
||||
bool invalidated = m_print->apply_config(config);
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
91
xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
91
xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class GCodePreviewData;
|
||||
class Print;
|
||||
|
||||
// Support for the GUI background processing (Slicing and G-code generation).
|
||||
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
|
||||
class BackgroundSlicingProcess
|
||||
{
|
||||
public:
|
||||
BackgroundSlicingProcess();
|
||||
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
|
||||
~BackgroundSlicingProcess();
|
||||
|
||||
void set_print(Print *print) { m_print = print; }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
|
||||
// and the background processing will transition into G-code export.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
|
||||
|
||||
// Set the output path of the G-code.
|
||||
void set_output_path(const std::string &path) { m_output_path = path; }
|
||||
// Start the background processing. Returns false if the background processing was already running.
|
||||
bool start();
|
||||
// Cancel the background processing. Returns false if the background processing was not running.
|
||||
// A stopped background processing may be restarted with start().
|
||||
bool stop();
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
bool apply_config(const DynamicPrintConfig &config);
|
||||
|
||||
enum State {
|
||||
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
|
||||
STATE_INITIAL = 0,
|
||||
// m_thread is waiting for the task to execute.
|
||||
STATE_IDLE,
|
||||
STATE_STARTED,
|
||||
// m_thread is executing a task.
|
||||
STATE_RUNNING,
|
||||
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
|
||||
STATE_FINISHED,
|
||||
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
|
||||
STATE_CANCELED,
|
||||
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
|
||||
STATE_EXIT,
|
||||
STATE_EXITED,
|
||||
};
|
||||
State state() const { return m_state; }
|
||||
bool idle() const { return m_state == STATE_IDLE; }
|
||||
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
|
||||
|
||||
private:
|
||||
void thread_proc();
|
||||
void join_background_thread();
|
||||
|
||||
Print *m_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
std::string m_temp_output_path;
|
||||
std::string m_output_path;
|
||||
// Thread, on which the background processing is executed. The thread will always be present
|
||||
// and ready to execute the slicing process.
|
||||
std::thread m_thread;
|
||||
// Mutex and condition variable to synchronize m_thread with the UI thread.
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
State m_state = STATE_INITIAL;
|
||||
|
||||
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
|
||||
int m_event_sliced_id = 0;
|
||||
// wxWidgets command ID to be sent to the platter to inform that the task finished.
|
||||
int m_event_finished_id = 0;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
#include "../../slic3r/GUI/GLGizmo.hpp"
|
||||
#include "../../libslic3r/ClipperUtils.hpp"
|
||||
#include "../../libslic3r/PrintConfig.hpp"
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
#include "../../libslic3r/GCode/PreviewData.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
|
@ -21,6 +20,9 @@
|
|||
#include <wx/image.h>
|
||||
#include <wx/settings.h>
|
||||
|
||||
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
||||
#include "../../libslic3r/Print.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
|
||||
|
|
@ -1031,7 +1033,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
|
|||
// Get a maximum layer height value.
|
||||
// FIXME This is a duplicate code of Slicing.cpp.
|
||||
double layer_height_max = DBL_MAX;
|
||||
const PrintConfig& print_config = print_object.print()->config;
|
||||
const PrintConfig& print_config = print_object.print()->config();
|
||||
const std::vector<double>& nozzle_diameters = dynamic_cast<const ConfigOptionFloats*>(print_config.option("nozzle_diameter"))->values;
|
||||
const std::vector<double>& layer_heights_min = dynamic_cast<const ConfigOptionFloats*>(print_config.option("min_layer_height"))->values;
|
||||
const std::vector<double>& layer_heights_max = dynamic_cast<const ConfigOptionFloats*>(print_config.option("max_layer_height"))->values;
|
||||
|
|
@ -1046,7 +1048,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
|
|||
layer_height_max *= 1.12;
|
||||
|
||||
double max_z = unscale<double>(print_object.size(2));
|
||||
double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config.option("layer_height"))->value;
|
||||
double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config().option("layer_height"))->value;
|
||||
float l = bar_rect.get_left();
|
||||
float w = bar_rect.get_right() - l;
|
||||
float b = bar_rect.get_bottom();
|
||||
|
|
@ -1095,6 +1097,9 @@ GLCanvas3D::Mouse::Drag::Drag()
|
|||
GLCanvas3D::Mouse::Mouse()
|
||||
: dragging(false)
|
||||
, position(DBL_MAX, DBL_MAX)
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
, ignore_up_event(false)
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1181,9 +1186,11 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent)
|
|||
return false;
|
||||
}
|
||||
|
||||
#if !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// temporary disable x and y grabbers
|
||||
gizmo->disable_grabber(0);
|
||||
gizmo->disable_grabber(1);
|
||||
#endif // !ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo));
|
||||
|
||||
|
|
@ -1360,6 +1367,18 @@ void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray, const Point* mouse_pos)
|
|||
curr->update(mouse_ray, mouse_pos);
|
||||
}
|
||||
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
void GLCanvas3D::Gizmos::process_double_click()
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
GLGizmoBase* curr = _get_current();
|
||||
if (curr != nullptr)
|
||||
curr->process_double_click();
|
||||
}
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
|
||||
GLCanvas3D::Gizmos::EType GLCanvas3D::Gizmos::get_current_type() const
|
||||
{
|
||||
return m_current;
|
||||
|
|
@ -1432,6 +1451,35 @@ void GLCanvas3D::Gizmos::set_scale(float scale)
|
|||
reinterpret_cast<GLGizmoScale3D*>(it->second)->set_scale(scale);
|
||||
}
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
Vec3d GLCanvas3D::Gizmos::get_rotation() const
|
||||
{
|
||||
if (!m_enabled)
|
||||
return Vec3d::Zero();
|
||||
|
||||
GizmosMap::const_iterator it = m_gizmos.find(Rotate);
|
||||
return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoRotate3D*>(it->second)->get_rotation() : Vec3d::Zero();
|
||||
}
|
||||
|
||||
void GLCanvas3D::Gizmos::set_rotation(const Vec3d& rotation)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
GizmosMap::const_iterator it = m_gizmos.find(Rotate);
|
||||
if (it != m_gizmos.end())
|
||||
reinterpret_cast<GLGizmoRotate3D*>(it->second)->set_rotation(rotation);
|
||||
}
|
||||
|
||||
Vec3d GLCanvas3D::Gizmos::get_flattening_rotation() const
|
||||
{
|
||||
if (!m_enabled)
|
||||
return Vec3d::Zero();
|
||||
|
||||
GizmosMap::const_iterator it = m_gizmos.find(Flatten);
|
||||
return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoFlatten*>(it->second)->get_flattening_rotation() : Vec3d::Zero();
|
||||
}
|
||||
#else
|
||||
float GLCanvas3D::Gizmos::get_angle_z() const
|
||||
{
|
||||
if (!m_enabled)
|
||||
|
|
@ -1459,6 +1507,7 @@ Vec3d GLCanvas3D::Gizmos::get_flattening_normal() const
|
|||
GizmosMap::const_iterator it = m_gizmos.find(Flatten);
|
||||
return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoFlatten*>(it->second)->get_flattening_normal() : Vec3d::Zero();
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object)
|
||||
{
|
||||
|
|
@ -1619,7 +1668,7 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg)
|
|||
wxCoord w, h;
|
||||
memDC.GetTextExtent(msg, &w, &h);
|
||||
|
||||
int pow_of_two_size = next_highest_power_of_2((int)std::max(w, h));
|
||||
int pow_of_two_size = next_highest_power_of_2(std::max<unsigned int>(w, h));
|
||||
|
||||
m_original_width = (int)w;
|
||||
m_original_height = (int)h;
|
||||
|
|
@ -2428,7 +2477,11 @@ void GLCanvas3D::update_gizmos_data()
|
|||
m_gizmos.set_position(Vec3d(model_instance->offset(0), model_instance->offset(1), 0.0));
|
||||
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
|
||||
m_gizmos.set_scale(model_instance->scaling_factor);
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_gizmos.set_rotation(model_instance->get_rotation());
|
||||
#else
|
||||
m_gizmos.set_angle_z(model_instance->rotation);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_gizmos.set_flattening_data(model_object);
|
||||
m_gizmos.set_model_object_ptr(model_object);
|
||||
}
|
||||
|
|
@ -2438,7 +2491,11 @@ void GLCanvas3D::update_gizmos_data()
|
|||
{
|
||||
m_gizmos.set_position(Vec3d::Zero());
|
||||
m_gizmos.set_scale(1.0f);
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_gizmos.set_rotation(Vec3d::Zero());
|
||||
#else
|
||||
m_gizmos.set_angle_z(0.0f);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_gizmos.set_flattening_data(nullptr);
|
||||
}
|
||||
}
|
||||
|
|
@ -2626,11 +2683,11 @@ void GLCanvas3D::reload_scene(bool force)
|
|||
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||
|
||||
float depth = m_print->get_wipe_tower_depth();
|
||||
if (!m_print->state.is_done(psWipeTower))
|
||||
if (!m_print->is_step_done(psWipeTower))
|
||||
depth = (900.f/w) * (float)(extruders_count - 1) ;
|
||||
|
||||
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower),
|
||||
m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower),
|
||||
m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2710,7 +2767,7 @@ void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors)
|
|||
|
||||
_load_print_toolpaths();
|
||||
_load_wipe_tower_toolpaths(str_tool_colors);
|
||||
for (const PrintObject* object : m_print->objects)
|
||||
for (const PrintObject* object : m_print->objects())
|
||||
{
|
||||
if (object != nullptr)
|
||||
_load_print_object_toolpaths(*object, str_tool_colors);
|
||||
|
|
@ -2822,6 +2879,19 @@ void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback)
|
|||
m_on_gizmo_scale_uniformly_callback.register_callback(callback);
|
||||
}
|
||||
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
void GLCanvas3D::register_on_gizmo_rotate_3D_callback(void* callback)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
m_on_gizmo_rotate_3D_callback.register_callback(callback);
|
||||
}
|
||||
|
||||
void GLCanvas3D::register_on_gizmo_flatten_3D_callback(void* callback)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
m_on_gizmo_flatten_3D_callback.register_callback(callback);
|
||||
}
|
||||
#else
|
||||
void GLCanvas3D::register_on_gizmo_rotate_callback(void* callback)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
|
|
@ -2833,6 +2903,7 @@ void GLCanvas3D::register_on_gizmo_flatten_callback(void* callback)
|
|||
if (callback != nullptr)
|
||||
m_on_gizmo_flatten_callback.register_callback(callback);
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
|
||||
void GLCanvas3D::register_on_update_geometry_info_callback(void* callback)
|
||||
{
|
||||
|
|
@ -3108,6 +3179,41 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
m_toolbar_action_running = true;
|
||||
m_toolbar.do_action((unsigned int)toolbar_contains_mouse);
|
||||
}
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
else if (evt.LeftDClick() && m_gizmos.grabber_contains_mouse())
|
||||
{
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
m_mouse.ignore_up_event = true;
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
m_gizmos.process_double_click();
|
||||
switch (m_gizmos.get_current_type())
|
||||
{
|
||||
case Gizmos::Scale:
|
||||
{
|
||||
m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale());
|
||||
update_scale_values();
|
||||
m_dirty = true;
|
||||
break;
|
||||
}
|
||||
case Gizmos::Rotate:
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const Vec3d& rotation = m_gizmos.get_rotation();
|
||||
m_on_gizmo_rotate_3D_callback.call(rotation(0), rotation(1), rotation(2));
|
||||
#else
|
||||
m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
update_rotation_values();
|
||||
m_dirty = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
else if (evt.LeftDown() || evt.RightDown())
|
||||
{
|
||||
// If user pressed left or right button we first check whether this happened
|
||||
|
|
@ -3126,7 +3232,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
if (evt.LeftDown())
|
||||
{
|
||||
// A volume is selected and the mouse is inside the reset button.
|
||||
m_print->get_object(layer_editing_object_idx)->reset_layer_height_profile();
|
||||
// The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself,
|
||||
// therefore it is safe to call it while the background processing is running.
|
||||
const_cast<PrintObject*>(m_print->get_object(layer_editing_object_idx))->reset_layer_height_profile();
|
||||
// Index 2 means no editing, just wait for mouse up event.
|
||||
m_layers_editing.state = LayersEditing::Completed;
|
||||
|
||||
|
|
@ -3155,6 +3263,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx);
|
||||
|
||||
if (m_gizmos.get_current_type() == Gizmos::Flatten) {
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Rotate the object so the normal points downward:
|
||||
const Vec3d& rotation = m_gizmos.get_flattening_rotation();
|
||||
m_on_gizmo_flatten_3D_callback.call(rotation(0), rotation(1), rotation(2));
|
||||
#else
|
||||
// Rotate the object so the normal points downward:
|
||||
Vec3d normal = m_gizmos.get_flattening_normal();
|
||||
if (normal(0) != 0.0 || normal(1) != 0.0 || normal(2) != 0.0) {
|
||||
|
|
@ -3162,6 +3275,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
float angle = acos(clamp(-1.0, 1.0, -normal(2)));
|
||||
m_on_gizmo_flatten_callback.call(angle, (float)axis(0), (float)axis(1), (float)axis(2));
|
||||
}
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
}
|
||||
|
||||
m_dirty = true;
|
||||
|
|
@ -3348,6 +3462,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
}
|
||||
case Gizmos::Rotate:
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
// Apply new temporary rotation
|
||||
Vec3d rotation = m_gizmos.get_rotation();
|
||||
for (GLVolume* v : volumes)
|
||||
{
|
||||
v->set_rotation(rotation);
|
||||
}
|
||||
update_rotation_value(rotation);
|
||||
#else
|
||||
// Apply new temporary angle_z
|
||||
float angle_z = m_gizmos.get_angle_z();
|
||||
for (GLVolume* v : volumes)
|
||||
|
|
@ -3355,6 +3478,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
v->set_rotation((double)angle_z);
|
||||
}
|
||||
update_rotation_value((double)angle_z, Z);
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
@ -3463,12 +3587,20 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
|
||||
{
|
||||
// deselect and propagate event through callback
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
if (!m_mouse.ignore_up_event && m_picking_enabled && !m_toolbar_action_running)
|
||||
#else
|
||||
if (m_picking_enabled && !m_toolbar_action_running)
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
{
|
||||
deselect_volumes();
|
||||
_on_select(-1, -1);
|
||||
update_gizmos_data();
|
||||
}
|
||||
#if ENABLE_GIZMOS_RESET
|
||||
else if (m_mouse.ignore_up_event)
|
||||
m_mouse.ignore_up_event = false;
|
||||
#endif // ENABLE_GIZMOS_RESET
|
||||
}
|
||||
else if (evt.LeftUp() && m_gizmos.is_dragging())
|
||||
{
|
||||
|
|
@ -3502,14 +3634,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
}
|
||||
case Gizmos::Rotate:
|
||||
{
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
const Vec3d& rotation = m_gizmos.get_rotation();
|
||||
m_on_gizmo_rotate_3D_callback.call(rotation(0), rotation(1), rotation(2));
|
||||
#else
|
||||
m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_gizmos.stop_dragging();
|
||||
Slic3r::GUI::update_settings_value();
|
||||
update_settings_value();
|
||||
}
|
||||
|
||||
m_mouse.drag.move_volume_idx = -1;
|
||||
|
|
@ -3949,8 +4086,13 @@ void GLCanvas3D::_deregister_callbacks()
|
|||
m_on_wipe_tower_moved_callback.deregister_callback();
|
||||
m_on_enable_action_buttons_callback.deregister_callback();
|
||||
m_on_gizmo_scale_uniformly_callback.deregister_callback();
|
||||
#if ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_on_gizmo_rotate_3D_callback.deregister_callback();
|
||||
m_on_gizmo_flatten_3D_callback.deregister_callback();
|
||||
#else
|
||||
m_on_gizmo_rotate_callback.deregister_callback();
|
||||
m_on_gizmo_flatten_callback.deregister_callback();
|
||||
#endif // ENABLE_MODELINSTANCE_3D_ROTATION
|
||||
m_on_update_geometry_info_callback.deregister_callback();
|
||||
|
||||
m_action_add_callback.deregister_callback();
|
||||
|
|
@ -3977,7 +4119,7 @@ void GLCanvas3D::_mark_volumes_for_layer_height() const
|
|||
int shader_id = m_layers_editing.get_shader_program_id();
|
||||
|
||||
if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected &&
|
||||
vol->has_layer_height_texture() && (object_id < (int)m_print->objects.size()))
|
||||
vol->has_layer_height_texture() && (object_id < (int)m_print->objects().size()))
|
||||
{
|
||||
vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id,
|
||||
m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width);
|
||||
|
|
@ -4215,7 +4357,7 @@ void GLCanvas3D::_render_layer_editing_overlay() const
|
|||
// If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion
|
||||
// and an update by Platter::async_apply_config.
|
||||
int object_idx = int(volume->select_group_id / 1000000);
|
||||
if ((int)m_print->objects.size() < object_idx)
|
||||
if ((int)m_print->objects().size() < object_idx)
|
||||
return;
|
||||
|
||||
const PrintObject* print_object = m_print->get_object(object_idx);
|
||||
|
|
@ -4302,7 +4444,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
|
|||
if (m_print == nullptr)
|
||||
return;
|
||||
|
||||
PrintObject* selected_obj = m_print->get_object(object_idx_selected);
|
||||
const PrintObject* selected_obj = m_print->get_object(object_idx_selected);
|
||||
if (selected_obj == nullptr)
|
||||
return;
|
||||
|
||||
|
|
@ -4317,14 +4459,15 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
|
|||
|
||||
// Mark the volume as modified, so Print will pick its layer height profile ? Where to mark it ?
|
||||
// Start a timer to refresh the print ? schedule_background_process() ?
|
||||
// The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself.
|
||||
selected_obj->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action);
|
||||
// The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself,
|
||||
// therefore it is safe to call it while the background processing is running.
|
||||
const_cast<PrintObject*>(selected_obj)->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action);
|
||||
|
||||
// searches the id of the first volume of the selected object
|
||||
int volume_idx = 0;
|
||||
for (int i = 0; i < object_idx_selected; ++i)
|
||||
{
|
||||
PrintObject* obj = m_print->get_object(i);
|
||||
const PrintObject* obj = m_print->get_object(i);
|
||||
if (obj != nullptr)
|
||||
{
|
||||
for (int j = 0; j < (int)obj->region_volumes.size(); ++j)
|
||||
|
|
@ -4395,7 +4538,7 @@ int GLCanvas3D::_get_first_selected_object_id() const
|
|||
{
|
||||
if (m_print != nullptr)
|
||||
{
|
||||
int objects_count = (int)m_print->objects.size();
|
||||
int objects_count = (int)m_print->objects().size();
|
||||
|
||||
for (const GLVolume* vol : m_volumes.volumes)
|
||||
{
|
||||
|
|
@ -4434,36 +4577,36 @@ void GLCanvas3D::_load_print_toolpaths()
|
|||
if (m_print == nullptr)
|
||||
return;
|
||||
|
||||
if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim))
|
||||
if (!m_print->is_step_done(psSkirt) || !m_print->is_step_done(psBrim))
|
||||
return;
|
||||
|
||||
if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0))
|
||||
if (!m_print->has_skirt() && (m_print->config().brim_width.value == 0))
|
||||
return;
|
||||
|
||||
const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish
|
||||
|
||||
// number of skirt layers
|
||||
size_t total_layer_count = 0;
|
||||
for (const PrintObject* print_object : m_print->objects)
|
||||
for (const PrintObject* print_object : m_print->objects())
|
||||
{
|
||||
total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
|
||||
}
|
||||
size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count);
|
||||
if ((skirt_height == 0) && (m_print->config.brim_width.value > 0))
|
||||
size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config().skirt_height.value, total_layer_count);
|
||||
if ((skirt_height == 0) && (m_print->config().brim_width.value > 0))
|
||||
skirt_height = 1;
|
||||
|
||||
// get first skirt_height layers (maybe this should be moved to a PrintObject method?)
|
||||
const PrintObject* object0 = m_print->objects.front();
|
||||
const PrintObject* object0 = m_print->objects().front();
|
||||
std::vector<float> print_zs;
|
||||
print_zs.reserve(skirt_height * 2);
|
||||
for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i)
|
||||
for (size_t i = 0; i < std::min(skirt_height, object0->layers().size()); ++i)
|
||||
{
|
||||
print_zs.push_back(float(object0->layers[i]->print_z));
|
||||
print_zs.push_back(float(object0->layers()[i]->print_z));
|
||||
}
|
||||
//FIXME why there are support layers?
|
||||
for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i)
|
||||
for (size_t i = 0; i < std::min(skirt_height, object0->support_layers().size()); ++i)
|
||||
{
|
||||
print_zs.push_back(float(object0->support_layers[i]->print_z));
|
||||
print_zs.push_back(float(object0->support_layers()[i]->print_z));
|
||||
}
|
||||
sort_remove_duplicates(print_zs);
|
||||
if (print_zs.size() > skirt_height)
|
||||
|
|
@ -4476,9 +4619,9 @@ void GLCanvas3D::_load_print_toolpaths()
|
|||
volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size());
|
||||
volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size());
|
||||
if (i == 0)
|
||||
_3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume);
|
||||
_3DScene::extrusionentity_to_verts(m_print->brim(), print_zs[i], Point(0, 0), volume);
|
||||
|
||||
_3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume);
|
||||
_3DScene::extrusionentity_to_verts(m_print->skirt(), print_zs[i], Point(0, 0), volume);
|
||||
}
|
||||
volume.bounding_box = volume.indexed_vertex_array.bounding_box();
|
||||
volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized);
|
||||
|
|
@ -4517,20 +4660,20 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
|
|||
}
|
||||
} ctxt;
|
||||
|
||||
ctxt.shifted_copies = &print_object._shifted_copies;
|
||||
ctxt.shifted_copies = &print_object.copies();
|
||||
|
||||
// order layers by print_z
|
||||
ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size());
|
||||
for (const Layer *layer : print_object.layers)
|
||||
ctxt.layers.reserve(print_object.layers().size() + print_object.support_layers().size());
|
||||
for (const Layer *layer : print_object.layers())
|
||||
ctxt.layers.push_back(layer);
|
||||
for (const Layer *layer : print_object.support_layers)
|
||||
for (const Layer *layer : print_object.support_layers())
|
||||
ctxt.layers.push_back(layer);
|
||||
std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
|
||||
|
||||
// Maximum size of an allocation block: 32MB / sizeof(float)
|
||||
ctxt.has_perimeters = print_object.state.is_done(posPerimeters);
|
||||
ctxt.has_infill = print_object.state.is_done(posInfill);
|
||||
ctxt.has_support = print_object.state.is_done(posSupportMaterial);
|
||||
ctxt.has_perimeters = print_object.is_step_done(posPerimeters);
|
||||
ctxt.has_infill = print_object.is_step_done(posInfill);
|
||||
ctxt.has_support = print_object.is_step_done(posSupportMaterial);
|
||||
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start";
|
||||
|
|
@ -4570,10 +4713,10 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
|
|||
}
|
||||
}
|
||||
for (const Point © : *ctxt.shifted_copies) {
|
||||
for (const LayerRegion *layerm : layer->regions) {
|
||||
for (const LayerRegion *layerm : layer->regions()) {
|
||||
if (ctxt.has_perimeters)
|
||||
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
|
||||
*vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]);
|
||||
*vols[ctxt.volume_idx(layerm->region()->config().perimeter_extruder.value, 0)]);
|
||||
if (ctxt.has_infill) {
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities) {
|
||||
// fill represents infill extrusions of a single island.
|
||||
|
|
@ -4582,8 +4725,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
|
|||
_3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
|
||||
*vols[ctxt.volume_idx(
|
||||
is_solid_infill(fill->entities.front()->role()) ?
|
||||
layerm->region()->config.solid_infill_extruder :
|
||||
layerm->region()->config.infill_extruder,
|
||||
layerm->region()->config().solid_infill_extruder :
|
||||
layerm->region()->config().infill_extruder,
|
||||
1)]);
|
||||
}
|
||||
}
|
||||
|
|
@ -4595,8 +4738,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
|
|||
_3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy,
|
||||
*vols[ctxt.volume_idx(
|
||||
(extrusion_entity->role() == erSupportMaterial) ?
|
||||
support_layer->object()->config.support_material_extruder :
|
||||
support_layer->object()->config.support_material_interface_extruder,
|
||||
support_layer->object()->config().support_material_extruder :
|
||||
support_layer->object()->config().support_material_interface_extruder,
|
||||
2)]);
|
||||
}
|
||||
}
|
||||
|
|
@ -4640,10 +4783,10 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
|
|||
|
||||
void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty())
|
||||
if ((m_print == nullptr) || m_print->wipe_tower_data().tool_changes.empty())
|
||||
return;
|
||||
|
||||
if (!m_print->state.is_done(psWipeTower))
|
||||
if (!m_print->is_step_done(psWipeTower))
|
||||
return;
|
||||
|
||||
std::vector<float> tool_colors = _parse_colors(str_tool_colors);
|
||||
|
|
@ -4671,9 +4814,10 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
|
|||
}
|
||||
|
||||
const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) {
|
||||
const auto &tool_changes = print->wipe_tower_data().tool_changes;
|
||||
return priming.empty() ?
|
||||
((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) :
|
||||
((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]);
|
||||
((idx == tool_changes.size()) ? final : tool_changes[idx]) :
|
||||
((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]);
|
||||
}
|
||||
std::vector<WipeTower::ToolChangeResult> priming;
|
||||
std::vector<WipeTower::ToolChangeResult> final;
|
||||
|
|
@ -4681,18 +4825,18 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
|
|||
|
||||
ctxt.print = m_print;
|
||||
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
|
||||
if (m_print->m_wipe_tower_priming && m_print->config.single_extruder_multi_material_priming)
|
||||
ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get());
|
||||
if (m_print->m_wipe_tower_final_purge)
|
||||
ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get());
|
||||
if (m_print->wipe_tower_data().priming && m_print->config().single_extruder_multi_material_priming)
|
||||
ctxt.priming.emplace_back(*m_print->wipe_tower_data().priming.get());
|
||||
if (m_print->wipe_tower_data().final_purge)
|
||||
ctxt.final.emplace_back(*m_print->wipe_tower_data().final_purge.get());
|
||||
|
||||
ctxt.wipe_tower_angle = ctxt.print->config.wipe_tower_rotation_angle.value/180.f * PI;
|
||||
ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config.wipe_tower_x.value, ctxt.print->config.wipe_tower_y.value);
|
||||
ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI;
|
||||
ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
|
||||
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
|
||||
size_t n_items = m_print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
|
||||
size_t grain_size = std::max(n_items / 128, size_t(1));
|
||||
tbb::spin_mutex new_volume_mutex;
|
||||
auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* {
|
||||
|
|
@ -5297,15 +5441,15 @@ void GLCanvas3D::_load_shells()
|
|||
size_t initial_volumes_count = m_volumes.volumes.size();
|
||||
m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count);
|
||||
|
||||
if (m_print->objects.empty())
|
||||
if (m_print->objects().empty())
|
||||
// nothing to render, return
|
||||
return;
|
||||
|
||||
// adds objects' volumes
|
||||
unsigned int object_id = 0;
|
||||
for (PrintObject* obj : m_print->objects)
|
||||
for (const PrintObject* obj : m_print->objects())
|
||||
{
|
||||
ModelObject* model_obj = obj->model_object();
|
||||
const ModelObject* model_obj = obj->model_object();
|
||||
|
||||
std::vector<int> instance_ids(model_obj->instances.size());
|
||||
for (int i = 0; i < (int)model_obj->instances.size(); ++i)
|
||||
|
|
@ -5319,15 +5463,15 @@ void GLCanvas3D::_load_shells()
|
|||
}
|
||||
|
||||
// adds wipe tower's volume
|
||||
double max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max(2);
|
||||
const PrintConfig& config = m_print->config;
|
||||
double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
||||
const PrintConfig& config = m_print->config();
|
||||
unsigned int extruders_count = config.nozzle_diameter.size();
|
||||
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
|
||||
float depth = m_print->get_wipe_tower_depth();
|
||||
if (!m_print->state.is_done(psWipeTower))
|
||||
if (!m_print->is_step_done(psWipeTower))
|
||||
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ;
|
||||
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
|
||||
m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue