使用 plotly 的具有正确纵横比的交互式 3D 图
interactive 3D plot with right aspect ratio using plotly
我正在使用 matplotlib 绘制 3D 图像(即 3D 装箱问题,如装载容器)。绘图时,length/width/height 会自动缩放,这与其实际值不成比例,即长度是高度的 6 倍,但图片显示三个轴的比例几乎相同(见下面第一张图片) .我知道 matplot3D 在以正确的纵横比绘制 3D 图方面有其局限性。
我需要的是像下图一样以更真实的方式绘制绘图。我们可以很容易地看到容器的 space 和其中加载的项目。许多人推荐使用 plotly,它支持很好的交互式 3D 绘图。我从来没有用过这样的工具来绘制 3D 图。有人可以帮助提供这样做的代码示例吗?谢谢
下面是我的代码:
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 bin packing 计算输出看起来像,其中“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",
),
)
- 稍微简化了你的打包代码
- 主要使用https://plotly.com/python/3d-mesh/#mesh-cube技术
- 开销相当大
- https://github.com/enzoruiz/3dbinpacking好像比较慢
- plotly 中每个项目的跟踪非常昂贵
- 已设置宽高比,但仅适用于更完整的容器
- plotly 布局可以进一步完善,但是您的配置的周转时间很长
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",
),
)
附加要求
如何制作单独的图(不要将子图合并到同一张图中,因为它们太小了,看不清);
- 简单地为每个 bin 创建一个图形
如何解决non-full容器的纵横比问题?如果没有满,我希望它显示空的 space 供用户查看;
- 更改为使用数据框根据数据计算纵横比
如何制作不同的边缘颜色以区别于每个单独的立方体
- 这是比较复杂的部分。使用
Scatter3d()` 以及构建立方体 co-ordinates 的顶点
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()
我正在使用 matplotlib 绘制 3D 图像(即 3D 装箱问题,如装载容器)。绘图时,length/width/height 会自动缩放,这与其实际值不成比例,即长度是高度的 6 倍,但图片显示三个轴的比例几乎相同(见下面第一张图片) .我知道 matplot3D 在以正确的纵横比绘制 3D 图方面有其局限性。
我需要的是像下图一样以更真实的方式绘制绘图。我们可以很容易地看到容器的 space 和其中加载的项目。许多人推荐使用 plotly,它支持很好的交互式 3D 绘图。我从来没有用过这样的工具来绘制 3D 图。有人可以帮助提供这样做的代码示例吗?谢谢
下面是我的代码:
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 bin packing 计算输出看起来像,其中“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",
),
)
- 稍微简化了你的打包代码
- 主要使用https://plotly.com/python/3d-mesh/#mesh-cube技术
- 开销相当大
- https://github.com/enzoruiz/3dbinpacking好像比较慢
- plotly 中每个项目的跟踪非常昂贵
- 已设置宽高比,但仅适用于更完整的容器
- plotly 布局可以进一步完善,但是您的配置的周转时间很长
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",
),
)
附加要求
如何制作单独的图(不要将子图合并到同一张图中,因为它们太小了,看不清);
- 简单地为每个 bin 创建一个图形
如何解决non-full容器的纵横比问题?如果没有满,我希望它显示空的 space 供用户查看;
- 更改为使用数据框根据数据计算纵横比
如何制作不同的边缘颜色以区别于每个单独的立方体
- 这是比较复杂的部分。使用
- 这是比较复杂的部分。使用
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()