我有一个 3D 装箱模型,它使用绘图来绘制输出图。我注意到绘制了 600+ 个项目,生成 html 文件需要很长时间,文件大小为 89M,这太疯狂了(我怀疑可能存在一些巨大的重复,或者由 \xe2\x80\x9cadd_trace\ 引起) xe2\x80\x9d 方法来绘制单个项目图)。为什么它会产生这么大的文件?如何将大小控制在可接受的水平(不超过 5M,因为我需要在我的网站中渲染它)。非常感谢您的帮助。
\n\n下面是我的完整代码(请跳过模型代码并从绘图代码中查看)
\nfrom py3dbp import Packer, Bin, Item, Painter\nimport time\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nimport plotly\nimport pandas as pd\n\nstart = time.time()\nimport numpy as np\n\n# -----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------\n###library reference: https://github.com/jerry800416/3D-bin-packing\n\n# init packing function\npacker = Packer()\n# init bin\n# box = Bin(\'40HC-1\', (1203, 235, 259), 18000.0,0,0)\nbox = Bin(\'40HC-1\', (1202.4, 235, 269.7), 18000.0, 0, 0)\npacker.addBin(box)\n\n\n# add item\n# for num in range(10):\n# packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cube\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(55):\n# packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(50):\n# packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\n# add item\n# for num in range(12):\n# packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cylinder\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(120):\n# packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(60):\n# packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\n# for num in range(12):\n# packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cylinder\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(33):\n# packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(32):\n# packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\nfor num in range(252):\n packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cube\', (65, 33, 26), 2.06, 1, 100, True, \'red\'))\nfor num in range(222):\n packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (84, 42.5, 33), 2.72, 1, 100, True, \'blue\'))\nfor num in range(270):\n packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (48, 48, 38), 2.17, 1, 100, True, \'gray\'))\n\n\n\n# calculate packing\npacker.pack(bigger_first=True, distribute_items=False, fix_point=True, number_of_decimals=0)\n\n# print result\nb = packer.bins[0]\nvolume = b.width * b.height * b.depth\nprint(":::::::::::", b.string())\n\nprint("FITTED ITEMS:")\nvolume_t = 0\nvolume_f = 0\nunfitted_name = \'\'\nfor item in b.items:\n print("partno : ", item.partno)\n print("color : ", item.color)\n print("position : ", item.position)\n print("type of : ", item.typeof)\n print("rotation type : ", item.rotation_type)\n print("W*H*D : ", str(item.width) + \'*\' + str(item.height) + \'*\' + str(item.depth))\n print("volume : ", float(item.width) * float(item.height) * float(item.depth))\n print("weight : ", float(item.weight))\n volume_t += float(item.width) * float(item.height) * float(item.depth)\n print("***************************************************")\nprint("***************************************************")\nprint("UNFITTED ITEMS:")\nfor item in b.unfitted_items:\n print("partno : ", item.partno)\n print("color : ", item.color)\n print("W*H*D : ", str(item.width) + \'*\' + str(item.height) + \'*\' + str(item.depth))\n print("volume : ", float(item.width) * float(item.height) * float(item.depth))\n print("weight : ", float(item.weight))\n volume_f += float(item.width) * float(item.height) * float(item.depth)\n unfitted_name += \'{},\'.format(item.partno)\n print("***************************************************")\nprint("***************************************************")\nprint(\'space utilization : {}%\'.format(round(volume_t / float(volume) * 100, 2)))\nprint(\'residual volumn : \', float(volume) - volume_t)\nprint(\'unpack item : \', unfitted_name)\nprint(\'unpack item volumn : \', volume_f)\nprint("gravity distribution : ", b.gravity)\nstop = time.time()\nprint(\'used time : \', stop - start)\n\n\n# draw results\n# painter = Painter(b)\n# painter.plotBoxAndItems()\n\n# ----------------------------------end---------------------------------------------\n\n\n############################### PLOTLY ############################################\n# https://plotly.com/python/3d-mesh/#mesh-cube\ndef vertices(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1):\n return {\n "x": [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax],\n "y": [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin],\n "z": [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax],\n "i": [7, 0, 0, 0, 4, 4, 6, 1, 4, 0, 3, 6],\n "j": [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],\n "k": [0, 7, 2, 3, 6, 7, 1, 6, 5, 5, 7, 2],\n }\n\n\ndef parallelipipedic_frame(xm, xM, ym, yM, zm, zM):\n # defines the coords of each segment followed by None, if the line is\n # discontinuous\n x = [xm, xM, xM, xm, xm, None, xm, xM, xM, xm, xm, None, xm, xm, None, xM, xM,\n None, xM, xM, None, xm, xm]\n y = [ym, ym, yM, yM, ym, None, ym, ym, yM, yM, ym, None, ym, ym, None, ym, ym,\n None, yM, yM, None, yM, yM]\n z = [zm, zm, zm, zm, zm, None, zM, zM, zM, zM, zM, None, zm, zM, None, zm, zM,\n None, zm, zM, None, zm, zM]\n return x, y, z\n\n\ndef slice_triangles(z, n, i, j, k, l):\n """Create the triangles of a single slice"""\n return [[z, j, i], [i, j, l], [l, j, k], [k, n, l]]\n\n\ndef cylinder_mesh(r, xs, ys, zs, h, n_slices=40):\n """Create a cylindrical mesh"""\n theta = np.linspace(0, 2 * np.pi, n_slices + 1)\n x = xs + r * np.cos(theta)\n y = ys + r * np.sin(theta)\n z1 = zs + 0 * np.ones_like(x)\n z2 = (zs + h) * np.ones_like(x)\n\n # index of the final point in the mesh\n n = n_slices * 2 + 1\n\n # build triangulation\n triangles = []\n for s in range(1, n_slices + 1):\n j = (s + 1) if (s <= n_slices - 1) else 1\n k = j + n_slices if (s <= n_slices - 1) else n_slices + 1\n l = s + n_slices\n triangles += slice_triangles(0, n, s, j, k, l)\n triangles = np.array(triangles)\n\n # coordinates of the vertices\n x_coords = np.hstack([xs, x[:-1], x[:-1], xs])\n y_coords = np.hstack([ys, y[:-1], y[:-1], ys])\n z_coords = np.hstack([zs, z1[:-1], z2[:-1], (zs + h)])\n vertices = np.stack([x_coords, y_coords, z_coords]).T\n\n return vertices, triangles, x, y, z1, z2\n\n# def cylinder_traces(r, xs, ys, zs, h, n_slices=40, show_mesh=True, n_sub=4, surface_kw={}, line_kw={}):\ndef cylinder_traces(r, xs, ys, zs, h, color, name, n_slices=40, show_mesh=True, n_sub=4, line_kw={}):\n """\n r : radius\n xs, ys, zs : start position of the cylinder\n h : height of the cylinder\n n_slices : number of slices in the circumferential direction\n show_mesh : whether to display pseudo-wireframe\n n_sub : number of subdivision in along the height for the pseudo-wireframe\n surface_kw : customize the appearance of the surface\n line_kw : customize the appearance of the wireframe\n """\n vertices, triangles, x, y, z1, z2 = cylinder_mesh(r, xs, ys, zs, h, n_slices)\n # surface = go.Mesh3d(\n # x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],\n # i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],\n # **surface_kw)\n # print("box_id: ", name)\n surface = go.Mesh3d(\n x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],\n i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],\n color=color, name=name)\n\n traces = [surface]\n if not show_mesh:\n return traces\n\n line_kw.setdefault("showlegend", False)\n # horizontal mesh lines\n zsubs = np.linspace(zs, zs + h, n_sub + 1)\n for zc in zsubs:\n traces.append(go.Scatter3d(x=x, y=y, z=zc * np.ones_like(x), mode="lines",name=name, **line_kw))\n # vertical mesh lines\n for _x, _y in zip(x, y):\n traces.append(go.Scatter3d(x=[_x, _x], y=[_y, _y], z=[zs, zs + h], mode="lines", name=name, **line_kw))\n # print("traces: ", traces)\n return traces\n\n\n# take a packer item and build parameters to a plotly mesh3d cube\ndef packer_to_plotly(item):\n colors = ["crimson", "limegreen", "green", "red", "cyan", "magenta", "yellow"]\n ret = vertices(\n *item.position, *[sum(x) for x in zip(item.position, item.getDimension())]\n )\n ret["name"] = item.name\n ret["color"] = colors[ord(item.name.split("_")[0][-1]) - ord("A")]\n return ret\n\n\n# create a figure for each bin\nfig = go.Figure()\n\n# add a trace for each packer item\nfor row, pbin in enumerate(packer.bins):\n for item in pbin.items:\n fig.add_trace(go.Mesh3d(packer_to_plotly(item)))\n\n # some first attempts at sorting out layout, prmarily aspect ratio\n fig.update_layout(\n margin={"l": 0, "r": 0, "t": 0, "b": 0},\n autosize=False,\n scene=dict(\n camera=dict(\n # eye=dict(x=0.1, y=0.1, z=1.5)\n ),\n aspectratio=dict(x=1, y=.2, z=0.2),\n aspectmode="manual",\n ),\n )\n\n# push data into a data frame to enable more types of analysis\ndf = pd.DataFrame(\n [\n {\n "bin_name": b.partno,\n "bin_index": i,\n **packer_to_plotly(item),\n "item_typeof": item.typeof,\n **{d: v for v, d in zip(item.getDimension(), list("hwl"))},\n **{d + d: v for v, d in zip(item.position, list("xyz"))},\n }\n for i, b in enumerate(packer.bins)\n for item in b.items\n ]\n)\n# print("dataframe: \\n", df[\'item_typeof\'])\n\n# create a figure for each container (bin)\nfor pbin, d in df.groupby("bin_name"):\n fig = go.Figure()\n xx = []\n yy = []\n zz = []\n\n # create a trace for each box (bin)\n for _, r in d.iterrows():\n # print("_, ", _,)\n # print("r ", r)\n if r["item_typeof"] == \'cube\':\n fig.add_trace(\n go.Mesh3d(r[["x", "y", "z", "i", "j", "k", "name", "color"]].to_dict())\n )\n xx += [r.xx, r.xx + r.h, r.xx + r.h, r.xx, r.xx, None] * 2 + [r.xx] * 5 + [None]\n yy += [r.yy, r.yy, r.yy + r.w, r.yy + r.w, r.yy, None] * 2 + [\n r.yy,\n r.yy + r.w,\n r.yy + r.w,\n r.yy,\n r.yy,\n None,\n ]\n zz += (\n [r.zz] * 5\n + [None]\n + [r.zz + r.l] * 5\n + [None]\n + [r.zz, r.zz, r.zz + r.l, r.zz + r.l, r.zz, None]\n )\n\n fig.add_trace(\n go.Scatter3d(\n x=xx,\n y=yy,\n z=zz,\n mode="lines",\n line_color="black",\n line_width=2,\n hoverinfo="skip",\n )\n )\n else:\n name = r["name"]\n color = r["color"]\n radius = float(r["w"])/2\n height = float(r["l"])\n x_list = r["x"]\n # print("x_list: ", x_list)\n y_list = r["y"]\n # print("y_list: ", y_list)\n z_list = r["z"]\n x_min = float(min(x_list))\n # print("x_min ", x_min)\n x_max = float(max(x_list))\n # print("x_max ", x_max)\n y_min = float(min(y_list))\n y_max = float(max(y_list))\n x_cor = x_min + (x_max - x_min)/2\n y_cor = y_min + (y_max - y_min)/2\n z_cor = float(min(z_list))\n # print("xyz! ", x_cor,y_cor,z_cor)\n # colorscale = [[0, \'#636EFA\'], [1, \'#636EFA\']]\n # print("colorscale ", colorscale)\n fig.add_traces(\n # cylinder_traces(radius, x_cor, y_cor, z_cor, height, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})\n # )\n cylinder_traces(radius, x_cor, y_cor, z_cor, height, color, name, n_sub=1,\n line_kw={"line_color": "#202020", "line_width": 3}))\n\n\n x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)\n\n fig.add_trace(\n go.Scatter3d(\n x=x,\n y=y,\n z=z,\n mode="lines",\n line_color="blue",\n line_width=2,\n hoverinfo="skip",\n )\n )\n\n # -----------------newly added code to test plotting cylinder\n # fig.add_traces(\n # cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})\n # )\n\n # -----------------end for newly added code to test plotting cylinder-------------------\n\n ar = 4\n xr = max(d["x"].max()) - min(d["x"].min())\n # fig.update_layout(\n # showlegend=False,\n # title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},\n # margin={"l": 0, "r": 0, "t": 0, "b": 0},\n # # autosize=False,\n # scene=dict(\n # camera=dict(eye=dict(x=2, y=2, z=2)),\n # aspectmode="data",\n # ),\n # )\n fig.update_layout(\n showlegend=False,\n title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},\n margin={"l": 0, "r": 0, "t": 0, "b": 0},\n # autosize=False,\n scene=dict(\n camera=dict(eye=dict(x=2, y=2, z=2)),\n aspectratio={\n **{"x": ar},\n **{\n c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar\n for c in list("yz")\n },\n },\n aspectmode="manual",\n ),\n )\n\n\n\n plotly.offline.plot(fig, filename=\'C:/Users/mike/Desktop/3D_BinPack_\' + str(row) + \'.html\', auto_open=False,\n config={\'displaylogo\': False})\n # fig.write_html(\'C:/Users/mike/Desktop/3D_BinPack_\' + str(row) + \'.html\', auto_open=False,\n # include_plotlyjs="cdn",config={\'displaylogo\': False})\n fig.show(config={\'displaylogo\': False})\nRun Code Online (Sandbox Code Playgroud)\n
您可以使用plotly的.to_html()函数来创建html文件。在该函数中,有一个参数决定是否包含plotlyjs源代码 - 通过包含该代码,.html文件无需互联网连接即可使用,但会使文件大小变得相当大。但是,您可以选择使用参数排除plotlyjs源代码include_plotlyjs='cdn'。这将使文件变得更小,但意味着该文件只能在线查看。
您应该能够将图形 (fig) 保存到 .html 文件,如下所示:
f = open('filename_here.html', "w")
f.close()
with open('filename_here.html', 'a') as f:
f.write(fig.to_html(full_html=False, include_plotlyjs='cdn'))
f.close()
Run Code Online (Sandbox Code Playgroud)
同样,这会将您的无花果写入一个需要互联网连接才能查看的文件,但与包含 js 源代码的文件相比会相当小。
如果您想创建一个可离线使用的文件(以较大的文件大小为代价),您可以将参数更改include_plotlyjs为True如下所示:
f = open('filename_here.html', "w")
f.close()
with open('filename_here.html', 'a') as f:
f.write(fig.to_html(full_html=False, include_plotlyjs=True))
f.close()
Run Code Online (Sandbox Code Playgroud)
注意:我已经包含了第一个open和.close()调用,以展示如何确保您正在写入的文件首先是空的。这样做将清除文件,然后添加您的图形。在不首先清除文件的情况下,如果使用同一输出文件多次运行程序,最终可能会“追加”到该文件,从而每次都会使其变得越来越大。
请告诉我这是否适合您。
作为奖励:如果您确实想将多个图形写入单个 .html 文件,您可以创建一个绘图图形列表或字典,然后在打开文件的情况下迭代列表/字典,在每次迭代之前写入新图形关闭文件:
dictionary_of_figures = {} ### add your figures to this dictionary
f = open(filename_here.html,"w")
f.close()
with open(filename_here.html, 'a') as f:
for key in dictionary_of_figures:
f.write(dictionary_of_figures[key].to_html(full_html=False, include_plotlyjs='cdn'))
f.close()
Run Code Online (Sandbox Code Playgroud)