Merge pull request 'Perpendicular' (#7) from Perpendicular into master

Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
2025-12-15 20:46:36 +01:00
5 changed files with 171 additions and 66 deletions

View File

@@ -1,4 +1,6 @@
#include "Canvas.h" #include "Canvas.h"
#define WIDGET_POSITION event->pos()
#define UCS_POSITION screenToLogical(WIDGET_POSITION)
// =================================================================== // ===================================================================
// Вспомогательные функции // Вспомогательные функции
@@ -78,7 +80,6 @@ void Canvas::changeMode(Mode _mode)
Line* Canvas::findAt(QPointF& pos, qreal tolerance) Line* Canvas::findAt(QPointF& pos, qreal tolerance)
{ {
// TODO: реализовать проверку, находится ли точка на линии
for (Line* line : lines) { for (Line* line : lines) {
if (line->contains(pos, tolerance)) { if (line->contains(pos, tolerance)) {
return line; return line;
@@ -136,48 +137,62 @@ bool Canvas::areAlreadyParallel(Line* l1, Line* l2)
return parallelPairs.count(makeOrderedPair<LinePair>(l1, l2)); return parallelPairs.count(makeOrderedPair<LinePair>(l1, l2));
} }
bool Canvas::areAlreadyPerpendicular(Line* line1, Line* line2)
{
return perpendicularPairs.count(makeOrderedPair<LinePair>(line1, line2));
}
// =================================================================== // ===================================================================
// Методы работы с ограничениями // Методы работы с ограничениями
// =================================================================== // ===================================================================
void Canvas::remove_constraints() // TODO - переделать в bool для отображения статуса выполнения
void Canvas::remove_constraint(int tag)
{ {
auto it = C_Info.find(tag);
if (it == C_Info.end()) return;
ConstraintInfo info = it->second;
// Удаляем ограничение из солвера // Удаляем ограничение из солвера
sys.clearByTag(constraints_count - 1); sys.clearByTag(tag);
// Удаляем из соответствующих контейнеров в зависимости от типа ограничения // Удаляем из соответствующих контейнеров в зависимости от типа ограничения
switch (lastConstraint.mode) { switch (info.mode) {
case Mode::Horizontal: { case Mode::Horizontal: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) { if (auto* pair = std::get_if<PointPair>(&info.data)) {
HORIZ_pairs.erase(*pair); HORIZ_pairs.erase(*pair);
} }
break; break;
} }
case Mode::Vertical: { case Mode::Vertical: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) { if (auto* pair = std::get_if<PointPair>(&info.data)) {
VERT_pairs.erase(*pair); VERT_pairs.erase(*pair);
} }
break; break;
} }
case Mode::Parallel: { case Mode::Parallel: {
if (auto* pair = std::get_if<LinePair>(&lastConstraint.data)) { if (auto* pair = std::get_if<LinePair>(&info.data)) {
parallelPairs.erase(*pair); parallelPairs.erase(*pair);
} }
break; break;
} }
case Mode::Coincedent: { case Mode::Coincedent: {
if (auto* pair = std::get_if<PointPair>(&lastConstraint.data)) { if (auto* pair = std::get_if<PointPair>(&info.data)) {
P2Ppairs.erase(*pair); P2Ppairs.erase(*pair);
} }
break; break;
} }
case Mode::Perpendicular: {
if (auto* pair = std::get_if<LinePair>(&info.data)) {
perpendicularPairs.erase(*pair);
}
break;
}
default: default:
break; break;
} }
C_Info.erase(it);
// Сбрасываем информацию о последнем ограничении
lastConstraint.mode = Mode::None;
constraints_count--;
} }
// =================================================================== // ===================================================================
@@ -244,17 +259,15 @@ void Canvas::mousePressEvent(QMouseEvent* event)
sys.addConstraintHorizontal(*found, constraints_count++); sys.addConstraintHorizontal(*found, constraints_count++);
auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref); auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref);
HORIZ_pairs.insert(pair); HORIZ_pairs.insert(pair);
lastConstraint.mode = Mode::Horizontal; C_Info[constraints_count - 1] = { Mode::Horizontal, pair };
lastConstraint.data = pair;
} }
else { else {
sys.addConstraintVertical(*found, constraints_count++); sys.addConstraintVertical(*found, constraints_count++);
auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref); auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref);
VERT_pairs.insert(pair); VERT_pairs.insert(pair);
lastConstraint.mode = Mode::Vertical; C_Info[constraints_count - 1] = { Mode::Vertical, pair };
lastConstraint.data = pair;
} }
update(); solve_for_canvas();
after_constraint = true; after_constraint = true;
} }
mode = Mode::None; mode = Mode::None;
@@ -315,7 +328,7 @@ void Canvas::mousePressEvent(QMouseEvent* event)
mode = Mode::None; mode = Mode::None;
QMessageBox::critical(this, "WHOOPS", QMessageBox::critical(this, "WHOOPS",
"Sorry, your line is very short", QMessageBox::Ok); "Sorry, your line is very short", QMessageBox::Ok);
update(); solve_for_canvas();
return; return;
} }
@@ -330,7 +343,7 @@ void Canvas::mousePressEvent(QMouseEvent* event)
obj_count++; obj_count++;
after_constraint = true; after_constraint = true;
} }
update(); solve_for_canvas();
return; return;
} }
@@ -340,21 +353,21 @@ void Canvas::mousePressEvent(QMouseEvent* event)
if (!found) { if (!found) {
current_line = nullptr; current_line = nullptr;
update(); solve_for_canvas();
return; return;
} }
// Первый клик: выбираем первую линию // Первый клик: выбираем первую линию
if (!current_line) { if (!current_line) {
current_line = found; current_line = found;
update(); solve_for_canvas();
return; return;
} }
// Повторный клик на ту же линию: сброс выбора // Повторный клик на ту же линию: сброс выбора
if (found == current_line) { if (found == current_line) {
current_line = nullptr; current_line = nullptr;
update(); solve_for_canvas();
return; return;
} }
@@ -363,13 +376,12 @@ void Canvas::mousePressEvent(QMouseEvent* event)
auto pair = makeOrderedPair<LinePair>(found, current_line); auto pair = makeOrderedPair<LinePair>(found, current_line);
sys.addConstraintParallel(*found, *current_line, constraints_count++); sys.addConstraintParallel(*found, *current_line, constraints_count++);
parallelPairs.insert(pair); parallelPairs.insert(pair);
lastConstraint.mode = Mode::Parallel; C_Info[constraints_count - 1] = { Mode::Parallel, pair };
lastConstraint.data = pair;
current_line = nullptr; current_line = nullptr;
mode = Mode::None; mode = Mode::None;
after_constraint = true; after_constraint = true;
update(); solve_for_canvas();
} }
else { else {
// Линии уже параллельны - сообщаем об ошибке // Линии уже параллельны - сообщаем об ошибке
@@ -383,7 +395,7 @@ void Canvas::mousePressEvent(QMouseEvent* event)
QMessageBox::Ok QMessageBox::Ok
); );
current_line = nullptr; current_line = nullptr;
update(); solve_for_canvas();
return; return;
} }
} }
@@ -393,21 +405,21 @@ void Canvas::mousePressEvent(QMouseEvent* event)
Point* clickedPoint = findPointAt(scene); Point* clickedPoint = findPointAt(scene);
if (!clickedPoint) { if (!clickedPoint) {
firstPoint = nullptr; firstPoint = nullptr;
update(); solve_for_canvas();
return; return;
} }
// Первый клик: выбираем первую точку // Первый клик: выбираем первую точку
if (!firstPoint) { if (!firstPoint) {
firstPoint = clickedPoint; firstPoint = clickedPoint;
update(); solve_for_canvas();
return; return;
} }
// Повторный клик на ту же точку: сброс выбора // Повторный клик на ту же точку: сброс выбора
if (clickedPoint == firstPoint) { if (clickedPoint == firstPoint) {
firstPoint = nullptr; firstPoint = nullptr;
update(); solve_for_canvas();
return; return;
} }
@@ -426,7 +438,7 @@ void Canvas::mousePressEvent(QMouseEvent* event)
QMessageBox::critical(this, QString("NO!"), QString("P2P failed")); QMessageBox::critical(this, QString("NO!"), QString("P2P failed"));
firstPoint = nullptr; firstPoint = nullptr;
mode = Mode::None; mode = Mode::None;
update(); solve_for_canvas();
return; return;
} }
@@ -434,15 +446,61 @@ void Canvas::mousePressEvent(QMouseEvent* event)
sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++); sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++);
auto pair = makeOrderedPair<PointPair>(clickedPoint, firstPoint); auto pair = makeOrderedPair<PointPair>(clickedPoint, firstPoint);
P2Ppairs.insert(pair); P2Ppairs.insert(pair);
lastConstraint.mode = Mode::Coincedent; C_Info[constraints_count - 1] = { Mode::Coincedent, pair };
lastConstraint.data = pair;
firstPoint = nullptr; firstPoint = nullptr;
mode = Mode::None; mode = Mode::None;
after_constraint = true; after_constraint = true;
update(); solve_for_canvas();
return; return;
} }
else if (mode == Mode::Perpendicular) {
Line* found = findAt(scene);
if (!found) {
current_line = nullptr;
solve_for_canvas();
return;
}
// Первый клик: выбираем первую линию
if (!current_line) {
current_line = found;
solve_for_canvas();
return;
}
// Повторный клик на ту же линию: сброс выбора
if (found == current_line) {
current_line = nullptr;
solve_for_canvas();
return;
}
if (!areAlreadyPerpendicular(found, current_line)) {
auto pair = makeOrderedPair<LinePair>(found, current_line);
sys.addConstraintPerpendicular(*found, *current_line, constraints_count++);
perpendicularPairs.insert(pair);
C_Info[constraints_count - 1] = { Mode::Perpendicular, pair };
current_line = nullptr;
mode = Mode::None;
after_constraint = true;
solve_for_canvas();
}
else {
QMessageBox::warning(this,
QString("Wrong"),
QString("Perpendicular lines can not be more perpendicular!"),
QMessageBox::Ok
);
current_line = nullptr;
solve_for_canvas();
return;
}
}
} }
void Canvas::mouseMoveEvent(QMouseEvent* event) void Canvas::mouseMoveEvent(QMouseEvent* event)
@@ -451,8 +509,18 @@ void Canvas::mouseMoveEvent(QMouseEvent* event)
if (draggedPoint) { if (draggedPoint) {
QPointF pos = UCS_POSITION - dragOffset; QPointF pos = UCS_POSITION - dragOffset;
auto coincidentGroup = getCoincidentGroup(draggedPoint); *draggedPoint->x = pos.x();
*draggedPoint->y = pos.y();
// TODO
for (Point* pair : points) {
if (areCoincident(draggedPoint, pair)) {
*pair->x = pos.x();
*pair->y = pos.y();
}
}
auto coincidentGroup = getCoincidentGroup(draggedPoint);
for (Point* pt : coincidentGroup) { for (Point* pt : coincidentGroup) {
*pt->x = pos.x(); *pt->x = pos.x();
*pt->y = pos.y(); *pt->y = pos.y();
@@ -469,9 +537,16 @@ void Canvas::mouseMoveEvent(QMouseEvent* event)
} }
} }
*draggedPoint->x = pos.x(); for (Point* other : points) {
*draggedPoint->y = pos.y(); if (areHorizontalVertical(draggedPoint, other, true)) {
update(); *other->x = pos.x();
}
if (areHorizontalVertical(draggedPoint, other, false)) {
*other->y = pos.y();
}
}
solve_for_canvas();
} }
// ====================== Перемещение линии ====================== // ====================== Перемещение линии ======================
@@ -503,7 +578,7 @@ void Canvas::mouseMoveEvent(QMouseEvent* event)
} }
} }
} }
update(); solve_for_canvas();
} }
#ifdef _DEBUG #ifdef _DEBUG
else else
@@ -517,12 +592,12 @@ void Canvas::mouseReleaseEvent(QMouseEvent* event)
if (draggedPoint) { if (draggedPoint) {
draggedPoint = nullptr; draggedPoint = nullptr;
update(); solve_for_canvas();
} }
if (draggedLine) { if (draggedLine) {
draggedLine = nullptr; draggedLine = nullptr;
update(); solve_for_canvas();
} }
} }
@@ -543,22 +618,6 @@ void Canvas::paintEvent(QPaintEvent* event)
p.drawLine(-5, 0, 5, 0); p.drawLine(-5, 0, 5, 0);
p.drawLine(0, -5, 0, 5); p.drawLine(0, -5, 0, 5);
// ====================== Решение системы уравнений ======================
if (!params.empty()) {
int res = sys.solve(params);
if (res == SolveStatus::Success || res == SolveStatus::Converged) {
sys.applySolution();
}
else if (res == SolveStatus::Failed && after_constraint) {
// Ошибка решения: удаляем последнее добавленное ограничение
QMessageBox::warning(this, QString("Error!"),
QString("Last constraint is unavailable!"));
remove_constraints();
}
after_constraint = false;
}
// ====================== Отрисовка линий ====================== // ====================== Отрисовка линий ======================
for (Line* line : lines) { for (Line* line : lines) {
bool isSelected = (mode == Mode::Parallel && line == current_line); bool isSelected = (mode == Mode::Parallel && line == current_line);
@@ -651,3 +710,30 @@ std::vector<Point*> Canvas::getCoincidentGroup(Point* p)
} }
return group; return group;
} }
void Canvas::solve_for_canvas()
{
bool flag = false;
int res = sys.solve(params);
if (res != SolveStatus::Success && res != SolveStatus::Converged) {
flag = true;
}
else {
sys.applySolution();
for (Line* line : lines) {
if (abs(*line->p1.x - *line->p2.x) < EPS && abs(*line->p1.y - *line->p2.y) < EPS && after_constraint) {
sys.undoSolution();
flag = true;
break;
}
}
}
if (flag) {
QMessageBox::warning(this, QString("Error!"), QString("Last constraint is unavailable!"));
remove_constraint(constraints_count - 1);
constraints_count--;
}
after_constraint = false;
update();
}

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
constexpr auto EPS = 1e-9;
#define WIDGET_POSITION event->pos()
#define UCS_POSITION screenToLogical(WIDGET_POSITION)
#include <QWidget> #include <QWidget>
#include <QMouseEvent> #include <QMouseEvent>
@@ -30,7 +28,8 @@ enum class Mode : int
Parallel = 2, ///< Режим задания параллельности Parallel = 2, ///< Режим задания параллельности
Coincedent = 3, ///< Режим задания совпадения точек Coincedent = 3, ///< Режим задания совпадения точек
Horizontal = 4, ///< Режим задания горизонтальности Horizontal = 4, ///< Режим задания горизонтальности
Vertical = 5 ///< Режим задания вертикальности Vertical = 5, ///< Режим задания вертикальности
Perpendicular = 6
}; };
/// Удобный тип для хранения пары параллельных линий (порядок не важен) /// Удобный тип для хранения пары параллельных линий (порядок не важен)
@@ -94,10 +93,12 @@ private:
/// Проверить, являются ли две линии уже параллельными (дубликат ограничения) /// Проверить, являются ли две линии уже параллельными (дубликат ограничения)
bool areAlreadyParallel(Line* line1, Line* line2); bool areAlreadyParallel(Line* line1, Line* line2);
bool areAlreadyPerpendicular(Line* line1, Line* line2);
// ====================== Методы работы с ограничениями ====================== // ====================== Методы работы с ограничениями ======================
/// Удалить последние добавленные ограничения при ошибке солвера /// Удалить последние добавленные ограничения при ошибке солвера
void remove_constraints(); void remove_constraint(int);
// ====================== Данные для перемещения объектов ====================== // ====================== Данные для перемещения объектов ======================
@@ -115,6 +116,7 @@ private:
// ====================== Коллекции ограничений ====================== // ====================== Коллекции ограничений ======================
std::set<LinePair> parallelPairs; ///< Пары параллельных линий std::set<LinePair> parallelPairs; ///< Пары параллельных линий
std::set<LinePair> perpendicularPairs;
std::set<PointPair> P2Ppairs; ///< Пары совпадающих точек std::set<PointPair> P2Ppairs; ///< Пары совпадающих точек
std::set<PointPair> HORIZ_pairs; ///< Пары точек горизонтальных линий std::set<PointPair> HORIZ_pairs; ///< Пары точек горизонтальных линий
std::set<PointPair> VERT_pairs; ///< Пары точек вертикальных линий std::set<PointPair> VERT_pairs; ///< Пары точек вертикальных линий
@@ -138,10 +140,12 @@ private:
/// Структура для хранения информации о последнем добавленном ограничении /// Структура для хранения информации о последнем добавленном ограничении
/// (используется для отката при ошибке солвера) /// (используется для отката при ошибке солвера)
struct LastConstraint {
Mode mode{ Mode::None }; ///< Тип последнего ограничения
std::variant<LinePair, PointPair> data; ///< Данные ограничения
};
LastConstraint lastConstraint; ///< Информация о последнем добавленном ограничении struct ConstraintInfo {
Mode mode;
std::variant<LinePair, PointPair> data;
};
std::map<int, ConstraintInfo> C_Info;
void solve_for_canvas();
}; };

View File

@@ -40,3 +40,9 @@ void DRAWer_2_0::on_Vertical_Button_clicked()
ui.widget->changeMode(Mode::Vertical); ui.widget->changeMode(Mode::Vertical);
} }
void DRAWer_2_0::on_Perpendicular_Button_clicked()
{
ui.widget->changeMode(Mode::Perpendicular);
}

View File

@@ -22,6 +22,8 @@ private slots:
void on_Vertical_Button_clicked(); void on_Vertical_Button_clicked();
void on_Perpendicular_Button_clicked();
private: private:
Ui::DRAWer_2_0Class ui; Ui::DRAWer_2_0Class ui;
int counter; int counter;

View File

@@ -52,6 +52,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="Perpendicular_Button">
<property name="text">
<string>Perpendicular</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="P2P_Button"> <widget class="QPushButton" name="P2P_Button">
<property name="minimumSize"> <property name="minimumSize">