diff --git a/src/core/atomic/atomstruct_cpp/Atom.cpp b/src/core/atomic/atomstruct_cpp/Atom.cpp
index 1903479..9032ebf 100644
--- a/src/core/atomic/atomstruct_cpp/Atom.cpp
+++ b/src/core/atomic/atomstruct_cpp/Atom.cpp
@@ -846,6 +846,14 @@ Atom::get_idatm_info_map()
     return _idatm_map;
 }
 
+void 
+Atom::bonds_track_change(const std::string& reason) {
+    for(std::vector<Bond*>::iterator b = _bonds.begin(); b != _bonds.end(); ++b) {
+        (*b)->track_change(reason);
+    }
+}
+
+
 bool
 Atom::has_missing_structure_pseudobond() const
 {
@@ -1112,7 +1120,7 @@ Atom::set_alt_loc(char alt_loc, bool create, bool _from_residue)
     if (alt_loc == _alt_loc || alt_loc == ' ')
         return;
     graphics_container()->set_gc_shape();
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_ALT_LOC);
+    track_change(ChangeTracker::REASON_ALT_LOC);
     if (create) {
         if (_alt_loc_map.find(alt_loc) != _alt_loc_map.end()) {
             set_alt_loc(alt_loc, create=false);
@@ -1135,7 +1143,7 @@ Atom::set_alt_loc(char alt_loc, bool create, bool _from_residue)
         _Alt_loc_info &info = (*i).second;
         _serial_number = info.serial_number;
         _alt_loc = alt_loc;
-        structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_COORD);
+        track_change(ChangeTracker::REASON_COORD);
     } else {
         residue()->set_alt_loc(alt_loc);
     }
@@ -1160,7 +1168,7 @@ Atom::set_aniso_u(float u11, float u12, float u13, float u22, float u23, float u
     (*a)[3] = u22;
     (*a)[4] = u23;
     (*a)[5] = u33;
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_ANISO_U);
+    track_change(ChangeTracker::REASON_ANISO_U);
 }
 
 void
@@ -1171,7 +1179,7 @@ Atom::set_bfactor(float bfactor)
         (*i).second.bfactor = bfactor;
     } else
         structure()->active_coord_set()->set_bfactor(this, bfactor);
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_BFACTOR);
+    track_change(ChangeTracker::REASON_BFACTOR);
 }
 
 void
@@ -1180,14 +1188,16 @@ Atom::set_color(const Rgba& rgba)
     if (rgba == _rgba)
         return;
     graphics_container()->set_gc_color();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_COLOR);
+    track_change(ChangeTracker::REASON_COLOR);
+    bonds_track_change(ChangeTracker::REASON_COLOR);
     _rgba = rgba;
 }
 
 void
 Atom::set_coord(const Coord& coord, CoordSet* cs)
-{
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_COORD);
+{   
+    track_change(ChangeTracker::REASON_COORD);
+    bonds_track_change(ChangeTracker::REASON_COORD);
     if (cs == nullptr) {
         cs = structure()->active_coord_set();
         if (cs == nullptr) {
@@ -1214,7 +1224,9 @@ Atom::set_display(bool d)
     if (d == _display)
         return;
     graphics_container()->set_gc_shape();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_DISPLAY);
+    structure()->set_visible_atoms_changed(true);
+    structure()->set_visible_bonds_changed(true);
+    track_change(ChangeTracker::REASON_DISPLAY);
     _display = d;
 }
 
@@ -1223,8 +1235,10 @@ Atom::set_draw_mode(DrawMode dm)
 {
     if (dm == _draw_mode)
         return;
+    if ( _draw_mode == DrawMode::Sphere || dm == DrawMode::Sphere )
+        structure()->set_visible_bonds_changed(true);
     graphics_container()->set_gc_shape();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_DRAW_MODE);
+    track_change(ChangeTracker::REASON_DRAW_MODE);
     _draw_mode = dm;
 }
 
@@ -1234,14 +1248,16 @@ Atom::set_hide(int h)
     if (h == _hide)
         return;
     graphics_container()->set_gc_shape();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_HIDE);
+    structure()->set_visible_atoms_changed(true);
+    structure()->set_visible_bonds_changed(true);
+    track_change(ChangeTracker::REASON_HIDE);
     _hide = h;
 }
 
 void
 Atom::set_occupancy(float occupancy)
 {
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_OCCUPANCY);
+    track_change(ChangeTracker::REASON_OCCUPANCY);
     if (_alt_loc != ' ') {
         _Alt_loc_map::iterator i = _alt_loc_map.find(_alt_loc);
         (*i).second.occupancy = occupancy;
@@ -1259,7 +1275,7 @@ Atom::set_radius(float r)
         return;
 
     graphics_container()->set_gc_shape();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_RADIUS);
+    track_change(ChangeTracker::REASON_RADIUS);
     _radius = r;
 }
 
@@ -1269,7 +1285,8 @@ Atom::set_selected(bool s)
     if (s == _selected)
         return;
     graphics_container()->set_gc_select();
-    change_tracker()->add_modified(this, ChangeTracker::REASON_SELECTED);
+    track_change(ChangeTracker::REASON_SELECTED);
+    bonds_track_change(ChangeTracker::REASON_SELECTED);
     _selected = s;
 }
 
@@ -1281,7 +1298,7 @@ Atom::set_serial_number(int sn)
         _Alt_loc_map::iterator i = _alt_loc_map.find(_alt_loc);
         (*i).second.serial_number = sn;
     }
-    structure()->change_tracker()->add_modified(this, ChangeTracker::REASON_SERIAL_NUMBER);
+    track_change(ChangeTracker::REASON_SERIAL_NUMBER);
 }
 
 std::string
diff --git a/src/core/atomic/atomstruct_cpp/Atom.h b/src/core/atomic/atomstruct_cpp/Atom.h
index eb8c7ed..e9542da 100644
--- a/src/core/atomic/atomstruct_cpp/Atom.h
+++ b/src/core/atomic/atomstruct_cpp/Atom.h
@@ -109,6 +109,7 @@ private:
     typedef std::map<unsigned char, _Alt_loc_info>  _Alt_loc_map;
     _Alt_loc_map  _alt_loc_map;
     std::vector<float>*  _aniso_u;
+    bool _changed = false; 
     Bonds  _bonds; // _bonds/_neighbors in same order
     mutable AtomType  _computed_idatm_type;
     unsigned int  _coord_index;
@@ -128,7 +129,7 @@ private:
     mutable Rings  _rings;
     bool  _selected = false;
     int  _serial_number;
-    void  _set_structure_category(Atom::StructCat sc) const;
+    void  _set_structure_category(Atom::StructCat sc);
     Structure*  _structure;
     mutable StructCat  _structure_category;
 public:
@@ -137,6 +138,8 @@ public:
     void  _switch_initial_element(const Element& e) { _element = &e; }
 
 public:
+    bool changed() const { return _changed; }
+    void set_changed(bool flag) { _changed = flag; }
     void  add_bond(Bond *b);
     char  alt_loc() const { return _alt_loc; }
     std::set<char>  alt_locs() const;
@@ -208,6 +211,13 @@ public:
 
     // change tracking
     ChangeTracker*  change_tracker() const;
+    void track_change(const std::string& reason) {
+        change_tracker()->add_modified(this, reason);
+        this->set_changed(true);
+    }
+    
+    void bonds_track_change(const std::string& reason); 
+    
 
     // graphics related
     const Rgba&  color() const { return _rgba; }
@@ -243,18 +253,20 @@ Atom::idatm_type() const {
 }
 
 inline void
-Atom::_set_structure_category(Atom::StructCat sc) const
+Atom::_set_structure_category(Atom::StructCat sc)
 {
     if (sc == _structure_category)
         return;
-    change_tracker()->add_modified(const_cast<Atom*>(this),
-        ChangeTracker::REASON_STRUCTURE_CATEGORY);
+    track_change(ChangeTracker::REASON_STRUCTURE_CATEGORY);
+    //change_tracker()->add_modified(const_cast<Atom*>(this),
+    //    ChangeTracker::REASON_STRUCTURE_CATEGORY);
     _structure_category = sc;
 }
 
 inline void
 Atom::set_computed_idatm_type(const char* it) {
     if (!idatm_is_explicit() && _computed_idatm_type != it) {
+        track_change(ChangeTracker::REASON_IDATM_TYPE);
         change_tracker()->add_modified(this, ChangeTracker::REASON_IDATM_TYPE);
     }
     _computed_idatm_type =  it;
@@ -267,7 +279,8 @@ Atom::set_idatm_type(const char* it) {
     if (!(_explicit_idatm_type.empty() && _computed_idatm_type == it)
     && !(*it == '\0' && _explicit_idatm_type == _computed_idatm_type)
     && !(!_explicit_idatm_type.empty() && it == _explicit_idatm_type)) {
-        change_tracker()->add_modified(this, ChangeTracker::REASON_IDATM_TYPE);
+        track_change(ChangeTracker::REASON_IDATM_TYPE);
+        //change_tracker()->add_modified(this, ChangeTracker::REASON_IDATM_TYPE);
     }
     _explicit_idatm_type = it;
 }
@@ -276,8 +289,10 @@ inline void
 Atom::set_name(const AtomName& name) {
     if (name == _name)
         return;
-    change_tracker()->add_modified(this, ChangeTracker::REASON_NAME);
+    track_change(ChangeTracker::REASON_NAME);
+    //change_tracker()->add_modified(this, ChangeTracker::REASON_NAME);
     _name = name;
+    _changed = true;
 }
 
 inline Atom::StructCat
diff --git a/src/core/atomic/atomstruct_cpp/Bond.cpp b/src/core/atomic/atomstruct_cpp/Bond.cpp
index 268e3f8..dd23972 100644
--- a/src/core/atomic/atomstruct_cpp/Bond.cpp
+++ b/src/core/atomic/atomstruct_cpp/Bond.cpp
@@ -174,4 +174,20 @@ Bond::polymeric_start_atom() const
     return psa;
 }
 
+void
+Bond::set_hide(int h) {
+    if (h == _hide)
+        return;
+    structure()->set_visible_bonds_changed(true);
+    Connection::set_hide(h);
+}
+
+void
+Bond::set_display(bool d) {
+    if (d == _display)
+        return;
+    structure()->set_visible_bonds_changed(true);
+    Connection::set_display(d);
+}
+
 }  // namespace atomstruct
diff --git a/src/core/atomic/atomstruct_cpp/Bond.h b/src/core/atomic/atomstruct_cpp/Bond.h
index 43b2fcd..b7db12c 100644
--- a/src/core/atomic/atomstruct_cpp/Bond.h
+++ b/src/core/atomic/atomstruct_cpp/Bond.h
@@ -46,11 +46,14 @@ private:
     const char*  err_msg_loop() const
         { return "Can't bond an atom to itself"; }
     mutable Rings  _rings;
-
+    bool _changed = false;
+    
     static int  session_base_version(int /*version*/) { return 1; }
     static int  SESSION_NUM_INTS(int /*version*/=CURRENT_SESSION_VERSION) { return 0; }
     static int  SESSION_NUM_FLOATS(int /*version*/=CURRENT_SESSION_VERSION) { return 0; }
 public:
+    void set_hide(int h);
+    void set_display(bool d);
     virtual ~Bond() {}
     virtual bool shown() const;
     const Rings&  all_rings(bool cross_residues = false, int size_threshold = 0,
@@ -62,6 +65,8 @@ public:
         return rings(cross_residues, 0, ignore);
     }
     static bool  polymer_bond_atoms(Atom* first, Atom* second);
+    bool changed() const { return _changed; }
+    void set_changed(bool flag) { _changed = flag; }
     Atom*  polymeric_start_atom() const;
     const Rings&  rings(bool cross_residues = false, int all_size_threshold = 0,
         std::set<const Residue*>* ignore = nullptr) const;
@@ -80,8 +85,9 @@ public:
 
     // change tracking
     ChangeTracker*  change_tracker() const;
-    void track_change(const std::string& reason) const {
+    void track_change(const std::string& reason) {
         change_tracker()->add_modified(this, reason);
+        this->set_changed(true);
     }
 
     // graphics related
diff --git a/src/core/atomic/atomstruct_cpp/Connection.h b/src/core/atomic/atomstruct_cpp/Connection.h
index d0ba00e..443d9cc 100644
--- a/src/core/atomic/atomstruct_cpp/Connection.h
+++ b/src/core/atomic/atomstruct_cpp/Connection.h
@@ -106,7 +106,7 @@ public:
     }
 
     // change tracking
-    virtual void  track_change(const std::string& reason) const = 0;
+    virtual void  track_change(const std::string& reason) = 0;
 
     // graphics related
     const Rgba&  color() const { return _rgba; }
diff --git a/src/core/atomic/atomstruct_cpp/CoordSet.cpp b/src/core/atomic/atomstruct_cpp/CoordSet.cpp
index 69eac6b..94399c5 100644
--- a/src/core/atomic/atomstruct_cpp/CoordSet.cpp
+++ b/src/core/atomic/atomstruct_cpp/CoordSet.cpp
@@ -130,13 +130,15 @@ CoordSet::session_save(int** ints, float** floats) const
 void
 CoordSet::set_bfactor(const Atom *a, float val)
 {
-    _bfactor_map.insert(std::pair<const Atom *, float>(a, val));
+    //_bfactor_map.insert(std::pair<const Atom *, float>(a, val));
+    _bfactor_map[a] = val;
 }
 
 void
 CoordSet::set_occupancy(const Atom *a, float val)
 {
-    _occupancy_map.insert(std::pair<const Atom *, float>(a, val));
+    //_occupancy_map.insert_or_assign(std::pair<const Atom *, float>(a, val));
+    _occupancy_map[a] = val;
 }
 
 }  // namespace atomstruct
diff --git a/src/core/atomic/atomstruct_cpp/PBGroup.h b/src/core/atomic/atomstruct_cpp/PBGroup.h
index f8e0eb9..cb5dc9b 100644
--- a/src/core/atomic/atomstruct_cpp/PBGroup.h
+++ b/src/core/atomic/atomstruct_cpp/PBGroup.h
@@ -76,7 +76,10 @@ protected:
 
     void _check_destroyed_atoms(PBGroup::Pseudobonds& pbonds, const std::set<void*>& destroyed);
     void delete_pbs_check(const std::set<Pseudobond*>& pbs) const;
+    bool _changed = false;
 public:
+    bool changed() const { return _changed; }
+    void set_changed(bool flag) { _changed = flag; }
     virtual void  clear() = 0;
     virtual const Rgba&  color() const { return _color; }
     virtual const std::string&  category() const { return _category; }
diff --git a/src/core/atomic/atomstruct_cpp/Pseudobond.h b/src/core/atomic/atomstruct_cpp/Pseudobond.h
index 52cc7ae..de7d1d9 100644
--- a/src/core/atomic/atomstruct_cpp/Pseudobond.h
+++ b/src/core/atomic/atomstruct_cpp/Pseudobond.h
@@ -60,7 +60,10 @@ protected:
         { return "Can't form pseudobond to itself"; }
     const char*  err_msg_not_end() const
         { return "Atom given to other_end() not in pseudobond!"; }
+    bool _changed = false;
 public:
+    bool changed() const { return _changed; }
+    void set_changed(bool flag) { _changed = flag; }
     ChangeTracker*  change_tracker() const;
     GraphicsContainer*  graphics_container() const;
     PBGroup*  group() const { return _group; }
@@ -76,8 +79,9 @@ public:
     }
     void  session_restore(int version, int** ints, float** floats);
     void  session_save(int** ints, float** floats) const;
-    void  track_change(const std::string& reason) const {
+    void  track_change(const std::string& reason) {
         change_tracker()->add_modified(this, reason);
+        this->set_changed(true);
     }
 };
 
diff --git a/src/core/atomic/atomstruct_cpp/Residue.h b/src/core/atomic/atomstruct_cpp/Residue.h
index c1ec67e..a5fecaa 100644
--- a/src/core/atomic/atomstruct_cpp/Residue.h
+++ b/src/core/atomic/atomstruct_cpp/Residue.h
@@ -79,7 +79,10 @@ private:
     int  _ss_id;
     SSType _ss_type;
     Structure *  _structure;
+    bool _changed = false;
 public:
+    bool changed() const { return _changed; }
+    void set_changed(bool flag) { _changed = flag; }
     void  add_atom(Atom*);
     const Atoms&  atoms() const { return _atoms; }
     AtomsMap  atoms_map() const;
diff --git a/src/core/atomic/atomstruct_cpp/Structure.h b/src/core/atomic/atomstruct_cpp/Structure.h
index 60ce973..caa4da8 100644
--- a/src/core/atomic/atomstruct_cpp/Structure.h
+++ b/src/core/atomic/atomstruct_cpp/Structure.h
@@ -184,8 +184,17 @@ protected:
     static int  SESSION_NUM_MISC(int version=CURRENT_SESSION_VERSION) {
         return version > 7 ? 3 : 4;
     }
-
+    bool _visible_atoms_changed = false;
+    bool _visible_bonds_changed = false;
+    bool _changed = false;
 public:
+    bool changed() const { return _changed; }
+    void set_changed(const bool& flag) { _changed = flag; }
+    bool visible_atoms_changed() const { return _visible_atoms_changed; }
+    void set_visible_atoms_changed(const bool& flag) { _visible_atoms_changed = flag; }
+    bool visible_bonds_changed() const { return _visible_bonds_changed; }
+    void set_visible_bonds_changed(const bool& flag) { _visible_bonds_changed = flag; }
+    
     Structure(PyObject* logger = nullptr);
     virtual  ~Structure();
 
diff --git a/src/core/atomic/molarray.py b/src/core/atomic/molarray.py
index ac0a869..a149860 100644
--- a/src/core/atomic/molarray.py
+++ b/src/core/atomic/molarray.py
@@ -342,6 +342,8 @@ class Atoms(Collection):
         "Return list of 2-tuples of (structure, Atoms for that structure)."
         astruct = self.structures._pointers
         return [(us, self.filter(astruct==us._c_pointer.value)) for us in self.unique_structures]
+    
+    _changed = cvec_property('atom_changed', bool, doc='Changed since last redraw')
     chain_ids = cvec_property('atom_chain_id', string, read_only = True)
     colors = cvec_property('atom_color', uint8, 4,
         doc="Returns a :mod:`numpy` Nx4 array of uint8 RGBA values. Can be set "
@@ -611,6 +613,15 @@ class Bonds(Collection):
     def __init__(self, bond_pointers = None):
         Collection.__init__(self, bond_pointers, molobject.Bond, Bonds)
 
+
+    _changed = cvec_property('bond_changed', bool, doc='Changed since last redraw')
+    '''
+    True if the bond or the position(s) of its atoms have been changed 
+    since the last time they were drawn.
+    Returns a :mod:`numpy` array of bool. Can be set with such an array
+    (or equivalent sequence), or with a single value.
+    '''
+
     atoms = cvec_property('bond_atoms', cptr, 2, astype = _atoms_pair, read_only = True)
     '''
     Returns a two-tuple of :class:`Atoms` objects.
diff --git a/src/core/atomic/molc.cpp b/src/core/atomic/molc.cpp
index 4e7e24c..abb3782 100755
--- a/src/core/atomic/molc.cpp
+++ b/src/core/atomic/molc.cpp
@@ -248,6 +248,21 @@ inline bool normalize(float *v)
 // -------------------------------------------------------------------------
 // atom functions
 //
+
+
+extern "C" EXPORT void atom_changed(void *atoms, size_t n, npy_bool *flags)
+{
+    Atom **a = static_cast<Atom **>(atoms);
+    error_wrap_array_get(a, n, &Atom::changed, flags);
+}
+
+extern "C" EXPORT void set_atom_changed (void *atoms, size_t n, npy_bool *flags)
+{
+    Atom **a = static_cast<Atom **>(atoms);
+    error_wrap_array_set(a, n, &Atom::set_changed, flags);
+}
+    
+
 extern "C" EXPORT void atom_bfactor(void *atoms, size_t n, float32_t *bfactors)
 {
     Atom **a = static_cast<Atom **>(atoms);
@@ -1024,6 +1039,20 @@ extern "C" EXPORT PyObject *atom_intra_bonds(void *atoms, size_t n)
 // -------------------------------------------------------------------------
 // bond functions
 //
+
+extern "C" EXPORT void bond_changed(void *bonds, size_t n, npy_bool *flags)
+{
+    Bond **b = static_cast<Bond **>(bonds);
+    error_wrap_array_get(b, n, &Bond::changed, flags);
+}
+
+extern "C" EXPORT void set_bond_changed (void *bonds, size_t n, npy_bool *flags)
+{
+    Bond **b = static_cast<Bond **>(bonds);
+    error_wrap_array_set(b, n, &Bond::set_changed, flags);
+}
+
+
 extern "C" EXPORT void bond_atoms(void *bonds, size_t n, pyobject_t *atoms)
 {
     Bond **b = static_cast<Bond **>(bonds);
@@ -1224,6 +1253,21 @@ extern "C" EXPORT void *bond_other_atom(void *bond, void *atom)
 // -------------------------------------------------------------------------
 // pseudobond functions
 //
+
+extern "C" EXPORT void pseudobond_changed(void *pbonds, size_t n, npy_bool *flags)
+{
+    Pseudobond **b = static_cast<Pseudobond **>(pbonds);
+    error_wrap_array_get(b, n, &Pseudobond::changed, flags);
+}
+
+extern "C" EXPORT void set_pseudobond_changed (void *pbonds, size_t n, npy_bool *flags)
+{
+    Pseudobond **b = static_cast<Pseudobond **>(pbonds);
+    error_wrap_array_set(b, n, &Pseudobond::set_changed, flags);
+}
+
+
+
 extern "C" EXPORT void pseudobond_atoms(void *pbonds, size_t n, pyobject_t *atoms)
 {
     Pseudobond **b = static_cast<Pseudobond **>(pbonds);
@@ -1734,6 +1778,20 @@ extern "C" EXPORT void pseudobond_global_manager_session_save_teardown(void *man
 // -------------------------------------------------------------------------
 // residue functions
 //
+
+extern "C" EXPORT void residue_changed(void *residues, size_t n, npy_bool *flags)
+{
+    Residue **r = static_cast<Residue **>(residues);
+    error_wrap_array_get(r, n, &Residue::changed, flags);
+}
+
+extern "C" EXPORT void set_residue_changed (void *residues, size_t n, npy_bool *flags)
+{
+    Residue **r = static_cast<Residue **>(residues);
+    error_wrap_array_set(r, n, &Residue::set_changed, flags);
+}
+
+
 extern "C" EXPORT void residue_atoms(void *residues, size_t n, pyobject_t *atoms)
 {
     Residue **r = static_cast<Residue **>(residues);
@@ -3211,6 +3269,8 @@ extern "C" EXPORT int sequence_ungapped_to_gapped(void *seq, int32_t index)
 // -------------------------------------------------------------------------
 // structure functions
 //
+
+
 extern "C" EXPORT void set_structure_color(void *mol, uint8_t *rgba)
 {
     Structure *m = static_cast<Structure *>(mol);
@@ -3303,6 +3363,47 @@ extern "C" EXPORT void structure_atoms(void *mols, size_t n, pyobject_t *atoms)
     }
 }
 
+extern "C" EXPORT void structure_visible_atoms_changed(void *mols, size_t n, npy_bool *changed)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            *changed++ = m[i]->visible_atoms_changed();
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+extern "C" EXPORT void set_structure_visible_atoms_changed(void *mols, size_t n, npy_bool *changed)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            m[i]->set_visible_atoms_changed(*changed++);
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+extern "C" EXPORT void structure_visible_atoms(void *mols, size_t n, pyobject_t *atoms)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            const Structure::Atoms &a = m[i]->atoms();
+            for (size_t j = 0; j != a.size(); ++j) {
+                if ( a[j]->visible() ) 
+                    *atoms++ = a[j];
+            }
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+
 extern "C" EXPORT void structure_ball_scale(void *mols, size_t n, float32_t *bscales)
 {
     Structure **m = static_cast<Structure **>(mols);
@@ -3334,6 +3435,65 @@ extern "C" EXPORT void structure_bonds(void *mols, size_t n, pyobject_t *bonds)
     }
 }
 
+extern "C" EXPORT void structure_num_bonds_visible(void *mols, size_t n, size_t *nbonds)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i)
+          {
+            const Structure::Bonds &bonds = m[i]->bonds();
+            int c = 0;
+            for (auto b: bonds)
+              if (b->shown())
+                c++;
+            nbonds[i] = c;
+          }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+extern "C" EXPORT void structure_visible_bonds(void *mols, size_t n, pyobject_t *bonds)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            const Structure::Bonds &b = m[i]->bonds();
+            for (size_t j = 0; j != b.size(); ++j) {
+                if ( b[j]->shown() ) 
+                    *bonds++ = b[j];
+            }
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+extern "C" EXPORT void structure_visible_bonds_changed(void *mols, size_t n, npy_bool *changed)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            *changed++ = m[i]->visible_bonds_changed();
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+extern "C" EXPORT void set_structure_visible_bonds_changed(void *mols, size_t n, npy_bool *changed)
+{
+    Structure **m = static_cast<Structure **>(mols);
+    try {
+        for (size_t i = 0; i != n; ++i) {
+            m[i]->set_visible_bonds_changed(*changed++);
+        }
+    } catch (...) {
+        molc_error();
+    }
+}
+
+
 extern "C" EXPORT void structure_num_residues(void *mols, size_t n, size_t *nres)
 {
     Structure **m = static_cast<Structure **>(mols);
diff --git a/src/core/atomic/molobject.py b/src/core/atomic/molobject.py
index a4937f2..9413f68 100644
--- a/src/core/atomic/molobject.py
+++ b/src/core/atomic/molobject.py
@@ -125,7 +125,8 @@ class Atom(State):
 
     def atomspec(self):
         return self.residue.atomspec() + '@' + self.name
-
+    
+    _changed = c_property('atom_changed', bool, doc='Changed since last redraw')
     alt_loc = c_property('atom_alt_loc', byte, doc='Alternate location indicator')
     bfactor = c_property('atom_bfactor', float32, doc = "B-factor, floating point value.")
     bonds = c_property('atom_bonds', cptr, "num_bonds", astype=_bonds, read_only=True,
@@ -378,6 +379,9 @@ class Bond(State):
     def atomspec(self):
         return a1.atomspec() + a2.atomspec()
 
+
+    _changed = c_property('bond_changed', bool, doc='Changed since last redraw')
+
     atoms = c_property('bond_atoms', cptr, 2, astype = _atom_pair, read_only = True)
     '''Two-tuple of :py:class:`Atom` objects that are the bond end points.'''
     color = c_property('bond_color', uint8, 4)
@@ -1463,10 +1467,20 @@ class StructureData:
     '''Index of the active coordinate set.'''
     atoms = c_property('structure_atoms', cptr, 'num_atoms', astype = _atoms, read_only = True)
     ''':class:`.Atoms` collection containing all atoms of the structure.'''
+    _visible_atoms = c_property('structure_visible_atoms', cptr, 'num_atoms_visible', astype = _atoms, 
+                          read_only = True)
+    ''':class:`.Atoms` containing only the visible atoms of the structure. Read only.'''
+    _visible_atoms_changed = c_property('structure_visible_atoms_changed', bool)
+    '''A boolean flag indicating whether any atom has changed its visibility.'''
     ball_scale = c_property('structure_ball_scale', float32,
         doc = "Scales sphere radius in ball-and-stick style.")
     bonds = c_property('structure_bonds', cptr, 'num_bonds', astype = _bonds, read_only = True)
     ''':class:`.Bonds` collection containing all bonds of the structure.'''
+    _visible_bonds = c_property('structure_visible_bonds', cptr, 'num_bonds_visible', astype = _bonds, 
+                          read_only = True)
+    ''':class:`.Bonds` containing only the visible bonds of the structure. Read only.'''
+    _visible_bonds_changed = c_property('structure_visible_bonds_changed', bool)
+    '''A boolean flag indicating whether any atom has changed its visibility.'''
     chains = c_property('structure_chains', cptr, 'num_chains', astype = _chains, read_only = True)
     ''':class:`.Chains` collection containing all chains of the structure.'''
     coordset_ids = c_property('structure_coordset_ids', int32, 'num_coordsets', read_only = True)
@@ -1477,10 +1491,12 @@ class StructureData:
     '''Structure has lower case chain ids. Boolean'''
     num_atoms = c_property('structure_num_atoms', size_t, read_only = True)
     '''Number of atoms in structure. Read only.'''
-    num_atoms_visible = c_property('structure_num_atoms_visible', size_t, read_only = True)
+    _num_atoms_visible = c_property('structure_num_atoms_visible', size_t, read_only = True)
     '''Number of visible atoms in structure. Read only.'''
     num_bonds = c_property('structure_num_bonds', size_t, read_only = True)
     '''Number of bonds in structure. Read only.'''
+    _num_bonds_visible = c_property('structure_num_bonds_visible', size_t, read_only = True)
+    '''Number of visible atoms in structure. Read only.'''
     num_coordsets = c_property('structure_num_coordsets', size_t, read_only = True)
     '''Number of coordinate sets in structure. Read only.'''
     num_chains = c_property('structure_num_chains', size_t, read_only = True)
diff --git a/src/core/atomic/structure.py b/src/core/atomic/structure.py
index fb07010..fd555da 100644
--- a/src/core/atomic/structure.py
+++ b/src/core/atomic/structure.py
@@ -54,7 +54,12 @@ class Structure(Model, StructureData):
         self._ribbon_t2r = {}         # ribbon triangles-to-residue map
         self._ribbon_r2t = {}         # ribbon residue-to-triangles map
         self._ribbon_tether = []      # ribbon tethers from ribbon to floating atoms
-
+        
+        self._cached_num_atoms_visible = self._num_atoms_visible
+        self._cached_visible_atoms = self._visible_atoms
+        self._cached_num_bonds_visible = self._num_bonds_visible
+        self._cached_visible_bonds = self._visible_bonds   
+             
         from . import molobject
         molobject.add_to_object_map(self)
 
@@ -64,7 +69,7 @@ class Structure(Model, StructureData):
                 ("save_teardown", "end save session")]:
             self._ses_handlers.append(t.add_handler(trig_name,
                     lambda *args, qual=ses_func: self._ses_call(qual)))
-
+        
         self._make_drawing()
 
     def __str__(self):
@@ -257,11 +262,11 @@ class Structure(Model, StructureData):
         from ..colors import most_common_color
         if ribbon_displays.any():
             return most_common_color(residues.filter(ribbon_displays).ribbon_colors)
-        atoms = self.atoms
-        shown = atoms.filter(atoms.displays)
+        shown = self.visible_atoms
+        #shown = atoms.filter(atoms.displays)
         if shown:
             return most_common_color(shown.colors)
-        return most_common_color(atoms.colors)
+        return most_common_color(self.atoms.colors)
     def _set_single_color(self, color):
         self.atoms.colors = color
         self.residues.ribbon_colors = color
@@ -269,26 +274,52 @@ class Structure(Model, StructureData):
 
     def _make_drawing(self):
         # Create graphics
-        self._update_atom_graphics()
-        self._update_bond_graphics()
-        for pbg in self.pbg_map.values():
-            pbg._update_graphics()
-        self._create_ribbon_graphics()
+        self._update_graphics_if_needed(force_recalc = True)
+        #~ self._update_atom_graphics()
+        #~ self._update_bond_graphics()
+        #~ for pbg in self.pbg_map.values():
+            #~ pbg._update_graphics()
+        #~ self._create_ribbon_graphics()
 
     @property
     def _level_of_detail(self):
         gu = structure_graphics_updater(self.session)
         return gu.level_of_detail
+    
+    @property
+    def num_atoms_visible(self):
+        if self._visible_atoms_changed:
+            self._cached_num_atoms_visible = self._num_atoms_visible
+        return self._cached_num_atoms_visible
+    
+    @property
+    def visible_atoms(self):
+        if self._visible_atoms_changed:
+            self._cached_visible_atoms = self._visible_atoms
+        return self._cached_visible_atoms
+    
+    @property
+    def num_bonds_visible(self):
+        if self._visible_bonds_changed:
+            self._cached_num_bonds_visible = self._num_bonds_visible
+        return self._cached_num_bonds_visible
 
+    @property
+    def visible_bonds(self):
+        if self._visible_bonds_changed:
+            self._cached_visible_bonds = self._visible_bonds
+        return self._cached_visible_bonds
+        
     def new_atoms(self):
         # TODO: Handle instead with a C++ notification that atoms added or deleted
         self._atom_bounds_needs_update = True
 
-    def _update_graphics_if_needed(self, *_):
+    def _update_graphics_if_needed(self, *_, force_recalc = False):
         gc = self._graphics_changed
-        if gc == 0:
+        if gc == 0 and not force_recalc:
             return
-
+        
+        
         if gc & self._RIBBON_CHANGE:
             self._create_ribbon_graphics()
             # Displaying ribbon can set backbone atom hide bits producing shape change.
@@ -299,7 +330,7 @@ class Structure(Model, StructureData):
         s = (gc & self._SHAPE_CHANGE)
         if gc & (self._COLOR_CHANGE | self._RIBBON_CHANGE) or s:
             self._update_ribbon_tethers()
-        self._update_graphics(gc)
+        self._update_graphics(gc, force_recalc = force_recalc)
         self.redraw_needed(shape_changed = s,
                            selection_changed = (gc & self._SELECT_CHANGE))
         if s:
@@ -313,49 +344,107 @@ class Structure(Model, StructureData):
                 if isinstance(surf, MolecularSurface):
                     surf.update_selection()
 
-    def _update_graphics(self, changes = StructureData._ALL_CHANGE):
-        self._update_atom_graphics(changes)
-        self._update_bond_graphics(changes)
+    def _update_graphics(self, changes = StructureData._ALL_CHANGE, force_recalc = False):
+        import numpy
+        atoms = self.visible_atoms
+        bonds = self.visible_bonds
+        force_atom_recalc = False
+        force_bond_recalc = False
+        
+        if force_recalc:
+            force_atom_recalc = True
+            force_bond_recalc = True
+        
+        else:
+            if self._visible_atoms_changed:
+                force_atom_recalc = True
+                self._visible_atoms_changed = False
+            
+            if self._visible_bonds_changed:
+                force_bond_recalc = True
+                self._visible_bonds_changed = False
+        
+        
+        if force_atom_recalc:
+            achanged = numpy.array([True]*len(atoms))
+            ch_atoms = atoms
+        else:
+            achanged = atoms._changed
+            ch_atoms = atoms[achanged]
+        
+        if force_bond_recalc:
+            bchanged = numpy.array([True]*len(bonds))
+            ch_bonds = bonds
+        else:
+            bchanged = bonds._changed
+            ch_bonds = bonds[bchanged]
+            
+        
+        half_bond_change_filter = numpy.concatenate((bchanged,bchanged))
+        
+        self._update_atom_graphics( ch_atoms, achanged, changes = changes, force_recalc = force_atom_recalc)
+        self._update_bond_graphics(ch_bonds, half_bond_change_filter, changes = changes, force_recalc = force_bond_recalc)
         for pbg in self.pbg_map.values():
             pbg._update_graphics(changes)
         self._update_ribbon_graphics()
 
-    def _update_atom_graphics(self, changes = StructureData._ALL_CHANGE):
-        atoms = self.atoms  # optimzation, avoid making new numpy array from C++
-        avis = atoms.visibles
+    def _update_atom_graphics(self, atoms, ch_filter, changes = StructureData._ALL_CHANGE, force_recalc = False):
+        import numpy
         p = self._atoms_drawing
+        from numpy import empty, float32, multiply
+        #ch_indices = numpy.where(ch_filter)[0]
+        
         if p is None:
-            if avis.sum() == 0:
-                return
+            #avis = atoms.visibles
+            #if avis.sum() == 0:
+            #    return
             self._atoms_drawing = p = self.new_drawing('atoms')
             self._atoms_drawing.custom_x3d = self._custom_atom_x3d
             # Update level of detail of spheres
             self._level_of_detail.set_atom_sphere_geometry(p)
+    
+        if force_recalc:
+            n = len(atoms)
+            xyzr = empty((n, 4), float32)
+            xyzr[:, :3] = atoms.coords
+            xyzr[:, 3] = self._atom_display_radii(atoms)
 
+            from ..geometry import Places
+            p.colors = atoms.colors
+            p.positions = Places(shift_and_scale=xyzr)
+            p.display_positions = numpy.array([True]*len(atoms))
+            p.selected_positions = atoms.selected # if atoms.num_selected > 0 else None
+            atoms._changed = False
+            return
+        
+        
         if changes & self._SHAPE_CHANGE:
             # Set instanced sphere center position and radius
             n = len(atoms)
-            from numpy import empty, float32, multiply
             xyzr = empty((n, 4), float32)
             xyzr[:, :3] = atoms.coords
             xyzr[:, 3] = self._atom_display_radii(atoms)
-
+            
             from ..geometry import Places
-            p.positions = Places(shift_and_scale=xyzr)
-            p.display_positions = avis
+            p.positions.set_shift_and_scale(ch_filter, xyzr)
+            #p.display_positions = avis
+            
 
         if changes & (self._COLOR_CHANGE | self._SHAPE_CHANGE):
             # Set atom colors
-            p.colors = atoms.colors
+            p.set_colors(atoms.colors, ch_filter)
 
         if changes & (self._SELECT_CHANGE | self._SHAPE_CHANGE):
             # Set selected
-            p.selected_positions = atoms.selected if atoms.num_selected > 0 else None
+            pmask = ch_filter*False
+            pmask[ch_filter] = atoms.selected 
+            p.selected_positions = pmask # atoms.selected if atoms.num_selected > 0 else None
+        atoms._changed = False
 
     def _custom_atom_x3d(self, stream, x3d_scene, indent, place):
         from numpy import empty, float32
         p = self._atoms_drawing
-        atoms = self.atoms
+        atoms = self.visible_atoms
         radii = self._atom_display_radii(atoms)
         tab = ' ' * indent
         for v, xyz, r, c in zip(p.display_positions, atoms.coords, radii, p.colors):
@@ -371,32 +460,47 @@ class Structure(Model, StructureData):
     def _atom_display_radii(self, atoms):
         return atoms.display_radii(self.ball_scale, self.bond_radius)
     
-    def _update_bond_graphics(self, changes = StructureData._ALL_CHANGE):
-        bonds = self.bonds  # optimzation, avoid making new numpy array from C++
+    def _update_bond_graphics(self, bonds, ch_filter, changes = StructureData._ALL_CHANGE, force_recalc = False):
+        import numpy
+        #ch_indices = numpy.where(ch_filter)[0]
         p = self._bonds_drawing
         if p is None:
-            if bonds.num_shown == 0:
-                return
+            #if bonds.num_shown == 0:
+            #    return
             self._bonds_drawing = p = self.new_drawing('bonds')
             self._bonds_drawing.custom_x3d = self._custom_bond_x3d
             # Update level of detail of cylinders
             self._level_of_detail.set_bond_cylinder_geometry(p)
-
-        if changes & (self._SHAPE_CHANGE | self._SELECT_CHANGE):
+        
+        if force_recalc:
             bond_atoms = bonds.atoms
-        if changes & self._SHAPE_CHANGE:
             ba1, ba2 = bond_atoms
+            p.colors = bonds.half_colors
             p.positions = _halfbond_cylinder_placements(ba1.coords, ba2.coords, bonds.radii)
             p.display_positions = _shown_bond_cylinders(bonds)
+            p.selected_positions = _selected_bond_cylinders(bond_atoms)
+            bonds._changed = False
+            return
+        
+        if changes & (self._SHAPE_CHANGE | self._SELECT_CHANGE):
+            bond_atoms = bonds.atoms
+        if changes & self._SHAPE_CHANGE:
+            ba1, ba2 = bond_atoms
+            p.positions.set_opengl_matrices(ch_filter, 
+                _halfbond_cylinder_placements(ba1.coords, ba2.coords, bonds.radii)._opengl_array)
+            #p.display_positions = ch_filter #_shown_bond_cylinders(bonds)
         if changes & (self._COLOR_CHANGE | self._SHAPE_CHANGE):
-            p.colors = c = bonds.half_colors
+            p.set_colors(bonds.half_colors, ch_filter)
         if changes & (self._SELECT_CHANGE | self._SHAPE_CHANGE):
-            p.selected_positions = _selected_bond_cylinders(bond_atoms)
+            pmask = ch_filter*False
+            pmask[ch_filter] = _selected_bond_cylinders(bond_atoms)
+            p.selected_positions = pmask
+        bonds._changed = False
 
     def _custom_bond_x3d(self, stream, x3d_scene, indent, place):
         from numpy import empty, float32
         p = self._bonds_drawing
-        bonds = self.bonds
+        bonds = self.visible_bonds
         ba1, ba2 = bonds.atoms
         cyl_info = _halfbond_cylinder_x3d(ba1.coords, ba2.coords, bonds.radii)
         tab = ' ' * indent
@@ -1376,7 +1480,7 @@ class Structure(Model, StructureData):
     def _update_ribbon_graphics(self):
         # Set selected ribbons in graphics
         from .molarray import Residues
-        atoms = self.atoms
+        atoms = self.visible_atoms
         if atoms.num_selected > 0:
             residues = atoms.filter(atoms.selected).unique_residues
             from numpy import array
@@ -1450,10 +1554,9 @@ class Structure(Model, StructureData):
     def _atom_bounds(self):
         if not self._atom_bounds_needs_update:
             return self._cached_atom_bounds
-        a = self.atoms
-        adisp = a[a.displays]
-        xyz = adisp.coords
-        radii = adisp.radii
+        a = self.visible_atoms
+        xyz = a.coords
+        radii = a.radii
         # TODO: Currently 40% of time is taken in getting atom radii because
         #       they are recomputed from element and bonds every time. Ticket #789.
         #       If that was fixed by using a precomputed radius, then it would make
