123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- package Slic3r::Model;
- use Moo;
- use List::Util qw(first max);
- use Slic3r::Geometry qw(X Y Z MIN move_points);
- has 'materials' => (is => 'ro', default => sub { {} });
- has 'objects' => (is => 'ro', default => sub { [] });
- has '_bounding_box' => (is => 'rw');
- sub read_from_file {
- my $class = shift;
- my ($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";
-
- $_->input_file($input_file) for @{$model->objects};
- return $model;
- }
- sub merge {
- my $class = shift;
- my @models = @_;
-
- my $new_model = $class->new;
- foreach my $model (@models) {
- # merge material attributes (should we rename them in case of duplicates?)
- $new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} })
- for keys %{$model->materials};
-
- foreach my $object (@{$model->objects}) {
- my $new_object = $new_model->add_object(
- input_file => $object->input_file,
- vertices => $object->vertices,
- config => $object->config,
- layer_height_ranges => $object->layer_height_ranges,
- );
-
- $new_object->add_volume(
- material_id => $_->material_id,
- facets => $_->facets,
- ) for @{$object->volumes};
-
- $new_object->add_instance(
- offset => $_->offset,
- rotation => $_->rotation,
- ) for @{ $object->instances // [] };
- }
- }
-
- return $new_model;
- }
- sub add_object {
- my $self = shift;
-
- my $object = Slic3r::Model::Object->new(model => $self, @_);
- push @{$self->objects}, $object;
- $self->_bounding_box(undef);
- return $object;
- }
- sub set_material {
- my $self = shift;
- my ($material_id, $attributes) = @_;
-
- return $self->materials->{$material_id} = Slic3r::Model::Region->new(
- model => $self,
- attributes => $attributes || {},
- );
- }
- sub arrange_objects {
- my $self = shift;
- my ($config) = @_;
-
- # do we have objects with no position?
- if (first { !defined $_->instances } @{$self->objects}) {
- # we shall redefine positions for all objects
-
- my ($copies, @positions) = $self->_arrange(
- config => $config,
- items => $self->objects,
- );
-
- # apply positions to objects
- foreach my $object (@{$self->objects}) {
- $object->align_to_origin;
-
- $object->instances([]);
- $object->add_instance(
- offset => $_,
- rotation => 0,
- ) for splice @positions, 0, $copies;
- }
-
- } else {
- # we only have objects with defined position
-
- # align the whole model to origin as it is
- $self->align_to_origin;
-
- # arrange this model as a whole
- my ($copies, @positions) = $self->_arrange(
- config => $config,
- items => [$self],
- );
-
- # apply positions to objects by translating the current positions
- foreach my $object (@{$self->objects}) {
- my @old_instances = @{$object->instances};
- $object->instances([]);
- foreach my $instance (@old_instances) {
- $object->add_instance(
- offset => $_,
- rotation => $instance->rotation,
- scaling_factor => $instance->scaling_factor,
- ) for move_points($instance->offset, @positions);
- }
- }
- }
- }
- sub _arrange {
- my $self = shift;
- my %params = @_;
-
- my $config = $params{config};
- my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size()
-
- if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) {
- if (@items > 1) {
- die "Grid duplication is not supported with multiple objects\n";
- }
- my @positions = ();
- my $size = $items[0]->size;
- my $dist = $config->duplicate_distance;
- for my $x_copy (1..$config->duplicate_grid->[X]) {
- for my $y_copy (1..$config->duplicate_grid->[Y]) {
- push @positions, [
- ($size->[X] + $dist) * ($x_copy-1),
- ($size->[Y] + $dist) * ($y_copy-1),
- ];
- }
- }
- return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions;
- } else {
- my $total_parts = $config->duplicate * @items;
- my @sizes = map $_->size, @items;
- my $partx = max(map $_->[X], @sizes);
- my $party = max(map $_->[Y], @sizes);
- return $config->duplicate,
- Slic3r::Geometry::arrange
- ($total_parts, $partx, $party, (map $_, @{$config->bed_size}),
- $config->min_object_distance, $config);
- }
- }
- sub vertices {
- my $self = shift;
- return [ map @{$_->vertices}, @{$self->objects} ];
- }
- sub used_vertices {
- my $self = shift;
- return [ map @{$_->used_vertices}, @{$self->objects} ];
- }
- sub size {
- my $self = shift;
- return $self->bounding_box->size;
- }
- sub bounding_box {
- my $self = shift;
-
- if (!defined $self->_bounding_box) {
- $self->_bounding_box(Slic3r::Geometry::BoundingBox->new_from_points_3D($self->used_vertices));
- }
- return $self->_bounding_box;
- }
- sub align_to_origin {
- my $self = shift;
-
- # calculate the displacements needed to
- # have lowest value for each axis at coordinate 0
- {
- my $bb = $self->bounding_box;
- $self->move(map -$bb->extents->[$_][MIN], X,Y,Z);
- }
-
- # align all instances to 0,0 as well
- {
- my @instances = map @{$_->instances}, @{$self->objects};
- my @extents = Slic3r::Geometry::bounding_box_3D([ map $_->offset, @instances ]);
- $_->offset->translate(-$extents[X][MIN], -$extents[Y][MIN]) for @instances;
- }
- }
- sub scale {
- my $self = shift;
- $_->scale(@_) for @{$self->objects};
- $self->_bounding_box->scale(@_) if defined $self->_bounding_box;
- }
- sub move {
- my $self = shift;
- my @shift = @_;
-
- $_->move(@shift) for @{$self->objects};
- $self->_bounding_box->translate(@shift) if defined $self->_bounding_box;
- }
- # flattens everything to a single mesh
- sub mesh {
- my $self = shift;
-
- my @meshes = ();
- foreach my $object (@{$self->objects}) {
- my @instances = $object->instances ? @{$object->instances} : (undef);
- foreach my $instance (@instances) {
- my $mesh = $object->mesh->clone;
- if ($instance) {
- $mesh->rotate($instance->rotation);
- $mesh->scale($instance->scaling_factor);
- $mesh->align_to_origin;
- $mesh->move(@{$instance->offset});
- }
- push @meshes, $mesh;
- }
- }
-
- return Slic3r::TriangleMesh->merge(@meshes);
- }
- # this method splits objects into multiple distinct objects by walking their meshes
- sub split_meshes {
- my $self = shift;
-
- my @objects = @{$self->objects};
- @{$self->objects} = ();
-
- foreach my $object (@objects) {
- if (@{$object->volumes} > 1) {
- # We can't split meshes if there's more than one material, because
- # we can't group the resulting meshes by object afterwards
- push @{$self->objects}, $object;
- next;
- }
-
- my $volume = $object->volumes->[0];
- foreach my $mesh ($volume->mesh->split_mesh) {
- my $new_object = $self->add_object(
- input_file => $object->input_file,
- config => $object->config,
- layer_height_ranges => $object->layer_height_ranges,
- );
- $new_object->add_volume(
- vertices => $mesh->vertices,
- facets => $mesh->facets,
- material_id => $volume->material_id,
- );
-
- # let's now align the new object to the origin and put its displacement
- # (extents) in the instances info
- my $bb = $mesh->bounding_box;
- $new_object->align_to_origin;
-
- # add one instance per original instance applying the displacement
- $new_object->add_instance(
- offset => [ $_->offset->[X] + $bb->x_min, $_->offset->[Y] + $bb->y_min ],
- rotation => $_->rotation,
- scaling_factor => $_->scaling_factor,
- ) for @{ $object->instances // [] };
- }
- }
- }
- sub print_info {
- my $self = shift;
- $_->print_info for @{$self->objects};
- }
- sub get_material_name {
- my $self = shift;
- my ($material_id) = @_;
-
- my $name;
- if (exists $self->materials->{$material_id}) {
- $name //= $self->materials->{$material_id}->attributes->{$_} for qw(Name name);
- } elsif ($material_id eq '_') {
- $name = 'Default material';
- }
- $name //= $material_id;
- return $name;
- }
- package Slic3r::Model::Region;
- use Moo;
- has 'model' => (is => 'ro', weak_ref => 1, required => 1);
- has 'attributes' => (is => 'rw', default => sub { {} });
- package Slic3r::Model::Object;
- use Moo;
- use File::Basename qw(basename);
- use List::Util qw(first sum);
- use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D);
- use Storable qw(dclone);
- has 'input_file' => (is => 'rw');
- 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');
- has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
- has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
- has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => extruder_idx }
- has 'mesh_stats' => (is => 'rw');
- has '_bounding_box' => (is => 'rw');
- sub add_volume {
- my $self = shift;
- my %args = @_;
-
- if (my $vertices = delete $args{vertices}) {
- my $v_offset = @{$self->vertices};
- push @{$self->vertices}, @$vertices;
-
- @{$args{facets}} = map {
- my $f = [@$_];
- $f->[$_] += $v_offset for -3..-1;
- $f;
- } @{$args{facets}};
- }
-
- my $volume = Slic3r::Model::Volume->new(object => $self, %args);
- push @{$self->volumes}, $volume;
- $self->_bounding_box(undef);
- $self->model->_bounding_box(undef);
- return $volume;
- }
- sub add_instance {
- my $self = shift;
-
- $self->instances([]) if !defined $self->instances;
- push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_);
- $self->model->_bounding_box(undef);
- return $self->instances->[-1];
- }
- sub mesh {
- my $self = shift;
-
- # this mesh won't be suitable for check_manifoldness as multiple
- # facets from different volumes may use the same vertices
- return Slic3r::TriangleMesh->new(
- vertices => $self->vertices,
- facets => [ map @{$_->facets}, @{$self->volumes} ],
- );
- }
- sub used_vertices {
- my $self = shift;
- return [ map $self->vertices->[$_], map @$_, map @{$_->facets}, @{$self->volumes} ];
- }
- sub size {
- my $self = shift;
- return $self->bounding_box->size;
- }
- sub center {
- my $self = shift;
- return $self->bounding_box->center;
- }
- sub bounding_box {
- my $self = shift;
-
- if (!defined $self->_bounding_box) {
- $self->_bounding_box(Slic3r::Geometry::BoundingBox->new_from_points_3D($self->used_vertices));
- }
- return $self->_bounding_box;
- }
- sub align_to_origin {
- my $self = shift;
-
- # calculate the displacements needed to
- # have lowest value for each axis at coordinate 0
- my $bb = $self->bounding_box;
- my @shift = map -$bb->extents->[$_][MIN], X,Y,Z;
- $self->move(@shift);
- return @shift;
- }
- sub move {
- my $self = shift;
- my @shift = @_;
-
- @{$self->vertices} = move_points_3D([ @shift ], @{$self->vertices});
- $self->_bounding_box->translate(@shift) if defined $self->_bounding_box;
- }
- sub scale {
- my $self = shift;
- my ($factor) = @_;
- return if $factor == 1;
-
- # transform vertex coordinates
- foreach my $vertex (@{$self->vertices}) {
- $vertex->[$_] *= $factor for X,Y,Z;
- }
-
- $self->_bounding_box->scale($factor) if defined $self->_bounding_box;
- $self->mesh_stats->{volume} *= ($factor**3) if defined $self->mesh_stats;
- }
- sub rotate {
- my $self = shift;
- my ($deg) = @_;
- return if $deg == 0;
-
- my $rad = Slic3r::Geometry::deg2rad($deg);
-
- # transform vertex coordinates
- foreach my $vertex (@{$self->vertices}) {
- @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]);
- }
-
- $self->_bounding_box(undef);
- }
- sub materials_count {
- my $self = shift;
-
- my %materials = map { $_->material_id // '_default' => 1 } @{$self->volumes};
- return scalar keys %materials;
- }
- sub unique_materials {
- my $self = shift;
-
- my %materials = ();
- $materials{ $_->material_id // '_' } = 1 for @{$self->volumes};
- return sort keys %materials;
- }
- sub facets_count {
- my $self = shift;
- return sum(map $_->facets_count, @{$self->volumes});
- }
- sub check_manifoldness {
- my $self = shift;
- return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1;
- }
- sub needed_repair {
- my $self = shift;
-
- return $self->mesh_stats
- && first { $self->mesh_stats->{$_} > 0 }
- qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges);
- }
- sub print_info {
- my $self = shift;
-
- printf "Info about %s:\n", basename($self->input_file);
- printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->size};
- if (my $stats = $self->mesh_stats) {
- printf " number of facets: %d\n", $stats->{number_of_facets};
- printf " number of shells: %d\n", $stats->{number_of_parts};
- printf " volume: %.3f\n", $stats->{volume};
- if ($self->needed_repair) {
- printf " needed repair: yes\n";
- printf " degenerate facets: %d\n", $stats->{degenerate_facets};
- printf " edges fixed: %d\n", $stats->{edges_fixed};
- printf " facets removed: %d\n", $stats->{facets_removed};
- printf " facets added: %d\n", $stats->{facets_added};
- printf " facets reversed: %d\n", $stats->{facets_reversed};
- printf " backwards edges: %d\n", $stats->{backwards_edges};
- } else {
- printf " needed repair: no\n";
- }
- } else {
- printf " number of facets: %d\n", scalar(map @{$_->facets}, @{$self->volumes});
- }
- }
- sub clone { dclone($_[0]) }
- 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,
- );
- }
- sub facets_count {
- my $self = shift;
- return scalar(@{$self->facets}); # TODO: optimize in XS
- }
- package Slic3r::Model::Instance;
- use Moo;
- has 'object' => (is => 'ro', weak_ref => 1, required => 1);
- has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point
- has 'scaling_factor' => (is => 'rw', default => sub { 1 });
- has 'offset' => (is => 'rw'); # must be Slic3r::Point object
- 1;
|