You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6绘制原子双键/三键异常:135°与315°方向显示为单键

问题描述

我正在为选中的原子绘制候选化学键:通过cos和sin计算出以45°步长分布的8个候选原子位置,这部分功能正常。但绘制双键或三键时,135°(左上)和315°(右下)方向的键始终显示为单键,无法正确渲染为设定的双键/三键。

相关代码片段

主绘制逻辑

# Calculate possible positions for new atoms in 360° around the selected atom
if self.selected_atom is not None:
    potential_positions = self.calc_potential_positions(self.selected_atom)
    for pos in potential_positions:
        # If atoms at position don't overlap, draw the potential bonds and atoms in different colour
        if not self.check_atom_overlap(pos[0], pos[1]):
            if self.bond_order == 2:
                self.draw_double_bond(self.selected_atom.x_coords, self.selected_atom.y_coords, pos[0],
                                      pos[1], False)
            elif self.bond_order == 3:
                self.draw_triple_bond(self.selected_atom.x_coords, self.selected_atom.y_coords,
                                      pos[0], pos[1], False)
            else:
                self.draw_single_bond(self.selected_atom.x_coords, self.selected_atom.y_coords,
                                      pos[0], pos[1], False)
            self.draw_atom_circle(pos[0], pos[1], self.selected_atom.x_coords,
                                  self.selected_atom.y_coords)
            self.draw_atom(pos[0], pos[1], self.element.SYMBOL, True)
            self.draw_center_atom(self.selected_atom.x_coords, self.selected_atom.y_coords,
                                  self.selected_atom.symbol)

候选位置计算方法

@staticmethod
def calc_potential_positions(atom: chem_editor_logic.Atom) -> list[tuple[int, int]]:
    """
    :param atom:
    :return: list of tuples containing x and y coordinates of potential positions for new atoms
    """

    x = atom.x_coords
    y = atom.y_coords
    distance = 40

    # Calculate coordinates for angles in steps of 45 degrees from 0 to 360
    coordinates_list = []
    for angle_degrees in range(0, 360, 45):
        angle_radians = math.radians(angle_degrees)
        new_x = x + distance * math.cos(angle_radians)
        new_y = y + distance * math.sin(angle_radians)
        coordinates_list.append((int(new_x), int(new_y)))

    print(x, y)
    print(coordinates_list)
    return coordinates_list

化学键绘制方法

def draw_single_bond(self, atom1_x: int, atom1_y: int, atom2_x: int, atom2_y: int, actual_bond: bool) -> None:
    painter = QPainter(self)
    pen = QPen()

    # Set colour black
    pen.setColor(QColor(0, 0, 0))
    pen.setWidth(2)

    if not actual_bond:
        # Set colour red
        pen.setColor(QColor(255, 0, 0))
    painter.setPen(pen)

    painter.drawLine(QPoint(atom1_x, atom1_y), QPoint(atom2_x, atom2_y))

def draw_double_bond(self, atom1_x: int, atom1_y: int, atom2_x: int, atom2_y: int, actual_bond: bool) -> None:
    painter = QPainter(self)
    pen = QPen()

    # Set colour black
    pen.setColor(QColor(0, 0, 0))
    pen.setWidth(2)

    if not actual_bond:
        # Set colour red
        pen.setColor(QColor(255, 0, 0))
    painter.setPen(pen)

    # Draw two lines for a double bond
    offset = 3
    painter.drawLine(QPoint(atom1_x - offset, atom1_y - offset),
                     QPoint(atom2_x - offset, atom2_y - offset))
    painter.drawLine(QPoint(atom1_x + offset, atom1_y + offset),
                     QPoint(atom2_x + offset, atom2_y + offset))

def draw_triple_bond(self, atom1_x: int, atom1_y: int, atom2_x: int, atom2_y: int, actual_bond: bool) -> None:
    painter = QPainter(self)
    pen = QPen()

    # Set colour black
    pen.setColor(QColor(0, 0, 0))
    pen.setWidth(2)

    if not actual_bond:
        # Set colour red
        pen.setColor(QColor(255, 0, 0))
    painter.setPen(pen)

    # Draw three lines for a triple bond
    offset = 4
    painter.drawLine(QPoint(atom1_x, atom1_y),
                     QPoint(atom2_x, atom2_y))
    painter.drawLine(QPoint(atom1_x - offset, atom1_y - offset),
                     QPoint(atom2_x - offset, atom2_y - offset))
    painter.drawLine(QPoint(atom1_x + offset, atom1_y + offset),
                     QPoint(atom2_x + offset, atom2_y + offset))
问题原因

当前双键和三键的绘制逻辑使用固定的±offset同时偏移x和y坐标,这种方式仅在**轴对齐方向(0°、90°、180°、270°)**有效。对于135°(左上,x减少、y增加)和315°(右下,x增加、y减少)的对角线方向:

  • 两条偏移线会与主键线平行且视觉上重叠成一条线,看起来和单键无异
  • 本质是固定的x/y偏移没有垂直于键的方向,导致辅助线无法区分开来
解决方案

修改draw_double_bond和draw_triple_bond方法,根据键的方向计算垂直偏移量,确保辅助线始终垂直于主键方向,从而在所有角度下都能正确显示双键/三键。

具体实现步骤:

  1. 计算键的方向向量dx = atom2_x - atom1_x,dy = atom2_y - atom1_y
  2. 计算垂直于键方向的单位向量:2D中向量(dx, dy)的垂直向量为(-dy, dx)和(dy, -dx),归一化后乘以偏移量得到实际偏移坐标
  3. 使用偏移后的坐标绘制辅助线

修改后的代码示例:

import math

def draw_double_bond(self, atom1_x: int, atom1_y: int, atom2_x: int, atom2_y: int, actual_bond: bool) -> None:
    painter = QPainter(self)
    pen = QPen()

    pen.setColor(QColor(0, 0, 0))
    pen.setWidth(2)
    if not actual_bond:
        pen.setColor(QColor(255, 0, 0))
    painter.setPen(pen)

    # 计算方向向量和垂直偏移
    dx = atom2_x - atom1_x
    dy = atom2_y - atom1_y
    length = math.hypot(dx, dy)
    if length == 0:
        return  # 避免除以0

    # 垂直单位向量(两个方向)
    perp_x1 = -dy / length
    perp_y1 = dx / length
    perp_x2 = dy / length
    perp_y2 = -dx / length

    offset = 3
    # 计算偏移后的起点和终点
    start1_x = atom1_x + perp_x1 * offset
    start1_y = atom1_y + perp_y1 * offset
    end1_x = atom2_x + perp_x1 * offset
    end1_y = atom2_y + perp_y1 * offset

    start2_x = atom1_x + perp_x2 * offset
    start2_y = atom1_y + perp_y2 * offset
    end2_x = atom2_x + perp_x2 * offset
    end2_y = atom2_y + perp_y2 * offset

    painter.drawLine(QPoint(int(start1_x), int(start1_y)), QPoint(int(end1_x), int(end1_y)))
    painter.drawLine(QPoint(int(start2_x), int(start2_y)), QPoint(int(end2_x), int(end2_y)))

def draw_triple_bond(self, atom1_x: int, atom1_y: int, atom2_x: int, atom2_y: int, actual_bond: bool) -> None:
    painter = QPainter(self)
    pen = QPen()

    pen.setColor(QColor(0, 0, 0))
    pen.setWidth(2)
    if not actual_bond:
        pen.setColor(QColor(255, 0, 0))
    painter.setPen(pen)

    # 先画主键
    painter.drawLine(QPoint(atom1_x, atom1_y), QPoint(atom2_x, atom2_y))

    # 计算方向向量和垂直偏移
    dx = atom2_x - atom1_x
    dy = atom2_y - atom1_y
    length = math.hypot(dx, dy)
    if length == 0:
        return

    # 垂直单位向量
    perp_x1 = -dy / length
    perp_y1 = dx / length
    perp_x2 = dy / length
    perp_y2 = -dx / length

    offset = 4
    # 计算偏移后的起点和终点
    start1_x = atom1_x + perp_x1 * offset
    start1_y = atom1_y + perp_y1 * offset
    end1_x = atom2_x + perp_x1 * offset
    end1_y = atom2_y + perp_y1 * offset

    start2_x = atom1_x + perp_x2 * offset
    start2_y = atom1_y + perp_y2 * offset
    end2_x = atom2_x + perp_x2 * offset
    end2_y = atom2_y + perp_y2 * offset

    painter.drawLine(QPoint(int(start1_x), int(start1_y)), QPoint(int(end1_x), int(end1_y)))
    painter.drawLine(QPoint(int(start2_x), int(start2_y)), QPoint(int(end2_x), int(end2_y)))

内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:49:53