diff --git a/CMakeLists.txt b/CMakeLists.txt index fea0c1f..d9fa63a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.16) project(DRAWer_2_0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -29,34 +29,37 @@ PUBLIC target_compile_features(${PlaneGCS_project_name} PUBLIC cxx_std_20) # DRAWer 2_0 -set(PROJECT_SOURCES - main.cpp - DRAWer_2_0.ui - DRAWer_2_0.cpp - Canvas.cpp -) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui Widgets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) -find_package(Qt${QT_VERSION_MAJOR} - COMPONENTS - Core - Gui - Widgets -) qt_standard_project_setup() -qt_add_executable(${PROJECT_NAME} ${PROJECT_SOURCES}) -set_target_properties(${PROJECT_NAME} - PROPERTIES - WIN32_EXECUTABLE TRUE +qt_add_executable(${PROJECT_NAME} + src/main.cpp + src/DRAWer_2_0.cpp + src/Canvas.cpp + include/DRAWer_2_0.h + include/Canvas.h + forms/DRAWer_2_0.ui +) + +# Добавляем путь к src/ для поиска .ui файла (обходит AutoUic ошибку) +set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/forms) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/GCS + ${CMAKE_CURRENT_BINARY_DIR} +) + +set_target_properties(${PROJECT_NAME} PROPERTIES + WIN32_EXECUTABLE TRUE ) target_link_libraries(${PROJECT_NAME} - PUBLIC - Qt::Core - Qt::Gui - Qt::Widgets PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets ${PlaneGCS_project_name} -) - +) \ No newline at end of file diff --git a/DRAWer_2_0.ui b/forms/DRAWer_2_0.ui similarity index 100% rename from DRAWer_2_0.ui rename to forms/DRAWer_2_0.ui diff --git a/Canvas.h b/include/Canvas.h similarity index 81% rename from Canvas.h rename to include/Canvas.h index b4857d1..9c5489b 100644 --- a/Canvas.h +++ b/include/Canvas.h @@ -1,56 +1,5 @@ #pragma once - -constexpr auto EPS = 1e-9; -constexpr auto ZOOM_STEP = 0.05; - -#define WIDGET_POSITION event->pos() -#define UCS_POSITION screenToLogical(WIDGET_POSITION) -#define CURVE_AS_LINE(CURVE) dynamic_cast(CURVE) -#define CURVE_AS_CIRCLE(CURVE) dynamic_cast(CURVE) - -#include -#include -#include -#include -#include -#include - -#ifdef _DEBUG -#include -#endif - -// GCS — геометрический солвер из FreeCAD -#include "GCS/Geo.h" -#include "GCS/GCS.h" -using namespace GCS; - -// =================================================================== -// Типы и перечисления -// =================================================================== - -/** - * @brief Режимы работы с холстом - */ -enum class Mode : int -{ - None = 0, ///< Режим отсутствия действия - DrawingLine = 1, ///< Режим рисования линии - Parallel = 2, ///< Режим задания параллельности - Coincedent = 3, ///< Режим задания совпадения точек - Horizontal = 4, ///< Режим задания горизонтальности - Vertical = 5, ///< Режим задания вертикальности - Perpendicular = 6, ///< Режим задания перпендикулярности - DrawingCircle = 7, ///< Режим рисования окружности - Tangent = 8 //< Режим задания касательности -}; - -/// Удобный тип для хранения пары параллельных линий (порядок не важен) -using LinePair = std::pair; - -/// Удобный тип для хранения пары точек (порядок не важен) -using PointPair = std::pair; - -using CurvePair = std::pair; +#include "System.h" // =================================================================== // Основной класс холста @@ -258,6 +207,18 @@ private: */ void clearCanvas(); + // ===================== Обработчики кнопок ============================== + void ON_NONE(QPointF*); + + void ON_LINE(QPointF*); + void ON_CIRCLE(QPointF*); + + void ON_PARALLEL(QPointF*); + void ON_PERPENDICULAR(QPointF*); + void ON_P2P(QPointF*); + void ON_TANGENT(QPointF*); + void ON_HORIZ_VERT(QPointF*); + // ====================== Данные для перемещения объектов ====================== Point* draggedPoint{ nullptr }; ///< Точка, которую перемещают Line* draggedLine{ nullptr }; ///< Линия, которую перемещают @@ -294,13 +255,5 @@ private: int constraints_count{ 0 }; ///< Счётчик ограничений (для тегов) // ====================== Информация о последнем ограничении ====================== - /** - * @brief Структура для хранения информации об ограничении - */ - struct ConstraintInfo { - Mode mode; ///< Тип ограничения - std::variant data; ///< Данные ограничения - }; - std::map C_Info; ///< Карта информации об ограничениях }; \ No newline at end of file diff --git a/DRAWer_2_0.h b/include/DRAWer_2_0.h similarity index 100% rename from DRAWer_2_0.h rename to include/DRAWer_2_0.h diff --git a/include/System.h b/include/System.h new file mode 100644 index 0000000..9f3cc94 --- /dev/null +++ b/include/System.h @@ -0,0 +1,73 @@ +#pragma once +#define WIDGET_POSITION event->pos() +#define UCS_POSITION screenToLogical(WIDGET_POSITION) +#define CURVE_AS_LINE(CURVE) dynamic_cast(CURVE) +#define CURVE_AS_CIRCLE(CURVE) dynamic_cast(CURVE) + +// GCS — геометрический солвер из FreeCAD +#include "Geo.h" +#include "GCS.h" +using namespace GCS; + +/// Удобный тип для хранения пары линий (порядок не важен) +using LinePair = std::pair; + +/// Удобный тип для хранения пары точек (порядок не важен) +using PointPair = std::pair; + +/// Удобный тип для хранения пары кривых (порядок не важен) +using CurvePair = std::pair; + +#include +#include +#include +#include +#include +#include + + +#ifdef _DEBUG +#include +#endif + +constexpr auto EPS = 1e-9; +constexpr auto ZOOM_STEP = 0.05; + +// =================================================================== +// Типы и перечисления +// =================================================================== + +/** + * @brief Режимы работы с холстом + */ +enum class Mode : int +{ + None = 0, ///< Режим отсутствия действия + DrawingLine = 1, ///< Режим рисования линии + Parallel = 2, ///< Режим задания параллельности + Coincedent = 3, ///< Режим задания совпадения точек + Horizontal = 4, ///< Режим задания горизонтальности + Vertical = 5, ///< Режим задания вертикальности + Perpendicular = 6, ///< Режим задания перпендикулярности + DrawingCircle = 7, ///< Режим рисования окружности + Tangent = 8 //< Режим задания касательности +}; + +struct TextObject { + QPointF pos; ///< Логическая позиция (левый верхний угол) + QString text; ///< Текст + int tag; ///< Тег для идентификации + QFont font{ QFont("Arial", 12) }; ///< Шрифт (можно менять позже) +}; + +/** + * @brief Структура для хранения информации об ограничении + */ +struct ConstraintInfo { + Mode mode; ///< Тип ограничения + std::variant data; ///< Данные ограничения +}; + +// =================================================================== +// Типы и перечисления +// =================================================================== \ No newline at end of file diff --git a/Canvas.cpp b/src/Canvas.cpp similarity index 68% rename from Canvas.cpp rename to src/Canvas.cpp index 631426c..f372633 100644 --- a/Canvas.cpp +++ b/src/Canvas.cpp @@ -223,6 +223,12 @@ void Canvas::remove_constraint(int tag) } break; } + case Mode::Tangent: { + if (auto* pair = std::get_if(&info.data)) { + tangentPairs.erase(*pair); + } + break; + } default: break; } @@ -237,426 +243,41 @@ void Canvas::mousePressEvent(QMouseEvent* event) { QPointF scene = UCS_POSITION; -#ifdef _DEBUG + #ifdef _DEBUG qDebug() << "Scene point in" << scene.x() << scene.y(); -#endif + #endif - // ====================== Режим None: перемещение объектов ====================== - if (mode == Mode::None) { - Point* p = findPointAt(scene); - if (p) { - draggedPoint = p; - getCoincidentGroup(draggedPoint); - dragOffset = scene - QPointF(*p->x, *p->y); - return; - } - - Line* found = CURVE_AS_LINE(findAt(scene)); - if (found) { - draggedCurve = found; - QPointF lineCenter( - (*found->p1.x + *found->p2.x) / 2.0, - (*found->p1.y + *found->p2.y) / 2.0 - ); - dragOffset = scene - lineCenter; - return; - } - - Circle* found_circle = CURVE_AS_CIRCLE(findAt(scene)); - if (found_circle) { - draggedCurve = found_circle; - QPointF center(*found_circle->center.x, *found_circle->center.y); - dragOffset = scene - center; - return; - } - } - - // ====================== Режим Horizontal/Vertical ====================== - else if (mode == Mode::Horizontal || mode == Mode::Vertical) { - Line* found = CURVE_AS_LINE(findAt(scene)); - - if (found) { - // Проверка: если линия уже вертикальна, нельзя сделать её горизонтальной - if (mode == Mode::Horizontal && isLineVertical(found)) { - QMessageBox::warning(this, - QString("Невозможно"), - QString("Эта линия уже вертикальна и не может быть горизонтальной!"), - QMessageBox::Ok - ); - mode = Mode::None; - return; - } - - // Проверка: если линия уже горизонтальна, нельзя сделать её вертикальной - if (mode == Mode::Vertical && isLineHorizontal(found)) { - QMessageBox::warning(this, - QString("Невозможно"), - QString("Эта линия уже горизонтальна и не может быть вертикальной!"), - QMessageBox::Ok - ); - mode = Mode::None; - return; - } - - if (mode == Mode::Horizontal) { - sys.addConstraintHorizontal(*found, constraints_count++); - auto pair = makeOrderedPair(found->start_ref, found->end_ref); - HORIZ_pairs.insert(pair); - C_Info[constraints_count - 1] = { Mode::Horizontal, pair }; - } - else { - sys.addConstraintVertical(*found, constraints_count++); - auto pair = makeOrderedPair(found->start_ref, found->end_ref); - VERT_pairs.insert(pair); - C_Info[constraints_count - 1] = { Mode::Vertical, pair }; - } - solve_for_canvas(); - after_constraint = true; - } - mode = Mode::None; - } - - // ====================== Режим DrawingLine: рисование линий ====================== - else if (mode == Mode::DrawingLine) { - if (!current_line) { - // Первый клик: создаем новую линию - current_line = new Line(); - - // Создаем координаты для точек линии - double* x1 = new double(scene.x()); - double* y1 = new double(scene.y()); - double* x2 = new double(scene.x()); - double* y2 = new double(scene.y()); - - // Добавляем параметры в солвер - params.push_back(x1); - params.push_back(y1); - - // Создаем точки и привязываем к линии - points.push_back(new Point(x1, y1, obj_count++)); - points.push_back(new Point(x2, y2, obj_count++)); - - current_line->p1.x = x1; - current_line->p1.y = y1; - current_line->p2.x = x2; - current_line->p2.y = y2; - current_line->start_ref = points[points.size() - 2]; - current_line->end_ref = points[points.size() - 1]; - } - else { - // Второй клик: завершаем рисование линии - *current_line->p2.x = scene.x(); - *current_line->p2.y = scene.y(); - - // Проверка минимальной длины линии - double len = sqrt(pow(*current_line->p2.x - *current_line->p1.x, 2) + - pow(*current_line->p2.y - *current_line->p1.y, 2)); - - if (len < 10) { - // Линия слишком короткая - отменяем создание - delete current_line; - current_line = nullptr; - - // Удаляем созданные точки и параметры - delete points.back(); - points.pop_back(); - delete points.back(); - points.pop_back(); - delete params.back(); - params.pop_back(); - delete params.back(); - params.pop_back(); - - mode = Mode::None; - QMessageBox::critical(this, "WHOOPS", - "Sorry, your line is very short", QMessageBox::Ok); - solve_for_canvas(); - return; - } - - // Добавляем координаты второй точки в параметры - params.push_back(current_line->p2.x); - params.push_back(current_line->p2.y); - - current_line->set_tag(obj_count++); - // Завершаем создание линии - curves.append(current_line); - current_line = nullptr; - mode = Mode::None; - } - solve_for_canvas(); - return; - } - - else if (mode == Mode::DrawingCircle) { - if (!current_circle) { - current_circle = new Circle(); - // Создаем координаты для центра окружности - double* x = new double(scene.x()); - double* y = new double(scene.y()); - - points.push_back(new Point(x, y, obj_count++)); - - // Добавляем параметры в солвер - params.push_back(x); - params.push_back(y); - - current_circle->center.x = x; - current_circle->center.y = y; - current_circle->center_ref = points[points.size() - 1]; - } - else { - double* r = new double(dist_P2P(QPointF(current_circle->center.get_X(), current_circle->center.get_Y()), QPointF(scene.x(), scene.y()))); - current_circle->rad = r; - - params.push_back(r); - curves.append(current_circle); - current_circle->set_tag(obj_count++); - current_circle = nullptr; - mode = Mode::None; - } - solve_for_canvas(); - return; - } - // ====================== Режим Parallel: задание параллельности ====================== - else if (mode == Mode::Parallel) { - Line* found = CURVE_AS_LINE(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 (!areAlreadyParallel(found, current_line)) { - auto pair = makeOrderedPair(found, current_line); - sys.addConstraintParallel(*found, *current_line, constraints_count++); - parallelPairs.insert(pair); - C_Info[constraints_count - 1] = { Mode::Parallel, pair }; - - current_line = nullptr; - mode = Mode::None; - after_constraint = true; - solve_for_canvas(); - } - else { - // Линии уже параллельны - сообщаем об ошибке -#ifdef _DEBUG - qDebug() << "Line" << current_line << "and" << found << "are parallel. Abort!"; -#endif - - QMessageBox::warning(this, - QString("Wrong"), - QString("Parallel lines can not be more parallel!"), - QMessageBox::Ok - ); - current_line = nullptr; - solve_for_canvas(); - return; - } - } - - // ====================== Режим Coincedent: совпадение точек ====================== - else if (mode == Mode::Coincedent) { - Point* clickedPoint = findPointAt(scene); - if (!clickedPoint) { - firstPoint = nullptr; - solve_for_canvas(); - return; - } - - // Первый клик: выбираем первую точку - if (!firstPoint) { - firstPoint = clickedPoint; - solve_for_canvas(); - return; - } - - // Повторный клик на ту же точку: сброс выбора - if (clickedPoint == firstPoint) { - firstPoint = nullptr; - solve_for_canvas(); - return; - } - - // Находим линии, к которым принадлежат точки - Line* l1 = nullptr; - Line* l2 = nullptr; - - for (Curve* curve : curves) { - if (Line* l = CURVE_AS_LINE(curve)) { - if (l->start_ref == firstPoint || l->end_ref == firstPoint) - l1 = l; - if (l->start_ref == clickedPoint || l->end_ref == clickedPoint) - l2 = l; - } - } - - // Проверка на невозможность ограничения - if ((l1 == l2 && l1 && l2) || areCoincident(firstPoint, clickedPoint)) { - QMessageBox::critical(this, QString("NO!"), QString("P2P failed")); - firstPoint = nullptr; - mode = Mode::None; - solve_for_canvas(); - return; - } - - // Добавляем ограничение совпадения точек - sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++); - auto pair = makeOrderedPair(clickedPoint, firstPoint); - P2Ppairs.insert(pair); - C_Info[constraints_count - 1] = { Mode::Coincedent, pair }; - - firstPoint = nullptr; - mode = Mode::None; - after_constraint = true; - solve_for_canvas(); - return; - } - - else if (mode == Mode::Perpendicular) { - Line* found = CURVE_AS_LINE(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(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; - } - } - - else if (mode == Mode::Tangent) { - // Ищем любой объект под курсором: линия или окружность - Curve* selected = findAt(scene); - - if (!selected) { - current_curve = nullptr; - update(); - return; - } - - // Если кликнули по уже выбранному объекту — сброс - if (current_curve && selected == current_curve) { - current_curve = nullptr; - update(); - return; - } - - // Первый выбор - if (!current_curve) { - current_curve = selected; - update(); // подсветим выбранный объект - return; - } - - // Второй выбор — теперь у нас два объекта - Curve* first = current_curve; - Curve* second = selected; - - // Проверка на уже существующее ограничение касательности - if (areAlreadyTangent(first, second)) { - QMessageBox::warning(this, "Ошибка", - "Эти объекты уже касательны!", QMessageBox::Ok); - current_curve = nullptr; - update(); - return; - } - - QString message; - bool constraintAdded = false; - - // Вариант 1: Линия → Окружность - if (Line* line = dynamic_cast(first)) { - if (Circle* circle = dynamic_cast(second)) { - sys.addConstraintTangent(*line, *circle, constraints_count++); - constraintAdded = true; - } - } - // Вариант 2: Окружность → Линия - else if (Circle* circle = dynamic_cast(first)) { - if (Line* line = dynamic_cast(second)) { - sys.addConstraintTangent(*line, *circle, constraints_count++); // порядок Line, Circle - constraintAdded = true; - } - } - // Вариант 3: Окружность → Окружность - if (Circle* c1 = dynamic_cast(first)) { - if (Circle* c2 = dynamic_cast(second)) { - sys.addConstraintTangent(*c1, *c2, constraints_count++); - constraintAdded = true; - } - } - - // Если комбинация недопустима - if (!constraintAdded) { - message = "Недопустимая комбинация объектов для касания!\n" - "Поддерживаются: линия-окружность или окружность-окружность."; - QMessageBox::warning(this, "Ошибка", message, QMessageBox::Ok); - } - else { - // Сохраняем информацию об ограничении (для undo и удаления) - auto orderedPair = makeOrderedPair(first, second); - tangentPairs.insert(orderedPair); - C_Info[constraints_count - 1] = { Mode::Tangent, orderedPair }; - after_constraint = true; - } - - // Сброс режима и выделения - current_curve = nullptr; - mode = Mode::None; - solve_for_canvas(); - update(); + switch (mode) + { + case Mode::None: + ON_NONE(&scene); + break; + case Mode::DrawingLine: + ON_LINE(&scene); + break; + case Mode::Parallel: + ON_PARALLEL(&scene); + break; + case Mode::Coincedent: + ON_P2P(&scene); + break; + case Mode::Horizontal: + ON_HORIZ_VERT(&scene); + break; + case Mode::Vertical: + ON_HORIZ_VERT(&scene); + break; + case Mode::Perpendicular: + ON_PERPENDICULAR(&scene); + break; + case Mode::DrawingCircle: + ON_CIRCLE(&scene); + break; + case Mode::Tangent: + ON_TANGENT(&scene); + break; + default: + break; } } @@ -1074,4 +695,420 @@ void Canvas::clearCanvas() update(); groups.clear(); -} \ No newline at end of file +} + +void Canvas::ON_NONE(QPointF* scene) +{ + Point* p = findPointAt(*scene); + if (p) { + draggedPoint = p; + getCoincidentGroup(draggedPoint); + dragOffset = *scene - QPointF(*p->x, *p->y); + return; + } + + Line* found = CURVE_AS_LINE(findAt(*scene)); + if (found) { + draggedCurve = found; + QPointF lineCenter( + (*found->p1.x + *found->p2.x) / 2.0, + (*found->p1.y + *found->p2.y) / 2.0 + ); + dragOffset = *scene - lineCenter; + return; + } + + Circle* found_circle = CURVE_AS_CIRCLE(findAt(*scene)); + if (found_circle) { + draggedCurve = found_circle; + QPointF center(*found_circle->center.x, *found_circle->center.y); + dragOffset = *scene - center; + return; + } + + return; +} + +void Canvas::ON_LINE(QPointF* pos) +{ + QPointF scene = *pos; + + if (!current_line) { + // Первый клик: создаем новую линию + current_line = new Line(); + + // Создаем координаты для точек линии + double* x1 = new double(scene.x()); + double* y1 = new double(scene.y()); + double* x2 = new double(scene.x()); + double* y2 = new double(scene.y()); + + // Добавляем параметры в солвер + params.push_back(x1); + params.push_back(y1); + + // Создаем точки и привязываем к линии + points.push_back(new Point(x1, y1, obj_count++)); + points.push_back(new Point(x2, y2, obj_count++)); + + current_line->p1.x = x1; + current_line->p1.y = y1; + current_line->p2.x = x2; + current_line->p2.y = y2; + current_line->start_ref = points[points.size() - 2]; + current_line->end_ref = points[points.size() - 1]; + } + else { + // Второй клик: завершаем рисование линии + *current_line->p2.x = scene.x(); + *current_line->p2.y = scene.y(); + + // Проверка минимальной длины линии + double len = sqrt(pow(*current_line->p2.x - *current_line->p1.x, 2) + + pow(*current_line->p2.y - *current_line->p1.y, 2)); + + if (len < 10) { + // Линия слишком короткая - отменяем создание + delete current_line; + current_line = nullptr; + + // Удаляем созданные точки и параметры + delete points.back(); + points.pop_back(); + delete points.back(); + points.pop_back(); + delete params.back(); + params.pop_back(); + delete params.back(); + params.pop_back(); + + mode = Mode::None; + QMessageBox::critical(this, "WHOOPS", + "Sorry, your line is very short", QMessageBox::Ok); + solve_for_canvas(); + return; + } + + // Добавляем координаты второй точки в параметры + params.push_back(current_line->p2.x); + params.push_back(current_line->p2.y); + + current_line->set_tag(obj_count++); + // Завершаем создание линии + curves.append(current_line); + current_line = nullptr; + mode = Mode::None; + } + solve_for_canvas(); + return; +} +void Canvas::ON_CIRCLE(QPointF* pos) +{ + QPointF scene = *pos; + if (!current_circle) { + current_circle = new Circle(); + // Создаем координаты для центра окружности + double* x = new double(scene.x()); + double* y = new double(scene.y()); + + points.push_back(new Point(x, y, obj_count++)); + + // Добавляем параметры в солвер + params.push_back(x); + params.push_back(y); + + current_circle->center.x = x; + current_circle->center.y = y; + current_circle->center_ref = points[points.size() - 1]; + } + else { + double* r = new double(dist_P2P(QPointF(current_circle->center.get_X(), current_circle->center.get_Y()), QPointF(scene.x(), scene.y()))); + current_circle->rad = r; + + params.push_back(r); + curves.append(current_circle); + current_circle->set_tag(obj_count++); + current_circle = nullptr; + mode = Mode::None; + } + solve_for_canvas(); + return; +} + +void Canvas::ON_PARALLEL(QPointF* scene) +{ + Line* found = CURVE_AS_LINE(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 (!areAlreadyParallel(found, current_line)) { + auto pair = makeOrderedPair(found, current_line); + sys.addConstraintParallel(*found, *current_line, constraints_count++); + parallelPairs.insert(pair); + C_Info[constraints_count - 1] = { Mode::Parallel, pair }; + + current_line = nullptr; + mode = Mode::None; + after_constraint = true; + solve_for_canvas(); + } + else { + // Линии уже параллельны - сообщаем об ошибке + QMessageBox::warning(this, + QString("Wrong"), + QString("Parallel lines can not be more parallel!"), + QMessageBox::Ok + ); + current_line = nullptr; + solve_for_canvas(); + return; + } +} +void Canvas::ON_PERPENDICULAR(QPointF* scene) +{ + Line* found = CURVE_AS_LINE(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(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::ON_P2P(QPointF* scene) +{ + Point* clickedPoint = findPointAt(*scene); + if (!clickedPoint) { + firstPoint = nullptr; + solve_for_canvas(); + return; + } + + // Первый клик: выбираем первую точку + if (!firstPoint) { + firstPoint = clickedPoint; + solve_for_canvas(); + return; + } + + // Повторный клик на ту же точку: сброс выбора + if (clickedPoint == firstPoint) { + firstPoint = nullptr; + solve_for_canvas(); + return; + } + + // Находим линии, к которым принадлежат точки + Line* l1 = nullptr; + Line* l2 = nullptr; + + for (Curve* curve : curves) { + if (Line* l = CURVE_AS_LINE(curve)) { + if (l->start_ref == firstPoint || l->end_ref == firstPoint) + l1 = l; + if (l->start_ref == clickedPoint || l->end_ref == clickedPoint) + l2 = l; + } + } + + // Проверка на невозможность ограничения + if ((l1 == l2 && l1 && l2) || areCoincident(firstPoint, clickedPoint)) { + QMessageBox::critical(this, QString("NO!"), QString("P2P failed")); + firstPoint = nullptr; + mode = Mode::None; + solve_for_canvas(); + return; + } + + // Добавляем ограничение совпадения точек + sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++); + auto pair = makeOrderedPair(clickedPoint, firstPoint); + P2Ppairs.insert(pair); + C_Info[constraints_count - 1] = { Mode::Coincedent, pair }; + + firstPoint = nullptr; + mode = Mode::None; + after_constraint = true; + solve_for_canvas(); + return; +} +void Canvas::ON_TANGENT(QPointF* scene) +{ + // Ищем любой объект под курсором: линия или окружность + Curve* selected = findAt(*scene); + + if (!selected) { + current_curve = nullptr; + update(); + return; + } + + // Если кликнули по уже выбранному объекту — сброс + if (current_curve && selected == current_curve) { + current_curve = nullptr; + update(); + return; + } + + // Первый выбор + if (!current_curve) { + current_curve = selected; + update(); // подсветим выбранный объект + return; + } + + // Второй выбор — теперь у нас два объекта + Curve* first = current_curve; + Curve* second = selected; + + // Проверка на уже существующее ограничение касательности + if (areAlreadyTangent(first, second)) { + QMessageBox::warning(this, "Ошибка", + "Эти объекты уже касательны!", QMessageBox::Ok); + current_curve = nullptr; + update(); + return; + } + + bool constraintAdded = false; + + // Вариант 1: Линия → Окружность + if (Line* line = dynamic_cast(first)) { + if (Circle* circle = dynamic_cast(second)) { + sys.addConstraintTangent(*line, *circle, constraints_count++); + constraintAdded = true; + } + } + // Вариант 2: Окружность → Линия + else if (Circle* circle = dynamic_cast(first)) { + if (Line* line = dynamic_cast(second)) { + sys.addConstraintTangent(*line, *circle, constraints_count++); // порядок Line, Circle + constraintAdded = true; + } + } + // Вариант 3: Окружность → Окружность + if (Circle* c1 = dynamic_cast(first)) { + if (Circle* c2 = dynamic_cast(second)) { + sys.addConstraintTangent(*c1, *c2, constraints_count++); + constraintAdded = true; + } + } + + // Если комбинация недопустима + if (!constraintAdded) { + QString message = "Недопустимая комбинация объектов для касания!\n" + "Поддерживаются: линия-окружность или окружность-окружность."; + QMessageBox::warning(this, "Ошибка", message, QMessageBox::Ok); + } + else { + // Сохраняем информацию об ограничении (для undo и удаления) + auto orderedPair = makeOrderedPair(first, second); + tangentPairs.insert(orderedPair); + C_Info[constraints_count - 1] = { Mode::Tangent, orderedPair }; + after_constraint = true; + } + + // Сброс режима и выделения + current_curve = nullptr; + mode = Mode::None; + solve_for_canvas(); +} +void Canvas::ON_HORIZ_VERT(QPointF* scene) +{ + Line* found = CURVE_AS_LINE(findAt(*scene)); + + if (found) { + // Проверка: если линия уже вертикальна, нельзя сделать её горизонтальной + if (mode == Mode::Horizontal && isLineVertical(found)) { + QMessageBox::warning(this, + QString("Невозможно"), + QString("Эта линия уже вертикальна и не может быть горизонтальной!"), + QMessageBox::Ok + ); + mode = Mode::None; + return; + } + + // Проверка: если линия уже горизонтальна, нельзя сделать её вертикальной + else if (mode == Mode::Vertical && isLineHorizontal(found)) { + QMessageBox::warning(this, + QString("Невозможно"), + QString("Эта линия уже горизонтальна и не может быть вертикальной!"), + QMessageBox::Ok + ); + mode = Mode::None; + return; + } + + if (mode == Mode::Horizontal) { + sys.addConstraintHorizontal(*found, constraints_count++); + auto pair = makeOrderedPair(found->start_ref, found->end_ref); + HORIZ_pairs.insert(pair); + C_Info[constraints_count - 1] = { Mode::Horizontal, pair }; + } + else { + sys.addConstraintVertical(*found, constraints_count++); + auto pair = makeOrderedPair(found->start_ref, found->end_ref); + VERT_pairs.insert(pair); + C_Info[constraints_count - 1] = { Mode::Vertical, pair }; + } + solve_for_canvas(); + after_constraint = true; + } + mode = Mode::None; + return; +} diff --git a/DRAWer_2_0.cpp b/src/DRAWer_2_0.cpp similarity index 100% rename from DRAWer_2_0.cpp rename to src/DRAWer_2_0.cpp diff --git a/main.cpp b/src/main.cpp similarity index 100% rename from main.cpp rename to src/main.cpp