diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 1bc2eeae02..760a0f330a 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -24,6 +24,7 @@ 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::Preferences; use Slic3r::GUI::ProgressStatusBar; diff --git a/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm b/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm new file mode 100644 index 0000000000..683b990bd1 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm @@ -0,0 +1,222 @@ +# 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 Slic3r::Geometry qw(PI X); +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; diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 4882233a67..6955d27750 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -7,7 +7,7 @@ use warnings; use utf8; use File::Basename qw(basename); -use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG +use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED); use base 'Wx::Panel'; @@ -22,6 +22,18 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); 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], @@ -41,10 +53,12 @@ sub new { # 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); if ($Slic3r::GUI::have_button_icons) { $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)); } @@ -52,21 +66,71 @@ sub new { my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $buttons_sizer->Add($self->{btn_load_part}, 0); $buttons_sizer->Add($self->{btn_load_modifier}, 0); + $buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0); $buttons_sizer->Add($self->{btn_delete}, 0); $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); # part settings panel $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; }); 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, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0); + $left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); # right pane with preview canvas my $canvas; @@ -84,7 +148,7 @@ sub new { $canvas->load_object($self->{model_object}, undef, [0]); $canvas->set_auto_bed_shape; - $canvas->SetSize([500,500]); + $canvas->SetSize([500,700]); $canvas->zoom_to_volumes; } @@ -107,6 +171,7 @@ sub new { }); 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); $self->reload_tree; @@ -179,6 +244,21 @@ sub selection_changed { $self->{btn_delete}->Disable; $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); @@ -191,6 +271,12 @@ sub selection_changed { # attach volume config to settings panel my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; + + if ($volume->modifier) { + $self->{optgroup_movers}->enable; + } else { + $self->{optgroup_movers}->disable; + } $config = $volume->config; $self->{staticbox}->SetLabel('Part Settings'); @@ -200,6 +286,7 @@ sub selection_changed { # 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; @@ -252,19 +339,56 @@ sub on_btn_load { $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::make_cube($params->{"dim"}[0], $params->{"dim"}[1], $params->{"dim"}[2]); + } elsif ($type eq "cylinder") { + $mesh = Slic3r::TriangleMesh::make_cylinder($params->{"cyl_r"}, $params->{"cyl_h"}); + } elsif ($type eq "sphere") { + $mesh = Slic3r::TriangleMesh::make_sphere($params->{"sph_rho"}); + } elsif ($type eq "slab") { + $mesh = Slic3r::TriangleMesh::make_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_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; } @@ -310,4 +434,27 @@ sub PartSettingsChanged { return $self->{part_settings_changed}; } +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}; + $self->{canvas}->reset_objects; + $self->{canvas}->load_object($_, undef, [0]) for @objects; + $self->{canvas}->Render; +} + 1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 1d8431b14e..4b4d35520d 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -27,6 +27,14 @@ sub mesh { $facets = [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], ], + } elsif ($name eq 'box') { + my ($x, $y, $z) = @{ $params{"dim"} }; + $vertices = [ + [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z], + ]; + $facets = [ + [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], + ], } elsif ($name eq 'cube_with_hole') { $vertices = [ [0,0,0],[0,0,10],[0,20,0],[0,20,10],[20,0,0],[20,0,10],[5,5,0],[15,5,0],[5,15,0],[20,20,0],[15,15,0],[20,20,10],[5,5,10],[5,15,10],[15,5,10],[15,15,10] diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 992cd59a78..5ce86817d8 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -25,6 +25,48 @@ TriangleMesh::TriangleMesh() stl_initialize(&this->stl); } +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets ) + : repaired(false) +{ + stl_initialize(&this->stl); + stl_file &stl = this->stl; + stl.error = 0; + stl.stats.type = inmemory; + + // count facets and allocate memory + stl.stats.number_of_facets = facets.size(); + stl.stats.original_num_facets = stl.stats.number_of_facets; + stl_allocate(&stl); + + for (int i = 0; i < stl.stats.number_of_facets; i++) { + stl_facet facet; + facet.normal.x = 0; + facet.normal.y = 0; + facet.normal.z = 0; + + const Pointf3& ref_f1 = points[facets[i].x]; + facet.vertex[0].x = ref_f1.x; + facet.vertex[0].y = ref_f1.y; + facet.vertex[0].z = ref_f1.z; + + const Pointf3& ref_f2 = points[facets[i].y]; + facet.vertex[1].x = ref_f2.x; + facet.vertex[1].y = ref_f2.y; + facet.vertex[1].z = ref_f2.z; + + const Pointf3& ref_f3 = points[facets[i].z]; + facet.vertex[2].x = ref_f3.x; + facet.vertex[2].y = ref_f3.y; + facet.vertex[2].z = ref_f3.z; + + facet.extra[0] = 0; + facet.extra[1] = 0; + + stl.facet_start[i] = facet; + } + stl_get_size(&stl); +} + TriangleMesh::TriangleMesh(const TriangleMesh &other) : stl(other.stl), repaired(other.repaired) { @@ -477,6 +519,154 @@ TriangleMesh::extrude_tin(float offset) this->repair(); } +// Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. +TriangleMesh +TriangleMesh::make_cube(double x, double y, double z) { + Pointf3 pv[8] = { + Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), + Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), + Pointf3(0, 0, z), Pointf3(x, 0, z) + }; + Point3 fv[12] = { + Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), + Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), + Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), + Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) + }; + + std::vector facets(&fv[0], &fv[0]+12); + Pointf3s vertices(&pv[0], &pv[0]+8); + + TriangleMesh mesh(vertices ,facets); + return mesh; +} + +// Generate the mesh for a cylinder and return it, using +// the generated angle to calculate the top mesh triangles. +// Default is 360 sides, angle fa is in radians. +TriangleMesh +TriangleMesh::make_cylinder(double r, double h, double fa) { + Pointf3s vertices; + std::vector facets; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.push_back(Pointf3(0.0, 0.0, 0.0)); + vertices.push_back(Pointf3(0.0, 0.0, h)); + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // for each line along the polygon approximating the top/bottom of the + // circle, generate four points and four facets (2 for the wall, 2 for the + // top and bottom. + // Special case: Last line shares 2 vertices with the first line. + unsigned id = vertices.size() - 1; + vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, 0)); + vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, h)); + for (double i = 0; i < 2*PI; i+=angle) { + Pointf3 b(0, r, 0); + Pointf3 t(0, r, h); + b.rotate(i, Pointf3(0,0,0)); + t.rotate(i, Pointf3(0,0,h)); + vertices.push_back(b); + vertices.push_back(t); + id = vertices.size() - 1; + facets.push_back(Point3( 0, id - 1, id - 3)); // top + facets.push_back(Point3(id, 1, id - 2)); // bottom + facets.push_back(Point3(id, id - 2, id - 3)); // upper-right of side + facets.push_back(Point3(id, id - 3, id - 1)); // bottom-left of side + } + // Connect the last set of vertices with the first. + facets.push_back(Point3( 2, 0, id - 1)); + facets.push_back(Point3( 1, 3, id)); + facets.push_back(Point3(id, 3, 2)); + facets.push_back(Point3(id, 2, id - 1)); + + TriangleMesh mesh(vertices, facets); + return mesh; +} + +// Generates mesh for a sphere centered about the origin, using the generated angle +// to determine the granularity. +// Default angle is 1 degree. +TriangleMesh +TriangleMesh::make_sphere(double rho, double fa) { + Pointf3s vertices; + std::vector facets; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative coordinates. + // Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + for (double i = 0; i < 2*PI; i+=angle) { + ring.push_back(i); + } + const size_t steps = ring.size(); + const double increment = (double)(1.0 / (double)steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + vertices.push_back(Pointf3(0.0, 0.0, -rho)); + size_t id = vertices.size(); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0; + // radius of the circle for this step. + const double r = sqrt(abs(rho*rho - z*z)); + Pointf3 b(0, r, z); + b.rotate(ring[i], Pointf3(0,0,z)); + vertices.push_back(b); + if (i == 0) { + facets.push_back(Point3(1, 0, ring.size())); + } else { + facets.push_back(Point3(id, 0, id - 1)); + } + id++; + } + + // General case: insert and form facets for each step, joining it to the ring below it. + for (size_t s = 2; s < steps - 1; s++) { + const double z = -rho + increment*(double)s*2.0*rho; + const double r = sqrt(abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Pointf3 b(0, r, z); + b.rotate(ring[i], Pointf3(0,0,z)); + vertices.push_back(b); + if (i == 0) { + // wrap around + facets.push_back(Point3(id + ring.size() - 1 , id, id - 1)); + facets.push_back(Point3(id, id - ring.size(), id - 1)); + } else { + facets.push_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); + facets.push_back(Point3(id, id - 1 - ring.size() , id - 1)); + } + id++; + } + } + + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + vertices.push_back(Pointf3(0.0, 0.0, rho)); + for (size_t i = 0; i < ring.size(); i++) { + if (i == 0) { + // third vertex is on the other side of the ring. + facets.push_back(Point3(id, id - ring.size(), id - 1)); + } else { + facets.push_back(Point3(id, id - ring.size() + i, id - ring.size() + (i - 1))); + } + } + id++; + TriangleMesh mesh(vertices, facets); + return mesh; +} + template void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 4a9bb9e5d9..225d744d45 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -21,6 +21,7 @@ class TriangleMesh { public: TriangleMesh(); + TriangleMesh(const Pointf3s &points, const std::vector &facets); TriangleMesh(const TriangleMesh &other); TriangleMesh& operator= (TriangleMesh other); void swap(TriangleMesh &other); @@ -56,6 +57,11 @@ class TriangleMesh bool needed_repair() const; size_t facets_count() const; void extrude_tin(float offset); + + static TriangleMesh make_cube(double x, double y, double z); + static TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)); + static TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); + stl_file stl; bool repaired; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 1640322df0..a3b1bf4a02 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -39,6 +39,7 @@ %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); void reset_repair_stats(); + %{ void @@ -252,6 +253,13 @@ TriangleMesh::bb3() %package{Slic3r::TriangleMesh}; +Clone make_cube(double x, double y, double z) + %code{% RETVAL = TriangleMesh::make_cube(x, y, z); %}; +Clone make_cylinder(double r, double h) + %code{% RETVAL = TriangleMesh::make_cylinder(r, h); %}; +Clone make_sphere(double rho) + %code{% RETVAL = TriangleMesh::make_sphere(rho); %}; + %{ PROTOTYPES: DISABLE