pythonplotly

How to correctly mask section of a cylindrical surface in Plotly 3D surface plot?


I’m trying to plot a 3D cylindrical pipe using Plotly in Python, and I want to exclude a section of the pipe based on angle. I’m using np.nan to mask the values in the array, but I’m facing issues with the shape distortion.

Below is the code I am using

import plotly.graph_objects as go
import numpy as np

mask_cylinder = False  

theta = np.linspace(0, 2 * np.pi, 100)  
z = np.linspace(0, 10, 100)
theta_grid, z_grid = np.meshgrid(theta, z)

r = 1

x = r * np.cos(theta_grid)
y = r * np.sin(theta_grid)


if mask_cylinder:
    mask = theta_grid > np.deg2rad(25) # exclude beyond 25 degrees
    x = np.where(mask, np.nan, x)
    y = np.where(mask, np.nan, y)
    z = np.where(mask, np.nan, z_grid)
else:
    z = z_grid 

fig = go.Figure()

fig.add_trace(
    go.Surface(
        x=x,
        y=y,
        z=z,
        colorscale="Viridis",
        showscale=True, 
    )
)

fig.update_layout(
    title="3D Pipe",
    scene=dict(
        xaxis_title="X Axis",
        yaxis_title="Y Axis",
        zaxis_title="Z Axis",
        xaxis=dict(visible=True),
        yaxis=dict(visible=True),
        zaxis=dict(visible=True),
    ),
)

fig.show()

You could achieve it using transparency i.e. plotting first 25 degrees with full opacity and next 335 degrees with full transparency. That does preserve the original cylindrical geometry but is computationally expensive especially if the resolution is higher

Is there a better way to exclude certain regions while preserving the integrity of the cylindrical geometry?


Solution

  • It seems that the issue was with the limits of each axis.

    import plotly.graph_objects as go
    import numpy as np
    
    mask_cylinder = False
    
    theta = np.linspace(0, 2 * np.pi, 100)
    z = np.linspace(0, 10, 100)
    theta_grid, z_grid = np.meshgrid(theta, z)
    
    r = 1
    
    x = r * np.cos(theta_grid)
    y = r * np.sin(theta_grid)
    
    
    xlim = (x.min(), x.max())
    ylim = (y.min(), y.max())
    zlim = (z.min(), z.max())
    
    
    # padding to avoid plot artifacts
    def pad_limits(min_val, max_val, padding_factor=0.05):
        range_val = max_val - min_val
        padding = range_val * padding_factor
        return min_val - padding, max_val + padding
    
    
    xlim = pad_limits(x.min(), x.max())
    ylim = pad_limits(y.min(), y.max())
    zlim = pad_limits(z.min(), z.max())
    
    if mask_cylinder:
        mask = theta_grid > np.deg2rad(25)  # exclude beyond 25 degrees
        x = np.where(mask, np.nan, x)
        y = np.where(mask, np.nan, y)
        z = np.where(mask, np.nan, z_grid)
    else:
        z = z_grid
    
    fig = go.Figure()
    
    fig.add_trace(
        go.Surface(
            x=x,
            y=y,
            z=z,
            colorscale="Viridis",
            showscale=True,
        )
    )
    
    fig.update_layout(
        title="3D Pipe",
        scene=dict(
            xaxis_title="X Axis",
            yaxis_title="Y Axis",
            zaxis_title="Z Axis",
            xaxis=dict(visible=True, range=xlim),
            yaxis=dict(visible=True, range=ylim),
            zaxis=dict(visible=True, range=zlim),
        ),
    )
    
    fig.show()