[Python] PyQt6 - How can I change the direction of the scale/ruler

Discussão em 'Python' iniciado por Stack, Agosto 25, 2025.

  1. Stack

    Stack Membro Participativo

    I have this Ruler class

    Right now the scale looks like the below image. I want the ticks to have their roots towards the view instead of having them outwards. The base of the scale should be inside.

    I am not able to get the roots to the scales inner border(the black line).

    Here is my code for the class:

    class Ruler(QWidget):
    def __init__(self, orientation=Qt.Orientation.Horizontal, parent=None):
    super().__init__(parent)
    self._orientation = orientation
    self._origin = 0.0
    self._unit = 1.0
    self._zoom = 1.0
    self._mouse_tracking = False
    self._draw_text = False
    self._cursor_pos = QPoint()
    self.setMouseTracking(True)
    f = QFont("Courier", 9)
    f.setStyleHint(QFont.StyleHint.TypeWriter)
    self.setFont(f)

    # Ensure room for labels inside the ruler
    if orientation == Qt.Orientation.Horizontal:
    self.setFixedHeight(RULER_BREADTH + 5)
    else:
    self.setFixedWidth(RULER_BREADTH + 5)

    def minimumSizeHint(self):
    return self.sizeHint() # We'll constrain via layout if needed

    def sizeHint(self):
    if self._orientation == Qt.Orientation.Horizontal:
    return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())
    return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())


    def paintEvent(self, event):
    painter = QPainter(self)
    painter.setRenderHints(
    QPainter.RenderHint.Antialiasing |
    QPainter.RenderHint.TextAntialiasing
    )

    # Fill background
    painter.fillRect(self.rect(), QColor(236, 233, 216))

    # Minor ticks: every 1 mm, length = 1/3 of breadth
    self._draw_scale(painter, mm_interval=1,
    tick_length=RULER_BREADTH / 3,
    draw_labels=False)

    # Medium ticks: every 5 mm, length = 2/3 of breadth
    self._draw_scale(painter, mm_interval=5,
    tick_length=RULER_BREADTH * 2 / 3,
    draw_labels=False)

    # Major ticks: every 10 mm, full length, with labels
    self._draw_scale(painter, mm_interval=10,
    tick_length=RULER_BREADTH,
    draw_labels=True)

    # Draw mouse position indicator
    if self._mouse_tracking:
    painter.setOpacity(0.4)
    # assume _draw_mouse_tick exists
    self._draw_mouse_tick(painter)
    painter.setOpacity(1.0)

    # Draw border line
    painter.setPen(QPen(Qt.GlobalColor.black, 2))
    if self._orientation == Qt.Orientation.Horizontal:
    painter.drawLine(self.rect().bottomLeft(),
    self.rect().bottomRight())
    else:
    painter.drawLine(self.rect().topRight(),
    self.rect().bottomRight())


    def _draw_scale(self, painter, mm_interval, tick_length, draw_labels=False):
    is_horz = (self._orientation == Qt.Orientation.Horizontal)
    length = self.width() if is_horz else self.height()

    # Compute pixels-per-mm from DPI
    screen = QApplication.primaryScreen()
    dpi = (screen.logicalDotsPerInchX() if is_horz
    else screen.logicalDotsPerInchY())
    px_per_mm = dpi / 25.4
    step_pix = mm_interval * px_per_mm * self._zoom

    # Determine visible tick indices
    first_tick = int(self._origin // step_pix) - 1
    last_tick = int((self._origin + length) // step_pix) + 1

    for tick in range(first_tick, last_tick + 1):
    scene_pos = tick * step_pix
    screen_pos = scene_pos - self._origin
    if screen_pos < 0 or screen_pos > length:
    continue

    # Draw the tick line
    painter.setPen(QPen(Qt.GlobalColor.red, 1))
    if is_horz:

    painter.drawLine(
    QPointF(screen_pos, 0),
    QPointF(screen_pos, tick_length)
    )

    else:
    painter.drawLine(
    QPointF(0, screen_pos),
    QPointF(tick_length, screen_pos)
    )

    # Draw the label inside the ruler
    if draw_labels and (tick % (10 // mm_interval) == 0):
    label = str(abs(tick * mm_interval))
    painter.setPen(QPen(Qt.GlobalColor.black))
    if is_horz:
    x = int(screen_pos + 2)
    y = int(tick_length - 10)
    painter.drawText(x, y, label)
    else:
    painter.save()
    tx = tick_length - 2
    ty = screen_pos + 2
    painter.translate(tx, ty)
    painter.rotate(-90)
    painter.drawText(0, 0, label)
    painter.restore()



    def _draw_mouse_tick(self, painter):
    if self._orientation == Qt.Orientation.Horizontal:
    x = self._cursor_pos.x()
    painter.drawLine(QPointF(x, 0), QPointF(x, self.height()))
    else:
    y = self._cursor_pos.y()
    painter.drawLine(QPointF(0, y), QPointF(self.width(), y))

    def mouseMoveEvent(self, event):
    self._cursor_pos = event.position().toPoint()
    self.update()
    super().mouseMoveEvent(event)

    def setOrigin(self, val):
    self._origin = val
    self.update()

    def setUnit(self, val):
    self._unit = val
    self.update()

    def setZoom(self, val):
    self._zoom = val
    self.update()

    def setMouseTrack(self, track: bool):
    self._mouse_tracking = track
    self.update()

    @staticmethod
    def toMM(orientation: Qt.Orientation) -> float:
    screen = QApplication.primaryScreen()
    if orientation == Qt.Orientation.Horizontal:
    dpi = screen.logicalDotsPerInchX()
    else:
    dpi = screen.logicalDotsPerInchY()
    # 1 inch = 25.4 mm → mm per pixel = 25.4 mm / (dots per inch)
    return 25.4 / dpi


    Can anyone please help me solve the issue.

    If could specify the direction of the ruler's base dynamically that would be better.

    Thank you

    : [​IMG]

    Continue reading...

Compartilhe esta Página