Plotly - 如何绘制圆柱体?

Plotly - how to plot Cylinder?

我有一个使用 matplotlib 绘制圆柱体的函数。

我想知道如何使用 plotly 做同样的事情?

下面是我绘制圆柱体的matplotlib函数:

        #function to plot the cylinder
        def _plotCylinder(self, ax, x, y, z, dx, dy, dz, color='red',mode=2):
            """ Auxiliary function to plot a Cylinder  """
            # plot the two circles above and below the cylinder
            p = Circle((x+dx/2,y+dy/2),radius=dx/2,color=color,ec='black')
            p2 = Circle((x+dx/2,y+dy/2),radius=dx/2,color=color,ec='black')
            ax.add_patch(p)
            ax.add_patch(p2)
            art3d.pathpatch_2d_to_3d(p, z=z, zdir="z")
            art3d.pathpatch_2d_to_3d(p2, z=z+dz, zdir="z")
            # plot a circle in the middle of the cylinder
            center_z = np.linspace(0, dz, 15)
            theta = np.linspace(0, 2*np.pi, 15)
            theta_grid, z_grid=np.meshgrid(theta, center_z)
            x_grid = dx / 2 * np.cos(theta_grid) + x + dx / 2
            y_grid = dy / 2 * np.sin(theta_grid) + y + dy / 2
            z_grid = z_grid + z
            ax.plot_surface(x_grid, y_grid, z_grid,shade=False,fc=color,ec='black',alpha=1,color=color)
    x,y,z = item.position
    [w,h,d] = item.getDimension()
    # plot item of cylinder
    self._plotCylinder(axGlob, float(x), float(y), float(z), float(w),float(h),float(d),color=color,mode=2)

更新 - 绘制立方体类型项目的当前代码

### PLOTLY ###
# https://plotly.com/python/3d-mesh/#mesh-cube
def vertices(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1):
    return {
        "x": [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax],
        "y": [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin],
        "z": [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax],
        "i": [7, 0, 0, 0, 4, 4, 6, 1, 4, 0, 3, 6],
        "j": [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],
        "k": [0, 7, 2, 3, 6, 7, 1, 6, 5, 5, 7, 2],
    }



def parallelipipedic_frame(xm, xM, ym, yM, zm, zM):
    # defines the coords of each segment followed by None, if the line is
    # discontinuous
    x = [xm, xM, xM, xm, xm, None, xm, xM, xM, xm, xm, None, xm, xm, None, xM, xM,
         None, xM, xM, None, xm, xm]
    y = [ym, ym, yM, yM, ym, None, ym, ym, yM, yM, ym, None, ym, ym, None, ym, ym,
         None, yM, yM, None, yM, yM]
    z = [zm, zm, zm, zm, zm, None, zM, zM, zM, zM, zM, None, zm, zM, None, zm, zM,
         None, zm, zM, None, zm, zM]
    return x, y, z

# take a packer item and build parameters to a plotly mesh3d cube
def packer_to_plotly(item):
    colors = ["crimson", "limegreen", "green", "red", "cyan", "magenta", "yellow"]

    ret = vertices(
        *item.position, *[sum(x) for x in zip(item.position, item.getDimension())]
    )
    ret["name"] = item.name
    ret["color"] = colors[ord(item.name.split("_")[0][-1]) - ord("A")]
    return ret

# create a multi-plot figure for each bin
# fig = make_subplots(rows=len(packer.bins), cols=1, specs=[[{"type":"mesh3d"}], [{"type":"mesh3d"}]])
fig = go.Figure()

# add a trace for each packer item
for row, pbin in enumerate(packer.bins):
    for item in pbin.items:
        fig.add_trace(go.Mesh3d(packer_to_plotly(item)))

    # sorting out layout, prmarily aspect ratio
    fig.update_layout(
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
        autosize=False,
        scene=dict(
            camera=dict(
                # eye=dict(x=0.1, y=0.1, z=1.5)
            ),
            aspectratio=dict(x=1, y=.2, z=0.2),
            aspectmode="manual",
        ),
    )


    # push data into a data frame to enable more types of analysis
df = pd.DataFrame(
    [
        {
            "bin_name": b.partno,
            "bin_index": i,
            **packer_to_plotly(item),
            **{d: v for v, d in zip(item.getDimension(), list("hwl"))},
            **{d + d: v for v, d in zip(item.position, list("xyz"))},
        }
        for i, b in enumerate(packer.bins)
        for item in b.items
    ]
)

# create a figure for each container (bin)
for pbin, d in df.groupby("bin_name"):
    fig = go.Figure()
    xx = []
    yy = []
    zz = []

    # create a trace for each box (bin)
    for _, r in d.iterrows():
        fig.add_trace(
            go.Mesh3d(r[["x", "y", "z", "i", "j", "k", "name", "color"]].to_dict())
        )
        xx += [r.xx, r.xx + r.h, r.xx + r.h, r.xx, r.xx, None] * 2 + [r.xx] * 5 + [None]
        yy += [r.yy, r.yy, r.yy + r.w, r.yy + r.w, r.yy, None] * 2 + [
            r.yy,
            r.yy + r.w,
            r.yy + r.w,
            r.yy,
            r.yy,
            None,
        ]
        zz += (
            [r.zz] * 5
            + [None]
            + [r.zz + r.l] * 5
            + [None]
            + [r.zz, r.zz, r.zz + r.l, r.zz + r.l, r.zz, None]
        )

    fig.add_trace(
        go.Scatter3d(
            x=xx,
            y=yy,
            z=zz,
            mode="lines",
            line_color="black",
            line_width=2,
            hoverinfo="skip",
        )
    )

    x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)

    fig.add_trace(
        go.Scatter3d(
            x=x,
            y=y,
            z=z,
            mode="lines",
            line_color="blue",
            line_width=2,
            hoverinfo="skip",
        )
    )

    ar = 4
    xr = max(d["x"].max()) - min(d["x"].min())
    fig.update_layout(
        showlegend=False,
        title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
        # autosize=False,
        scene=dict(
            camera=dict(eye=dict(x=2, y=2, z=2)),
            aspectratio={
                **{"x": ar},
                **{
                    c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar
                    for c in list("yz")
                },
            },
            aspectmode="manual",
        ),
    )
fig.show()

我有机会花一些时间解决创建圆柱体网格的问题,因此开始研究三角剖分。

这是结果。主要功能是 cylinder_traces:它 returns go.Surface 轨迹,以及可选的 go.Scatter3d 代表线框网格的轨迹。

import plotly.graph_objects as go
import numpy as np

def slice_triangles(z, n, i, j, k, l):
    """Create the triangles of a single slice"""
    return [[z, j, i], [i, j, l], [l, j, k], [k, n, l]]

def cylinder_mesh(r, xs, ys, zs, h, n_slices=40):
    """Create a cylindrical mesh"""
    theta = np.linspace(0, 2 * np.pi, n_slices + 1)
    x = xs + r * np.cos(theta)
    y = ys + r * np.sin(theta)
    z1 = zs + 0 * np.ones_like(x)
    z2 = (zs + h) * np.ones_like(x)
    
    # index of the final point in the mesh
    n = n_slices * 2 + 1
    
    # build triangulation
    triangles = []
    for s in range(1, n_slices + 1):
        j = (s + 1) if (s <= n_slices - 1) else 1
        k = j + n_slices if (s <= n_slices - 1) else n_slices + 1
        l = s + n_slices
        triangles += slice_triangles(0, n, s, j, k, l)
    triangles = np.array(triangles)
    
    # coordinates of the vertices
    x_coords = np.hstack([xs, x[:-1], x[:-1], xs])
    y_coords = np.hstack([ys, y[:-1], y[:-1], ys])
    z_coords = np.hstack([zs, z1[:-1], z2[:-1], (zs + h)])
    vertices = np.stack([x_coords, y_coords, z_coords]).T
    
    return vertices, triangles, x, y, z1, z2

def cylinder_traces(r, xs, ys, zs, h, n_slices=40, show_mesh=True, n_sub=4, surface_kw={}, line_kw={}):
    """
    r : radius
    xs, ys, zs : start position of the cylinder
    h : height of the cylinder
    n_slices : number of slices in the circumferential direction
    show_mesh : wheter to display pseudo-wireframe
    n_sub : number of subdivision in along the height for the pseudo-wireframe
    surface_kw : customize the appearance of the surface
    line_kw : customize the appearance of the wireframe
    """
    vertices, triangles, x, y, z1, z2 = cylinder_mesh(r, xs, ys, zs, h, n_slices)
    surface = go.Mesh3d(
        x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],
        i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],
        **surface_kw)
    
    traces = [surface]
    if not show_mesh:
        return traces
    
    line_kw.setdefault("showlegend", False)
    # horizontal mesh lines
    zsubs = np.linspace(zs, zs + h, n_sub + 1)
    for zc in zsubs:
        traces.append(go.Scatter3d(x=x, y=y, z=zc*np.ones_like(x), mode="lines", **line_kw))
    # vertical mesh lines
    for _x, _y in zip(x, y):
        traces.append(go.Scatter3d(x=[_x, _x], y=[_y, _y], z=[zs, zs + h], mode="lines", **line_kw))
    return traces

fig = go.Figure()
fig.add_traces(
    cylinder_traces(2, 5, 3, 1, 4, 20, n_sub=4, line_kw={"line_color":"#202020", "line_width": 3})
)
fig

原始答案:OP 可能与气缸盖有关。所以,我们需要创建:

  • 圆柱壁的表面,类似于您已经使用 matplotlib 所做的。
  • 底盖表面。
  • 顶盖表面。

注意:在下图中,我为气缸壁和气缸盖使用了不同的颜色以更好地形象化它们。

import numpy as np
import plotly.graph_objects as go

def cylinder(x, y, z, r, dz):
    """Create a cylindrical mesh located at x, y, z, with radius r and height dz"""
    center_z = np.linspace(0, dz, 15)
    theta = np.linspace(0, 2*np.pi, 15)
    theta_grid, z_grid = np.meshgrid(theta, center_z)
    x_grid = r * np.cos(theta_grid) + x
    y_grid = r * np.sin(theta_grid) + y
    z_grid = z_grid + z
    return x_grid, y_grid, z_grid

def circle(x, y, z, r):
    """Create a circular mesh located at x, y, z with radius r"""
    r_discr = np.linspace(0, r, 2)
    theta_discr = np.linspace(0, 2*np.pi, 15)
    r_grid, theta_grid = np.meshgrid(r_discr, theta_discr)
    x_circle = r_grid * np.cos(theta_grid) + x
    y_circle = r_grid * np.sin(theta_grid) + y
    z_circle = np.zeros_like(x_circle) + z
    return x_circle, y_circle, z_circle

# cylinder mesh
x_cyl, y_cyl, z_cyl = cylinder(0, 0, 0, 2, 8)
# bottom cap
x_circle1, y_circle1, z_circle1 = circle(0, 0, 0, 2)
# top cap
x_circle2, y_circle2, z_circle2 = circle(0, 0, 8, 2)

colorscale = [[0, '#636EFA'],
             [1, '#636EFA']]

fig = go.Figure([
    go.Surface(x=x_cyl, y=y_cyl, z=z_cyl, colorscale=colorscale, showscale=False, opacity=0.5),
    go.Surface(x=x_circle1, y=y_circle1, z=z_circle1, showscale=False, opacity=0.5),
    go.Surface(x=x_circle2, y=y_circle2, z=z_circle2, showscale=False, opacity=0.5),
])
fig