diff --git a/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCore.H b/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCore.H
index 3bbc92855e52f4c6c21b7be82807c7553582b44a..e7feca1f9009f5e25c6c6b35ed752f37a462c0b4 100644
--- a/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCore.H
+++ b/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCore.H
@@ -160,13 +160,59 @@ public:
         const std::string& datasetName
     );
 
+    //- Add names to array selection
+    template<class StringType>
+    static label addToArray
+    (
+        vtkDataArraySelection* select,
+        const std::string& prefix,
+        const UList<StringType>& names
+    );
+
+    //- Add names to array selection
+    template<class StringType>
+    static label addToArray
+    (
+        vtkDataArraySelection* select,
+        const UList<StringType>& names,
+        const std::string& suffix = string::null
+    );
+
+    //- Add objects of Type to array selection
+    template<class Type>
+    static label addToSelection
+    (
+        vtkDataArraySelection* select,
+        const std::string& prefix,
+        const IOobjectList& objects
+    );
+
     //- Add objects of Type to array selection
     template<class Type>
     static label addToSelection
     (
         vtkDataArraySelection* select,
         const IOobjectList& objects,
-        const std::string& prefix = string::null
+        const std::string& suffix = string::null
+    );
+
+    //- Add objects of Type to array selection
+    template<class Type>
+    static label addToSelection
+    (
+        vtkDataArraySelection* select,
+        const std::string& prefix,
+        const HashTable<wordHashSet>& objects
+    );
+
+
+    //- Add objects of Type to array selection
+    template<class Type>
+    static label addToSelection
+    (
+        vtkDataArraySelection* select,
+        const HashTable<wordHashSet>& objects,
+        const std::string& suffix = string::null
     );
 
 
diff --git a/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCoreTemplates.C b/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCoreTemplates.C
index 54eeee88c09bb8d06756afcade39996a2c85f461..cd9e2302b23d80aa907e3b197081ae550463ff36 100644
--- a/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCoreTemplates.C
+++ b/applications/utilities/postProcessing/graphics/PVReaders/foamPv/foamPvCoreTemplates.C
@@ -28,25 +28,53 @@ License
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-template<class Type>
-Foam::label Foam::foamPvCore::addToSelection
+template<class StringType>
+Foam::label Foam::foamPvCore::addToArray
 (
     vtkDataArraySelection *select,
-    const IOobjectList& objects,
-    const std::string& prefix
+    const std::string& prefix,
+    const UList<StringType>& names
 )
 {
-    const wordList names = objects.sortedNames(Type::typeName);
+    if (prefix.empty())
+    {
+        for (const auto& name : names)
+        {
+            select->AddArray(name.c_str());
+        }
+    }
+    else
+    {
+        for (const auto& name : names)
+        {
+            select->AddArray((prefix + name).c_str());
+        }
+    }
+
+    return names.size();
+}
+
 
-    forAll(names, i)
+template<class StringType>
+Foam::label Foam::foamPvCore::addToArray
+(
+    vtkDataArraySelection *select,
+    const UList<StringType>& names,
+    const std::string& suffix
+)
+{
+    if (suffix.empty())
     {
-        if (prefix.empty())
+        for (const auto& name : names)
         {
-            select->AddArray(names[i].c_str());
+            select->AddArray(name.c_str());
         }
-        else
+    }
+    else
+    {
+        for (const auto& name : names)
         {
-            select->AddArray((prefix + names[i]).c_str());
+            select->AddArray((name + suffix).c_str());
         }
     }
 
@@ -54,6 +82,68 @@ Foam::label Foam::foamPvCore::addToSelection
 }
 
 
+template<class Type>
+Foam::label Foam::foamPvCore::addToSelection
+(
+    vtkDataArraySelection *select,
+    const std::string& prefix,
+    const IOobjectList& objects
+)
+{
+    return addToArray(select, prefix, objects.sortedNames(Type::typeName));
+}
+
+
+template<class Type>
+Foam::label Foam::foamPvCore::addToSelection
+(
+    vtkDataArraySelection *select,
+    const IOobjectList& objects,
+    const std::string& suffix
+)
+{
+    return addToArray(select, objects.sortedNames(Type::typeName), suffix);
+}
+
+
+template<class Type>
+Foam::label Foam::foamPvCore::addToSelection
+(
+    vtkDataArraySelection *select,
+    const std::string& prefix,
+    const HashTable<wordHashSet>& objects
+)
+{
+    auto iter = objects.cfind(Type::typeName);
+
+    if (iter.found())
+    {
+        return addToArray(select, prefix, iter.object().sortedToc());
+    }
+
+    return 0;
+}
+
+
+template<class Type>
+Foam::label Foam::foamPvCore::addToSelection
+(
+    vtkDataArraySelection *select,
+    const HashTable<wordHashSet>& objects,
+    const std::string& suffix
+)
+{
+    auto iter = objects.cfind(Type::typeName);
+
+    if (iter.found())
+    {
+        return addToArray(select, iter.object().sortedToc(), suffix);
+    }
+
+    return 0;
+}
+
+
 template<class AnyValue, class AnyHasher>
 void Foam::foamPvCore::setSelectedArrayEntries
 (
diff --git a/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoam.H b/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoam.H
index 55ec9d73e1e3916ea309d99687662028d039e677..226ff6b9bdb9c92a736c7fe15c0c7d8d9003be8b 100644
--- a/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoam.H
+++ b/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoam.H
@@ -155,7 +155,10 @@ class vtkPVFoam
             vtkSmartPointer<dataType> getCopy() const
             {
                 auto copy = vtkSmartPointer<dataType>::New();
-                copy->ShallowCopy(vtkgeom);
+                if (vtkgeom)
+                {
+                    copy->ShallowCopy(vtkgeom);
+                }
                 return copy;
             }
 
@@ -163,7 +166,10 @@ class vtkPVFoam
             void reuse()
             {
                 dataset = vtkSmartPointer<dataType>::New();
-                dataset->ShallowCopy(vtkgeom);
+                if (vtkgeom)
+                {
+                    dataset->ShallowCopy(vtkgeom);
+                }
             }
 
             //- Set the geometry and make a shallow copy to dataset
diff --git a/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoamUpdateInfo.C b/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoamUpdateInfo.C
index 335a975fc84cb9da27298383c56ebc5ee4f4e5b2..9e61618cab65c5488fd357c7a7bd8e090b52297f 100644
--- a/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoamUpdateInfo.C
+++ b/applications/utilities/postProcessing/graphics/PVReaders/vtkPVFoam/vtkPVFoamUpdateInfo.C
@@ -85,11 +85,11 @@ Foam::wordList Foam::vtkPVFoam::getZoneNames
     wordList names(zmesh.size());
     label nZone = 0;
 
-    forAll(zmesh, zonei)
+    for (const auto& zn : zmesh)
     {
-        if (!zmesh[zonei].empty())
+        if (!zn.empty())
         {
-            names[nZone++] = zmesh[zonei].name();
+            names[nZone++] = zn.name();
         }
     }
     names.setSize(nZone);
@@ -100,8 +100,6 @@ Foam::wordList Foam::vtkPVFoam::getZoneNames
 
 Foam::wordList Foam::vtkPVFoam::getZoneNames(const word& zoneType) const
 {
-    wordList names;
-
     // mesh not loaded - read from file
     IOobject ioObj
     (
@@ -119,14 +117,17 @@ Foam::wordList Foam::vtkPVFoam::getZoneNames(const word& zoneType) const
         false
     );
 
+    wordList names;
     if (ioObj.typeHeaderOk<cellZoneMesh>(false, false))
     {
         zonesEntries zones(ioObj);
 
         names.setSize(zones.size());
-        forAll(zones, zonei)
+        label nZone = 0;
+
+        for (const auto& zn : zones)
         {
-            names[zonei] = zones[zonei].keyword();
+            names[nZone++] = zn.keyword();
         }
     }
 
@@ -167,7 +168,7 @@ void Foam::vtkPVFoam::updateInfoLagrangian
             << "    " << dbPtr_->timePath()/cloud::prefix << endl;
     }
 
-    // use the db directly since this might be called without a mesh,
+    // Use the db directly since this might be called without a mesh,
     // but the region must get added back in
     fileName lagrangianPrefix(cloud::prefix);
     if (meshRegion_ != polyMesh::defaultRegion)
@@ -175,23 +176,24 @@ void Foam::vtkPVFoam::updateInfoLagrangian
         lagrangianPrefix = meshRegion_/cloud::prefix;
     }
 
-    // Search for list of lagrangian objects for this time
-    fileNameList cloudDirs
-    (
-        readDir(dbPtr_->timePath()/lagrangianPrefix, fileName::DIRECTORY)
-    );
+    // List of lagrangian objects across all times
+    HashSet<fileName> names;
 
-    rangeLagrangian_.reset(select->GetNumberOfArrays());
-    forAll(cloudDirs, cloudi)
+    for (const instant& t : dbPtr_().times())
     {
-        // Add cloud to GUI list
-        select->AddArray
+        names.insert
         (
-            ("lagrangian/" + cloudDirs[cloudi]).c_str()
+            readDir
+            (
+                dbPtr_->path()/t.name()/lagrangianPrefix,
+                fileName::DIRECTORY
+            )
         );
-        ++rangeLagrangian_;
     }
 
+    rangeLagrangian_.reset(select->GetNumberOfArrays());
+    rangeLagrangian_ += addToArray(select, "lagrangian/", names.sortedToc());
+
     if (debug)
     {
         Info<< "<end> " << FUNCTION_NAME << endl;
@@ -257,39 +259,26 @@ void Foam::vtkPVFoam::updateInfoPatches
                     const polyPatch& pp = patches[patchId];
                     if (pp.size())
                     {
-                        enabledEntries.insert
-                        (
-                            "patch/" + pp.name()
-                        );
+                        enabledEntries.insert("patch/" + pp.name());
                     }
                 }
             }
         }
 
-        // Sort group names
-        Foam::sort(displayNames);
-        for (const auto& name : displayNames)
-        {
-            select->AddArray(name.c_str());
-            ++rangePatches_;
-        }
+        Foam::sort(displayNames);  // Sorted group names
+        rangePatches_ += addToArray(select, displayNames);
 
         // Add (non-zero) patches to the list of mesh parts
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
         if (!reader_->GetShowGroupsOnly())
         {
-            forAll(patches, patchi)
+            for (const polyPatch& pp : patches)
             {
-                const polyPatch& pp = patches[patchi];
-
                 if (pp.size())
                 {
                     // Add patch to GUI list
-                    select->AddArray
-                    (
-                        ("patch/" + pp.name()).c_str()
-                    );
+                    select->AddArray(("patch/" + pp.name()).c_str());
                     ++rangePatches_;
                 }
             }
@@ -339,9 +328,9 @@ void Foam::vtkPVFoam::updateInfoPatches
                  && patchDict.readIfPresent("inGroups", groupNames)
                 )
                 {
-                    forAll(groupNames, groupI)
+                    for (const auto& groupName : groupNames)
                     {
-                        groups(groupNames[groupI]).insert(patchi);
+                        groups(groupName).insert(patchi);
                     }
                 }
             }
@@ -370,21 +359,13 @@ void Foam::vtkPVFoam::updateInfoPatches
                 {
                     for (auto patchId : patchIDs)
                     {
-                        enabledEntries.insert
-                        (
-                            "patch/" + names[patchId]
-                        );
+                        enabledEntries.insert("patch/" + names[patchId]);
                     }
                 }
             }
 
-            // Sort group names
-            Foam::sort(displayNames);
-            for (const auto& name : displayNames)
-            {
-                select->AddArray(name.c_str());
-                ++rangePatches_;
-            }
+            Foam::sort(displayNames);  // Sorted group names
+            rangePatches_ += addToArray(select, displayNames);
 
             // Add (non-zero) patches to the list of mesh parts
             // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -396,10 +377,7 @@ void Foam::vtkPVFoam::updateInfoPatches
                     // Valid patch if nFace > 0 - add patch to GUI list
                     if (sizes[patchi])
                     {
-                        select->AddArray
-                        (
-                            ("patch/" + names[patchi]).c_str()
-                        );
+                        select->AddArray(("patch/" + names[patchi]).c_str());
                         ++rangePatches_;
                     }
                 }
@@ -430,74 +408,44 @@ void Foam::vtkPVFoam::updateInfoZones
             << " [meshPtr=" << (meshPtr_ ? "set" : "null") << "]" << endl;
     }
 
-    wordList namesLst;
 
-    //
-    // cellZones information
-    // ~~~~~~~~~~~~~~~~~~~~~
-    if (meshPtr_)
+    // cellZones
     {
-        namesLst = getZoneNames(meshPtr_->cellZones());
-    }
-    else
-    {
-        namesLst = getZoneNames("cellZones");
-    }
-
-    rangeCellZones_.reset(select->GetNumberOfArrays());
-    forAll(namesLst, elemI)
-    {
-        select->AddArray
+        const wordList names =
         (
-            ("cellZone/" + namesLst[elemI]).c_str()
+            meshPtr_
+          ? getZoneNames(meshPtr_->cellZones())
+          : getZoneNames("cellZones")
         );
-        ++rangeCellZones_;
-    }
 
-
-    //
-    // faceZones information
-    // ~~~~~~~~~~~~~~~~~~~~~
-    if (meshPtr_)
-    {
-        namesLst = getZoneNames(meshPtr_->faceZones());
-    }
-    else
-    {
-        namesLst = getZoneNames("faceZones");
+        rangeCellZones_.reset(select->GetNumberOfArrays());
+        rangeCellZones_ += addToArray(select, "cellZone/", names);
     }
 
-    rangeFaceZones_.reset(select->GetNumberOfArrays());
-    forAll(namesLst, elemI)
+    // faceZones
     {
-        select->AddArray
+        const wordList names =
         (
-            ("faceZone/" + namesLst[elemI]).c_str()
+            meshPtr_
+          ? getZoneNames(meshPtr_->faceZones())
+          : getZoneNames("faceZones")
         );
-        ++rangeFaceZones_;
-    }
 
-
-    //
-    // pointZones information
-    // ~~~~~~~~~~~~~~~~~~~~~~
-    if (meshPtr_)
-    {
-        namesLst = getZoneNames(meshPtr_->pointZones());
-    }
-    else
-    {
-        namesLst = getZoneNames("pointZones");
+        rangeFaceZones_.reset(select->GetNumberOfArrays());
+        rangeFaceZones_ += addToArray(select, "faceZone/", names);
     }
 
-    rangePointZones_.reset(select->GetNumberOfArrays());
-    forAll(namesLst, elemI)
+    // pointZones
     {
-        select->AddArray
+        const wordList names =
         (
-            ("pointZone/" + namesLst[elemI]).c_str()
+            meshPtr_
+          ? getZoneNames(meshPtr_->pointZones())
+          : getZoneNames("pointZones")
         );
-        ++rangePointZones_;
+
+        rangePointZones_.reset(select->GetNumberOfArrays());
+        rangePointZones_ += addToArray(select, "pointZone/", names);
     }
 
     if (debug)
@@ -525,14 +473,14 @@ void Foam::vtkPVFoam::updateInfoSets
     // Add names of sets. Search for last time directory with a sets
     // subdirectory. Take care not to search beyond the last mesh.
 
-    word facesInstance = dbPtr_().findInstance
+    const word facesInstance = dbPtr_().findInstance
     (
         meshDir_,
         "faces",
         IOobject::READ_IF_PRESENT
     );
 
-    word setsInstance = dbPtr_().findInstance
+    const word setsInstance = dbPtr_().findInstance
     (
         meshDir_/"sets",
         word::null,
@@ -540,7 +488,7 @@ void Foam::vtkPVFoam::updateInfoSets
         facesInstance
     );
 
-    IOobjectList objects(dbPtr_(), setsInstance, meshDir_/"sets");
+    const IOobjectList objects(dbPtr_(), setsInstance, meshDir_/"sets");
 
     if (debug)
     {
@@ -553,24 +501,24 @@ void Foam::vtkPVFoam::updateInfoSets
     rangeCellSets_ += addToSelection<cellSet>
     (
         select,
-        objects,
-        "cellSet/"
+        "cellSet/",
+        objects
     );
 
     rangeFaceSets_.reset(select->GetNumberOfArrays());
     rangeFaceSets_ += addToSelection<faceSet>
     (
         select,
-        objects,
-        "faceSet/"
+        "faceSet/",
+        objects
     );
 
     rangePointSets_.reset(select->GetNumberOfArrays());
     rangePointSets_ += addToSelection<pointSet>
     (
         select,
-        objects,
-        "pointSet/"
+        "pointSet/",
+        objects
     );
 
     if (debug)
@@ -594,21 +542,20 @@ void Foam::vtkPVFoam::updateInfoLagrangianFields
     HashSet<string> enabled = getSelectedArraySet(select);
     select->RemoveAllArrays();
 
-    // TODO - currently only get fields from ONE cloud
-    // have to decide if the second set of fields get mixed in
-    // or dealt with separately
-
     const arrayRange& range = rangeLagrangian_;
     if (range.empty())
     {
         return;
     }
 
-    // Add Lagrangian fields even if particles are not enabled?
-    const int partId = range.start();
-    const word cloudName = getReaderPartName(partId);
+    // Reuse the previously determined cloud information.
+    DynamicList<word> cloudNames(range.size());
+    for (auto partId : range)
+    {
+        cloudNames.append(getReaderPartName(partId));
+    }
 
-    // use the db directly since this might be called without a mesh,
+    // Use the db directly since this might be called without a mesh,
     // but the region must get added back in
     fileName lagrangianPrefix(cloud::prefix);
     if (meshRegion_ != polyMesh::defaultRegion)
@@ -616,19 +563,37 @@ void Foam::vtkPVFoam::updateInfoLagrangianFields
         lagrangianPrefix = meshRegion_/cloud::prefix;
     }
 
-    IOobjectList objects
-    (
-        dbPtr_(),
-        dbPtr_().timeName(),
-        lagrangianPrefix/cloudName
-    );
+    // List of lagrangian fields across all clouds and all times.
+    // ParaView displays "(partial)" after field names that only apply
+    // to some of the clouds.
+    HashTable<wordHashSet> fields;
+
+    for (const instant& t : dbPtr_().times())
+    {
+        for (const auto& cloudName : cloudNames)
+        {
+            const HashTable<wordHashSet> localFields =
+                IOobjectList
+                (
+                    dbPtr_(),
+                    t.name(),
+                    lagrangianPrefix/cloudName
+                ).classes();
+
+            forAllConstIters(localFields, iter)
+            {
+                fields(iter.key()) |= iter.object();
+            }
+        }
+    }
 
-    addToSelection<IOField<label>>(select, objects);
-    addToSelection<IOField<scalar>>(select, objects);
-    addToSelection<IOField<vector>>(select, objects);
-    addToSelection<IOField<sphericalTensor>>(select, objects);
-    addToSelection<IOField<symmTensor>>(select, objects);
-    addToSelection<IOField<tensor>>(select, objects);
+    // Known/supported field-types
+    addToSelection<IOField<label>>(select, fields);
+    addToSelection<IOField<scalar>>(select, fields);
+    addToSelection<IOField<vector>>(select, fields);
+    addToSelection<IOField<sphericalTensor>>(select, fields);
+    addToSelection<IOField<symmTensor>>(select, fields);
+    addToSelection<IOField<tensor>>(select, fields);
 
     // Restore the enabled selections
     setSelectedArrayEntries(select, enabled);