2

I am using matplotlib to plot 3D image. 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).

enter image description here

How do I modify the configuration to allow it display in a correct propotion according to the acual axis value like the below one?

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 = [
#   [250, 250, 500],
#   [500, 500, 400],
#   [300, 300, 300],
#   [300, 300, 200],
#   [300, 300, 100],
#   [500, 500, 500]
# ]



# containers = [
# #     [12000000, 2350000, 2697000],
# #     [12000000, 2350000, 2697000],
# #     [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# # ]


containers = [
    [589.5, 235, 239],
    [1202.4, 235, 269],
    # [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()

UPDATE

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

I have updated matplotlib to use .set_box_aspect function. there seems to be two problems:

  1. the x y z axis label is severely overlaped. It does not auto adjust. enter image description here

  2. the drawing picture becomes much smaller after using .set_box_aspect. When enlarging the drawing image, the axis does not auto enlarge accordingly. Hence, the image at center will overlap with the axis at edge, which is akward. enter image description here

  3. plus, there are showing error messages despite of sucessful drawing.

    RuntimeWarning: divide by zero encountered in double_scalars
    dz /= az
    
     ..........
    
     x, y, z = proj3d.inv_transform(xd, yd, z, self.M)
       File "C:\Users\Jack\AppData\Local\Programs\Python\Python36\lib\site-packages\mpl_toolkits\mplot3d\proj3d.py", line 125, in inv_transform
         iM = linalg.inv(M)
       File "<__array_function__ internals>", line 6, in inv
       File "C:\Users\Jack\AppData\Roaming\Python\Python36\site-packages\numpy\linalg\linalg.py", line 546, in inv
         ainv = _umath_linalg.inv(a, signature=signature, extobj=extobj)
       File "C:\Users\Jack\AppData\Roaming\Python\Python36\site-packages\numpy\linalg\linalg.py", line 88, in _raise_linalgerror_singular
         raise LinAlgError("Singular matrix")
     numpy.linalg.LinAlgError: Singular matrix
    

Are there ways to mitigate above problems?

Zephyr
  • 11,891
  • 53
  • 45
  • 80
Jack
  • 1,339
  • 1
  • 12
  • 31
  • Does this answer your question? [set matplotlib 3d plot aspect ratio?](https://stackoverflow.com/questions/8130823/set-matplotlib-3d-plot-aspect-ratio) – Zephyr Aug 31 '21 at 08:02

1 Answers1

1

If you have the coordinates of verteces of boxes, then you can use matplotlib.axes.Axes.set_box_aspect as explained in this answer.
This means that you have to add these lines at the bottom of your code, just before plotting:

positions_array = np.array(positions)
ax.set_box_aspect((np.ptp(positions_array[:, 0]), np.ptp(positions_array[:, 1]), np.ptp(positions_array[:, 2])))
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 = [
#   [250, 250, 500],
#   [500, 500, 400],
#   [300, 300, 300],
#   [300, 300, 200],
#   [300, 300, 100],
#   [500, 500, 500]
# ]



# containers = [
# #     [12000000, 2350000, 2697000],
# #     [12000000, 2350000, 2697000],
# #     [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# #     # [12000000, 2350000, 2697000],
# # ]


containers = [
    [589.5, 235, 239],
    [1202.4, 235, 269],
    # [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])])

        positions_array = np.array(positions)
        ax.set_box_aspect((np.ptp(positions_array[:, 0]), np.ptp(positions_array[:, 1]), np.ptp(positions_array[:, 2])))

plt.show()

enter image description here

Zephyr
  • 11,891
  • 53
  • 45
  • 80
  • thanks a lot. could you please take a look of code to suggest more specific edits directly on my code? – Jack Sep 01 '21 at 13:29
  • You should find a way to retrieve boxes vertex coordinates. I don't know `py3dbp` enogh to retreive those coordinates – Zephyr Sep 01 '21 at 19:13
  • 1
    it's in positions: 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)) – Jack Sep 01 '21 at 21:09
  • your plotting approach is quite different from mine. but it doesn't matter. I just need to know how to adjust the display of x y z scale in line with their actual value. – Jack Sep 01 '21 at 21:12
  • Ok, I edited my answer. Let me know if it works for you – Zephyr Sep 01 '21 at 22:48
  • please see my update and issues above. I hope the solution is similar to the second picuture mentioned above, which is very clear for user to see. thank you – Jack Sep 02 '21 at 15:39
  • If you don't need axis, you can turn them off with `ax.axis('off')`: [image](https://i.stack.imgur.com/0qszL.png) – Zephyr Sep 03 '21 at 13:22
  • I think it's very difficult to do better than that. Keep in mind that matplotlib is not optimized for 3d rendering (you could use other packages like pyqtgraphics or mayavi for that) – Zephyr Sep 03 '21 at 13:24
  • Keep in mind that you will never find a way to proper draw a good-looking 3d plot with aspect ratio as extreme as in the last image in your question, whatever the package you use – Zephyr Sep 03 '21 at 13:28
  • many thanks for your useful advice. Might I ask if there is any package which produce better result like the second image. it's a real plot so there must be some tool to achieve this. – Jack Sep 03 '21 at 17:07
  • [Mayavi](https://docs.enthought.com/mayavi/mayavi/), [PyQtGraph](https://www.pyqtgraph.org), [Plotly](https://plotly.com/python/3d-charts/), [Bokeh](https://docs.bokeh.org/en/latest/) support nice 3D visualization – Zephyr Sep 03 '21 at 18:35
  • 1
    Mayavi and PyQtGraph are more oriented on scientific plotting, while Plotly and Bokeh are more oriented on creating an interactive (the user can pan, zoom and rotate the plot) plot in a html file (easy to incorporate on a web page) – Zephyr Sep 03 '21 at 18:37