I am implementing a contact model that calculated a penalty term backed on the level of penetration into a boundary face. Right now I have it working in 2D convex objects by pulling the DOFs associated with the boundary and organizing them by the angle to the horizontal:

import numpy as np
import ufl
from dolfinx import fem, mesh, plot
domain = mesh.create_rectangle(MPI.COMM_WORLD,[[0.0,0.0],[1,1]],[10,10],mesh.CellType.triangle)
V_vec = fem.VectorFunctionSpace(domain,("CG",2))
# Obtaining the boundary node indexes
edges = mesh.locate_entities_boundary(domain,1,lambda x: np.ones_like(x[0,:]))
edge_dofs = fem.locate_dofs_topological(V_vec,1,edges,1)
X0 = fem.Function(V_vec) # Local Reference Configuration of the domain
X0.interpolate(lambda x: [x[0],x[1]])
NN = len(X0.x.array) # number of nodes
x_inds = np.arange(0,NN,2) # indices of the x coordinates
y_inds = np.arange(1,NN+1,2) # indices of the y coordinates
y_ = X0.x.array[y_inds[edge_dofs]] # getting x and y coordinates of the edge nodes
x_ = X0.x.array[x_inds[edge_dofs]]
mx = np.mean(x_)
my = np.mean(y_)
angles = np.arctan2((y_ - my),(x_ - mx)) # sorting the nodes by increasing angle
edges_ind = edge_dofs[np.argsort(angles)]
boundary = np.zeros((len(self.edges_ind),2))
# re-organizing the nodes by increasing angle
boundary[:,0] = X0.x.array[x_inds[edges_ind]]
boundary[:,1] = X0.x.array[y_inds[edges_ind]]

With this organized structure, I can loop through all of the nodes and find the penetration beyond a line segment captured by two adjacent nodes. However, this method will not work for concave hulls and does not easily generalize to 3D meshes.

Are there built-in tools that allow for the nodes associated with all boundary facet to be grouped in a way that each facet can be looped over? (ie. a function that returns a structure whose elements are sets of nodes each associated with a single boundary face, and hopefully that are organized in a known way). It seems like there are some tools that do something similar in FEniCS (something like BoundaryMesh), but not yet in FEniCS-x or have changed their implementation.