Get 3D coordinates of point in watertight mesh

I’m using Plotly Mesh3D to display an obj file. I’m also using Dash for callbacks using ClickData. However, on clicking on a part of the mesh, ClickData only returns the coordinates of the closest vertex, instead of the actual 3D coordinates of the point that was clicked on. I would need the exact coordinates of the point to find the barycentric coordinates of that point.
My problem is very similar to this (except that I’m using obj format).
Is there a way to get the 3D coordinates of the point being clicked on, instead of the nearest vertex? Or better yet, is there a way to directly obtain the barycentric coordinates (along with the face index) of that point?

Hey @ac13, could you provide a MRE for this?

I’ve done something similar a while ago, but I think your issues might be caused by the watertightness.

Hello,
Thank you for the reply.

This is the Dash callback:

args = [
    Output("thing", "figure"),
    Input("thing", "clickData"),
    State("thing", "figure")]

@self.app.callback(*args)
def get_coords(click_data, fig):
    vertex_id = click_data["points"][0]["pointNumber"] 

It is here that I want the exact 3D coordinates on the mesh surface. Currently, the data from clickData gives the coordinates of the closest vertex

The element:

dcc.Graph(
    id='thing',
    figure=self.create_fig(obj="thing"))

The create_fig function:

def create_fig(self, colors: Sequence[int] = None, camera: CameraType = None) -> FigureType:
    colors = colors or []
    return go.Figure(
            data=self.thing.mesh_3d(colors),
            layout=self.create_thing_layout(camera)) 

The thing class:

class Thing:

    def __init__(self, mesh_path: PathLike, *_args) -> None:
        self._trimesh = trimesh.load(mesh_path, process=False)
        self._vertices = np.array(self._trimesh.vertices)
        self._faces = np.array(self._trimesh.faces)

    @property
    def vertices(self) -> ArrayType:
        """The mesh vertices."""
        return self._vertices

    @property
    def faces(self) -> ArrayType:
        """The mesh faces."""
        return self._faces       
    
    @property
    def n_vertices(self) -> int:
        """The number of vertices in the mesh."""
        return self._vertices.shape[0]

    @property
    def n_faces(self) -> int:
        """The number of faces in the mesh."""
        return self._faces.shape[0]

    @property
    def x(self) -> ArrayType:
        """An array of vertex x coordinates"""
        return self._vertices[:, 0]

    @property
    def y(self) -> ArrayType:
        """An array of vertex y coordinates"""
        return self._vertices[:, 1]

    @property
    def z(self) -> ArrayType:
        """An array of vertex z coordinates"""
        return self._vertices[:, 2]

    @property
    def i(self) -> ArrayType:
        """An array of the first face vertices"""
        return self._faces[:, 0]

    @property
    def j(self) -> ArrayType:
        """An array of the second face vertices"""
        return self._faces[:, 1]

    @property
    def k(self) -> ArrayType:
        """An array of the third face vertices"""
        return self._faces[:, 2]

    @property
    def default_selection(self):
        """Default patch selection mask."""
        raise NotImplementedError()

    def _colors(self, *_args, **_kwargs) -> ArrayType:
        return np.tile(
            COLOR_DEFAULT,
            (self._vertices.shape[0], 1))

    def mesh_3d(
            self,
            selection: Sequence[int],
            lighting: Mapping[str, float] = None,
            light_position: Mapping[str, int] = None) -> Mesh3d:
        lighting = lighting or DEFAULT_LIGHTING
        light_position = light_position or DEFAULT_LIGHT_POSITION
        return Mesh3d(
            x=self.x, y=self.y, z=self.z,
            i=self.i, j=self.j, k=self.k,
            vertexcolor=self._colors(selection),
            lighting=lighting,
            lightposition=light_position,
            hoverinfo='none')

I hope this will suffice