diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index d4e76255bbccc94bb56e78155ddfd51fad43b24a..6eaa79bb0443d51334860fc4870cb43b359e0053 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int'
 import { UniqueArray } from 'mol-data/generic'
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { Model, ElementIndex } from '../model'
-import { sort, arraySwap, hash1, sortArray, hashString } from 'mol-data/util';
+import { sort, arraySwap, hash1, sortArray, hashString, hashFnv32a } from 'mol-data/util';
 import StructureElement from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
@@ -44,9 +44,11 @@ class Structure {
         entityIndices?: ReadonlyArray<EntityIndex>,
         uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
         hashCode: number,
+        /** Hash based on all unit.id values in the structure, reflecting the units transformation */
+        transformHash: number,
         elementCount: number,
         polymerResidueCount: number,
-    } = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 };
+    } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 };
 
     subsetBuilder(isSorted: boolean) {
         return new StructureSubsetBuilder(this, isSorted);
@@ -74,6 +76,12 @@ class Structure {
         return this.computeHash();
     }
 
+    get transformHash() {
+        if (this._props.transformHash !== -1) return this._props.transformHash;
+        this._props.transformHash = hashFnv32a(this.units.map(u => u.id))
+        return this._props.transformHash;
+    }
+
     private computeHash() {
         let hash = 23;
         for (let i = 0, _i = this.units.length; i < _i; i++) {
@@ -389,6 +397,7 @@ namespace Structure {
         return s.hashCode;
     }
 
+    /** Hash based on all unit.model conformation values in the structure */
     export function conformationHash(s: Structure) {
         return hashString(s.units.map(u => Unit.conformationId(u)).join('|'))
     }
@@ -409,6 +418,13 @@ namespace Structure {
         return true;
     }
 
+    export function areEquivalent(a: Structure, b: Structure) {
+        return a === b || (
+            a.hashCode === b.hashCode &&
+            StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups)
+        )
+    }
+
     export class ElementLocationIterator implements Iterator<StructureElement> {
         private current = StructureElement.create();
         private unitIndex = 0;
diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts
index 1b35f8341f75d86dccf08e88b08cc8a475e3506f..5af14696a9e1b5017299ace7cbe5643956a2bc1f 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -78,6 +78,15 @@ namespace StructureSymmetry {
 
         return ret;
     }
+
+    /** Checks if transform groups are equal up to their unit's transformations */
+    export function areTransformGroupsEquivalent(a: ReadonlyArray<Unit.SymmetryGroup>, b: ReadonlyArray<Unit.SymmetryGroup>) {
+        if (a.length !== b.length) return false
+        for (let i = 0, il = a.length; i < il; ++i) {
+            if (a[i].hashCode !== b[i].hashCode) return false
+        }
+        return true
+    }
 }
 
 function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index fd41f3db12facc596554f41aacc7d8af0d36c489..a1cd1097eb667b89cd95b9597c9ef2338ef0d07c 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings';
 import StructureElement from './element'
 import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing';
 import { IntMap, SortedArray } from 'mol-data/int';
-import { hash2 } from 'mol-data/util';
+import { hash2, hashFnv32a } from 'mol-data/util';
 import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer';
 import { getNucleotideElements } from './util/nucleotide';
 import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density';
@@ -48,7 +48,10 @@ namespace Unit {
         readonly units: ReadonlyArray<Unit>
         /** Maps unit.id to index of unit in units array */
         readonly unitIndexMap: IntMap<number>
+        /** Hash based on unit.invariantId which is the same for all units in the group */
         readonly hashCode: number
+        /** Hash based on all unit.id values in the group, reflecting the units transformation*/
+        readonly transformHash: number
     }
 
     function getUnitIndexMap(units: Unit[]) {
@@ -72,7 +75,8 @@ namespace Unit {
                 props.unitIndexMap = getUnitIndexMap(units)
                 return props.unitIndexMap
             },
-            hashCode: hashUnit(units[0])
+            hashCode: hashUnit(units[0]),
+            transformHash: hashFnv32a(units.map(u => u.id))
         }
     }
 
diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts
index 1574648d233413ae5aa69cf208025d085c6b160d..a5adc76f991b2b652ac9f9d290adc256ae67fb56 100644
--- a/src/mol-repr/structure/complex-visual.ts
+++ b/src/mol-repr/structure/complex-visual.ts
@@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
         VisualUpdateState.reset(updateState)
         setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
         const newConformationHash = Structure.conformationHash(currentStructure)
         if (newConformationHash !== conformationHash) {
             conformationHash = newConformationHash
             updateState.createGeometry = true
         }
 
-        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
-        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
-
         //
 
         if (updateState.createGeometry) {
@@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
                 throw new Error('missing structure')
             } else if (structure && (!currentStructure || !renderObject)) {
                 await create(ctx, structure, theme, props)
-            } else if (structure && structure.hashCode !== currentStructure.hashCode) {
+            } else if (structure && !Structure.areEquivalent(structure, currentStructure)) {
                 await create(ctx, structure, theme, props)
             } else {
                 if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) {
diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts
index abd56451a196e91b1ada57ad8a2319b1129224f5..ac47dae9dc1f26b3288a17b6332c7b94895af485 100644
--- a/src/mol-repr/structure/units-representation.ts
+++ b/src/mol-repr/structure/units-representation.ts
@@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
             if (!_structure && !structure) {
                 throw new Error('missing structure')
             } else if (structure && !_structure) {
-                // console.log('initial structure')
+                // console.log(label, 'initial structure')
                 // First call with a structure, create visuals for each group.
                 _groups = structure.unitSymmetryGroups;
                 for (let i = 0; i < _groups.length; i++) {
@@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                     visuals.set(group.hashCode, { visual, group })
                 }
-            } else if (structure && _structure.hashCode !== structure.hashCode) {
-                // console.log('_structure.hashCode !== structure.hashCode')
+            } else if (structure && !Structure.areEquivalent(structure, _structure)) {
+                // console.log(label, 'structure not equivalent')
                 // Tries to re-use existing visuals for the groups of the new structure.
                 // Creates additional visuals if needed, destroys left-over visuals.
                 _groups = structure.unitSymmetryGroups;
@@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     const group = _groups[i];
                     const visualGroup = oldVisuals.get(group.hashCode)
                     if (visualGroup) {
+                        // console.log(label, 'found visualGroup to reuse')
+                        // console.log('old', visualGroup.group)
+                        // console.log('new', group)
                         const { visual } = visualGroup
                         await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                         oldVisuals.delete(group.hashCode)
                     } else {
+                        // console.log(label, 'not found visualGroup to reuse, creating new')
                         // newGroups.push(group)
                         const visual = visualCtor()
                         await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                     }
                 }
-                oldVisuals.forEach(({ visual }) => visual.destroy())
+                oldVisuals.forEach(({ visual }) => {
+                    // console.log(label, 'removed unused visual')
+                    visual.destroy()
+                })
 
                 // TODO review logic
                 // For new groups, re-use left-over visuals
@@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                 //     visuals.set(group.hashCode, { visual, group })
                 // })
                 // unusedVisuals.forEach(visual => visual.destroy())
-            } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) {
-                // console.log('_structure.hashCode === structure.hashCode')
+            } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
+                console.log(label, 'structures equivalent but not identical')
                 // Expects that for structures with the same hashCode,
                 // the unitSymmetryGroups are the same as well.
                 // Re-uses existing visuals for the groups of the new structure.
                 _groups = structure.unitSymmetryGroups;
+                // console.log('new', structure.unitSymmetryGroups)
+                // console.log('old', _structure.unitSymmetryGroups)
                 for (let i = 0; i < _groups.length; i++) {
                     const group = _groups[i];
                     const visualGroup = visuals.get(group.hashCode)
@@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     }
                 }
             } else {
-                // console.log('no new structure')
+                // console.log(label, 'no new structure')
                 // No new structure given, just update all visuals with new props.
                 const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
                 visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts
index f89364eb97b285c708707478672754fcb2479bc8..a80885a74a463239d8abeae566e131512e352270 100644
--- a/src/mol-repr/structure/units-visual.ts
+++ b/src/mol-repr/structure/units-visual.ts
@@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
 
-function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
-    return (
-        groupA.units.length === groupB.units.length &&
-        Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
-    )
-}
-
 type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
 interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> {
@@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
         renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) {
+    async function update(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<PD.Values<P>> = {}) {
         if (!renderObject) return
 
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
-        const unit = currentGroup.units[0]
+        const unit = group.units[0]
 
         locationIt.reset()
         VisualUpdateState.reset(updateState)
         setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
+        if (group.transformHash !== currentGroup.transformHash) {
+            if (group.units.length !== currentGroup.units.length || updateState.updateColor) {
+                updateState.updateTransform = true
+            } else {
+                updateState.updateMatrix = true
+            }
+        }
+
+        // check if the conformation of unit.model has changed
         const newConformationId = Unit.conformationId(unit)
         if (newConformationId !== currentConformationId) {
             currentConformationId = newConformationId
             updateState.createGeometry = true
         }
 
-        if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
-
-        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
-        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
-
         //
 
         if (updateState.updateTransform) {
-            locationIt = createLocationIterator(currentGroup)
+            locationIt = createLocationIterator(group)
             const { instanceCount, groupCount } = locationIt
-            createUnitsTransform(currentGroup, renderObject.values)
             createMarkers(instanceCount * groupCount, renderObject.values)
             updateState.updateColor = true
+            updateState.updateMatrix = true
+        }
+
+        if (updateState.updateMatrix) {
+            createUnitsTransform(group, renderObject.values)
         }
 
         if (updateState.createGeometry) {
@@ -143,6 +147,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
 
         currentProps = newProps
         currentTheme = theme
+        currentGroup = group
     }
 
     return {
@@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
                 await create(ctx, group, theme, props)
             } else {
                 // console.log('unit-visual update')
-                if (group && !sameGroupConformation(group, currentGroup)) {
-                    // console.log('unit-visual new conformation')
-                    currentGroup = group
-                }
-                await update(ctx, theme, props)
+                await update(ctx, group || currentGroup, theme, props)
             }
         },
         getLoci(pickingId: PickingId) {
diff --git a/src/mol-repr/util.ts b/src/mol-repr/util.ts
index ad7d101646a55f50ca2820049e251d538b4ff006..dd3acb447f6b4d09febdddc97fc27f098aa7c264 100644
--- a/src/mol-repr/util.ts
+++ b/src/mol-repr/util.ts
@@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry';
 
 export interface VisualUpdateState {
     updateTransform: boolean
+    updateMatrix: boolean
     updateColor: boolean
     updateSize: boolean
     createGeometry: boolean
@@ -18,6 +19,7 @@ export namespace VisualUpdateState {
     export function create(): VisualUpdateState {
         return {
             updateTransform: false,
+            updateMatrix: false,
             updateColor: false,
             updateSize: false,
             createGeometry: false
@@ -25,6 +27,7 @@ export namespace VisualUpdateState {
     }
     export function reset(state: VisualUpdateState) {
         state.updateTransform = false
+        state.updateMatrix = false
         state.updateColor = false
         state.updateSize = false
         state.createGeometry = false