Цель кода:
каждая нарисованная линия должна иметь параллельную ей размерную линию с двумя стрелками (которые позже будут использоваться для обозначения длины) проблема:
каждый раз, когда я рисую новую линию, стрелки предыдущей размерной линии удаляются, и я не могу найти решение этой проблемы
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()
здесь я пытаюсь сказать, что если размерная линия завершена, не удаляйте связанные с ней стрелки
Я знаю, что исправить это может быть очень просто, но я просто не могу его найти, скажите мне, если у вас есть идеи - спасибо
[b]Цель кода:[/b] каждая нарисованная линия должна иметь параллельную ей размерную линию с двумя стрелками (которые позже будут использоваться для обозначения длины) [b]проблема:[/b] каждый раз, когда я рисую новую линию, стрелки предыдущей размерной линии удаляются, и я не могу найти решение этой проблемы [code]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.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)
# 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)
# 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
# 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_())
[/code] часть, в которой очищаются временные стрелки, чтобы сцена была чистой: [code] for arrow in self.arrowheads: self.scene.removeItem(arrow) self.arrowheads.clear() [/code] и без этой части все будет в беспорядке (если вы не понимаете, о чем я, вы можете удалить эту часть и запустить код, чтобы увидеть), но, похоже, она также удаляет финальную версию наконечники стрел тоже, я пробовал несколько вещей, чтобы последние стрелки оставались фиксированными на своем месте, но, похоже, это не работает, либо я что-то испортил, либо программа полностью вылетает [b]например: [/b] [code] for arrow in self.arrowheads: if dimension_line: break else: self.scene.removeItem(arrow) self.arrowheads.clear() [/code] здесь я пытаюсь сказать, что если размерная линия завершена, не удаляйте связанные с ней стрелки Я знаю, что исправить это может быть очень просто, но я просто не могу его найти, скажите мне, если у вас есть идеи - спасибо