diff --git a/applications/utilities/surface/surfaceFeatures/Make/files b/applications/utilities/surface/surfaceFeatures/Make/files
new file mode 100644
index 000000000..75ef45edf
--- /dev/null
+++ b/applications/utilities/surface/surfaceFeatures/Make/files
@@ -0,0 +1,3 @@
+surfaceFeatures.C
+
+EXE = $(FOAM_APPBIN)/surfaceFeatures
diff --git a/applications/utilities/surface/surfaceFeatures/Make/options b/applications/utilities/surface/surfaceFeatures/Make/options
new file mode 100644
index 000000000..e96fbb368
--- /dev/null
+++ b/applications/utilities/surface/surfaceFeatures/Make/options
@@ -0,0 +1,11 @@
+EXE_INC = \
+ -I$(LIB_SRC)/finiteVolume/lnInclude \
+ -I$(LIB_SRC)/meshTools/lnInclude \
+ -I$(LIB_SRC)/triSurface/lnInclude \
+ -I$(LIB_SRC)/surfMesh/lnInclude \
+ -I$(LIB_SRC)/sampling/lnInclude
+
+EXE_LIBS = \
+ -lmeshTools \
+ -ltriSurface \
+ -lsampling
diff --git a/applications/utilities/surface/surfaceFeatures/surfaceFeatures.C b/applications/utilities/surface/surfaceFeatures/surfaceFeatures.C
new file mode 100644
index 000000000..b160331ab
--- /dev/null
+++ b/applications/utilities/surface/surfaceFeatures/surfaceFeatures.C
@@ -0,0 +1,729 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | Copyright (C) 2018 OpenFOAM Foundation
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "Time.H"
+#include "triSurfaceMesh.H"
+#include "featureEdgeMesh.H"
+#include "extendedFeatureEdgeMesh.H"
+#include "surfaceFeatures.H"
+#include "triSurfaceFields.H"
+#include "vtkSurfaceWriter.H"
+#include "IOdictionary.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+ autoPtr extractFromFile
+ (
+ const fileName& featureEdgeFile,
+ const triSurface& surf,
+ const Switch& geometricTestOnly
+ )
+ {
+ edgeMesh eMesh(featureEdgeFile);
+
+ // Sometimes duplicate edges are present. Remove them.
+ eMesh.mergeEdges();
+
+ Info<< nl << "Reading existing feature edges from file "
+ << featureEdgeFile << nl
+ << "Selecting edges purely based on geometric tests: "
+ << geometricTestOnly.asText() << endl;
+
+ return autoPtr
+ (
+ new surfaceFeatures
+ (
+ surf,
+ eMesh.points(),
+ eMesh.edges(),
+ 1e-6,
+ geometricTestOnly
+ )
+ );
+ }
+
+
+ autoPtr extractFromSurface
+ (
+ const triSurface& surf,
+ const Switch& geometricTestOnly,
+ const scalar includedAngle
+ )
+ {
+ Info<< nl
+ << "Constructing feature set from included angle "
+ << includedAngle << nl
+ << "Selecting edges purely based on geometric tests: "
+ << geometricTestOnly.asText() << endl;
+
+ return autoPtr
+ (
+ new surfaceFeatures
+ (
+ surf,
+ includedAngle,
+ 0,
+ 0,
+ geometricTestOnly
+ )
+ );
+ }
+
+
+ autoPtr surfaceFeatureSet
+ (
+ const fileName& surfaceFileName,
+ const triSurface& surf,
+ const dictionary& dict,
+ const scalar includedAngle
+ )
+ {
+ const Switch geometricTestOnly = dict.lookupOrDefault
+ (
+ "geometricTestOnly",
+ "no"
+ );
+
+ if (dict.found("files"))
+ {
+ HashTable fileNames(dict.lookup("files"));
+
+ if (fileNames.found(surfaceFileName))
+ {
+ return extractFromFile
+ (
+ fileNames[surfaceFileName],
+ surf,
+ geometricTestOnly
+ );
+ }
+ else
+ {
+ return extractFromSurface
+ (
+ surf,
+ geometricTestOnly,
+ includedAngle
+ );
+ }
+ }
+ else
+ {
+ return extractFromSurface
+ (
+ surf,
+ geometricTestOnly,
+ includedAngle
+ );
+ }
+ }
+
+
+ void extractFeatures
+ (
+ const fileName& surfaceFileName,
+ const Time& runTime,
+ const dictionary& dict
+ )
+ {
+ const fileName sFeatFileName = surfaceFileName.lessExt().name();
+
+ Info<< "Surface : " << surfaceFileName << nl << endl;
+
+ const Switch writeVTK =
+ dict.lookupOrDefault("writeVTK", "off");
+ const Switch writeObj =
+ dict.lookupOrDefault("writeObj", "off");
+ const Switch verboseObj =
+ dict.lookupOrDefault("verboseObj", "off");
+
+ const Switch curvature =
+ dict.lookupOrDefault("curvature", "off");
+ const Switch featureProximity =
+ dict.lookupOrDefault("featureProximity", "off");
+ const Switch closeness =
+ dict.lookupOrDefault("closeness", "off");
+
+
+ Info<< nl << "Feature line extraction is only valid on closed manifold "
+ << "surfaces." << endl;
+
+ // Read
+ // ~~~~
+
+ triSurface surf(runTime.constantPath()/"triSurface"/surfaceFileName);
+
+ Info<< "Statistics:" << endl;
+ surf.writeStats(Info);
+ Info<< endl;
+
+ const faceList faces(surf.faces());
+
+ // Either construct features from surface & featureAngle or read set.
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ const scalar includedAngle = readScalar(dict.lookup("includedAngle"));
+
+ autoPtr set
+ (
+ surfaceFeatureSet
+ (
+ surfaceFileName,
+ surf,
+ dict,
+ includedAngle
+ )
+ );
+
+ // Trim set
+ // ~~~~~~~~
+
+ if (dict.isDict("trimFeatures"))
+ {
+ dictionary trimDict = dict.subDict("trimFeatures");
+
+ scalar minLen =
+ trimDict.lookupOrAddDefault("minLen", -great);
+
+ label minElem = trimDict.lookupOrAddDefault