end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

shapely for python で作成したpolygonをmesh化し、matplotlib で 3D表示

shapelyによるpolygon作成や、numpyによる座標変換(affine変換)、 mpl_toolkits.mplot3d.art3dによる3D表示の練習です。

# coding: utf-8

import numpy as np
# cf. https://shapely.readthedocs.io/en/stable/manual.html
from shapely.geometry       import Polygon
from shapely.geometry       import MultiPolygon
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.art3d as art3d

# 回転や移動の定義
# cf. https://org-technology.com/posts/rotational-transformation-matrix.html
affines = {
    # Z方向へ+40移動
    "z90"   : np.array([[ 1, 0, 0, 0],
                        [ 0, 1, 0, 0],
                        [ 0, 0, 1,40],
                        [ 0, 0, 0, 1]] ),
    # X軸に90度回転
    "rx90"  : np.array([[ 1, 0, 0, 0],    # 1, 0,   0
                        [ 0, 0,-1, 0],    # 0,cos,-sin
                        [ 0, 1, 0, 0],    # 0 sin, cos
                        [ 0, 0, 0, 1]] ),
    # Z軸に90度回転後、Y方向へ+40
    "rz90"  : np.array([[ 0, 1, 0, 0],    #  cos,sin,0
                        [-1, 0, 0,40],    # -sin,cos,0
                        [ 0, 0, 1, 0],    #  0   0,  1
                        [ 0, 0, 0, 1]] ),
    # Z軸に180度回転後、X方向へ+70、Y方向へ+40
    "rz180" : np.array([[-1, 0, 0,70],    #  cos,sin,0
                        [ 0,-1, 0,40],    # -sin,cos,0
                        [ 0, 0, 1, 0],    #  0   0,  1
                        [ 0, 0, 0, 1]] ),
    # Z軸に270度回転後、X方向へ+70
    "rz270" : np.array([[ 0,-1, 0,70],    #  cos,sin,0
                        [ 1, 0, 0, 0],    # -sin,cos,0
                        [ 0, 0, 1, 0],    #  0   0,  1
                        [ 0, 0, 0, 1]] )
}

def main():
    # 床,天井,壁のpolygonを Local座標で作成
    (floor, ceil, wall_a, wall_b, wall_c, wall_d) = \
        make_polygon_for_floor_ceil_wall()

    # メッシュに分割し、numpyのlistを返却
    mesh_size = 10
    meshs_floor = divide_by_mesh(floor,mesh_size)
    meshs_ceil  = divide_by_mesh(ceil, mesh_size)
    meshs_wall_a = divide_by_mesh(wall_a, mesh_size)
    meshs_wall_b = divide_by_mesh(wall_b, mesh_size)
    meshs_wall_c = divide_by_mesh(wall_c, mesh_size)
    meshs_wall_d = divide_by_mesh(wall_d, mesh_size)

    # 絶対座標へ変換(配置)
    meshs_ceil   = mesh_coords_to_abs( meshs_ceil,  [affines["z90"]] )
    meshs_wall_a = mesh_coords_to_abs( meshs_wall_a,[affines["rx90"]] )
    meshs_wall_b = mesh_coords_to_abs( meshs_wall_b,[affines["rx90"],
                                                     affines["rz180"]] )
    meshs_wall_c = mesh_coords_to_abs( meshs_wall_c,[affines["rx90"],
                                                     affines["rz90"]] )
    meshs_wall_d = mesh_coords_to_abs( meshs_wall_d,[affines["rx90"],
                                                     affines["rz270"]] )

    # 3Dで表示
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    for meshs in [meshs_ceil,
                  meshs_wall_a, meshs_wall_b,
                  meshs_wall_c, meshs_wall_d ]:
        for mesh in meshs:
            ax.scatter3D(mesh[:,0],mesh[:,1],mesh[:,2],
                         color='gray',
                         s=5)

            ax.add_collection3d(
                art3d.Poly3DCollection(meshs,
                                       facecolors='gray',
                                       linewidths=0.5,
                                       edgecolors='gray',
                                       alpha=.01) )
    plt.show()
    
def mesh_coords_to_abs( meshs, affines ):
    ret_meshs = []
    for mesh in meshs:
        np_mesh = np.array( mesh ).T
        # affine変換の為、最終行に[1,1,1,1]を一時追加
        np_mesh = np.insert(np_mesh, 3,[1,1,1,1],axis=0)
        # アフィン変換
        for affine in affines:
            np_mesh =  np.dot( affine , np_mesh )
        # 先程、一時追加した[1,1,1,1]を削除し、転置
        np_mesh = np.delete(np_mesh, 3,axis=0).T
        
        ret_meshs.append(np_mesh)
    return np.array( ret_meshs )
    
def calc_center( meshes ):
    centers = []

    for mesh in meshes:
        min_x = mesh[0][0]
        min_y = mesh[0][1]
        max_x = mesh[1][0]
        max_y = mesh[1][1]
        centers.append([(min_x + max_x) / 2,
                        (min_y + max_y) / 2,
                        0 ] )
    return centers

def divide_by_mesh(polygon,mesh_size):
    
    (min_x,y, max_x,max_y) = polygon.bounds
    x = min_x
    meshes = []
    
    while y < max_y:
        while x < max_x:
            mesh = Polygon(
                [ [x, y+mesh_size],[x+mesh_size,y+mesh_size],
                  [x+mesh_size,y ],[x, y]] )
            intersects = polygon.intersection(mesh)

            if intersects.type == "Polygon":
                (m_min_x,m_min_y, m_max_x,m_max_y) = intersects.bounds
                mesh = np.array( [[m_min_x,m_min_y,0],[m_min_x,m_max_y,0],
                                  [m_max_x,m_max_y,0],[m_max_x,m_min_y,0]] )
                meshes.append( mesh )
            elif intersects.type == "MultiPolygon":
                for intersect in intersects.geoms:
                    if intersect.type != "Polygon":
                        continue
                    (m_min_x,m_min_y, m_max_x,m_max_y) = intersect.bounds
                    mesh = np.array( [[m_min_x,m_min_y,0],[m_min_x,m_max_y,0],
                                      [m_max_x,m_max_y,0],[m_max_x,m_min_y,0]] )
                    meshes.append( mesh )
            x += mesh_size
        x = min_x
        y += mesh_size
    return meshes

def make_polygon_for_floor_ceil_wall():
    # 床/天井
    horizontal_shell =[ [ 0, 0],[ 0,40],[70,40],[70, 0],[0, 0] ]
    floor =  Polygon( shell=horizontal_shell )
    ceil  =  Polygon( shell=horizontal_shell )
    
    # ┌─壁-B─┐
    #壁-C      壁-D
    # └─壁-A─┘
    wall_ab_shell =[ [ 0, 0],[ 0,40],[70,40],[70, 0],[60,0],
                     [60,30],[40,30],[40, 0],[ 0, 0] ]
    wall_ab_holes =[[[10,10],[30,10],[30,30],[10,30],[10,10]] ]
    wall_a =  Polygon( shell=wall_ab_shell, holes=wall_ab_holes )
    wall_b =  Polygon( shell=wall_ab_shell, holes=wall_ab_holes )

    wall_cd_shell =[ [ 0, 0],[ 0,40],[40,40],[40, 0],[0, 0] ]
    wall_cd_holes =[[[ 0,10],[30,10],[30,30],[ 0,30],[0,10]] ]
    wall_c =  Polygon( shell=wall_cd_shell, holes=wall_cd_holes )
    wall_d =  Polygon( shell=wall_cd_shell, holes=wall_cd_holes )

    return (floor, ceil, wall_a, wall_b, wall_c, wall_d)

if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます