Как я могу использовать PyVista для создания пользовательских 3D-объектов из списков / массивов?
Я хочу создать 3D-объекты, состоящие из 2D-полигонов, каждый из которых имеет текстуру, состоящую из одного jpeg-изображения. У меня есть трехмерные координаты значений X, Y и Z для многоугольников, а также координаты текстуры в интервале [0, 1]. Я могу построить 3D-объекты в matplotlib, используяPoly3DCollection
, но, как я уже читал, matplotlib не поддерживает наложение текстуры для многоугольников. Я нашел PyVista, который кажется хорошим выбором для наложения текстур, но я не понимаю, как создать на основе моих данных набор данных, совместимый с PyVista. Вот мой рабочий пример matplotlib:
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
[
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
]
]
polygon_b = [
[
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
]
]
polygon_c = [
[
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
]
]
polygon_d = [
[
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
]
]
polygon_e = [
[
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
]
]
polygon_f = [
[
(371987, 5812889, 44),
(371982, 5812893, 44),
(371988, 5812900, 44),
(371993, 5812896, 44),
(371987, 5812889, 44),
]
]
# texture coordinates of interval [0, 1]
texture_coords_a = [
0.993515,
0.590665,
0.583403,
0.995886,
0.001318,
0.409513,
0.411194,
0.00281,
0.993515,
0.590665,
]
texture_coords_b = [
0.814495,
0.004965,
0.986562,
0.175202,
0.172649,
0.994582,
0.004011,
0.820917,
0.814495,
0.004965,
]
texture_coords_c = [
0.992976,
0.869131,
0.867654,
0.99699,
0.009377,
0.134356,
0.138307,
0.010153,
0.992976,
0.869131,
]
texture_coords_d = [
0.007693,
0.148416,
0.15451,
0.00767,
0.994519,
0.86112,
0.844256,
0.998197,
0.007693,
0.148416,
]
texture_coords_e = [
0.997322,
0.660826,
0.89938,
0.990736,
0.006374,
0.337104,
0.106732,
0.00748,
0.997322,
0.660826,
]
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910.jpg"
img_b = "tex_2962971.jpg"
img_c = "tex_2962990.jpg"
img_d = "tex_2962933.jpg"
img_e = "tex_2962915.jpg"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e, polygon_f]
# 3D plot of the polygons using matplotlib, but without textures
fig = plt.figure()
ax = Axes3D(fig)
for polygon in polygons:
ax.add_collection3d(Poly3DCollection(polygon, alpha=0.5))
ax.set_xlim3d(371980, 371995)
ax.set_ylim3d(5812889, 5812902)
ax.set_zlim3d(44, 48)
plt.show()
Буду признателен за любую помощь в правильном направлении!
1 ответ
Для тех, у кого есть подобная проблема: я нашел два простых в использовании пакета, которые предоставляют необходимую функциональность для наложения текстур на множество полигонов. Первый - PyVista, рабочий код следующий:
import pyvista as pv
import numpy as np
from PIL import Image
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
]
polygon_b = [
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
]
polygon_c = [
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
]
polygon_d = [
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
]
polygon_e = [
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
]
texture_coords_a = np.array(
[
[0.993515, 0.590665],
[0.583403, 0.995886],
[0.001318, 0.409513],
[0.411194, 0.00281],
[0.993515, 0.590665],
]
)
texture_coords_b = np.array(
[
[0.814495, 0.004965],
[0.986562, 0.175202],
[0.172649, 0.994582],
[0.004011, 0.820917],
[0.814495, 0.004965],
]
)
texture_coords_c = np.array(
[
[0.992976, 0.869131],
[0.867654, 0.99699],
[0.009377, 0.134356],
[0.138307, 0.010153],
[0.992976, 0.869131],
]
)
texture_coords_d = np.array(
[
[0.007693, 0.148416],
[0.15451, 0.00767],
[0.994519, 0.86112],
[0.844256, 0.998197],
[0.007693, 0.148416],
]
)
texture_coords_e = np.array(
[
[0.997322, 0.660826],
[0.89938, 0.990736],
[0.006374, 0.337104],
[0.106732, 0.00748],
[0.997322, 0.660826],
]
)
# define polygon faces for each polygon
faces_a = np.hstack([[4, 0, 1, 2, 3]])
faces_b = np.hstack([[4, 0, 1, 2, 3]])
faces_c = np.hstack([[4, 0, 1, 2, 3]])
faces_d = np.hstack([[4, 0, 1, 2, 3]])
faces_e = np.hstack([[4, 0, 1, 2, 3]])
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910.jpg"
img_b = "tex_2962971.jpg"
img_c = "tex_2962990.jpg"
img_d = "tex_2962933.jpg"
img_e = "tex_2962915.jpg"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e]
faces = [faces_a, faces_b, faces_c, faces_d, faces_e]
textures = [img_a, img_b, img_c, img_d, img_e]
texture_coords = [
texture_coords_a,
texture_coords_b,
texture_coords_c,
texture_coords_d,
texture_coords_e,
]
def create_meshes_with_textures(polygon, face, texture, texture_coords):
""" Opens each image with PIL, converts them to np.arrays and converts those to
VTK textures. Each textur gets mapped to a polygon according to the coordinates.
"""
img = Image.open(texture)
img.load()
img = np.asarray(img, dtype=np.uint8)
img = pv.numpy_to_texture(img)
polygon = np.array(polygon)
mesh = pv.PolyData(polygon, face)
mesh.t_coords = texture_coords
return mesh, img
# create Plotter object from PyVista
p = pv.Plotter()
for polygon, face, texture, texture_coords in zip(
polygons, faces, textures, texture_coords
):
# map the textures to the polygons
mesh, img = create_meshes_with_textures(polygon, face, texture, texture_coords)
# add the resulting meshes to the Plotter object
p.add_mesh(mesh, texture=img)
p.show()
Второй вариант - использовать vtkplotter, спасибо разработчику, который реализовал эту функцию через этот выпуск GitHub. В целом оба пакета хорошо справляются с этой проблемой, поскольку vtkplotter требует меньше шагов преобразования для данных текстуры.
from vtkplotter import *
import numpy as np
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
[
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
],
[[0, 1, 2, 3, 4]],
]
polygon_b = [
[
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_c = [
[
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_d = [
[
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_e = [
[
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_f = [
[
(371987, 5812889, 44),
(371982, 5812893, 44),
(371988, 5812900, 44),
(371993, 5812896, 44),
(371987, 5812889, 44),
],
[[0, 1, 2, 3, 4]],
]
# texture coordinates of X, Y with interval [0, 1]
texture_coords_a = [
0.993515,
0.590665,
0.583403,
0.995886,
0.001318,
0.409513,
0.411194,
0.00281,
0.993515,
0.590665,
]
texture_coords_b = [
0.814495,
0.004965,
0.986562,
0.175202,
0.172649,
0.994582,
0.004011,
0.820917,
0.814495,
0.004965,
]
texture_coords_c = [
0.992976,
0.869131,
0.867654,
0.99699,
0.009377,
0.134356,
0.138307,
0.010153,
0.992976,
0.869131,
]
texture_coords_d = [
0.007693,
0.148416,
0.15451,
0.00767,
0.994519,
0.86112,
0.844256,
0.998197,
0.007693,
0.148416,
]
texture_coords_e = [
0.997322,
0.660826,
0.89938,
0.990736,
0.006374,
0.337104,
0.106732,
0.00748,
0.997322,
0.660826,
]
texture_coords = [
texture_coords_a,
texture_coords_b,
texture_coords_c,
texture_coords_d,
texture_coords_e,
]
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910"
img_b = "tex_2962971"
img_c = "tex_2962990"
img_d = "tex_2962933"
img_e = "tex_2962915"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e]
textures = [img_a, img_b, img_c, img_d, img_e]
meshes = []
# loop through all polygons and their textures
for polygon, texture, texture_coord in zip(polygons, textures, texture_coords):
# reformat texture coordinates as [(u,v), ...]
texture_coord = np.split(np.array(texture_coord), 5)
# create an Actor object for each polygon
polygon = Actor(polygon)
# map the textur with the according coordinates
polygon.texture(texture, tcoords=texture_coord)
meshes.append(polygon)
# assemble the objects
polygons = Assembly(meshes)
show(polygons, viewup="z", axes=8)