Merge pull request 'Refactor' (#10) from Refactor into master
Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
@@ -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<Line*>(CURVE)
|
||||
#define CURVE_AS_CIRCLE(CURVE) dynamic_cast<Circle*>(CURVE)
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMouseEvent>
|
||||
#include <QPointF>
|
||||
#include <QMessageBox>
|
||||
#include <QToolTip>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include <QDebug>
|
||||
#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<Line*, Line*>;
|
||||
|
||||
/// Удобный тип для хранения пары точек (порядок не важен)
|
||||
using PointPair = std::pair<Point*, Point*>;
|
||||
|
||||
using CurvePair = std::pair<Curve*, Curve*>;
|
||||
#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<LinePair, PointPair, CurvePair> data; ///< Данные ограничения
|
||||
};
|
||||
|
||||
std::map<int, ConstraintInfo> C_Info; ///< Карта информации об ограничениях
|
||||
};
|
||||
73
include/System.h
Normal file
73
include/System.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#define WIDGET_POSITION event->pos()
|
||||
#define UCS_POSITION screenToLogical(WIDGET_POSITION)
|
||||
#define CURVE_AS_LINE(CURVE) dynamic_cast<Line*>(CURVE)
|
||||
#define CURVE_AS_CIRCLE(CURVE) dynamic_cast<Circle*>(CURVE)
|
||||
|
||||
// GCS — геометрический солвер из FreeCAD
|
||||
#include "Geo.h"
|
||||
#include "GCS.h"
|
||||
using namespace GCS;
|
||||
|
||||
/// Удобный тип для хранения пары линий (порядок не важен)
|
||||
using LinePair = std::pair<Line*, Line*>;
|
||||
|
||||
/// Удобный тип для хранения пары точек (порядок не важен)
|
||||
using PointPair = std::pair<Point*, Point*>;
|
||||
|
||||
/// Удобный тип для хранения пары кривых (порядок не важен)
|
||||
using CurvePair = std::pair<Curve*, Curve*>;
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMouseEvent>
|
||||
#include <QPointF>
|
||||
#include <QMessageBox>
|
||||
#include <QToolTip>
|
||||
#include <QKeyEvent>
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include <QDebug>
|
||||
#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<LinePair, PointPair, CurvePair> data; ///< Данные ограничения
|
||||
};
|
||||
|
||||
// ===================================================================
|
||||
// Типы и перечисления
|
||||
// ===================================================================
|
||||
@@ -223,6 +223,12 @@ void Canvas::remove_constraint(int tag)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::Tangent: {
|
||||
if (auto* pair = std::get_if<CurvePair>(&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<PointPair>(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<PointPair>(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<LinePair>(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<PointPair>(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<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;
|
||||
}
|
||||
}
|
||||
|
||||
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<Line*>(first)) {
|
||||
if (Circle* circle = dynamic_cast<Circle*>(second)) {
|
||||
sys.addConstraintTangent(*line, *circle, constraints_count++);
|
||||
constraintAdded = true;
|
||||
}
|
||||
}
|
||||
// Вариант 2: Окружность → Линия
|
||||
else if (Circle* circle = dynamic_cast<Circle*>(first)) {
|
||||
if (Line* line = dynamic_cast<Line*>(second)) {
|
||||
sys.addConstraintTangent(*line, *circle, constraints_count++); // порядок Line, Circle
|
||||
constraintAdded = true;
|
||||
}
|
||||
}
|
||||
// Вариант 3: Окружность → Окружность
|
||||
if (Circle* c1 = dynamic_cast<Circle*>(first)) {
|
||||
if (Circle* c2 = dynamic_cast<Circle*>(second)) {
|
||||
sys.addConstraintTangent(*c1, *c2, constraints_count++);
|
||||
constraintAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Если комбинация недопустима
|
||||
if (!constraintAdded) {
|
||||
message = "Недопустимая комбинация объектов для касания!\n"
|
||||
"Поддерживаются: линия-окружность или окружность-окружность.";
|
||||
QMessageBox::warning(this, "Ошибка", message, QMessageBox::Ok);
|
||||
}
|
||||
else {
|
||||
// Сохраняем информацию об ограничении (для undo и удаления)
|
||||
auto orderedPair = makeOrderedPair<CurvePair>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1075,3 +696,419 @@ void Canvas::clearCanvas()
|
||||
|
||||
groups.clear();
|
||||
}
|
||||
|
||||
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<LinePair>(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<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::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<PointPair>(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<Line*>(first)) {
|
||||
if (Circle* circle = dynamic_cast<Circle*>(second)) {
|
||||
sys.addConstraintTangent(*line, *circle, constraints_count++);
|
||||
constraintAdded = true;
|
||||
}
|
||||
}
|
||||
// Вариант 2: Окружность → Линия
|
||||
else if (Circle* circle = dynamic_cast<Circle*>(first)) {
|
||||
if (Line* line = dynamic_cast<Line*>(second)) {
|
||||
sys.addConstraintTangent(*line, *circle, constraints_count++); // порядок Line, Circle
|
||||
constraintAdded = true;
|
||||
}
|
||||
}
|
||||
// Вариант 3: Окружность → Окружность
|
||||
if (Circle* c1 = dynamic_cast<Circle*>(first)) {
|
||||
if (Circle* c2 = dynamic_cast<Circle*>(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<CurvePair>(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<PointPair>(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<PointPair>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user