diff --git a/src/faOptions/sources/derived/externalFileSource/externalFileSource.C b/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
index 6f0a35080e3926456003315d1142a8f4537ea94d..a1d23336163763d78e1eb5b8b163181c1839db0f 100644
--- a/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
+++ b/src/faOptions/sources/derived/externalFileSource/externalFileSource.C
@@ -65,7 +65,11 @@ Foam::fa::externalFileSource::externalFileSource
             mesh_,
             IOobject::NO_READ,
             IOobject::NO_WRITE,
-            false  // Do not register
+            (
+                dict.getOrDefault("store", false)
+              ? IOobject::REGISTER
+              : IOobject::NO_REGISTER
+            )
         ),
         regionMesh(),
         dimensionedScalar(dimPressure, Zero)
@@ -75,6 +79,10 @@ Foam::fa::externalFileSource::externalFileSource
 {
     fieldNames_.resize(1, fieldName_);
 
+    /// FUTURE?
+    /// // Optional entry (mandatory = false)
+    /// pExt_.dimensions().readEntry("dimensions",  dict, false);
+
     fa::option::resetApplied();
 
     read(dict);
@@ -155,7 +163,7 @@ bool Foam::fa::externalFileSource::read(const dictionary& dict)
                     new PatchFunction1Types::MappedFile<scalar>
                     (
                         p,
-                        "uniformValue",
+                        "uniformValue", // entryName
                         dict,
                         tableName_,     // field table name
                         true            // face values
diff --git a/src/faOptions/sources/derived/externalFileSource/externalFileSource.H b/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
index a2056e18b0a13236e2ed3740b19d95e48c51f9e0..74a0faa4d9b05d5bcf6b08dd26c204a6fc93b7f4 100644
--- a/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
+++ b/src/faOptions/sources/derived/externalFileSource/externalFileSource.H
@@ -54,6 +54,7 @@ Usage
       type      | Type name: externalFileSource  | word  | yes  | -
       fieldName | Name of operand field          | word  | yes  | -
       tableName | Name of operand table file     | word  | yes  | -
+      store     | Register external field 'pExt' | bool  | no   | false
     \endtable
 
     The inherited entries are elaborated in:
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 7b633c6b3f4d8e149e9a1a045b7e7c9ba21fd608..53d04f78524ce58c5e627838c9d9043880670765 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -1,3 +1,4 @@
+common/fileFormats.C
 common/manifoldCellsMeshObject.C
 
 colours/colourTable.C
diff --git a/src/fileFormats/common/fileFormats.C b/src/fileFormats/common/fileFormats.C
new file mode 100644
index 0000000000000000000000000000000000000000..d5a55e834f0e2cba2d6bf6130d80aa01ce64a28c
--- /dev/null
+++ b/src/fileFormats/common/fileFormats.C
@@ -0,0 +1,122 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "fileFormats.H"
+#include "dictionary.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Extract and merge 'default' + formatName from list of dictionaries
+//
+// \returns dictionary of merged options
+static dictionary combineFormatOptions
+(
+    const word& formatName,
+    std::initializer_list<const dictionary*> dicts
+)
+{
+    dictionary options;
+
+    // Default specification. Merge from all levels
+    // - literal search only
+    for (const dictionary* dict : dicts)
+    {
+        if
+        (
+            dict
+         && (dict = dict->findDict("default", keyType::LITERAL)) != nullptr
+        )
+        {
+            options.merge(*dict);
+        }
+    }
+
+    // Format specification. Merge from all levels
+    // - allow REGEX search
+    if (!formatName.empty())
+    {
+        for (const dictionary* dict : dicts)
+        {
+            if
+            (
+                dict
+             && (dict = dict->findDict(formatName)) != nullptr
+            )
+            {
+                options.merge(*dict);
+            }
+        }
+    }
+
+    return options;
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * //
+
+Foam::dictionary Foam::fileFormats::getFormatOptions
+(
+    const dictionary& dict,
+    const word& formatName,
+    const word& entryName
+)
+{
+    return combineFormatOptions
+    (
+        formatName,
+        {
+            dict.findDict(entryName, keyType::LITERAL)
+        }
+    );
+}
+
+
+Foam::dictionary Foam::fileFormats::getFormatOptions
+(
+    const dictionary& dict,
+    const dictionary& altDict,
+    const word& formatName,
+    const word& entryName
+)
+{
+    return combineFormatOptions
+    (
+        formatName,
+        {
+            dict.findDict(entryName, keyType::LITERAL),
+            altDict.findDict(entryName, keyType::LITERAL)
+        }
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/common/fileFormats.H b/src/fileFormats/common/fileFormats.H
new file mode 100644
index 0000000000000000000000000000000000000000..ec1f4ed9f4bf53b3e5cbbd71ebb372be80c2125b
--- /dev/null
+++ b/src/fileFormats/common/fileFormats.H
@@ -0,0 +1,102 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Namespace
+    Foam::fileFormats
+
+Description
+    Namespace to isolate specifics for file formats,
+    and some common utilities.
+
+SourceFiles
+    fileFormats.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_fileFormats_H
+#define Foam_fileFormats_H
+
+#include "word.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class dictionary;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace fileFormats
+{
+
+//- Find "formatOptions" in a top-level dictionary.
+//- Extract and merge 'default' + formatName values.
+//
+//  \returns dictionary of merged formatOptions
+dictionary getFormatOptions
+(
+    //! The top-level dictionary to search
+    const dictionary& dict,
+
+    //! The format name. Eg, \c ensight
+    const word& formatName,
+
+    //! Dictionary sub-entry to search for
+    const word& entryName = "formatOptions"
+);
+
+//- Find "formatOptions" in a top-level dictionary,
+//- and optional override dictionary.
+//- Extract and merge 'default' + formatName values.
+//
+//  \returns dictionary of merged formatOptions
+dictionary getFormatOptions
+(
+    //! The top-level dictionary to search
+    const dictionary& dict,
+
+    //! Additional dictionary to search
+    const dictionary& altDict,
+
+    //! The format name. Eg, \c ensight
+    const word& formatName,
+
+    //! Dictionary sub-entry to search for
+    const word& entryName = "formatOptions"
+);
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace fileFormats
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/read/ensightReadFile.C b/src/fileFormats/ensight/read/ensightReadFile.C
index 07064ce263371e50f5280535357c3c333bb922b2..3dcf09e4a1751b9abff8d858ab48520e33b547d8 100644
--- a/src/fileFormats/ensight/read/ensightReadFile.C
+++ b/src/fileFormats/ensight/read/ensightReadFile.C
@@ -180,7 +180,27 @@ Foam::Istream& Foam::ensightReadFile::read(label& value)
 }
 
 
-Foam::Istream& Foam::ensightReadFile::read(scalar& value)
+Foam::Istream& Foam::ensightReadFile::read(float& value)
+{
+    if (format() == IOstreamOption::BINARY)
+    {
+        read
+        (
+            reinterpret_cast<char*>(&value),
+            sizeof(value)
+        );
+    }
+    else
+    {
+        stdStream() >> value;
+        syncState();
+    }
+
+    return *this;
+}
+
+
+Foam::Istream& Foam::ensightReadFile::read(double& value)
 {
     float fvalue;
 
@@ -191,15 +211,14 @@ Foam::Istream& Foam::ensightReadFile::read(scalar& value)
             reinterpret_cast<char*>(&fvalue),
             sizeof(fvalue)
         );
-
-        value = fvalue;
     }
     else
     {
-        stdStream() >> value;
+        stdStream() >> fvalue;
         syncState();
     }
 
+    value = fvalue;
     return *this;
 }
 
diff --git a/src/fileFormats/ensight/read/ensightReadFile.H b/src/fileFormats/ensight/read/ensightReadFile.H
index 4f8ff48529a2928a94e798d9cc2a3c6585700ea0..6982ae907237799487ce1675f9ee633464554396 100644
--- a/src/fileFormats/ensight/read/ensightReadFile.H
+++ b/src/fileFormats/ensight/read/ensightReadFile.H
@@ -27,7 +27,8 @@ Class
     Foam::ensightReadFile
 
 Description
-    Ensight output with specialized read() for strings, integers and floats.
+    A variant of IFstream with specialised read() for
+    strings, integers and floats.
     Correctly handles binary read as well.
 
 \*---------------------------------------------------------------------------*/
@@ -98,13 +99,16 @@ public:
         virtual Istream& read(char* buf, std::streamsize count);
 
         //- Read string as "%80s" or as binary
-        Istream& read(string& value);
+        virtual Istream& read(string& value);
 
         //- Read integer as "%10d" or as binary
-        Istream& read(label& value);
+        virtual Istream& read(label& value);
 
-        //- Read float as "%12.5e" or as binary
-        Istream& read(scalar& value);
+        //- Read floating-point as "%12.5e" or as binary
+        virtual Istream& read(float& value);
+
+        //- Read floating-point as "%12.5e" or as a binary (narrowed) float
+        virtual Istream& read(double& value);
 
         //- Read element keyword
         virtual Istream& readKeyword(string& key);
diff --git a/src/finiteArea/fields/areaFields/areaFields.C b/src/finiteArea/fields/areaFields/areaFields.C
index 08412b1a69dc8e5b6019d5e03d06550f8141c04a..2120369959b8aba8b87e0509e576c7dc6e97a601 100644
--- a/src/finiteArea/fields/areaFields/areaFields.C
+++ b/src/finiteArea/fields/areaFields/areaFields.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2016-2017 Wikki Ltd
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -92,5 +92,14 @@ const Foam::wordList Foam::fieldTypes::area
     "areaTensorField"
 });
 
+const Foam::wordList Foam::fieldTypes::area_internal
+({
+    "areaScalarField::Internal",
+    "areaVectorField::Internal",
+    "areaSphericalTensorField::Internal",
+    "areaSymmTensorField::Internal",
+    "areaTensorField::Internal"
+});
+
 
 // ************************************************************************* //
diff --git a/src/finiteArea/fields/areaFields/areaFieldsFwd.H b/src/finiteArea/fields/areaFields/areaFieldsFwd.H
index 621222c69b40c9614f4587011cb86370fb14394e..202298c2d3bb866445e4680d73d7743bc06a6f39 100644
--- a/src/finiteArea/fields/areaFields/areaFieldsFwd.H
+++ b/src/finiteArea/fields/areaFields/areaFieldsFwd.H
@@ -92,6 +92,9 @@ namespace fieldTypes
     //- Standard area field types (scalar, vector, tensor, etc)
     extern const wordList area;
 
+    //- Standard dimensioned field types (scalar, vector, tensor, etc)
+    extern const wordList area_internal;
+
 } // End namespace fieldTypes
 
 
diff --git a/src/functionObjects/utilities/areaWrite/areaWrite.C b/src/functionObjects/utilities/areaWrite/areaWrite.C
index 6ef6ee820a37e2cf0b0ec71001a04b92a0673cff..8a6e7b898ef5326d9c1c4be7712bdfd486af74c4 100644
--- a/src/functionObjects/utilities/areaWrite/areaWrite.C
+++ b/src/functionObjects/utilities/areaWrite/areaWrite.C
@@ -53,6 +53,10 @@ namespace Foam
 
 Foam::scalar Foam::areaWrite::mergeTol_ = 1e-10;
 
+
+// Implementation
+#include "areaWriteImpl.C"
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::areaWrite::areaWrite
@@ -110,7 +114,7 @@ Foam::areaWrite::areaWrite
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::areaWrite::verbose(const bool on)
+bool Foam::areaWrite::verbose(const bool on) noexcept
 {
     bool old(verbose_);
     verbose_ = on;
@@ -137,7 +141,7 @@ bool Foam::areaWrite::read(const dictionary& dict)
                 obr_,
                 IOobject::NO_READ,
                 IOobject::NO_WRITE,
-                false
+                IOobject::NO_REGISTER
             )
         )
     );
@@ -299,7 +303,11 @@ bool Foam::areaWrite::write()
             const word& clsName = iter.key();
             const label n = iter.val().size();
 
-            if (fieldTypes::area.found(clsName))
+            if
+            (
+                fieldTypes::area.found(clsName)
+             || fieldTypes::area_internal.found(clsName)
+            )
             {
                 nAreaFields += n;
             }
@@ -318,11 +326,43 @@ bool Foam::areaWrite::write()
 
         // Write fields
 
-        performAction<areaScalarField>(outWriter, areaMesh, objects);
-        performAction<areaVectorField>(outWriter, areaMesh, objects);
-        performAction<areaSphericalTensorField>(outWriter, areaMesh, objects);
-        performAction<areaSymmTensorField>(outWriter, areaMesh, objects);
-        performAction<areaTensorField>(outWriter, areaMesh, objects);
+        {
+            // Area fields
+            #undef  doLocalCode
+            #define doLocalCode(Type)                                         \
+            performAction                                                     \
+            <                                                                 \
+                GeometricField<Type, Foam::faPatchField, Foam::areaMesh>      \
+            >                                                                 \
+            (                                                                 \
+                outWriter, areaMesh, objects                                  \
+            );                                                                \
+
+            doLocalCode(scalar);
+            doLocalCode(vector);
+            doLocalCode(sphericalTensor);
+            doLocalCode(symmTensor);
+            doLocalCode(tensor);
+
+            // Area internal fields
+            #undef  doLocalCode
+            #define doLocalCode(Type)                                         \
+            performAction                                                     \
+            <                                                                 \
+                DimensionedField<Type, Foam::areaMesh>                        \
+            >                                                                 \
+            (                                                                 \
+                outWriter, areaMesh, objects                                  \
+            );
+
+            doLocalCode(scalar);
+            doLocalCode(vector);
+            doLocalCode(sphericalTensor);
+            doLocalCode(symmTensor);
+            doLocalCode(tensor);
+
+            #undef doLocalCode
+        }
 
         // Finish this time step
 
@@ -384,17 +424,17 @@ void Foam::areaWrite::readUpdate(const polyMesh::readUpdateState state)
 }
 
 
-Foam::scalar Foam::areaWrite::mergeTol()
+Foam::scalar Foam::areaWrite::mergeTol() noexcept
 {
     return mergeTol_;
 }
 
 
-Foam::scalar Foam::areaWrite::mergeTol(const scalar tol)
+Foam::scalar Foam::areaWrite::mergeTol(const scalar tol) noexcept
 {
-    const scalar prev(mergeTol_);
+    scalar old(mergeTol_);
     mergeTol_ = tol;
-    return prev;
+    return old;
 }
 
 
diff --git a/src/functionObjects/utilities/areaWrite/areaWrite.H b/src/functionObjects/utilities/areaWrite/areaWrite.H
index fdcec555c3fd43cbb72c573560d84df21362d4fc..bca7c2a36de0f3f810f7982d757b6dc81d917093 100644
--- a/src/functionObjects/utilities/areaWrite/areaWrite.H
+++ b/src/functionObjects/utilities/areaWrite/areaWrite.H
@@ -210,7 +210,7 @@ public:
 
         //- Enable/disable verbose output
         //  \return old value
-        bool verbose(const bool on);
+        bool verbose(const bool on) noexcept;
 
         //- Read the areaWrite dictionary
         virtual bool read(const dictionary& dict);
@@ -231,10 +231,10 @@ public:
         virtual void readUpdate(const polyMesh::readUpdateState state);
 
         //- Get merge tolerance
-        static scalar mergeTol();
+        static scalar mergeTol() noexcept;
 
         //- Set merge tolerance and return old value
-        static scalar mergeTol(const scalar tol);
+        static scalar mergeTol(const scalar tol) noexcept;
 };
 
 
@@ -244,12 +244,6 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-#ifdef NoRepository
-    #include "areaWriteTemplates.C"
-#endif
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 #endif
 
 // ************************************************************************* //
diff --git a/src/functionObjects/utilities/areaWrite/areaWriteTemplates.C b/src/functionObjects/utilities/areaWrite/areaWriteImpl.C
similarity index 100%
rename from src/functionObjects/utilities/areaWrite/areaWriteTemplates.C
rename to src/functionObjects/utilities/areaWrite/areaWriteImpl.C
diff --git a/src/meshTools/PatchFunction1/MappedFile/MappedFile.C b/src/meshTools/PatchFunction1/MappedFile/MappedFile.C
index 270ceaeaf1c8248388e84a813a4e0d67ca98006f..6d6138017aac29615a66739457e94d2bfbe6287f 100644
--- a/src/meshTools/PatchFunction1/MappedFile/MappedFile.C
+++ b/src/meshTools/PatchFunction1/MappedFile/MappedFile.C
@@ -27,24 +27,26 @@ License
 
 #include "polyMesh.H"
 #include "rawIOField.H"
+#include "clockTime.H"
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 template<class Type>
 Foam::PatchFunction1Types::MappedFile<Type>::MappedFile
 (
+    const bool dictConstructed,
     const polyPatch& pp,
-    const word& redirectType,
     const word& entryName,
     const dictionary& dict,
+    const word& fieldTableName,
     const bool faceValues
 )
 :
     PatchFunction1<Type>(pp, entryName, dict, faceValues),
-    dictConstructed_(true),
+    dictConstructed_(dictConstructed),
     setAverage_(dict.getOrDefault("setAverage", false)),
     perturb_(dict.getOrDefault<scalar>("perturb", 1e-5)),
-    fieldTableName_(dict.getOrDefault<word>("fieldTable", entryName)),
+    fieldTableName_(fieldTableName),
     pointsName_(dict.getOrDefault<word>("points", "points")),
     mapMethod_(),
     filterRadius_(dict.getOrDefault<scalar>("filterRadius", 0)),
@@ -60,6 +62,11 @@ Foam::PatchFunction1Types::MappedFile<Type>::MappedFile
     sampleValues_(),
     offset_(Function1<Type>::NewIfPresent("offset", dict))
 {
+    if (fieldTableName_.empty())
+    {
+        fieldTableName_ = entryName;
+    }
+
     // Simple sanity check
     if ((filterSweeps_ < 1) || (filterRadius_ <= VSMALL))
     {
@@ -74,7 +81,12 @@ Foam::PatchFunction1Types::MappedFile<Type>::MappedFile
         fileName fName(readerFile_);
         fName.expand();
 
-        readerPtr_ = surfaceReader::New(readerFormat_, fName);
+        readerPtr_ = surfaceReader::New
+        (
+            readerFormat_,
+            fName,
+            surfaceReader::formatOptions(dict, readerFormat_, "readOptions")
+        );
     }
 
     if (debug)
@@ -115,81 +127,44 @@ template<class Type>
 Foam::PatchFunction1Types::MappedFile<Type>::MappedFile
 (
     const polyPatch& pp,
+    const word& redirectType,
     const word& entryName,
     const dictionary& dict,
-    const word& fieldTableName,
     const bool faceValues
 )
 :
-    PatchFunction1<Type>(pp, entryName, dict, faceValues),
-    dictConstructed_(false),
-    setAverage_(dict.getOrDefault("setAverage", false)),
-    perturb_(dict.getOrDefault<scalar>("perturb", 1e-5)),
-    fieldTableName_(fieldTableName),
-    pointsName_(dict.getOrDefault<word>("points", "points")),
-    mapMethod_(),
-    filterRadius_(dict.getOrDefault<scalar>("filterRadius", 0)),
-    filterSweeps_(dict.getOrDefault<label>("filterSweeps", 0)),
-    filterFieldPtr_(nullptr),
-    readerFormat_(),
-    readerFile_(),
-    readerPtr_(nullptr),
-    mapperPtr_(nullptr),
-    sampleTimes_(),
-    sampleIndex_(-1, -1),
-    sampleAverage_(Zero, Zero),
-    sampleValues_(),
-    offset_(Function1<Type>::NewIfPresent("offset", dict))
-{
-    // Simple sanity check
-    if ((filterSweeps_ < 1) || (filterRadius_ <= VSMALL))
-    {
-        filterRadius_ = 0;
-        filterSweeps_ = 0;
-    }
-
-    if (dict.readIfPresent("sampleFormat", readerFormat_))
-    {
-        dict.readEntry("sampleFile", readerFile_);
-
-        fileName fName(readerFile_);
-        fName.expand();
-
-        readerPtr_ = surfaceReader::New(readerFormat_, fName);
-    }
+    MappedFile<Type>
+    (
+        true,  // dictConstructed = true
+        pp,
+        entryName,
+        dict,
+        dict.getOrDefault<word>("fieldTable", entryName),
+        faceValues
+    )
+{}
 
-    if (debug)
-    {
-        Info<< "mappedFile:" << nl;
-        if (readerFormat_.empty())
-        {
-            Info<< "    boundary format" << nl;
-        }
-        else
-        {
-            Info<< "    format:" << readerFormat_
-                << " file:" << readerFile_ << nl;
-        }
 
-        Info<< "    filter radius=" << filterRadius_
-            << " sweeps=" << filterSweeps_ << endl;
-    }
-
-    if
+template<class Type>
+Foam::PatchFunction1Types::MappedFile<Type>::MappedFile
+(
+    const polyPatch& pp,
+    const word& entryName,
+    const dictionary& dict,
+    const word& fieldTableName,
+    const bool faceValues
+)
+:
+    MappedFile<Type>
     (
-        dict.readIfPresent("mapMethod", mapMethod_)
-     && !mapMethod_.empty()
-     && mapMethod_ != "nearest"
-     && !mapMethod_.starts_with("planar")
+        false,  // dictConstructed = false
+        pp,
+        entryName,
+        dict,
+        fieldTableName,
+        faceValues
     )
-    {
-        FatalIOErrorInFunction(dict)
-            << "Unknown mapMethod type " << mapMethod_
-            << "\n\nValid mapMethod types :\n"
-            << "(nearest planar)" << nl
-            << exit(FatalIOError);
-    }
-}
+{}
 
 
 template<class Type>
@@ -313,9 +288,20 @@ void Foam::PatchFunction1Types::MappedFile<Type>::updateSampledValues
 
         label fieldIndex = fieldNames.find(fieldTableName_);
 
-        DebugInfo
-            << "checkTable : Update index=" << sampleIndex
-            << " field=" << fieldNames[fieldIndex] << endl;
+        if (fieldIndex < 0)
+        {
+            FatalErrorInFunction
+                << "Sample field='" << fieldTableName_
+                << "' not found. Known field names: "
+                << flatOutput(fieldNames) << nl
+                << exit(FatalError);
+        }
+
+        if (debug)
+        {
+            Pout<< "checkTable : Update index=" << sampleIndex
+                << " field=" << fieldNames[fieldIndex] << endl;
+        }
 
         tvalues = readerPtr_->field
         (
@@ -418,6 +404,8 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
     // Initialise
     if (!mapperPtr_ && readerPtr_)
     {
+        clockTime timing;
+
         auto& reader = readerPtr_();
 
         const meshedSurface& geom = reader.geometry(0);
@@ -436,7 +424,8 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
             << "Read " << samplePoints.size() << " sample points from "
             << readerFile_ << endl
             << "Found times "
-            << pointToPointPlanarInterpolation::timeNames(sampleTimes_) << nl;
+            << pointToPointPlanarInterpolation::timeNames(sampleTimes_) << nl
+            << "... in " << timing.timeIncrement() << 's' << endl;
 
         // tbd: run-time selection
         const bool nearestOnly =
@@ -444,7 +433,6 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
             !mapMethod_.empty() && !mapMethod_.starts_with("planar")
         );
 
-
         // Allocate the interpolator
         if (this->faceValues())
         {
@@ -473,10 +461,19 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
             );
         }
 
+        DebugInfo
+            << "Created point/point planar interpolation"
+            << " - in " << timing.timeIncrement() << 's' << endl;
+
+
         // Setup median filter (if any)
         if (filterSweeps_ > 0)
         {
             filterFieldPtr_.reset(new FilterField(geom, filterRadius_));
+
+            DebugInfo
+                << "Calculated field-filter"
+                << " - in " << timing.timeIncrement() << 's' << endl;
         }
         else
         {
@@ -485,6 +482,8 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
     }
     else if (!mapperPtr_)
     {
+        clockTime timing;
+
         // Reread values and interpolate
         const fileName samplePointsFile
         (
@@ -509,16 +508,23 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
         // Read data (no average value!)
         const rawIOField<point> samplePoints(io);
 
+        // Read the times for which data is available
+        sampleTimes_ = Time::findTimes(samplePointsFile.path());
+
+        DebugInfo
+            << "Read " << samplePoints.size() << " sample points from "
+            << samplePointsFile << endl
+            << "Found times "
+            << pointToPointPlanarInterpolation::timeNames(sampleTimes_) << nl
+            << "... in " << timing.timeIncrement() << 's' << endl;
+
+
         // tbd: run-time selection
         const bool nearestOnly =
         (
             !mapMethod_.empty() && !mapMethod_.starts_with("planar")
         );
 
-        DebugInfo
-            << "Read " << samplePoints.size() << " sample points from "
-            << samplePointsFile << endl;
-
         // Allocate the interpolator
         if (this->faceValues())
         {
@@ -547,19 +553,19 @@ void Foam::PatchFunction1Types::MappedFile<Type>::checkTable
             );
         }
 
-        // Read the times for which data is available
-        const fileName samplePointsDir = samplePointsFile.path();
-        sampleTimes_ = Time::findTimes(samplePointsDir);
-
         DebugInfo
-            << "Found times "
-            << pointToPointPlanarInterpolation::timeNames(sampleTimes_)
-            << endl;
+            << "Created point/point planar interpolation"
+            << " - in " << timing.timeIncrement() << 's' << endl;
+
 
         // Setup median filter (if any)
         if (filterSweeps_ > 0)
         {
             filterFieldPtr_.reset(new FilterField(samplePoints, filterRadius_));
+
+            DebugInfo
+                << "Calculated field-filter"
+                << " - in " << timing.timeIncrement() << 's' << endl;
         }
         else
         {
diff --git a/src/meshTools/PatchFunction1/MappedFile/MappedFile.H b/src/meshTools/PatchFunction1/MappedFile/MappedFile.H
index b8ef66b9d8f20d8d37a5e7d10d0de6ef50797d00..7a87184340448b1fa7c9e7c9eb167526d1bbcefa 100644
--- a/src/meshTools/PatchFunction1/MappedFile/MappedFile.H
+++ b/src/meshTools/PatchFunction1/MappedFile/MappedFile.H
@@ -62,6 +62,7 @@ Description
       sampleFile   | <case>/foo/bar/window.case
       filterRadius | Search radius [m] for median filter neighbours
       filterSweeps | Filter sweeps for median filter
+      readOptions  | Format options for surfaceReader format (eg, ensight)
     \endtable
 
 Note
@@ -173,6 +174,17 @@ class MappedFile
             Type& avg
         ) const;
 
+        //- Construct from entry name and dictionary
+        MappedFile
+        (
+            const bool dictConstructed,
+            const polyPatch& pp,
+            const word& entryName,
+            const dictionary& dict,
+            const word& fieldTableName,
+            const bool faceValues
+        );
+
 public:
 
     //- Runtime type information
@@ -204,7 +216,7 @@ public:
             const word& entryName,
             const dictionary& dict,
             const word& fieldTableName,
-            const bool faceValues
+            const bool faceValues = true
         );
 
         //- Copy construct
diff --git a/src/meshTools/PatchFunction1/MappedFile/MappedFileFilterField.C b/src/meshTools/PatchFunction1/MappedFile/MappedFileFilterField.C
index fd738efbcbe85d006bf814232f6fba159eb4344f..8e7ce676cd6505fbeca14939a8fccc6b8ac36e0b 100644
--- a/src/meshTools/PatchFunction1/MappedFile/MappedFileFilterField.C
+++ b/src/meshTools/PatchFunction1/MappedFile/MappedFileFilterField.C
@@ -227,7 +227,7 @@ void Foam::PatchFunction1Types::FilterField::buildWeightsImpl
             total += n;
         }
 
-        Info<< "Weight neighbours: min=" << limits.min()
+        Pout<< "Weight neighbours: min=" << limits.min()
             << " avg=" << (total / addressing_.size())
             << " max=" << limits.max() << endl;
     }
@@ -279,7 +279,7 @@ void Foam::PatchFunction1Types::FilterField::reset
 
     if (debug)
     {
-        Info<< "Apply " << RBF_typeNames_[interp] << " filter,"
+        Pout<< "Apply " << RBF_typeNames_[interp] << " filter,"
             << " radius=" << radius << nl
             << "Create tree..." << endl;
     }
@@ -325,15 +325,9 @@ void Foam::PatchFunction1Types::FilterField::reset
 
     if (debug)
     {
-        Info<< "Apply " << RBF_typeNames_[interp] << " filter,";
-
-        if (relative)
-        {
-            Info<< " relative";
-        }
-
-        Info<< " radius=" << radius << endl;
-        Info<< "Create tree..." << endl;
+        Pout<< "Apply " << RBF_typeNames_[interp] << " filter,"
+            << (relative ? " relative" : "") << " radius=" << radius << nl
+            << "Create tree..." << endl;
     }
 
     autoPtr<indexedOctree<treeDataPoint>> treePtr
diff --git a/src/meshTools/coordSet/writers/common/coordSetWriter.C b/src/meshTools/coordSet/writers/common/coordSetWriter.C
index 8f9e3789e6bbd2946f3bf2bd4862eef4246a02e0..35e53056b0dd423faf98d2a34acd19dc00560d29 100644
--- a/src/meshTools/coordSet/writers/common/coordSetWriter.C
+++ b/src/meshTools/coordSet/writers/common/coordSetWriter.C
@@ -27,6 +27,7 @@ License
 
 #include "coordSet.H"
 #include "coordSetWriter.H"
+#include "fileFormats.H"
 #include "Time.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -78,46 +79,6 @@ Foam::word Foam::coordSetWriter::suffix
 }
 
 
-Foam::dictionary Foam::coordSetWriter::formatOptions
-(
-    const word& formatName,
-    std::initializer_list<const dictionary*> dicts
-)
-{
-    dictionary options;
-
-    // Default specification. Top-level and surface-specific
-    // - literal search only
-    for (const dictionary* dict : dicts)
-    {
-        if
-        (
-            dict
-         && (dict = dict->findDict("default", keyType::LITERAL)) != nullptr
-        )
-        {
-            options.merge(*dict);
-        }
-    }
-
-    // Format specification. Top-level and surface-specific
-    // - allow REGEX search
-    for (const dictionary* dict : dicts)
-    {
-        if
-        (
-            dict && !formatName.empty()
-         && (dict = dict->findDict(formatName)) != nullptr
-        )
-        {
-            options.merge(*dict);
-        }
-    }
-
-    return options;
-}
-
-
 Foam::dictionary Foam::coordSetWriter::formatOptions
 (
     const dictionary& dict,
@@ -125,13 +86,7 @@ Foam::dictionary Foam::coordSetWriter::formatOptions
     const word& entryName
 )
 {
-    return formatOptions
-    (
-        formatName,
-        {
-            dict.findDict(entryName, keyType::LITERAL)
-        }
-    );
+    return fileFormats::getFormatOptions(dict, formatName, entryName);
 }
 
 
@@ -143,14 +98,7 @@ Foam::dictionary Foam::coordSetWriter::formatOptions
     const word& entryName
 )
 {
-    return formatOptions
-    (
-        formatName,
-        {
-            dict.findDict(entryName, keyType::LITERAL),
-            setDict.findDict(entryName, keyType::LITERAL)
-        }
-    );
+    return fileFormats::getFormatOptions(dict, setDict, formatName, entryName);
 }
 
 
diff --git a/src/meshTools/coordSet/writers/common/coordSetWriter.H b/src/meshTools/coordSet/writers/common/coordSetWriter.H
index 14befa5a1ac6fa6e039a4d3912679047056d6f80..05b2f43170c950ff4e089eaaac82d2d0ddf845af 100644
--- a/src/meshTools/coordSet/writers/common/coordSetWriter.H
+++ b/src/meshTools/coordSet/writers/common/coordSetWriter.H
@@ -274,16 +274,6 @@ protected:
         //- No copy assignment
         void operator=(const coordSetWriter&) = delete;
 
-        //- Extract and merge 'default' + formatName from
-        //- top-level and set-specific formatOptions dictionaries
-        //
-        //  \returns dictionary of merged formatOptions
-        static dictionary formatOptions
-        (
-            const word& formatName,
-            std::initializer_list<const dictionary*> dicts
-        );
-
 
 public:
 
@@ -314,10 +304,7 @@ public:
 
     // Helpers
 
-        //- Find "formatOptions" in a top-level dictionary.
-        //- Extract and merge 'default' + formatName values.
-        //
-        //  \returns dictionary of merged formatOptions
+        //- Same as fileFormats::getFormatOptions
         static dictionary formatOptions
         (
             const dictionary& dict,
@@ -325,11 +312,7 @@ public:
             const word& entryName = "formatOptions"
         );
 
-        //- Find "formatOptions" in a top-level dictionary,
-        //- and in a set-specific dictionary.
-        //- Extract and merge 'default' + formatName values.
-        //
-        //  \returns dictionary of merged formatOptions
+        //- Same as fileFormats::getFormatOptions
         static dictionary formatOptions
         (
             const dictionary& dict,
diff --git a/src/meshTools/triSurface/triSurfaceTools/pointToPointPlanarInterpolation.H b/src/meshTools/triSurface/triSurfaceTools/pointToPointPlanarInterpolation.H
index 93a297d0c6b4b1c8d3e42ea9de7f8aacfeac8a60..dd7da0d64e5411b35116bc1a5def546d9bda7950 100644
--- a/src/meshTools/triSurface/triSurfaceTools/pointToPointPlanarInterpolation.H
+++ b/src/meshTools/triSurface/triSurfaceTools/pointToPointPlanarInterpolation.H
@@ -178,6 +178,12 @@ public:
             return nPoints_;
         }
 
+        //- Number of target points
+        label targetSize() const noexcept
+        {
+            return nearestVertex_.size();
+        }
+
         //- Interpolation addressing to face centres of underlying patch
         const List<FixedList<label, 3>>& nearestVertex() const noexcept
         {
diff --git a/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.C b/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.C
index 14030e120504f36c7aaf203750362535106e7a50..4e28be30f75fa566c5883760e6e405412d668fba 100644
--- a/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.C
+++ b/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.C
@@ -150,13 +150,26 @@ Foam::boundaryDataSurfaceReader::boundaryDataSurfaceReader
     const word& pointsName
 )
 :
-    surfaceReader(fName),
+    boundaryDataSurfaceReader(fName, dictionary(), pointsName)
+{}
+
+
+Foam::boundaryDataSurfaceReader::boundaryDataSurfaceReader
+(
+    const fileName& fName,
+    const dictionary& options,
+    const word& pointsName
+)
+:
+    surfaceReader(fName, options),
     baseDir_(fName.path()),
     pointsName_(pointsName),
     timeValues_(),
     fieldNames_(),
     surfPtr_(nullptr)
 {
+    options.readIfPresent("points", pointsName_);
+
     baseDir_.toAbsolute();
     debug = 1;
     DebugInFunction << endl;
diff --git a/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.H b/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.H
index 76821c298d8ecf7b8a9bd650770d3ba95c71cbdf..102f2ccc25a35ebd3227c3c7c46115c89825c1ff 100644
--- a/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.H
+++ b/src/surfMesh/readers/boundary/boundaryDataSurfaceReader.H
@@ -37,8 +37,25 @@ Description
     // Values
     <case>/constant/region0/"boundaryData"/patchName/TIME/field
 
+    \verbatim
+    readOptions
+    {
+        boundaryData
+        {
+            points      points;
+        }
+    }
+    \endverbatim
+
+    Format options for boundaryData:
+    \table
+        Property | Description                              | Required | Default
+        points   | Name of the "points" file                | no  | points
+    \endtable
+
 SourceFiles
     boundaryDataSurfaceReader.C
+    boundaryDataSurfaceReaderTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -113,6 +130,14 @@ public:
             const word& pointsName = "points"
         );
 
+        //- Construct from fileName with reader options
+        boundaryDataSurfaceReader
+        (
+            const fileName& fName,
+            const dictionary& options,
+            const word& pointsName = "points"
+        );
+
 
     //- Destructor
     virtual ~boundaryDataSurfaceReader() = default;
diff --git a/src/surfMesh/readers/common/surfaceReader.C b/src/surfMesh/readers/common/surfaceReader.C
index e9251975b171e998888b163831e4e732e1eb572d..cd3dd74d6f65b61d0e457c2d076845d2c490f90a 100644
--- a/src/surfMesh/readers/common/surfaceReader.C
+++ b/src/surfMesh/readers/common/surfaceReader.C
@@ -26,6 +26,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "surfaceReader.H"
+#include "fileFormats.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -36,21 +37,48 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::dictionary Foam::surfaceReader::formatOptions
+(
+    const dictionary& dict,
+    const word& formatName,
+    const word& entryName
+)
+{
+    return fileFormats::getFormatOptions(dict, formatName, entryName);
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::surfaceReader::surfaceReader(const fileName& fName)
+Foam::surfaceReader::surfaceReader
+(
+    const fileName& fName
+)
 :
     fileName_(fName)
 {}
 
 
+Foam::surfaceReader::surfaceReader
+(
+    const fileName& fName,
+    const dictionary& options
+)
+:
+    surfaceReader(fName)
+{}
+
+
 // * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * * //
 
 Foam::autoPtr<Foam::surfaceReader>
 Foam::surfaceReader::New
 (
     const word& readerType,
-    const fileName& fName
+    const fileName& fName,
+    const dictionary& options
 )
 {
     auto* ctorPtr = fileNameConstructorTable(readerType);
@@ -65,7 +93,7 @@ Foam::surfaceReader::New
         ) << exit(FatalError);
     }
 
-    return autoPtr<surfaceReader>(ctorPtr(fName));
+    return autoPtr<surfaceReader>(ctorPtr(fName, options));
 }
 
 
diff --git a/src/surfMesh/readers/common/surfaceReader.H b/src/surfMesh/readers/common/surfaceReader.H
index bd640289a49c096dc51b361a063e4671ca1939c7..99f79efddcbfe75d8555d8ef4bd6ef797d566f5d 100644
--- a/src/surfMesh/readers/common/surfaceReader.H
+++ b/src/surfMesh/readers/common/surfaceReader.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2015 OpenCFD Ltd.
+    Copyright (C) 2015-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,6 +29,24 @@ Class
 Description
     Abstract base class for surface readers with fields.
 
+    Some readers support different input options, these are typically
+    specified as 'readOptions' in the containing dictionary.
+
+    \verbatim
+    readOptions
+    {
+        default
+        {
+            verbose     false;
+        }
+
+        ensight
+        {
+            masterOnly  false;
+        }
+    }
+    \endverbatim
+
 SourceFiles
     surfaceReader.C
 
@@ -73,9 +91,21 @@ public:
             surfaceReader,
             fileName,
             (
-                const fileName& fName
+                const fileName& fName,
+                const dictionary& options
             ),
-            (fName)
+            (fName, options)
+        );
+
+
+    // Helpers
+
+        //- Same as fileFormats::getFormatOptions
+        static dictionary formatOptions
+        (
+            const dictionary& dict,
+            const word& formatName,
+            const word& entryName = "formatOptions"
         );
 
 
@@ -85,7 +115,8 @@ public:
         static autoPtr<surfaceReader> New
         (
             const word& readType,
-            const fileName& fName
+            const fileName& fName,
+            const dictionary& options = dictionary()
         );
 
 
@@ -94,6 +125,9 @@ public:
         //- Construct from fileName
         explicit surfaceReader(const fileName& fName);
 
+        //- Construct from fileName and specified options
+        surfaceReader(const fileName& fName, const dictionary& options);
+
 
     //- Destructor
     virtual ~surfaceReader() = default;
diff --git a/src/surfMesh/readers/ensight/ensightSurfaceReader.C b/src/surfMesh/readers/ensight/ensightSurfaceReader.C
index d4d7f1f2b86ababcec19ec2853f49bc8f3c36320..ab72a87efceaae0ccabad9ea3dbd8de7c6115cb2 100644
--- a/src/surfMesh/readers/ensight/ensightSurfaceReader.C
+++ b/src/surfMesh/readers/ensight/ensightSurfaceReader.C
@@ -337,9 +337,18 @@ void Foam::ensightSurfaceReader::readCase(ISstream& is)
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::ensightSurfaceReader::ensightSurfaceReader(const fileName& fName)
+Foam::ensightSurfaceReader::ensightSurfaceReader
+(
+    const fileName& fName,
+    const dictionary& options
+)
 :
-    surfaceReader(fName),
+    surfaceReader(fName, options),
+    masterOnly_
+    (
+        Pstream::parRun()
+     && options.getOrDefault("masterOnly", false)
+    ),
     readFormat_(IOstreamOption::ASCII),  // Placeholder value
     baseDir_(fName.path()),
     meshFileName_(),
@@ -351,24 +360,46 @@ Foam::ensightSurfaceReader::ensightSurfaceReader(const fileName& fName)
     timeValues_(),
     surfPtr_(nullptr)
 {
-    IFstream is(fName);
-    readCase(is);
+    if (options.getOrDefault("debug", false))
+    {
+        debug |= 1;
+    }
+
+    if (!masterOnly_ || UPstream::master(UPstream::worldComm))
+    {
+        IFstream is(fName);
+        readCase(is);
+    }
+
+    if (masterOnly_ && Pstream::parRun())
+    {
+        Pstream::broadcasts
+        (
+            UPstream::worldComm,
+            meshFileName_,
+            fieldNames_,
+            fieldFileNames_,
+            nTimeSteps_,
+            timeStartIndex_,
+            timeIncrement_,
+            timeValues_
+        );
+    }
 }
 
 
 // * * * * * * * * * * * * * Public Member Functions   * * * * * * * * * * * //
 
-const Foam::meshedSurface& Foam::ensightSurfaceReader::geometry
+Foam::meshedSurface Foam::ensightSurfaceReader::readGeometry
 (
-    const label timeIndex
+    const fileName& geometryFile
 )
 {
     DebugInFunction << endl;
 
-    if (!surfPtr_)
     {
         // Auto-detect ascii/binary format
-        ensightReadFile is(baseDir_/replaceMask(meshFileName_, timeIndex));
+        ensightReadFile is(geometryFile);
 
         // Format detected from the geometry
         readFormat_ = is.format();
@@ -573,6 +604,15 @@ const Foam::meshedSurface& Foam::ensightSurfaceReader::geometry
             }
         }
 
+        // From 1-based Ensight addressing to 0-based OF addressing
+        for (face& f : dynFaces)
+        {
+            for (label& fp : f)
+            {
+                --fp;
+            }
+        }
+
         faceTypeInfo_.transfer(faceTypeInfo);
         faceList faces(std::move(dynFaces));
 
@@ -580,16 +620,35 @@ const Foam::meshedSurface& Foam::ensightSurfaceReader::geometry
             << "read nFaces: " << faces.size() << nl
             << "file schema: " << faceTypeInfo_ << nl;
 
-        // Convert from 1-based Ensight addressing to 0-based OF addressing
-        for (face& f : faces)
+        return meshedSurface(std::move(points), std::move(faces));
+    }
+}
+
+
+const Foam::meshedSurface& Foam::ensightSurfaceReader::geometry
+(
+    const label timeIndex
+)
+{
+    DebugInFunction << endl;
+
+    if (!surfPtr_)
+    {
+        surfPtr_.reset(new meshedSurface);
+        auto& surf = *surfPtr_;
+
+        fileName geomFile(baseDir_/replaceMask(meshFileName_, timeIndex));
+
+        if (!masterOnly_ || UPstream::master(UPstream::worldComm))
         {
-            for (label& fp : f)
-            {
-                --fp;
-            }
+            surf = readGeometry(geomFile);
         }
 
-        surfPtr_.reset(new meshedSurface(std::move(points), std::move(faces)));
+        if (masterOnly_ && Pstream::parRun())
+        {
+            // Note: don't need faceTypeInfo_ on (non-reading) ranks
+            Pstream::broadcast(surf, UPstream::worldComm);
+        }
     }
 
     return *surfPtr_;
diff --git a/src/surfMesh/readers/ensight/ensightSurfaceReader.H b/src/surfMesh/readers/ensight/ensightSurfaceReader.H
index bf72aed3da5fedf8c75499916696797d7ba5d0ef..b364d4f15f02bdb04ab09e3322dfa8de031198c9 100644
--- a/src/surfMesh/readers/ensight/ensightSurfaceReader.H
+++ b/src/surfMesh/readers/ensight/ensightSurfaceReader.H
@@ -29,6 +29,24 @@ Class
 Description
     Ensight format surface reader
 
+    \verbatim
+    readOptions
+    {
+        ensight
+        {
+            debug       false;
+            masterOnly  false;
+        }
+    }
+    \endverbatim
+
+    Format options for ensight:
+    \table
+        Property | Description                              | Required | Default
+        debug    | Add debug flag                           | no  | false
+        masterOnly | Read files on master and broadcast values | no  | true
+    \endtable
+
 SourceFiles
     ensightSurfaceReader.C
     ensightSurfaceReaderTemplates.C
@@ -76,6 +94,9 @@ protected:
 
     // Protected Data
 
+        //- Read on master and broadcast (in parallel)
+        bool masterOnly_;
+
         //- Format flag
         IOstreamOption::streamFormat readFormat_;
 
@@ -135,6 +156,9 @@ protected:
         //- Read the case file
         void readCase(ISstream& is);
 
+        //- Read and return surface geometry. Updates faceTypeInfo_
+        meshedSurface readGeometry(const fileName& geometryFile);
+
         //- Helper function to return Type after skipping n tokens
         template<class Type>
         void readFromLine(const label nSkip, Istream& is, Type& value) const;
@@ -148,6 +172,14 @@ protected:
             Type& value
         ) const;
 
+        //- Helper function to return a field
+        template<class Type>
+        tmp<Field<Type>> readField
+        (
+            const fileName& dataFile,
+            const word& fieldName
+        ) const;
+
         //- Helper function to return a field
         template<class Type>
         tmp<Field<Type>> readField
@@ -165,8 +197,12 @@ public:
 
     // Constructors
 
-        //- Construct from fileName
-        explicit ensightSurfaceReader(const fileName& fName);
+        //- Construct from fileName, with reader options
+        explicit ensightSurfaceReader
+        (
+            const fileName& fName,
+            const dictionary& options = dictionary()
+        );
 
 
     //- Destructor
diff --git a/src/surfMesh/readers/ensight/ensightSurfaceReaderTemplates.C b/src/surfMesh/readers/ensight/ensightSurfaceReaderTemplates.C
index ede7b84b6f0b23d5782376c45f5e268b645b849a..28cfbb1698c4f9a55c53a7740bea29b2e8c190d2 100644
--- a/src/surfMesh/readers/ensight/ensightSurfaceReaderTemplates.C
+++ b/src/surfMesh/readers/ensight/ensightSurfaceReaderTemplates.C
@@ -61,106 +61,142 @@ void Foam::ensightSurfaceReader::readFromLine
 template<class Type>
 Foam::tmp<Foam::Field<Type>> Foam::ensightSurfaceReader::readField
 (
-    const label timeIndex,
-    const label fieldIndex
+    const fileName& dataFile,
+    const word& fieldName
 ) const
 {
-    DebugInFunction << endl;
-
-    const word& fieldName = fieldNames_[fieldIndex];
-    const label fileIndex = timeStartIndex_ + timeIndex*timeIncrement_;
-
-    // Use previously detected ascii/binary format
-    ensightReadFile is
-    (
-        baseDir_/replaceMask(fieldFileNames_[fieldIndex], fileIndex),
-        readFormat_
-    );
+    auto tfield = tmp<Field<Type>>::New(surfPtr_->nFaces(), Zero);
+    auto& field = tfield.ref();
 
-    if (!is.good())
+    if (!masterOnly_ || UPstream::master(UPstream::worldComm))
     {
-        FatalErrorInFunction
-            << "Cannot read file " << is.name()
-            << " for field " << fieldName
-            << exit(FatalError);
-    }
-
-    // Check that data type is as expected
-    // (assumes OpenFOAM generated the data set)
-    string primitiveType;
-    is.read(primitiveType);
+        // Use previously detected ascii/binary format
+        ensightReadFile is(dataFile, readFormat_);
 
-    DebugInfo << "primitiveType: " << primitiveType << endl;
-
-    if
-    (
-        primitiveType != ensightPTraits<Type>::typeName
-     && primitiveType != pTraits<Type>::typeName
-    )
-    {
-        IOWarningInFunction(is)
-            << "Expected <" << ensightPTraits<Type>::typeName
-            << "> values for <" << pTraits<Type>::typeName
-            << "> but found " << primitiveType << nl
-            << "    This may be okay, but could indicate an error" << nl << nl;
-    }
+        if (!is.good())
+        {
+            FatalErrorInFunction
+                << "Cannot read file " << is.name()
+                << " for field " << fieldName
+                << exit(FatalError);
+        }
 
-    auto tfield = tmp<Field<Type>>::New(surfPtr_->nFaces(), Zero);
-    auto& field = tfield.ref();
+        // Check that data type is as expected
+        // (assumes OpenFOAM generated the data set)
+        string primitiveType;
+        is.read(primitiveType);
 
-    string strValue;
-    label iValue;
+        DebugInfo << "primitiveType: " << primitiveType << endl;
 
-    // Read header info: part index, e.g. part 1
-    is.read(strValue);
-    is.read(iValue);
+        if
+        (
+            primitiveType != ensightPTraits<Type>::typeName
+         && primitiveType != pTraits<Type>::typeName
+        )
+        {
+            IOWarningInFunction(is)
+                << "Expected <" << ensightPTraits<Type>::typeName
+                << "> values for <" << pTraits<Type>::typeName
+                << "> but found " << primitiveType << nl
+                << "    This may be okay, but could indicate an error"
+                << nl << nl;
+        }
 
-    label begFace = 0;
+        string strValue;
+        label iValue;
 
-    // Loop through different element types when reading the field values
-    for (const faceInfoTuple& facesInfo : faceTypeInfo_)
-    {
-        // [faceType, faceCount]
-        const label endFace = begFace + facesInfo.second();
+        // Read header info: part index, e.g. part 1
+        is.read(strValue);
+        is.read(iValue);
 
-        DebugInfo
-            << "Reading <" << pTraits<Type>::typeName << "> face type "
-            << ensightFaces::elemNames[facesInfo.first()]
-            << " data:" << facesInfo.second() << endl;
+        label begFace = 0;
 
-        if (begFace < endFace)
+        // Loop through different element types when reading the field values
+        for (const faceInfoTuple& facesInfo : faceTypeInfo_)
         {
-            // The element type, optionally with 'undef'
-            is.read(strValue);
-
-            if (strValue.contains("undef"))
-            {
-                // Skip undef entry
-                scalar value;
-                is.read(value);
-            }
+            // [faceType, faceCount]
+            const label endFace = begFace + facesInfo.second();
 
-            // Ensight fields are written component-wise
-            // (can be in different order than OpenFOAM uses)
+            DebugInfo
+                << "Reading <" << pTraits<Type>::typeName << "> face type "
+                << ensightFaces::elemNames[facesInfo.first()]
+                << " data:" << facesInfo.second() << endl;
 
-            for (direction d = 0; d < pTraits<Type>::nComponents; ++d)
+            if (begFace < endFace)
             {
-                const direction cmpt = ensightPTraits<Type>::componentOrder[d];
+                // The element type, optionally with 'undef'
+                is.read(strValue);
 
-                for (label facei = begFace; facei < endFace; ++facei)
+                if (strValue.contains("undef"))
                 {
+                    // Skip undef entry
                     scalar value;
                     is.read(value);
-                    setComponent(field[facei], cmpt) = value;
                 }
-            }
 
-            begFace = endFace;
+                // Ensight fields are written component-wise
+                // (can be in different order than OpenFOAM uses)
+
+                for (direction d = 0; d < pTraits<Type>::nComponents; ++d)
+                {
+                    const direction cmpt =
+                        ensightPTraits<Type>::componentOrder[d];
+
+                    for (label facei = begFace; facei < endFace; ++facei)
+                    {
+                        scalar value;
+                        is.read(value);
+                        setComponent(field[facei], cmpt) = value;
+                    }
+                }
+
+                begFace = endFace;
+            }
         }
     }
 
+    if (masterOnly_ && Pstream::parRun())
+    {
+        Pstream::broadcast(field, UPstream::worldComm);
+    }
+
     return tfield;
 }
 
 
+template<class Type>
+Foam::tmp<Foam::Field<Type>> Foam::ensightSurfaceReader::readField
+(
+    const label timeIndex,
+    const label fieldIndex
+) const
+{
+    if (fieldIndex < 0 || fieldIndex >= fieldNames_.size())
+    {
+        FatalErrorInFunction
+            << "Invalid timeIndex:" << timeIndex
+            << " should be in range [0.." << fieldNames_.size() << ')' << nl
+            << "Possibly used incorrect field lookup name. Known field names: "
+            << flatOutput(fieldNames_) << nl
+            << exit(FatalError);
+    }
+
+    const word& fieldName = fieldNames_[fieldIndex];
+    const label fileIndex = timeStartIndex_ + timeIndex*timeIncrement_;
+
+    const fileName dataFile
+    (
+        baseDir_/replaceMask(fieldFileNames_[fieldIndex], fileIndex)
+    );
+
+    if (debug)
+    {
+        Pout<< "Read <" << pTraits<Type>::typeName << "> field, file="
+            << dataFile << endl;
+    }
+
+    return readField<Type>(dataFile, fieldName);
+}
+
+
 // ************************************************************************* //
diff --git a/src/surfMesh/writers/common/surfaceWriter.C b/src/surfMesh/writers/common/surfaceWriter.C
index 83fce91cd0db2b839a7791373e77fb2d978ae394..eaf0ad1f4b9264adb4ad39516036e0606cfbfd92 100644
--- a/src/surfMesh/writers/common/surfaceWriter.C
+++ b/src/surfMesh/writers/common/surfaceWriter.C
@@ -29,6 +29,7 @@ License
 #include "proxySurfaceWriter.H"
 #include "MeshedSurfaceProxy.H"
 
+#include "fileFormats.H"
 #include "Time.H"
 #include "coordinateRotation.H"
 #include "transformField.H"
@@ -59,46 +60,6 @@ bool Foam::surfaceWriter::supportedType(const word& writeType)
 }
 
 
-Foam::dictionary Foam::surfaceWriter::formatOptions
-(
-    const word& formatName,
-    std::initializer_list<const dictionary*> dicts
-)
-{
-    dictionary options;
-
-    // Default specification. Top-level and surface-specific
-    // - literal search only
-    for (const dictionary* dict : dicts)
-    {
-        if
-        (
-            dict
-         && (dict = dict->findDict("default", keyType::LITERAL)) != nullptr
-        )
-        {
-            options.merge(*dict);
-        }
-    }
-
-    // Format specification. Top-level and surface-specific
-    // - allow REGEX search
-    for (const dictionary* dict : dicts)
-    {
-        if
-        (
-            dict && !formatName.empty()
-         && (dict = dict->findDict(formatName)) != nullptr
-        )
-        {
-            options.merge(*dict);
-        }
-    }
-
-    return options;
-}
-
-
 Foam::dictionary Foam::surfaceWriter::formatOptions
 (
     const dictionary& dict,
@@ -106,13 +67,7 @@ Foam::dictionary Foam::surfaceWriter::formatOptions
     const word& entryName
 )
 {
-    return formatOptions
-    (
-        formatName,
-        {
-            dict.findDict(entryName, keyType::LITERAL)
-        }
-    );
+    return fileFormats::getFormatOptions(dict, formatName, entryName);
 }
 
 
@@ -124,14 +79,7 @@ Foam::dictionary Foam::surfaceWriter::formatOptions
     const word& entryName
 )
 {
-    return formatOptions
-    (
-        formatName,
-        {
-            dict.findDict(entryName, keyType::LITERAL),
-            surfDict.findDict(entryName, keyType::LITERAL)
-        }
-    );
+    return fileFormats::getFormatOptions(dict, surfDict, formatName, entryName);
 }
 
 
diff --git a/src/surfMesh/writers/common/surfaceWriter.H b/src/surfMesh/writers/common/surfaceWriter.H
index aa2eadbe842daa3e291f7fdce4493b4656f3e152..ea3692cd41c384652dcfaa471d878e9917348d06 100644
--- a/src/surfMesh/writers/common/surfaceWriter.H
+++ b/src/surfMesh/writers/common/surfaceWriter.H
@@ -272,18 +272,6 @@ protected:
             return fileName::null;
         }
 
-
-        //- Extract and merge 'default' + formatName from
-        //- top-level and surface-specific formatOptions dictionaries
-        //
-        //  \returns dictionary of merged formatOptions
-        static dictionary formatOptions
-        (
-            const word& formatName,
-            std::initializer_list<const dictionary*> dicts
-        );
-
-
 public:
 
     // Public Data
@@ -320,10 +308,7 @@ public:
 
     // Helpers
 
-        //- Find "formatOptions" in a top-level dictionary.
-        //- Extract and merge 'default' + formatName values.
-        //
-        //  \returns dictionary of merged formatOptions
+        //- Same as fileFormats::getFormatOptions
         static dictionary formatOptions
         (
             const dictionary& dict,
@@ -331,11 +316,7 @@ public:
             const word& entryName = "formatOptions"
         );
 
-        //- Find "formatOptions" in a top-level dictionary,
-        //- and in a set-specific dictionary.
-        //- Extract and merge 'default' + formatName values.
-        //
-        //  \returns dictionary of merged formatOptions
+        //- Same as fileFormats::getFormatOptions
         static dictionary formatOptions
         (
             const dictionary& dict,