diff --git a/README.markdown b/README.markdown index 4fe41b879..3d1ef2848 100644 --- a/README.markdown +++ b/README.markdown @@ -90,6 +90,8 @@ The author of the Silk icon set is Mark James. -o, --output File to output gcode to (by default, the file will be saved into the same directory as the input file using the --output-filename-format to generate the filename) + --repair Automatically repair given STL files and saves them as _fixed.obj + GUI options: --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index d264f73fc..0d0699e2c 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -26,6 +26,7 @@ use constant MI_QUICK_SLICE => &Wx::NewId; use constant MI_REPEAT_QUICK => &Wx::NewId; use constant MI_QUICK_SAVE_AS => &Wx::NewId; use constant MI_SLICE_SVG => &Wx::NewId; +use constant MI_REPAIR_STL => &Wx::NewId; use constant MI_COMBINE_STLS => &Wx::NewId; use constant MI_PLATER_EXPORT_GCODE => &Wx::NewId; @@ -112,6 +113,7 @@ sub OnInit { $fileMenu->AppendSeparator(); $fileMenu->Append(MI_SLICE_SVG, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG'); $fileMenu->AppendSeparator(); + $fileMenu->Append(MI_REPAIR_STL, "Repair STL file…", 'Automatically repair an STL file'); $fileMenu->Append(MI_COMBINE_STLS, "Combine multi-material STL files…", 'Combine multiple STL files into a single multi-material AMF file'); $fileMenu->AppendSeparator(); $fileMenu->Append(wxID_PREFERENCES, "Preferences…", 'Application preferences'); @@ -125,6 +127,7 @@ sub OnInit { EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->quick_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->quick_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($frame, MI_REPAIR_STL, sub { $self->{skeinpanel}->repair_stl }); EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); EVT_MENU($frame, wxID_PREFERENCES, sub { Slic3r::GUI::Preferences->new($frame)->ShowModal }); EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 7644136b1..85d2d5dae 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -192,6 +192,38 @@ sub quick_slice { Slic3r::GUI::catch_error($self, sub { $process_dialog->Destroy if $process_dialog }); } +sub repair_stl { + my $self = shift; + + my $input_file; + { + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; + my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if ($dialog->ShowModal != wxID_OK) { + $dialog->Destroy; + return; + } + $input_file = $dialog->GetPaths; + $dialog->Destroy; + } + + my $output_file = $input_file; + { + $output_file =~ s/\.stl$/_fixed.obj/i; + my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file), + basename($output_file), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return undef; + } + $output_file = $dlg->GetPath; + $dlg->Destroy; + } + + Slic3r::TriangleMesh::XS::stl_repair($input_file, $output_file); + Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); +} + sub init_print { my $self = shift; diff --git a/slic3r.pl b/slic3r.pl index 0b05cd043..771dc80ce 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -33,6 +33,7 @@ my %cli_options = (); 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, + 'repair' => \$opt{repair}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; @@ -92,6 +93,18 @@ die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line $config->validate; + if ($opt{repair}) { + foreach my $file (@ARGV) { + die "Repair is currently supported only on STL files\n" + if $file !~ /\.stl$/i; + + my $output_file = $file; + $output_file =~ s/\.(stl)$/_fixed.obj/i; + Slic3r::TriangleMesh::XS::stl_repair($file, $output_file); + } + exit; + } + while (my $input_file = shift @ARGV) { my $model; if ($opt{merge}) { @@ -150,6 +163,7 @@ Usage: slic3r.pl [ OPTIONS ] file.stl -o, --output File to output gcode to (by default, the file will be saved into the same directory as the input file using the --output-filename-format to generate the filename) + --repair Automatically repair given STL files and saves them as _fixed.obj $j GUI options: --no-plater Disable the plater tab diff --git a/xs/src/admesh/shared.c b/xs/src/admesh/shared.c index 681ec0ff9..53fbb1621 100644 --- a/xs/src/admesh/shared.c +++ b/xs/src/admesh/shared.c @@ -241,3 +241,26 @@ stl_write_vrml(stl_file *stl, char *file) fprintf(fp, "}\n"); fclose(fp); } + +void stl_write_obj (stl_file *stl, char *file) { + int i; + + /* Open the file */ + FILE* fp = fopen(file, "w"); + if (fp == NULL) { + char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ + sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); + perror(error_msg); + free(error_msg); + exit(1); + } + + for (i = 0; i < stl->stats.shared_vertices; i++) { + fprintf(fp, "v %f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); + } + for (i = 0; i < stl->stats.number_of_facets; i++) { + fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); + } + + fclose(fp); +} diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h index 370a95c26..92f14000f 100644 --- a/xs/src/admesh/stl.h +++ b/xs/src/admesh/stl.h @@ -164,6 +164,7 @@ extern void stl_mirror_yz(stl_file *stl); extern void stl_mirror_xz(stl_file *stl); extern void stl_open_merge(stl_file *stl, char *file); extern void stl_generate_shared_vertices(stl_file *stl); +extern void stl_write_obj(stl_file *stl, char *file); extern void stl_write_off(stl_file *stl, char *file); extern void stl_write_dxf(stl_file *stl, char *file, char *label); extern void stl_write_vrml(stl_file *stl, char *file); diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 7ba9724f1..9e0da64a6 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -13,25 +13,72 @@ hello_world() OUTPUT: RETVAL -float -stl_volume(filename) - char* filename; +void +stl_repair(input_file, output_file) + char* input_file; + char* output_file; CODE: - stl_file stl_in; - stl_open(&stl_in, filename); + int i; + stl_file stl_in; + stl_open(&stl_in, input_file); + + // checking exact stl_check_facets_exact(&stl_in); stl_in.stats.facets_w_1_bad_edge = (stl_in.stats.connected_facets_2_edge - stl_in.stats.connected_facets_3_edge); stl_in.stats.facets_w_2_bad_edge = (stl_in.stats.connected_facets_1_edge - stl_in.stats.connected_facets_2_edge); stl_in.stats.facets_w_3_bad_edge = (stl_in.stats.number_of_facets - stl_in.stats.connected_facets_1_edge); + // checking nearby + int last_edges_fixed = 0; + float tolerance = stl_in.stats.shortest_edge; + float increment = stl_in.stats.bounding_diameter / 10000.0; + int iterations = 2; + if (stl_in.stats.connected_facets_3_edge < stl_in.stats.number_of_facets) { + for (i = 0; i < iterations; i++) { + if (stl_in.stats.connected_facets_3_edge < stl_in.stats.number_of_facets) { + printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); + stl_check_facets_nearby(&stl_in, tolerance); + printf(" Fixed %d edges.\n", stl_in.stats.edges_fixed - last_edges_fixed); + last_edges_fixed = stl_in.stats.edges_fixed; + tolerance += increment; + } else { + printf("All facets connected. No further nearby check necessary.\n"); + break; + } + } + } else { + printf("All facets connected. No nearby check necessary.\n"); + } + + // remove_unconnected + if (stl_in.stats.connected_facets_3_edge < stl_in.stats.number_of_facets) { + printf("Removing unconnected facets...\n"); + stl_remove_unconnected_facets(&stl_in); + } else + printf("No unconnected need to be removed.\n"); + + // fill_holes + if (stl_in.stats.connected_facets_3_edge < stl_in.stats.number_of_facets) { + printf("Filling holes...\n"); + stl_fill_holes(&stl_in); + } else + printf("No holes need to be filled.\n"); + + // normal_directions + printf("Checking normal directions...\n"); stl_fix_normal_directions(&stl_in); + + // normal_values + printf("Checking normal values...\n"); stl_fix_normal_values(&stl_in); - stl_calculate_volume(&stl_in); - RETVAL = stl_in.stats.volume; + // write STL + //stl_write_ascii(&stl_in, output_file, (char*)"Repaired by Slic3r + ADMesh"); + + stl_generate_shared_vertices(&stl_in); + stl_write_obj(&stl_in, output_file); + stl_close(&stl_in); - OUTPUT: - RETVAL %}