Draw a 3d object without any inside line

Hi @empet
I saw your great solution on (Issue with highlighting edges of the 3D polyhedra - #2 by empet)

As it is shown on the below figure, the results will be a 3d object that is generated from tetrahedras and tetrahedra faces inside the 3d object are visible. Is there any way to define corner points (boundary points) of a 3d object to plot that object wothout any internal line. For example imagine I have points of surface of a ball and I want to draw the ball with a shell or a boundary surface how only the node or point on its surface connect to each other and inside the ball be empty. (please if it is possible guide me on plotly python and do not use dash)

image

@Bijan
Yes, it is possible to find out the boundary faces, using the python library, pyvista. I answered a similar question a few years ago and this is the notebook that extracts the boundary faces and plot them:
https://chart-studio.plotly.com/~empet/15549

Install pyvista with pip:

pip install pyvista

and here are the pyvista docs: https://docs.pyvista.org/.

1 Like

@empet

I wrote the below code according above links. But seems here is some errors?! (I have installed pyvista)

(I get the error you assigned in the alphashape3d function β€œFor a 3d point cloud only unconnected points3d, edges, triangles and tetrahedra are set up”)

import numpy as np
import plotly.graph_objs as go
import pyvista as pv  #pip install pyvista


def pcloud(points3d, marker_size=1.5,  marker_color='#454F8C'):
    #define the trace representing the point cloud
    points3d=np.asarray(points3d)
    if points3d.ndim != 2  or points3d.shape[1] != 3:
        raise ValueError('your data is not a 3D point cloud')
    return go.Scatter3d(
                name='',
                mode='markers',
                x=points3d[:,0],
                y=points3d[:,1], 
                z=points3d[:,2],
                marker_size=marker_size,
                marker_color=marker_color
               )

def get_mesh(points3d, faces,   color='lightblue',  opacity=1):
    # define the Mesh3d representing the alpha-shape
    points3d=np.asarray(points3d)
    if points3d.ndim != 2  or points3d.shape[1] != 3:
        raise ValueError('your data is not a 3D point cloud')  
    faces = np.asarray(faces)
    i, j, k = np.asarray(faces).T
    return  go.Mesh3d(
                color=color,
                opacity=opacity,
                i=i, j=j, k=k,
                x =points3d[:,0],
                y= points3d[:,1], 
                z= points3d[:,2],
                flatshading=True
                        )               


def alphashape3d(points3d, alpha=1):
    #extract  0, 1, 2, 3-simplices of the  alpha shape constructed from points3d
    # Here alpha =1/alphahull, where alphahull is a property of a Plotly alpha shape
    cloud = pv.PolyData(points3d) 
    mesh = cloud.delaunay_3d(alpha=alpha)
    unconnected_points3d = []  #isolated 0-simplices
    edges = [] # isolated edges, 1-simplices
    faces = []  # triangles that are not faces of some tetrahedra
    tetrahedra = []  # 3-simplices
    for k  in mesh.offset:
        length = mesh.cells[k] 
        if length == 2:
            edges.append(list(mesh.cells[k+1: k+length+1]))
        elif length ==3:
            faces.append(list(mesh.cells[k+1: k+length+1]))
        elif length == 4:
            tetrahedra.append(list(mesh.cells[k+1: k+length+1]))
        elif length == 1:
            unconnected_points3d.append(mesh.cells[k+1])
        else:  
            raise ValueError('For a 3d point cloud only unconneted points3d, edges,\
                             triangles and tetrahedra are set up') 
      
    return unconnected_points3d, edges, faces, tetrahedra

def get_tetrahedra_faces(tetrahedra, faces):
    # extract tetrahedra faces and append them to the existing faces
    for t in tetrahedra:
        faces.extend([[t[0], t[1], t[2]],
                      [t[0], t[2], t[3]],
                      [t[0], t[3], t[1]],
                      [t[1], t[2], t[3]]])
    return faces  


def edge_trace(points, edges):
    # define the trace for isolated edges of an alpha shape
    Xe = []
    Ye = []
    Ze = []
    for e in edge:
        Xe.extend([points[e[0], 0], points[e[1], 0], None])
        Ye.extend([points[e[0], 1], points[e[1], 1], None])
        Ze.extend([points[e[0], 2], points[e[1], 2], None])
    return go.Scatter3d(x=Xe, y=Ye, z=Ze, 
                          mode='lines', 
                          line_width=1, 
                          line_color='rgb(50,50,50)')   

######################################################################################
pts = np.loadtxt(np.DataSource().open('https://raw.githubusercontent.com/empet/Plotly-plots/master/Data/data-file.txt'))
alpha= 0.5

title = f'Alpha Shape of a 3D Point Cloud<br>alpha={alpha}'

scatt =  pcloud(pts,  marker_color='#454F8C', marker_size=3)

#Extract the simplicial structure of the alpha shape defined via pyvista
unconnected_points, edges,  faces, tetrahedra = alphashape3d(pts, alpha=alpha)
faces = get_tetrahedra_faces(tetrahedra, faces)
alphashape = get_mesh(pts, faces, opacity=1)
fig1 = go.Figure(data =[scatt, alphashape])
fig1.update_layout(title_text=title, title_x=0.5, width=800, height=800)
fig1.show()

@Bijan
My code was written two years ago. Meanwhile pyvista released a few new versions, and it seems that the structure of the mesh, returned by cloud.delaunay_3d() changed. It’s your interest to understand how the above code works, and which is the new definition of the mesh. Hence, please read the pyvista docs, unpack the body of the function alphashape3d and try to modify the lines that extract the faces, according to the new mesh structure/definition.

1 Like

Thanks @empet

I will read it and put the results in this forum. But you made a conceptual question in my mind. What is PyVista (I visited its page) and I confused what is the difference of PyVista and Plotly?!!! Is that another package for plotting like plotly or something else?

@Bijan
Pyvista is a Python package ( a graphics library) that plots VTK data structures. Once a data structure, like a Delaunay3d mesh, is constructed, you can derive the basic elements needed to make a corresponding Plotly plot.

Could you give more details on what are you working to be able to give you the right pointers on how to proceed? For a few weeks you only paste here lines of code and address some question related to that code. This is insufficient to realize what is your purpose.

1 Like

@empet

Thanks for your curious, and this will be so helpful for me. I am working on a finite element package. In this package I have Nodes (That are points), Frame elements (That Are lines), Shell elements (That are surfaces with various shapes), And Solids (that are volumes like boxes but with various shapes). Until now I could Draw Nodes and Frames exactly according what I needed (High quality and very fast) like this:

and recently (with your guidance) I could draw shell elements exactly according what I needed. But I think because using add_scatter3d that the process is too much slow and quality is too much low (I think I should use mesh3D command or Trisurf . ( Plotly 3d_Charts page I think need to become more completed and currently is a little bit annoying because I expected to find a command that I could simply give the corners of my elements in a pandas dataframe and it simply draw them but seems this is not available a I had to use a loop for this purpose to draw all of them!. Let me know if you have any suggestion.)

Finally I should draw Solids (Volume elements) that they have various corners and shapes . like what is shown in the following (also for this issue I’m using what you have suggested in this page. Please let me know any better way or command):

image

@Bijan Did you know that there is a python package, FeniCS Plotly, (see FEniCS-Plotly β€” fenics-plotly), which can plot any graphical object generated by the library FeniCS, which is very popular within FEM community?

1 Like