docs: def c_join(self, other, psi=-40.76, omega=-178.25, phi=-65.07,
                 o_c_n_angle=None, c_n_ca_angle=None, c_n_length=None,
                 relabel=True):
          """Joins other to self at the C-terminus via a peptide bond.
  
          Notes
          -----
          This function directly modifies self. It does not return a new object.
  
          Parameters
          ----------
          other: Residue or Polypeptide
          psi: float, optional
              Psi torsion angle (degrees) between final `Residue` of self
              and first `Residue` of other.
          omega: float, optional
              Omega torsion angle (degrees) between final `Residue` of
              self and first `Residue` of other.
          phi: float, optional
              Phi torsion angle (degrees) between final `Residue` of self
              and first `Residue` of other.
          o_c_n_angle: float or None, optional
              Desired angle between O, C (final `Residue` of self) and N
              (first `Residue` of other) atoms. If `None`, default value is
              taken from `ideal_backbone_bond_angles`.
          c_n_ca_angle: float or None, optional
              Desired angle between C (final `Residue` of self) and N, CA
              (first `Residue` of other) atoms. If `None`, default value is
              taken from `ideal_backbone_bond_angles`.
          c_n_length: float or None, optional
              Desired peptide bond length between final `Residue` of self
              and first `Residue` of other. If `None`, default value is taken
              from `ideal_backbone_bond_lengths`.
          relabel: bool, optional
              If `True`, `relabel_all` is run on self before returning.
  
          Raises
          ------
          TypeError:
              If other is not a `Residue` or a Polypeptide.
          """
          if isinstance(other, Residue):
              other = Polypeptide([other])
          if not isinstance(other, Polypeptide):
              raise TypeError(
                  'Only Polypeptide or Residue objects can be joined to a Polypeptide')
          if abs(omega) >= 90:
              peptide_conformation = 'trans'
          else:
              peptide_conformation = 'cis'
          if o_c_n_angle is None:
              o_c_n_angle = ideal_backbone_bond_angles[peptide_conformation]['o_c_n']
          if c_n_ca_angle is None:
              c_n_ca_angle = ideal_backbone_bond_angles[peptide_conformation]['c_n_ca']
          if c_n_length is None:
              c_n_length = ideal_backbone_bond_lengths['c_n']
          r1 = self[-1]
          r1_ca = r1['CA']._vector
          r1_c = r1['C']._vector
          r1_o = r1['O']._vector
          # p1 is point that will be used to position the N atom of r2.
          p1 = r1_o[:]
          # rotate p1 by o_c_n_angle, about axis perpendicular to the
          # r1_ca, r1_c, r1_o plane, passing through r1_c.
          axis = numpy.cross((r1_ca - r1_c), (r1_o - r1_c))
          q = Quaternion.angle_and_axis(angle=o_c_n_angle, axis=axis)
          p1 = q.rotate_vector(v=p1, point=r1_c)
          # Ensure p1 is separated from r1_c by the correct distance.
          p1 = r1_c + (c_n_length * unit_vector(p1 - r1_c))
          # rotate p1 and r1['O'] by to obtain desired psi value at the join.
          measured_psi = dihedral(r1['N'], r1['CA'], r1['C'], p1)
          q = Quaternion.angle_and_axis(
              angle=(psi - measured_psi), axis=(r1_c - r1_ca))
          p1 = q.rotate_vector(v=p1, point=r1_c)
          r1['O']._vector = q.rotate_vector(v=r1_o, point=r1_c)
          # translate other so that its first N atom is at p1
          other.translate(vector=(p1 - other[0]['N']._vector))
          # rotate other so that c_n_ca angle is correct.
          v1 = r1_c - other[0]['N']._vector
          v2 = other[0]['CA']._vector - other[0]['N']._vector
          measured_c_n_ca = angle_between_vectors(v1, v2)
          axis = numpy.cross(v1, v2)
          other.rotate(angle=(c_n_ca_angle - measured_c_n_ca),
                       axis=axis, point=other[0]['N']._vector)
          # rotate other to obtain desired omega and phi values at the join
          measured_omega = dihedral(
              r1['CA'], r1['C'], other[0]['N'], other[0]['CA'])
          other.rotate(angle=(omega - measured_omega),
                       axis=(other[0]['N'] - r1['C']), point=other[0]['N']._vector)
          measured_phi = dihedral(
              r1['C'], other[0]['N'], other[0]['CA'], other[0]['C'])
          other.rotate(angle=(phi - measured_phi),
                       axis=(other[0]['CA'] - other[0]['N']), point=other[0]['CA']._vector)
          self.extend(other)
          if relabel:
              self.relabel_all()
          self.tags['assigned_ff'] = False
          return