예제 #1
0
    def normalize_bond_length(self, bond_length=30):
        """make the average bond-length be bond_length by scaling the structure up"""
        if not self.edges or len(self.vertices) < 2:
            return False

        minx = None
        maxx = None
        miny = None
        maxy = None
        # atoms
        for v in self.vertices:
            if not maxx or v.x > maxx:
                maxx = v.x
            if not minx or v.x < minx:
                minx = v.x
            if not miny or v.y < miny:
                miny = v.y
            if not maxy or v.y > maxy:
                maxy = v.y

        scale = bond_length / self.get_mean_bond_length()
        movex = (maxx + minx) / 2
        movey = (maxy + miny) / 2
        trans = transform3d.transform3d()
        trans.set_move(-movex, -movey, 0)
        trans.set_scaling(scale)
        trans.set_move(movex, movey, 0)
        for v in self.atoms:
            v.x, v.y, v.z = trans.transform_xyz(v.x, v.y, v.z)
        return True
예제 #2
0
파일: molecule.py 프로젝트: Robot-Will/OASA
  def normalize_bond_length( self, bond_length=30):
    """make the average bond-length be bond_length by scaling the structure up"""
    if not self.edges or len( self.vertices) < 2:
      return False

    minx = None
    maxx = None
    miny = None
    maxy = None
    # atoms
    for v in self.vertices:
      if not maxx or v.x > maxx:
        maxx = v.x
      if not minx or v.x < minx:
        minx = v.x
      if not miny or v.y < miny:
        miny = v.y
      if not maxy or v.y > maxy:
        maxy = v.y

    scale = bond_length / self.get_mean_bond_length()
    movex = (maxx+minx)/2
    movey = (maxy+miny)/2
    trans = transform3d.transform3d()
    trans.set_move( -movex, -movey, 0)
    trans.set_scaling( scale)
    trans.set_move( movex, movey, 0)
    for v in self.atoms:
      v.x, v.y, v.z = trans.transform_xyz( v.x, v.y, v.z)
    return True
예제 #3
0
def create_transformation_to_rotate_around_particular_axis(
        line_start, line_end, theta):
    """
  taken from http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html
  """
    a, b, c = line_start
    u, v, w = line_end
    u -= a
    v -= b
    w -= c
    u2 = u * u
    v2 = v * v
    w2 = w * w
    cosT = cos(theta)
    sinT = sin(theta)
    l2 = u2 + v2 + w2
    l = sqrt(l2)

    if (l2 < 0.000000001):
        raise ValueError("RotationMatrix: direction vector too short!")

    m11 = (u2 + (v2 + w2) * cosT) / l2
    m12 = (u * v * (1 - cosT) - w * l * sinT) / l2
    m13 = (u * w * (1 - cosT) + v * l * sinT) / l2
    m14 = (a * (v2 + w2) - u * (b * v + c * w) + (u * (b * v + c * w) - a *
                                                  (v2 + w2)) * cosT +
           (b * w - c * v) * l * sinT) / l2

    m21 = (u * v * (1 - cosT) + w * l * sinT) / l2
    m22 = (v2 + (u2 + w2) * cosT) / l2
    m23 = (v * w * (1 - cosT) - u * l * sinT) / l2
    m24 = (b * (u2 + w2) - v * (a * u + c * w) + (v * (a * u + c * w) - b *
                                                  (u2 + w2)) * cosT +
           (c * u - a * w) * l * sinT) / l2

    m31 = (u * w * (1 - cosT) - v * l * sinT) / l2
    m32 = (v * w * (1 - cosT) + u * l * sinT) / l2
    m33 = (w2 + (u2 + v2) * cosT) / l2
    m34 = (c * (u2 + v2) - w * (a * u + b * v) + (w * (a * u + b * v) - c *
                                                  (u2 + v2)) * cosT +
           (a * v - b * u) * l * sinT) / l2
    from transform3d import transform3d
    t = transform3d([[m11, m12, m13, m14], [m21, m22, m23, m24],
                     [m31, m32, m33, m34], [0, 0, 0, 1]])
    return t
예제 #4
0
def create_transformation_to_coincide_point_with_z_axis(mov, point):
    """takes 3d coordinates 'point' (vector mov->point) and returns 3d transform object
  (transform3d.transform3d) that performs rotation to get 'point' onto z axis (x,y)=(0,0)
  with positive 'z'.
  NOTE: this is probably far from efficient, but it works
  """
    from transform3d import transform3d
    t = transform3d()
    a, b, c = mov
    t.set_move(-a, -b, -c)
    x, y, z = t.transform_xyz(*point)
    t.set_rotation_y(atan2(x, z))
    x, y, z = t.transform_xyz(*point)
    t.set_rotation_x(-atan2(y, sqrt(x**2 + z**2)))
    x, y, z = t.transform_xyz(*point)
    if z < 0:
        t.set_rotation_x(pi)
    #t.set_move( *mov)
    return t
예제 #5
0
파일: geometry.py 프로젝트: Robot-Will/OASA
def create_transformation_to_coincide_point_with_z_axis( mov, point):
  """takes 3d coordinates 'point' (vector mov->point) and returns 3d transform object
  (transform3d.transform3d) that performs rotation to get 'point' onto z axis (x,y)=(0,0)
  with positive 'z'.
  NOTE: this is probably far from efficient, but it works
  """
  from transform3d import transform3d
  t = transform3d()
  a,b,c = mov
  t.set_move( -a, -b, -c)
  x,y,z = t.transform_xyz( *point)
  t.set_rotation_y( atan2( x, z))
  x,y,z = t.transform_xyz( *point)
  t.set_rotation_x( -atan2( y, sqrt(x**2+z**2)))
  x,y,z = t.transform_xyz( *point)
  if z < 0:
      t.set_rotation_x( pi)
  #t.set_move( *mov)
  return t
예제 #6
0
파일: geometry.py 프로젝트: Robot-Will/OASA
def create_transformation_to_rotate_around_particular_axis( line_start, line_end, theta):
  """
  taken from http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html
  """
  a,b,c = line_start
  u,v,w = line_end
  u -= a
  v -= b
  w -= c
  u2 = u*u;
  v2 = v*v;
  w2 = w*w;
  cosT = cos(theta);
  sinT = sin(theta);
  l2 = u2 + v2 + w2;
  l =  sqrt(l2);

  if (l2 < 0.000000001):
      raise ValueError("RotationMatrix: direction vector too short!")

  m11 = (u2 + (v2 + w2) * cosT)/l2;
  m12 = (u*v * (1 - cosT) - w*l*sinT)/l2;
  m13 = (u*w * (1 - cosT) + v*l*sinT)/l2;
  m14 = (a*(v2 + w2) - u*(b*v + c*w) 
      + (u*(b*v + c*w) - a*(v2 + w2))*cosT + (b*w - c*v)*l*sinT)/l2;

  m21 = (u*v * (1 - cosT) + w*l*sinT)/l2;
  m22 = (v2 + (u2 + w2) * cosT)/l2;
  m23 = (v*w * (1 - cosT) - u*l*sinT)/l2;
  m24 = (b*(u2 + w2) - v*(a*u + c*w) 
      + (v*(a*u + c*w) - b*(u2 + w2))*cosT + (c*u - a*w)*l*sinT)/l2;

  m31 = (u*w * (1 - cosT) - v*l*sinT)/l2;
  m32 = (v*w * (1 - cosT) + u*l*sinT)/l2;
  m33 = (w2 + (u2 + v2) * cosT)/l2;
  m34 = (c*(u2 + v2) - w*(a*u + b*v) 
      + (w*(a*u + b*v) - c*(u2 + v2))*cosT + (a*v - b*u)*l*sinT)/l2;
  from transform3d import transform3d
  t = transform3d( [[m11,m12,m13,m14],[m21,m22,m23,m24],[m31,m32,m33,m34],[0,0,0,1]])
  return t
예제 #7
0
  def _draw_edge( self, e):
    def draw_plain_or_colored_line( _start, _end, second=False):
      """second means if this is not the main line, drawing might be different"""
      if not has_shown_vertex or not self.color_bonds:
        if not second:
          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_ROUND)
        else:
          self._draw_line( _start, _end, line_width=self.line_width, capstyle=cairo.LINE_CAP_BUTT)
      else:
        self._draw_colored_line( _start, _end, line_width=self.line_width, start_color=color1, end_color=color2)

    def draw_plain_or_colored_wedge( _start, _end):
      x1, y1 = _start
      x2, y2 = _end
      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
      # no coloring now
      if not has_shown_vertex or not self.color_bonds:
        self._create_cairo_path( [(xa, ya), (x0, y0), (2*x2-x0, 2*y2-y0), (2*x1-xa, 2*y1-ya)], closed=True)
        self.context.set_source_rgb( 0,0,0)
        self.context.fill()
      else:
        # ratio 0.4 looks better than 0.5 because the area difference
        # is percieved more than length difference
        ratio = 0.4
        xm1 = ratio*xa + (1-ratio)*x0 
        ym1 = ratio*ya + (1-ratio)*y0
        xm2 = (1-ratio)*(2*x2-x0) + ratio*(2*x1-xa)
        ym2 = (1-ratio)*(2*y2-y0) + ratio*(2*y1-ya)
        self.context.set_source_rgb( *color1)
        self._create_cairo_path( [(xa,ya), (xm1,ym1), (xm2,ym2), (2*x1-xa, 2*y1-ya)], closed=True)
        self.context.fill()
        self.context.set_source_rgb( *color2)
        self._create_cairo_path( [(xm1,ym1), (x0, y0), (2*x2-x0, 2*y2-y0), (xm2,ym2)], closed=True)
        self.context.fill()

    def draw_plain_or_colored_hatch( _start, _end):
      x1, y1 = _start
      x2, y2 = _end
      # no coloring now
      x, y, x0, y0 = geometry.find_parallel( x1, y1, x2, y2, self.wedge_width/2.0)
      xa, ya, xb, yb = geometry.find_parallel( x1, y1, x2, y2, self.line_width/2.0) 
      d = math.sqrt( (x1-x2)**2 + (y1-y2)**2) # length of the bond
      if d == 0:  
        return  # to prevent division by zero
      dx1 = (x0 - xa)/d 
      dy1 = (y0 - ya)/d 
      dx2 = (2*x2 -x0 -2*x1 +xa)/d 
      dy2 = (2*y2 -y0 -2*y1 +ya)/d 
      # we have to decide if the first line should be at the position of the first atom
      draw_start = 1  # is index not boolean
      if not v1 in self._vertex_to_bbox and v1.occupied_valency > 1:
        draw_start = 1
      draw_end = 1    # is added to index not boolean
      if not v2 in self._vertex_to_bbox and v2.occupied_valency > 1:
        draw_end = 0
      # adjust the step length
      step_size = 2*(self.line_width)
      ns = round( d / step_size) or 1
      step_size = d / ns
      # now we finally draw
      self.context.set_line_cap( cairo.LINE_CAP_BUTT)
      self.context.set_source_rgb( *color1)
      middle = 0.5 * (draw_start + int( round( d/ step_size)) + draw_end - 2)
      for i in range( draw_start, int( round( d/ step_size)) +draw_end):
        coords = [xa+dx1*i*step_size, ya+dy1*i*step_size, 2*x1-xa+dx2*i*step_size, 2*y1-ya+dy2*i*step_size] 
        if coords[0] == coords[2] and coords[1] == coords[3]:
          if (dx1+dx2) > (dy1+dy2): 
            coords[0] += 1
          else:
            coords[1] += 1
        self._create_cairo_path( [coords[:2],coords[2:]])
        if i >= middle:
          self.context.stroke()
          self.context.set_source_rgb( *color2)
      self.context.stroke()

    # code itself
    # at first detect the need to make 3D adjustments
    self._transform = transform3d.transform3d()
    self._invtransform = transform3d.transform3d()
    transform = None
    if e.order > 1:
      atom1,atom2 = e.vertices
      for n in atom1.neighbors + atom2.neighbors:
        # e.atom1 and e.atom2 are in this list as well
        if n.z != 0:
          # engage 3d transform prior to detection of where to draw
          transform = self._get_3dtransform_for_drawing( e)
          #transform = None
          break
      if transform:
        for n in atom1.neighbors + atom2.neighbors:
          n.coords = transform.transform_xyz( *n.coords)
        self._transform = transform
        self._invtransform = transform.get_inverse()
    # // end of 3D adjustments
    # now the code itself
    coords = self._where_to_draw_from_and_to( e)
    if coords:
      start = coords[:2]
      end = coords[2:]
      v1, v2 = e.vertices
      if self.color_bonds:
        color1 = self.atom_colors.get( v1.symbol, (0,0,0))
        color2 = self.atom_colors.get( v2.symbol, (0,0,0))
      else:
        color1 = color2 = (0,0,0)
      has_shown_vertex = bool( [1 for _v in e.vertices if _v in self._vertex_to_bbox])

      if e.order == 1:
        if e.type == 'w':
          draw_plain_or_colored_wedge( start, end)
        elif e.type == 'h':
          draw_plain_or_colored_hatch( start, end)
        else:
          draw_plain_or_colored_line( start, end)

      if e.order == 2:
        side = 0
        # find how to center the bonds
        # rings have higher priority in setting the positioning
        in_ring = False
        for ring in self.molecule.get_smallest_independent_cycles_dangerous_and_cached():
          double_bonds = len( [b for b in self.molecule.vertex_subgraph_to_edge_subgraph(ring) if b.order == 2])
          if v1 in ring and v2 in ring:
            in_ring = True
            side += double_bonds * reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y)) for a in ring if a!=v1 and a!=v2])
        # if rings did not decide, use the other neigbors
        if not side:
          for v in v1.neighbors + v2.neighbors:
            if v != v1 and v!= v2:
              side += geometry.on_which_side_is_point( start+end, (v.x, v.y))
        # if neighbors did not decide either
        if not side and (in_ring or not has_shown_vertex):
          if in_ring:
            # we don't want centered bonds inside rings
            side = 1 # select arbitrary value
          else:
            # bond between two unshown atoms - we want to center them only in some cases
            if len( v1.neighbors) == 1 and len( v2.neighbors) == 1:
              # both atoms have only one neighbor 
              side = 0
            elif len( v1.neighbors) < 3 and len( v2.neighbors) < 3:
              # try to figure out which side is more towards the center of the molecule
              side = reduce( operator.add, [geometry.on_which_side_is_point( start+end, (a.x,a.y))
                                            for a in self.molecule.vertices if a!=v1 and a!=v2], 0)
              if not side:
                side = 1 # we choose arbitrary value, we don't want centering
        if side:
          draw_plain_or_colored_line( start, end)
          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], self.bond_width*misc.signum( side))
          # shorten the second line
          length = geometry.point_distance( x1,y1,x2,y2)
          if v2 not in self._vertex_to_bbox:
            x2, y2 = geometry.elongate_line( x1, y1, x2, y2, -self.bond_second_line_shortening*length)
          if v1 not in self._vertex_to_bbox:
            x1, y1 = geometry.elongate_line( x2, y2, x1, y1, -self.bond_second_line_shortening*length)
          draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)
        else:
          for i in (1,-1):
            x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.5)
            draw_plain_or_colored_line( (x1, y1), (x2, y2))

      elif e.order == 3:
        self._draw_line( start, end, line_width=self.line_width)
        for i in (1,-1):
          x1, y1, x2, y2 = geometry.find_parallel( start[0], start[1], end[0], end[1], i*self.bond_width*0.7)
          draw_plain_or_colored_line( (x1, y1), (x2, y2), second=True)

    if transform:
      # if transform was used, we need to transform back
      for n in atom1.neighbors + atom2.neighbors:
        n.coords = self._invtransform.transform_xyz( *n.coords)
예제 #8
0
    def _draw_edge(self, e):
        def draw_plain_or_colored_line(_start, _end, second=False):
            """second means if this is not the main line, drawing might be different"""
            if not has_shown_vertex or not self.color_bonds:
                if not second:
                    self._draw_line(_start,
                                    _end,
                                    line_width=self.line_width,
                                    capstyle=cairo.LINE_CAP_ROUND)
                else:
                    self._draw_line(_start,
                                    _end,
                                    line_width=self.line_width,
                                    capstyle=cairo.LINE_CAP_BUTT)
            else:
                self._draw_colored_line(_start,
                                        _end,
                                        line_width=self.line_width,
                                        start_color=color1,
                                        end_color=color2)

        def draw_plain_or_colored_wedge(_start, _end):
            x1, y1 = _start
            x2, y2 = _end
            x, y, x0, y0 = geometry.find_parallel(x1, y1, x2, y2,
                                                  self.wedge_width / 2.0)
            xa, ya, xb, yb = geometry.find_parallel(x1, y1, x2, y2,
                                                    self.line_width / 2.0)
            # no coloring now
            if not has_shown_vertex or not self.color_bonds:
                self._create_cairo_path([(xa, ya), (x0, y0),
                                         (2 * x2 - x0, 2 * y2 - y0),
                                         (2 * x1 - xa, 2 * y1 - ya)],
                                        closed=True)
                self.context.set_source_rgb(0, 0, 0)
                self.context.fill()
            else:
                # ratio 0.4 looks better than 0.5 because the area difference
                # is percieved more than length difference
                ratio = 0.4
                xm1 = ratio * xa + (1 - ratio) * x0
                ym1 = ratio * ya + (1 - ratio) * y0
                xm2 = (1 - ratio) * (2 * x2 - x0) + ratio * (2 * x1 - xa)
                ym2 = (1 - ratio) * (2 * y2 - y0) + ratio * (2 * y1 - ya)
                self.context.set_source_rgb(*color1)
                self._create_cairo_path([(xa, ya), (xm1, ym1), (xm2, ym2),
                                         (2 * x1 - xa, 2 * y1 - ya)],
                                        closed=True)
                self.context.fill()
                self.context.set_source_rgb(*color2)
                self._create_cairo_path([(xm1, ym1), (x0, y0),
                                         (2 * x2 - x0, 2 * y2 - y0),
                                         (xm2, ym2)],
                                        closed=True)
                self.context.fill()

        def draw_plain_or_colored_hatch(_start, _end):
            x1, y1 = _start
            x2, y2 = _end
            # no coloring now
            x, y, x0, y0 = geometry.find_parallel(x1, y1, x2, y2,
                                                  self.wedge_width / 2.0)
            xa, ya, xb, yb = geometry.find_parallel(x1, y1, x2, y2,
                                                    self.line_width / 2.0)
            d = math.sqrt((x1 - x2)**2 + (y1 - y2)**2)  # length of the bond
            if d == 0:
                return  # to prevent division by zero
            dx1 = (x0 - xa) / d
            dy1 = (y0 - ya) / d
            dx2 = (2 * x2 - x0 - 2 * x1 + xa) / d
            dy2 = (2 * y2 - y0 - 2 * y1 + ya) / d
            # we have to decide if the first line should be at the position of the first atom
            draw_start = 1  # is index not boolean
            if not v1 in self._vertex_to_bbox and v1.occupied_valency > 1:
                draw_start = 1
            draw_end = 1  # is added to index not boolean
            if not v2 in self._vertex_to_bbox and v2.occupied_valency > 1:
                draw_end = 0
            # adjust the step length
            step_size = 2 * (self.line_width)
            ns = round(d / step_size) or 1
            step_size = d / ns
            # now we finally draw
            self.context.set_line_cap(cairo.LINE_CAP_BUTT)
            self.context.set_source_rgb(*color1)
            middle = 0.5 * (draw_start + int(round(d / step_size)) + draw_end -
                            2)
            for i in range(draw_start, int(round(d / step_size)) + draw_end):
                coords = [
                    xa + dx1 * i * step_size, ya + dy1 * i * step_size,
                    2 * x1 - xa + dx2 * i * step_size,
                    2 * y1 - ya + dy2 * i * step_size
                ]
                if coords[0] == coords[2] and coords[1] == coords[3]:
                    if (dx1 + dx2) > (dy1 + dy2):
                        coords[0] += 1
                    else:
                        coords[1] += 1
                self._create_cairo_path([coords[:2], coords[2:]])
                if i >= middle:
                    self.context.stroke()
                    self.context.set_source_rgb(*color2)
            self.context.stroke()

        # code itself
        # at first detect the need to make 3D adjustments
        self._transform = transform3d.transform3d()
        self._invtransform = transform3d.transform3d()
        transform = None
        if e.order > 1:
            atom1, atom2 = e.vertices
            for n in atom1.neighbors + atom2.neighbors:
                # e.atom1 and e.atom2 are in this list as well
                if n.z != 0:
                    # engage 3d transform prior to detection of where to draw
                    transform = self._get_3dtransform_for_drawing(e)
                    #transform = None
                    break
            if transform:
                for n in atom1.neighbors + atom2.neighbors:
                    n.coords = transform.transform_xyz(*n.coords)
                self._transform = transform
                self._invtransform = transform.get_inverse()
        # // end of 3D adjustments
        # now the code itself
        coords = self._where_to_draw_from_and_to(e)
        if coords:
            start = coords[:2]
            end = coords[2:]
            v1, v2 = e.vertices
            if self.color_bonds:
                color1 = self.atom_colors.get(v1.symbol, (0, 0, 0))
                color2 = self.atom_colors.get(v2.symbol, (0, 0, 0))
            else:
                color1 = color2 = (0, 0, 0)
            has_shown_vertex = bool(
                [1 for _v in e.vertices if _v in self._vertex_to_bbox])

            if e.order == 1:
                if e.type == 'w':
                    draw_plain_or_colored_wedge(start, end)
                elif e.type == 'h':
                    draw_plain_or_colored_hatch(start, end)
                else:
                    draw_plain_or_colored_line(start, end)

            if e.order == 2:
                side = 0
                # find how to center the bonds
                # rings have higher priority in setting the positioning
                in_ring = False
                for ring in self.molecule.get_smallest_independent_cycles_dangerous_and_cached(
                ):
                    double_bonds = len([
                        b for b in
                        self.molecule.vertex_subgraph_to_edge_subgraph(ring)
                        if b.order == 2
                    ])
                    if v1 in ring and v2 in ring:
                        in_ring = True
                        side += double_bonds * reduce(operator.add, [
                            geometry.on_which_side_is_point(
                                start + end, (a.x, a.y))
                            for a in ring if a != v1 and a != v2
                        ])
                # if rings did not decide, use the other neigbors
                if not side:
                    for v in v1.neighbors + v2.neighbors:
                        if v != v1 and v != v2:
                            side += geometry.on_which_side_is_point(
                                start + end, (v.x, v.y))
                # if neighbors did not decide either
                if not side and (in_ring or not has_shown_vertex):
                    if in_ring:
                        # we don't want centered bonds inside rings
                        side = 1  # select arbitrary value
                    else:
                        # bond between two unshown atoms - we want to center them only in some cases
                        if len(v1.neighbors) == 1 and len(v2.neighbors) == 1:
                            # both atoms have only one neighbor
                            side = 0
                        elif len(v1.neighbors) < 3 and len(v2.neighbors) < 3:
                            # try to figure out which side is more towards the center of the molecule
                            side = reduce(operator.add, [
                                geometry.on_which_side_is_point(
                                    start + end, (a.x, a.y))
                                for a in self.molecule.vertices
                                if a != v1 and a != v2
                            ], 0)
                            if not side:
                                side = 1  # we choose arbitrary value, we don't want centering
                if side:
                    draw_plain_or_colored_line(start, end)
                    x1, y1, x2, y2 = geometry.find_parallel(
                        start[0], start[1], end[0], end[1],
                        self.bond_width * misc.signum(side))
                    # shorten the second line
                    length = geometry.point_distance(x1, y1, x2, y2)
                    if v2 not in self._vertex_to_bbox:
                        x2, y2 = geometry.elongate_line(
                            x1, y1, x2, y2,
                            -self.bond_second_line_shortening * length)
                    if v1 not in self._vertex_to_bbox:
                        x1, y1 = geometry.elongate_line(
                            x2, y2, x1, y1,
                            -self.bond_second_line_shortening * length)
                    draw_plain_or_colored_line((x1, y1), (x2, y2), second=True)
                else:
                    for i in (1, -1):
                        x1, y1, x2, y2 = geometry.find_parallel(
                            start[0], start[1], end[0], end[1],
                            i * self.bond_width * 0.5)
                        draw_plain_or_colored_line((x1, y1), (x2, y2))

            elif e.order == 3:
                self._draw_line(start, end, line_width=self.line_width)
                for i in (1, -1):
                    x1, y1, x2, y2 = geometry.find_parallel(
                        start[0], start[1], end[0], end[1],
                        i * self.bond_width * 0.7)
                    draw_plain_or_colored_line((x1, y1), (x2, y2), second=True)

        if transform:
            # if transform was used, we need to transform back
            for n in atom1.neighbors + atom2.neighbors:
                n.coords = self._invtransform.transform_xyz(*n.coords)