How can i get the face`s perimeter?

I can get the nodeID and faceIndex, and I can also get all the edges, but if I want to get the perimeter of a face, I need to find out which edges belong to that face. How can I get this data through the web-viewer API? Or is there another method?

async calcFaceData(nodeId, faceIndex) {
    const model = this._viewer.model
    const data = {
        area: 0,
        perimeter: 0
    }

    const edgeCount = await model.getEdgeCount(nodeId)

    // how to get the edge belong to the face
    for (let i = 0; i < edgeCount; i++) {
        // const edgeProp = await model.getEdgeProperty(nodeId, i)
        // data.perimeter += edgeProp.length ?? Math.PI * edgeProp.radius ** 2
    }
    const meshData = await model.getNodeMeshData(nodeId)
    const faceElement = meshData.faces.element(faceIndex)
    const vertices = []
    
    for (const vertex of faceElement) {
        const [x, y, z] = vertex.position
        vertices.push({x, y, z})
    }

    if (vertices.length < 3) {
        return data
    }

    const A = vertices[0]
    for (let i = 1; i < vertices.length - 1; i++) {
        const B = vertices[i]
        const C = vertices[i + 1]
        data.area += triangleArea(A, B, C)
    }

    return data
}

Hello,

For a given face identified by a face index, it will be made up of individual triangles making up the face. The following code snippet returns the points of the individual triangles and then adds a polyline. If you wish to only get the outer edges, you will have to do additional processing on your end.

async function showNodeTessellation(viewer, nodeid, faceIndex) {
    const mdata = new Communicator.MeshData();
    const matrix = viewer.model.getNodeMatrix(nodeid);

    const data = await viewer.model.getNodeMeshData(nodeid);
    const lines = [];
    
        const face = data.faces.element(faceIndex);
        const pp = face.iterate();
        
        for (let j = 0; j < face.vertexCount; j += 3) {
            const tp = [];
            for (let k = 0; k < 3; k++) {
                const rawpoint = pp.next();
                const p = new Communicator.Point3(rawpoint.position[0], rawpoint.position[1], rawpoint.position[2]);
                
                const transformedP = matrix.transform(p);
                tp.push(transformedP.x, transformedP.y, transformedP.z);
            }
            tp.push(tp[0], tp[1], tp[2]);
            mdata.addPolyline(tp);
        }
    
    
    mdata.addPolyline(lines);
    const mid = await viewer.model.createMesh(mdata);
    const meshInstanceData = new Communicator.MeshInstanceData(mid);
    const node = viewer.model.createNode(viewer.model.getNodeParent(nodeid), "meshnode");
    const resnode = await viewer.model.createMeshInstance(meshInstanceData, node);
    
    viewer.model.setNodesLineColor([node], Communicator.Color.black());
    return resnode;
}

But my goal is to obtain the perimeter(Circumference), so how can I get the outer edges?

I used AI to generate the algorithm below. It is a companion function for the code I previously provided.

The parameter for the function is a flat array of vertex coordinates where every 12 elements represent a triangle. The triangles are stored as four 3D points, where the fourth point is a repeat of the first to close the loop to make it easier to draw polylines as a visual reference.

I’ve tested the algorithm on circles, squares and simple curved surfaces and the results are correct. That said, feel free to modify the code to best suit your data set and objectives.

function getFlatPerimeter(flatArray, precision = 6) {
    const edgeCount = new Map();
    
    // Helper to normalize coordinates (crucial for 3D float matching)
    const norm = (val) => Number(val).toFixed(precision);
    const getPointKey = (arr) => `${norm(arr[0])},${norm(arr[1])},${norm(arr[2])}`;

    // 1. Identify all edges and count occurrences
    // Each triangle is 4 points (12 floats). We only need the first 3 points.
    for (let i = 0; i < flatArray.length; i += 12) {
        const p1 = getPointKey(flatArray.slice(i, i + 3));
        const p2 = getPointKey(flatArray.slice(i + 3, i + 6));
        const p3 = getPointKey(flatArray.slice(i + 6, i + 9));

        const triangleEdges = [[p1, p2], [p2, p3], [p3, p1]];

        triangleEdges.forEach(([a, b]) => {
            // Sort keys so [A,B] and [B,A] are the same edge
            const key = [a, b].sort().join('|');
            edgeCount.set(key, (edgeCount.get(key) || 0) + 1);
        });
    }

    // 2. Build adjacency map of perimeter edges (count === 1)
    const adj = new Map();
    for (let [key, count] of edgeCount) {
        if (count === 1) {
            const [u, v] = key.split('|');
            if (!adj.has(u)) adj.set(u, []);
            if (!adj.has(v)) adj.set(v, []);
            adj.get(u).push(v);
            adj.get(v).push(u);
        }
    }

    if (adj.size === 0) return [];

    // 3. Sequence the points into a flat array
    const result = [];
    const visited = new Set();
    
    let startPointKey = adj.keys().next().value; 
    let current = startPointKey;

    while (current && !visited.has(current)) {
        visited.add(current);
        
        // Push current x, y, z to the flat array
        const coords = current.split(',').map(Number);
        result.push(...coords);

        const neighbors = adj.get(current);
        // Find next neighbor we haven't visited yet
        current = neighbors.find(n => !visited.has(n));
    }

    // 4. CLOSE THE LOOP
    // Append the very first point's coordinates to the end
    const firstPointCoords = startPointKey.split(',').map(Number);
    result.push(...firstPointCoords);

    return result;
}

Thank you, tino, this is precisely the solution.