/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2016 OpenFOAM Foundation Copyright (C) 2015-2023 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 . \*---------------------------------------------------------------------------*/ #include "cyclicPolyPatch.H" #include "addToRunTimeSelectionTable.H" #include "polyBoundaryMesh.H" #include "polyMesh.H" #include "OFstream.H" #include "matchPoints.H" #include "edgeHashes.H" #include "Time.H" #include "transformField.H" #include "SubField.H" #include "unitConversion.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // namespace Foam { defineTypeNameAndDebug(cyclicPolyPatch, 0); addToRunTimeSelectionTable(polyPatch, cyclicPolyPatch, word); addToRunTimeSelectionTable(polyPatch, cyclicPolyPatch, dictionary); } // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // Foam::label Foam::cyclicPolyPatch::findMaxArea ( const UList& points, const UList& faces ) { label maxI = -1; scalar maxAreaSqr = -GREAT; forAll(faces, facei) { scalar areaSqr = faces[facei].magSqr(points); if (maxAreaSqr < areaSqr) { maxAreaSqr = areaSqr; maxI = facei; } } return maxI; } void Foam::cyclicPolyPatch::calcTransforms() { if (size()) { // Half0 const cyclicPolyPatch& half0 = *this; vectorField half0Areas(half0.size()); forAll(half0, facei) { half0Areas[facei] = half0[facei].areaNormal(half0.points()); } // Half1 const cyclicPolyPatch& half1 = neighbPatch(); vectorField half1Areas(half1.size()); forAll(half1, facei) { half1Areas[facei] = half1[facei].areaNormal(half1.points()); } calcTransforms ( half0, half0.faceCentres(), half0Areas, half1.faceCentres(), half1Areas ); } } void Foam::cyclicPolyPatch::calcTransforms ( const primitivePatch& half0, const pointField& half0Ctrs, const vectorField& half0Areas, const pointField& half1Ctrs, const vectorField& half1Areas ) { if (debug && owner()) { fileName casePath(boundaryMesh().mesh().time().path()); { fileName nm0(casePath/name()+"_faces.obj"); Pout<< "cyclicPolyPatch::calcTransforms : Writing " << name() << " faces to OBJ file " << nm0 << endl; writeOBJ(nm0, half0, half0.points()); } const cyclicPolyPatch& half1 = neighbPatch(); { fileName nm1(casePath/half1.name()+"_faces.obj"); Pout<< "cyclicPolyPatch::calcTransforms : Writing " << half1.name() << " faces to OBJ file " << nm1 << endl; writeOBJ(nm1, half1, half1.points()); } { OFstream str(casePath/name()+"_to_" + half1.name() + ".obj"); label vertI = 0; Pout<< "cyclicPolyPatch::calcTransforms :" << " Writing coupled face centres as lines to " << str.name() << endl; forAll(half0Ctrs, i) { const point& p0 = half0Ctrs[i]; str << "v " << p0.x() << ' ' << p0.y() << ' ' << p0.z() << nl; vertI++; const point& p1 = half1Ctrs[i]; str << "v " << p1.x() << ' ' << p1.y() << ' ' << p1.z() << nl; vertI++; str << "l " << vertI-1 << ' ' << vertI << nl; } } } // Some sanity checks if (half0Ctrs.size() != half1Ctrs.size()) { FatalErrorInFunction << "For patch " << name() << " there are " << half0Ctrs.size() << " face centres, for the neighbour patch " << neighbPatch().name() << " there are " << half1Ctrs.size() << exit(FatalError); } if (transform() != neighbPatch().transform()) { FatalErrorInFunction << "Patch " << name() << " has transform type " << transformTypeNames[transform()] << ", neighbour patch " << neighbPatchName() << " has transform type " << neighbPatch().transformTypeNames[neighbPatch().transform()] << exit(FatalError); } // Calculate transformation tensors if (half0Ctrs.size() > 0) { vectorField half0Normals(half0Areas.size()); vectorField half1Normals(half1Areas.size()); scalar maxAreaDiff = -GREAT; label maxAreaFacei = -1; forAll(half0, facei) { scalar magSf = mag(half0Areas[facei]); scalar nbrMagSf = mag(half1Areas[facei]); scalar avSf = (magSf + nbrMagSf)/2.0; if (magSf < ROOTVSMALL && nbrMagSf < ROOTVSMALL) { // Undetermined normal. Use dummy normal to force separation // check. (note use of sqrt(VSMALL) since that is how mag // scales) half0Normals[facei] = point(1, 0, 0); half1Normals[facei] = half0Normals[facei]; } else { scalar areaDiff = mag(magSf - nbrMagSf)/avSf; if (areaDiff > maxAreaDiff) { maxAreaDiff = areaDiff; maxAreaFacei = facei; } if (areaDiff > matchTolerance()) { FatalErrorInFunction << "face " << facei << " area does not match neighbour by " << 100*areaDiff << "% -- possible face ordering problem." << endl << "patch:" << name() << " my area:" << magSf << " neighbour area:" << nbrMagSf << " matching tolerance:" << matchTolerance() << endl << "Mesh face:" << start()+facei << " fc:" << half0Ctrs[facei] << endl << "Neighbour fc:" << half1Ctrs[facei] << endl << "If you are certain your matching is correct" << " you can increase the 'matchTolerance' setting" << " in the patch dictionary in the boundary file." << endl << "Rerun with cyclic debug flag set" << " for more information." << exit(FatalError); } else { half0Normals[facei] = half0Areas[facei] / magSf; half1Normals[facei] = half1Areas[facei] / nbrMagSf; } } } // Print area match if (debug) { Pout<< "cyclicPolyPatch::calcTransforms :" << " patch:" << name() << " Max area error:" << 100*maxAreaDiff << "% at face:" << maxAreaFacei << " at:" << half0Ctrs[maxAreaFacei] << " coupled face at:" << half1Ctrs[maxAreaFacei] << endl; } // Calculate transformation tensors if (transform() == ROTATIONAL) { // Calculate using the given rotation axis and centre. Do not // use calculated normals. vector n0 = findFaceMaxRadius(half0Ctrs); vector n1 = -findFaceMaxRadius(half1Ctrs); n0.normalise(); n1.normalise(); if (debug) { Pout<< "cyclicPolyPatch::calcTransforms :" << " patch:" << name() << " Specified rotation :" << " n0:" << n0 << " n1:" << n1 << " swept angle: " << radToDeg(acos(n0 & n1)) << " [deg]" << endl; } // Extended tensor from two local coordinate systems calculated // using normal and rotation axis const tensor E0 ( rotationAxis_, (n0 ^ rotationAxis_), n0 ); const tensor E1 ( rotationAxis_, (-n1 ^ rotationAxis_), -n1 ); const tensor revT(E1.T() & E0); const_cast(forwardT()) = tensorField(1, revT.T()); const_cast(reverseT()) = tensorField(1, revT); const_cast(separation()).clear(); const_cast(collocated()) = boolList(1, false); } else { scalarField half0Tols ( matchTolerance() *calcFaceTol ( half0, half0.points(), static_cast(half0Ctrs) ) ); calcTransformTensors ( static_cast(half0Ctrs), static_cast(half1Ctrs), half0Normals, half1Normals, half0Tols, matchTolerance(), transform() ); if (transform() == TRANSLATIONAL) { if (debug) { Pout<< "cyclicPolyPatch::calcTransforms :" << " patch:" << name() << " Specified separation vector : " << separationVector_ << endl; } // Check that separation vectors are same. const scalar avgTol = average(half0Tols); if ( mag(separationVector_ + neighbPatch().separationVector_) > avgTol ) { WarningInFunction << "Specified separation vector " << separationVector_ << " differs by that of neighbouring patch " << neighbPatch().separationVector_ << " by more than tolerance " << avgTol << endl << "patch:" << name() << " neighbour:" << neighbPatchName() << endl; } // Override computed transform with specified. if ( separation().size() != 1 || mag(separation()[0] - separationVector_) > avgTol ) { WarningInFunction << "Specified separationVector " << separationVector_ << " differs from computed separation vector " << separation() << endl << "This probably means your geometry is not consistent" << " with the specified separation and might lead" << " to problems." << endl << "Continuing with specified separation vector " << separationVector_ << endl << "patch:" << name() << " neighbour:" << neighbPatchName() << endl; } // Set tensors const_cast(forwardT()).clear(); const_cast(reverseT()).clear(); const_cast(separation()) = vectorField ( 1, separationVector_ ); const_cast(collocated()) = boolList(1, false); } } } } void Foam::cyclicPolyPatch::getCentresAndAnchors ( const primitivePatch& pp0, const primitivePatch& pp1, pointField& half0Ctrs, pointField& half1Ctrs, pointField& anchors0, scalarField& tols ) const { // Get geometric data on both halves. half0Ctrs = pp0.faceCentres(); anchors0 = getAnchorPoints(pp0, pp0.points(), transform()); half1Ctrs = pp1.faceCentres(); if (debug) { Pout<< "cyclicPolyPatch::getCentresAndAnchors :" << " patch:" << name() << nl << "half0 untransformed faceCentres (avg) : " << gAverage(half0Ctrs) << nl << "half1 untransformed faceCentres (avg) : " << gAverage(half1Ctrs) << endl; } if (half0Ctrs.size()) { switch (transform()) { case ROTATIONAL: { vector n0 = findFaceMaxRadius(half0Ctrs); vector n1 = -findFaceMaxRadius(half1Ctrs); n0.normalise(); n1.normalise(); if (debug) { Pout<< "cyclicPolyPatch::getCentresAndAnchors :" << " patch:" << name() << " Specified rotation :" << " n0:" << n0 << " n1:" << n1 << " swept angle: " << radToDeg(acos(n0 & n1)) << " [deg]" << endl; } // Extended tensor from two local coordinate systems calculated // using normal and rotation axis const tensor E0 ( rotationAxis_, (n0 ^ rotationAxis_), n0 ); const tensor E1 ( rotationAxis_, (-n1 ^ rotationAxis_), -n1 ); const tensor revT(E1.T() & E0); // Rotation forAll(half0Ctrs, facei) { half0Ctrs[facei] = Foam::transform ( revT, half0Ctrs[facei] - rotationCentre_ ) + rotationCentre_; anchors0[facei] = Foam::transform ( revT, anchors0[facei] - rotationCentre_ ) + rotationCentre_; } break; } case TRANSLATIONAL: { // Transform 0 points. if (debug) { Pout<< "cyclicPolyPatch::getCentresAndAnchors :" << " patch:" << name() << "Specified translation : " << separationVector_ << endl; } // Note: getCentresAndAnchors gets called on the neighbour side // so separationVector is owner-neighbour points. half0Ctrs -= separationVector_; anchors0 -= separationVector_; break; } default: { // Assumes that cyclic is rotational. This is also the initial // condition for patches without faces. // Determine the face with max area on both halves. These // two faces are used to determine the transformation tensors label max0I = findMaxArea(pp0.points(), pp0); const vector n0 = pp0[max0I].unitNormal(pp0.points()); label max1I = findMaxArea(pp1.points(), pp1); const vector n1 = pp1[max1I].unitNormal(pp1.points()); if (mag(n0 & n1) < 1-matchTolerance()) { if (debug) { Pout<< "cyclicPolyPatch::getCentresAndAnchors :" << " patch:" << name() << " Detected rotation :" << " n0:" << n0 << " n1:" << n1 << endl; } // Rotation (around origin) const tensor revT(rotationTensor(n0, -n1)); // Rotation forAll(half0Ctrs, facei) { half0Ctrs[facei] = Foam::transform ( revT, half0Ctrs[facei] ); anchors0[facei] = Foam::transform ( revT, anchors0[facei] ); } } else { // Parallel translation. Get average of all used points. const point ctr0(sum(pp0.localPoints())/pp0.nPoints()); const point ctr1(sum(pp1.localPoints())/pp1.nPoints()); if (debug) { Pout<< "cyclicPolyPatch::getCentresAndAnchors :" << " patch:" << name() << " Detected translation :" << " n0:" << n0 << " n1:" << n1 << " ctr0:" << ctr0 << " ctr1:" << ctr1 << endl; } half0Ctrs += ctr1 - ctr0; anchors0 += ctr1 - ctr0; } break; } } } // Calculate typical distance per face tols = matchTolerance()*calcFaceTol(pp1, pp1.points(), half1Ctrs); } Foam::vector Foam::cyclicPolyPatch::findFaceMaxRadius ( const pointField& faceCentres ) const { // Determine a face furthest away from the axis const vectorField n((faceCentres - rotationCentre_) ^ rotationAxis_); const scalarField magRadSqr(magSqr(n)); label facei = findMax(magRadSqr); if (debug) { Info<< "findFaceMaxRadius(const pointField&) : patch: " << name() << nl << " rotFace = " << facei << nl << " point = " << faceCentres[facei] << nl << " distance = " << Foam::sqrt(magRadSqr[facei]) << endl; } return n[facei]; } // * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * * * * * // Foam::cyclicPolyPatch::cyclicPolyPatch ( const word& name, const label size, const label start, const label index, const polyBoundaryMesh& bm, const word& patchType, const transformType transform ) : coupledPolyPatch(name, size, start, index, bm, patchType, transform), neighbPatchName_(), neighbPatchID_(-1), rotationAxis_(Zero), rotationCentre_(Zero), separationVector_(Zero), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const word& name, const label size, const label start, const label index, const polyBoundaryMesh& bm, const word& neighbPatchName, const transformType transform, const vector& rotationAxis, const point& rotationCentre, const vector& separationVector ) : coupledPolyPatch(name, size, start, index, bm, typeName, transform), neighbPatchName_(neighbPatchName), neighbPatchID_(-1), rotationAxis_(rotationAxis), rotationCentre_(rotationCentre), separationVector_(separationVector), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const word& name, const dictionary& dict, const label index, const polyBoundaryMesh& bm, const word& patchType ) : coupledPolyPatch(name, dict, index, bm, patchType), neighbPatchName_(dict.getOrDefault("neighbourPatch", word::null)), coupleGroup_(dict), neighbPatchID_(-1), rotationAxis_(Zero), rotationCentre_(Zero), separationVector_(Zero), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { if (neighbPatchName_.empty() && !coupleGroup_.good()) { FatalIOErrorInFunction(dict) << "No \"neighbourPatch\" provided." << endl << "Is your mesh uptodate with split cyclics?" << endl << "Run foamUpgradeCyclics to convert mesh and fields" << " to split cyclics." << exit(FatalIOError); } if (neighbPatchName_ == name) { FatalIOErrorInFunction(dict) << "Neighbour patch name " << neighbPatchName_ << " cannot be the same as this patch " << name << exit(FatalIOError); } switch (transform()) { case ROTATIONAL: { dict.readEntry("rotationAxis", rotationAxis_); dict.readEntry("rotationCentre", rotationCentre_); scalar magRot = mag(rotationAxis_); if (magRot < SMALL) { FatalIOErrorInFunction(dict) << "Illegal rotationAxis " << rotationAxis_ << endl << "Please supply a non-zero vector." << exit(FatalIOError); } rotationAxis_ /= magRot; break; } case TRANSLATIONAL: { dict.readEntry("separationVector", separationVector_); break; } default: { // no additional info required } } // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const cyclicPolyPatch& pp, const polyBoundaryMesh& bm ) : coupledPolyPatch(pp, bm), neighbPatchName_(pp.neighbPatchName_), coupleGroup_(pp.coupleGroup_), neighbPatchID_(-1), rotationAxis_(pp.rotationAxis_), rotationCentre_(pp.rotationCentre_), separationVector_(pp.separationVector_), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const cyclicPolyPatch& pp, const label nrbPatchID, const labelList& faceCells ) : coupledPolyPatch(pp, faceCells), neighbPatchName_(pp.neighbPatchName_), coupleGroup_(pp.coupleGroup_), neighbPatchID_(nrbPatchID), rotationAxis_(pp.rotationAxis_), rotationCentre_(pp.rotationCentre_), separationVector_(pp.separationVector_), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const cyclicPolyPatch& pp, const polyBoundaryMesh& bm, const label index, const label newSize, const label newStart, const word& neighbName ) : coupledPolyPatch(pp, bm, index, newSize, newStart), neighbPatchName_(neighbName), coupleGroup_(pp.coupleGroup_), neighbPatchID_(-1), rotationAxis_(pp.rotationAxis_), rotationCentre_(pp.rotationCentre_), separationVector_(pp.separationVector_), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) { if (neighbName == name()) { FatalErrorInFunction << "Neighbour patch name " << neighbName << " cannot be the same as this patch " << name() << exit(FatalError); } // Neighbour patch might not be valid yet so no transformation // calculation possible. } Foam::cyclicPolyPatch::cyclicPolyPatch ( const cyclicPolyPatch& pp, const polyBoundaryMesh& bm, const label index, const labelUList& mapAddressing, const label newStart ) : coupledPolyPatch(pp, bm, index, mapAddressing, newStart), neighbPatchName_(pp.neighbPatchName_), coupleGroup_(pp.coupleGroup_), neighbPatchID_(-1), rotationAxis_(pp.rotationAxis_), rotationCentre_(pp.rotationCentre_), separationVector_(pp.separationVector_), coupledPointsPtr_(nullptr), coupledEdgesPtr_(nullptr) {} // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // Foam::cyclicPolyPatch::~cyclicPolyPatch() {} // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // const Foam::word& Foam::cyclicPolyPatch::neighbPatchName() const { if (neighbPatchName_.empty()) { // Try and use patchGroup to find samplePatch and sampleRegion label patchID = coupleGroup_.findOtherPatchID(*this); neighbPatchName_ = boundaryMesh()[patchID].name(); } return neighbPatchName_; } Foam::label Foam::cyclicPolyPatch::neighbPatchID() const { if (neighbPatchID_ == -1) { neighbPatchID_ = this->boundaryMesh().findPatchID(neighbPatchName()); if (neighbPatchID_ == -1) { FatalErrorInFunction << "Illegal neighbourPatch name " << neighbPatchName() << endl << "Valid patch names are " << this->boundaryMesh().names() << exit(FatalError); } // Check that it is a cyclic const cyclicPolyPatch& nbrPatch = refCast ( this->boundaryMesh()[neighbPatchID_] ); if (nbrPatch.neighbPatchName() != name()) { WarningInFunction << "Patch " << name() << " specifies neighbour patch " << neighbPatchName() << endl << " but that in return specifies " << nbrPatch.neighbPatchName() << endl; } } return neighbPatchID_; } void Foam::cyclicPolyPatch::transformPosition(pointField& l) const { if (!parallel()) { if (transform() == ROTATIONAL) { l = Foam::transform(forwardT(), l-rotationCentre_) + rotationCentre_; } else { l = Foam::transform(forwardT(), l); } } else if (separated()) { // transformPosition gets called on the receiving side, // separation gets calculated on the sending side so subtract. const vectorField& s = separation(); if (s.size() == 1) { forAll(l, i) { l[i] -= s[0]; } } else { l -= s; } } } void Foam::cyclicPolyPatch::transformPosition(point& l, const label facei) const { if (!parallel()) { const tensor& T = ( forwardT().size() == 1 ? forwardT()[0] : forwardT()[facei] ); if (transform() == ROTATIONAL) { l = Foam::transform(T, l-rotationCentre_) + rotationCentre_; } else { l = Foam::transform(T, l); } } else if (separated()) { const vector& s = ( separation().size() == 1 ? separation()[0] : separation()[facei] ); l -= s; } } void Foam::cyclicPolyPatch::initGeometry(PstreamBuffers& pBufs) { polyPatch::initGeometry(pBufs); } void Foam::cyclicPolyPatch::initGeometry ( const primitivePatch& referPatch, pointField& nbrCtrs, vectorField& nbrAreas, pointField& nbrCc ) {} void Foam::cyclicPolyPatch::calcGeometry ( const primitivePatch& referPatch, const pointField& thisCtrs, const vectorField& thisAreas, const pointField& thisCc, const pointField& nbrCtrs, const vectorField& nbrAreas, const pointField& nbrCc ) { calcTransforms ( referPatch, thisCtrs, thisAreas, nbrCtrs, nbrAreas ); } void Foam::cyclicPolyPatch::calcGeometry(PstreamBuffers& pBufs) { calcGeometry ( *this, faceCentres(), faceAreas(), faceCellCentres(), neighbPatch().faceCentres(), neighbPatch().faceAreas(), neighbPatch().faceCellCentres() ); } void Foam::cyclicPolyPatch::initMovePoints ( PstreamBuffers& pBufs, const pointField& p ) { polyPatch::initMovePoints(pBufs, p); } void Foam::cyclicPolyPatch::movePoints ( PstreamBuffers& pBufs, const pointField& p ) { polyPatch::movePoints(pBufs, p); calcTransforms(); } void Foam::cyclicPolyPatch::initUpdateMesh(PstreamBuffers& pBufs) { polyPatch::initUpdateMesh(pBufs); } void Foam::cyclicPolyPatch::updateMesh(PstreamBuffers& pBufs) { polyPatch::updateMesh(pBufs); coupledPointsPtr_.reset(nullptr); coupledEdgesPtr_.reset(nullptr); } const Foam::edgeList& Foam::cyclicPolyPatch::coupledPoints() const { if (!coupledPointsPtr_) { const faceList& nbrLocalFaces = neighbPatch().localFaces(); const labelList& nbrMeshPoints = neighbPatch().meshPoints(); // Now all we know is that relative face index in *this is same // as coupled face in nbrPatch and also that the 0th vertex // corresponds. // From local point to nbrPatch or -1. labelList coupledPoint(nPoints(), -1); forAll(*this, patchFacei) { const face& fA = localFaces()[patchFacei]; const face& fB = nbrLocalFaces[patchFacei]; forAll(fA, indexA) { label patchPointA = fA[indexA]; if (coupledPoint[patchPointA] == -1) { label indexB = (fB.size() - indexA) % fB.size(); // Filter out points on wedge axis if (meshPoints()[patchPointA] != nbrMeshPoints[fB[indexB]]) { coupledPoint[patchPointA] = fB[indexB]; } } } } coupledPointsPtr_.reset(new edgeList(nPoints())); auto& connected = *coupledPointsPtr_; // Extract coupled points. label connectedI = 0; forAll(coupledPoint, i) { if (coupledPoint[i] != -1) { connected[connectedI++] = edge(i, coupledPoint[i]); } } connected.setSize(connectedI); if (debug) { OFstream str ( boundaryMesh().mesh().time().path() /name() + "_coupledPoints.obj" ); label vertI = 0; Pout<< "Writing file " << str.name() << " with coordinates of " << "coupled points" << endl; forAll(connected, i) { const point& a = points()[meshPoints()[connected[i][0]]]; const point& b = points()[nbrMeshPoints[connected[i][1]]]; str<< "v " << a.x() << ' ' << a.y() << ' ' << a.z() << nl; str<< "v " << b.x() << ' ' << b.y() << ' ' << b.z() << nl; vertI += 2; str<< "l " << vertI-1 << ' ' << vertI << nl; } } } return *coupledPointsPtr_; } const Foam::edgeList& Foam::cyclicPolyPatch::coupledEdges() const { if (!coupledEdgesPtr_) { const edgeList& pointCouples = coupledPoints(); // Build map from points on *this (A) to points on neighbourpatch (B) Map