mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-30 12:11:15 -06:00
Merge branch 'master' into sender
Conflicts: Build.PL
This commit is contained in:
commit
3ae6f2630e
106 changed files with 2262 additions and 994 deletions
|
|
@ -1,4 +1,4 @@
|
|||
package Slic3r::GUI::PreviewCanvas;
|
||||
package Slic3r::GUI::3DScene::Base;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
|
|||
use Wx::GLCanvas qw(:all);
|
||||
|
||||
__PACKAGE__->mk_accessors( qw(_quat _dirty init
|
||||
enable_cutting
|
||||
enable_picking
|
||||
enable_moving
|
||||
on_hover
|
||||
|
|
@ -21,7 +22,6 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||
on_right_click
|
||||
on_move
|
||||
volumes
|
||||
print
|
||||
_sphi _stheta
|
||||
cutting_plane_z
|
||||
cut_lines_vertices
|
||||
|
|
@ -40,12 +40,12 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||
_zoom
|
||||
) );
|
||||
|
||||
use constant TRACKBALLSIZE => 0.8;
|
||||
use constant TRACKBALLSIZE => 0.8;
|
||||
use constant TURNTABLE_MODE => 1;
|
||||
use constant GROUND_Z => -0.02;
|
||||
use constant DEFAULT_COLOR => [1,1,0];
|
||||
use constant SELECTED_COLOR => [0,1,0,1];
|
||||
use constant HOVER_COLOR => [0.4,0.9,0,1];
|
||||
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
{
|
||||
|
|
@ -135,6 +135,17 @@ sub mouse_event {
|
|||
if ($self->enable_picking) {
|
||||
$self->deselect_volumes;
|
||||
$self->select_volume($volume_idx);
|
||||
|
||||
if ($volume_idx != -1) {
|
||||
my $group_id = $self->volumes->[$volume_idx]->select_group_id;
|
||||
my @volumes;
|
||||
if ($group_id != -1) {
|
||||
$self->select_volume($_)
|
||||
for grep $self->volumes->[$_]->select_group_id == $group_id,
|
||||
0..$#{$self->volumes};
|
||||
}
|
||||
}
|
||||
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
|
|
@ -163,8 +174,13 @@ sub mouse_event {
|
|||
# get volume being dragged
|
||||
my $volume = $self->volumes->[$self->_drag_volume_idx];
|
||||
|
||||
# get all volumes belonging to the same group but only having the same instance_idx
|
||||
my @volumes = grep $_->group_id == $volume->group_id && $_->instance_idx == $volume->instance_idx, @{$self->volumes};
|
||||
# get all volumes belonging to the same group, if any
|
||||
my @volumes;
|
||||
if ($volume->drag_group_id == -1) {
|
||||
@volumes = ($volume);
|
||||
} else {
|
||||
@volumes = grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
|
||||
}
|
||||
|
||||
# apply new temporary volume origin and ignore Z
|
||||
$_->origin->translate($vector->x, $vector->y, 0) for @volumes; #,,
|
||||
|
|
@ -209,8 +225,17 @@ sub mouse_event {
|
|||
$self->_drag_start_xy($pos);
|
||||
}
|
||||
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
|
||||
if ($self->on_move && defined $self->_drag_volume_idx) {
|
||||
$self->on_move->($self->_drag_volume_idx) if $self->_dragged;
|
||||
if ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
|
||||
# get all volumes belonging to the same group, if any
|
||||
my @volume_idxs;
|
||||
my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
|
||||
if ($group_id == -1) {
|
||||
@volume_idxs = ($self->_drag_volume_idx);
|
||||
} else {
|
||||
@volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
|
||||
0..$#{$self->volumes};
|
||||
}
|
||||
$self->on_move->(@volume_idxs);
|
||||
}
|
||||
$self->_drag_volume_idx(undef);
|
||||
$self->_drag_start_pos(undef);
|
||||
|
|
@ -256,7 +281,7 @@ sub zoom_to_volume {
|
|||
my ($self, $volume_idx) = @_;
|
||||
|
||||
my $volume = $self->volumes->[$volume_idx];
|
||||
my $bb = $volume->bounding_box;
|
||||
my $bb = $volume->transformed_bounding_box;
|
||||
$self->zoom_to_bounding_box($bb);
|
||||
}
|
||||
|
||||
|
|
@ -269,7 +294,7 @@ sub volumes_bounding_box {
|
|||
my ($self) = @_;
|
||||
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
|
||||
$bb->merge($_->bounding_box) for @{$self->volumes};
|
||||
$bb->merge($_->transformed_bounding_box) for @{$self->volumes};
|
||||
return $bb;
|
||||
}
|
||||
|
||||
|
|
@ -348,59 +373,6 @@ sub set_bed_shape {
|
|||
$self->origin(Slic3r::Pointf->new(0,0));
|
||||
}
|
||||
|
||||
sub load_object {
|
||||
my ($self, $object, $all_instances) = @_;
|
||||
|
||||
my $z_min = $object->raw_bounding_box->z_min;
|
||||
|
||||
# color mesh(es) by material
|
||||
my @materials = ();
|
||||
|
||||
# sort volumes: non-modifiers first
|
||||
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
|
||||
my @volumes_idx = ();
|
||||
my $group_id = $#{$self->volumes} + 1;
|
||||
foreach my $volume (@volumes) {
|
||||
my @instance_idxs = $all_instances ? (0..$#{$object->instances}) : (0);
|
||||
foreach my $instance_idx (@instance_idxs) {
|
||||
my $instance = $object->instances->[$instance_idx];
|
||||
my $mesh = $volume->mesh->clone;
|
||||
$instance->transform_mesh($mesh);
|
||||
|
||||
my $material_id = $volume->material_id // '_';
|
||||
my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;
|
||||
if (!defined $color_idx) {
|
||||
push @materials, $material_id;
|
||||
$color_idx = $#materials;
|
||||
}
|
||||
|
||||
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
||||
push @$color, $volume->modifier ? 0.5 : 1;
|
||||
push @{$self->volumes}, my $v = Slic3r::GUI::PreviewCanvas::Volume->new(
|
||||
group_id => $group_id,
|
||||
instance_idx => $instance_idx,
|
||||
mesh => $mesh,
|
||||
color => $color,
|
||||
origin => Slic3r::Pointf3->new(0,0,-$z_min),
|
||||
);
|
||||
push @volumes_idx, $#{$self->volumes};
|
||||
|
||||
{
|
||||
my $vertices = $mesh->vertices;
|
||||
my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
|
||||
$v->verts(OpenGL::Array->new_list(GL_FLOAT, @verts));
|
||||
}
|
||||
|
||||
{
|
||||
my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
|
||||
$v->norms(OpenGL::Array->new_list(GL_FLOAT, @norms));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @volumes_idx;
|
||||
}
|
||||
|
||||
sub deselect_volumes {
|
||||
my ($self) = @_;
|
||||
$_->selected(0) for @{$self->volumes};
|
||||
|
|
@ -422,6 +394,7 @@ sub SetCuttingPlane {
|
|||
my @verts = ();
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
next if !$volume->mesh;
|
||||
my $expolygons = $volume->mesh->slice([ $z - $volume->origin->z ])->[0];
|
||||
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
|
||||
|
||||
|
|
@ -698,7 +671,13 @@ sub Render {
|
|||
$_->hover(0) for @{$self->volumes};
|
||||
if ($volume_idx <= $#{$self->volumes}) {
|
||||
$self->_hover_volume_idx($volume_idx);
|
||||
|
||||
$self->volumes->[$volume_idx]->hover(1);
|
||||
my $group_id = $self->volumes->[$volume_idx]->select_group_id;
|
||||
if ($group_id != -1) {
|
||||
$_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
|
||||
}
|
||||
|
||||
$self->on_hover->($volume_idx) if $self->on_hover;
|
||||
}
|
||||
}
|
||||
|
|
@ -833,73 +812,6 @@ sub draw_volumes {
|
|||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (defined($self->print) && !$fakecolor) {
|
||||
my $tess = gluNewTess();
|
||||
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
|
||||
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
foreach my $layer (@{$object->layers}) {
|
||||
my $gap = 0;
|
||||
my $top_z = $layer->print_z;
|
||||
my $bottom_z = $layer->print_z - $layer->height + $gap;
|
||||
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
glPushMatrix();
|
||||
glTranslatef(map unscale($_), @$copy, 0);
|
||||
|
||||
foreach my $slice (@{$layer->slices}) {
|
||||
glColor3f(@{COLORS->[0]});
|
||||
gluTessBeginPolygon($tess);
|
||||
glNormal3f(0,0,1);
|
||||
foreach my $polygon (@$slice) {
|
||||
gluTessBeginContour($tess);
|
||||
gluTessVertex_p($tess, (map unscale($_), @$_), $layer->print_z) for @$polygon;
|
||||
gluTessEndContour($tess);
|
||||
}
|
||||
gluTessEndPolygon($tess);
|
||||
|
||||
foreach my $polygon (@$slice) {
|
||||
foreach my $line (@{$polygon->lines}) {
|
||||
if (0) {
|
||||
glLineWidth(1);
|
||||
glColor3f(0,0,0);
|
||||
glBegin(GL_LINES);
|
||||
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
|
||||
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
glLineWidth(0);
|
||||
glColor3f(@{COLORS->[0]});
|
||||
glBegin(GL_QUADS);
|
||||
# We'll use this for the middle normal when using 4 quads:
|
||||
#my $xy_normal = $line->normal;
|
||||
#$_xynormal->scale(1/$line->length);
|
||||
glNormal3f(0,0,-1);
|
||||
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
|
||||
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
|
||||
glNormal3f(0,0,1);
|
||||
glVertex3f((map unscale($_), @{$line->b}), $top_z);
|
||||
glVertex3f((map unscale($_), @{$line->a}), $top_z);
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glPopMatrix(); # copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gluDeleteTess($tess);
|
||||
return;
|
||||
}
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
|
|
@ -908,10 +820,6 @@ sub draw_volumes {
|
|||
glPushMatrix();
|
||||
glTranslatef(@{$volume->origin});
|
||||
|
||||
glVertexPointer_p(3, $volume->verts);
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
glNormalPointer_p($volume->norms);
|
||||
if ($fakecolor) {
|
||||
my $r = ($volume_idx & 0x000000FF) >> 0;
|
||||
my $g = ($volume_idx & 0x0000FF00) >> 8;
|
||||
|
|
@ -924,7 +832,49 @@ sub draw_volumes {
|
|||
} else {
|
||||
glColor4f(@{ $volume->color });
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLES, 0, $volume->verts->elements / 3);
|
||||
|
||||
my @sorted_z = ();
|
||||
my ($min_z, $max_z);
|
||||
if ($volume->range && $volume->offsets) {
|
||||
@sorted_z = sort { $a <=> $b } keys %{$volume->offsets};
|
||||
|
||||
($min_z, $max_z) = @{$volume->range};
|
||||
$min_z = first { $_ >= $min_z } @sorted_z;
|
||||
$max_z = first { $_ > $max_z } @sorted_z;
|
||||
}
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
if ($volume->qverts) {
|
||||
my ($min_offset, $max_offset);
|
||||
if (defined $min_z) {
|
||||
$min_offset = $volume->offsets->{$min_z}->[0];
|
||||
}
|
||||
if (defined $max_z) {
|
||||
$max_offset = $volume->offsets->{$max_z}->[0];
|
||||
}
|
||||
$min_offset //= 0;
|
||||
$max_offset //= $volume->qverts->size;
|
||||
|
||||
glVertexPointer_c(3, GL_FLOAT, 0, $volume->qverts->verts_ptr);
|
||||
glNormalPointer_c(GL_FLOAT, 0, $volume->qverts->norms_ptr);
|
||||
glDrawArrays(GL_QUADS, $min_offset / 3, ($max_offset-$min_offset) / 3);
|
||||
}
|
||||
|
||||
if ($volume->tverts) {
|
||||
my ($min_offset, $max_offset);
|
||||
if (defined $min_z) {
|
||||
$min_offset = $volume->offsets->{$min_z}->[1];
|
||||
}
|
||||
if (defined $max_z) {
|
||||
$max_offset = $volume->offsets->{$max_z}->[1];
|
||||
}
|
||||
$min_offset //= 0;
|
||||
$max_offset //= $volume->tverts->size;
|
||||
|
||||
glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr);
|
||||
glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr);
|
||||
glDrawArrays(GL_TRIANGLES, $min_offset / 3, ($max_offset-$min_offset) / 3);
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
|
@ -940,25 +890,352 @@ sub draw_volumes {
|
|||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::PreviewCanvas::Volume;
|
||||
package Slic3r::GUI::3DScene::Volume;
|
||||
use Moo;
|
||||
|
||||
has 'mesh' => (is => 'ro', required => 1);
|
||||
has 'color' => (is => 'ro', required => 1);
|
||||
has 'group_id' => (is => 'ro', required => 1);
|
||||
has 'instance_idx' => (is => 'ro', default => sub { 0 });
|
||||
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
|
||||
has 'verts' => (is => 'rw');
|
||||
has 'norms' => (is => 'rw');
|
||||
has 'selected' => (is => 'rw', default => sub { 0 });
|
||||
has 'hover' => (is => 'rw', default => sub { 0 });
|
||||
has 'bounding_box' => (is => 'ro', required => 1);
|
||||
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
|
||||
has 'color' => (is => 'ro', required => 1);
|
||||
has 'select_group_id' => (is => 'rw', default => sub { -1 });
|
||||
has 'drag_group_id' => (is => 'rw', default => sub { -1 });
|
||||
has 'selected' => (is => 'rw', default => sub { 0 });
|
||||
has 'hover' => (is => 'rw', default => sub { 0 });
|
||||
has 'range' => (is => 'rw');
|
||||
|
||||
sub bounding_box {
|
||||
# geometric data
|
||||
has 'qverts' => (is => 'rw'); # GLVertexArray object
|
||||
has 'tverts' => (is => 'rw'); # GLVertexArray object
|
||||
has 'mesh' => (is => 'rw'); # only required for cut contours
|
||||
has 'offsets' => (is => 'rw'); # [ z => [ qverts_idx, tverts_idx ] ]
|
||||
|
||||
sub transformed_bounding_box {
|
||||
my ($self) = @_;
|
||||
|
||||
my $bb = $self->mesh->bounding_box;
|
||||
my $bb = $self->bounding_box;
|
||||
$bb->translate(@{$self->origin});
|
||||
return $bb;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::3DScene;
|
||||
use base qw(Slic3r::GUI::3DScene::Base);
|
||||
|
||||
use OpenGL qw(:glconstants :gluconstants :glufunctions);
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(scale unscale epsilon);
|
||||
use Slic3r::Print::State ':steps';
|
||||
|
||||
use constant COLORS => [ [1,1,0,1], [1,0.5,0.5,1], [0.5,1,0.5,1], [0.5,0.5,1,1] ];
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(
|
||||
color_by
|
||||
select_by
|
||||
drag_by
|
||||
volumes_by_object
|
||||
_objects_by_volumes
|
||||
));
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->color_by('volume'); # object | volume
|
||||
$self->select_by('object'); # object | volume | instance
|
||||
$self->drag_by('instance'); # object | instance
|
||||
$self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ]
|
||||
$self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ]
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub load_object {
|
||||
my ($self, $model, $obj_idx, $instance_idxs) = @_;
|
||||
|
||||
my $model_object;
|
||||
if ($model->isa('Slic3r::Model::Object')) {
|
||||
$model_object = $model;
|
||||
$model = $model_object->model;
|
||||
$obj_idx = 0;
|
||||
} else {
|
||||
$model_object = $model->get_object($obj_idx);
|
||||
}
|
||||
|
||||
$instance_idxs ||= [0..$#{$model_object->instances}];
|
||||
|
||||
my @volumes_idx = ();
|
||||
foreach my $volume_idx (0..$#{$model_object->volumes}) {
|
||||
my $volume = $model_object->volumes->[$volume_idx];
|
||||
foreach my $instance_idx (@$instance_idxs) {
|
||||
my $instance = $model_object->instances->[$instance_idx];
|
||||
my $mesh = $volume->mesh->clone;
|
||||
$instance->transform_mesh($mesh);
|
||||
|
||||
my $color_idx;
|
||||
if ($self->color_by eq 'volume') {
|
||||
$color_idx = $volume_idx;
|
||||
} elsif ($self->color_by eq 'object') {
|
||||
$color_idx = $obj_idx;
|
||||
}
|
||||
|
||||
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
||||
$color->[3] = $volume->modifier ? 0.5 : 1;
|
||||
push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
|
||||
bounding_box => $mesh->bounding_box,
|
||||
color => $color,
|
||||
);
|
||||
$v->mesh($mesh) if $self->enable_cutting;
|
||||
if ($self->select_by eq 'object') {
|
||||
$v->select_group_id($obj_idx*1000000);
|
||||
} elsif ($self->select_by eq 'volume') {
|
||||
$v->select_group_id($obj_idx*1000000 + $volume_idx*1000);
|
||||
} elsif ($self->select_by eq 'instance') {
|
||||
$v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx);
|
||||
}
|
||||
if ($self->drag_by eq 'object') {
|
||||
$v->drag_group_id($obj_idx*1000);
|
||||
} elsif ($self->drag_by eq 'instance') {
|
||||
$v->drag_group_id($obj_idx*1000 + $instance_idx);
|
||||
}
|
||||
push @volumes_idx, my $scene_volume_idx = $#{$self->volumes};
|
||||
$self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ];
|
||||
|
||||
my $verts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
$verts->load_mesh($mesh);
|
||||
$v->tverts($verts);
|
||||
}
|
||||
}
|
||||
|
||||
$self->volumes_by_object->{$obj_idx} = [@volumes_idx];
|
||||
return @volumes_idx;
|
||||
}
|
||||
|
||||
sub load_print_object_slices {
|
||||
my ($self, $object) = @_;
|
||||
|
||||
my @verts = ();
|
||||
my @norms = ();
|
||||
my @quad_verts = ();
|
||||
my @quad_norms = ();
|
||||
foreach my $layer (@{$object->layers}) {
|
||||
my $gap = 0;
|
||||
my $top_z = $layer->print_z;
|
||||
my $bottom_z = $layer->print_z - $layer->height + $gap;
|
||||
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
{
|
||||
my @expolygons = map $_->clone, @{$layer->slices};
|
||||
$_->translate(@$copy) for @expolygons;
|
||||
$self->_expolygons_to_verts(\@expolygons, $layer->print_z, \@verts, \@norms);
|
||||
}
|
||||
foreach my $slice (@{$layer->slices}) {
|
||||
foreach my $polygon (@$slice) {
|
||||
foreach my $line (@{$polygon->lines}) {
|
||||
$line->translate(@$copy);
|
||||
|
||||
push @quad_norms, (0,0,-1), (0,0,-1);
|
||||
push @quad_verts, (map unscale($_), @{$line->a}), $bottom_z;
|
||||
push @quad_verts, (map unscale($_), @{$line->b}), $bottom_z;
|
||||
push @quad_norms, (0,0,1), (0,0,1);
|
||||
push @quad_verts, (map unscale($_), @{$line->b}), $top_z;
|
||||
push @quad_verts, (map unscale($_), @{$line->a}), $top_z;
|
||||
|
||||
# We'll use this for the middle normal when using 4 quads:
|
||||
#my $xy_normal = $line->normal;
|
||||
#$_xynormal->scale(1/$line->length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $obb = $object->bounding_box;
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
|
||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->min_point}, 0));
|
||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->max_point}, $object->size->z));
|
||||
|
||||
push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
|
||||
bounding_box => $bb,
|
||||
color => COLORS->[0],
|
||||
verts => OpenGL::Array->new_list(GL_FLOAT, @verts),
|
||||
norms => OpenGL::Array->new_list(GL_FLOAT, @norms),
|
||||
quad_verts => OpenGL::Array->new_list(GL_FLOAT, @quad_verts),
|
||||
quad_norms => OpenGL::Array->new_list(GL_FLOAT, @quad_norms),
|
||||
);
|
||||
}
|
||||
|
||||
sub load_print_object_toolpaths {
|
||||
my ($self, $object) = @_;
|
||||
|
||||
my $perim_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
my $perim_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
my $infill_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
my $infill_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
my $support_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
my $support_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||
|
||||
my %perim_offsets = (); # print_z => [ qverts, tverts ]
|
||||
my %infill_offsets = ();
|
||||
my %support_offsets = ();
|
||||
|
||||
# order layers by print_z
|
||||
my @layers = sort { $a->print_z <=> $b->print_z }
|
||||
@{$object->layers}, @{$object->support_layers};
|
||||
|
||||
foreach my $layer (@layers) {
|
||||
my $top_z = $layer->print_z;
|
||||
|
||||
if (!exists $perim_offsets{$top_z}) {
|
||||
$perim_offsets{$top_z} = [
|
||||
$perim_qverts->size, $perim_tverts->size,
|
||||
];
|
||||
$infill_offsets{$top_z} = [
|
||||
$infill_qverts->size, $infill_tverts->size,
|
||||
];
|
||||
$support_offsets{$top_z} = [
|
||||
$support_qverts->size, $support_tverts->size,
|
||||
];
|
||||
}
|
||||
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
$self->_extrusionentity_to_verts($layerm->perimeters, $top_z, $copy,
|
||||
$perim_qverts, $perim_tverts);
|
||||
}
|
||||
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
$self->_extrusionentity_to_verts($layerm->fills, $top_z, $copy,
|
||||
$infill_qverts, $infill_tverts);
|
||||
}
|
||||
}
|
||||
|
||||
if ($layer->isa('Slic3r::Layer::Support') && $object->step_done(STEP_SUPPORTMATERIAL)) {
|
||||
$self->_extrusionentity_to_verts($layer->support_fills, $top_z, $copy,
|
||||
$support_qverts, $support_tverts);
|
||||
|
||||
$self->_extrusionentity_to_verts($layer->support_interface_fills, $top_z, $copy,
|
||||
$support_qverts, $support_tverts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $obb = $object->bounding_box;
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
my $cbb = $obb->clone;
|
||||
$cbb->translate(@$copy);
|
||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0));
|
||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z));
|
||||
}
|
||||
|
||||
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
||||
bounding_box => $bb,
|
||||
color => COLORS->[0],
|
||||
qverts => $perim_qverts,
|
||||
tverts => $perim_tverts,
|
||||
offsets => { %perim_offsets },
|
||||
);
|
||||
|
||||
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
||||
bounding_box => $bb,
|
||||
color => COLORS->[1],
|
||||
qverts => $infill_qverts,
|
||||
tverts => $infill_tverts,
|
||||
offsets => { %infill_offsets },
|
||||
);
|
||||
|
||||
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
||||
bounding_box => $bb,
|
||||
color => COLORS->[2],
|
||||
qverts => $support_qverts,
|
||||
tverts => $support_tverts,
|
||||
offsets => { %support_offsets },
|
||||
);
|
||||
}
|
||||
|
||||
sub set_toolpaths_range {
|
||||
my ($self, $min_z, $max_z) = @_;
|
||||
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
$volume->range([ $min_z, $max_z ]);
|
||||
}
|
||||
}
|
||||
|
||||
sub _expolygons_to_verts {
|
||||
my ($self, $expolygons, $z, $verts, $norms) = @_;
|
||||
|
||||
my $tess = gluNewTess();
|
||||
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_VERTEX, sub {
|
||||
my ($x, $y, $z) = @_;
|
||||
push @$verts, $x, $y, $z;
|
||||
push @$norms, (0,0,1), (0,0,1), (0,0,1);
|
||||
});
|
||||
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
|
||||
|
||||
foreach my $expolygon (@$expolygons) {
|
||||
gluTessBeginPolygon($tess);
|
||||
foreach my $polygon (@$expolygon) {
|
||||
gluTessBeginContour($tess);
|
||||
gluTessVertex_p($tess, (map unscale($_), @$_), $z) for @$polygon;
|
||||
gluTessEndContour($tess);
|
||||
}
|
||||
gluTessEndPolygon($tess);
|
||||
}
|
||||
|
||||
gluDeleteTess($tess);
|
||||
}
|
||||
|
||||
sub _extrusionentity_to_verts {
|
||||
my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_;
|
||||
|
||||
my ($lines, $widths, $heights, $closed);
|
||||
if ($entity->isa('Slic3r::ExtrusionPath::Collection')) {
|
||||
$self->_extrusionentity_to_verts($_, $top_z, $copy, $qverts, $tverts)
|
||||
for @$entity;
|
||||
return;
|
||||
} elsif ($entity->isa('Slic3r::ExtrusionPath')) {
|
||||
my $polyline = $entity->polyline->clone;
|
||||
$polyline->remove_duplicate_points;
|
||||
$polyline->translate(@$copy);
|
||||
$lines = $polyline->lines;
|
||||
$widths = [ map $entity->width, 0..$#$lines ];
|
||||
$heights = [ map $entity->height, 0..$#$lines ];
|
||||
$closed = 0;
|
||||
} else {
|
||||
$lines = [];
|
||||
$widths = [];
|
||||
$heights = [];
|
||||
$closed = 1;
|
||||
foreach my $path (@$entity) {
|
||||
my $polyline = $path->polyline->clone;
|
||||
$polyline->remove_duplicate_points;
|
||||
$polyline->translate(@$copy);
|
||||
my $path_lines = $polyline->lines;
|
||||
push @$lines, @$path_lines;
|
||||
push @$widths, map $path->width, 0..$#$path_lines;
|
||||
push @$heights, map $path->height, 0..$#$path_lines;
|
||||
}
|
||||
}
|
||||
Slic3r::GUI::_3DScene::_extrusionentity_to_verts_do($lines, $widths, $heights,
|
||||
$closed, $top_z, $copy, $qverts, $tverts);
|
||||
}
|
||||
|
||||
sub object_idx {
|
||||
my ($self, $volume_idx) = @_;
|
||||
return $self->_objects_by_volumes->{$volume_idx}[0];
|
||||
}
|
||||
|
||||
sub volume_idx {
|
||||
my ($self, $volume_idx) = @_;
|
||||
return $self->_objects_by_volumes->{$volume_idx}[1];
|
||||
}
|
||||
|
||||
sub instance_idx {
|
||||
my ($self, $volume_idx) = @_;
|
||||
return $self->_objects_by_volumes->{$volume_idx}[2];
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -47,5 +47,9 @@ sub GetValue {
|
|||
my ($self) = @_;
|
||||
return $self->{devices}[ $self->{choice}->GetSelection ]->address;
|
||||
}
|
||||
sub GetPort {
|
||||
my ($self) = @_;
|
||||
return $self->{devices}[ $self->{choice}->GetSelection ]->port;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -47,8 +47,14 @@ sub new {
|
|||
$self->Fit;
|
||||
$self->SetMinSize([760, 490]);
|
||||
if (defined $Slic3r::GUI::Settings->{_}{main_frame_size}) {
|
||||
$self->SetSize([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ]);
|
||||
$self->Move([ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ]);
|
||||
my $size = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_size}, 2 ];
|
||||
$self->SetSize($size);
|
||||
|
||||
my $display = Wx::Display->new->GetClientArea();
|
||||
my $pos = [ split ',', $Slic3r::GUI::Settings->{_}{main_frame_pos}, 2 ];
|
||||
if (($pos->[X] + $size->[X]/2) < $display->GetRight && ($pos->[Y] + $size->[Y]/2) < $display->GetBottom) {
|
||||
$self->Move($pos);
|
||||
}
|
||||
$self->Maximize(1) if $Slic3r::GUI::Settings->{_}{main_frame_maximized};
|
||||
} else {
|
||||
$self->SetSize($self->GetMinSize);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl
|
|||
:panel :sizer :toolbar :window wxTheApp :notebook);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED
|
||||
EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
|
||||
EVT_CHOICE EVT_TIMER);
|
||||
EVT_CHOICE EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant TB_ADD => &Wx::NewId;
|
||||
|
|
@ -28,7 +28,6 @@ use constant TB_SCALE => &Wx::NewId;
|
|||
use constant TB_SPLIT => &Wx::NewId;
|
||||
use constant TB_CUT => &Wx::NewId;
|
||||
use constant TB_SETTINGS => &Wx::NewId;
|
||||
use constant CONFIG_TIMER_ID => &Wx::NewId;
|
||||
|
||||
# package variables to avoid passing lexicals to threads
|
||||
our $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType;
|
||||
|
|
@ -53,8 +52,6 @@ sub new {
|
|||
$self->{model} = Slic3r::Model->new;
|
||||
$self->{print} = Slic3r::Print->new;
|
||||
$self->{objects} = [];
|
||||
$self->{apply_config_timer} = Wx::Timer->new($self, CONFIG_TIMER_ID)
|
||||
if $Slic3r::have_threads;
|
||||
|
||||
$self->{print}->set_status_cb(sub {
|
||||
my ($percent, $message) = @_;
|
||||
|
|
@ -87,32 +84,47 @@ sub new {
|
|||
$canvas->PopupMenu($menu, $click_pos);
|
||||
$menu->Destroy;
|
||||
};
|
||||
my $on_instance_moved = sub {
|
||||
my ($obj_idx, $instance_idx) = @_;
|
||||
my $on_instances_moved = sub {
|
||||
$self->update;
|
||||
};
|
||||
|
||||
# Initialize 3D plater
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config});
|
||||
$self->{preview_notebook}->AddPage($self->{canvas3D}, '3D');
|
||||
$self->{canvas3D}->set_on_select_object($on_select_object);
|
||||
$self->{canvas3D}->set_on_double_click($on_double_click);
|
||||
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
|
||||
$self->{canvas3D}->set_on_instances_moved($on_instances_moved);
|
||||
}
|
||||
|
||||
# Initialize 2D preview canvas
|
||||
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config});
|
||||
$self->{preview_notebook}->AddPage($self->{canvas}, '2D');
|
||||
$self->{canvas}->on_select_object($on_select_object);
|
||||
$self->{canvas}->on_double_click($on_double_click);
|
||||
$self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); });
|
||||
$self->{canvas}->on_instance_moved($on_instance_moved);
|
||||
$self->{canvas}->on_instances_moved($on_instances_moved);
|
||||
|
||||
# Initialize 3D preview and toolpaths preview
|
||||
# Initialize 3D toolpaths preview
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config});
|
||||
$self->{preview_notebook}->AddPage($self->{canvas3D}, '3D');
|
||||
$self->{canvas3D}->set_on_select_object($on_select_object);
|
||||
$self->{canvas3D}->set_on_double_click($on_double_click);
|
||||
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
|
||||
$self->{canvas3D}->set_on_instance_moved($on_instance_moved);
|
||||
|
||||
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
|
||||
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Preview');
|
||||
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
|
||||
$self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview');
|
||||
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
|
||||
}
|
||||
|
||||
# Initialize toolpaths preview
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
|
||||
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers');
|
||||
}
|
||||
|
||||
EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub {
|
||||
if ($self->{preview_notebook}->GetSelection == $self->{preview3D_page_idx}) {
|
||||
$self->{preview3D}->load_print;
|
||||
}
|
||||
});
|
||||
|
||||
# toolbar for object manipulation
|
||||
if (!&Wx::wxMSW) {
|
||||
Wx::ToolTip::Enable(1);
|
||||
|
|
@ -250,7 +262,8 @@ sub new {
|
|||
}
|
||||
|
||||
$_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self))
|
||||
for grep defined($_), $self, $self->{canvas}, $self->{canvas3D}, $self->{list};
|
||||
for grep defined($_),
|
||||
$self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list};
|
||||
|
||||
EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
|
|
@ -282,16 +295,23 @@ sub new {
|
|||
Slic3r::thread_cleanup();
|
||||
});
|
||||
|
||||
EVT_TIMER($self, CONFIG_TIMER_ID, sub {
|
||||
my ($self, $event) = @_;
|
||||
$self->async_apply_config;
|
||||
});
|
||||
if ($Slic3r::have_threads) {
|
||||
my $timer_id = Wx::NewId();
|
||||
$self->{apply_config_timer} = Wx::Timer->new($self, $timer_id);
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
$self->async_apply_config;
|
||||
});
|
||||
}
|
||||
|
||||
$self->{canvas}->update_bed_size;
|
||||
if ($self->{canvas3D}) {
|
||||
$self->{canvas3D}->update_bed_size;
|
||||
$self->{canvas3D}->zoom_to_bed;
|
||||
}
|
||||
if ($self->{preview3D}) {
|
||||
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape);
|
||||
}
|
||||
$self->update;
|
||||
|
||||
{
|
||||
|
|
@ -549,6 +569,7 @@ sub remove {
|
|||
|
||||
# Prevent toolpaths preview from rendering while we modify the Print object
|
||||
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
|
||||
$self->{preview3D}->enabled(0) if $self->{preview3D};
|
||||
|
||||
# if no object index is supplied, remove the selected one
|
||||
if (!defined $obj_idx) {
|
||||
|
|
@ -573,6 +594,7 @@ sub reset {
|
|||
|
||||
# Prevent toolpaths preview from rendering while we modify the Print object
|
||||
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
|
||||
$self->{preview3D}->enabled(0) if $self->{preview3D};
|
||||
|
||||
@{$self->{objects}} = ();
|
||||
$self->{model}->clear_objects;
|
||||
|
|
@ -585,17 +607,20 @@ sub reset {
|
|||
}
|
||||
|
||||
sub increase {
|
||||
my $self = shift;
|
||||
my ($self, $copies) = @_;
|
||||
|
||||
$copies //= 1;
|
||||
my ($obj_idx, $object) = $self->selected_object;
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
my $last_instance = $model_object->instances->[-1];
|
||||
my $i = $model_object->add_instance(
|
||||
offset => Slic3r::Pointf->new(map 10+$_, @{$last_instance->offset}),
|
||||
scaling_factor => $last_instance->scaling_factor,
|
||||
rotation => $last_instance->rotation,
|
||||
);
|
||||
$self->{print}->objects->[$obj_idx]->add_copy($i->offset);
|
||||
my $instance = $model_object->instances->[-1];
|
||||
for my $i (1..$copies) {
|
||||
$instance = $model_object->add_instance(
|
||||
offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}),
|
||||
scaling_factor => $instance->scaling_factor,
|
||||
rotation => $instance->rotation,
|
||||
);
|
||||
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
|
||||
}
|
||||
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
|
||||
|
||||
# only autoarrange if user has autocentering enabled
|
||||
|
|
@ -609,15 +634,18 @@ sub increase {
|
|||
}
|
||||
|
||||
sub decrease {
|
||||
my $self = shift;
|
||||
my ($self, $copies) = @_;
|
||||
|
||||
$copies //= 1;
|
||||
$self->stop_background_process;
|
||||
|
||||
my ($obj_idx, $object) = $self->selected_object;
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
if ($model_object->instances_count >= 2) {
|
||||
$model_object->delete_last_instance;
|
||||
$self->{print}->objects->[$obj_idx]->delete_last_copy;
|
||||
if ($model_object->instances_count > $copies) {
|
||||
for my $i (1..$copies) {
|
||||
$model_object->delete_last_instance;
|
||||
$self->{print}->objects->[$obj_idx]->delete_last_copy;
|
||||
}
|
||||
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
|
||||
} else {
|
||||
$self->remove;
|
||||
|
|
@ -631,6 +659,28 @@ sub decrease {
|
|||
$self->schedule_background_process;
|
||||
}
|
||||
|
||||
sub set_number_of_copies {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->pause_background_process;
|
||||
|
||||
# get current number of copies
|
||||
my ($obj_idx, $object) = $self->selected_object;
|
||||
my $model_object = $self->{model}->objects->[$obj_idx];
|
||||
|
||||
# prompt user
|
||||
my $copies = Wx::GetNumberFromUser("", "Enter the number of copies of the selected object:", "Copies", $model_object->instances_count, 0, 1000, $self);
|
||||
my $diff = $copies - $model_object->instances_count;
|
||||
if ($diff == 0) {
|
||||
# no variation
|
||||
$self->resume_background_process;
|
||||
} elsif ($diff > 0) {
|
||||
$self->increase($diff);
|
||||
} elsif ($diff < 0) {
|
||||
$self->decrease(-$diff);
|
||||
}
|
||||
}
|
||||
|
||||
sub rotate {
|
||||
my $self = shift;
|
||||
my ($angle, $axis) = @_;
|
||||
|
|
@ -667,6 +717,9 @@ sub rotate {
|
|||
$_->set_rotation(0) for @{ $model_object->instances };
|
||||
}
|
||||
$model_object->rotate(deg2rad($angle), $axis);
|
||||
|
||||
# realign object to Z = 0
|
||||
$model_object->center_around_origin;
|
||||
$self->make_thumbnail($obj_idx);
|
||||
}
|
||||
|
||||
|
|
@ -696,6 +749,9 @@ sub flip {
|
|||
|
||||
$model_object->flip($axis);
|
||||
$model_object->update_bounding_box;
|
||||
|
||||
# realign object to Z = 0
|
||||
$model_object->center_around_origin;
|
||||
$self->make_thumbnail($obj_idx);
|
||||
|
||||
# update print and start background processing
|
||||
|
|
@ -733,6 +789,7 @@ sub changescale {
|
|||
my $versor = [1,1,1];
|
||||
$versor->[$axis] = $scale/100;
|
||||
$model_object->scale_xyz(Slic3r::Pointf3->new(@$versor));
|
||||
# object was already aligned to Z = 0, so no need to realign it
|
||||
$self->make_thumbnail($obj_idx);
|
||||
} else {
|
||||
# max scale factor should be above 2540 to allow importing files exported in inches
|
||||
|
|
@ -825,13 +882,16 @@ sub schedule_background_process {
|
|||
|
||||
if (defined $self->{apply_config_timer}) {
|
||||
$self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot
|
||||
$self->{toolpaths2D}->reload_print;
|
||||
}
|
||||
}
|
||||
|
||||
sub async_apply_config {
|
||||
my ($self) = @_;
|
||||
|
||||
# reset preview canvases
|
||||
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
|
||||
$self->{preview3D}->reload_print if $self->{preview3D};
|
||||
|
||||
# pause process thread before applying new config
|
||||
# since we don't want to touch data that is being used by the threads
|
||||
$self->pause_background_process;
|
||||
|
|
@ -904,7 +964,8 @@ sub stop_background_process {
|
|||
$self->statusbar->SetCancelCallback(undef);
|
||||
$self->statusbar->StopBusy;
|
||||
$self->statusbar->SetStatusText("");
|
||||
$self->{toolpaths2D}->reload_print;
|
||||
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
|
||||
$self->{preview3D}->reload_print if $self->{preview3D};
|
||||
|
||||
if ($self->{process_thread}) {
|
||||
Slic3r::debugf "Killing background process.\n";
|
||||
|
|
@ -1028,7 +1089,8 @@ sub on_process_completed {
|
|||
$self->{process_thread} = undef;
|
||||
|
||||
return if $error;
|
||||
$self->{toolpaths2D}->reload_print;
|
||||
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
|
||||
$self->{preview3D}->reload_print if $self->{preview3D};
|
||||
|
||||
# if we have an export filename, start a new thread for exporting G-code
|
||||
if ($self->{export_gcode_output_file}) {
|
||||
|
|
@ -1299,6 +1361,8 @@ sub on_config_change {
|
|||
if ($opt_key eq 'bed_shape') {
|
||||
$self->{canvas}->update_bed_size;
|
||||
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
|
||||
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
|
||||
if $self->{preview3D};
|
||||
$self->update;
|
||||
} elsif ($opt_key eq 'serial_port') {
|
||||
if ($config->get('serial_port')) {
|
||||
|
|
@ -1346,7 +1410,7 @@ sub list_item_activated {
|
|||
my ($self, $event, $obj_idx) = @_;
|
||||
|
||||
$obj_idx //= $event->GetIndex;
|
||||
$self->object_cut_dialog($obj_idx);
|
||||
$self->object_settings_dialog($obj_idx);
|
||||
}
|
||||
|
||||
sub object_cut_dialog {
|
||||
|
|
@ -1518,6 +1582,7 @@ sub refresh_canvases {
|
|||
|
||||
$self->{canvas}->Refresh;
|
||||
$self->{canvas3D}->update if $self->{canvas3D};
|
||||
$self->{preview3D}->reload_print if $self->{preview3D};
|
||||
}
|
||||
|
||||
sub validate_config {
|
||||
|
|
@ -1549,6 +1614,9 @@ sub object_menu {
|
|||
$frame->_append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub {
|
||||
$self->decrease;
|
||||
});
|
||||
$frame->_append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub {
|
||||
$self->set_number_of_copies;
|
||||
});
|
||||
$menu->AppendSeparator();
|
||||
$frame->_append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub {
|
||||
$self->rotate(-45);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ sub new {
|
|||
$self->{on_select_object} = sub {};
|
||||
$self->{on_double_click} = sub {};
|
||||
$self->{on_right_click} = sub {};
|
||||
$self->{on_instance_moved} = sub {};
|
||||
$self->{on_instances_moved} = sub {};
|
||||
|
||||
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
|
||||
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
|
||||
|
|
@ -63,9 +63,9 @@ sub on_right_click {
|
|||
$self->{on_right_click} = $cb;
|
||||
}
|
||||
|
||||
sub on_instance_moved {
|
||||
sub on_instances_moved {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_instance_moved} = $cb;
|
||||
$self->{on_instances_moved} = $cb;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
|
|
@ -211,7 +211,7 @@ sub mouse_event {
|
|||
}
|
||||
$self->Refresh;
|
||||
} elsif ($event->LeftUp) {
|
||||
$self->{on_instance_moved}->(@{ $self->{drag_object} })
|
||||
$self->{on_instances_moved}->()
|
||||
if $self->{drag_object};
|
||||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use Slic3r::Geometry qw();
|
|||
use Slic3r::Geometry::Clipper qw();
|
||||
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw();
|
||||
use base 'Slic3r::GUI::PreviewCanvas';
|
||||
use base qw(Slic3r::GUI::3DScene Class::Accessor);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
|
|
@ -17,45 +17,42 @@ sub new {
|
|||
my $self = $class->SUPER::new($parent);
|
||||
$self->enable_picking(1);
|
||||
$self->enable_moving(1);
|
||||
$self->select_by('object');
|
||||
$self->drag_by('instance');
|
||||
|
||||
$self->{objects} = $objects;
|
||||
$self->{model} = $model;
|
||||
$self->{config} = $config;
|
||||
$self->{on_select_object} = sub {};
|
||||
$self->{on_instance_moved} = sub {};
|
||||
$self->{on_instances_moved} = sub {};
|
||||
|
||||
$self->on_select(sub {
|
||||
my ($volume_idx) = @_;
|
||||
|
||||
my $obj_idx = undef;
|
||||
if ($volume_idx != -1) {
|
||||
$obj_idx = $self->{_volumes_inv}{$volume_idx};
|
||||
$self->volumes->[$_]->selected(1) for @{$self->{_volumes}{$obj_idx}};
|
||||
$self->Refresh;
|
||||
$obj_idx = $self->object_idx($volume_idx);
|
||||
}
|
||||
$self->{on_select_object}->($obj_idx)
|
||||
if $self->{on_select_object};
|
||||
});
|
||||
$self->on_hover(sub {
|
||||
my ($volume_idx) = @_;
|
||||
|
||||
my $obj_idx = $self->{_volumes_inv}{$volume_idx};
|
||||
$self->volumes->[$_]->hover(1) for @{$self->{_volumes}{$obj_idx}};
|
||||
});
|
||||
$self->on_move(sub {
|
||||
my ($volume_idx) = @_;
|
||||
my @volume_idxs = @_;
|
||||
|
||||
my $volume = $self->volumes->[$volume_idx];
|
||||
my $obj_idx = $self->{_volumes_inv}{$volume_idx};
|
||||
my $model_object = $self->{model}->get_object($obj_idx);
|
||||
$model_object
|
||||
->instances->[$volume->instance_idx]
|
||||
->offset
|
||||
->translate($volume->origin->x, $volume->origin->y); #))
|
||||
$model_object->invalidate_bounding_box;
|
||||
foreach my $volume_idx (@volume_idxs) {
|
||||
my $volume = $self->volumes->[$volume_idx];
|
||||
my $obj_idx = $self->object_idx($volume_idx);
|
||||
my $instance_idx = $self->instance_idx($volume_idx);
|
||||
my $model_object = $self->{model}->get_object($obj_idx);
|
||||
$model_object
|
||||
->instances->[$instance_idx]
|
||||
->offset
|
||||
->translate($volume->origin->x, $volume->origin->y); #))
|
||||
$model_object->invalidate_bounding_box;
|
||||
}
|
||||
|
||||
$self->{on_instance_moved}->($obj_idx, $volume->instance_idx)
|
||||
if $self->{on_instance_moved};
|
||||
$self->{on_instances_moved}->()
|
||||
if $self->{on_instances_moved};
|
||||
});
|
||||
|
||||
return $self;
|
||||
|
|
@ -76,27 +73,19 @@ sub set_on_right_click {
|
|||
$self->on_right_click($cb);
|
||||
}
|
||||
|
||||
sub set_on_instance_moved {
|
||||
sub set_on_instances_moved {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_instance_moved} = $cb;
|
||||
$self->{on_instances_moved} = $cb;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->{_volumes} = {}; # obj_idx => [ volume_idx, volume_idx ]
|
||||
$self->{_volumes_inv} = {}; # volume_idx => obj_idx
|
||||
$self->reset_objects;
|
||||
|
||||
$self->update_bed_size;
|
||||
|
||||
foreach my $obj_idx (0..$#{$self->{model}->objects}) {
|
||||
my $model_object = $self->{model}->get_object($obj_idx);
|
||||
my @volume_idxs = $self->load_object($model_object, 1);
|
||||
|
||||
# store mapping between canvas volumes and model objects
|
||||
$self->{_volumes}{$obj_idx} = [ @volume_idxs ];
|
||||
$self->{_volumes_inv}{$_} = $obj_idx for @volume_idxs;
|
||||
my @volume_idxs = $self->load_object($self->{model}, $obj_idx);
|
||||
|
||||
if ($self->{objects}[$obj_idx]->selected) {
|
||||
$self->select_volume($_) for @volume_idxs;
|
||||
|
|
|
|||
144
lib/Slic3r/GUI/Plater/3DPreview.pm
Normal file
144
lib/Slic3r/GUI/Plater/3DPreview.pm
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package Slic3r::GUI::Plater::3DPreview;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Print::State ':steps';
|
||||
use Wx qw(:misc :sizer :slider :statictext wxWHITE);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider));
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $print) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
|
||||
|
||||
# init GUI elements
|
||||
my $canvas = Slic3r::GUI::3DScene->new($self);
|
||||
$self->canvas($canvas);
|
||||
my $slider = Wx::Slider->new(
|
||||
$self, -1,
|
||||
0, # default
|
||||
0, # min
|
||||
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
|
||||
# will skip drawing the slider if max <= min:
|
||||
1, # max
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxVERTICAL | wxSL_INVERSE,
|
||||
);
|
||||
$self->slider($slider);
|
||||
|
||||
my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
|
||||
$vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
|
||||
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
EVT_SLIDER($self, $slider, sub {
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue])
|
||||
if $self->enabled;
|
||||
});
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($s, $event) = @_;
|
||||
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($key == 85 || $key == 315) {
|
||||
$slider->SetValue($slider->GetValue + 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key == 68 || $key == 317) {
|
||||
$slider->SetValue($slider->GetValue - 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
}
|
||||
});
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
# init canvas
|
||||
$self->print($print);
|
||||
$self->reload_print;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reload_print {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->canvas->reset_objects;
|
||||
$self->_loaded(0);
|
||||
$self->load_print;
|
||||
}
|
||||
|
||||
sub load_print {
|
||||
my ($self) = @_;
|
||||
|
||||
return if $self->_loaded;
|
||||
|
||||
# we require that there's at least one object and the posSlice step
|
||||
# is performed on all of them (this ensures that _shifted_copies was
|
||||
# populated and we know the number of layers)
|
||||
if (!$self->print->object_step_done(STEP_SLICE)) {
|
||||
$self->enabled(0);
|
||||
$self->slider->Hide;
|
||||
$self->canvas->Refresh; # clears canvas
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
my %z = (); # z => 1
|
||||
foreach my $object (@{$self->{print}->objects}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
$z{$layer->print_z} = 1;
|
||||
}
|
||||
}
|
||||
$self->enabled(1);
|
||||
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
|
||||
$self->slider->SetRange(0, scalar(@{$self->{layers_z}})-1);
|
||||
if ((my $z_idx = $self->slider->GetValue) <= $#{$self->{layers_z}} && $self->slider->GetValue != 0) {
|
||||
$self->set_z($self->{layers_z}[$z_idx]);
|
||||
} else {
|
||||
$self->slider->SetValue(scalar(@{$self->{layers_z}})-1);
|
||||
$self->set_z($self->{layers_z}[-1]) if @{$self->{layers_z}};
|
||||
}
|
||||
$self->slider->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
if ($self->IsShown) {
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
$self->canvas->load_print_object_toolpaths($object);
|
||||
|
||||
#my @volume_ids = $self->canvas->load_object($object->model_object);
|
||||
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
|
||||
}
|
||||
$self->canvas->zoom_to_volumes;
|
||||
$self->_loaded(1);
|
||||
}
|
||||
}
|
||||
|
||||
sub set_z {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
return if !$self->enabled;
|
||||
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
|
||||
$self->canvas->set_toolpaths_range(0, $z);
|
||||
$self->canvas->Refresh if $self->IsShown;
|
||||
}
|
||||
|
||||
sub set_bed_shape {
|
||||
my ($self, $bed_shape) = @_;
|
||||
$self->canvas->set_bed_shape($bed_shape);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -86,8 +86,9 @@ sub new {
|
|||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
$canvas->enable_cutting(1);
|
||||
$canvas->load_object($self->{model_object}, undef, [0]);
|
||||
$canvas->set_auto_bed_shape;
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas->SetMinSize($canvas->GetSize);
|
||||
|
|
@ -153,6 +154,7 @@ sub perform_cut {
|
|||
$self->{new_model} = $new_model;
|
||||
$self->{new_model_objects} = [];
|
||||
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
|
||||
$upper_object->center_around_origin; # align to Z = 0
|
||||
push @{$self->{new_model_objects}}, $upper_object;
|
||||
}
|
||||
if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
|
||||
|
|
|
|||
|
|
@ -68,8 +68,18 @@ sub new {
|
|||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
$canvas->enable_picking(1);
|
||||
$canvas->select_by('volume');
|
||||
|
||||
$canvas->on_select(sub {
|
||||
my ($volume_idx) = @_;
|
||||
|
||||
# convert scene volume to model object volume
|
||||
$self->reload_tree($canvas->volume_idx($volume_idx));
|
||||
});
|
||||
|
||||
$canvas->load_object($self->{model_object}, undef, [0]);
|
||||
$canvas->set_auto_bed_shape;
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas->zoom_to_volumes;
|
||||
|
|
@ -101,20 +111,24 @@ sub new {
|
|||
}
|
||||
|
||||
sub reload_tree {
|
||||
my ($self) = @_;
|
||||
my ($self, $selected_volume_idx) = @_;
|
||||
|
||||
$selected_volume_idx //= -1;
|
||||
my $object = $self->{model_object};
|
||||
my $tree = $self->{tree};
|
||||
my $rootId = $tree->GetRootItem;
|
||||
|
||||
$tree->DeleteChildren($rootId);
|
||||
|
||||
my $itemId;
|
||||
my $selectedId = $rootId;
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
|
||||
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
|
||||
$itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
|
||||
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
|
||||
if ($volume_id == $selected_volume_idx) {
|
||||
$selectedId = $itemId;
|
||||
}
|
||||
$tree->SetPlData($itemId, {
|
||||
type => 'volume',
|
||||
volume_id => $volume_id,
|
||||
|
|
@ -122,10 +136,9 @@ sub reload_tree {
|
|||
}
|
||||
$tree->ExpandAll;
|
||||
|
||||
# select last appended part
|
||||
# This will trigger the selection_changed() event
|
||||
Slic3r::GUI->CallAfter(sub {
|
||||
$self->{tree}->SelectItem($itemId);
|
||||
$self->{tree}->SelectItem($selectedId);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +157,7 @@ sub selection_changed {
|
|||
|
||||
# deselect all meshes
|
||||
if ($self->{canvas}) {
|
||||
$_->{selected} = 0 for @{$self->{canvas}->volumes};
|
||||
$_->selected(0) for @{$self->{canvas}->volumes};
|
||||
}
|
||||
|
||||
# disable things as if nothing is selected
|
||||
|
|
@ -169,10 +182,7 @@ sub selection_changed {
|
|||
# get default values
|
||||
@opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
|
||||
} elsif ($itemData->{type} eq 'object') {
|
||||
# select all object volumes in 3D preview
|
||||
if ($self->{canvas}) {
|
||||
$_->{selected} = 1 for @{$self->{canvas}->volumes};
|
||||
}
|
||||
# select nothing in 3D preview
|
||||
|
||||
# attach object config to settings panel
|
||||
$self->{staticbox}->SetLabel('Object Settings');
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package Slic3r::GUI::Plater::ObjectPreviewDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{model_object} = $params{model_object};
|
||||
|
||||
my $canvas = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas->set_bounding_box($self->{model_object}->bounding_box);
|
||||
$canvas->set_auto_bed_shape;
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($canvas, 1, wxEXPAND, 0);
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -22,6 +22,7 @@ sub new {
|
|||
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->SetSizer($self->{vsizer});
|
||||
$self->build;
|
||||
$self->_update;
|
||||
|
||||
{
|
||||
my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize);
|
||||
|
|
@ -71,12 +72,14 @@ sub load_config {
|
|||
$self->{config}->set($opt_key, $config->get($opt_key));
|
||||
}
|
||||
$_->reload_config for @{$self->{optgroups}};
|
||||
$self->_update;
|
||||
}
|
||||
|
||||
sub load_presets {}
|
||||
|
||||
sub is_dirty { 0 }
|
||||
sub config { $_[0]->{config}->clone }
|
||||
sub _update {}
|
||||
|
||||
sub on_value_change {
|
||||
my ($self, $cb) = @_;
|
||||
|
|
@ -88,7 +91,19 @@ sub on_presets_changed {}
|
|||
# propagate event to the parent
|
||||
sub _on_value_change {
|
||||
my $self = shift;
|
||||
|
||||
$self->{on_value_change}->(@_) if $self->{on_value_change};
|
||||
$self->_update;
|
||||
}
|
||||
|
||||
sub get_field {
|
||||
my ($self, $opt_key, $opt_index) = @_;
|
||||
|
||||
foreach my $optgroup (@{ $self->{optgroups} }) {
|
||||
my $field = $optgroup->get_fieldc($opt_key, $opt_index);
|
||||
return $field if defined $field;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::SimpleTab::Print;
|
||||
|
|
@ -104,10 +119,12 @@ sub build {
|
|||
|
||||
$self->init_config_options(qw(
|
||||
layer_height perimeters top_solid_layers bottom_solid_layers
|
||||
fill_density fill_pattern support_material support_material_spacing raft_layers
|
||||
fill_density fill_pattern external_fill_pattern
|
||||
support_material support_material_spacing raft_layers
|
||||
support_material_contact_distance dont_support_bridges
|
||||
perimeter_speed infill_speed travel_speed
|
||||
brim_width
|
||||
complete_objects extruder_clearance_radius extruder_clearance_height
|
||||
xy_size_compensation
|
||||
));
|
||||
|
||||
{
|
||||
|
|
@ -127,12 +144,15 @@ sub build {
|
|||
my $optgroup = $self->new_optgroup('Infill');
|
||||
$optgroup->append_single_option_line('fill_density');
|
||||
$optgroup->append_single_option_line('fill_pattern');
|
||||
$optgroup->append_single_option_line('external_fill_pattern');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Support material');
|
||||
$optgroup->append_single_option_line('support_material');
|
||||
$optgroup->append_single_option_line('support_material_spacing');
|
||||
$optgroup->append_single_option_line('support_material_contact_distance');
|
||||
$optgroup->append_single_option_line('dont_support_bridges');
|
||||
$optgroup->append_single_option_line('raft_layers');
|
||||
}
|
||||
|
||||
|
|
@ -149,18 +169,35 @@ sub build {
|
|||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Sequential printing');
|
||||
$optgroup->append_single_option_line('complete_objects');
|
||||
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Extruder clearance (mm)',
|
||||
);
|
||||
$line->append_option($optgroup->get_option('extruder_clearance_radius'));
|
||||
$line->append_option($optgroup->get_option('extruder_clearance_height'));
|
||||
$optgroup->append_line($line);
|
||||
my $optgroup = $self->new_optgroup('Other');
|
||||
$optgroup->append_single_option_line('xy_size_compensation');
|
||||
}
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
|
||||
my $config = $self->{config};
|
||||
|
||||
my $have_perimeters = $config->perimeters > 0;
|
||||
$self->get_field($_)->toggle($have_perimeters)
|
||||
for qw(perimeter_speed);
|
||||
|
||||
my $have_infill = $config->fill_density > 0;
|
||||
my $have_solid_infill = $config->top_solid_layers > 0 || $config->bottom_solid_layers > 0;
|
||||
$self->get_field($_)->toggle($have_infill)
|
||||
for qw(fill_pattern);
|
||||
$self->get_field($_)->toggle($have_solid_infill)
|
||||
for qw(external_fill_pattern);
|
||||
$self->get_field($_)->toggle($have_infill || $have_solid_infill)
|
||||
for qw(infill_speed);
|
||||
|
||||
my $have_support_material = $config->support_material || $config->raft_layers > 0;
|
||||
$self->get_field($_)->toggle($have_support_material)
|
||||
for qw(support_material_spacing dont_support_bridges
|
||||
support_material_contact_distance);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::SimpleTab::Filament;
|
||||
use base 'Slic3r::GUI::SimpleTab';
|
||||
|
||||
|
|
@ -206,6 +243,8 @@ sub build {
|
|||
|
||||
package Slic3r::GUI::SimpleTab::Printer;
|
||||
use base 'Slic3r::GUI::SimpleTab';
|
||||
use Wx qw(:sizer :button :bitmap :misc :id);
|
||||
use Wx::Event qw(EVT_BUTTON);
|
||||
|
||||
sub name { 'printer' }
|
||||
sub title { 'Printer Settings' }
|
||||
|
|
@ -214,17 +253,46 @@ sub build {
|
|||
my $self = shift;
|
||||
|
||||
$self->init_config_options(qw(
|
||||
bed_shape
|
||||
z_offset
|
||||
gcode_flavor
|
||||
nozzle_diameter
|
||||
retract_length retract_lift
|
||||
retract_length retract_lift wipe
|
||||
start_gcode
|
||||
end_gcode
|
||||
));
|
||||
|
||||
{
|
||||
my $bed_shape_widget = sub {
|
||||
my ($parent) = @_;
|
||||
|
||||
my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if ($Slic3r::GUI::have_button_icons) {
|
||||
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG));
|
||||
}
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($btn);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape);
|
||||
if ($dlg->ShowModal == wxID_OK) {
|
||||
my $value = $dlg->GetValue;
|
||||
$self->{config}->set('bed_shape', $value);
|
||||
$self->_on_value_change('bed_shape', $value);
|
||||
}
|
||||
});
|
||||
|
||||
return $sizer;
|
||||
};
|
||||
|
||||
my $optgroup = $self->new_optgroup('Size and coordinates');
|
||||
# TODO: add bed_shape
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Bed shape',
|
||||
widget => $bed_shape_widget,
|
||||
);
|
||||
$optgroup->append_line($line);
|
||||
$optgroup->append_single_option_line('z_offset');
|
||||
}
|
||||
|
||||
|
|
@ -242,6 +310,7 @@ sub build {
|
|||
my $optgroup = $self->new_optgroup('Retraction');
|
||||
$optgroup->append_single_option_line('retract_length', 0);
|
||||
$optgroup->append_single_option_line('retract_lift', 0);
|
||||
$optgroup->append_single_option_line('wipe', 0);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -265,4 +334,14 @@ sub build {
|
|||
}
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
|
||||
my $config = $self->{config};
|
||||
|
||||
my $have_retraction = $config->retract_length->[0] > 0;
|
||||
$self->get_field($_, 0)->toggle($have_retraction)
|
||||
for qw(retract_lift wipe);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -292,16 +292,24 @@ sub reload_config {
|
|||
}
|
||||
|
||||
sub update_tree {
|
||||
my $self = shift;
|
||||
my ($select) = @_;
|
||||
my ($self) = @_;
|
||||
|
||||
$select //= 0; #/
|
||||
# get label of the currently selected item
|
||||
my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection);
|
||||
|
||||
my $rootItem = $self->{treectrl}->GetRootItem;
|
||||
$self->{treectrl}->DeleteChildren($rootItem);
|
||||
my $have_selection = 0;
|
||||
foreach my $page (@{$self->{pages}}) {
|
||||
my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID});
|
||||
$self->{treectrl}->SelectItem($itemId) if $self->{treectrl}->GetChildrenCount($rootItem) == $select + 1;
|
||||
if ($page->{title} eq $selected) {
|
||||
$self->{treectrl}->SelectItem($itemId);
|
||||
$have_selection = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$have_selection) {
|
||||
$self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -391,6 +399,7 @@ sub load_config {
|
|||
$self->update_dirty;
|
||||
}
|
||||
$self->reload_config;
|
||||
$self->_update;
|
||||
}
|
||||
|
||||
sub get_preset_config {
|
||||
|
|
@ -453,7 +462,7 @@ sub build {
|
|||
raft_layers
|
||||
support_material_pattern support_material_spacing support_material_angle
|
||||
support_material_interface_layers support_material_interface_spacing
|
||||
dont_support_bridges
|
||||
support_material_contact_distance dont_support_bridges
|
||||
notes
|
||||
complete_objects extruder_clearance_radius extruder_clearance_height
|
||||
gcode_comments output_filename_format
|
||||
|
|
@ -465,7 +474,7 @@ sub build {
|
|||
extrusion_width first_layer_extrusion_width perimeter_extrusion_width
|
||||
external_perimeter_extrusion_width infill_extrusion_width solid_infill_extrusion_width
|
||||
top_infill_extrusion_width support_material_extrusion_width
|
||||
bridge_flow_ratio
|
||||
infill_overlap bridge_flow_ratio
|
||||
xy_size_compensation threads resolution
|
||||
));
|
||||
|
||||
|
|
@ -556,6 +565,7 @@ sub build {
|
|||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Options for support material and raft');
|
||||
$optgroup->append_single_option_line('support_material_contact_distance');
|
||||
$optgroup->append_single_option_line('support_material_pattern');
|
||||
$optgroup->append_single_option_line('support_material_spacing');
|
||||
$optgroup->append_single_option_line('support_material_angle');
|
||||
|
|
@ -634,6 +644,10 @@ sub build {
|
|||
$optgroup->append_single_option_line('top_infill_extrusion_width');
|
||||
$optgroup->append_single_option_line('support_material_extrusion_width');
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Overlap');
|
||||
$optgroup->append_single_option_line('infill_overlap');
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Flow');
|
||||
$optgroup->append_single_option_line('bridge_flow_ratio');
|
||||
|
|
@ -702,13 +716,20 @@ sub _update {
|
|||
my $config = $self->{config};
|
||||
|
||||
if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 0)) {
|
||||
my $dialog = Wx::MessageDialog->new($self, "The Spiral Vase mode requires one perimeter, no top solid layers and 0% fill density. Shall I adjust those settings in order to enable Spiral Vase?",
|
||||
my $dialog = Wx::MessageDialog->new($self,
|
||||
"The Spiral Vase mode requires:\n"
|
||||
. "- one perimeter\n"
|
||||
. "- no top solid layers\n"
|
||||
. "- 0% fill density\n"
|
||||
. "- no support material\n"
|
||||
. "\nShall I adjust those settings in order to enable Spiral Vase?",
|
||||
'Spiral Vase', wxICON_WARNING | wxYES | wxNO);
|
||||
if ($dialog->ShowModal() == wxID_YES) {
|
||||
my $new_conf = Slic3r::Config->new;
|
||||
$new_conf->set("perimeters", 1);
|
||||
$new_conf->set("top_solid_layers", 0);
|
||||
$new_conf->set("fill_density", 0);
|
||||
$new_conf->set("support_material", 0);
|
||||
$self->load_config($new_conf);
|
||||
} else {
|
||||
my $new_conf = Slic3r::Config->new;
|
||||
|
|
@ -759,7 +780,7 @@ sub _update {
|
|||
for qw(support_material_threshold support_material_enforce_layers
|
||||
support_material_pattern support_material_spacing support_material_angle
|
||||
support_material_interface_layers dont_support_bridges
|
||||
support_material_extrusion_width);
|
||||
support_material_extrusion_width support_material_contact_distance);
|
||||
$self->get_field($_)->toggle($have_support_material && $have_support_interface)
|
||||
for qw(support_material_interface_spacing support_material_interface_extruder
|
||||
support_material_interface_speed);
|
||||
|
|
@ -929,7 +950,7 @@ sub build {
|
|||
octoprint_host octoprint_apikey
|
||||
use_firmware_retraction pressure_advance vibration_limit
|
||||
use_volumetric_e
|
||||
start_gcode end_gcode layer_gcode toolchange_gcode
|
||||
start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode
|
||||
nozzle_diameter extruder_offset
|
||||
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
|
||||
retract_length_toolchange retract_restart_extra_toolchange
|
||||
|
|
@ -1037,7 +1058,7 @@ sub build {
|
|||
EVT_BUTTON($self, $btn, sub {
|
||||
my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
|
||||
if ($dlg->ShowModal == wxID_OK) {
|
||||
my $value = $dlg->GetValue;
|
||||
my $value = $dlg->GetValue . ":" . $dlg->GetPort;
|
||||
$self->{config}->set('octoprint_host', $value);
|
||||
$self->update_dirty;
|
||||
$self->_on_value_change('octoprint_host', $value);
|
||||
|
|
@ -1060,7 +1081,7 @@ sub build {
|
|||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
|
||||
my $res = $ua->post(
|
||||
my $res = $ua->get(
|
||||
"http://" . $self->{config}->octoprint_host . "/api/version",
|
||||
'X-Api-Key' => $self->{config}->octoprint_apikey,
|
||||
);
|
||||
|
|
@ -1115,7 +1136,16 @@ sub build {
|
|||
$optgroup->append_single_option_line($option);
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Layer change G-code',
|
||||
my $optgroup = $page->new_optgroup('Before layer change G-code',
|
||||
label_width => 0,
|
||||
);
|
||||
my $option = $optgroup->get_option('before_layer_gcode');
|
||||
$option->full_width(1);
|
||||
$option->height(150);
|
||||
$optgroup->append_single_option_line($option);
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('After layer change G-code',
|
||||
label_width => 0,
|
||||
);
|
||||
my $option = $optgroup->get_option('layer_gcode');
|
||||
|
|
@ -1203,6 +1233,7 @@ sub _build_extruder_pages {
|
|||
|
||||
# remove extra pages
|
||||
if ($self->{extruders_count} <= $#{$self->{extruder_pages}}) {
|
||||
$_->Destroy for @{$self->{extruder_pages}}[$self->{extruders_count}..$#{$self->{extruder_pages}}];
|
||||
splice @{$self->{extruder_pages}}, $self->{extruders_count};
|
||||
}
|
||||
|
||||
|
|
@ -1219,7 +1250,7 @@ sub _build_extruder_pages {
|
|||
(grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}),
|
||||
@{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ],
|
||||
);
|
||||
$self->update_tree(0);
|
||||
$self->update_tree;
|
||||
}
|
||||
|
||||
sub _update {
|
||||
|
|
@ -1420,8 +1451,8 @@ sub config {
|
|||
return Slic3r::Config->new_from_defaults(@$keys);
|
||||
} else {
|
||||
if (!-e $self->file) {
|
||||
Slic3r::GUI::show_error($self, "The selected preset does not exist anymore (" . $self->file . ").");
|
||||
return;
|
||||
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
|
||||
return undef;
|
||||
}
|
||||
|
||||
# apply preset values on top of defaults
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue