diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 34dd48ca8..364c1c0a9 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -48,6 +48,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init bed_shape bed_triangles bed_grid_lines + bed_polygon background origin _mouse_pos @@ -55,6 +56,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init _drag_volume_idx _drag_start_pos + _drag_volume_center_offset _drag_start_xy _dragged @@ -388,8 +390,18 @@ sub mouse_event { if ($volume_idx != -1) { if ($e->LeftDown && $self->enable_moving) { - $self->_drag_volume_idx($volume_idx); - $self->_drag_start_pos($self->mouse_to_3d(@$pos)); + my $pos3d = $self->mouse_to_3d(@$pos); + # Only accept the initial position, if it is inside the volume bounding box. + my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box; + $volume_bbox->offset(0.01); + if ($volume_bbox->contains_point($pos3d)) { + # The dragging operation is initiated. + $self->_drag_volume_idx($volume_idx); + $self->_drag_start_pos($pos3d); + # Remember the shift to to the object center. The object center will later be used + # to limit the object placement close to the bed. + $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center)); + } } elsif ($e->RightDown) { # if right clicking on volume, propagate event through callback $self->on_right_click->($e->GetPosition) @@ -398,26 +410,29 @@ sub mouse_event { } } } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) { - # get new position at the same Z of the initial click point - my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY); - my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z); - - # calculate the translation vector - my $vector = $self->_drag_start_pos->vector_to($cur_pos); - - # get volume being dragged - my $volume = $self->volumes->[$self->_drag_volume_idx]; - - # 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}; + # Get new position at the same Z of the initial click point. + my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane($self->_drag_start_pos->z); + # Clip the new position, so the object center remains close to the bed. + { + $cur_pos->translate(@{$self->_drag_volume_center_offset}); + my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y)); + if (! $self->bed_polygon->contains_point($cur_pos2)) { + my $ip = $self->bed_polygon->point_projection($cur_pos2); + $cur_pos->set_x(unscale($ip->x)); + $cur_pos->set_y(unscale($ip->y)); + } + $cur_pos->translate(@{$self->_drag_volume_center_offset->negative}); } - - # apply new temporary volume origin and ignore Z - $_->translate($vector->x, $vector->y, 0) for @volumes; #,, + # Calculate the translation vector. + my $vector = $self->_drag_start_pos->vector_to($cur_pos); + # Get the volume being dragged. + my $volume = $self->volumes->[$self->_drag_volume_idx]; + # Get all volumes belonging to the same group, if any. + my @volumes = ($volume->drag_group_id == -1) ? + ($volume) : + grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes}; + # Apply new temporary volume origin and ignore Z. + $_->translate($vector->x, $vector->y, 0) for @volumes; $self->_drag_start_pos($cur_pos); $self->_dragged(1); $self->Refresh; @@ -430,6 +445,7 @@ sub mouse_event { if (defined $self->_drag_start_pos) { my $orig = $self->_drag_start_pos; if (TURNTABLE_MODE) { + # Turntable mode is enabled by default. $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; @@ -668,6 +684,8 @@ sub max_bounding_box { return $bb; } +# Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane +# to support the scene objects. sub set_auto_bed_shape { my ($self, $bed_shape) = @_; @@ -680,9 +698,14 @@ sub set_auto_bed_shape { [ $center->x + $max_size, $center->y + $max_size ], #++ [ $center->x - $max_size, $center->y + $max_size ], #++ ]); + # Set the origin for painting of the coordinate system axes. $self->origin(Slic3r::Pointf->new(@$center[X,Y])); } +# Set the bed shape to a single closed 2D polygon (array of two element arrays), +# triangulate the bed and store the triangles into $self->bed_triangles, +# fills the $self->bed_grid_lines and sets $self->origin. +# Sets $self->bed_polygon to limit the object placement. sub set_bed_shape { my ($self, $bed_shape) = @_; @@ -695,7 +718,7 @@ sub set_bed_shape { { my @points = (); foreach my $triangle (@{ $expolygon->triangulate }) { - push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #)) + push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; } $self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points)); } @@ -723,7 +746,10 @@ sub set_bed_shape { $self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points)); } + # Set the origin for painting of the coordinate system axes. $self->origin(Slic3r::Pointf->new(0,0)); + + $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7)->[0]->contour->clone); } sub deselect_volumes { @@ -1073,6 +1099,7 @@ sub Render { } if (TURNTABLE_MODE) { + # Turntable mode is enabled by default. glRotatef(-$self->_stheta, 1, 0, 0); # pitch glRotatef($self->_sphi, 0, 0, 1); # yaw } else { diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 6c40b062f..99e1f4070 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -15,7 +15,7 @@ typedef std::vector ThickLines; class Line { - public: +public: Point a; Point b; Line() {}; diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 8b181d7bb..a1bc2140d 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -140,6 +140,29 @@ MultiPoint::intersection(const Line& line, Point* intersection) const return false; } +bool MultiPoint::first_intersection(const Line& line, Point* intersection) const +{ + bool found = false; + double dmin = 0.; + for (const Line &l : this->lines()) { + Point ip; + if (l.intersection(line, &ip)) { + if (! found) { + found = true; + dmin = ip.distance_to(line.a); + *intersection = ip; + } else { + double d = ip.distance_to(line.a); + if (d < dmin) { + dmin = d; + *intersection = ip; + } + } + } + } + return found; +} + std::string MultiPoint::dump_perl() const { diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index b7a190ebf..c49a673fe 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -13,7 +13,7 @@ class BoundingBox; class MultiPoint { - public: +public: Points points; operator Points() const; @@ -35,8 +35,24 @@ class MultiPoint double length() const; bool is_valid() const { return this->points.size() >= 2; } - int find_point(const Point &point) const; + int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; + int closest_point_index(const Point &point) const { + int idx = -1; + if (! this->points.empty()) { + idx = 0; + double dist_min = this->points.front().distance_to(point); + for (int i = 1; i < int(this->points.size()); ++ i) { + double d = this->points[i].distance_to(point); + if (d < dist_min) { + dist_min = d; + idx = i; + } + } + } + return idx; + } + const Point* closest_point(const Point &point) const { return this->points.empty() ? nullptr : &this->points[this->closest_point_index(point)]; } BoundingBox bounding_box() const; // Return true if there are exact duplicates. bool has_duplicate_points() const; @@ -56,6 +72,7 @@ class MultiPoint } bool intersection(const Line& line, Point* intersection) const; + bool first_intersection(const Line& line, Point* intersection) const; std::string dump_perl() const; static Points _douglas_peucker(const Points &points, const double tolerance); diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 60ccd792d..27f9a2ca1 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -293,6 +293,44 @@ Polygon::convex_points(double angle) const return points; } +// Projection of a point onto the polygon. +Point Polygon::point_projection(const Point &point) const +{ + Point proj = point; + double dmin = std::numeric_limits::max(); + if (! this->points.empty()) { + for (size_t i = 0; i < this->points.size(); ++ i) { + const Point &pt0 = this->points[i]; + const Point &pt1 = this->points[(i + 1 == this->points.size()) ? 0 : i + 1]; + double d = pt0.distance_to(point); + if (d < dmin) { + dmin = d; + proj = pt0; + } + d = pt1.distance_to(point); + if (d < dmin) { + dmin = d; + proj = pt1; + } + Pointf v1(coordf_t(pt1.x - pt0.x), coordf_t(pt1.y - pt0.y)); + coordf_t div = dot(v1); + if (div > 0.) { + Pointf v2(coordf_t(point.x - pt0.x), coordf_t(point.y - pt0.y)); + coordf_t t = dot(v1, v2) / div; + if (t > 0. && t < 1.) { + Point foot(coord_t(floor(coordf_t(pt0.x) + t * v1.x + 0.5)), coord_t(floor(coordf_t(pt0.y) + t * v1.y + 0.5))); + d = foot.distance_to(point); + if (d < dmin) { + dmin = d; + proj = foot; + } + } + } + } + } + return proj; +} + BoundingBox get_extents(const Polygon &poly) { return poly.bounding_box(); diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index a938492ed..65f0b1400 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -51,6 +51,8 @@ public: std::string wkt() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; + // Projection of a point onto the polygon. + Point point_projection(const Point &point) const; }; extern BoundingBox get_extents(const Polygon &poly); diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index aec5be564..a326c7501 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -95,6 +95,8 @@ new_from_points(CLASS, points) void merge_point(Pointf3* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y, double z); + void offset(double delta); + bool contains_point(Pointf3* point) %code{% RETVAL = THIS->contains(*point); %}; Clone size(); Clone center(); double radius(); diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index f569a899d..f5db9f515 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -42,12 +42,20 @@ std::string wkt(); Points concave_points(double angle); Points convex_points(double angle); + Clone point_projection(Point* point) + %code{% RETVAL = THIS->point_projection(*point); %}; Clone intersection(Line* line) %code{% Point p; (void)THIS->intersection(*line, &p); RETVAL = p; %}; + Clone first_intersection(Line* line) + %code{% + Point p; + (void)THIS->first_intersection(*line, &p); + RETVAL = p; + %}; %{ Polygon*