diff --git a/modelling/doc/gap_handling.rst b/modelling/doc/gap_handling.rst
index b2dd897ccaaa61ff1fae0f7846bf1e1c07a05335..e939f9f60fa779b43bfdb196e0649ebd28306a08 100644
--- a/modelling/doc/gap_handling.rst
+++ b/modelling/doc/gap_handling.rst
@@ -278,6 +278,26 @@ Gap Handling Functions
   :raises: A :exc:`RuntimeError` if any gap in mhandle.gaps is only partially
            enclosed by given gap.
 
+.. function:: InsertLoopClearGaps(mhandle, bb_list, gap)
+
+  Insert loop into model, update scoring environments and remove all gaps from
+  *mhandle* which are fully enclosed by given *gap* (see :meth:`InsertLoop` and
+  :meth:`ClearGaps`).
+
+  :param mhandle: Modelling handle on which to apply change.
+  :type mhandle:  :class:`ModellingHandle`
+  :param bb_list: Loop to insert (backbone only).
+  :type bb_list:  :class:`~promod3.loop.BackboneList`
+  :param gap:     Gap defining range of loop to insert (must be consistent!).
+  :type gap:      :class:`StructuralGap`
+
+  :return: Index of next gap in mhandle.gaps after removal.
+           Returns -1 if last gap was removed or no gaps in *mhandle*.
+  :rtype:  :class:`int`
+
+  :raises: A :exc:`RuntimeError` if *bb_list* and *gap* are inconsistent or
+           if any gap in mhandle.gaps is only partially enclosed by *gap*.
+
 .. function:: MergeGaps(mhandle, index)
 
   Merges two gaps `mhandle.gaps[index]` and `mhandle.gaps[index+1]`.
diff --git a/modelling/doc/pipeline.rst b/modelling/doc/pipeline.rst
index 2db13068e8ab3637707d0d8b85ce74694cd8af04..68839c6707fd955c7ac1d022caa1fb22329d0a68 100644
--- a/modelling/doc/pipeline.rst
+++ b/modelling/doc/pipeline.rst
@@ -273,6 +273,22 @@ Modelling Steps
   :param mhandle: Modelling handle to check.
   :type mhandle:  :class:`ModellingHandle`
 
+.. function:: InsertLoop(mhandle, bb_list, start_resnum, chain_idx)
+
+  Insert loop into model and ensure consistent updating of scoring environments.
+  Note that we do not update :attr:`~ModellingHandle.all_atom_scorer_env` as
+  that one is meant to be updated only while scoring. To clear a gap while
+  inserting a loop, use the simpler :meth:`InsertLoopClearGaps`.
+
+  :param mhandle: Modelling handle on which to apply change.
+  :type mhandle:  :class:`ModellingHandle`
+  :param bb_list: Loop to insert (backbone only).
+  :type bb_list:  :class:`~promod3.loop.BackboneList`
+  :param start_resnum: Res. number defining the start position in the SEQRES.
+  :type start_resnum:  :class:`int`
+  :param chain_idx: Index of chain the loop belongs to.
+  :type chain_idx:  :class:`int`
+
 .. function:: RemoveTerminalGaps(mhandle)
 
   Removes terminal gaps without modelling them (just removes them from the list
diff --git a/modelling/pymod/_closegaps.py b/modelling/pymod/_closegaps.py
index 0ed500e5689e09124f1245478b81630e49db8c1a..279c13b3d5b930668892904f1fb9dfb2b3863171 100644
--- a/modelling/pymod/_closegaps.py
+++ b/modelling/pymod/_closegaps.py
@@ -289,18 +289,11 @@ def _CloseLoopFrame(mhandle, gap_orig, actual_candidates, actual_extended_gaps,
         _ResolveLogInfo(gap_orig, actual_extended_gaps[idx_a],
                         len(final_loop_candidates), bool(actual_db_scores),
                         max_num_all_atom > 0)
-        # update model
-        start_resnum = actual_extended_gaps[idx_a].before.GetNumber()
+        # update model and clear gaps
         bb_list = actual_candidates[idx_a][idx_b]
-        bb_list.InsertInto(mhandle.model.chains[actual_chain_idx], start_resnum)
-        # update score env.
-        mhandle.backbone_scorer_env.SetEnvironment(bb_list, start_resnum,
-                                                   actual_chain_idx)
-        if IsAllAtomScoringSetUp(mhandle):
-            mhandle.all_atom_sidechain_env.SetEnvironment(bb_list, start_resnum,
-                                                          actual_chain_idx)
-        # will return -1 if last gap removed
-        return ClearGaps(mhandle, actual_extended_gaps[idx_a])
+        actual_gap = actual_extended_gaps[idx_a]
+        # will return -1 if last gap removed, else next gap idx
+        return InsertLoopClearGaps(mhandle, bb_list, actual_gap)
     else:
         ost.LogInfo("Failed at loop insertion (%s)" % str(gap_orig))
         return -2
@@ -349,20 +342,9 @@ def _CloseLoopBare(mhandle, gap_orig, actual_candidates, actual_extended_gaps,
         # report
         _ResolveLogInfo(gap_orig, optimal_gap, n_candidates,
                         bool(actual_db_scores), max_num_all_atom > 0)
-        # update model
-        start_resnum = optimal_gap.before.GetNumber()
-        optimal_candidate.InsertInto(mhandle.model.chains[actual_chain_idx],
-                                     start_resnum)
-        # update score env.
-        mhandle.backbone_scorer_env.SetEnvironment(optimal_candidate,
-                                                   start_resnum,
-                                                   actual_chain_idx)
-        if IsAllAtomScoringSetUp(mhandle):
-            mhandle.all_atom_sidechain_env.SetEnvironment(optimal_candidate,
-                                                          start_resnum,
-                                                          actual_chain_idx)
-        # will return -1 if last gap removed
-        return ClearGaps(mhandle, optimal_gap)
+        # update model and clear gaps
+        # will return -1 if last gap removed, else next gap idx
+        return InsertLoopClearGaps(mhandle, optimal_candidate, optimal_gap)
     else:
         ost.LogInfo("Failed at loop insertion (%s)" % str(gap_orig))
         return -2
@@ -573,11 +555,7 @@ def CloseSmallDeletions(mhandle, max_extension=9, clash_thresh=1.0,
                    bb_list.TransOmegaTorsions():
                     ost.LogInfo("Closed: %s by relaxing %s" % \
                                 (mhandle.gaps[current_gap_index], current_gap))
-                    chain = current_gap.before.GetChain()
-                    bb_list.InsertInto(chain, n_stem_resnum)
-                    mhandle.backbone_scorer_env.SetEnvironment(\
-                        bb_list, n_stem_resnum, current_chain_index)
-                    ClearGaps(mhandle, current_gap)
+                    InsertLoopClearGaps(mhandle, bb_list, current_gap)
                     success = True
                     break
 
@@ -1365,15 +1343,11 @@ def ModelTermini(mhandle, torsion_sampler, fragger_handles=None,
                        actual_chain_idx)
             min_score = min(scores)
             min_idx = scores.index(min_score)
-            # update model
-            bb_list = candidates[min_idx]
-            bb_list.InsertInto(actual_chain, start_resnum)
-            # update score env.
-            mhandle.backbone_scorer_env.SetEnvironment(bb_list, start_resnum,
-                                                       actual_chain_idx)
+            # report
             ost.LogInfo("Resolved terminal gap %s (%d candidates)" % \
                         (str(actual_gap), len(candidates)))
-            ClearGaps(mhandle, actual_gap)
+            # update model and clear gap
+            InsertLoopClearGaps(mhandle, candidates[min_idx], actual_gap)
         else:
             ost.LogInfo("Failed to model terminal gap (%s)" % str(actual_gap))
             
@@ -1578,7 +1552,7 @@ def CloseLargeDeletions(mhandle, structure_db, linker_length=8,
         # replace fragment part
         fragment.InsertInto(actual_chain, n_stem_res_num)
 
-        # update score env.
+        # update score env. (manual to keep all atom sidechains)
         mhandle.backbone_scorer_env.SetEnvironment(bb_list, first_num,
                                                    actual_chain_idx)
         # will return -1 if last gap removed
@@ -1586,7 +1560,10 @@ def CloseLargeDeletions(mhandle, structure_db, linker_length=8,
 
         ost.LogInfo("Resolved %s by sampling %s as linker" % \
                     (str(gap_orig), str(actual_gap)))
-        
+    
+    # reset all atom env. if it's set (changes are too drastic so we set all)
+    if IsAllAtomScoringSetUp(mhandle):
+        mhandle.all_atom_sidechain_env.SetInitialEnvironment(mhandle.model)
 
 # these methods will be exported into module
 __all__ = ('CloseSmallDeletions', 'MergeGapsByDistance', 'FillLoopsByDatabase',
diff --git a/modelling/pymod/export_model.cc b/modelling/pymod/export_model.cc
index 542934720aa121d053924c05e262e32fd70abd0c..a6851322639523545597fdb64782bed101865ff4 100644
--- a/modelling/pymod/export_model.cc
+++ b/modelling/pymod/export_model.cc
@@ -158,7 +158,7 @@ void export_model()
                                              &WrapSetSidechainRec)
   ;
 
-  def("ClearGaps", ClearGaps, (arg("mhandle"),arg("gap")));
+  def("ClearGaps", ClearGaps, (arg("mhandle"), arg("gap")));
   def("CountEnclosedGaps", WrapCountEnclosedGaps, (arg("mhandle"),
                                                    arg("gap")));
   def("CountEnclosedInsertions", WrapCountEnclosedIns, (arg("mhandle"),
@@ -184,6 +184,10 @@ void export_model()
                                       arg("end_resnum"),
                                       arg("transform"),
                                       arg("reset_scoring_env") = true));
+  def("InsertLoop", InsertLoop,
+      (arg("mhandle"), arg("bb_list"), arg("start_resnum"), arg("chain_idx")));
+  def("InsertLoopClearGaps", InsertLoopClearGaps,
+      (arg("mhandle"), arg("bb_list"), arg("gap")));
   def("BuildRawModel", BuildRawModelHandle, 
       (arg("aln"),
        arg("include_ligands")=false,
diff --git a/modelling/src/model.cc b/modelling/src/model.cc
index 59371f1cfee9d87823b5d480407b4df37234190f..9e2b8b26abab6ad078186f4b3929efdcc47d5f08 100644
--- a/modelling/src/model.cc
+++ b/modelling/src/model.cc
@@ -627,6 +627,37 @@ void MergeMHandle(const ModellingHandle& source_mhandle,
   }
 }
 
+void InsertLoop(ModellingHandle& mhandle, const loop::BackboneList& bb_list,
+                uint start_resnum, uint chain_idx) {
+  // update model
+  ost::mol::ChainHandleList chain_list = mhandle.model.GetChainList();
+  bb_list.InsertInto(chain_list[chain_idx], start_resnum);
+  // update scoring
+  if (IsBackboneScoringSetUp(mhandle)) {
+    mhandle.backbone_scorer_env->SetEnvironment(bb_list, start_resnum,
+                                                chain_idx);
+  }
+  if (IsAllAtomScoringSetUp(mhandle)) {
+    mhandle.all_atom_sidechain_env->SetEnvironment(bb_list, start_resnum,
+                                                   chain_idx);
+  }
+}
+
+int InsertLoopClearGaps(ModellingHandle& mhandle,
+                        const loop::BackboneList& bb_list,
+                        const StructuralGap& gap) {
+  // check consistency
+  if (bb_list.size() != gap.GetFullSeq().size()) {
+    throw promod3::Error("Inconsistent gap for given loop to insert.");
+  }
+  // update model
+  uint chain_idx = gap.GetChainIndex();
+  uint start_resnum = 1;
+  if (!gap.IsNTerminal()) start_resnum = gap.before.GetNumber().GetNum();
+  InsertLoop(mhandle, bb_list, start_resnum, chain_idx);
+  return ClearGaps(mhandle, gap);
+}
+
 bool CopyConserved(ResidueView src_res, ResidueHandle dst_res, XCSEditor& edi,
                    bool& has_cbeta)
 {
diff --git a/modelling/src/model.hh b/modelling/src/model.hh
index 8ef7ac4044c857464bb300ffbf326ac5b5ab760a..9637b1763415802139f6c215290145c7bb786cff 100644
--- a/modelling/src/model.hh
+++ b/modelling/src/model.hh
@@ -90,7 +90,7 @@ void SetPsipredPredictions(ModellingHandle& mhandle,
                            const promod3::loop::PsipredPredictionList& 
                            psipred_predictions);
 
-//see Python doc
+// see Python doc
 void MergeMHandle(const ModellingHandle& source_mhandle,
                   ModellingHandle& target_mhandle,
                   uint source_chain_idx, uint target_chain_idx,
@@ -98,6 +98,12 @@ void MergeMHandle(const ModellingHandle& source_mhandle,
                   const geom::Mat4& transform,
                   bool reset_scoring_env = true);
 
+// see Python doc
+void InsertLoop(ModellingHandle& mhandle, const loop::BackboneList& bb_list,
+                uint start_resnum, uint chain_idx);
+int InsertLoopClearGaps(ModellingHandle& mhandle,
+                        const loop::BackboneList& bb_list,
+                        const StructuralGap& gap);
 
 /// \brief copies all atom of src_res to dst_res
 /// \param has_cbeta will be set to true if the src_res has a cbeta and the