Нарисовать размерную линию в виде стрелки в pyqtPython

Программы на Python
Ответить
Anonymous
 Нарисовать размерную линию в виде стрелки в pyqt

Сообщение Anonymous »

Цель кода:
каждая нарисованная линия должна иметь параллельную ей размерную линию с двумя стрелками (которые позже будут использоваться для обозначения длины)
проблема:
каждый раз, когда я рисую новую линию, стрелки предыдущей размерной линии удаляются, и я не могу найти решение этой проблемы

Код: Выделить всё

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QVBoxLayout, QToolBar, QAction, QWidget, QGraphicsLineItem
from PyQt5.QtGui import QPen, QColor, QPolygonF, QBrush
from PyQt5.QtCore import Qt, QPointF, QRectF

class SketchView(QGraphicsView):
def __init__(self):
super().__init__()

self.scene = QGraphicsScene(self)
self.setScene(self.scene)

# Set the initial visible area:
self.visible_area = QRectF(0, -2000, 3000, 2000)  # Initial visible area
self.setMouseTracking(True)  # Enable mouse tracking

# Initialize variables for panning and drawing
self.dragging = False
self.drawing = False
self.last_pos = None
self.start_point = None

# Drawing mode
self.drawing_mode = None

self.lines = []  # List to store the drawn lines

self.arrowheads = []  # Initialize the arrowheads list to store arrowhead items

self.line_dimension_map = {}  # Store each line with its related dimension line

# Temporary lines
self.temporary_line = None
self.temporary_dimension_line = None

self.dimension_line_arrows = {}  # Store dimension lines and their associated arrows

def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if self.drawing_mode and not self.lines:
# Start the first line freely in drawing mode
self.start_point = self.mapToScene(event.pos())
self.drawing = True
elif self.drawing_mode and self.lines:
# Drawing continues from the end of the last line
self.drawing = True
elif self.drawing_mode is None:
# Start panning
self.dragging = True
self.last_pos = event.pos()
self.setCursor(Qt.ClosedHandCursor)

def mouseMoveEvent(self, event):
if self.dragging and self.drawing_mode is None:
# Handle panning when not in drawing mode
delta = event.pos() - self.last_pos
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - delta.x())
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - delta.y())
self.last_pos = event.pos()
self.setCursor(Qt.SizeAllCursor)
elif self.drawing and self.start_point:
# Draw a temporary line in drawing mode
end_point = self.mapToScene(event.pos())
self.drawTemporaryLine(self.start_point, end_point)

def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
if self.dragging and self.drawing_mode is None:
# Stop panning
self.dragging = False
self.setCursor(Qt.ArrowCursor)
elif self.drawing:
# Finalize the current line
end_point = self.mapToScene(event.pos())
self.drawLine(self.start_point, end_point)

# Check if the shape is closed
if len(self.lines) >  1:
first_line_start = self.lines[0].line().p1()
if end_point == first_line_start:
# Shape is closed
self.drawing = False
self.drawing_mode = None
self.temp_line = None
self.update()  # Refresh the view
return

# Automatically start a new line from the current endpoint
self.start_point = end_point

def drawTemporaryLine(self, start_point, end_point):
"""
Draws a temporary line and its dimension line as the user moves the mouse.
"""
# Remove any existing temporary lines
if self.temporary_line:
self.scene.removeItem(self.temporary_line)
self.temporary_line = None

if self.temporary_dimension_line:
self.scene.removeItem(self.temporary_dimension_line)
self.temporary_dimension_line = None

# Draw the main temporary line
temporary_pen = QPen(Qt.black)
temporary_pen.setStyle(Qt.DashLine)
temporary_pen.setWidth(10)

self.temporary_line = self.scene.addLine(
start_point.x(), start_point.y(), end_point.x(), end_point.y(), temporary_pen)

# Draw the temporary dimension line
self.temporary_dimension_line = self.drawDimensionLine(start_point, end_point)

def drawLine(self, start_point, end_point):
"""
Draws a permanent line and adds it to the list of drawn lines.
Calls drawDimensionLine to create a parallel dimension line.
"""
# Draw the main line
pen = QPen(Qt.black)
pen.setWidth(10)
main_line = self.scene.addLine(
start_point.x(), start_point.y(), end_point.x(), end_point.y(), pen)
self.lines.append(main_line)

# Draw the associated dimension line
dimension_line = self.drawDimensionLine(start_point, end_point)
if dimension_line:   # Only store if a valid dimension line is created
self.line_dimension_map[main_line] = dimension_line
print('dimension line added to the map')

def drawArrowHead(self, point, direction, line):
"""
Draws a proper arrowhead at the given point in the specified direction.
The arrowhead will be a triangle pointing in the direction of the line.
"""
arrow_size = 15  # Size of the arrowhead, increase or decrease as needed
arrow_angle = 30  # Angle of the arrowhead, can be adjusted for appearance

# Calculate the points for the arrowhead (triangle)
left = QPointF(point.x() + direction.x() * arrow_size, point.y() + direction.y() * arrow_size)
right = QPointF(point.x() - direction.x() * arrow_size, point.y() - direction.y() * arrow_size)

left_angle = QPointF(left.x() + direction.y() * arrow_angle, left.y() - direction.x() * arrow_angle)
right_angle = QPointF(right.x() + direction.y() * arrow_angle, right.y() - direction.x() * arrow_angle)

arrow_head = QPolygonF([point, left_angle, right_angle])

arrow_pen = QPen(Qt.darkGray)
arrow_pen.setWidth(2)

# Draw the arrowhead
arrow_item = self.scene.addPolygon(arrow_head, arrow_pen)

return arrow_item

def drawDimensionLine(self, start_point, end_point):
"""
Draws a parallel dimension line slightly shorter than the main line,
adds arrowheads at both ends, and ensures previous arrowheads are removed.
"""
dimension_pen = QPen(Qt.darkGray)
dimension_pen.setWidth(6)
offset = 20  # Distance from the main line
shorter_factor = 0.85  # Dimension line will be slightly shorter

dx = end_point.x() - start_point.x()
dy = end_point.y() - start_point.y()
length = (dx**2 + dy**2)**0.5

if length == 0:
return None

nx = -dy / length
ny = dx / length

offset_start = QPointF(start_point.x() + nx * offset, start_point.y() + ny * offset)
offset_end = QPointF(end_point.x() + nx * offset, end_point.y() + ny * offset)

mid_x = (offset_start.x() + offset_end.x()) / 2
mid_y = (offset_start.y() + offset_end.y()) / 2
dimension_start = QPointF(mid_x + (offset_start.x() - mid_x) * shorter_factor,
mid_y + (offset_start.y() - mid_y) * shorter_factor)

dimension_end = QPointF(mid_x + (offset_end.x() - mid_x) * shorter_factor,
mid_y + (offset_end.y() - mid_y) * shorter_factor)

# Draw the dimension line
dimension_line = self.scene.addLine(
dimension_start.x(), dimension_start.y(), dimension_end.x(), dimension_end.y(), dimension_pen)

# Remove previous arrowheads before adding new ones
for arrow in self.arrowheads:
self.scene.removeItem(arrow)
self.arrowheads.clear()

# Draw arrowheads at both ends
direction_start = QPointF(nx, ny)
direction_end = QPointF(-nx, -ny)
arrow_start = self.drawArrowHead(dimension_start, direction_start, dimension_line)
arrow_end = self.drawArrowHead(dimension_end, direction_end, dimension_line)

self.arrowheads = [arrow_start, arrow_end]

return dimension_line

def DrawingMode(self, mode):
# Set the drawing mode
if self.drawing_mode == mode:
self.drawing_mode = None
else:
self.drawing_mode = mode
if mode == "external_wall":
self.pen_color = Qt.black
self.pen_width = 15
elif mode == "internal_wall":
self.pen_color = Qt.gray
self.pen_width = 10

class MainWindow(QMainWindow):
def __init__(self):
super().__init__()

# Create a central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)

layout = QVBoxLayout(central_widget)

# Create a toolbar
toolbar = QToolBar()
self.addToolBar(Qt.LeftToolBarArea, toolbar)

# Add actions to the toolbar
external_wall_action = QAction("External Wall", self)
external_wall_action.triggered.connect(lambda:  self.sketch_view.DrawingMode("external_wall"))
toolbar.addAction(external_wall_action)

internal_wall_action = QAction("Internal Wall", self)
internal_wall_action.triggered.connect(lambda: self.sketch_view.DrawingMode("internal_wall"))
toolbar.addAction(internal_wall_action)

# Add the QGraphicsView to the layout
self.sketch_view = SketchView()
layout.addWidget(self.sketch_view)

if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.showMaximized()  # Start the program in full screen
sys.exit(app.exec_())

часть, в которой очищаются временные стрелки, чтобы сцена была чистой:

Код: Выделить всё

        for arrow in self.arrowheads:
self.scene.removeItem(arrow)
self.arrowheads.clear()
и без этой части все будет в беспорядке (если вы не понимаете, о чем я, вы можете удалить эту часть и запустить код, чтобы увидеть), но, похоже, она также удаляет финальную версию наконечники стрел тоже, я пробовал несколько вещей, чтобы последние стрелки оставались фиксированными на своем месте, но, похоже, это не работает, либо я что-то испортил, либо программа полностью вылетает
например:

Код: Выделить всё

       for arrow in self.arrowheads:
if dimension_line:
break
else:
self.scene.removeItem(arrow)
self.arrowheads.clear()
здесь я пытаюсь сказать, что если размерная линия завершена, не удаляйте связанные с ней стрелки
Я знаю, что исправить это может быть очень просто, но я просто не могу его найти, скажите мне, если у вас есть идеи - спасибо

Подробнее здесь: https://stackoverflow.com/questions/793 ... -line-pyqt
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»