diff --git a/package/linux/appimage.sh b/package/linux/appimage.sh index 3a68b5e17..05e61f909 100755 --- a/package/linux/appimage.sh +++ b/package/linux/appimage.sh @@ -32,7 +32,7 @@ cd $WD/${APP}.AppDir mkdir -p $WD/${APP}.AppDir/usr/bin # Copy primary Slic3r script here and perl-local, as well as var -for i in {var,slic3r.pl,perl-local}; do +for i in {var,Slic3r}; do cp -R $srcfolder/$i $WD/${APP}.AppDir/usr/bin/ done @@ -48,8 +48,6 @@ for i in $(cat $WD/libpaths.appimage.txt | grep -v "^#" | awk -F# '{print $1}'); done -cp -R $srcfolder/local-lib ${WD}/${APP}.AppDir/usr/lib/local-lib - cat > $WD/${APP}.AppDir/AppRun << 'EOF' #!/usr/bin/env bash # some magic to find out the real location of this script dealing with symlinks diff --git a/package/linux/libpaths.txt b/package/linux/libpaths.txt index ef876a13a..3c64c9758 100644 --- a/package/linux/libpaths.txt +++ b/package/linux/libpaths.txt @@ -1,8 +1,3 @@ -/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_baseu-3.0.so.0 -/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_adv-3.0.so.0 -/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_core-3.0.so.0 -/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_gl-3.0.so.0 -/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_html-3.0.so.0 /lib/x86_64-linux-gnu/liblzma.so.5 /lib/x86_64-linux-gnu/libpng12.so.0 /usr/lib/x86_64-linux-gnu/libjpeg.so.8 diff --git a/package/linux/make_archive.sh b/package/linux/make_archive.sh index ad87b2a06..3eaf8b601 100755 --- a/package/linux/make_archive.sh +++ b/package/linux/make_archive.sh @@ -10,7 +10,6 @@ if [ "$#" -ne 1 ]; then echo "Usage: $(basename $0) arch_name" exit 1; fi -libdirs=$(find ./local-lib -iname *.so -exec dirname {} \; | sort -u | paste -sd ";" -) WD=./$(dirname $0) source $(dirname $0)/../common/util.sh # Determine if this is a tagged (release) commit. @@ -22,7 +21,6 @@ set_build_id set_branch set_app_name set_pr_id -install_par # If we're on a branch, add the branch name to the app name. if [ "$current_branch" == "master" ]; then @@ -35,8 +33,7 @@ else dmgfile=slic3r-${SLIC3R_BUILD_ID}-${1}-${current_branch}.tar.bz2 fi -rm -rf $WD/_tmp -mkdir -p $WD/_tmp +mkdir -p $WD # Set the application folder infomation. appfolder="$WD/${appname}" @@ -46,8 +43,6 @@ resourcefolder=$appfolder echo "Appfolder: $appfolder, archivefolder: $archivefolder" # Our slic3r dir and location of perl -PERL_BIN=$(which perl) -PP_BIN=$(which pp) SLIC3R_DIR="./" if [[ -d "${appfolder}" ]]; then @@ -67,14 +62,12 @@ echo "Copying resources..." cp -rf $SLIC3R_DIR/var $resourcefolder/ echo "Copying Slic3r..." -cp $SLIC3R_DIR/slic3r.pl $archivefolder/slic3r.pl -cp -fRP $SLIC3R_DIR/local-lib $archivefolder/local-lib -cp -fRP $SLIC3R_DIR/lib/* $archivefolder/local-lib/lib/perl5/ +cp $SLIC3R_DIR/slic3r $archivefolder/Slic3r mkdir $archivefolder/bin echo "Installing libraries to $archivefolder/bin ..." if [ -z ${WXDIR+x} ]; then - for bundle in $(find $archivefolder/local-lib/lib/perl5 -name '*.so' | grep "Wx") $(find $archivefolder/local-lib/lib/perl5 -name '*.so' -type f | grep "wxWidgets"); do + for bundle in $archivefolder/Slic3r; do echo "$(LD_LIBRARY_PATH=$libdirs ldd $bundle | grep .so | grep local-lib | awk '{print $3}')" for dylib in $(LD_LIBRARY_PATH=$libdirs ldd $bundle | grep .so | grep local-lib | awk '{print $3}'); do install -v $dylib $archivefolder/bin @@ -91,37 +84,8 @@ echo "Copying startup script..." cp -f $WD/startup_script.sh $archivefolder/$appname chmod +x $archivefolder/$appname -echo "Copying perl from $PERL_BIN" -# Edit package/common/coreperl to add/remove core Perl modules added to this package, one per line. -cp -f $PERL_BIN $archivefolder/perl-local -${PP_BIN} wxextension .0 \ - -M $(grep -v "^#" ${WD}/../common/coreperl | xargs | awk 'BEGIN { OFS=" -M "}; {$1=$1; print $0}') \ - -B -p -e "print 123" -o $WD/_tmp/test.par -unzip -qq -o $WD/_tmp/test.par -d $WD/_tmp/ -cp -rf $WD/_tmp/lib/* $archivefolder/local-lib/lib/perl5/ -cp -rf $WD/_tmp/shlib $archivefolder/ -rm -rf $WD/_tmp for i in $(cat $WD/libpaths.txt | grep -v "^#" | awk -F# '{print $1}'); do install -v $i $archivefolder/bin done -echo "Cleaning local-lib" -rm -rf $archivefolder/local-lib/bin -rm -rf $archivefolder/local-lib/man -rm -f $archivefolder/local-lib/lib/perl5/Algorithm/*.pl -rm -rf $archivefolder/local-lib/lib/perl5/unicore -rm -rf $archivefolder/local-lib/lib/perl5/App -rm -rf $archivefolder/local-lib/lib/perl5/Devel/CheckLib.pm -rm -rf $archivefolder/local-lib/lib/perl5/ExtUtils -rm -rf $archivefolder/local-lib/lib/perl5/Module/Build* -rm -rf $(pwd)$archivefolder/local-lib/lib/perl5/TAP -rm -rf $(pwd)/$archivefolder/local-lib/lib/perl5/Test* -find $(pwd)/$archivefolder/local-lib -type d -path '*/Wx/*' \( -name WebView \ - -or -name DocView -or -name STC -or -name IPC \ - -or -name Calendar -or -name DataView \ - -or -name DateTime -or -name Media -or -name PerlTest \ - -or -name Ribbon \) -exec rm -rf "{}" \; -rm -rf $archivefolder/local-lib/lib/perl5/*/Alien/wxWidgets/*/include -find $archivefolder/local-lib -depth -type d -empty -exec rmdir "{}" \; - tar -C$(pwd)/$(dirname $appfolder) -cjf $(pwd)/$dmgfile "$appname" diff --git a/package/linux/startup_script.sh b/package/linux/startup_script.sh index 30f0e2c69..dbe4d7981 100755 --- a/package/linux/startup_script.sh +++ b/package/linux/startup_script.sh @@ -3,4 +3,4 @@ BIN=$(readlink "$0") DIR=$(dirname "$BIN") export LD_LIBRARY_PATH="$DIR/bin" -exec "$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@ +exec "$DIR/Slic3r" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85abe164b..fe2927e41 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -286,10 +286,14 @@ IF(wxWidgets_FOUND) ${GUI_LIBDIR}/Dialogs/PrintEditor.cpp ${GUI_LIBDIR}/Dialogs/PrinterEditor.cpp ${GUI_LIBDIR}/Dialogs/MaterialEditor.cpp + ${GUI_LIBDIR}/Dialogs/ObjectCutDialog.cpp ${GUI_LIBDIR}/GUI.cpp ${GUI_LIBDIR}/MainFrame.cpp ${GUI_LIBDIR}/Plater.cpp + ${GUI_LIBDIR}/Scene3D.cpp ${GUI_LIBDIR}/Plater/Plate2D.cpp + ${GUI_LIBDIR}/Plater/Plate3D.cpp + ${GUI_LIBDIR}/Plater/Preview3D.cpp ${GUI_LIBDIR}/Plater/PlaterObject.cpp ${GUI_LIBDIR}/ProgressStatusBar.cpp ${GUI_LIBDIR}/Settings.cpp @@ -298,6 +302,7 @@ IF(wxWidgets_FOUND) ${GUI_LIBDIR}/OptionsGroup/UI_Choice.cpp ${GUI_LIBDIR}/OptionsGroup/UI_Point.cpp ${GUI_LIBDIR}/OptionsGroup/UI_Point3.cpp + ${LIBDIR}/slic3r/GUI/3DScene.cpp ) target_compile_features(slic3r_gui PUBLIC cxx_std_14) #only build GUI lib if building with wx diff --git a/src/GUI/ColorScheme/Default.hpp b/src/GUI/ColorScheme/Default.hpp index 69b7e0a98..2adb8cc61 100644 --- a/src/GUI/ColorScheme/Default.hpp +++ b/src/GUI/ColorScheme/Default.hpp @@ -7,7 +7,7 @@ class DefaultColor : public ColorScheme { public: const std::string name() const { return "Default"; } const bool SOLID_BACKGROUNDCOLOR() const { return false; }; - const wxColour SELECTED_COLOR() const { return wxColour(0, 1, 0); }; + const wxColour SELECTED_COLOR() const { return wxColour(0, 255, 0); }; const wxColour HOVER_COLOR() const { return wxColour(255*0.4, 255*0.9, 0); }; //name), wxDefaultPosition, wxSize(500, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), model_object(_model_object) { + +/* + $self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1); + + // cut options + my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z; + $self->{cut_options} = { + axis => Z, + z => $size_z/2, + keep_upper => 0, + keep_lower => 1, + rotate_lower => 0, + preview => 1, + }; + + 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 => 'axis', + type => 'select', + label => 'Axis', + labels => ['X','Y','Z'], + values => [X,Y,Z], + default => $self->{cut_options}{axis}, + )); + $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 => $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); + $self->{btn_cut}->SetDefault; + $cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10); + + $self->{btn_cut_grid} = Wx::Button->new($self, -1, "Cut by grid…", wxDefaultPosition, wxDefaultSize); + $cut_button_sizer->Add($self->{btn_cut_grid}, 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); + $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->set_auto_bed_shape; + $canvas->SetSize([500,500]); + $canvas->SetMinSize($canvas->GetSize); + $canvas->zoom_to_volumes; + } + + $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 (my $lower = $self->{new_model_objects}[0]) { + if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) { + $lower->rotate(PI, X); + } + $lower->center_around_origin; # align to Z = 0 + } + if (my $upper = $self->{new_model_objects}[1]) { + $upper->center_around_origin; # align to Z = 0 + } + + // Note that the window was already closed, so a pending update will not be executed. + already_closed = true; + EndModal(wxID_OK); + Destroy(); + }); + + EVT_BUTTON($self, $self->{btn_cut_grid}, sub { + my $grid_x = Wx::GetTextFromUser("Enter the width of the desired tiles along the X axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_x) || $grid_x <= 0; + + my $grid_y = Wx::GetTextFromUser("Enter the width of the desired tiles along the Y axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_y) || $grid_y <= 0; + + my $process_dialog = Wx::ProgressDialog->new('Cutting…', "Cutting model by grid…", 100, $self, 0); + $process_dialog->Pulse; + + my $meshes = $self->{model_object}->mesh->cut_by_grid(Slic3r::Pointf->new($grid_x, $grid_y)); + $self->{new_model_objects} = []; + + my $bb = $self->{model_object}->bounding_box; + $self->{new_model} = my $model = Slic3r::Model->new; + for my $i (0..$#$meshes) { + push @{$self->{new_model_objects}}, my $o = $model->add_object( + name => sprintf('%s (%d)', $self->{model_object}->name, $i+1), + ); + my $v = $o->add_volume( + mesh => $meshes->[$i], + name => $o->name, + ); + $o->center_around_origin; + my $i = $o->add_instance( + offset => Slic3r::Pointf->new(@{$o->origin_translation->negative}[X,Y]), + ); + $i->offset->translate( + 5 * ceil(($i->offset->x - $bb->center->x) / $grid_x), + 5 * ceil(($i->offset->y - $bb->center->y) / $grid_y), + ); + } + + $process_dialog->Destroy; + + # Note that the window was already closed, so a pending update will not be executed. + $self->{already_closed} = 1; + $self->EndModal(wxID_OK); + $self->Destroy(); + }); + + EVT_CLOSE($self, sub { + # Note that the window was already closed, so a pending update will not be executed. + already_closed = true; + EndModal(wxID_CANCEL); + Destroy(); + }); +*/ + _update(); + +} + +// 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 +void ObjectCutDialog::_mesh_slice_z_pos(){ +/* + my $bb = $self->{model_object}->instance_bounding_box(0); + my $z = $self->{cut_options}{axis} == X ? $bb->x_min + : $self->{cut_options}{axis} == Y ? $bb->y_min + : $bb->z_min; + + $z += $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; + + return $z; +*/ +} + +// Only perform live preview if just a single part of the object shall survive. +void ObjectCutDialog::_life_preview_active() { + /* + return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower}); + */ +} + +// Slice the mesh, keep the top / bottom part. +void ObjectCutDialog::_perform_cut(){ + + + // Early exit. If the cut is valid, don't recalculate it. + if (mesh_cut_valid) return; + +/* + my $z = $self->_mesh_slice_z_pos(); + + my ($new_model) = $self->{model_object}->cut($self->{cut_options}{axis}, $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}[1] = $upper_object; + } + if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) { + $self->{new_model_objects}[0] = $lower_object; + } +*/ + mesh_cut_valid = true; +} + +void ObjectCutDialog::_update() { + + // 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. + if (already_closed) return; +/* + # 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, grep defined, @{$self->{new_model_objects}}; + } else { + push @objects, $self->{model_object}; + } + + # get section contour + my @expolygons = (); + foreach my $volume (@{$self->{model_object}->volumes}) { + next if !$volume->mesh; + next if $volume->modifier; + my $expp = $volume->mesh->slice_at($self->{cut_options}{axis}, $z); + push @expolygons, @$expp; + } + + my $offset = $self->{model_object}->instances->[0]->offset; + foreach my $expolygon (@expolygons) { + $self->{model_object}->instances->[0]->transform_polygon($_) + for @$expolygon; + + if ($self->{cut_options}{axis} != X) { + $expolygon->translate(0, Slic3r::Geometry::scale($offset->y)); #) + } + if ($self->{cut_options}{axis} != Y) { + $expolygon->translate(Slic3r::Geometry::scale($offset->x), 0); + } + } + + $self->{canvas}->reset_objects; + $self->{canvas}->load_object($_, undef, [0]) for @objects; + + my $plane_z = $self->{cut_options}{z}; + $plane_z += 0.02 if !$self->{cut_options}{keep_upper}; + $plane_z -= 0.02 if !$self->{cut_options}{keep_lower}; + $self->{canvas}->SetCuttingPlane( + $self->{cut_options}{axis}, + $plane_z, + [@expolygons], + ); + $self->{canvas}->Render; + } + } + + # update controls + { + my $z = $self->{cut_options}{z}; + my $optgroup = $self->{optgroup}; + { + my $bb = $self->{model_object}->instance_bounding_box(0); + my $max = $self->{cut_options}{axis} == X ? $bb->size->x + : $self->{cut_options}{axis} == Y ? $bb->size->y ### + : $bb->size->z; + $optgroup->get_field('z')->set_range(0, $max); + } + $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} && $self->{cut_options}{axis} == Z); + $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; + } + } +*/ +} + +void ObjectCutDialog::NewModelObjects() { +/* + my ($self) = @_; + return grep defined, @{ $self->{new_model_objects} }; +*/ +} + +}} // namespace Slic3r::GUI diff --git a/src/GUI/Dialogs/ObjectCutDialog.hpp b/src/GUI/Dialogs/ObjectCutDialog.hpp new file mode 100644 index 000000000..f87cc66ef --- /dev/null +++ b/src/GUI/Dialogs/ObjectCutDialog.hpp @@ -0,0 +1,51 @@ +#ifndef OBJECTCUTDIALOG_HPP +#define OBJECTCUTDIALOG_HPP + + +#include + +#include "Scene3D.hpp" +#include "Model.hpp" + +namespace Slic3r { namespace GUI { + +class ObjectCutCanvas : public Scene3D { + // Not sure yet + +protected: + // Draws the cutting plane + void after_render(); +}; + +struct CutOptions { + float z; + enum {X,Y,Z} axis; + bool keep_upper, keep_lower, rotate_lower; + bool preview; +}; + +class ObjectCutDialog : public wxDialog { +public: + ObjectCutDialog(wxWindow* parent, ModelObject* _model); +private: + // 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. + bool mesh_cut_valid = false; + // Note whether the window was already closed, so a pending update is not executed. + bool already_closed = false; + +// ObjectCutCanvas canvas; + ModelObject* model_object; + + //std::shared_ptr model; + + void _mesh_slice_z_pos(); + void _life_preview_active(); + void _perform_cut(); + void _update(); + void NewModelObjects(); + +}; + +}} // namespace Slic3r::GUI +#endif // OBJECTCUTDIALOG_HPP diff --git a/src/GUI/Plater.cpp b/src/GUI/Plater.cpp index 84f93a6a0..a5cba3665 100644 --- a/src/GUI/Plater.cpp +++ b/src/GUI/Plater.cpp @@ -13,6 +13,7 @@ #include "BoundingBox.hpp" #include "Geometry.hpp" #include "Dialogs/AnglePicker.hpp" +#include "Dialogs/ObjectCutDialog.hpp" namespace Slic3r { namespace GUI { @@ -88,7 +89,10 @@ Plater::Plater(wxWindow* parent, const wxString& title) : canvas3D = new Plate3D(preview_notebook, wxDefaultSize, objects, model, config); preview_notebook->AddPage(canvas3D, _("3D")); - preview3D = new Preview3D(preview_notebook, wxDefaultSize, objects, model, config); + canvas3D->on_select_object = std::function(on_select_object); + canvas3D->on_instances_moved = std::function(on_instances_moved); + + preview3D = new Preview3D(preview_notebook, wxDefaultSize, print, objects, model, config); preview_notebook->AddPage(preview3D, _("Preview")); preview2D = new Preview2D(preview_notebook, wxDefaultSize, objects, model, config); @@ -494,13 +498,15 @@ void Plater::arrange() { } void Plater::on_model_change(bool force_autocenter) { + Log::info(LogChannel, L"Called on_modal_change"); // reload the select submenu (if already initialized) { auto* menu = this->GetFrame()->plater_select_menu; if (menu != nullptr) { - for (auto* item : menu->GetMenuItems() ) { menu->Delete(item); } + auto list = menu->GetMenuItems(); + for (auto it = list.begin();it!=list.end(); it++) { menu->Delete(*it); } for (const auto& obj : this->objects) { const auto idx {obj.identifier}; auto name {wxString(obj.name)}; @@ -561,6 +567,7 @@ void Plater::select_object() { void Plater::selection_changed() { // Remove selection in 2D plater this->canvas2D->set_selected(-1, -1); + this->canvas3D->selection_changed(); auto obj = this->selected_object(); bool have_sel {obj != this->objects.end()}; @@ -882,6 +889,13 @@ void Plater::changescale() { void Plater::object_cut_dialog() { //TODO + ObjRef obj {this->selected_object()}; + if (obj == this->objects.end()) return; + + auto* model_object {this->model->objects.at(obj->identifier)}; + auto cut_dialog = new ObjectCutDialog(nullptr, model_object); + cut_dialog->ShowModal(); + cut_dialog->Destroy(); } void Plater::object_layers_dialog() { diff --git a/src/GUI/Plater/3DPreview.pm b/src/GUI/Plater/3DPreview.pm new file mode 100644 index 000000000..c4f9e6c5e --- /dev/null +++ b/src/GUI/Plater/3DPreview.pm @@ -0,0 +1,171 @@ +package Slic3r::GUI::Plater::3DPreview; +use strict; +use warnings; +use utf8; + +use Slic3r::Print::State ':steps'; +use Wx qw(:misc :sizer :slider :statictext); +use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); +use base qw(Wx::Panel Class::Accessor); + +__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider)); + +sub new { + my $class = shift; + my ($parent, $print) = @_; + + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); + + # init GUI elements + my $canvas = Slic3r::GUI::3DScene->new($self); + $self->canvas($canvas); + my $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, + ); + $self->slider($slider); + + 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) = @_; + + my $key = $event->GetKeyCode; + if ($key == 85 || $key == 315) { + $slider->SetValue($slider->GetValue + 1); + $self->set_z($self->{layers_z}[$slider->GetValue]); + } elsif ($key == 68 || $key == 317) { + $slider->SetValue($slider->GetValue - 1); + $self->set_z($self->{layers_z}[$slider->GetValue]); + } else { + $event->Skip; + } + }); + + $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); + $sizer->SetSizeHints($self); + + # init canvas + $self->print($print); + $self->reload_print; + + return $self; +} + +sub reload_print { + my ($self, $obj_idx) = @_; + + $self->canvas->reset_objects; + $self->_loaded(0); + $self->load_print($obj_idx); +} + +sub load_print { + my ($self, $obj_idx) = @_; + + return if $self->_loaded; + + # 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; + } + + my $z_idx; + { + my %z = (); # z => 1 + if(defined $obj_idx) { # Load only given object + foreach my $layer (@{$self->{print}->get_object($obj_idx)->layers}) { + $z{$layer->print_z} = 1; + } + }else{ # Load all objects on the plater + support material + 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 (($z_idx = $self->slider->GetValue) <= $#{$self->{layers_z}} && $self->slider->GetValue != 0) { + # use $z_idx + } else { + $self->slider->SetValue(scalar(@{$self->{layers_z}})-1); + $z_idx = @{$self->{layers_z}} ? -1 : undef; + } + $self->slider->Show; + $self->Layout; + } + + if ($self->IsShown) { + # set colors + $self->canvas->color_toolpaths_by($Slic3r::GUI::Settings->{_}{color_toolpaths_by}); + if ($self->canvas->color_toolpaths_by eq 'extruder') { + my @filament_colors = map { s/^#//; [ map $_/255, (unpack 'C*', pack 'H*', $_), 255 ] } + @{$self->print->config->filament_colour}; + $self->canvas->colors->[$_] = $filament_colors[$_] for 0..$#filament_colors; + } else { + $self->canvas->colors([ $self->canvas->default_colors ]); + } + + if(defined $obj_idx) { # Load only one object + $self->canvas->load_print_object_toolpaths($self->{print}->get_object($obj_idx)); + }else{ # load all objects + # load skirt and brim + $self->canvas->load_print_toolpaths($self->print); + + foreach my $object (@{$self->print->objects}) { + $self->canvas->load_print_object_toolpaths($object); + + #my @volume_ids = $self->canvas->load_object($object->model_object); + #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; + } + } + $self->_loaded(1); + } + + $self->set_z($self->{layers_z}[$z_idx]); +} + +sub set_z { + my ($self, $z) = @_; + + return if !$self->enabled; + $self->{z_label}->SetLabel(sprintf '%.2f', $z); + $self->canvas->set_toolpaths_range(0, $z); + $self->canvas->Refresh if $self->IsShown; +} + +sub set_bed_shape { + my ($self, $bed_shape) = @_; + $self->canvas->set_bed_shape($bed_shape); +} + +1; diff --git a/src/GUI/Plater/Plate3D.cpp b/src/GUI/Plater/Plate3D.cpp new file mode 100644 index 000000000..6750a8897 --- /dev/null +++ b/src/GUI/Plater/Plate3D.cpp @@ -0,0 +1,176 @@ +#include "Plater/Plate3D.hpp" +#include "misc_ui.hpp" + +namespace Slic3r { namespace GUI { + +Plate3D::Plate3D(wxWindow* parent, const wxSize& size, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config) : + Scene3D(parent, size), objects(_objects), model(_model), config(_config) +{ + // Bind the extra mouse events + this->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { this->mouse_down(e); }); + this->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent &e) { this->mouse_down(e); }); +} + +void Plate3D::mouse_down(wxMouseEvent &e){ + if(hover){ + on_select_object(hover_object); + moving = true; + moving_volume = hover_volume; + move_start = Point(e.GetX(),e.GetY()); + }else{ + on_select_object(-1); + } + hover = false; +} + +void Plate3D::mouse_up(wxMouseEvent &e){ + if(moving){ + //translate object + moving = false; + uint i = 0; + for(const PlaterObject &object: objects){ + const auto &modelobj = model->objects.at(object.identifier); + for(ModelInstance *instance: modelobj->instances){ + uint size = modelobj->volumes.size(); + if(i <= moving_volume && moving_volume < i+size){ + instance->offset.translate(volumes.at(i).origin); + modelobj->update_bounding_box(); + on_instances_moved(); + Refresh(); + return; + }else{ + i+=size; + } + } + } + } + Scene3D::mouse_up(e); +} + +void Plate3D::mouse_move(wxMouseEvent &e){ + if(!e.Dragging()){ + pos = Point(e.GetX(),e.GetY()); + mouse = true; + Refresh(); + } else if(moving){ + const auto p = Point(e.GetX(),e.GetY()); + const auto current = mouse_ray(p).intersect_plane(0); + const auto old = mouse_ray(move_start).intersect_plane(0); + move_start = p; + uint i = 0; + for(const PlaterObject &object: objects){ + const auto &modelobj = model->objects.at(object.identifier); + for(ModelInstance *instance: modelobj->instances){ + uint size = modelobj->volumes.size(); + if(i <= moving_volume && moving_volume < i+size){ + for(ModelVolume* volume: modelobj->volumes){ + volumes.at(i).origin.translate(old.vector_to(current)); + i++; + } + Refresh(); + return; + }else{ + i+=size; + } + } + } + } else { + Scene3D::mouse_move(e); + } +} + +void Plate3D::update(){ + volumes.clear(); + for(const PlaterObject &object: objects){ + const auto &modelobj = model->objects.at(object.identifier); + for(ModelInstance *instance: modelobj->instances){ + for(ModelVolume* volume: modelobj->volumes){ + TriangleMesh copy = volume->mesh; + instance->transform_mesh(©); + GLVertexArray model; + model.load_mesh(copy); + volumes.push_back(Volume{ wxColor(200,200,200), Pointf3(0,0,0), model, copy.bounding_box()}); + } + } + } + color_volumes(); + Refresh(); +} + +void Plate3D::color_volumes(){ + uint i = 0; + for(const PlaterObject &object: objects){ + const auto &modelobj = model->objects.at(object.identifier); + bool hover_object = hover && i <= hover_volume && hover_volume < i+modelobj->instances.size()*modelobj->volumes.size(); + for(ModelInstance *instance: modelobj->instances){ + for(ModelVolume* volume: modelobj->volumes){ + auto& rendervolume = volumes.at(i); + if(object.selected){ + rendervolume.color = ui_settings->color->SELECTED_COLOR(); + }else if(hover_object){ + rendervolume.color = ui_settings->color->HOVER_COLOR(); + } else { + rendervolume.color = ui_settings->color->COLOR_PARTS(); + } + i++; + } + } + } +} + +void Plate3D::before_render(){ + if (!mouse){ + color_volumes(); + return; + } + + // Color each volume a different color, render and test which color is beneath the mouse. + + //glDisable(GL_MULTISAMPLE) if ($self->{can_multisample}); + glDisable(GL_LIGHTING); + uint i = 1; + for(Volume &volume : volumes){ + volume.color = wxColor((i>>16)&0xFF,(i>>8)&0xFF,i&0xFF); + i++; + } + draw_volumes(); + glFlush(); + glFinish(); + GLubyte color[4] = {0,0,0,0}; + glReadPixels(pos.x, GetSize().GetHeight()- pos.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color); + + // Handle the hovered volume + uint index = (color[0]<<16) + (color[1]<<8) + color[2]; + hover = false; + ///*$self->_hover_volume_idx(undef); + //$_->hover(0) for @{$self->volumes}; + if (index != 0 && index <= volumes.size()) { + hover = true; + hover_volume = index - 1; + uint k = 0; + for(const PlaterObject &object: objects){ + const auto &modelobj = model->objects.at(object.identifier); + if(k <= hover_volume && hover_volume < k+modelobj->instances.size()*modelobj->volumes.size()){ + hover_object = k; + break; + } + k++; + } + /* + $self->volumes->[$volume_idx]->hover(1); + my $group_id = $self->volumes->[$volume_idx]->select_group_id; + if ($group_id != -1) { + $_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes}; + }*/ + + //$self->on_hover->($volume_idx) if $self->on_hover; + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glFlush(); + glFinish(); + glEnable(GL_LIGHTING); + color_volumes(); + mouse = false; +} + +} } // Namespace Slic3r::GUI diff --git a/src/GUI/Plater/Plate3D.hpp b/src/GUI/Plater/Plate3D.hpp index 8e6814d25..605ce9d65 100644 --- a/src/GUI/Plater/Plate3D.hpp +++ b/src/GUI/Plater/Plate3D.hpp @@ -4,18 +4,44 @@ #ifndef WX_PRECOMP #include #endif +#include "Plater/PlaterObject.hpp" +#include "Scene3D.hpp" +#include "Settings.hpp" #include "Model.hpp" #include "Config.hpp" namespace Slic3r { namespace GUI { -class Plate3D : public wxPanel { +class Plate3D : public Scene3D { public: - void update() {}; - Plate3D(wxWindow* parent, const wxSize& size, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config) : - wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), objects(_objects), model(_model), config(_config) - {} + Plate3D(wxWindow* parent, const wxSize& size, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config); + + /// Called to regenerate rendered volumes from the model + void update(); + + /// Registered function to fire when objects are selected. + std::function on_select_object {}; + + /// Registered function to fire when an instance is moved. + std::function on_instances_moved {}; + + void selection_changed(){Refresh();} + protected: + // Render each volume as a different color and check what color is beneath + // the mouse to detemine the hovered volume + void before_render(); + + // Mouse events are needed to handle selecting and moving objects + void mouse_up(wxMouseEvent &e); + void mouse_move(wxMouseEvent &e); + void mouse_down(wxMouseEvent &e); + private: + void color_volumes(); + Point pos, move_start; + bool hover = false, mouse = false, moving = false; + uint hover_volume, hover_object, moving_volume; + std::vector& objects; //< reference to parent vector std::shared_ptr model; std::shared_ptr config; diff --git a/src/GUI/Plater/Preview3D.cpp b/src/GUI/Plater/Preview3D.cpp new file mode 100644 index 000000000..139f521fb --- /dev/null +++ b/src/GUI/Plater/Preview3D.cpp @@ -0,0 +1,145 @@ +#include "Preview3D.hpp" +#include + +namespace Slic3r { namespace GUI { + +Preview3D::Preview3D(wxWindow* parent, const wxSize& size, std::shared_ptr _print, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config) : + wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), print(_print), objects(_objects), model(_model), config(_config), canvas(this,size) +{ + + // init GUI elements + slider = new wxSlider( + this, -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 + ); + + this->z_label = new wxStaticText(this, -1, "", wxDefaultPosition, + wxSize(40,-1), wxALIGN_CENTRE_HORIZONTAL); + //z_label->SetFont(Slic3r::GUI::small_font); + + auto* vsizer = new wxBoxSizer(wxVERTICAL); + vsizer->Add(slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3); + vsizer->Add(z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3); + + auto* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(&canvas, 1, wxALL | wxEXPAND, 0); + sizer->Add(vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); + + this->Bind(wxEVT_SLIDER, [this](wxCommandEvent &e){ + //$self->set_z($self->{layers_z}[$slider->GetValue]) + // if $self->enabled; + }); + this->Bind(wxEVT_CHAR, [this](wxKeyEvent &e) { + /*my ($s, $event) = @_; + + my $key = $event->GetKeyCode; + if ($key == 85 || $key == 315) { + $slider->SetValue($slider->GetValue + 1); + $self->set_z($self->{layers_z}[$slider->GetValue]); + } elsif ($key == 68 || $key == 317) { + $slider->SetValue($slider->GetValue - 1); + $self->set_z($self->{layers_z}[$slider->GetValue]); + } else { + $event->Skip; + }*/ + }); + + SetSizer(sizer); + SetMinSize(GetSize()); + sizer->SetSizeHints(this); + + // init canvas + reload_print(); + +} +void Preview3D::reload_print(){ + + canvas.resetObjects(); + loaded = false; + load_print(); +} + +void Preview3D::load_print() { + if(loaded) return; + + // 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(!print->step_done(posSlice)) { + _enabled = false; + slider->Hide(); + canvas.Refresh(); // clears canvas + return; + } + + size_t z_idx = 0; + { + layers_z.clear(); + // Load all objects on the plater + support material + for(auto* object : print->objects) { + for(auto layer : object->layers){ + layers_z.push_back(layer->print_z); + } + for(auto layer : object->support_layers) { + layers_z.push_back(layer->print_z); + } + } + + _enabled = true; + std::sort(layers_z.begin(),layers_z.end()); + slider->SetRange(0, layers_z.size()-1); + z_idx = slider->GetValue(); + // If invalide z_idx, move the slider to the top + if (z_idx >= layers_z.size() || slider->GetValue() == 0) { + slider->SetValue(layers_z.size()-1); + //$z_idx = @{$self->{layer_z}} ? -1 : undef; + z_idx = slider->GetValue(); // not sure why the perl version makes z_idx invalid + } + slider->Show(); + Layout(); + } + if (IsShown()) { + // set colors + /*canvas.color_toolpaths_by($Slic3r::GUI::Settings->{_}{color_toolpaths_by}); + if ($self->canvas->color_toolpaths_by eq 'extruder') { + my @filament_colors = map { s/^#//; [ map $_/255, (unpack 'C*', pack 'H*', $_), 255 ] } + @{$self->print->config->filament_colour}; + $self->canvas->colors->[$_] = $filament_colors[$_] for 0..$#filament_colors; + } else { + $self->canvas->colors([ $self->canvas->default_colors ]); + }*/ + + // load skirt and brim + //$self->canvas->load_print_toolpaths($self->print); + + /*foreach my $object (@{$self->print->objects}) { + canvas.load_print_object_toolpaths($object); + }*/ + loaded = true; + } + + set_z(layers_z.at(z_idx)); +} +void Preview3D::set_z(float z) { + if(!_enabled) return; + z_label->SetLabel(std::to_string(z)); + //canvas.set_toolpaths_range(0, $z); + if(IsShown())canvas.Refresh(); +} + +/* +void set_bed_shape() { + my ($self, $bed_shape) = @_; + $self->canvas->set_bed_shape($bed_shape); +} +*/ + +} } // Namespace Slic3r::GUI + diff --git a/src/GUI/Plater/Preview3D.hpp b/src/GUI/Plater/Preview3D.hpp index d2ec12b48..15af3e95b 100644 --- a/src/GUI/Plater/Preview3D.hpp +++ b/src/GUI/Plater/Preview3D.hpp @@ -5,20 +5,36 @@ #include #endif +#include "PlaterObject.hpp" +#include "Scene3D.hpp" #include "Model.hpp" #include "Config.hpp" +#include "Print.hpp" namespace Slic3r { namespace GUI { +class PreviewScene3D : public Scene3D { +public: + PreviewScene3D(wxWindow* parent, const wxSize& size) : Scene3D(parent,size){} + // TODO: load_print_toolpaths(Print); + // TODO: load_print_object_toolpaths(PrintObject); + void resetObjects(){volumes.clear();} +}; + class Preview3D : public wxPanel { public: - void reload_print() {}; - Preview3D(wxWindow* parent, const wxSize& size, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config) : - wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), objects(_objects), model(_model), config(_config) - {} - + void reload_print(); + Preview3D(wxWindow* parent, const wxSize& size, std::shared_ptr _print, std::vector& _objects, std::shared_ptr _model, std::shared_ptr _config); void enabled(bool enable = true) {} private: + void load_print(); + void set_z(float z); + bool loaded = false, _enabled = false; + std::vector layers_z; + std::shared_ptr print; + PreviewScene3D canvas; + wxSlider* slider; + wxStaticText* z_label; std::vector& objects; //< reference to parent vector std::shared_ptr model; std::shared_ptr config; diff --git a/src/GUI/Scene3D.cpp b/src/GUI/Scene3D.cpp new file mode 100644 index 000000000..cb4b059bd --- /dev/null +++ b/src/GUI/Scene3D.cpp @@ -0,0 +1,494 @@ +#include "Scene3D.hpp" +#include "Line.hpp" +#include "ClipperUtils.hpp" +#include "misc_ui.hpp" + +#include + +namespace Slic3r { namespace GUI { + +Scene3D::Scene3D(wxWindow* parent, const wxSize& size) : + wxGLCanvas(parent, wxID_ANY, wxDefaultPosition, size) +{ + + this->glContext = new wxGLContext(this); + this->Bind(wxEVT_PAINT, [this](wxPaintEvent &e) { this->repaint(e); }); + this->Bind(wxEVT_SIZE, [this](wxSizeEvent &e ){ + dirty = true; + Refresh(); + }); + + + // Bind the varying mouse events + this->Bind(wxEVT_MOTION, [this](wxMouseEvent &e) { this->mouse_move(e); }); + this->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &e) { this->mouse_up(e); }); + this->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent &e) { this->mouse_up(e); }); + this->Bind(wxEVT_MIDDLE_DCLICK, [this](wxMouseEvent &e) { this->mouse_dclick(e); }); + this->Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent &e) { this->mouse_wheel(e); }); + + Points p; + const coord_t w = scale_(200), z = 0; + p.push_back(Point(z,z)); + p.push_back(Point(z,w)); + p.push_back(Point(w,w)); + p.push_back(Point(w,z)); + set_bed_shape(p); +} + +float clamp(float low, float x, float high){ + if(x < low) return low; + if(x > high) return high; + return x; +} + +Linef3 Scene3D::mouse_ray(Point win){ + GLdouble proj[16], mview[16]; + glGetDoublev(GL_MODELVIEW_MATRIX, mview); + glGetDoublev(GL_PROJECTION_MATRIX, proj); + GLint view[4]; + glGetIntegerv(GL_VIEWPORT, view); + win.y = view[3]-win.y; + GLdouble x = 0.0, y = 0.0, z = 0.0; + gluUnProject(win.x,win.y,0,mview,proj,view,&x,&y,&z); + Pointf3 first = Pointf3(x,y,z); + GLint a = gluUnProject(win.x,win.y,1,mview,proj,view,&x,&y,&z); + return Linef3(first,Pointf3(x,y,z)); +} + +void Scene3D::mouse_move(wxMouseEvent &e){ + if(e.Dragging()){ + //const auto s = GetSize(); + const auto pos = Point(e.GetX(),e.GetY()); + if(dragging){ + if (e.ShiftDown()) { // TODO: confirm alt -> shift is ok + // Move the camera center on the Z axis based on mouse Y axis movement + _camera_target.translate(0, 0, (pos.y - drag_start.y)); + } else if (e.LeftIsDown()) { + // if dragging over blank area with left button, rotate + //if (TURNTABLE_MODE) { + const float TRACKBALLSIZE = 0.8f, GIMBAL_LOCK_THETA_MAX = 170.0f; + + phi += (pos.x - drag_start.x) * TRACKBALLSIZE; + theta -= (pos.y - drag_start.y) * TRACKBALLSIZE; + theta = clamp(0, theta, GIMBAL_LOCK_THETA_MAX); + /*} else { + my $size = $self->GetClientSize; + my @quat = trackball( + $orig->x / ($size->width / 2) - 1, + 1 - $orig->y / ($size->height / 2), #/ + $pos->x / ($size->width / 2) - 1, + 1 - $pos->y / ($size->height / 2), #/ + ); + $self->_quat(mulquats($self->_quat, \@quat)); + }*/ + } else if (e.MiddleIsDown() || e.RightIsDown()) { + // if dragging over blank area with right button, translate + // get point in model space at Z = 0 + const auto current = mouse_ray(pos).intersect_plane(0); + const auto old = mouse_ray(drag_start).intersect_plane(0); + _camera_target.translate(current.vector_to(old)); + } + //$self->on_viewport_changed->() if $self->on_viewport_changed; + Refresh(); + } + dragging = true; + drag_start = pos; + }else{ + e.Skip(); + } +} + +void Scene3D::mouse_up(wxMouseEvent &e){ + dragging = false; + Refresh(); +} + +void Scene3D::mouse_wheel(wxMouseEvent &e){ + // Calculate the zoom delta and apply it to the current zoom factor + auto _zoom = ((float)e.GetWheelRotation()) / e.GetWheelDelta(); + /*if ($Slic3r::GUI::Settings->{_}{invert_zoom}) { + _zoom *= -1; + }*/ + _zoom = clamp(-4, _zoom,4); + _zoom /= 10; + zoom /= 1-_zoom; + /* + # In order to zoom around the mouse point we need to translate + # the camera target + my $size = Slic3r::Pointf->new($self->GetSizeWH); + my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- + $self->_camera_target->translate( + # ($pos - $size/2) represents the vector from the viewport center + # to the mouse point. By multiplying it by $zoom we get the new, + # transformed, length of such vector. + # Since we want that point to stay fixed, we move our camera target + # in the opposite direction by the delta of the length of such vector + # ($zoom - 1). We then scale everything by 1/$self->_zoom since + # $self->_camera_target is expressed in terms of model units. + -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, + -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, + 0, + ) if 0; + */ + + dirty = true; + Refresh(); +} + +void Scene3D::mouse_dclick(wxMouseEvent &e){ + /* + if (@{$self->volumes}) { + $self->zoom_to_volumes; + } else { + $self->zoom_to_bed; + }*/ + + dirty = true; + Refresh(); +} + +void Scene3D::resize(){ + if(!dirty)return; + dirty = false; + const auto s = GetSize(); + glViewport(0,0,s.GetWidth(),s.GetHeight()); + const auto x = s.GetWidth()/zoom, + y = s.GetHeight()/zoom, + depth = 1000.0f; // my $depth = 10 * max(@{ $self->max_bounding_box->size }); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho( + -x/2, x/2, -y/2, y/2, + -depth, 2*depth + ); + glMatrixMode(GL_MODELVIEW); +} + +void Scene3D::set_bed_shape(Points _bed_shape){ + + bed_shape = _bed_shape; + const float GROUND_Z = -0.02f; + + // triangulate bed + const auto expoly = ExPolygon(Polygon(bed_shape)); + const auto box = expoly.bounding_box(); + bed_bound = box; + { + std::vector triangles; + expoly.triangulate(&triangles); + bed_verts.clear(); + for(const auto &triangle : triangles){ + for(const auto &point : triangle.points){ + bed_verts.push_back(unscale(point.x)); + bed_verts.push_back(unscale(point.y)); + bed_verts.push_back(GROUND_Z); + } + } + } + { + std::vector lines; + Points tmp; + for (coord_t x = box.min.x; x <= box.max.x; x += scale_(10)) { + lines.push_back(Polyline()); + lines.back().append(Point(x,box.min.y)); + lines.back().append(Point(x,box.max.y)); + } + for (coord_t y = box.min.y; y <= box.max.y; y += scale_(10)) { + lines.push_back(Polyline()); + lines.back().append(Point(box.min.x,y)); + lines.back().append(Point(box.max.x,y)); + } + // clip with a slightly grown expolygon because our lines lay on the contours and + // may get erroneously clipped + // my @lines = map Slic3r::Line->new(@$_[0,-1]), + grid_verts.clear(); + const Polylines clipped = intersection_pl(lines,offset_ex(expoly,SCALED_EPSILON).at(0)); + for(const Polyline &line : clipped){ + for(const Point &point : line.points){ + grid_verts.push_back(unscale(point.x)); + grid_verts.push_back(unscale(point.y)); + grid_verts.push_back(GROUND_Z); + } + } + // append bed contours + for(const Line &line : expoly.lines()){ + grid_verts.push_back(unscale(line.a.x)); + grid_verts.push_back(unscale(line.a.y)); + grid_verts.push_back(GROUND_Z); + grid_verts.push_back(unscale(line.b.x)); + grid_verts.push_back(unscale(line.b.y)); + grid_verts.push_back(GROUND_Z); + } + } + + //$self->origin(Slic3r::Pointf->new(0,0)); +} + +void Scene3D::init_gl(){ + if(this->init)return; + this->init = true; + + glClearColor(0, 0, 0, 1); + glColor3f(1, 0, 0); + glEnable(GL_DEPTH_TEST); + glClearDepth(1.0); + glDepthFunc(GL_LEQUAL); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Set antialiasing/multisampling + glDisable(GL_LINE_SMOOTH); + glDisable(GL_POLYGON_SMOOTH); + //glEnable(GL_MULTISAMPLE) if ($self->{can_multisample}); + + // ambient lighting + GLfloat ambient[] = {0.1f, 0.1f, 0.1f, 1.0f}; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + // light from camera + GLfloat pos[] = {1.0f, 0.0f, 1.0f, 0.0f}, spec[] = {0.8f, 0.8f, 0.8f, 1.0f}, diff[] = {0.4f, 0.4f, 0.4f, 1.0f}; + glLightfv(GL_LIGHT1, GL_POSITION, pos); + glLightfv(GL_LIGHT1, GL_SPECULAR, spec); + glLightfv(GL_LIGHT1, GL_DIFFUSE, diff); + + // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. Default: GL_SMOOTH + glShadeModel(GL_SMOOTH); + + GLfloat fbdiff[] = {0.3f, 0.3f, 0.3f,1}, fbspec[] = {1.0f, 1.0f, 1.0f, 1.0f}, fbemis[] = {0.1f,0.1f,0.1f,0.9f}; + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fbdiff); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fbspec); + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, fbemis); + + // A handy trick -- have surface material mirror the color. + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); + //glEnable(GL_MULTISAMPLE) if ($self->{can_multisample}); +} + +void Scene3D::draw_background(){ + glDisable(GL_LIGHTING); + glPushMatrix(); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glBegin(GL_QUADS); + auto bottom = ui_settings->color->BOTTOM_COLOR(), top = ui_settings->color->TOP_COLOR(); + if(ui_settings->color->SOLID_BACKGROUNDCOLOR()){ + bottom = top = ui_settings->color->BACKGROUND_COLOR(); + } + glColor3ub(bottom.Red(), bottom.Green(), bottom.Blue()); + glVertex2f(-1.0,-1.0); + glVertex2f(1,-1.0); + glColor3ub(top.Red(), top.Green(), top.Blue()); + glVertex2f(1, 1); + glVertex2f(-1.0, 1); + glEnd(); + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +void Scene3D::draw_ground(){ + glDisable(GL_DEPTH_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + /*my $triangle_vertex; + if (HAS_VBO) { + ($triangle_vertex) = + glGenBuffersARB_p(1); + $self->bed_triangles->bind($triangle_vertex); + glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_triangles, GL_STATIC_DRAW_ARB); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + } else {*/ + // fall back on old behavior + glVertexPointer(3, GL_FLOAT, 0, bed_verts.data()); + const auto ground = ui_settings->color->GROUND_COLOR(), grid = ui_settings->color->GRID_COLOR(); + + glColor4ub(ground.Red(), ground.Green(), ground.Blue(),ground.Alpha()); + glNormal3d(0,0,1); + glDrawArrays(GL_TRIANGLES, 0, bed_verts.size() / 3); + + // we need depth test for grid, otherwise it would disappear when looking + // the object from below + glEnable(GL_DEPTH_TEST); + + // draw grid + glLineWidth(2); + /*my $grid_vertex; + if (HAS_VBO) { + ($grid_vertex) = + glGenBuffersARB_p(1); + $self->bed_grid_lines->bind($grid_vertex); + glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_grid_lines, GL_STATIC_DRAW_ARB); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + } else {*/ + // fall back on old behavior + glVertexPointer(3, GL_FLOAT, 0, grid_verts.data()); + + glColor4ub(grid.Red(), grid.Green(), grid.Blue(),grid.Alpha()); + glNormal3d(0,0,1); + glDrawArrays(GL_LINES, 0, grid_verts.size() / 3); + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_BLEND); + /*if (HAS_VBO) { + # Turn off buffer objects to let the rest of the draw code work. + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + glDeleteBuffersARB_p($grid_vertex); + glDeleteBuffersARB_p($triangle_vertex); + }*/ +} + +void Scene3D::draw_axes (Pointf3 center, float length, int width, bool always_visible){ + /* + my $volumes_bb = $self->volumes_bounding_box; + + { + # draw axes + # disable depth testing so that axes are not covered by ground + glDisable(GL_DEPTH_TEST); + my $origin = $self->origin; + my $axis_len = max( + max(@{ $self->bed_bounding_box->size }), + 1.2 * max(@{ $volumes_bb->size }), + ); + glLineWidth(2); + glBegin(GL_LINES); + # draw line for x axis + glColor3f(1, 0, 0); + glVertex3f(@$origin, $ground_z); + glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,, + # draw line for y axis + glColor3f(0, 1, 0); + glVertex3f(@$origin, $ground_z); + glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++ + glEnd(); + # draw line for Z axis + # (re-enable depth test so that axis is correctly shown when objects are behind it) + glEnable(GL_DEPTH_TEST); + glBegin(GL_LINES); + glColor3f(0, 0, 1); + glVertex3f(@$origin, $ground_z); + glVertex3f(@$origin, $ground_z+$axis_len); + glEnd(); + } + */ + if (always_visible) { + glDisable(GL_DEPTH_TEST); + } else { + glEnable(GL_DEPTH_TEST); + } + glLineWidth(width); + glBegin(GL_LINES); + // draw line for x axis + glColor3f(1, 0, 0); + glVertex3f(center.x, center.y, center.z); + glVertex3f(center.x + length, center.y, center.z); + // draw line for y axis + glColor3f(0, 1, 0); + glVertex3f(center.x, center.y, center.z); + glVertex3f(center.x, center.y + length, center.z); + // draw line for Z axis + glColor3f(0, 0, 1); + glVertex3f(center.x, center.y, center.z); + glVertex3f(center.x, center.y, center.z + length); + glEnd(); + glEnable(GL_DEPTH_TEST); +} +void Scene3D::draw_volumes(){ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + for(const Volume &volume : volumes){ + glPushMatrix(); + glTranslatef(volume.origin.x, volume.origin.y, volume.origin.z); + glCullFace(GL_BACK); + glVertexPointer(3, GL_FLOAT, 0, volume.model.verts.data()); + glNormalPointer(GL_FLOAT, 0, volume.model.norms.data()); + glColor4ub(volume.color.Red(), volume.color.Green(), volume.color.Blue(), volume.color.Alpha()); + glDrawArrays(GL_TRIANGLES, 0, volume.model.verts.size()/3); + glPopMatrix(); + } + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_BLEND); +} + +void Scene3D::repaint(wxPaintEvent& e) { + if(!this->IsShownOnScreen())return; + // There should be a context->IsOk check once wx is updated + if(!this->SetCurrent(*(this->glContext)))return; + init_gl(); + resize(); + + glClearColor(1, 1, 1, 1); + glClearDepth(1); + glDepthFunc(GL_LESS); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glRotatef(-theta, 1, 0, 0); // pitch + glRotatef(phi, 0, 0, 1); // yaw + /*} else { + my @rotmat = quat_to_rotmatrix($self->quat); + glMultMatrixd_p(@rotmat[0..15]); + }*/ + glTranslatef(-_camera_target.x, -_camera_target.y, -_camera_target.z); + + // light from above + GLfloat pos[] = {-0.5f, -0.5f, 1.0f, 0.0f}, spec[] = {0.2f, 0.2f, 0.2f, 1.0f}, diff[] = {0.5f, 0.5f, 0.5f, 1.0f}; + glLightfv(GL_LIGHT0, GL_POSITION, pos); + glLightfv(GL_LIGHT0, GL_SPECULAR, spec); + glLightfv(GL_LIGHT0, GL_DIFFUSE, diff); + + before_render(); + + draw_background(); + draw_ground(); + /*my $origin = $self->origin; + my $axis_len = max( + max(@{ $self->bed_bounding_box->size }), + 1.2 * max(@{ $volumes_bb->size }), + );*/ + draw_axes(Pointf3(0.0f,0.0f,0.0f), + unscale(bed_bound.radius()),2,true/*origin,calulcated length,2, true*/); + + // draw objects + glEnable(GL_LIGHTING); + draw_volumes(); + + after_render(); + + if (dragging/*defined $self->_drag_start_pos || defined $self->_drag_start_xy*/) { + draw_axes(_camera_target, 10.0f, 1, true/*camera,10,1,true*/); + draw_axes(_camera_target, 10.0f, 4, false/*camera,10,4,false*/); + } + + glFlush(); + SwapBuffers(); + // Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes. + glFinish(); + +} + +} } // Namespace Slic3r::GUI diff --git a/src/GUI/Scene3D.hpp b/src/GUI/Scene3D.hpp new file mode 100644 index 000000000..21dd47108 --- /dev/null +++ b/src/GUI/Scene3D.hpp @@ -0,0 +1,73 @@ +#ifndef SCENE3D_HPP +#define SCENE3D_HPP +#include +#ifndef WX_PRECOMP + #include +#endif + #include +#include "Settings.hpp" +#include "Point.hpp" +#include "3DScene.hpp" +#include "BoundingBox.hpp" +#include "Model.hpp" + +namespace Slic3r { namespace GUI { + +struct Volume { + wxColor color; + Pointf3 origin; + GLVertexArray model; + BoundingBoxf3 bb; +}; + +class Scene3D : public wxGLCanvas { +public: + Scene3D(wxWindow* parent, const wxSize& size); +private: + wxGLContext* glContext; + + // Camera settings + float zoom = 5.0f, phi = 0.0f, theta = 0.0f; + Pointf3 _camera_target = Pointf3(0.0f,0.0f,0.0f); + + // Optional point used for dragging calculations + bool dragging = false; + Point drag_start = Point(0,0); + + // Bed Stuff + std::vector bed_verts, grid_verts; + Points bed_shape; + BoundingBox bed_bound; + + void repaint(wxPaintEvent &e); // Redraws every frame + + bool dirty = true; // Resize needs to be called before render + void resize(); // Handle glViewport and projection matrices + + bool init = false; // Has opengl been initted + void init_gl(); // Handles lights and materials + + // Useded in repaint + void draw_background(); + void draw_ground(); + void draw_axes(Pointf3 center, float length, int width, bool alwaysvisible); + +protected: + Linef3 mouse_ray(Point win); // Utility for backtracking from window coordinates + void draw_volumes(); // Draws volumes (for use in before_render) + void set_bed_shape(Points _bed_shape); + + std::vector volumes; + Volume load_object(ModelVolume &mv, ModelInstance &mi); + + // Virtual methods to override + virtual void mouse_up(wxMouseEvent &e); + virtual void mouse_move(wxMouseEvent &e); + virtual void mouse_dclick(wxMouseEvent &e); + virtual void mouse_wheel(wxMouseEvent &e); + virtual void before_render(){}; + virtual void after_render(){}; + }; + +} } // Namespace Slic3r::GUI +#endif diff --git a/t/config.t b/t/config.t index 829ef5f39..d36912301 100644 --- a/t/config.t +++ b/t/config.t @@ -1,4 +1,4 @@ -use Test::More tests => 1; +use Test::More tests => 4; use strict; use warnings; @@ -15,6 +15,22 @@ use Slic3r::Test; my $config = Slic3r::Config->new_from_defaults; $config->set('perimeter_extrusion_width', '250%'); ok $config->validate, 'percent extrusion width is validated'; + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); + { + my $invalid = $print->apply_config($config); + ok !($invalid), 're-applying same config does not invalidate'; + } + $config->set('perimeters', 20); + { + my $invalid = $print->apply_config($config); + ok $invalid, 're-applying with changed perimeters does invalidate previous config'; + } + $config->set('fill_density', '75%'); + { + my $invalid = $print->apply_config($config); + ok $invalid, 're-applying with changed fill_density does invalidate previous config'; + } } __END__ diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index a114ffb39..612f06030 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -20,6 +20,8 @@ #include +#include + namespace Slic3r { class InvalidObjectException : public std::exception {};