docs: def add_backbone_atoms_linearly(self, start_residue, end_residue, insertion_residues, insertion_residue_map):
          '''This function returns the PDB content for a structure with the missing backbone atoms - i.e. it adds the
             N, Ca, C atoms spaced evenly between the last existing backbone atom of start_residue and the first existing
             backbone atom of end_residue. O-atoms are not currently added although we could arbitrarily add them at 90
             degrees to the plane: If resiC_x + x = resjC_x and resiC_y + y = resjC_y, i + 1 = j, then the resiO atom could
             have coordinates (resiC_x - y,  resiC_y + x).
  
             Adds backbone atoms for insertion_residues in a straight line from start_residue to end_residue. This is useful
             for some computational methods which do not require the atoms to be in the correct coordinates but expect N, CA, and C backbone atoms
             to exist for all residues (O-atoms are currently ignored here).
  
             start_residue and end_residue are Residue objects. insertion_residues is a list of PDB residue IDs (columns 22-27
             of ATOM lines in the PDB format). insertion_residue_map is a mapping from PDB residue IDs to 1-letter amino acid
             codes. The keys of insertion_residue_map must be insertion_residues.
  
             start_residue and end_residue must exist in insertion_residues and the PDB file. There is no technical requirement for this;
             we just do not handle the alternate case yet. residue_ids are presumed to be ordered in sequence (N -> C) order.
             Existing N, CA, and C atoms corresponding to these two residues will be retained as long as their atoms which
             connect to the side of those residues not identified by residue_ids are present e.g.
                - if the CA atom of the first residue is present, it will be kept as long as the N atom is present and regardless of whether the C atom is present
                - if the CA atom of the last residue is present, it will be kept as long as the C atom is present and regardless of whether the N atom is present
             All O atoms of residues in residue_ids are discarded. ANISOU records corresponding to any removed ATOMS will be removed.
  
                     1st    2nd          n-1      n
               ... N-CA-C- N-CA-C- ... N-CA-C- N-CA-C- ..
  
             Note: This function currently only supports canonical amino acids.
          '''
  
          assert(sorted(insertion_residues) == sorted(insertion_residue_map.keys()))
          assert(start_residue.chain + start_residue.residue_id in insertion_residues)
          assert(end_residue.chain + end_residue.residue_id in insertion_residues)
          assert(start_residue.chain == end_residue.chain)
  
          atoms_to_remove = []
  
          discarded_atoms = []
  
          # Remove atoms from the segment's N-terminus residue
          # if N and CA and C, keep C else discard C
          start_res_atoms_ids = self.get_atom_serial_numbers_from_pdb_residue_ids([insertion_residues[0]])
          start_res_atoms = [self.atoms[id] for id in start_res_atoms_ids]
          start_res_atom_types = [a.name for a in start_res_atoms]
          start_atoms = [None, None, None]
          for a in start_res_atoms:
              if a.name == 'N': start_atoms[0] = a
              elif a.name == 'CA': start_atoms[1] = a
              elif a.name == 'C': start_atoms[2] = a
              else: discarded_atoms.append(a.serial_number)
          if 'C' in start_res_atom_types and 'CA' not in start_res_atom_types:
              discarded_atoms += start_atoms[2].serial_number
              start_atoms[2] = None
          if not start_atoms[0]:
              raise Exception('The N atom for the start residue must exist.')
          start_atoms = [a for a in start_atoms if a]
          start_atom = start_atoms[-1]
  
          # Remove atoms from the segment's C-terminus residue
          # if N and CA and C, keep N else discard N
          end_res_atoms_ids = self.get_atom_serial_numbers_from_pdb_residue_ids([insertion_residues[-1]])
          end_res_atoms = [self.atoms[id] for id in end_res_atoms_ids]
          end_res_atom_types = [a.name for a in end_res_atoms]
          end_atoms = [None, None, None]
          for a in end_res_atoms:
              if a.name == 'N': end_atoms[0] = a
              elif a.name == 'CA': end_atoms[1] = a
              elif a.name == 'C': end_atoms[2] = a
              else: discarded_atoms.append(a.serial_number)
          if 'N' in end_res_atom_types and 'CA' not in end_res_atom_types:
              discarded_atoms += end_atoms[0].serial_number
              end_atoms[0] = None
          if not end_atoms[-1]:
              raise Exception('The C atom for the end residue must exist.')
          end_atoms = [a for a in end_atoms if a]
          end_atom = end_atoms[0]
  
          # Remove all atoms from the remainder of the segment
          discarded_atoms += self.get_atom_serial_numbers_from_pdb_residue_ids(insertion_residues[1:-1])
  
          # Remove the atoms from the PDB
          bonsai_pdb_content, cutting_pdb_content, PSE_file, PSE_script = self.prune(set(discarded_atoms), generate_pymol_session = False)
          self.__init__(bonsai_pdb_content, buffer = self.buffer, bin_size = self.bin_size, safe_mode = self.safe_mode)
  
          # Create a list of all N, CA, C atoms for the insertion residues not including those present in the start and end residue
          # Find last of N CA C of first residue
          # Find last of N CA C of first residue
          new_atoms = []
          assert(len(start_atoms) >= 1) # N is guaranteed to exist
          if len(start_atoms) == 2:
              # add a C atom
              residue_id = insertion_residues[0]
              residue_type = insertion_residue_map[residue_id]
              assert(residue_type != 'X' and residue_type in residue_type_1to3_map)
              new_atoms.append((residue_id, residue_type_1to3_map[residue_type], 'C'))
          for insertion_residue in insertion_residues[1:-1]:
              # add an N, CA, C atoms
              residue_type = insertion_residue_map[insertion_residue]
              assert(residue_type != 'X' and residue_type in residue_type_1to3_map)
              residue_type = residue_type_1to3_map[residue_type]
              new_atoms.append((insertion_residue, residue_type, 'N'))
              new_atoms.append((insertion_residue, residue_type, 'CA'))
              new_atoms.append((insertion_residue, residue_type, 'C'))
          assert(len(end_atoms) >= 1) # C is guaranteed to exist
          if len(end_atoms) == 2:
              # add an N atom
              residue_id = insertion_residues[-1]
              residue_type = insertion_residue_map[residue_id]
              assert(residue_type != 'X' and residue_type in residue_type_1to3_map)
              new_atoms.append((residue_id, residue_type_1to3_map[residue_type], 'N'))
  
          return self.add_atoms_linearly(start_atom, end_atom, new_atoms)