Not familiar with Numpy? Let's draw a magic cube and play!

Blind agitation series~

preface

NumPy is the basic package of Python scientific computing. It is a python library that provides multidimensional array objects, various derived objects (such as mask arrays and matrices), and various routines for fast operation of arrays, including mathematics, logic, shape operation, sorting, selection, I/O, discrete Fourier transform, basic linear algebra, basic statistical operations, random simulation, etc.
github
Official documents

Recently, there was a demand for palletizing planning in the project. The three-dimensional array in Numpy is particularly easy to use, so I made a stir.
Then I saw the magic cube I bought two years ago on the table. I haven't played for a long time. As soon as my mind is hot, I want to draw a magic cube with Numpy!

Fuck!

Here, we choose to use Matplotlib as the visualization tool
Matplotlib GitHub
Matplotlib official documentation

Construction voxel

In order to make a magic cube, we mainly use voxels, a function in Matplotlib

voxels([x, y, z, ]/, filled, facecolors=None, edgecolors=None, **kwargs)
Draw a set of fill voxels
 All voxels are drawn as 1 on the axis x1x1 Cube, filled[0, 0, 0]of lower corner At the origin. Occluded faces are no longer drawn.

Take 3x3x3 cube as an example:

import matplotlib.pyplot as plt
import numpy as np
# Prepare a set of voxel coordinates
n_voxels = np.ones((3, 3, 3), dtype=bool)
# draw
ax = plt.figure().add_subplot(projection='3d')
ax.voxels(n_voxels)
plt.show()


It can be seen that although there are 3x3x3 voxels, there is no gap between voxels, which doesn't look very beautiful.

Make gap effect

In order to make a gap between voxels, you can up sample 3x3x3 voxels, that is, build a 5x5x5 voxel, so that the voxel in the middle of the two voxels is not displayed in each dimension, which can produce the effect of gap.

size = np.array(n_voxels.shape) * 2
filled_2 = np.zeros(size - 1, dtype=n_voxels.dtype)
filled_2[::2, ::2, ::2] = n_voxels


ax = plt.figure().add_subplot(projection='3d')
ax.voxels(filled_2)
plt.show()


In this way, there is a gap, but the gap is too large. At this time, you can use the optional parameters [x, y, z] of the voxels function to control the vertex position of each voxel, and then control the size of the gap

# lessen a gap
# Building voxels vertex control mesh
# x. Matrices with y and Z being 6x6x6 are voxels' grids
# //2 is to convert the index range from [0 1 2 3 4 5] to [0 0 0 1 1 2 2], so that the x,y,z range returns to 0 ~ 3
x, y, z = np.indices(np.array(filled_2.shape) + 1).astype(float) // 2   

x[1::2, :, :] += 0.95
y[:, 1::2, :] += 0.95
z[:, :, 1::2] += 0.95


In this way, the gap looks almost the same. Next, add color to the six faces of the cube.

Give each face a different color

Since only one color can be given to each voxel as a whole, and different colors cannot be assigned to different faces of a voxel, in order to realize different colors for no more than six faces, the matrix of 3x3x3 can only be changed to 5x5x5, and the thickness of the outermost layer of voxels can be set to be a little smaller, similar to the face, and then color.

import matplotlib.pyplot as plt
import numpy as np

# Prepare some coordinates
n_voxels = np.ones((5, 5, 5), dtype=bool)

# Generate gap
size = np.array(n_voxels.shape) * 2
filled_2 = np.zeros(size - 1, dtype=n_voxels.dtype)
filled_2[::2, ::2, ::2] = n_voxels

# lessen a gap
# Building voxels vertex control mesh
# x. Y and Z are 6x6x8 matrices, voxels meshes, 3x3x4 small squares, and 6x6x8 vertices in total.
# Here / / 2 is the essence. Convert the index range from [0 1 2 3 4 5] to [0 0 1 1 2 2], so that the vertex range of each block can be set separately
x, y, z = np.indices(np.array(filled_2.shape) + 1).astype(float) //2 # 3x6x6x8, where x, y and Z are 6x6x8

x[1::2, :, :] += 0.95
y[:, 1::2, :] += 0.95
z[:, :, 1::2] += 0.95
# Modify the thickness of the outermost voxel to use as six faces
x[0, :, :] += 0.94
y[:, 0, :] += 0.94
z[:, :, 0] += 0.94

x[-1, :, :] -= 0.94
y[:, -1, :] -= 0.94
z[:, :, -1] -= 0.94
# Remove leftovers
filled_2[0, 0, :] = 0
filled_2[0, -1, :] = 0
filled_2[-1, 0, :] = 0
filled_2[-1, -1, :] = 0

filled_2[:, 0, 0] = 0
filled_2[:, 0, -1] = 0
filled_2[:, -1, 0] = 0
filled_2[:, -1, -1] = 0

filled_2[0, :, 0] = 0
filled_2[0, :, -1] = 0
filled_2[-1, :, 0] = 0
filled_2[-1, :, -1] = 0

Then it is to give six faces different colors.

Six directions: up, down, left, right, front and back
Six colors: yellow, white, orange, red, blue and green
The initial form of the cube is: Upper Yellow, lower white, left orange, right red, front blue and back green.

Reference: color Daquan https://www.5tu.cn/colors/yansebiao.html

# Give different colors to the six sides of the cube
colors = np.array(['#ffd400', "#fffffb", "#f47920", "#d71345", "#145b7d", "#45b97c"])
facecolors = np.full(filled_2.shape, '#77787b')  # Set a gray tone
facecolors[:, :, -1] = colors[0]
facecolors[:, :, 0] = colors[1]
facecolors[:, 0, :] = colors[2]
facecolors[:, -1, :] = colors[3]
facecolors[0, :, :] = colors[4]
facecolors[-1, :, :] = colors[5]

Complete code

The complete code is as follows:

# -*- coding: utf-8 -*-
# @Time : DATE:2021/8/29
# @Author : yan
# @Email : 1792659158@qq.com
# @File : blogDemo.py

import matplotlib.pyplot as plt
import numpy as np


def generate_rubik_cube(nx, ny, nz):
    """
    Generate a magic cube of the specified size according to the input
    :param nx:
    :param ny:
    :param nz:
    :return:
    """
    # Prepare some coordinates
    n_voxels = np.ones((nx + 2, ny + 2, nz + 2), dtype=bool)

    # Generate gap
    size = np.array(n_voxels.shape) * 2
    filled_2 = np.zeros(size - 1, dtype=n_voxels.dtype)
    filled_2[::2, ::2, ::2] = n_voxels

    # lessen a gap
    # Building voxels vertex control mesh
    # x. Y and Z are 6x6x8 matrices, voxels meshes, 3x3x4 small squares, and 6x6x8 vertices in total.
    # Here / / 2 is the essence. Convert the index range from [0 1 2 3 4 5] to [0 0 1 1 2 2], so that the vertex range of each block can be set separately
    x, y, z = np.indices(np.array(filled_2.shape) + 1).astype(float) // 2 # 3x6x6x8, where x, y and Z are 6x6x8

    x[1::2, :, :] += 0.95
    y[:, 1::2, :] += 0.95
    z[:, :, 1::2] += 0.95

    # Modify the outermost face
    x[0, :, :] += 0.94
    y[:, 0, :] += 0.94
    z[:, :, 0] += 0.94

    x[-1, :, :] -= 0.94
    y[:, -1, :] -= 0.94
    z[:, :, -1] -= 0.94

    # Remove leftovers
    filled_2[0, 0, :] = 0
    filled_2[0, -1, :] = 0
    filled_2[-1, 0, :] = 0
    filled_2[-1, -1, :] = 0

    filled_2[:, 0, 0] = 0
    filled_2[:, 0, -1] = 0
    filled_2[:, -1, 0] = 0
    filled_2[:, -1, -1] = 0

    filled_2[0, :, 0] = 0
    filled_2[0, :, -1] = 0
    filled_2[-1, :, 0] = 0
    filled_2[-1, :, -1] = 0

    # Give different colors to the six sides of the cube
    colors = np.array(['#ffd400', "#fffffb", "#f47920", "#d71345", "#145b7d", "#45b97c"])
    facecolors = np.full(filled_2.shape, '#77787b')  # Set a gray tone
    # facecolors = np.zeros(filled_2.shape, dtype='U7')
    facecolors[:, :, -1] = colors[0]	# Upper Yellow
    facecolors[:, :, 0] = colors[1]	    # Lower white
    facecolors[:, 0, :] = colors[2]  	# Left orange
    facecolors[:, -1, :] = colors[3]	# Right red
    facecolors[0, :, :] = colors[4]	    # Front blue
    facecolors[-1, :, :] = colors[5]	# Post green

    ax = plt.figure().add_subplot(projection='3d')
    ax.voxels(x, y, z, filled_2, facecolors=facecolors)
    plt.show()


if __name__ == '__main__':
    generate_rubik_cube(3, 3, 3)

Magic squares of different sizes can be generated according to the input:
4x4x4:

6x6x6

Even 4x4x6, but this is not the magic cube we usually play~

Keywords: Python numpy matplotlib

Added by saeed42 on Sat, 18 Dec 2021 13:16:51 +0200