'Plotly - issue of display cylinder label

I have problem of not displaying the correct label when mouth pointing the cylinder item. The label of it can be shown correctly if mouth only points at the horizontal mesh lines. if mouth points at any position of vertical mesh lines it wont disaply the label of the item, rather it displays the label of the item close to it! see below graph.

when pointing at the bottom horizotal mesh lines enter image description here

when pointing at the main body vertical mesh lines enter image description here

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(12):
#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
# for num in range(120):
#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
# for num in range(60):
#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))


for num in range(12):
    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
for num in range(33):
    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
for num in range(32):
    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={}):
def cylinder_traces(r, xs, ys, zs, h, color, name, n_slices=40, show_mesh=True, n_sub=4, 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 : whether 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)
    # print("box_id: ", name)
    surface = go.Mesh3d(
        x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],
        i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],
        color=color, name=name)

    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",name=name, **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", name=name, **line_kw))
        # print("traces: ", traces)
    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",
        ),
    )

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


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


Sources

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

Source: Stack Overflow

Solution Source