Jack
Jack

Reputation: 1437

Matplotlib 3D plot: set right aspect ratio

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?

Upvotes: 2

Views: 1637

Answers (1)

Zephyr
Zephyr

Reputation: 12496

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

Upvotes: 1

Related Questions