mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into dynamic-flow
Conflicts: lib/Slic3r/ExPolygon.pm
This commit is contained in:
		
						commit
						1cedb00f20
					
				
					 34 changed files with 529 additions and 260 deletions
				
			
		|  | @ -7,7 +7,7 @@ use strict; | |||
| use warnings; | ||||
| require v5.10; | ||||
| 
 | ||||
| our $VERSION = "0.9.2-dev"; | ||||
| our $VERSION = "0.9.3-dev"; | ||||
| 
 | ||||
| our $debug = 0; | ||||
| sub debugf { | ||||
|  | @ -21,6 +21,9 @@ BEGIN { | |||
|     $have_threads = $Config{useithreads} && eval "use threads; use Thread::Queue; 1"; | ||||
| } | ||||
| 
 | ||||
| warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n" | ||||
|     if $^V >= v5.16; | ||||
| 
 | ||||
| use FindBin; | ||||
| our $var = "$FindBin::Bin/var"; | ||||
| 
 | ||||
|  | @ -41,6 +44,7 @@ use Slic3r::GCode; | |||
| use Slic3r::Geometry qw(PI); | ||||
| use Slic3r::Layer; | ||||
| use Slic3r::Line; | ||||
| use Slic3r::Model; | ||||
| use Slic3r::Point; | ||||
| use Slic3r::Polygon; | ||||
| use Slic3r::Polyline; | ||||
|  |  | |||
|  | @ -178,6 +178,7 @@ our $Options = { | |||
|     # extruder mapping | ||||
|     'perimeter_extruder' => { | ||||
|         label   => 'Perimeter extruder', | ||||
|         tooltip => 'The extruder to use when printing perimeters.', | ||||
|         cli     => 'perimeter-extruder=i', | ||||
|         type    => 'i', | ||||
|         aliases => [qw(perimeters_extruder)], | ||||
|  | @ -185,12 +186,14 @@ our $Options = { | |||
|     }, | ||||
|     'infill_extruder' => { | ||||
|         label   => 'Infill extruder', | ||||
|         tooltip => 'The extruder to use when printing infill.', | ||||
|         cli     => 'infill-extruder=i', | ||||
|         type    => 'i', | ||||
|         default => 1, | ||||
|     }, | ||||
|     'support_material_extruder' => { | ||||
|         label   => 'Support material extruder', | ||||
|         tooltip => 'The extruder to use when printing support material. This affects brim too.', | ||||
|         cli     => 'support-material-extruder=i', | ||||
|         type    => 'i', | ||||
|         default => 1, | ||||
|  | @ -470,6 +473,13 @@ our $Options = { | |||
|         type    => 'bool', | ||||
|         default => 1, | ||||
|     }, | ||||
|     'only_retract_when_crossing_perimeters' => { | ||||
|         label   => 'Only retract when crossing perimeters', | ||||
|         tooltip => 'Disables retraction when travelling between infill paths inside the same island.', | ||||
|         cli     => 'only-retract-when-crossing-perimeters!', | ||||
|         type    => 'bool', | ||||
|         default => 0, | ||||
|     }, | ||||
|     'support_material' => { | ||||
|         label   => 'Generate support material', | ||||
|         tooltip => 'Enable support material generation.', | ||||
|  |  | |||
|  | @ -61,13 +61,12 @@ sub boost_polygon { | |||
| 
 | ||||
| sub offset { | ||||
|     my $self = shift; | ||||
|     my ($distance, $scale, $joinType, $miterLimit) = @_; | ||||
|     $scale      ||= &Slic3r::SCALING_FACTOR * 1000000; | ||||
|     $joinType   = JT_MITER if !defined $joinType; | ||||
|     $miterLimit ||= 2; | ||||
|      | ||||
|     my $offsets = Math::Clipper::offset($self, $distance, $scale, $joinType, $miterLimit); | ||||
|     return @$offsets; | ||||
|     return Slic3r::Geometry::Clipper::offset($self, @_); | ||||
| } | ||||
| 
 | ||||
| sub offset_ex { | ||||
|     my $self = shift; | ||||
|     return Slic3r::Geometry::Clipper::offset_ex($self, @_); | ||||
| } | ||||
| 
 | ||||
| sub safety_offset { | ||||
|  |  | |||
|  | @ -170,7 +170,7 @@ sub make_fill { | |||
|                             ? ($surface->surface_type == S_TYPE_TOP ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) | ||||
|                             : EXTR_ROLE_FILL), | ||||
|                     depth_layers => $surface->depth_layers, | ||||
|                     flow_spacing => $params->{flow_spacing} || $flow_spacing, | ||||
|                     flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"), | ||||
|                 ), @paths, | ||||
|             ], | ||||
|         ); | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ sub fill_surface { | |||
|     my $min_spacing = scale $params{flow_spacing}; | ||||
|     my $distance = $min_spacing / $params{density}; | ||||
|      | ||||
|     my $flow_spacing; | ||||
|     my $flow_spacing = $params{flow_spacing}; | ||||
|     if ($params{density} == 1) { | ||||
|         $distance = $self->adjust_solid_spacing( | ||||
|             width       => $bounding_box->[X2] - $bounding_box->[X1], | ||||
|  |  | |||
|  | @ -94,7 +94,8 @@ sub fill_surface { | |||
|         paths => [ map Slic3r::ExtrusionPath->pack(polyline => $_, role => -1), @paths ], | ||||
|     ); | ||||
|      | ||||
|     return {}, map $_->polyline, $collection->shortest_path; | ||||
|     return { flow_spacing => $params{flow_spacing} }, | ||||
|         map $_->polyline, $collection->shortest_path; | ||||
| } | ||||
| 
 | ||||
| 1; | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ sub fill_surface { | |||
|     # paths must be rotated back | ||||
|     $self->rotate_points_back(\@paths, $rotate_vector); | ||||
|      | ||||
|     return {}, @paths; | ||||
|     return { flow_spacing => $params{flow_spacing} }, @paths; | ||||
| } | ||||
| 
 | ||||
| 1; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ use Moo; | |||
| 
 | ||||
| extends 'Slic3r::Fill::Base'; | ||||
| 
 | ||||
| use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale epsilon); | ||||
| use Slic3r::Geometry qw(X1 Y1 X2 Y2 A B X Y scale unscale scaled_epsilon); | ||||
| 
 | ||||
| sub fill_surface { | ||||
|     my $self = shift; | ||||
|  | @ -22,7 +22,7 @@ sub fill_surface { | |||
|     my $distance_between_lines = $min_spacing / $params{density}; | ||||
|     my $line_oscillation = $distance_between_lines - $min_spacing; | ||||
|      | ||||
|     my $flow_spacing; | ||||
|     my $flow_spacing = $params{flow_spacing}; | ||||
|     if ($params{density} == 1) { | ||||
|         $distance_between_lines = $self->adjust_solid_spacing( | ||||
|             width       => $bounding_box->[X2] - $bounding_box->[X1], | ||||
|  | @ -36,7 +36,7 @@ sub fill_surface { | |||
|     my $x = $bounding_box->[X1]; | ||||
|     my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); | ||||
|     my @vertical_lines = (); | ||||
|     for (my $i = 0; $x <= $bounding_box->[X2] + scale epsilon; $i++) { | ||||
|     for (my $i = 0; $x <= $bounding_box->[X2] + scaled_epsilon; $i++) { | ||||
|         my $vertical_line = Slic3r::Line->new([$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]]); | ||||
|         if ($is_line_pattern && $i % 2) { | ||||
|             $vertical_line->[A][X] += $line_oscillation; | ||||
|  | @ -49,7 +49,7 @@ sub fill_surface { | |||
|     # clip paths against a slightly offsetted expolygon, so that the first and last paths | ||||
|     # are kept even if the expolygon has vertical sides | ||||
|     my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection( | ||||
|         +($expolygon->offset_ex(scale epsilon))[0]->boost_polygon,  # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object | ||||
|         +($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon,  # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object | ||||
|         Boost::Geometry::Utils::linestring(@vertical_lines), | ||||
|     ) }; | ||||
|     for (@paths) { | ||||
|  | @ -64,7 +64,7 @@ sub fill_surface { | |||
|         ); | ||||
|         @paths = (); | ||||
|          | ||||
|         my $tolerance = 10 * scale epsilon; | ||||
|         my $tolerance = 10 * scaled_epsilon; | ||||
|         my $diagonal_distance = $distance_between_lines * 5; | ||||
|         my $can_connect = $is_line_pattern | ||||
|             ? sub { | ||||
|  |  | |||
|  | @ -12,28 +12,18 @@ sub read_file { | |||
|      | ||||
|     open my $fh, '<', $file or die "Failed to open $file\n"; | ||||
|      | ||||
|     my $vertices = []; | ||||
|     my $materials = {}; | ||||
|     my $meshes_by_material = {}; | ||||
|     my $model = Slic3r::Model->new; | ||||
|     XML::SAX::PurePerl | ||||
|         ->new(Handler => Slic3r::Format::AMF::Parser->new( | ||||
|             _vertices           => $vertices, | ||||
|             _materials          => $materials, | ||||
|             _meshes_by_material => $meshes_by_material, | ||||
|          )) | ||||
|         ->new(Handler => Slic3r::Format::AMF::Parser->new(_model => $model)) | ||||
|         ->parse_file($fh); | ||||
|      | ||||
|     close $fh; | ||||
|      | ||||
|     $_ = Slic3r::TriangleMesh->new(vertices => $vertices, facets => $_) | ||||
|         for values %$meshes_by_material; | ||||
|      | ||||
|     return $materials, $meshes_by_material; | ||||
|     return $model; | ||||
| } | ||||
| 
 | ||||
| sub write_file { | ||||
|     my $self = shift; | ||||
|     my ($file, $materials, $meshes_by_material) = @_; | ||||
|     my ($file, $model, %params) = @_; | ||||
|      | ||||
|     my %vertices_offset = (); | ||||
|      | ||||
|  | @ -42,20 +32,21 @@ sub write_file { | |||
|     printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n}; | ||||
|     printf $fh qq{<amf unit="millimeter">\n}; | ||||
|     printf $fh qq{  <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION; | ||||
|     foreach my $material_id (keys %$materials) { | ||||
|         printf $fh qq{  <material id="%s">\n}, $material_id; | ||||
|         for (keys %{$materials->{$material_id}}) { | ||||
|              printf $fh qq{    <metadata type=\"%s\">%s</metadata>\n}, $_, $materials->{$material_id}{$_}; | ||||
|     for my $material_id (sort keys %{ $model->materials }) { | ||||
|         my $material = $model->materials->{$material_id}; | ||||
|         printf $fh qq{  <material id="%d">\n}, $material_id; | ||||
|         for (keys %$material) { | ||||
|              printf $fh qq{    <metadata type=\"%s\">%s</metadata>\n}, $_, $material->{$_}; | ||||
|         } | ||||
|         printf $fh qq{  </material>\n}; | ||||
|     } | ||||
|     printf $fh qq{  <object id="0">\n}; | ||||
|     printf $fh qq{    <mesh>\n}; | ||||
|     printf $fh qq{      <vertices>\n}; | ||||
|     my $vertices_count = 0; | ||||
|     foreach my $mesh (values %$meshes_by_material) { | ||||
|         $vertices_offset{$mesh} = $vertices_count; | ||||
|         foreach my $vertex (@{$mesh->vertices}, ) { | ||||
|     my $instances = ''; | ||||
|     for my $object_id (0 .. $#{ $model->objects }) { | ||||
|         my $object = $model->objects->[$object_id]; | ||||
|         printf $fh qq{  <object id="%d">\n}, $object_id; | ||||
|         printf $fh qq{    <mesh>\n}; | ||||
|         printf $fh qq{      <vertices>\n}; | ||||
|         foreach my $vertex (@{$object->vertices}, ) { | ||||
|             printf $fh qq{        <vertex>\n}; | ||||
|             printf $fh qq{          <coordinates>\n}; | ||||
|             printf $fh qq{            <x>%s</x>\n}, $vertex->[X]; | ||||
|  | @ -63,24 +54,35 @@ sub write_file { | |||
|             printf $fh qq{            <z>%s</z>\n}, $vertex->[Z]; | ||||
|             printf $fh qq{          </coordinates>\n}; | ||||
|             printf $fh qq{        </vertex>\n}; | ||||
|             $vertices_count++; | ||||
|         } | ||||
|         printf $fh qq{      </vertices>\n}; | ||||
|         foreach my $volume (@{ $object->volumes }) { | ||||
|             printf $fh qq{      <volume%s>\n}, | ||||
|                 (!defined $volume->material_id) ? '' : (sprintf ' materialid="%s"', $volume->material_id); | ||||
|             foreach my $facet (@{$volume->facets}) { | ||||
|                 printf $fh qq{        <triangle>\n}; | ||||
|                 printf $fh qq{          <v%d>%d</v%d>\n}, (4+$_), $facet->[$_], (4+$_) for -3..-1; | ||||
|                 printf $fh qq{        </triangle>\n}; | ||||
|             } | ||||
|             printf $fh qq{      </volume>\n}; | ||||
|         } | ||||
|         printf $fh qq{    </mesh>\n}; | ||||
|         printf $fh qq{  </object>\n}; | ||||
|         if ($object->instances) { | ||||
|             foreach my $instance (@{$object->instances}) { | ||||
|                 $instances .= sprintf qq{    <instance objectid="%d">\n}, $object_id; | ||||
|                 $instances .= sprintf qq{      <deltax>%s</deltax>\n}, $instance->offset->[X]; | ||||
|                 $instances .= sprintf qq{      <deltax>%s</deltax>\n}, $instance->offset->[Y]; | ||||
|                 $instances .= sprintf qq{      <rz>%s</rz>\n}, $instance->rotation; | ||||
|                 $instances .= sprintf qq{    </instance>\n}; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     printf $fh qq{      </vertices>\n}; | ||||
|     foreach my $material_id (sort keys %$meshes_by_material) { | ||||
|         my $mesh = $meshes_by_material->{$material_id}; | ||||
|         printf $fh qq{      <volume%s>\n}, | ||||
|             ($material_id eq '_') ? '' : " materialid=\"$material_id\""; | ||||
|         foreach my $facet (@{$mesh->facets}) { | ||||
|             printf $fh qq{        <triangle>\n}; | ||||
|             printf $fh qq{          <v%d>%d</v%d>\n}, $_, $facet->[$_] + $vertices_offset{$mesh}, $_ | ||||
|                 for -3..-1; | ||||
|             printf $fh qq{        </triangle>\n}; | ||||
|         } | ||||
|         printf $fh qq{      </volume>\n}; | ||||
|     if ($instances) { | ||||
|         printf $fh qq{  <constellation id="1">\n}; | ||||
|         printf $fh $instances; | ||||
|         printf $fh qq{  </constellation>\n}; | ||||
|     } | ||||
|     printf $fh qq{    </mesh>\n}; | ||||
|     printf $fh qq{  </object>\n}; | ||||
|     printf $fh qq{</amf>\n}; | ||||
|     close $fh; | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ my %xyz_index = (x => 0, y => 1, z => 2); #= | |||
| sub new { | ||||
|     my $self = shift->SUPER::new(@_); | ||||
|     $self->{_tree} = []; | ||||
|     $self->{_objects_map} = {};  # this hash maps AMF object IDs to object indexes in $model->objects | ||||
|     $self->{_instances} = {};   # apply these lazily to make sure all objects have been parsed | ||||
|     $self; | ||||
| } | ||||
| 
 | ||||
|  | @ -18,23 +20,35 @@ sub start_element { | |||
|     my $self = shift; | ||||
|     my $data = shift; | ||||
|      | ||||
|     if ($data->{LocalName} eq 'vertex') { | ||||
|     if ($data->{LocalName} eq 'object') { | ||||
|         $self->{_object} = $self->{_model}->add_object; | ||||
|         $self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects }; | ||||
|     } elsif ($data->{LocalName} eq 'vertex') { | ||||
|         $self->{_vertex} = ["", "", ""]; | ||||
|     } elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') { | ||||
|         $self->{_coordinate} = $data->{LocalName}; | ||||
|     } elsif ($data->{LocalName} eq 'volume') { | ||||
|         $self->{_volume_materialid} = $self->_get_attribute($data, 'materialid') || '_'; | ||||
|         $self->{_volume} = []; | ||||
|         $self->{_volume} = $self->{_object}->add_volume( | ||||
|             material_id => $self->_get_attribute($data, 'materialid') || undef, | ||||
|         ); | ||||
|     } elsif ($data->{LocalName} eq 'triangle') { | ||||
|         $self->{_triangle} = ["", "", ""]; | ||||
|     } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { | ||||
|         $self->{_vertex_idx} = $1-1; | ||||
|     } elsif ($data->{LocalName} eq 'material') { | ||||
|         $self->{_material_id} = $self->_get_attribute($data, 'id') || '_'; | ||||
|         $self->{_material} = {}; | ||||
|         my $material_id = $self->_get_attribute($data, 'id') || '_'; | ||||
|         $self->{_material} = $self->{_model}->materials->{ $material_id } = {}; | ||||
|     } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { | ||||
|         $self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); | ||||
|         $self->{_material}{ $self->{_material_metadata_type} } = ""; | ||||
|     } elsif ($data->{LocalName} eq 'constellation') { | ||||
|         $self->{_constellation} = 1; # we merge all constellations as we don't support more than one | ||||
|     } elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) { | ||||
|         my $object_id = $self->_get_attribute($data, 'objectid'); | ||||
|         $self->{_instances}{$object_id} ||= []; | ||||
|         push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {}; | ||||
|     } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { | ||||
|         $self->{_instance_property} = $data->{LocalName}; | ||||
|     } | ||||
|      | ||||
|     push @{$self->{_tree}}, $data->{LocalName}; | ||||
|  | @ -50,6 +64,8 @@ sub characters { | |||
|         $self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data}; | ||||
|     } elsif ($self->{_material_metadata_type}) { | ||||
|         $self->{_material}{ $self->{_material_metadata_type} } .= $data->{Data}; | ||||
|     } elsif ($self->{_instance_property}) { | ||||
|         $self->{_instance}{ $self->{_instance_property} } .= $data->{Data}; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -59,26 +75,49 @@ sub end_element { | |||
|      | ||||
|     pop @{$self->{_tree}}; | ||||
|      | ||||
|     if ($data->{LocalName} eq 'vertex') { | ||||
|         push @{$self->{_vertices}}, $self->{_vertex}; | ||||
|     if ($data->{LocalName} eq 'object') { | ||||
|         $self->{_object} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'vertex') { | ||||
|         push @{$self->{_object}->vertices}, $self->{_vertex}; | ||||
|         $self->{_vertex} = undef; | ||||
|     } elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) { | ||||
|         $self->{_coordinate} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'volume') { | ||||
|         $self->{_meshes_by_material}{ $self->{_volume_materialid} } ||= []; | ||||
|         push @{ $self->{_meshes_by_material}{ $self->{_volume_materialid} } }, @{$self->{_volume}}; | ||||
|         $self->{_volume} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'triangle') { | ||||
|         push @{$self->{_volume}}, $self->{_triangle}; | ||||
|         push @{$self->{_volume}->facets}, $self->{_triangle}; | ||||
|         $self->{_triangle} = undef; | ||||
|     } elsif ($self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { | ||||
|     } elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) { | ||||
|         $self->{_vertex_idx} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'material') { | ||||
|         $self->{_materials}{ $self->{_material_id} } = $self->{_material}; | ||||
|         $self->{_material_id} = undef; | ||||
|         $self->{_material} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'metadata' && $self->{_material}) { | ||||
|         $self->{_material_metadata_type} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'constellation') { | ||||
|         $self->{_constellation} = undef; | ||||
|     } elsif ($data->{LocalName} eq 'instance') { | ||||
|         $self->{_instance} = undef; | ||||
|     } elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) { | ||||
|         $self->{_instance_property} = undef; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| sub end_document { | ||||
|     my $self = shift; | ||||
|      | ||||
|     foreach my $object_id (keys %{ $self->{_instances} }) { | ||||
|         my $new_object_id = $self->{_objects_map}{$object_id}; | ||||
|         if (!$new_object_id) { | ||||
|             warn "Undefined object $object_id referenced in constellation\n"; | ||||
|             next; | ||||
|         } | ||||
|          | ||||
|         foreach my $instance (@{ $self->{_instances}{$object_id} }) { | ||||
|             $self->{_model}->objects->[$new_object_id]->add_instance( | ||||
|                 rotation => $instance->{rz} || 0, | ||||
|                 offset   => [ $instance->{deltax} || 0, $instance->{deltay} ], | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,7 +17,10 @@ sub read_file { | |||
|     } | ||||
|     close $fh; | ||||
|      | ||||
|     return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); | ||||
|     my $model = Slic3r::Model->new; | ||||
|     my $object = $model->add_object(vertices => $vertices); | ||||
|     my $volume = $object->add_volume(facets => $facets); | ||||
|     return $model; | ||||
| } | ||||
| 
 | ||||
| 1; | ||||
|  |  | |||
|  | @ -117,7 +117,10 @@ sub read_file { | |||
|         } | ||||
|     } | ||||
|      | ||||
|     return Slic3r::TriangleMesh->new(vertices => $vertices, facets => $facets); | ||||
|     my $model = Slic3r::Model->new; | ||||
|     my $object = $model->add_object(vertices => $vertices); | ||||
|     my $volume = $object->add_volume(facets => $facets); | ||||
|     return $model; | ||||
| } | ||||
| 
 | ||||
| sub _read_ascii { | ||||
|  | @ -161,13 +164,13 @@ sub _read_binary { | |||
| 
 | ||||
| sub write_file { | ||||
|     my $self = shift; | ||||
|     my ($file, $mesh, $binary) = @_; | ||||
|     my ($file, $model, %params) = @_; | ||||
|      | ||||
|     open my $fh, '>', $file; | ||||
|      | ||||
|     $binary | ||||
|         ? _write_binary($fh, $mesh) | ||||
|         : _write_ascii($fh, $mesh); | ||||
|     $params{binary} | ||||
|         ? _write_binary($fh, $model->mesh) | ||||
|         : _write_ascii($fh, $model->mesh); | ||||
|      | ||||
|     close $fh; | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| package Slic3r::GCode; | ||||
| use Moo; | ||||
| 
 | ||||
| use List::Util qw(first); | ||||
| use Slic3r::ExtrusionPath ':roles'; | ||||
| use Slic3r::Geometry qw(scale unscale); | ||||
| use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y); | ||||
| 
 | ||||
| has 'layer'              => (is => 'rw'); | ||||
| has 'shift_x'            => (is => 'rw', default => sub {0} ); | ||||
|  | @ -48,8 +49,6 @@ my %role_speeds = ( | |||
|     &EXTR_ROLE_SUPPORTMATERIAL              => 'perimeter', | ||||
| ); | ||||
| 
 | ||||
| use Slic3r::Geometry qw(points_coincide PI X Y); | ||||
| 
 | ||||
| sub extruder { | ||||
|     my $self = shift; | ||||
|     return $Slic3r::extruders->[$self->extruder_idx]; | ||||
|  | @ -132,15 +131,13 @@ sub extrude_path { | |||
|     my $gcode = ""; | ||||
|      | ||||
|     # retract if distance from previous position is greater or equal to the one | ||||
|     # specified by the user *and* to the maximum distance between infill lines | ||||
|     # specified by the user | ||||
|     { | ||||
|         my $distance_from_last_pos = $self->last_pos->distance_to($path->points->[0]) * &Slic3r::SCALING_FACTOR; | ||||
|         my $distance_threshold = $self->extruder->retract_before_travel; | ||||
|         $distance_threshold = 2 * ($self->layer ? $self->layer->flow->width : $Slic3r::flow->width) / $Slic3r::Config->fill_density * sqrt(2) | ||||
|             if 0 && $Slic3r::Config->fill_density > 0 && $description =~ /fill/; | ||||
|      | ||||
|         if ($distance_from_last_pos >= $distance_threshold) { | ||||
|             $gcode .= $self->retract(travel_to => $path->points->[0]); | ||||
|         my $travel = Slic3r::Line->new($self->last_pos, $path->points->[0]); | ||||
|         if ($travel->length >= scale $self->extruder->retract_before_travel) { | ||||
|             if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->expolygon->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) { | ||||
|                 $gcode .= $self->retract(travel_to => $path->points->[0]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | @ -424,25 +421,34 @@ sub set_temperature { | |||
|      | ||||
|     return "" if $wait && $Slic3r::Config->gcode_flavor eq 'makerbot'; | ||||
|      | ||||
|     my ($code, $comment) = $wait | ||||
|     my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') | ||||
|         ? ('M109', 'wait for temperature to be reached') | ||||
|         : ('M104', 'set temperature'); | ||||
|     return sprintf "$code %s%d %s; $comment\n", | ||||
|     my $gcode = sprintf "$code %s%d %s; $comment\n", | ||||
|         ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, | ||||
|         (defined $tool && $tool != $self->extruder_idx) ? "T$tool " : ""; | ||||
|      | ||||
|     $gcode .= "M116 ; wait for temperature to be reached\n" | ||||
|         if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; | ||||
|      | ||||
|     return $gcode; | ||||
| } | ||||
| 
 | ||||
| sub set_bed_temperature { | ||||
|     my $self = shift; | ||||
|     my ($temperature, $wait) = @_; | ||||
|      | ||||
|     my ($code, $comment) = $wait | ||||
|     my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') | ||||
|         ? (($Slic3r::Config->gcode_flavor eq 'makerbot' ? 'M109' | ||||
|             : $Slic3r::Config->gcode_flavor eq 'teacup' ? 'M109 P1' | ||||
|             : 'M190'), 'wait for bed temperature to be reached') | ||||
|         : ('M140', 'set bed temperature'); | ||||
|     return sprintf "$code %s%d ; $comment\n", | ||||
|     my $gcode = sprintf "$code %s%d ; $comment\n", | ||||
|         ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; | ||||
|      | ||||
|     $gcode .= "M116 ; wait for bed temperature to be reached\n" | ||||
|         if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; | ||||
|      | ||||
|     return $gcode; | ||||
| } | ||||
| 
 | ||||
| 1; | ||||
|  |  | |||
|  | @ -22,6 +22,10 @@ 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_PLATER_EXPORT_GCODE => &Wx::NewId; | ||||
| use constant MI_PLATER_EXPORT_STL   => &Wx::NewId; | ||||
| use constant MI_PLATER_EXPORT_AMF   => &Wx::NewId; | ||||
| 
 | ||||
| use constant MI_TAB_PLATER    => &Wx::NewId; | ||||
| use constant MI_TAB_PRINT     => &Wx::NewId; | ||||
| use constant MI_TAB_FILAMENT  => &Wx::NewId; | ||||
|  | @ -47,7 +51,7 @@ sub OnInit { | |||
|     $self->{notifier} = Slic3r::GUI::Notifier->new; | ||||
|      | ||||
|     # locate or create data directory | ||||
|     $datadir = Wx::StandardPaths::Get->GetUserDataDir; | ||||
|     $datadir ||= Wx::StandardPaths::Get->GetUserDataDir; | ||||
|     Slic3r::debugf "Data directory: %s\n", $datadir; | ||||
|     my $run_wizard = (-d $datadir) ? 0 : 1; | ||||
|     for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") { | ||||
|  | @ -98,6 +102,17 @@ sub OnInit { | |||
|         EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); | ||||
|     } | ||||
|      | ||||
|     # Plater menu | ||||
|     my $platerMenu = Wx::Menu->new; | ||||
|     { | ||||
|         $platerMenu->Append(MI_PLATER_EXPORT_GCODE, "Export G-code...", 'Export current plate as G-code'); | ||||
|         $platerMenu->Append(MI_PLATER_EXPORT_STL, "Export STL...", 'Export current plate as STL'); | ||||
|         $platerMenu->Append(MI_PLATER_EXPORT_AMF, "Export AMF...", 'Export current plate as AMF'); | ||||
|         EVT_MENU($frame, MI_PLATER_EXPORT_GCODE, sub { $self->{skeinpanel}{plater}->export_gcode }); | ||||
|         EVT_MENU($frame, MI_PLATER_EXPORT_STL, sub { $self->{skeinpanel}{plater}->export_stl }); | ||||
|         EVT_MENU($frame, MI_PLATER_EXPORT_AMF, sub { $self->{skeinpanel}{plater}->export_amf }); | ||||
|     } | ||||
|      | ||||
|     # Window menu | ||||
|     my $windowMenu = Wx::Menu->new; | ||||
|     { | ||||
|  | @ -128,6 +143,7 @@ sub OnInit { | |||
|     { | ||||
|         my $menubar = Wx::MenuBar->new; | ||||
|         $menubar->Append($fileMenu, "&File"); | ||||
|         $menubar->Append($platerMenu, "&Plater"); | ||||
|         $menubar->Append($windowMenu, "&Window"); | ||||
|         $menubar->Append($helpMenu, "&Help"); | ||||
|         $frame->SetMenuBar($menubar); | ||||
|  |  | |||
|  | @ -296,7 +296,7 @@ sub load_file { | |||
|     my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0); | ||||
|     $process_dialog->Pulse; | ||||
|     local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); | ||||
|     $self->{print}->add_object_from_file($input_file); | ||||
|     $self->{print}->add_objects_from_file($input_file); | ||||
|     my $obj_idx = $#{$self->{print}->objects}; | ||||
|     $process_dialog->Destroy; | ||||
|      | ||||
|  | @ -419,9 +419,7 @@ sub rotate { | |||
|     # rotate, realign to 0,0 and update size | ||||
|     $object->mesh->rotate($angle); | ||||
|     $object->mesh->align_to_origin; | ||||
|     my @size = $object->mesh->size; | ||||
|     $object->x_length($size[X]); | ||||
|     $object->y_length($size[Y]); | ||||
|     $object->size([ $object->mesh->size ]); | ||||
|      | ||||
|     $self->make_thumbnail($obj_idx); | ||||
|     $self->recenter; | ||||
|  | @ -458,9 +456,7 @@ sub changescale { | |||
|     my $mesh = $object->mesh; | ||||
|     $mesh->scale($scale/100 / $self->{scale}[$obj_idx]); | ||||
|     $object->mesh->align_to_origin; | ||||
|     my @size = $object->mesh->size; | ||||
|     $object->x_length($size[X]); | ||||
|     $object->y_length($size[Y]); | ||||
|     $object->size([ $object->mesh->size ]); | ||||
|      | ||||
|     $self->{scale}[$obj_idx] = $scale/100; | ||||
|     $self->{list}->SetItem($obj_idx, 2, "$scale%"); | ||||
|  | @ -512,6 +508,8 @@ sub export_gcode { | |||
|      | ||||
|     # set this before spawning the thread because ->config needs GetParent and it's not available there | ||||
|     $self->{print}->config($self->skeinpanel->config); | ||||
|     $self->{print}->extra_variables->{"${_}_preset"} = $self->skeinpanel->{options_tabs}{$_}->current_preset->{name} | ||||
|         for qw(print filament printer); | ||||
|      | ||||
|     # select output file | ||||
|     $self->{output_file} = $main::opt{output}; | ||||
|  | @ -634,39 +632,58 @@ sub on_export_failed { | |||
| 
 | ||||
| sub export_stl { | ||||
|     my $self = shift; | ||||
|      | ||||
|     my $print = $self->{print}; | ||||
|          | ||||
|     # select output file | ||||
|     my $output_file = $self->_get_export_file('STL') or return; | ||||
|     Slic3r::Format::STL->write_file($output_file, $self->make_model, binary => 1); | ||||
|     $self->statusbar->SetStatusText("STL file exported to $output_file"); | ||||
| } | ||||
| 
 | ||||
| sub export_amf { | ||||
|     my $self = shift; | ||||
|          | ||||
|     my $output_file = $self->_get_export_file('AMF') or return; | ||||
|     Slic3r::Format::AMF->write_file($output_file, $self->make_model); | ||||
|     $self->statusbar->SetStatusText("AMF file exported to $output_file"); | ||||
| } | ||||
| 
 | ||||
| sub _get_export_file { | ||||
|     my $self = shift; | ||||
|     my ($format) = @_; | ||||
|      | ||||
|     my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; | ||||
|      | ||||
|     my $output_file = $main::opt{output}; | ||||
|     { | ||||
|         $output_file = $print->expanded_output_filepath($output_file); | ||||
|         $output_file =~ s/\.gcode$/.stl/i; | ||||
|         my $dlg = Wx::FileDialog->new($self, 'Save STL file as:', dirname($output_file), | ||||
|         $output_file = $self->{print}->expanded_output_filepath($output_file); | ||||
|         $output_file =~ s/\.gcode$/$suffix/i; | ||||
|         my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), | ||||
|             basename($output_file), $Slic3r::GUI::SkeinPanel::model_wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); | ||||
|         if ($dlg->ShowModal != wxID_OK) { | ||||
|             $dlg->Destroy; | ||||
|             return; | ||||
|             return undef; | ||||
|         } | ||||
|         $output_file = $Slic3r::GUI::SkeinPanel::last_output_file = $dlg->GetPath; | ||||
|         $dlg->Destroy; | ||||
|     } | ||||
|     return $output_file; | ||||
| } | ||||
| 
 | ||||
| sub make_model { | ||||
|     my $self = shift; | ||||
|      | ||||
|     my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); | ||||
|     for my $obj_idx (0 .. $#{$print->objects}) { | ||||
|         for my $copy (@{$print->copies->[$obj_idx]}) { | ||||
|             my $cloned_mesh = $print->objects->[$obj_idx]->mesh->clone; | ||||
|             $cloned_mesh->move(@$copy); | ||||
|             my $vertices_offset = scalar @{$mesh->vertices}; | ||||
|             push @{$mesh->vertices}, @{$cloned_mesh->vertices}; | ||||
|             push @{$mesh->facets}, map [ $_->[0], map $vertices_offset + $_, @$_[-3..-1] ], @{$cloned_mesh->facets}; | ||||
|     my $model = Slic3r::Model->new; | ||||
|     for my $obj_idx (0 .. $#{$self->{print}->objects}) { | ||||
|         my $mesh = $self->{print}->objects->[$obj_idx]->mesh->clone; | ||||
|         $mesh->scale(&Slic3r::SCALING_FACTOR); | ||||
|         my $object = $model->add_object(vertices => $mesh->vertices); | ||||
|         $object->add_volume(facets => $mesh->facets); | ||||
|         for my $copy (@{$self->{print}->copies->[$obj_idx]}) { | ||||
|             $object->add_instance(rotation => 0, offset => [ map unscale $_, @$copy ]); | ||||
|         } | ||||
|     } | ||||
|     $mesh->scale(&Slic3r::SCALING_FACTOR); | ||||
|     $mesh->align_to_origin; | ||||
|     # TODO: $model->align_to_origin; | ||||
|      | ||||
|     Slic3r::Format::STL->write_file($output_file, $mesh, 1); | ||||
|     $self->statusbar->SetStatusText("STL file exported to $output_file"); | ||||
|     return $model; | ||||
| } | ||||
| 
 | ||||
| sub make_thumbnail { | ||||
|  |  | |||
|  | @ -96,7 +96,7 @@ sub do_slice { | |||
|         Slic3r::GUI->save_settings; | ||||
|          | ||||
|         my $print = Slic3r::Print->new(config => $config); | ||||
|         $print->add_object_from_file($input_file); | ||||
|         $print->add_objects_from_file($input_file); | ||||
|         $print->validate; | ||||
| 
 | ||||
|         # select output file | ||||
|  |  | |||
|  | @ -401,7 +401,7 @@ sub build { | |||
|         }, | ||||
|         { | ||||
|             title => 'Advanced', | ||||
|             options => [qw(infill_every_layers fill_angle solid_infill_below_area)], | ||||
|             options => [qw(infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)], | ||||
|         }, | ||||
|     ]); | ||||
|      | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ our @EXPORT_OK = qw( | |||
|     shortest_path collinear scale unscale merge_collinear_lines | ||||
|     rad2deg_dir bounding_box_center line_intersects_any douglas_peucker | ||||
|     polyline_remove_short_segments normal triangle_normal polygon_is_convex | ||||
|     scaled_epsilon | ||||
| ); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -38,6 +39,7 @@ use constant MAX => 1; | |||
| our $parallel_degrees_limit = abs(deg2rad(3)); | ||||
| 
 | ||||
| sub epsilon () { 1E-4 } | ||||
| sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } | ||||
| 
 | ||||
| sub scale   ($) { $_[0] / &Slic3r::SCALING_FACTOR } | ||||
| sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } | ||||
|  |  | |||
|  | @ -4,29 +4,34 @@ use warnings; | |||
| 
 | ||||
| require Exporter; | ||||
| our @ISA = qw(Exporter); | ||||
| our @EXPORT_OK = qw(explode_expolygon explode_expolygons safety_offset offset | ||||
| our @EXPORT_OK = qw(safety_offset offset offset_ex | ||||
|     diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND | ||||
|     JT_SQUARE is_counter_clockwise); | ||||
| 
 | ||||
| use Math::Clipper 1.09 ':all'; | ||||
| use Math::Clipper 1.09 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); | ||||
| use Slic3r::Geometry qw(scale); | ||||
| our $clipper = Math::Clipper->new; | ||||
| 
 | ||||
| sub explode_expolygon { | ||||
|     my ($expolygon) = @_; | ||||
|     return ($expolygon->{outer}, @{ $expolygon->{holes} }); | ||||
| } | ||||
| 
 | ||||
| sub explode_expolygons { | ||||
|     my ($expolygons) = @_; | ||||
|     return map explode_expolygon($_), @$expolygons; | ||||
| } | ||||
| 
 | ||||
| sub safety_offset { | ||||
|     my ($polygons, $factor) = @_; | ||||
|     return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100, JT_MITER, 2); | ||||
| } | ||||
| 
 | ||||
| sub offset { | ||||
|     my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_; | ||||
|     $scale      ||= &Slic3r::SCALING_FACTOR * 1000000; | ||||
|     $joinType   = JT_MITER if !defined $joinType; | ||||
|     $miterLimit ||= 2; | ||||
|      | ||||
|     my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit); | ||||
|     return @$offsets; | ||||
| } | ||||
| 
 | ||||
| sub offset_ex { | ||||
|     # offset polygons and then apply holes to the right contours | ||||
|     return @{ union_ex([ offset(@_) ]) }; | ||||
| } | ||||
| 
 | ||||
| sub diff_ex { | ||||
|     my ($subject, $clip, $safety_offset) = @_; | ||||
|      | ||||
|  |  | |||
|  | @ -346,7 +346,7 @@ sub make_perimeters { | |||
|     { | ||||
|         my @thin_paths = (); | ||||
|         my %properties = ( | ||||
|             role            => EXTR_ROLE_PERIMETER, | ||||
|             role            => EXTR_ROLE_EXTERNAL_PERIMETER, | ||||
|             flow_spacing    => $self->perimeter_flow->spacing, | ||||
|         ); | ||||
|         for (@{ $self->thin_walls }) { | ||||
|  | @ -386,13 +386,30 @@ sub prepare_fill_surfaces { | |||
|     if ($Slic3r::Config->fill_density == 0) { | ||||
|         @surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; | ||||
|     } | ||||
|      | ||||
|     # remove unprintable regions (they would slow down the infill process and also cause | ||||
|     # some weird failures during bridge neighbor detection) | ||||
|     { | ||||
|         my $distance = scale $self->infill_flow->spacing / 2; | ||||
|         @surfaces = map { | ||||
|             my $surface = $_; | ||||
|              | ||||
|             # offset inwards | ||||
|             my @offsets = $surface->expolygon->offset_ex(-$distance); | ||||
|             @offsets = @{union_ex(Math::Clipper::offset([ map @$_, @offsets ], $distance, 100, JT_MITER))}; | ||||
|             map Slic3r::Surface->new( | ||||
|                 expolygon => $_, | ||||
|                 surface_type => $surface->surface_type, | ||||
|             ), @offsets; | ||||
|         } @surfaces; | ||||
|     } | ||||
|          | ||||
|     # turn too small internal regions into solid regions | ||||
|     { | ||||
|         my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls! | ||||
|         my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @surfaces; | ||||
|         $_->surface_type(S_TYPE_INTERNALSOLID) for @small; | ||||
|         Slic3r::debugf "identified %d small surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; | ||||
|         Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; | ||||
|     } | ||||
|      | ||||
|     $self->fill_surfaces([@surfaces]); | ||||
|  |  | |||
							
								
								
									
										112
									
								
								lib/Slic3r/Model.pm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								lib/Slic3r/Model.pm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| package Slic3r::Model; | ||||
| use Moo; | ||||
| 
 | ||||
| use Slic3r::Geometry qw(X Y Z); | ||||
| 
 | ||||
| has 'materials' => (is => 'ro', default => sub { {} }); | ||||
| has 'objects'   => (is => 'ro', default => sub { [] }); | ||||
| 
 | ||||
| sub add_object { | ||||
|     my $self = shift; | ||||
|      | ||||
|     my $object = Slic3r::Model::Object->new(model => $self, @_); | ||||
|     push @{$self->objects}, $object; | ||||
|     return $object; | ||||
| } | ||||
| 
 | ||||
| # flattens everything to a single mesh | ||||
| sub mesh { | ||||
|     my $self = shift; | ||||
|      | ||||
|     my $vertices = []; | ||||
|     my $facets = []; | ||||
|     foreach my $object (@{$self->objects}) { | ||||
|         my @instances = $object->instances ? @{$object->instances} : (undef); | ||||
|         foreach my $instance (@instances) { | ||||
|             my @vertices = @{$object->vertices}; | ||||
|             if ($instance) { | ||||
|                 # save Z coordinates, as rotation and translation discard them | ||||
|                 my @z = map $_->[Z], @vertices; | ||||
|                  | ||||
|                 if ($instance->rotation) { | ||||
|                     # transform vertex coordinates | ||||
|                     my $rad = Slic3r::Geometry::deg2rad($instance->rotation); | ||||
|                     @vertices = Slic3r::Geometry::rotate_points($rad, undef, @vertices); | ||||
|                 } | ||||
|                 @vertices = Slic3r::Geometry::move_points($instance->offset, @vertices); | ||||
|                  | ||||
|                 # reapply Z coordinates | ||||
|                 $vertices[$_][Z] = $z[$_] for 0 .. $#z; | ||||
|             } | ||||
|              | ||||
|             my $v_offset = @$vertices; | ||||
|             push @$vertices, @vertices; | ||||
|             foreach my $volume (@{$object->volumes}) { | ||||
|                 push @$facets, map { | ||||
|                     my $f = [@$_]; | ||||
|                     $f->[$_] += $v_offset for -3..-1; | ||||
|                     $f; | ||||
|                 } @{$volume->facets}; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return Slic3r::TriangleMesh->new( | ||||
|         vertices => $vertices, | ||||
|         facets   => $facets, | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| package Slic3r::Model::Material; | ||||
| use Moo; | ||||
| 
 | ||||
| has 'model'         => (is => 'ro', weak_ref => 1, required => 1); | ||||
| has 'attributes'    => (is => 'rw', default => sub { {} }); | ||||
| 
 | ||||
| package Slic3r::Model::Object; | ||||
| use Moo; | ||||
| 
 | ||||
| has 'model'     => (is => 'ro', weak_ref => 1, required => 1); | ||||
| has 'vertices'  => (is => 'ro', default => sub { [] }); | ||||
| has 'volumes'   => (is => 'ro', default => sub { [] }); | ||||
| has 'instances' => (is => 'rw'); | ||||
| 
 | ||||
| sub add_volume { | ||||
|     my $self = shift; | ||||
|      | ||||
|     my $volume = Slic3r::Model::Volume->new(object => $self, @_); | ||||
|     push @{$self->volumes}, $volume; | ||||
|     return $volume; | ||||
| } | ||||
| 
 | ||||
| sub add_instance { | ||||
|     my $self = shift; | ||||
|      | ||||
|     $self->instances([]) if !defined $self->instances; | ||||
|     push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_); | ||||
|     return $self->instances->[-1]; | ||||
| } | ||||
| 
 | ||||
| package Slic3r::Model::Volume; | ||||
| use Moo; | ||||
| 
 | ||||
| has 'object'        => (is => 'ro', weak_ref => 1, required => 1); | ||||
| has 'material_id'   => (is => 'rw'); | ||||
| has 'facets'        => (is => 'rw', default => sub { [] }); | ||||
| 
 | ||||
| sub mesh { | ||||
|     my $self = shift; | ||||
|     return Slic3r::TriangleMesh->new( | ||||
|         vertices => $self->object->vertices, | ||||
|         facets   => $self->facets, | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| package Slic3r::Model::Instance; | ||||
| use Moo; | ||||
| 
 | ||||
| has 'object'    => (is => 'ro', weak_ref => 1, required => 1); | ||||
| has 'rotation'  => (is => 'rw', default => sub { 0 }); | ||||
| has 'offset'    => (is => 'rw'); | ||||
| 
 | ||||
| 1; | ||||
|  | @ -71,13 +71,7 @@ sub safety_offset { | |||
| 
 | ||||
| sub offset { | ||||
|     my $self = shift; | ||||
|     my ($distance, $scale, $joinType, $miterLimit) = @_; | ||||
|     $scale      ||= &Slic3r::SCALING_FACTOR * 1000000; | ||||
|     $joinType   = JT_MITER if !defined $joinType; | ||||
|     $miterLimit ||= 2; | ||||
|      | ||||
|     my $offsets = Math::Clipper::offset([$self], $distance, $scale, $joinType, $miterLimit); | ||||
|     return map Slic3r::Polygon->new($_), @$offsets; | ||||
|     return map Slic3r::Polygon->new($_), Slic3r::Geometry::Clipper::offset([$self], @_); | ||||
| } | ||||
| 
 | ||||
| # this method subdivides the polygon segments to that no one of them | ||||
|  |  | |||
|  | @ -144,6 +144,7 @@ sub rotate { | |||
|     my $self = shift; | ||||
|     my ($angle, $center) = @_; | ||||
|     @$self = Slic3r::Geometry::rotate_points($angle, $center, @$self); | ||||
|     bless $_, 'Slic3r::Point' for @$self; | ||||
|     return $self; | ||||
| } | ||||
| 
 | ||||
|  | @ -151,6 +152,7 @@ sub translate { | |||
|     my $self = shift; | ||||
|     my ($x, $y) = @_; | ||||
|     @$self = Slic3r::Geometry::move_points([$x, $y], @$self); | ||||
|     bless $_, 'Slic3r::Point' for @$self; | ||||
|     return $self; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,11 +5,12 @@ use File::Basename qw(basename fileparse); | |||
| use File::Spec; | ||||
| use Math::ConvexHull 1.0.4 qw(convex_hull); | ||||
| use Slic3r::ExtrusionPath ':roles'; | ||||
| use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points); | ||||
| use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points nearest_point); | ||||
| use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE); | ||||
| use Time::HiRes qw(gettimeofday tv_interval); | ||||
| 
 | ||||
| has 'config'                 => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1); | ||||
| has 'extra_variables'        => (is => 'rw', default => sub {{}}); | ||||
| has 'objects'                => (is => 'rw', default => sub {[]}); | ||||
| has 'copies'                 => (is => 'rw', default => sub {[]});  # obj_idx => [copies...] | ||||
| has 'total_extrusion_length' => (is => 'rw'); | ||||
|  | @ -42,6 +43,14 @@ sub _trigger_config { | |||
|     # store config in a handy place | ||||
|     $Slic3r::Config = $self->config; | ||||
|      | ||||
|     # legacy with existing config files | ||||
|     $self->config->set('first_layer_height', $self->config->layer_height) | ||||
|         if !$self->config->first_layer_height; | ||||
|     $self->config->set_ifndef('small_perimeter_speed',  $self->config->perimeter_speed); | ||||
|     $self->config->set_ifndef('bridge_speed',           $self->config->infill_speed); | ||||
|     $self->config->set_ifndef('solid_infill_speed',     $self->config->infill_speed); | ||||
|     $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); | ||||
|      | ||||
|     # initialize extruder(s) | ||||
|     $Slic3r::extruders = []; | ||||
|     for my $t (0, map $_-1, map $self->config->get($_), qw(perimeter_extruder infill_extruder support_material_extruder)) { | ||||
|  | @ -71,36 +80,45 @@ sub _trigger_config { | |||
|     # G-code flavors | ||||
|     $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3'; | ||||
|     $self->config->set('extrusion_axis', '')  if $self->config->gcode_flavor eq 'no-extrusion'; | ||||
|      | ||||
|     # legacy with existing config files | ||||
|     $self->config->set_ifndef('small_perimeter_speed',  $self->config->perimeter_speed); | ||||
|     $self->config->set_ifndef('bridge_speed',           $self->config->infill_speed); | ||||
|     $self->config->set_ifndef('solid_infill_speed',     $self->config->infill_speed); | ||||
|     $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); | ||||
| } | ||||
| 
 | ||||
| sub add_object_from_file { | ||||
| sub add_objects_from_file { | ||||
|     my $self = shift; | ||||
|     my ($input_file) = @_; | ||||
|      | ||||
|     my $object; | ||||
|     if ($input_file =~ /\.stl$/i) { | ||||
|         my $mesh = Slic3r::Format::STL->read_file($input_file); | ||||
|     my $model = $input_file =~ /\.stl$/i            ? Slic3r::Format::STL->read_file($input_file) | ||||
|               : $input_file =~ /\.obj$/i            ? Slic3r::Format::OBJ->read_file($input_file) | ||||
|               : $input_file =~ /\.amf(\.xml)?$/i    ? Slic3r::Format::AMF->read_file($input_file) | ||||
|               : die "Input file must have .stl, .obj or .amf(.xml) extension\n"; | ||||
|      | ||||
|     my @print_objects = $self->add_model($model); | ||||
|     $_->input_file($input_file) for @print_objects; | ||||
| } | ||||
| 
 | ||||
| sub add_model { | ||||
|     my $self = shift; | ||||
|     my ($model) = @_; | ||||
|      | ||||
|     my @print_objects = (); | ||||
|     foreach my $object (@{ $model->objects }) { | ||||
|         my $mesh = $object->volumes->[0]->mesh; | ||||
|         $mesh->check_manifoldness; | ||||
|         $object = $self->add_object_from_mesh($mesh); | ||||
|     } elsif ($input_file =~ /\.obj$/i) { | ||||
|         my $mesh = Slic3r::Format::OBJ->read_file($input_file); | ||||
|         $mesh->check_manifoldness; | ||||
|         $object = $self->add_object_from_mesh($mesh); | ||||
|     } elsif ( $input_file =~ /\.amf(\.xml)?$/i) { | ||||
|         my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file); | ||||
|         $_->check_manifoldness for values %$meshes_by_material; | ||||
|         $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]); | ||||
|     } else { | ||||
|         die "Input file must have .stl, .obj or .amf(.xml) extension\n"; | ||||
|          | ||||
|         if ($object->instances) { | ||||
|             # we ignore the per-instance rotation currently and only  | ||||
|             # consider the first one | ||||
|             $mesh->rotate($object->instances->[0]->rotation); | ||||
|         } | ||||
|          | ||||
|         push @print_objects, $self->add_object_from_mesh($mesh); | ||||
|          | ||||
|         if ($object->instances) { | ||||
|             # replace the default [0,0] instance with the custom ones | ||||
|             @{$self->copies->[-1]} = map [ scale $_->offset->[X], scale $_->offset->[X] ], @{$object->instances}; | ||||
|         } | ||||
|     } | ||||
|     $object->input_file($input_file); | ||||
|     return $object; | ||||
|      | ||||
|     return @print_objects; | ||||
| } | ||||
| 
 | ||||
| sub add_object_from_mesh { | ||||
|  | @ -112,11 +130,9 @@ sub add_object_from_mesh { | |||
|     $mesh->align_to_origin; | ||||
|      | ||||
|     # initialize print object | ||||
|     my @size = $mesh->size; | ||||
|     my $object = Slic3r::Print::Object->new( | ||||
|         mesh     => $mesh, | ||||
|         x_length => $size[X], | ||||
|         y_length => $size[Y], | ||||
|         mesh => $mesh, | ||||
|         size => [ $mesh->size ], | ||||
|     ); | ||||
|      | ||||
|     push @{$self->objects}, $object; | ||||
|  | @ -201,8 +217,8 @@ sub duplicate { | |||
|         for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) { | ||||
|             for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) { | ||||
|                 push @{$self->copies->[0]}, [ | ||||
|                     ($object->x_length + $dist) * ($x_copy-1), | ||||
|                     ($object->y_length + $dist) * ($y_copy-1), | ||||
|                     ($object->size->[X] + $dist) * ($x_copy-1), | ||||
|                     ($object->size->[Y] + $dist) * ($y_copy-1), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
|  | @ -220,8 +236,8 @@ sub arrange_objects { | |||
|     my $total_parts = scalar map @$_, @{$self->copies}; | ||||
|     my $partx = my $party = 0; | ||||
|     foreach my $object (@{$self->objects}) { | ||||
|         $partx = $object->x_length if $object->x_length > $partx; | ||||
|         $party = $object->y_length if $object->y_length > $party; | ||||
|         $partx = $object->size->[X] if $object->size->[X] > $partx; | ||||
|         $party = $object->size->[Y] if $object->size->[Y] > $party; | ||||
|     } | ||||
|      | ||||
|     # object distance is max(duplicate_distance, clearance_radius) | ||||
|  | @ -246,9 +262,9 @@ sub bounding_box { | |||
|         foreach my $copy (@{$self->copies->[$obj_idx]}) { | ||||
|             push @points, | ||||
|                 [ $copy->[X], $copy->[Y] ], | ||||
|                 [ $copy->[X] + $object->x_length, $copy->[Y] ], | ||||
|                 [ $copy->[X] + $object->x_length, $copy->[Y] + $object->y_length ], | ||||
|                 [ $copy->[X], $copy->[Y] + $object->y_length ]; | ||||
|                 [ $copy->[X] + $object->size->[X], $copy->[Y] ], | ||||
|                 [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ], | ||||
|                 [ $copy->[X], $copy->[Y] + $object->size->[Y] ]; | ||||
|         } | ||||
|     } | ||||
|     return Slic3r::Geometry::bounding_box(\@points); | ||||
|  | @ -497,7 +513,7 @@ sub make_skirt { | |||
|     my @skirt = (); | ||||
|     for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) { | ||||
|         my $distance = scale ($Slic3r::Config->skirt_distance + ($flow->spacing * $i)); | ||||
|         my $outline = offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND); | ||||
|         my $outline = Math::Clipper::offset([$convex_hull], $distance, &Slic3r::SCALING_FACTOR * 100, JT_ROUND); | ||||
|         push @skirt, Slic3r::ExtrusionLoop->pack( | ||||
|             polygon => Slic3r::Polygon->new(@{$outline->[0]}), | ||||
|             role => EXTR_ROLE_SKIRT, | ||||
|  | @ -569,15 +585,6 @@ sub write_gcode { | |||
|     print $fh $gcodegen->set_tool(0); | ||||
|     print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; | ||||
|      | ||||
|     # this spits out some platic at start from each extruder when they are first used; | ||||
|     # the primary extruder will compensate by the normal retraction length, while  | ||||
|     # the others will compensate for their toolchange length + restart extra. | ||||
|     # this is a temporary solution as all extruders should use some kind of skirt  | ||||
|     # to be put into a consistent state. | ||||
|     $_->retracted($_->retract_length_toolchange + $_->retract_restart_extra_toolchange) | ||||
|         for @{$Slic3r::extruders}[1 .. $#{$Slic3r::extruders}]; | ||||
|     $gcodegen->retract; | ||||
|      | ||||
|     # write start commands to file | ||||
|     printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1), | ||||
|         if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M190/i; | ||||
|  | @ -647,6 +654,7 @@ sub write_gcode { | |||
|          | ||||
|         # extrude brim | ||||
|         if ($layer_id == 0 && !$brim_done) { | ||||
|             $gcode .= $gcodegen->set_tool($Slic3r::Config->support_material_extruder-1); | ||||
|             $gcodegen->shift_x($shift[X]); | ||||
|             $gcodegen->shift_y($shift[Y]); | ||||
|             $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; | ||||
|  | @ -811,6 +819,7 @@ sub expanded_output_filepath { | |||
|     return $Slic3r::Config->replace_options($path, { | ||||
|         input_filename      => $input_filename, | ||||
|         input_filename_base => $input_filename_base, | ||||
|         %{ $self->extra_variables }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,8 +8,7 @@ use Slic3r::Surface ':types'; | |||
| 
 | ||||
| has 'input_file'        => (is => 'rw', required => 0); | ||||
| has 'mesh'              => (is => 'rw', required => 0); | ||||
| has 'x_length'          => (is => 'rw', required => 1); | ||||
| has 'y_length'          => (is => 'rw', required => 1); | ||||
| has 'size'              => (is => 'rw', required => 1); | ||||
| 
 | ||||
| has 'layers' => ( | ||||
|     traits  => ['Array'], | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Alessandro Ranellucci
						Alessandro Ranellucci