'Plotly - drawing both cube and cylinder items

I am dealing with an issue of drawing cylinder item as a result of a 3D bin packing model. There is a simple example drawing cylinder item which can be referenced at: Plotly - how to plot Cylinder?

However, here I have the difficaulties for make it work in my own model drawing function, possibly due to the existing way of drawing the cube items and add_trace method. How to enable drawing both the cube and cylinder items given the current code structure?

from py3dbp import Packer, Bin, Item, Painter
import time
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly
import pandas as pd
start = time.time()
import numpy as np


#-----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------
###library reference: https://github.com/jerry800416/3D-bin-packing 

# init packing function
packer = Packer()
#  init bin
# box = Bin('40HC-1', (1203, 235, 259), 18000.0,0,0)
box = Bin('40HC-1', (1202.4, 235, 269.7), 18000.0,0,0)
packer.addBin(box)


# add item
for num in range(10):
    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}",'cube',(120, 120, 120), 8.20,1,100,True,'red'))
for num in range(55):
    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}",'cube',(65, 38, 90), 14,1,100,True,'blue'))
for num in range(50):
    packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}",'cube',(143, 52, 47), 10,1,100,True,'gray'))


# calculate packing 
packer.pack(bigger_first=True,distribute_items=False,fix_point=True,number_of_decimals=0)

# print result
b = packer.bins[0]
volume = b.width * b.height * b.depth
print(":::::::::::", b.string())

print("FITTED ITEMS:")
volume_t = 0
volume_f = 0
unfitted_name = ''
for item in b.items:
    print("partno : ",item.partno)
    print("color : ",item.color)
    print("position : ",item.position)
    print("rotation type : ",item.rotation_type)
    print("W*H*D : ",str(item.width) +'*'+ str(item.height) +'*'+ str(item.depth))
    print("volume : ",float(item.width) * float(item.height) * float(item.depth))
    print("weight : ",float(item.weight))
    volume_t += float(item.width) * float(item.height) * float(item.depth)
    print("***************************************************")
print("***************************************************")
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
    print("partno : ",item.partno)
    print("color : ",item.color)
    print("W*H*D : ",str(item.width) +'*'+ str(item.height) +'*'+ str(item.depth))
    print("volume : ",float(item.width) * float(item.height) * float(item.depth))
    print("weight : ",float(item.weight))
    volume_f += float(item.width) * float(item.height) * float(item.depth)
    unfitted_name += '{},'.format(item.partno)
    print("***************************************************")
print("***************************************************")
print('space utilization : {}%'.format(round(volume_t / float(volume) * 100 ,2)))
print('residual volumn : ', float(volume) - volume_t )
print('unpack item : ',unfitted_name)
print('unpack item volumn : ',volume_f)
print("gravity distribution : ",b.gravity)
stop = time.time()
print('used time : ',stop - start)

# draw results
# painter = Painter(b)
# painter.plotBoxAndItems()

#----------------------------------end---------------------------------------------


############################### 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


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, 35)
    theta = np.linspace(0, 2*np.pi, 35)
    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, 35)
    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




# 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 figure for each bin
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)))

    # some first attempts at 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",
        ),
    )

    # plotly.offline.plot(fig, filename='C:/Users/Jie/Desktop/3D_BinPack_' + str(row) + '.html', auto_open=False,
    #                       config={'displaylogo': False})


    # 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 = go.Figure(go.Scatter3d(x=x, y=y, z=z, mode="lines", line_width=4))

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


    # -----------------newly added code to test plotting cylinder: NOT WORKING!-------------------
    # cylinder mesh
    x_cyl, y_cyl, z_cyl = cylinder(0, 0, 0, 50, 80)
    # bottom cap
    x_circle1, y_circle1, z_circle1 = circle(0, 0, 0, 50)
    # top cap
    x_circle2, y_circle2, z_circle2 = circle(0, 0, 80, 50)

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


    fig.add_trace(
        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),
    )

    # -----------------end for newly added code to test plotting cylinder-------------------




    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(config= {'displaylogo': False})

UPDATE for drawing cylinder from items:

from py3dbp import Packer, Bin, Item, Painter
import time
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly
import pandas as pd

start = time.time()
import numpy as np

# -----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------
###library reference: https://github.com/jerry800416/3D-bin-packing

# init packing function
packer = Packer()
#  init bin
# box = Bin('40HC-1', (1203, 235, 259), 18000.0,0,0)
box = Bin('40HC-1', (1202.4, 235, 269.7), 18000.0, 0, 0)
packer.addBin(box)


# add item
# for num in range(10):
#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cube', (120, 120, 120), 8.20, 1, 100, True, 'red'))
# for num in range(55):
#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
# for num in range(50):
#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))


# add item
for num in range(3):
    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
for num in range(3):
    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
for num in range(2):
    packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))



# calculate packing
packer.pack(bigger_first=True, distribute_items=False, fix_point=True, number_of_decimals=0)

# print result
b = packer.bins[0]
volume = b.width * b.height * b.depth
print(":::::::::::", b.string())

print("FITTED ITEMS:")
volume_t = 0
volume_f = 0
unfitted_name = ''
for item in b.items:
    print("partno : ", item.partno)
    print("color : ", item.color)
    print("position : ", item.position)
    print("type of : ", item.typeof)
    print("rotation type : ", item.rotation_type)
    print("W*H*D : ", str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
    print("volume : ", float(item.width) * float(item.height) * float(item.depth))
    print("weight : ", float(item.weight))
    volume_t += float(item.width) * float(item.height) * float(item.depth)
    print("***************************************************")
print("***************************************************")
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
    print("partno : ", item.partno)
    print("color : ", item.color)
    print("W*H*D : ", str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
    print("volume : ", float(item.width) * float(item.height) * float(item.depth))
    print("weight : ", float(item.weight))
    volume_f += float(item.width) * float(item.height) * float(item.depth)
    unfitted_name += '{},'.format(item.partno)
    print("***************************************************")
print("***************************************************")
print('space utilization : {}%'.format(round(volume_t / float(volume) * 100, 2)))
print('residual volumn : ', float(volume) - volume_t)
print('unpack item : ', unfitted_name)
print('unpack item volumn : ', volume_f)
print("gravity distribution : ", b.gravity)
stop = time.time()
print('used time : ', stop - start)


# draw results
# painter = Painter(b)
# painter.plotBoxAndItems()

# ----------------------------------end---------------------------------------------


############################### 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


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


# 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
    # print("item.typeof!!!!!! ", item.typeof)
    # ret["typeof"] = item.typeof
    ret["color"] = colors[ord(item.name.split("_")[0][-1]) - ord("A")]
    # ret["typeof"] = item.typeof
    return ret


# create a figure for each bin
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)))

    # some first attempts at 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),
            "item_typeof": item.typeof,
            **{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
    ]
)
print("dataframe: \n", df['item_typeof'])

# 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():
        print("_, ", _,)
        print("r ", r)
        if r["item_typeof"] == 'cube':
            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",
                )
            )
        else:
            radius = float(r["w"])/2
            height = float(r["l"])
            x_list = r["x"]
            y_list = r["y"]
            z_list = r["z"]
            x_min = float(min(x_list))
            x_max = float(max(x_list))
            y_min = float(min(y_list))
            y_max = float(max(y_list))
            x_cor = (x_max - x_min)/2
            y_cor = (y_max - y_min)/2
            z_cor = float(min(z_list))
            print("xyz! ", x_cor,y_cor,z_cor)
            fig.add_traces(
                cylinder_traces(radius, x_cor, y_cor, z_cor, height, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})
            )


    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",
        )
    )

    # -----------------newly added code to test plotting cylinder
    # fig.add_traces(
    #   cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})
    # )

    # -----------------end for newly added code to test plotting cylinder-------------------

    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)),
    #       aspectmode="data",
    #   ),
    # )
    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(config={'displaylogo': False})

enter image description here



Solution 1:[1]

EDIT: let's use a different method to generate the cylinder.

from py3dbp import Packer, Bin, Item, Painter
import time
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly
import pandas as pd
start = time.time()
import numpy as np


#-----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------
###library reference: https://github.com/jerry800416/3D-bin-packing 

# init packing function
packer = Packer()
#  init bin
# box = Bin('40HC-1', (1203, 235, 259), 18000.0,0,0)
box = Bin('40HC-1', (1202.4, 235, 269.7), 18000.0,0,0)
packer.addBin(box)


# add item
for num in range(10):
    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}",'cube',(120, 120, 120), 8.20,1,100,True,'red'))
for num in range(55):
    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}",'cube',(65, 38, 90), 14,1,100,True,'blue'))
for num in range(50):
    packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}",'cube',(143, 52, 47), 10,1,100,True,'gray'))


# calculate packing 
packer.pack(bigger_first=True,distribute_items=False,fix_point=True,number_of_decimals=0)

# print result
b = packer.bins[0]
volume = b.width * b.height * b.depth
print(":::::::::::", b.string())

print("FITTED ITEMS:")
volume_t = 0
volume_f = 0
unfitted_name = ''
for item in b.items:
    print("partno : ",item.partno)
    print("color : ",item.color)
    print("position : ",item.position)
    print("rotation type : ",item.rotation_type)
    print("W*H*D : ",str(item.width) +'*'+ str(item.height) +'*'+ str(item.depth))
    print("volume : ",float(item.width) * float(item.height) * float(item.depth))
    print("weight : ",float(item.weight))
    volume_t += float(item.width) * float(item.height) * float(item.depth)
    print("***************************************************")
print("***************************************************")
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
    print("partno : ",item.partno)
    print("color : ",item.color)
    print("W*H*D : ",str(item.width) +'*'+ str(item.height) +'*'+ str(item.depth))
    print("volume : ",float(item.width) * float(item.height) * float(item.depth))
    print("weight : ",float(item.weight))
    volume_f += float(item.width) * float(item.height) * float(item.depth)
    unfitted_name += '{},'.format(item.partno)
    print("***************************************************")
print("***************************************************")
print('space utilization : {}%'.format(round(volume_t / float(volume) * 100 ,2)))
print('residual volumn : ', float(volume) - volume_t )
print('unpack item : ',unfitted_name)
print('unpack item volumn : ',volume_f)
print("gravity distribution : ",b.gravity)
stop = time.time()
print('used time : ',stop - start)

# draw results
# painter = Painter(b)
# painter.plotBoxAndItems()

#----------------------------------end---------------------------------------------


############################### 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


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




# 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 figure for each bin
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)))

    # some first attempts at 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",
        ),
    )

    # plotly.offline.plot(fig, filename='C:/Users/Jie/Desktop/3D_BinPack_' + str(row) + '.html', auto_open=False,
    #                       config={'displaylogo': False})


    # 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 = go.Figure(go.Scatter3d(x=x, y=y, z=z, mode="lines", line_width=4))

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


    # -----------------newly added code to test plotting cylinder
    fig.add_traces(
         cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={"line_color":"#202020", "line_width": 3})
    )
   

    # -----------------end for newly added code to test plotting cylinder-------------------




    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)),
            aspectmode="data",
        ),
    )


    fig.show(config= {'displaylogo': False})

enter image description here

ORIGINAL ANSWER: The problem is here:

    fig.add_trace(
        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),
    )

add_trace, as the name suggest, add a single trace. Instead, you are giving it three different traces. All you need to do is to use add_traces, like this:

fig.add_traces([
        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)
    ])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1