From f005b36b6633133839609a4e2b0d51ac8e6b4df7 Mon Sep 17 00:00:00 2001 From: Mark Olesen <Mark.Olesen@esi-group.com> Date: Wed, 26 Feb 2020 10:18:00 +0100 Subject: [PATCH] ENH: code cleanup in MeshedSurface (#1600) - removed swapZones method (unused, potentially fragile) - add subsetMesh by name BUG: incorrect zone assignment in MeshedSurface::subsetMesh - used the new (zero-sized) zone when determining the old zone ending instead of the original zone bounds. --- src/surfMesh/MeshedSurface/MeshedSurface.C | 200 ++++++++++-------- src/surfMesh/MeshedSurface/MeshedSurface.H | 28 +-- .../MeshedSurface/MeshedSurfaceZones.C | 107 ++++++---- .../UnsortedMeshedSurface.C | 12 +- .../UnsortedMeshedSurface.H | 3 +- .../surfaceFormats/surfaceFormatsCore.C | 19 ++ .../surfaceFormats/surfaceFormatsCore.H | 12 ++ 7 files changed, 229 insertions(+), 152 deletions(-) diff --git a/src/surfMesh/MeshedSurface/MeshedSurface.C b/src/surfMesh/MeshedSurface/MeshedSurface.C index f9055b3b017..3ab8d778844 100644 --- a/src/surfMesh/MeshedSurface/MeshedSurface.C +++ b/src/surfMesh/MeshedSurface/MeshedSurface.C @@ -178,7 +178,7 @@ Foam::MeshedSurface<Face>::MeshedSurface ) : ParentType(surf.surfFaces(), surf.points()), - zones_(surf.surfZones()) + zones_(surf.zones_) {} @@ -188,7 +188,7 @@ Foam::MeshedSurface<Face>::MeshedSurface const UnsortedMeshedSurface<Face>& surf ) : - ParentType(List<Face>(), surf.points()) + ParentType(List<Face>(), surf.points()) // Copy points only { labelList faceMap; this->storedZones() = surf.sortedZones(faceMap); @@ -230,7 +230,7 @@ Foam::MeshedSurface<Face>::MeshedSurface template<class Face> - Foam::MeshedSurface<Face>::MeshedSurface +Foam::MeshedSurface<Face>::MeshedSurface ( const pointField& pointLst, const UList<Face>& faceLst, @@ -505,10 +505,10 @@ Foam::MeshedSurface<Face>::~MeshedSurface() template<class Face> void Foam::MeshedSurface<Face>::remapFaces ( - const labelUList& faceMap + const labelUList& faceMapNewToOld ) { - if (faceMap.empty()) + if (faceMapNewToOld.empty()) { return; } @@ -517,23 +517,24 @@ void Foam::MeshedSurface<Face>::remapFaces if (zones.size() == 1) { - zones[0].size() = faceMap.size(); // Single zone case is trivial + // Single zone case is trivial + zones[0].size() = faceMapNewToOld.size(); return; } // Recalculate the zone start/size label newFacei = 0; - label origEndI = 0; + label origEndi = 0; for (surfZone& zone : zones) { // Adjust zone start zone.start() = newFacei; - origEndI += zone.size(); + origEndi += zone.size(); - for (label facei = newFacei; facei < faceMap.size(); ++facei) + for (label facei = newFacei; facei < faceMapNewToOld.size(); ++facei) { - if (faceMap[facei] < origEndI) + if (faceMapNewToOld[facei] < origEndi) { ++newFacei; } @@ -588,7 +589,7 @@ void Foam::MeshedSurface<Face>::scalePoints(const scalar scaleFactor) // Adapt for new point position ParentType::movePoints(newPoints); - storedPoints() = newPoints; + storedPoints() = std::move(newPoints); } } @@ -635,19 +636,19 @@ bool Foam::MeshedSurface<Face>::stitchFaces List<Face>& faceLst = this->storedFaces(); - List<label> faceMap(faceLst.size()); + labelList faceMap(faceLst.size(), -1); // Reset the point labels to the unique points array label newFacei = 0; forAll(faceLst, facei) { Face& f = faceLst[facei]; - forAll(f, fp) + for (label& vert : f) { - f[fp] = pointMap[f[fp]]; + vert = pointMap[vert]; } - // for extra safety: collapse face as well + // For extra safety: collapse face as well if (f.collapse() >= 3) { if (newFacei != facei) @@ -674,8 +675,9 @@ bool Foam::MeshedSurface<Face>::stitchFaces << "Removed " << faceLst.size() - newFacei << " faces" << endl; } - faceLst.setSize(newFacei); - faceMap.setSize(newFacei); + faceMap.resize(newFacei); + faceLst.resize(newFacei); + remapFaces(faceMap); } faceMap.clear(); @@ -697,26 +699,27 @@ bool Foam::MeshedSurface<Face>::checkFaces bool changed = false; List<Face>& faceLst = this->storedFaces(); - List<label> faceMap(faceLst.size()); + labelList faceMap(faceLst.size()); label newFacei = 0; + const label maxPointi = this->points().size(); + // Detect badly labelled faces and mark degenerate faces - const label maxPointi = this->points().size() - 1; forAll(faceLst, facei) { Face& f = faceLst[facei]; - // avoid degenerate faces + // Avoid degenerate faces if (f.collapse() >= 3) { - forAll(f, fp) + for (const label vert : f) { - if (f[fp] < 0 || f[fp] > maxPointi) + if (vert < 0 || vert >= maxPointi) { FatalErrorInFunction << "face " << f << " uses point indices outside point range 0.." - << maxPointi + << (maxPointi-1) << exit(FatalError); } } @@ -726,7 +729,7 @@ bool Foam::MeshedSurface<Face>::checkFaces } else { - // mark as bad face + // Mark as bad face faceMap[facei] = -1; changed = true; @@ -745,7 +748,7 @@ bool Foam::MeshedSurface<Face>::checkFaces newFacei = 0; forAll(faceLst, facei) { - // skip already collapsed faces: + // Skip already collapsed faces if (faceMap[facei] < 0) { continue; @@ -753,16 +756,14 @@ bool Foam::MeshedSurface<Face>::checkFaces const Face& f = faceLst[facei]; - // duplicate face check + // Duplicate face check bool okay = true; const labelList& neighbours = fFaces[facei]; // Check if faceNeighbours use same points as this face. // Note: discards normal information - sides of baffle are merged. - forAll(neighbours, neighI) + for (const label neiFacei : neighbours) { - const label neiFacei = neighbours[neighI]; - if (neiFacei <= facei || faceMap[neiFacei] < 0) { // lower numbered faces already checked @@ -801,6 +802,9 @@ bool Foam::MeshedSurface<Face>::checkFaces } } + + // Until now, faceMap is an identity for good faces and -1 for bad faces + // Phase 1: pack // Done to keep numbering constant in phase 1 @@ -815,7 +819,7 @@ bool Foam::MeshedSurface<Face>::checkFaces << " illegal faces." << endl; } - // compress the face list + // Compress the face list newFacei = 0; forAll(faceLst, facei) { @@ -823,14 +827,16 @@ bool Foam::MeshedSurface<Face>::checkFaces { if (newFacei != facei) { - faceLst[newFacei] = faceLst[facei]; + faceLst[newFacei] = std::move(faceLst[facei]); } faceMap[newFacei] = facei; ++newFacei; } } - faceLst.setSize(newFacei); + faceMap.resize(newFacei); + faceLst.resize(newFacei); + remapFaces(faceMap); } faceMap.clear(); @@ -866,9 +872,9 @@ Foam::label Foam::MeshedSurface<Face>::nTriangles const List<Face>& faceLst = surfFaces(); // Count triangles needed - forAll(faceLst, facei) + for (const auto& f : faceLst) { - nTri += faceLst[facei].nTriangles(); + nTri += f.nTriangles(); } // Nothing to do @@ -881,8 +887,8 @@ Foam::label Foam::MeshedSurface<Face>::nTriangles } else if (notNull(faceMap)) { - // face map requested - faceMap.setSize(nTri); + // Face map requested + faceMap.resize(nTri); nTri = 0; forAll(faceLst, facei) @@ -894,7 +900,7 @@ Foam::label Foam::MeshedSurface<Face>::nTriangles } } - faceMap.setSize(nTri); + faceMap.resize(nTri); } return nTri; @@ -940,10 +946,10 @@ Foam::label Foam::MeshedSurface<Face>::triangulate label maxTri = 0; // the maximum number of triangles for any single face List<Face>& faceLst = this->storedFaces(); - // determine how many triangles will be needed - forAll(faceLst, facei) + // How many triangles will be needed + for (const auto& f : faceLst) { - const label n = faceLst[facei].nTriangles(); + const label n = f.nTriangles(); if (maxTri < n) { maxTri = n; @@ -951,7 +957,7 @@ Foam::label Foam::MeshedSurface<Face>::triangulate nTri += n; } - // nothing to do + // Nothing to do if (nTri <= faceLst.size()) { if (notNull(faceMapOut)) @@ -969,7 +975,7 @@ Foam::label Foam::MeshedSurface<Face>::triangulate { faceMap.transfer(faceMapOut); } - faceMap.setSize(nTri); + faceMap.resize(nTri); if (this->points().empty()) { @@ -1047,58 +1053,50 @@ Foam::MeshedSurface<Face> Foam::MeshedSurface<Face>::subsetMesh const pointField& locPoints = this->localPoints(); const List<Face>& locFaces = this->localFaces(); - // Fill pointMap, faceMap PatchTools::subsetMap(*this, include, pointMap, faceMap); - // Create compact coordinate list and forward mapping array - pointField newPoints(pointMap.size()); - labelList oldToNew(locPoints.size()); + // Subset of points (compact) + pointField newPoints(UIndirectList<point>(locPoints, pointMap)); + + // Inverse point mapping - same as ListOps invert() without checks + labelList oldToNew(locPoints.size(), -1); forAll(pointMap, pointi) { - newPoints[pointi] = locPoints[pointMap[pointi]]; oldToNew[pointMap[pointi]] = pointi; } - // create/copy a new zones list, each zone with zero size - surfZoneList newZones(this->surfZones()); - forAll(newZones, zoneI) - { - newZones[zoneI].size() = 0; - } + // Subset of faces + List<Face> newFaces(UIndirectList<Face>(locFaces, faceMap)); // Renumber face node labels - List<Face> newFaces(faceMap.size()); - forAll(faceMap, facei) + for (auto& f : newFaces) { - const label origFacei = faceMap[facei]; - newFaces[facei] = Face(locFaces[origFacei]); - - // Renumber labels for face - Face& f = newFaces[facei]; - forAll(f, fp) + for (label& vert : f) { - f[fp] = oldToNew[f[fp]]; + vert = oldToNew[vert]; } } oldToNew.clear(); - // recalculate the zones start/size + // Deep copy of zones, leave start/size intact!! + surfZoneList newZones(zones_); + + // Recalculate the zone start/size label newFacei = 0; - label origEndI = 0; + label origEndi = 0; - // adjust zone sizes - forAll(newZones, zoneI) + for (surfZone& zone : newZones) { - surfZone& zone = newZones[zoneI]; + // The old zone ending + origEndi += zone.size(); - // adjust zone start + // The new zone start zone.start() = newFacei; - origEndI += zone.size(); for (label facei = newFacei; facei < faceMap.size(); ++facei) { - if (faceMap[facei] < origEndI) + if (faceMap[facei] < origEndi) { ++newFacei; } @@ -1108,17 +1106,18 @@ Foam::MeshedSurface<Face> Foam::MeshedSurface<Face>::subsetMesh } } - // adjust zone size + // The new zone size zone.size() = newFacei - zone.start(); } - // Construct a sub-surface - return MeshedSurface<Face> - ( - std::move(newPoints), - std::move(newFaces), - std::move(newZones) - ); + + // Construct the sub-surface + MeshedSurface<Face> newSurf; + newSurf.storedFaces().transfer(newFaces); + newSurf.storedPoints().transfer(newPoints); + newSurf.storedZones().transfer(newZones); + + return newSurf; } @@ -1144,6 +1143,33 @@ Foam::MeshedSurface<Face> Foam::MeshedSurface<Face>::subsetMesh } +template<class Face> +Foam::MeshedSurface<Face> Foam::MeshedSurface<Face>::subsetMesh +( + const wordRes& includeNames, + const wordRes& excludeNames +) const +{ + bitSet include(this->size()); + + for + ( + const label zonei + : fileFormats::surfaceFormatsCore::getSelectedPatches + ( + zones_, + includeNames, + excludeNames + ) + ) + { + include.set(zones_[zonei].range()); + } + + return subsetMesh(include); +} + + template<class Face> void Foam::MeshedSurface<Face>::swap ( @@ -1206,7 +1232,8 @@ void Foam::MeshedSurface<Face>::transfer UnsortedMeshedSurface<Face>& surf ) { - clear(); + // Clear everything + this->clear(); labelList faceMap; surfZoneList zoneLst = surf.sortedZones(faceMap); @@ -1265,13 +1292,6 @@ void Foam::MeshedSurface<Face>::swapPoints(pointField& points) } -template<class Face> -void Foam::MeshedSurface<Face>::swapZones(surfZoneList& zones) -{ - this->storedZones().swap(zones); -} - - template<class Face> bool Foam::MeshedSurface<Face>::read(const fileName& name) { @@ -1318,7 +1338,13 @@ void Foam::MeshedSurface<Face>::write template<class Face> void Foam::MeshedSurface<Face>::operator=(const MeshedSurface<Face>& surf) { - clear(); + if (this == &surf) + { + return; // Self-assignment is a no-op + } + + // Clear everything + this->clear(); this->storedPoints() = surf.points(); this->storedFaces() = surf.surfFaces(); diff --git a/src/surfMesh/MeshedSurface/MeshedSurface.H b/src/surfMesh/MeshedSurface/MeshedSurface.H index 4d407384705..87834f17603 100644 --- a/src/surfMesh/MeshedSurface/MeshedSurface.H +++ b/src/surfMesh/MeshedSurface/MeshedSurface.H @@ -175,7 +175,7 @@ protected: ); //- Set new zones from faceMap - virtual void remapFaces(const labelUList& faceMap); + virtual void remapFaces(const labelUList& faceMapNewToOld); public: @@ -377,7 +377,7 @@ public: } //- Return const access to the faces - inline const List<Face>& surfFaces() const + const List<Face>& surfFaces() const { return static_cast<const List<Face>&>(*this); } @@ -391,19 +391,19 @@ public: } //- Face area vectors (normals) - inline const vectorField& Sf() const + const vectorField& Sf() const { return ParentType::faceAreas(); } //- Face area magnitudes - inline const scalarField& magSf() const + const scalarField& magSf() const { return ParentType::magFaceAreas(); } //- Face centres - inline const vectorField& Cf() const + const vectorField& Cf() const { return ParentType::faceCentres(); } @@ -482,7 +482,7 @@ public: // The faceMap is zero-sized when no triangulation was done. virtual label triangulate(List<label>& faceMap); - //- Return new surface. + //- Return a new surface subsetted on the selected faces. // // \param[in] include the faces to select // \param[out] pointMap from subsetMeshMap @@ -495,12 +495,19 @@ public: labelList& faceMap ) const; - //- Return new surface + //- Return a new surface subsetted on the selected faces. MeshedSurface subsetMesh(const bitSet& include) const; - //- Return new surface + //- Return a new surface subsetted on the selected faces. MeshedSurface subsetMesh(const labelHashSet& include) const; + //- Return a new surface subsetted on the selected zone names + MeshedSurface subsetMesh + ( + const wordRes& includeNames, + const wordRes& excludeNames = wordRes() + ) const; + //- Swap contents void swap(MeshedSurface<Face>& surf); @@ -516,15 +523,12 @@ public: //- Release (clear) geometry and return for reuse autoPtr<MeshedSurface<Face>> releaseGeom(); - //- Swap the stored faces + //- Swap the stored faces. Use with caution void swapFaces(List<Face>& faces); //- Swap the stored points void swapPoints(pointField& points); - //- Swap the stored zones - void swapZones(surfZoneList& zones); - // Read diff --git a/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C b/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C index bbc475c6d7f..0dfea99cf0e 100644 --- a/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C +++ b/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C @@ -36,22 +36,23 @@ void Foam::MeshedSurface<Face>::checkZones() { // extra safety, ensure we have at some zones // and they cover all the faces - fix start silently - surfZoneList& zones = this->storedZones(); - if (zones.size()) + + auto& zones = this->storedZones(); + + label count = 0; + for (surfZone& zn : zones) { - label count = 0; - forAll(zones, zoneI) - { - zones[zoneI].start() = count; - count += zones[zoneI].size(); - } + zn.start() = count; + count += zn.size(); + } + if (!zones.empty()) + { if (count < this->size()) { WarningInFunction << "more faces " << this->size() << " than zones " << count - << " ... extending final zone" - << endl; + << " ... extending final zone" << nl; zones.last().size() += count - this->size(); } @@ -73,30 +74,38 @@ void Foam::MeshedSurface<Face>::sortFacesAndStore const bool sorted ) { - List<Face> oldFaces(std::move(unsortedFaces)); - List<label> zones(std::move(zoneIds)); + // Basic sanity check + const label nInputFaces = unsortedFaces.size(); + + if (sorted || zoneIds.size() != nInputFaces) + { + // Sorting not required or not possible + zoneIds.clear(); + sorted = true; + } if (sorted) { - // Already sorted - simply transfer faces - this->storedFaces().transfer(oldFaces); - zones.clear(); + // No additional sorting required + this->storedFaces().transfer(unsortedFaces); return; } - // Determine the sorted order: - // use sortedOrder directly since we discard the intermediate list anyhow - labelList faceMap(sortedOrder(zones)); - zones.clear(); + // The sorted order, based on zone-ids - // Sorted faces - List<Face> newFaces(faceMap.size()); - forAll(faceMap, facei) + labelList faceMap; + Foam::sortedOrder(zoneIds, faceMap); + zoneIds.clear(); + + auto& newFaces = this->storedFaces(); + newFaces.resize(nInputFaces); + + // Faces in sorted order + forAll(newFaces, facei) { - // use transfer to recover memory where possible - newFaces[facei].transfer(oldFaces[faceMap[facei]]); + // Can use transfer, faceMap is unique + newFaces[facei].transfer(unsortedFaces[faceMap[facei]]); } - this->storedFaces().transfer(newFaces); } @@ -109,19 +118,21 @@ void Foam::MeshedSurface<Face>::addZones const bool cullEmpty ) { + auto& zones = this->storedZones(); + zones.resize(zones.size()); + label nZone = 0; - surfZoneList& zones = this->storedZones(); - zones.setSize(zones.size()); - forAll(zones, zoneI) + forAll(zones, zonei) { - if (srfZones[zoneI].size() || !cullEmpty) + if (srfZones[zonei].size() || !cullEmpty) { - zones[nZone] = surfZone(srfZones[zoneI], nZone); + zones[nZone] = surfZone(srfZones[zonei], nZone); ++nZone; } } - zones.setSize(nZone); + + zones.resize(nZone); } @@ -133,27 +144,29 @@ void Foam::MeshedSurface<Face>::addZones const bool cullEmpty ) { + auto& zones = this->storedZones(); + zones.resize(sizes.size()); + label start = 0; label nZone = 0; - surfZoneList& zones = this->storedZones(); - zones.setSize(sizes.size()); - forAll(zones, zoneI) + forAll(zones, zonei) { - if (sizes[zoneI] || !cullEmpty) + if (sizes[zonei] || !cullEmpty) { zones[nZone] = surfZone ( - names[zoneI], - sizes[zoneI], + names[zonei], + sizes[zonei], start, nZone ); - start += sizes[zoneI]; + start += sizes[zonei]; ++nZone; } } - zones.setSize(nZone); + + zones.resize(nZone); } @@ -164,27 +177,29 @@ void Foam::MeshedSurface<Face>::addZones const bool cullEmpty ) { + auto& zones = this->storedZones(); + zones.resize(sizes.size()); + label start = 0; label nZone = 0; - surfZoneList& zones = this->storedZones(); - zones.setSize(sizes.size()); - forAll(zones, zoneI) + forAll(zones, zonei) { - if (sizes[zoneI] || !cullEmpty) + if (sizes[zonei] || !cullEmpty) { zones[nZone] = surfZone ( surfZone::defaultName(nZone), - sizes[zoneI], + sizes[zonei], start, nZone ); - start += sizes[zoneI]; + start += sizes[zonei]; ++nZone; } } - zones.setSize(nZone); + + zones.resize(nZone); } diff --git a/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.C b/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.C index 95633cd35d1..066c1397822 100644 --- a/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.C +++ b/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.C @@ -419,11 +419,11 @@ void Foam::UnsortedMeshedSurface<Face>::setZones template<class Face> void Foam::UnsortedMeshedSurface<Face>::remapFaces ( - const labelUList& faceMap + const labelUList& faceMapNewToOld ) { // Re-assign the zone Ids - if (faceMap.empty()) + if (faceMapNewToOld.empty()) { return; } @@ -438,13 +438,13 @@ void Foam::UnsortedMeshedSurface<Face>::remapFaces } else { - List<label> newZones(faceMap.size()); + List<label> newZonesIds(faceMapNewToOld.size()); - forAll(faceMap, facei) + forAll(faceMapNewToOld, facei) { - newZones[facei] = zoneIds_[faceMap[facei]]; + newZonesIds[facei] = zoneIds_[faceMapNewToOld[facei]]; } - zoneIds_.transfer(newZones); + zoneIds_.transfer(newZonesIds); } } diff --git a/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.H b/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.H index d286d417b69..f0dc3cd269e 100644 --- a/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.H +++ b/src/surfMesh/UnsortedMeshedSurface/UnsortedMeshedSurface.H @@ -125,6 +125,7 @@ private: //- Write to Ostream Ostream& write(Ostream&) const; + protected: // Protected Member Functions @@ -142,7 +143,7 @@ protected: } //- Set new zones from faceMap - virtual void remapFaces(const labelUList& faceMap); + virtual void remapFaces(const labelUList& faceMapNewToOld); public: diff --git a/src/surfMesh/surfaceFormats/surfaceFormatsCore.C b/src/surfMesh/surfaceFormats/surfaceFormatsCore.C index fed6385b6a9..c3062b0a74f 100644 --- a/src/surfMesh/surfaceFormats/surfaceFormatsCore.C +++ b/src/surfMesh/surfaceFormats/surfaceFormatsCore.C @@ -31,6 +31,7 @@ License #include "ListOps.H" #include "Fstream.H" #include "surfMesh.H" +#include "stringListOps.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // @@ -56,6 +57,24 @@ Foam::string Foam::fileFormats::surfaceFormatsCore::getLineNoComment } +Foam::labelList Foam::fileFormats::surfaceFormatsCore::getSelectedPatches +( + const surfZoneList& patches, + const wordRes& whitelist, + const wordRes& blacklist +) +{ + return + stringListOps::findMatching + ( + patches, + whitelist, + blacklist, + nameOp<surfZone>() + ); +} + + #if 0 Foam::fileName Foam::fileFormats::surfaceFormatsCore::localMeshFileName ( diff --git a/src/surfMesh/surfaceFormats/surfaceFormatsCore.H b/src/surfMesh/surfaceFormats/surfaceFormatsCore.H index 70fe0d39951..61605193809 100644 --- a/src/surfMesh/surfaceFormats/surfaceFormatsCore.H +++ b/src/surfMesh/surfaceFormats/surfaceFormatsCore.H @@ -40,6 +40,7 @@ SourceFiles #include "Map.H" #include "HashSet.H" +#include "wordRes.H" #include "labelList.H" #include "surfZoneList.H" #include "surfZoneIdentifierList.H" @@ -88,6 +89,17 @@ protected: return List<surfZone>(1, surfZone(name, container.size())); } + //- Return ids for zone/patch that match by name. + // Uses a combination of whitelist and blacklist. + // + // See Foam::stringListOps::findMatching + static labelList getSelectedPatches + ( + const surfZoneList& patches, + const wordRes& whitelist, + const wordRes& blacklist = wordRes() + ); + // IO helpers -- GitLab