diff --git a/src/common/common.hpp b/src/common/common.hpp index 99a226630..9466d45e6 100644 --- a/src/common/common.hpp +++ b/src/common/common.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "lut.hpp" namespace horizon { @@ -268,6 +269,13 @@ template class Coord { { return {x, y}; } + + std::string str() const + { + std::stringstream ss; + ss << "(" << x << ", " << y << ")"; + return ss.str(); + } }; @@ -333,4 +341,47 @@ constexpr shallow_copy_t shallow_copy = shallow_copy_t(); enum class CopyMode { DEEP, SHALLOW }; + +template class Segment { +public: + Coord from; + Coord to; + + Segment(Coord a, Coord b) : from(a), to(b) + { + } + + bool intersect(const Segment &other, Coord &output) const + { + const T &x1 = from.x; + const T &y1 = from.y; + const T &x2 = to.x; + const T &y2 = to.y; + const T &x3 = other.from.x; + const T &y3 = other.from.y; + const T &x4 = other.to.x; + const T &y4 = other.to.y; + + // See https://en.wikipedia.org/wiki/Line-line_intersection + double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + if (fabs(d) < 1e-6) { + return false; // Parallel or coincident + } + T a = (x1 * y2 - y1 * x2); + T b = (x3 * y4 - y3 * x4); + double x = (a * (x3 - x4) - (x1 - x2) * b) / d; + double y = (a * (y3 - y4) - (y1 - y2) * b) / d; + output.x = x; + output.y = y; + return true; + } + + std::string str() const + { + std::stringstream ss; + ss << "Segment(from=" << from.str() << ", to=" << to.str() << ")"; + return ss.str(); + } +}; + } // namespace horizon diff --git a/src/core/tools/tool_round_off_vertex.cpp b/src/core/tools/tool_round_off_vertex.cpp index e8ca544d6..ff69f5467 100644 --- a/src/core/tools/tool_round_off_vertex.cpp +++ b/src/core/tools/tool_round_off_vertex.cpp @@ -11,7 +11,8 @@ namespace horizon { bool ToolRoundOffVertex::can_begin() { - return sel_count_type(selection, ObjectType::POLYGON_VERTEX) == 1; + return (sel_count_type(selection, ObjectType::POLYGON_VERTEX) == 1 + || sel_count_type(selection, ObjectType::POLYGON_ARC_CENTER) == 1); } int ToolRoundOffVertex::wrap_index(int i) const @@ -27,8 +28,10 @@ int ToolRoundOffVertex::wrap_index(int i) const ToolResponse ToolRoundOffVertex::begin(const ToolArgs &args) { int vertex_idx = 0; + editing = sel_has_type(selection, ObjectType::POLYGON_ARC_CENTER); { - auto x = sel_find_one(selection, ObjectType::POLYGON_VERTEX); + ObjectType type = editing ? ObjectType::POLYGON_ARC_CENTER : ObjectType::POLYGON_VERTEX; + auto x = sel_find_one(selection, type); poly = doc.r->get_polygon(x.uuid); vertex_idx = x.vertex; } @@ -36,21 +39,38 @@ ToolResponse ToolRoundOffVertex::begin(const ToolArgs &args) auto v_next = wrap_index(vertex_idx + 1); auto v_prev = wrap_index(vertex_idx - 1); - if ((poly->vertices.at(vertex_idx).type == Polygon::Vertex::Type::ARC) - || (poly->vertices.at(v_prev).type == Polygon::Vertex::Type::ARC)) { - imp->tool_bar_flash("can't round off arc"); - return ToolResponse::end(); + + if (editing) { + auto v_inserted = v_next; // Inserted vertex + v_next = wrap_index(vertex_idx + 2); + + vxp = &poly->vertices.at(vertex_idx); + vxn = &poly->vertices.at(v_inserted); + + if (!revert_arc(p0, vxp->position, vxn->position, vxp->arc_center)) { + imp->tool_bar_flash("can't undo arc"); + return ToolResponse::end(); + } + } + else { + if ((poly->vertices.at(vertex_idx).type == Polygon::Vertex::Type::ARC) + || (poly->vertices.at(v_prev).type == Polygon::Vertex::Type::ARC)) { + imp->tool_bar_flash("can't round off arc"); + return ToolResponse::end(); + } + p0 = poly->vertices.at(vertex_idx).position; } selection.clear(); - p0 = poly->vertices.at(vertex_idx).position; - vn = (Coordd(poly->vertices.at(v_next).position) - p0).normalize(); - vp = (Coordd(poly->vertices.at(v_prev).position) - p0).normalize(); + Coordd p1 = poly->vertices.at(v_next).position; + Coordd p2 = poly->vertices.at(v_prev).position; + + vn = (p1 - p0).normalize(); + vp = (p2 - p0).normalize(); vh = (vn + vp).normalize(); - delta_max = std::min((poly->vertices.at(v_next).position - poly->vertices.at(vertex_idx).position).magd(), - (poly->vertices.at(v_prev).position - poly->vertices.at(vertex_idx).position).magd()); + delta_max = std::min((p1 - p0).mag(), (p2 - p0).mag()); alpha = acos(vh.dot(vp)); if (isnan(alpha) || (alpha > .99 * (M_PI / 2))) { imp->tool_bar_flash("can't round off collinear edges"); @@ -58,18 +78,20 @@ ToolResponse ToolRoundOffVertex::begin(const ToolArgs &args) } r_max = tan(alpha) * delta_max; - const bool rev = vn.cross(vp) < 0; + if (!editing) { + const bool rev = vn.cross(vp) < 0; - if (v_next == 0) { - poly->vertices.emplace_back(Coordi()); - vxn = &poly->vertices.back(); - } - else { - vxn = &*poly->vertices.emplace(poly->vertices.begin() + v_next, Coordi()); + if (v_next == 0) { + poly->vertices.emplace_back(Coordi()); + vxn = &poly->vertices.back(); + } + else { + vxn = &*poly->vertices.emplace(poly->vertices.begin() + v_next, Coordi()); + } + vxp = &poly->vertices.at(vertex_idx); + vxp->type = Polygon::Vertex::Type::ARC; + vxp->arc_reverse = rev; } - vxp = &poly->vertices.at(vertex_idx); - vxp->type = Polygon::Vertex::Type::ARC; - vxp->arc_reverse = rev; imp->set_snap_filter({{ObjectType::POLYGON, poly->uuid}}); @@ -122,7 +144,7 @@ ToolResponse ToolRoundOffVertex::update(const ToolArgs &args) else if (args.type == ToolEventType::ACTION) { switch (args.action) { case InToolActionID::LMB: - if (plane_finish()) + if (commit()) return ToolResponse::commit(); else return ToolResponse::revert(); @@ -153,7 +175,7 @@ ToolResponse ToolRoundOffVertex::update(const ToolArgs &args) } else if (data->event == ToolDataWindow::Event::OK) { imp->dialogs.close_nonmodal(); - if (plane_finish()) + if (commit()) return ToolResponse::commit(); else return ToolResponse::revert(); @@ -162,4 +184,27 @@ ToolResponse ToolRoundOffVertex::update(const ToolArgs &args) } return ToolResponse(); } + +bool ToolRoundOffVertex::commit() +{ + if (radius_current < 1) { + // Tiny radius, just ignore it + if (!editing) + return false; // Not a modification so just revert the action + + // Undo change to original vertex + vxp->arc_center = Coordi(); + vxp->arc_reverse = false; + vxp->position = p0.to_coordi(); + vxp->type = Polygon::Vertex::Type::LINE; + + // Remove inserted vertex + vxn->remove = true; + poly->vertices.erase( + std::remove_if(poly->vertices.begin(), poly->vertices.end(), [](const auto &x) { return x.remove; }), + poly->vertices.end()); + } + return plane_finish(); +} + } // namespace horizon diff --git a/src/core/tools/tool_round_off_vertex.hpp b/src/core/tools/tool_round_off_vertex.hpp index 9c09d8d6e..95d139712 100644 --- a/src/core/tools/tool_round_off_vertex.hpp +++ b/src/core/tools/tool_round_off_vertex.hpp @@ -37,9 +37,11 @@ class ToolRoundOffVertex : public virtual ToolBase, public ToolHelperPlane { double r_max = 0; double alpha = 0; double radius_current = 0; + bool editing = false; void update_poly(double r); void update_cursor(const Coordi &c); void update_tip(); + bool commit(); }; } // namespace horizon diff --git a/src/util/geom_util.cpp b/src/util/geom_util.cpp index fdaa0c702..9810205b9 100644 --- a/src/util/geom_util.cpp +++ b/src/util/geom_util.cpp @@ -181,4 +181,24 @@ Placement transform_text_placement_to_new_reference(Placement pl, Placement old_ return out; } + +/** + * Determine original point that the arc rounded off. If the parameters are + * invalid or it cannot determine the proper value return false. + */ +bool revert_arc(Coordd &output, const Coordd &p0, const Coordd &p1, const Coordd &c) +{ + // The original center position is found by computing the intersection of + // lines defined by each arc point and their tangents. Tangents are of an + // arc perpendicular to vector to center. + const Coordd v0 = (p0 - c); + const Coordd v1 = (p1 - c); + double a = v0.cross(v1) > 0 ? M_PI / 2 : -M_PI / 2; // CW / CCW + const Coordd t0 = v0.rotate(a); + const Coordd t1 = v1.rotate(-a); + Segment l0 = Segment(p0, t0 + p0); + Segment l1 = Segment(p1, t1 + p1); + return l0.intersect(l1, output); +} + } // namespace horizon diff --git a/src/util/geom_util.hpp b/src/util/geom_util.hpp index 91238dde2..fbfaf1893 100644 --- a/src/util/geom_util.hpp +++ b/src/util/geom_util.hpp @@ -35,4 +35,6 @@ template T c2pi(T x); Placement transform_package_placement_to_new_reference(Placement pl, Placement old_ref, Placement new_ref); Placement transform_text_placement_to_new_reference(Placement pl, Placement old_ref, Placement new_ref); +bool revert_arc(Coordd &output, const Coordd &p0, const Coordd &p1, const Coordd &c); + } // namespace horizon