我被一个可能很简单的问题卡住了,但在阅读了pyvista文档后,我仍然在寻找答案。我试图绘制一个网格,其中每个单元格将是一个定义为参数形状的网格,即supertorus。在 pyvista 的早期版本中,我定义了 "我自己的 "supertorus,如下所示。
def supertorus(yScale, xScale, Height, InternalRadius, Vertical, Horizontal,
deltaX=0, deltaY=0, deltaZ=0):
# initial range for values used in parametric equation
n = 100
u = np.linspace(-np.pi, np.pi, n)
t = np.linspace(-np.pi, np.pi, n)
u, t = np.meshgrid(u, t)
# a1: Y Scale <0, 2>
a1 = yScale
# a2: X Scale <0, 2>
a2 = xScale
# a3: Height <0, 5>
a3 = Height
# a4: Internal radius <0, 5>
a4 = InternalRadius
# e1: Vertical squareness <0.25, 1>
e1 = Vertical
# e2: Horizontal squareness <0.25, 1>
e2 = Horizontal
# Definition of parametric equation for supertorus
x = a1 * (a4 + np.sign(np.cos(u)) * np.abs(np.cos(u)) ** e1) *\
np.sign(np.cos(t)) * np.abs(np.cos(t)) ** e2
y = a2 * (a4 + np.sign(np.cos(u)) * np.abs(np.cos(u)) ** e1) *\
np.sign(np.sin(t)) * np.abs(np.sin(t)) ** e2
z = a3 * np.sign(np.sin(u)) * np.abs(np.sin(u)) ** e1
grid = pyvista.StructuredGrid(x + deltaX + 5, y + deltaY + 5, z + deltaZ)
return grid
我可以用 deltaX
, deltaY
和 deltaZ
不幸的是,这种方法并不有效,我正计划使用PyVista提供的supertoroidal网格(https:/docs.pyvista.orgexamples00-loadcreat-parametric-geometric-objects.html?highlight=supertoroid。). 我的问题是:如何将多个网格(如supertori)放置在坐标为 x
, y
, z
?
我相信你要找的是 字形. 你可以将你自己的数据集作为一个字形几何体传递给它,然后它将在超级网格的每个点上绘制数据集。在此不赘述字形的方向、根据标量着色等细节,这里以一个简单的 "外星人入侵 "场景为例。
import numpy as np
import pyvista as pv
# get dataset for the glyphs: supertoroid in xy plane
saucer = pv.ParametricSuperToroid(ringradius=0.5, n2=1.5, zradius=0.5)
saucer.rotate_y(90)
# saucer.plot() # <-- check how a single saucer looks like
# get dataset where to put glyphs
x,y,z = np.mgrid[-1:2, -1:2, :2]
mesh = pv.StructuredGrid(x, y, z)
# construct the glyphs on top of the mesh
glyphs = mesh.glyph(geom=saucer, factor=0.3)
# glyphs.plot() # <-- simplest way to plot it
# create Plotter and add our glyphs with some nontrivial lighting
plotter = pv.Plotter(window_size=(1000, 800))
plotter.add_mesh(glyphs, color=[0.2, 0.2, 0.2], specular=1, specular_power=15)
plotter.show()
我加入了 一些强烈的镜面照明 以使飞碟看起来更有威胁性。
但是你的问题的关键点是通过传递你的超级网格来创建字形。的 geom
的关键词 mesh.glyph
. 其他关键词如 orient
和 scale
对于箭头状的字形很有用,你可以用字形来表示数据集的向量信息。
你在评论中问到是否可以沿着数据集改变字形。我确信这是不可能的,然而 VTK文档 清楚地提到可以定义一个要使用的字形集合。
通过创建一个源对象表,每个对象定义一个不同的字形,可以使用一个以上的字形。如果定义了一个字形表,那么可以通过使用标量值或矢量大小来对该表进行索引。
事实证明 PyVista
还没有暴露这个功能,但基础的 vtk
包让我们可以动手了。这里是一个概念验证 基于 DataSetFilters.glyph
我会让 PyVista 的开发人员看看是否有兴趣公开这个功能。
import numpy as np
import pyvista as pv
from pyvista.core.filters import _get_output # just for this standalone example
import vtk
pyvista = pv # just for this standalone example
# below: adapted from core/filters.py
def multiglyph(dataset, orient=True, scale=True, factor=1.0,
tolerance=0.0, absolute=False, clamping=False, rng=None,
geom_datasets=None, geom_values=None):
"""Copy a geometric representation (called a glyph) to every point in the input dataset.
The glyphs may be oriented along the input vectors, and they may be scaled according to scalar
data or vector magnitude.
Parameters
----------
orient : bool
Use the active vectors array to orient the glyphs
scale : bool
Use the active scalars to scale the glyphs
factor : float
Scale factor applied to sclaing array
tolerance : float, optional
Specify tolerance in terms of fraction of bounding box length.
Float value is between 0 and 1. Default is 0.0. If ``absolute``
is ``True`` then the tolerance can be an absolute distance.
absolute : bool, optional
Control if ``tolerance`` is an absolute distance or a fraction.
clamping: bool
Turn on/off clamping of "scalar" values to range.
rng: tuple(float), optional
Set the range of values to be considered by the filter when scalars
values are provided.
geom_datasets : tuple(vtk.vtkDataSet), optional
The geometries to use for the glyphs in table mode
geom_values : tuple(float), optional
The value to assign to each geometry dataset, optional
"""
# Clean the points before glyphing
small = pyvista.PolyData(dataset.points)
small.point_arrays.update(dataset.point_arrays)
dataset = small.clean(point_merging=True, merge_tol=tolerance,
lines_to_points=False, polys_to_lines=False,
strips_to_polys=False, inplace=False,
absolute=absolute)
# Make glyphing geometry
if not geom_datasets:
arrow = vtk.vtkArrowSource()
arrow.Update()
geom_datasets = arrow.GetOutput(),
geom_values = 0,
# check if the geometry datasets are consistent
if not len(geom_datasets) == len(geom_values):
raise ValueError('geom_datasets and geom_values must have the same length!')
# TODO: other kinds of sanitization, e.g. check for sequences etc.
# Run the algorithm
alg = vtk.vtkGlyph3D()
if len(geom_values) == 1:
# use a single glyph
alg.SetSourceData(geom_datasets[0])
else:
alg.SetIndexModeToScalar()
# TODO: index by vectors?
# TODO: SetInputArrayToProcess for arbitrary arrays, maybe?
alg.SetRange(min(geom_values), max(geom_values))
# TODO: different Range?
for val, geom in zip(geom_values, geom_datasets):
alg.SetSourceData(val, geom)
if isinstance(scale, str):
dataset.active_scalars_name = scale
scale = True
if scale:
if dataset.active_scalars is not None:
if dataset.active_scalars.ndim > 1:
alg.SetScaleModeToScaleByVector()
else:
alg.SetScaleModeToScaleByScalar()
else:
alg.SetScaleModeToDataScalingOff()
if isinstance(orient, str):
dataset.active_vectors_name = orient
orient = True
if rng is not None:
alg.SetRange(rng)
alg.SetOrient(orient)
alg.SetInputData(dataset)
alg.SetVectorModeToUseVector()
alg.SetScaleFactor(factor)
alg.SetClamping(clamping)
alg.Update()
return _get_output(alg)
def example():
"""Small glyph example"""
rng = np.random.default_rng()
# get dataset for the glyphs: supertoroid in xy plane
# use N random kinds of toroids over a mesh with 27 points
N = 5
values = np.arange(N) # values for scalars to look up glyphs by
geoms = [pv.ParametricSuperToroid(n1=n1, n2=n2) for n1,n2 in rng.uniform(0.5, 2, size=(N, 2))]
for geom in geoms:
# make the disks horizontal for aesthetics
geom.rotate_y(90)
# get dataset where to put glyphs
x,y,z = np.mgrid[-1:2, -1:2, -1:2]
mesh = pv.StructuredGrid(x, y, z)
# add random scalars
mesh.point_arrays['scalars'] = rng.integers(0, N, size=x.size)
# construct the glyphs on top of the mesh; don't scale by scalars now
glyphs = multiglyph(mesh, geom_datasets=geoms, geom_values=values, scale=False, factor=0.3)
# create Plotter and add our glyphs with some nontrivial lighting
plotter = pv.Plotter(window_size=(1000, 800))
plotter.add_mesh(glyphs, specular=1, specular_power=15)
plotter.show()
if __name__ == "__main__":
example()
这个 multiglyph
在上面的函数中,大部分与 mesh.glyph
但我已经更换了 geom
用两个关键词。geom_datasets
和 geom_values
. 这些定义了一个索引->几何映射,然后用来根据数组标量查找每个字形。
你问是否可以独立给字形上色:可以。在上面的概念证明中,字形的选择是与标量联系在一起的(选择向量也同样容易;我对任意数组不太确定)。然而你可以很容易地选择用什么数组来着色,当你调用 pv.Plotter.add_mesh
因此,我的建议是使用适当的标量以外的东西来为你的字形着色。
我保留了用于着色的标量,以使其更容易看到字形之间的差异。你可以看到,有五种不同的字形是根据随机标量随机选择的。如果你设置非整数标量,它仍然会工作;我怀疑 vtk
选择最接近的标量或类似的东西进行查找。