mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-12 09:17:52 -06:00

1. first formal version of macos 2. add the bambu networking plugin install logic 3. auto compute the wipe volume when filament change 4. add the logic of wiping into support 5. refine the GUI layout and icons, improve the gui apperance in lots of small places 6. serveral improve to support 7. support AMS auto-mapping 8. disable lots of unstable features: such as params table, media file download, HMS 9. fix serveral kinds of bugs 10. update the document of building 11. ...
516 lines
No EOL
20 KiB
C++
516 lines
No EOL
20 KiB
C++
#include "Circle.hpp"
|
|
|
|
#include <cmath>
|
|
#include <cassert>
|
|
#include "Geometry.hpp"
|
|
|
|
|
|
//BBS: Refer to ArcWelderLib for the arc fitting functions
|
|
|
|
namespace Slic3r {
|
|
|
|
//BBS: threshold used to judge collineation
|
|
static const double Parallel_area_threshold = 0.0001;
|
|
|
|
bool Circle::try_create_circle(const Point& p1, const Point& p2, const Point& p3, const double max_radius, Circle& new_circle)
|
|
{
|
|
double x1 = p1.x();
|
|
double y1 = p1.y();
|
|
double x2 = p2.x();
|
|
double y2 = p2.y();
|
|
double x3 = p3.x();
|
|
double y3 = p3.y();
|
|
|
|
//BBS: use area of triangle to judge whether three points are almostly on one line
|
|
//Because the point is scale_ once, so area should scale_ twice.
|
|
if (fabs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) <= scale_(scale_(Parallel_area_threshold)))
|
|
return false;
|
|
|
|
double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
|
|
//BBS: take out to figure out how we handle very small values
|
|
if (fabs(a) < SCALED_EPSILON)
|
|
return false;
|
|
|
|
double b = (x1 * x1 + y1 * y1) * (y3 - y2)
|
|
+ (x2 * x2 + y2 * y2) * (y1 - y3)
|
|
+ (x3 * x3 + y3 * y3) * (y2 - y1);
|
|
|
|
double c = (x1 * x1 + y1 * y1) * (x2 - x3)
|
|
+ (x2 * x2 + y2 * y2) * (x3 - x1)
|
|
+ (x3 * x3 + y3 * y3) * (x1 - x2);
|
|
|
|
double center_x = -b / (2.0 * a);
|
|
double center_y = -c / (2.0 * a);
|
|
|
|
double delta_x = center_x - x1;
|
|
double delta_y = center_y - y1;
|
|
double radius = sqrt(delta_x * delta_x + delta_y * delta_y);
|
|
if (radius > max_radius)
|
|
return false;
|
|
|
|
new_circle.center = Point(center_x, center_y);
|
|
new_circle.radius = radius;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Circle::try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle)
|
|
{
|
|
size_t count = points.size();
|
|
size_t middle_index = count / 2;
|
|
// BBS: the middle point will almost always produce the best arcs with high possibility.
|
|
if (count == 3) {
|
|
return (Circle::try_create_circle(points[0], points[middle_index], points[count - 1], max_radius, new_circle)
|
|
&& !new_circle.is_over_deviation(points, tolerance));
|
|
} else {
|
|
Point middle_point = (count % 2 == 0) ? (points[middle_index] + points[middle_index - 1]) / 2 :
|
|
(points[middle_index - 1] + points[middle_index + 1]) / 2;
|
|
if (Circle::try_create_circle(points[0], middle_point, points[count - 1], max_radius, new_circle)
|
|
&& !new_circle.is_over_deviation(points, tolerance))
|
|
return true;
|
|
}
|
|
|
|
// BBS: Find the circle with the least deviation, if one exists.
|
|
Circle test_circle;
|
|
double least_deviation;
|
|
bool found_circle = false;
|
|
double current_deviation;
|
|
for (int index = 1; index < count - 1; index++)
|
|
{
|
|
if (index == middle_index)
|
|
// BBS: We already checked this one, and it failed. don't need to do again
|
|
continue;
|
|
|
|
if (Circle::try_create_circle(points[0], points[index], points[count - 1], max_radius, test_circle) && test_circle.get_deviation_sum_squared(points, tolerance, current_deviation))
|
|
{
|
|
if (!found_circle || current_deviation < least_deviation)
|
|
{
|
|
found_circle = true;
|
|
least_deviation = current_deviation;
|
|
new_circle = test_circle;
|
|
}
|
|
}
|
|
}
|
|
return found_circle;
|
|
}
|
|
|
|
double Circle::get_polar_radians(const Point& p1) const
|
|
{
|
|
double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x());
|
|
if (polar_radians < 0)
|
|
polar_radians = (2.0 * PI) + polar_radians;
|
|
return polar_radians;
|
|
}
|
|
|
|
bool Circle::is_over_deviation(const Points& points, const double tolerance)
|
|
{
|
|
Point closest_point;
|
|
Point temp;
|
|
double distance_from_center;
|
|
// BBS: skip the first and last points since they has fit perfectly.
|
|
for (size_t index = 0; index < points.size() - 1; index++)
|
|
{
|
|
if (index != 0)
|
|
{
|
|
//BBS: check fitting tolerance
|
|
temp = points[index] - center;
|
|
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
|
if (std::fabs(distance_from_center - radius) > tolerance)
|
|
return true;
|
|
}
|
|
|
|
//BBS: Check the point perpendicular from the segment to the circle's center
|
|
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
|
temp = closest_point - center;
|
|
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
|
if (std::fabs(distance_from_center - radius) > tolerance)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Circle::get_closest_perpendicular_point(const Point& p1, const Point& p2, const Point& c, Point& out)
|
|
{
|
|
double x1 = p1.x();
|
|
double y1 = p1.y();
|
|
double x2 = p2.x();
|
|
double y2 = p2.y();
|
|
double x_dif = x2 - x1;
|
|
double y_dif = y2 - y1;
|
|
//BBS: [(Cx - Ax)(Bx - Ax) + (Cy - Ay)(By - Ay)] / [(Bx - Ax) ^ 2 + (By - Ay) ^ 2]
|
|
double num = (c[0] - x1) * x_dif + (c[1] - y1) * y_dif;
|
|
double denom = (x_dif * x_dif) + (y_dif * y_dif);
|
|
double t = num / denom;
|
|
|
|
//BBS: Considering this a failure if t == 0 or t==1 within tolerance. In that case we hit the endpoint, which is OK.
|
|
if (Circle::less_than_or_equal(t, 0) || Circle::greater_than_or_equal(t, 1))
|
|
return false;
|
|
|
|
out[0] = x1 + t * (x2 - x1);
|
|
out[1] = y1 + t * (y2 - y1);
|
|
return true;
|
|
}
|
|
|
|
bool Circle::get_deviation_sum_squared(const Points& points, const double tolerance, double& total_deviation)
|
|
{
|
|
total_deviation = 0;
|
|
Point temp;
|
|
double distance_from_center, deviation;
|
|
// BBS: skip the first and last points since they are on the circle
|
|
for (int index = 1; index < points.size() - 1; index++)
|
|
{
|
|
//BBS: make sure the length from the center of our circle to the test point is
|
|
// at or below our max distance.
|
|
temp = points[index] - center;
|
|
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
|
deviation = std::fabs(distance_from_center - radius);
|
|
total_deviation += deviation * deviation;
|
|
if (deviation > tolerance)
|
|
return false;
|
|
|
|
}
|
|
Point closest_point;
|
|
//BBS: check the point perpendicular from the segment to the circle's center
|
|
for (int index = 0; index < points.size() - 1; index++)
|
|
{
|
|
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
|
temp = closest_point - center;
|
|
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
|
deviation = std::fabs(distance_from_center - radius);
|
|
total_deviation += deviation * deviation;
|
|
if (deviation > tolerance)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//BBS: only support calculate on X-Y plane, Z is useless
|
|
Vec3f Circle::calc_tangential_vector(const Vec3f& pos, const Vec3f& center_pos, const bool is_ccw)
|
|
{
|
|
Vec3f dir = center_pos - pos;
|
|
dir(2,0) = 0;
|
|
dir.normalize();
|
|
Vec3f res;
|
|
if (is_ccw)
|
|
res = { dir(1, 0), -dir(0, 0), 0.0f };
|
|
else
|
|
res = { -dir(1, 0), dir(0, 0), 0.0f };
|
|
return res;
|
|
}
|
|
|
|
bool ArcSegment::reverse()
|
|
{
|
|
if (!is_valid())
|
|
return false;
|
|
std::swap(start_point, end_point);
|
|
direction = (direction == ArcDirection::Arc_Dir_CCW) ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
|
angle_radians *= -1.0;
|
|
std::swap(polar_start_theta, polar_end_theta);
|
|
return true;
|
|
}
|
|
|
|
bool ArcSegment::clip_start(const Point &point)
|
|
{
|
|
if (!is_valid() || point == center || !is_point_inside(point))
|
|
return false;
|
|
start_point = get_closest_point(point);
|
|
update_angle_and_length();
|
|
return true;
|
|
}
|
|
|
|
bool ArcSegment::clip_end(const Point &point)
|
|
{
|
|
if (!is_valid() || point == center || !is_point_inside(point))
|
|
return false;
|
|
end_point = get_closest_point(point);
|
|
update_angle_and_length();
|
|
return true;
|
|
}
|
|
|
|
bool ArcSegment::split_at(const Point &point, ArcSegment& p1, ArcSegment& p2)
|
|
{
|
|
if (!is_valid() || point == center || !is_point_inside(point))
|
|
return false;
|
|
Point segment_point = get_closest_point(point);
|
|
p1 = ArcSegment(center, radius, this->start_point, segment_point, this->direction);
|
|
p2 = ArcSegment(center, radius, segment_point, this->end_point, this->direction);
|
|
return true;
|
|
}
|
|
|
|
bool ArcSegment::is_point_inside(const Point& point) const
|
|
{
|
|
double polar_theta = get_polar_radians(point);
|
|
double radian_delta = polar_theta - polar_start_theta;
|
|
if (radian_delta > 0 && direction == ArcDirection::Arc_Dir_CW)
|
|
radian_delta = radian_delta - 2 * M_PI;
|
|
else if (radian_delta < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
|
radian_delta = radian_delta + 2 * M_PI;
|
|
|
|
return (direction == ArcDirection::Arc_Dir_CCW ?
|
|
radian_delta > 0.0 && radian_delta < angle_radians :
|
|
radian_delta < 0.0 && radian_delta > angle_radians);
|
|
}
|
|
|
|
void ArcSegment::update_angle_and_length()
|
|
{
|
|
polar_start_theta = get_polar_radians(start_point);
|
|
polar_end_theta = get_polar_radians(end_point);
|
|
angle_radians = polar_end_theta - polar_start_theta;
|
|
if (angle_radians < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
|
angle_radians = angle_radians + 2 * M_PI;
|
|
else if (angle_radians > 0 && direction == ArcDirection::Arc_Dir_CW)
|
|
angle_radians = angle_radians - 2 * M_PI;
|
|
length = fabs(angle_radians) * radius;
|
|
is_arc = true;
|
|
}
|
|
|
|
bool ArcSegment::try_create_arc(
|
|
const Points& points,
|
|
ArcSegment& target_arc,
|
|
double approximate_length,
|
|
double max_radius,
|
|
double tolerance,
|
|
double path_tolerance_percent)
|
|
{
|
|
Circle test_circle = (Circle)target_arc;
|
|
if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle))
|
|
return false;
|
|
|
|
int mid_point_index = ((points.size() - 2) / 2) + 1;
|
|
ArcSegment test_arc;
|
|
if (!ArcSegment::try_create_arc(test_circle, points[0], points[mid_point_index], points[points.size() - 1], test_arc, approximate_length, path_tolerance_percent))
|
|
return false;
|
|
|
|
if (ArcSegment::are_points_within_slice(test_arc, points))
|
|
{
|
|
target_arc = test_arc;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ArcSegment::try_create_arc(
|
|
const Circle& c,
|
|
const Point& start_point,
|
|
const Point& mid_point,
|
|
const Point& end_point,
|
|
ArcSegment& target_arc,
|
|
double approximate_length,
|
|
double path_tolerance_percent)
|
|
{
|
|
double polar_start_theta = c.get_polar_radians(start_point);
|
|
double polar_mid_theta = c.get_polar_radians(mid_point);
|
|
double polar_end_theta = c.get_polar_radians(end_point);
|
|
|
|
double angle_radians = 0;
|
|
ArcDirection direction = ArcDirection::Arc_Dir_unknow;
|
|
//BBS: calculate the direction of the arc
|
|
if (polar_end_theta > polar_start_theta) {
|
|
if (polar_start_theta < polar_mid_theta && polar_mid_theta < polar_end_theta) {
|
|
direction = ArcDirection::Arc_Dir_CCW;
|
|
angle_radians = polar_end_theta - polar_start_theta;
|
|
} else if ((0.0 <= polar_mid_theta && polar_mid_theta < polar_start_theta) ||
|
|
(polar_end_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI))) {
|
|
direction = ArcDirection::Arc_Dir_CW;
|
|
angle_radians = polar_start_theta + ((2.0 * PI) - polar_end_theta);
|
|
}
|
|
} else if (polar_start_theta > polar_end_theta) {
|
|
if ((polar_start_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI)) ||
|
|
(0.0 < polar_mid_theta && polar_mid_theta < polar_end_theta)) {
|
|
direction = ArcDirection::Arc_Dir_CCW;
|
|
angle_radians = polar_end_theta + ((2.0 * PI) - polar_start_theta);
|
|
} else if (polar_end_theta < polar_mid_theta && polar_mid_theta < polar_start_theta) {
|
|
direction = ArcDirection::Arc_Dir_CW;
|
|
angle_radians = polar_start_theta - polar_end_theta;
|
|
}
|
|
}
|
|
|
|
// BBS: this doesn't always work.. in rare situations, the angle may be backward
|
|
if (direction == ArcDirection::Arc_Dir_unknow || std::fabs(angle_radians) < EPSILON)
|
|
return false;
|
|
|
|
// BBS: Check the length against the original length.
|
|
// This can trigger simply due to the differing path lengths
|
|
// but also could indicate that the vector calculation above
|
|
// got wrong direction
|
|
double arc_length = c.radius * angle_radians;
|
|
double difference = (arc_length - approximate_length) / approximate_length;
|
|
if (std::fabs(difference) >= path_tolerance_percent)
|
|
{
|
|
// BBS: So it's possible that vector calculation above got wrong direction.
|
|
// This can happen if there is a crazy arrangement of points
|
|
// extremely close to eachother. They have to be close enough to
|
|
// break our other checks. However, we may be able to salvage this.
|
|
// see if an arc moving in the opposite direction had the correct length.
|
|
|
|
//BBS: Find the rest of the angle across the circle
|
|
double test_radians = std::fabs(angle_radians - 2 * PI);
|
|
// Calculate the length of that arc
|
|
double test_arc_length = c.radius * test_radians;
|
|
difference = (test_arc_length - approximate_length) / approximate_length;
|
|
if (std::fabs(difference) >= path_tolerance_percent)
|
|
return false;
|
|
//BBS: Set the new length and flip the direction (but not the angle)!
|
|
arc_length = test_arc_length;
|
|
direction = direction == ArcDirection::Arc_Dir_CCW ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
|
}
|
|
|
|
if (direction == ArcDirection::Arc_Dir_CW)
|
|
angle_radians *= -1.0;
|
|
|
|
target_arc.is_arc = true;
|
|
target_arc.direction = direction;
|
|
target_arc.center = c.center;
|
|
target_arc.radius = c.radius;
|
|
target_arc.start_point = start_point;
|
|
target_arc.end_point = end_point;
|
|
target_arc.length = arc_length;
|
|
target_arc.angle_radians = angle_radians;
|
|
target_arc.polar_start_theta = polar_start_theta;
|
|
target_arc.polar_end_theta = polar_end_theta;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points& points)
|
|
{
|
|
//BBS: Check all the points and see if they fit inside of the angles
|
|
double previous_polar = test_arc.polar_start_theta;
|
|
bool will_cross_zero = false;
|
|
bool crossed_zero = false;
|
|
const int point_count = points.size();
|
|
|
|
Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
|
((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
|
Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
|
((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
|
|
|
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
|
will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta;
|
|
else
|
|
will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta;
|
|
|
|
//BBS: check if point 1 to point 2 cross zero
|
|
double polar_test;
|
|
for (int index = point_count - 2; index < point_count; index++)
|
|
{
|
|
if (index < point_count - 1)
|
|
polar_test = test_arc.get_polar_radians(points[index]);
|
|
else
|
|
polar_test = test_arc.polar_end_theta;
|
|
|
|
//BBS: First ensure the test point is within the arc
|
|
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
|
{
|
|
//BBS: Only check to see if we are within the arc if this isn't the endpoint
|
|
if (index < point_count - 1) {
|
|
if (will_cross_zero) {
|
|
if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta))
|
|
return false;
|
|
} else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta))
|
|
return false;
|
|
}
|
|
//BBS: check the angles are increasing
|
|
if (previous_polar > polar_test) {
|
|
if (!will_cross_zero)
|
|
return false;
|
|
|
|
//BBS: Allow the angle to cross zero once
|
|
if (crossed_zero)
|
|
return false;
|
|
crossed_zero = true;
|
|
}
|
|
} else {
|
|
if (index < point_count - 1) {
|
|
if (will_cross_zero) {
|
|
if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta))
|
|
return false;
|
|
} else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta))
|
|
return false;
|
|
}
|
|
//BBS: Now make sure the angles are decreasing
|
|
if (previous_polar < polar_test)
|
|
{
|
|
if (!will_cross_zero)
|
|
return false;
|
|
//BBS: Allow the angle to cross zero once
|
|
if (crossed_zero)
|
|
return false;
|
|
crossed_zero = true;
|
|
}
|
|
}
|
|
|
|
// BBS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc
|
|
Line segmemt(points[index - 1], points[index]);
|
|
if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) ||
|
|
(index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt)))
|
|
return false;
|
|
previous_polar = polar_test;
|
|
}
|
|
//BBS: Ensure that all arcs that cross zero
|
|
if (will_cross_zero != crossed_zero)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// BBS: this function is used to detect whether a ray cross the segment
|
|
bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment)
|
|
{
|
|
Vec2d v1 = Vec2d(rayOrigin.x() - segment.a.x(), rayOrigin.y() - segment.a.y());
|
|
Vec2d v2 = Vec2d(segment.b.x() - segment.a.x(), segment.b.y() - segment.a.y());
|
|
Vec2d v3 = Vec2d(-rayDirection(1), rayDirection(0));
|
|
|
|
double dot = v2(0) * v3(0) + v2(1) * v3(1);
|
|
if (std::fabs(dot) < SCALED_EPSILON)
|
|
return false;
|
|
|
|
double t1 = (v2(0) * v1(1) - v2(1) * v1(0)) / dot;
|
|
double t2 = (v1(0) * v3(0) + v1(1) * v3(1)) / dot;
|
|
|
|
if (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// BBS: new function to calculate arc radian in X-Y plane
|
|
float ArcSegment::calc_arc_radian(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
|
{
|
|
Vec3f delta1 = center_pos - start_pos;
|
|
Vec3f delta2 = center_pos - end_pos;
|
|
// only consider arc in x-y plane, so clean z distance
|
|
delta1(2,0) = 0;
|
|
delta2(2,0) = 0;
|
|
|
|
float radian;
|
|
if ((delta1 - delta2).norm() < 1e-6) {
|
|
// start_pos is same with end_pos, we think it's a full circle
|
|
radian = 2 * M_PI;
|
|
} else {
|
|
double dot = delta1.dot(delta2);
|
|
double cross = (double)delta1(0, 0) * (double)delta2(1, 0) - (double)delta1(1, 0) * (double)delta2(0, 0);
|
|
radian = atan2(cross, dot);
|
|
if (is_ccw)
|
|
radian = (radian < 0) ? 2 * M_PI + radian : radian;
|
|
else
|
|
radian = (radian < 0) ? abs(radian) : 2 * M_PI - radian;
|
|
}
|
|
return radian;
|
|
}
|
|
|
|
float ArcSegment::calc_arc_radius(Vec3f start_pos, Vec3f center_pos)
|
|
{
|
|
Vec3f delta1 = center_pos - start_pos;
|
|
delta1(2,0) = 0;
|
|
return delta1.norm();
|
|
}
|
|
|
|
// BBS: new function to calculate arc length in X-Y plane
|
|
float ArcSegment::calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
|
{
|
|
return calc_arc_radius(start_pos, center_pos) * calc_arc_radian(start_pos, end_pos, center_pos, is_ccw);
|
|
}
|
|
|
|
} |