/
Team8
/
kittycad
Обзор
Документация
Войти
/
Team8
/
kittycad
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/gui/document_view.cpp
460 строк
14 KB
levovix
add: drawing linear dimensions for parameters
11 янв 2026, 03:08
11 янв 2026, 03:08
c09faff
Код
Авторство
О чём код?
#include "document_view.h" #include <QApplication> #include <QPainter> #include <QPainterPath> #include <QHBoxLayout> #include <QStackedLayout> #include <QMouseEvent> #include <QGraphicsAnchorLayout> #include <qnamespace.h> #include <cur_line_segment.h> #include <mesh.h> #include <vsn_namespace.h> #include <vsn_prcameraorbit.h> #include <vsn_prcamerarotate.h> #include "data/assembly/constraints.h" #include "data/sketch/contours.h" #include "data/document.h" #include "data/sketch/sketch.h" #include "gui/edits/scene_tracer.h" #include "gui/model_tree.h" #include "gui/tools/sketch/sketch.h" #include "gui/tools/tool.h" #include "gui/vision/widget.h" #include "gui/view_cube.h" using namespace VSN; DocumentView::DocumentView(ref<data::Document> document, QString filePath, QObject* parent) : m_document(document) , m_filePath(filePath) , m_scene(new VSN::QtVision::QtOpenGLSceneWidget(this)) { // Получаем главный экран QScreen *screen = QGuiApplication::primaryScreen(); // Получаем масштаб экрана this->m_scale_factor = screen->devicePixelRatio(); initUi(); } DocumentView::~DocumentView() { setActiveTool(nullptr); setToolForSelectedObject(nullptr); m_document->documentView = nullptr; } void DocumentView::initUi() { setMouseTracking(true); #if 1 // сглаживание GlobalFlags::SetLineAntialiasing(true); m_scene->graphicsScene()->SetTransparencyOption(TransparencyOption::DepthPeeling); m_scene->graphicsScene()->SetTransparentModeAntialiasing(true); #endif m_scene->viewport()->SetVisibleSceneWidgets(false); m_scene->viewport()->SetVisibleViewWidgets(false); auto layout = new QStackedLayout(this); layout->setStackingMode(QStackedLayout::StackAll); layout->setContentsMargins(0, 0, 0, 0); { auto parent_widget = new QWidget(this); parent_widget->setAttribute(Qt::WA_TransparentForMouseEvents); auto parent_layout = new QGridLayout(parent_widget); m_cube = new ViewCube(this, parent_widget); m_cube->m_scene = m_scene; m_cube->setFixedSize(100, 100); parent_layout->addWidget(new QWidget(this), 0, 0); parent_layout->addWidget(new QWidget(this), 1, 0); parent_layout->addWidget(new QWidget(this), 1, 1); parent_layout->addWidget(m_cube, 0, 1); layout->addWidget(parent_widget); } m_overlay = new DocumentOverlay(this); layout->addWidget(m_overlay); layout->addWidget(m_scene); Object::Connect(m_scene->viewport(), &Viewport::RefreshScreen, m_scene, [this]() { m_overlay->update(); }); VSN::Object::Connect( m_scene->camera(), &VSN::Camera::OnCameraModified, m_scene, [this]() { documentUpdated(); // чтобы кубик вида обновлялся } ); m_document->sceneContent = m_scene->sceneContent(); m_document->documentView = this; m_sceneTracer = SceneTracer::fromWidget(m_scene); m_document->update(); // в конце итерации цикла событий Qt, проверяет если документ нужно обновить, и обновляем его если нужно auto onTick = new QTimer(this); connect(onTick, &QTimer::timeout, this, [this]() { if (m_document->updateRequested) { documentUpdated(); } }); onTick->setSingleShot(false); onTick->start(0); } Point3 DocumentView::screenToActivePlane(Point3 screenPoint) const { Point3DF point; m_scene->viewport()->ConvertScreenPointToParallelScreenPlane(Point2(screenPoint.x, screenPoint.y), Point3DF(0, 0, 0), point); return point; } Point3 DocumentView::screenToSketchPlane(Point3 screenPoint, data::Sketch const& sketch) const { // todo: проекция на плоскость эскиза ломается если эскиз повёрнут не ортогонально основным осям (это надо починить) auto pt = screenPoint; auto ptn = m_scene->viewport()->NormalizedMousePosition(pt.x, pt.y); auto ptn_v = m_scene->viewport()->NormalizedPositionMouseToVector(ptn.x, ptn.y); auto rayDir = Vec3(m_scene->viewport()->GetCamera()->GetForward().Normalized()); Vec3 cameraYAxis = m_scene->viewport()->GetCamera()->GetUpVector().Normalized(); Vec3 cameraXAxis = crossProduct(rayDir, cameraYAxis); Point3 rayOrigin = Point3(m_scene->viewport()->GetCamera()->GetPosition()) + cameraXAxis * ptn_v.x + cameraYAxis * ptn_v.y ; auto signedDistanceToPlane = [](Point3 ray_start, Vec3 ray_dir, Point3 origin, Vec3 normal) -> double { auto l = lengthOnAxis(origin - ray_start, normal); auto dl = lengthOnAxis(ray_dir, normal) / ray_dir.Length(); return l / dl; }; return rayOrigin + rayDir * signedDistanceToPlane( rayOrigin, rayDir, sketch.origin, sketch.normal() ); } Point3 DocumentView::screenPointToWorldPoint(Point3 screenPoint) const { Point3DF p; m_scene->viewport()->ScreenPointToWorldPoint(screenPoint * m_scale_factor, p); return p; }; Point3 DocumentView::worldPointToScreenPoint(Point3 worldPoint) const { Point3DF p; m_scene->viewport()->WorldPointToScreenPoint(worldPoint, p); return p / m_scale_factor; }; Point3 DocumentView::qPointToScreenPoint(QPoint screenPoint) const { return Point3(screenPoint.x() * m_scale_factor, screenPoint.y() * m_scale_factor, 0); } void DocumentView::documentUpdated() { if (m_document->updateRequested) { m_document->update(); } if (m_document->modelTree != nullptr && m_document->modelTree->m_model != nullptr) { emit m_document->modelTree->m_model->layoutChanged(); // todo: возможно следует добавить на документ событие обновления, на которое можно будет подписаться? } update(); m_scene->update(); m_overlay->update(); } void DocumentView::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); if (event->buttons() == Qt::MouseButton::RightButton) { f_mouseMoveX = (event->pos().x() - m_lastMousePos.x()) / (float)width() * rotate_sensitivity; f_mouseMoveY = (m_lastMousePos.y() - event->pos().y()) / (float)height() * rotate_sensitivity; m_lastMousePos = event->pos(); // f_mouseMoveX = qBound(-89.0f, f_mouseMoveX, 89.0f); if (event->modifiers().testFlag(Qt::ControlModifier)) { m_scene->camera()->RotateAboutTarget(m_scene->camera()->GetForward(), f_mouseMoveX); } else { m_scene->camera()->RotateAboutTarget(m_scene->camera()->GetUpVector(), -f_mouseMoveX); m_scene->camera()->RotateAboutTarget(m_scene->camera()->GetSideVector(), f_mouseMoveY); m_scene->camera()->RotateAboutTarget(m_scene->camera()->GetForward(), (f_mouseMoveY * start_position.x() * 3)); m_scene->camera()->RotateAboutTarget(m_scene->camera()->GetForward(), (f_mouseMoveX * start_position.y() * 3)); } } if (event->buttons() == Qt::MouseButton::MiddleButton) { auto dist = m_scene->camera()->DistanceCameraToTarget(); f_mouseMoveX = (m_lastMousePos.x() - event->pos().x()) * move_sensitivity * dist; f_mouseMoveY = (event->pos().y() - m_lastMousePos.y()) * move_sensitivity * dist; m_lastMousePos = event->pos(); m_scene->camera()->Pan(Vector3DF(f_mouseMoveX, f_mouseMoveY, 0)); } for (auto const& grip : m_pressedGripPoints) { Point3 new_s_pos = qPointToScreenPoint(event->pos()); Point3 old_s_pos = worldPointToScreenPoint(grip->position); new_s_pos.z = old_s_pos.z; Point3 new_pos; if (grip->projectionRule == data::GripPoint::sketchPlane && !m_document->activeSketch.expired()) { new_pos = screenToSketchPlane( new_s_pos, *m_document->activeSketch.lock() ); } else { new_pos = screenPointToWorldPoint(new_s_pos); } if (grip->enableSnapping) { applySnapRules(m_snapRules, grip->position, new_pos); } // todo: временно добавлять ограничение фиксирования геометрии для перемещаемой точки grip->onMove(new_pos); break; // можно двигать только одну контрольную точку за раз } emit mouseMoved(event); } void DocumentView::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); if (b_firstMouse) { m_lastMousePos = event->pos(); b_firstMouse = false; } m_pressedGripPoints.clear(); m_snapRules.clear(); if (!m_document->activeSketch.expired()) { auto sketch = m_document->activeSketch.lock(); if (m_activeInteractiveEntry == nullptr && sketch->visible && event->buttons() == Qt::MouseButton::LeftButton) { for (auto const& grip_ptr : sketch->gripPoints) { auto grip = grip_ptr.lock(); Point3 pos = worldPointToScreenPoint(grip->position); if ( ( Point2(pos.x, pos.y) - Point2(event->position().x(), event->position().y()) ).Length() <= 5 ) { m_pressedGripPoints.push_back(grip); } } } if (!m_pressedGripPoints.empty()) { m_snapRules.push_back(SnapRules(this).grips( &sketch->gripPoints, [this](data::GripPoint* grip) -> bool { return !std::ranges::contains( m_pressedGripPoints, grip->target, [](auto const& x){ return x->target; } ); }, sketch )); Point3 ortogonalGripOrigin = m_pressedGripPoints[0]->position; for (auto const& grip : sketch->gripPoints) { m_snapRules.push_back(SnapRules(this).ortogonal( [pos=grip.lock()->position]() { return pos; }, sketch )); } } if (event->buttons() == Qt::MouseButton::RightButton) { auto relative_position = this->mapFromGlobal( mapToGlobal(event->position()) ); start_position = QPointF{ relative_position.x() / width() - 0.5, relative_position.y() / height() - 0.5 }; } } emit mousePressed(event); } void DocumentView::mouseReleaseEvent(QMouseEvent *event) { QWidget::mouseReleaseEvent(event); m_pressedGripPoints.clear(); m_snapRules.clear(); b_firstMouse = true; emit mouseReleased(event); } void DocumentView::wheelEvent(QWheelEvent *event) { QWidget::wheelEvent(event); if (event->modifiers().testFlag(Qt::ControlModifier)) { // Если таймер уже есть, он останавливается if (m_overlay->notify_timer != nullptr) m_overlay->notify_timer->stop(); scale_sensitivity += event->angleDelta().y() * 0.00001; if (scale_sensitivity < 0) // если меньше нуля - сброс scale_sensitivity = 0.001f; m_overlay->notify_text = QString::number(scale_sensitivity); m_overlay->update(); m_overlay->notify_timer = new QTimer(); connect(m_overlay->notify_timer, &QTimer::timeout, this, [this](){ m_overlay->notify_text = ""; m_overlay->update(); }); m_overlay->notify_timer->start(2000); } else { float new_scale = std::pow(2, event->angleDelta().y() * scale_sensitivity); auto dist = m_scene->camera()->DistanceCameraToTarget(); Point3 cameraPos = m_scene->camera()->GetPosition(); m_scene->camera()->SetZoom(new_scale); m_scene->camera()->Pan(Vec3( (event->position().x() / width() - 0.5) * (new_scale - 1.0) * dist, -(event->position().y() / height() - 0.5) * (new_scale - 1.0) * dist, 0 )); } } void DocumentView::setActiveTool(Tool* v) { if (m_activeTool == v || m_toolForSelectedObject == v) { return; } if (m_activeTool != nullptr) { m_activeTool->deactivated(); } m_activeTool = v; if (v != nullptr) { v->m_docView = this; v->activated(this); } m_pressedGripPoints.clear(); m_snapRules.clear(); } void DocumentView::setToolForSelectedObject(Tool* v) { if (m_activeTool == v || m_toolForSelectedObject == v) { return; } if (m_toolForSelectedObject != nullptr) { m_toolForSelectedObject->deactivated(); } m_toolForSelectedObject = v; if (v != nullptr) { v->m_docView = this; v->activated(this); } } void DocumentView::setActiveInteractiveEntry(QWidget* v) { m_activeInteractiveEntry = v; m_sceneTracer->setMode({}); emit activeInteractiveEntryChanged(v); } DocumentOverlay::DocumentOverlay(DocumentView* parent) : QWidget(parent), docView(parent) { setAttribute(Qt::WA_TransparentForMouseEvents); } DocumentOverlay::~DocumentOverlay() {} void DocumentOverlay::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); p.setBrush(QBrush(QColor(76, 201, 255))); p.setPen(QPen(p.brush(), 2)); data::DrawContext ctx = { .p = &p, .point_worldToQScreen = [this](WorldPoint3 pt) -> QScreenPoint2 { ScreenPoint3 sp = docView->worldPointToScreenPoint(pt); return QScreenPoint2(sp.x, sp.y); } }; if (!docView->m_document->activeSketch.expired()) { auto sketch = docView->m_document->activeSketch.lock(); if (sketch->visible) { for (auto const& pt_ptr : sketch->gripPoints) { auto pt = pt_ptr.lock(); Point3 pos = docView->worldPointToScreenPoint(pt->position); p.drawEllipse(QPointF(pos.x, pos.y), 2, 2); } sketch->drawTree(ctx); } } for (auto obj : docView->m_document->objects) { obj->drawTree(ctx); } for (auto obj : docView->m_document->temporaryObjects) { if (!obj.expired()) { obj.lock()->drawTree(ctx); } } if (!notify_text.isEmpty()) { p.setBrush(QBrush(QColor(20, 20, 20))); p.drawRoundedRect(QRect( width() / 2 - 20 - notify_text.length() * 4, height() - 50, 40 + notify_text.length() * 8, 30), 16, 16 ); p.setBrush(QBrush(QColor(255, 255, 255))); p.drawText(QPoint((float)width() / 2 - notify_text.length() * 3.5, height() - 32), notify_text); } // todo: придумать более универсальный способ отрисовки объектов ещё не добавленных в документ if (auto tool = dynamic_cast<SketchTool*>(docView->m_activeTool)) { if (tool->m_jigRef != nullptr) { tool->m_jigRef->draw(ctx); } } }