Plotly - 绘制立方体和圆柱体项目
Plotly - drawing both cube and cylinder items
由于 3D 装箱模型,我正在处理绘制圆柱体项目的问题。有一个简单的示例绘制圆柱体项目,可以参考:
但是,这里我有一些困难,无法在我自己的模型绘制函数中使用,这可能是由于现有的绘制立方体项目的方式和 add_trace 方法。如何在给定当前代码结构的情况下同时绘制立方体和圆柱体项目?
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})
从项目中绘制圆柱体的更新:
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})
编辑:让我们使用不同的方法生成圆柱体。
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})
原始答案:问题在这里:
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
,顾名思义,加单迹。相反,你给它三个不同的痕迹。您需要做的就是使用 add_traces
,像这样:
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)
])
由于 3D 装箱模型,我正在处理绘制圆柱体项目的问题。有一个简单的示例绘制圆柱体项目,可以参考:
但是,这里我有一些困难,无法在我自己的模型绘制函数中使用,这可能是由于现有的绘制立方体项目的方式和 add_trace 方法。如何在给定当前代码结构的情况下同时绘制立方体和圆柱体项目?
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})
从项目中绘制圆柱体的更新:
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})
编辑:让我们使用不同的方法生成圆柱体。
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})
原始答案:问题在这里:
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
,顾名思义,加单迹。相反,你给它三个不同的痕迹。您需要做的就是使用 add_traces
,像这样:
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)
])