使用plotly绘制具有正确纵横比的交互式3D图

发布于 2025-01-11 11:03:53 字数 7748 浏览 5 评论 0原文

我正在使用 matplotlib 绘制 3D 图像(即 3D 装箱问题,如装载容器)。绘图时,长/宽/高会自动缩放,与实际值不成比例,即长度是高度的6倍,但图片显示三个轴的比例几乎相同(见下面第一张图片) 。我知道 matplot3D 在以正确的纵横比绘制 3D 绘图方面有其局限性。

输入图片这里的描述

我需要的是以更现实的方式绘制一个图,如下图所示。我们可以很容易地看到集装箱的空间和装载的物品。很多人推荐使用plotly,它支持很好的交互式3D绘图。我从来没有使用过这样的工具来绘制 3D 绘图。有人可以帮忙提供这样做的代码示例吗?谢谢

enter这里的图像描述

下面是我的代码:

from py3dbp import Packer, Bin, Item
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import matplotlib.pyplot as plt
import random





def cuboid_data2(o, size=(1, 1, 1)):
    X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
         [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
         [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
         [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
         [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
         [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
    X = np.array(X).astype(float)
    for i in range(3):
        X[:, :, i] *= size[i]
    X += np.array(o)
    return X


def plotCubeAt2(positions, sizes=None, colors=None, **kwargs):
    if not isinstance(colors, (list, np.ndarray)): colors = ["C0"] * len(positions)
    if not isinstance(sizes, (list, np.ndarray)): sizes = [(1, 1, 1)] * len(positions)
    g = []
    for p, s, c in zip(positions, sizes, colors):
        g.append(cuboid_data2(p, size=s))
    return Poly3DCollection(np.concatenate(g),
                            facecolors=np.repeat(colors, 6), **kwargs)
  

  containers = [
    [1203, 235, 259],
    [1203, 235, 259],
    # [1202.4, 235, 269],
    # [12.024, 2.350, 2.69],
    # [12.024, 2.350, 2.69],
    # [12.024, 2.350, 2.69],
]



packer = Packer()

containerX = 0
containerY = 0
containerZ = 0



for i, t in enumerate(range(len(containers))):
    containerX = containers[t][0]
    containerY = containers[t][1]
    containerZ = containers[t][2]
    i += 1
    packer.add_bin(Bin('40HC-' + str(i), containerX, containerY, containerZ, 18000.0))



for i in range(50):
    packer.add_item(Item('BoxA_' + str(i), 44, 39, 70, 8.20))

for i in range(35):
    packer.add_item(Item('BoxB_' + str(i), 65, 38, 40, 14))

for i in range(31):
    packer.add_item(Item('BoxC_' + str(i), 43, 52, 47, 10))

for i in range(38):
    packer.add_item(Item('BoxD_' + str(i), 60, 45, 40, 14))

for i in range(11):
    packer.add_item(Item('BoxE_' + str(i), 42, 46, 54, 9.70))

for i in range(525):
    packer.add_item(Item('BoxF_' + str(i), 62, 45, 35, 14.5))




# packer.pack()
# packer.pack(bigger_first=False)
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)





for b in packer.bins:
    positions = []
    sizes = []
    colors = []
    print(":::::::::::", b.string())

    print("FITTED ITEMS:")
    for item in b.items:
        print("====> ", item.string())
        x = float(item.position[0])
        y = float(item.position[1])
        z = float(item.position[2])
        positions.append((x, y, z))
        sizes.append(
            (float(item.get_dimension()[0]), float(item.get_dimension()[1]), float(item.get_dimension()[2])))

        colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
        if item.width == 44:
            colors.append(colorList[0])
        if item.width == 65:
            colors.append(colorList[1])
        if item.width == 43:
            colors.append(colorList[2])
        if item.width == 60:
            colors.append(colorList[3])
        if item.width == 42:
            colors.append(colorList[4])
        if item.width == 62:
            colors.append(colorList[5])


    print("UNFITTED ITEMS:")
    for item in b.unfitted_items:
        print("====> ", item.string())

    print("***************************************************")
    print("***************************************************")

    # colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
    #
    # for i in range(len(b.items)):
    #   f = random.randint(0, 7)
    #   colors.append(colorList[f])


    if len(colors) > 0:
        fig = plt.figure()
        fig.canvas.set_window_title(b.string().split("(")[0])
        ax = fig.gca(projection='3d')
        ax.set_aspect('auto')
        pc = plotCubeAt2(positions, sizes, colors=colors, edgecolor="k")
        ax.add_collection3d(pc)

        ax.set_xlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[0])])
        ax.set_ylim([0, float(b.string().split(",")[0].split("(")[1].split("x")[1])])
        ax.set_zlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[2])])



plt.show()

上面代码的 3D 装箱计算输出如下所示,其中“pos”应该是 3D 位置数据:

====>  BoxC_16(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1024.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_17(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1088.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_18(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1152.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_19(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1216.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_20(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1280.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_21(64.000x37.000x52.000, weight: 0.000) pos([0, Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_22(64.000x37.000x52.000, weight: 0.000) pos([Decimal('64.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_23(64.000x37.000x52.000, weight: 0.000) pos([Decimal('128.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_24(64.000x37.000x52.000, weight: 0.000) pos([Decimal('192.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_25(64.000x37.000x52.000, weight: 0.000) pos([Decimal('256.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_26(64.000x37.000x52.000, weight: 0.000) pos([Decimal('320.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)

UPADTE:(绘制外容器框架)

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

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

ar = 4
xr = max(d["x"].max()) - min(d["x"].min())
fig.update_layout(
    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",
    ),
)

在此处输入图像描述

I am using matplotlib to plot 3D image (i.e 3D bin packing problem like loading containers). when plotting, the length/width/height is automatically scaled which is not proportional to its actual value, i.e. the length is 6 times bigger than the height but the picture shows almost the same scale for the three axis (see below first one pic). I understood that matplot3D has its limitations on drawing 3D plot with right aspect ratio.

enter image description here

What I need is to draw a plot in more realistic manner like the below graph. we could easily see the space of the container and the items loaded in it. Many people recommand using plotly and it supports nice interactive 3D plotting. I have never used such tool to draw 3D plot. Can someone help to provide an example of code doing so? thanks

enter image description here

below is my code:

from py3dbp import Packer, Bin, Item
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import matplotlib.pyplot as plt
import random





def cuboid_data2(o, size=(1, 1, 1)):
    X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
         [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
         [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
         [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
         [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
         [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
    X = np.array(X).astype(float)
    for i in range(3):
        X[:, :, i] *= size[i]
    X += np.array(o)
    return X


def plotCubeAt2(positions, sizes=None, colors=None, **kwargs):
    if not isinstance(colors, (list, np.ndarray)): colors = ["C0"] * len(positions)
    if not isinstance(sizes, (list, np.ndarray)): sizes = [(1, 1, 1)] * len(positions)
    g = []
    for p, s, c in zip(positions, sizes, colors):
        g.append(cuboid_data2(p, size=s))
    return Poly3DCollection(np.concatenate(g),
                            facecolors=np.repeat(colors, 6), **kwargs)
  

  containers = [
    [1203, 235, 259],
    [1203, 235, 259],
    # [1202.4, 235, 269],
    # [12.024, 2.350, 2.69],
    # [12.024, 2.350, 2.69],
    # [12.024, 2.350, 2.69],
]



packer = Packer()

containerX = 0
containerY = 0
containerZ = 0



for i, t in enumerate(range(len(containers))):
    containerX = containers[t][0]
    containerY = containers[t][1]
    containerZ = containers[t][2]
    i += 1
    packer.add_bin(Bin('40HC-' + str(i), containerX, containerY, containerZ, 18000.0))



for i in range(50):
    packer.add_item(Item('BoxA_' + str(i), 44, 39, 70, 8.20))

for i in range(35):
    packer.add_item(Item('BoxB_' + str(i), 65, 38, 40, 14))

for i in range(31):
    packer.add_item(Item('BoxC_' + str(i), 43, 52, 47, 10))

for i in range(38):
    packer.add_item(Item('BoxD_' + str(i), 60, 45, 40, 14))

for i in range(11):
    packer.add_item(Item('BoxE_' + str(i), 42, 46, 54, 9.70))

for i in range(525):
    packer.add_item(Item('BoxF_' + str(i), 62, 45, 35, 14.5))




# packer.pack()
# packer.pack(bigger_first=False)
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)





for b in packer.bins:
    positions = []
    sizes = []
    colors = []
    print(":::::::::::", b.string())

    print("FITTED ITEMS:")
    for item in b.items:
        print("====> ", item.string())
        x = float(item.position[0])
        y = float(item.position[1])
        z = float(item.position[2])
        positions.append((x, y, z))
        sizes.append(
            (float(item.get_dimension()[0]), float(item.get_dimension()[1]), float(item.get_dimension()[2])))

        colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
        if item.width == 44:
            colors.append(colorList[0])
        if item.width == 65:
            colors.append(colorList[1])
        if item.width == 43:
            colors.append(colorList[2])
        if item.width == 60:
            colors.append(colorList[3])
        if item.width == 42:
            colors.append(colorList[4])
        if item.width == 62:
            colors.append(colorList[5])


    print("UNFITTED ITEMS:")
    for item in b.unfitted_items:
        print("====> ", item.string())

    print("***************************************************")
    print("***************************************************")

    # colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
    #
    # for i in range(len(b.items)):
    #   f = random.randint(0, 7)
    #   colors.append(colorList[f])


    if len(colors) > 0:
        fig = plt.figure()
        fig.canvas.set_window_title(b.string().split("(")[0])
        ax = fig.gca(projection='3d')
        ax.set_aspect('auto')
        pc = plotCubeAt2(positions, sizes, colors=colors, edgecolor="k")
        ax.add_collection3d(pc)

        ax.set_xlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[0])])
        ax.set_ylim([0, float(b.string().split(",")[0].split("(")[1].split("x")[1])])
        ax.set_zlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[2])])



plt.show()

The 3D bin packing calculation output from above code looks like, where "pos" should be the 3D position data:

====>  BoxC_16(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1024.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_17(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1088.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_18(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1152.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_19(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1216.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_20(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1280.000'), 0, 0]) rt(0) vol(123136.000)
====>  BoxC_21(64.000x37.000x52.000, weight: 0.000) pos([0, Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_22(64.000x37.000x52.000, weight: 0.000) pos([Decimal('64.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_23(64.000x37.000x52.000, weight: 0.000) pos([Decimal('128.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_24(64.000x37.000x52.000, weight: 0.000) pos([Decimal('192.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_25(64.000x37.000x52.000, weight: 0.000) pos([Decimal('256.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====>  BoxC_26(64.000x37.000x52.000, weight: 0.000) pos([Decimal('320.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)

UPADTE: (drawing outer container frame)

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

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

ar = 4
xr = max(d["x"].max()) - min(d["x"].min())
fig.update_layout(
    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",
    ),
)

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

知足的幸福 2025-01-18 11:03:53
from py3dbp import Packer, Bin, Item
import plotly.graph_objects as go
from plotly.subplots import make_subplots

containers = [
    [1203, 235, 259],
    [1203, 235, 259],
]

packer = Packer()

for i, t in enumerate(containers):
    packer.add_bin(Bin("40HC-" + str(i + 1), *t, 18000.0))

pbins = {
    "BoxA": {"n": 50, "s": [44, 39, 70, 8.20]},
    "BoxB": {"n": 35, "s": [65, 38, 40, 14]},
    "BoxC": {"n": 31, "s": [43, 52, 47, 10]},
    "BoxD": {"n": 38, "s": [60, 45, 40, 14]},
    "BoxE": {"n": 11, "s": [65, 38, 40, 14]},
    "BoxF": {"n": 525, "s": [62, 45, 35, 14.5]},
}

for name, cfg in pbins.items():
    for i in range(cfg["n"]): 
        packer.add_item(Item(f"{name}_{i}", *cfg["s"]))

# packer.pack()
# packer.pack(bigger_first=False)
print("about to pack")
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)
print("packed")


### 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],
    }

# 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.get_dimension())]
    )
    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"}]])

# 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)), row=row+1, col=1)

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

在此处输入图像描述

附加要求

  1. 如何制作单独的图(不要将子图合并在同一图中,因为它们太小而看不到);

    • 简单地为每个 bin 创建一个数字
  2. 如何解决非满容器的纵横比问题?如果未满,我希望它显示空白空间供用户查看;

    • 更改为使用数据框根据数据计算出宽高比
  3. 如何制作不同的边缘颜色以区分每个单独的立方体

    • 这是更复杂的部分。使用 Scatter3d()` 以及构建立方体顶点的坐标
import pandas as pd

# push data into a data frame to enable more types of analysis
df = pd.DataFrame(
    [
        {
            "bin_name": b.name,
            "bin_index": i,
            **packer_to_plotly(item),
            **{d: v for v, d in zip(item.get_dimension(), 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",
        )
    )
    ar = 4
    xr = max(d["x"].max()) - min(d["x"].min())
    fig.update_layout(
        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()

在此处输入图像描述

from py3dbp import Packer, Bin, Item
import plotly.graph_objects as go
from plotly.subplots import make_subplots

containers = [
    [1203, 235, 259],
    [1203, 235, 259],
]

packer = Packer()

for i, t in enumerate(containers):
    packer.add_bin(Bin("40HC-" + str(i + 1), *t, 18000.0))

pbins = {
    "BoxA": {"n": 50, "s": [44, 39, 70, 8.20]},
    "BoxB": {"n": 35, "s": [65, 38, 40, 14]},
    "BoxC": {"n": 31, "s": [43, 52, 47, 10]},
    "BoxD": {"n": 38, "s": [60, 45, 40, 14]},
    "BoxE": {"n": 11, "s": [65, 38, 40, 14]},
    "BoxF": {"n": 525, "s": [62, 45, 35, 14.5]},
}

for name, cfg in pbins.items():
    for i in range(cfg["n"]): 
        packer.add_item(Item(f"{name}_{i}", *cfg["s"]))

# packer.pack()
# packer.pack(bigger_first=False)
print("about to pack")
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)
print("packed")


### 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],
    }

# 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.get_dimension())]
    )
    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"}]])

# 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)), row=row+1, col=1)

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

enter image description here

additional requirements

  1. how to make individual plot (not merge subplot in the same graph as they too small to see);

    • simple create a figure per bin
  2. how to solve the problem of aspect ratio for non-full container? if not full, I want it to display the empty space for user to see;

    • change to using dataframe to work out aspect ratios from data
  3. how to make a different edge color in order to distinguish from each individual cube

    • this is the more complex part. use Scatter3d()` as well build co-ordinates of vertices of cube
import pandas as pd

# push data into a data frame to enable more types of analysis
df = pd.DataFrame(
    [
        {
            "bin_name": b.name,
            "bin_index": i,
            **packer_to_plotly(item),
            **{d: v for v, d in zip(item.get_dimension(), 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",
        )
    )
    ar = 4
    xr = max(d["x"].max()) - min(d["x"].min())
    fig.update_layout(
        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()

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文