File: logo.py

package info (click to toggle)
python-pyvista 0.46.4-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 176,968 kB
  • sloc: python: 94,346; sh: 216; makefile: 70
file content (362 lines) | stat: -rw-r--r-- 10,449 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
"""Generate the pyvista logo.

Logos generated with:
plot_logo(screenshot='pyvista_logo.png', window_size=(1920, 1080))
plot_logo(screenshot='pyvista_logo_sm.png', window_size=(960, 400), off_screen=True)

# different camera angle for square plot
cpos = [(-0.3654543687422538, 1.1098808905156292, 9.073223697728247),
        (2.553950615449191, 0.34145688392081264, 0.06127122762851659),
        (0.019308531920309947, 0.996708840795678, -0.07873161547192065)]

plot_logo(screenshot='pyvista_logo_sm_sq.png', window_size=(960, 960), cpos=cpos,
          off_screen=True)

"""

from __future__ import annotations

import os
from pathlib import Path

import numpy as np

import pyvista
from pyvista import examples
from pyvista._deprecate_positional_args import _deprecate_positional_args
from pyvista.core import _vtk_core as _vtk
from pyvista.core.utilities.features import _voxelize_legacy

THIS_PATH = str(Path(os.path.realpath(__file__)).parent)

LOGO_TITLE = 'PyVista'


def atomize(grid, shift_fac=0.1, scale=0.9):
    """Break apart and shrink and/or scale the individual cells of a mesh.

    Parameters
    ----------
    grid : pyvista.UnstructuredGrid
        The input mesh to atomize.
    shift_fac : float, default: 0.1
        Factor by which to shift the individual cells apart.
    scale : float, default: 0.9
        Factor by which to scale the individual cells.

    Returns
    -------
    pyvista.UnstructuredGrid
        The atomized mesh with individually shifted and scaled cells.

    """
    cent = grid.center
    cells = []
    for i in range(grid.n_cells):
        cell = grid.extract_cells(i)
        ccent = np.array(cell.center)
        cell.points[:] = (cell.points - ccent) * scale + ccent
        cell.points += (ccent - np.array(cent)) * shift_fac
        cells.append(cell)

    return cells[0].merge(cells[1:])


def text_3d(string, depth=0.5):
    """Create 3D text from a given string.

    Parameters
    ----------
    string : str
        The string of text to convert into 3D text.

    depth : float, default: 0.5
        The depth of the extrusion used to create the 3D text.

    Returns
    -------
    pyvista.DataSet
        The 3D text in the form of a PyVista DataSet.

    See Also
    --------
    :ref:`create_pixel_art_example`

    """
    from vtkmodules.vtkRenderingFreeType import vtkVectorText  # noqa: PLC0415

    vec_text = vtkVectorText()
    vec_text.SetText(string)

    extrude = _vtk.vtkLinearExtrusionFilter()
    extrude.SetInputConnection(vec_text.GetOutputPort())
    extrude.SetExtrusionTypeToNormalExtrusion()
    extrude.SetVector(0, 0, 1)
    extrude.SetScaleFactor(depth)

    tri_filter = _vtk.vtkTriangleFilter()
    tri_filter.SetInputConnection(extrude.GetOutputPort())
    tri_filter.Update()
    return pyvista.wrap(tri_filter.GetOutput())


@_deprecate_positional_args
def logo_letters(merge=False, depth=0.3):  # noqa: FBT002
    """Generate a mesh for each letter in "PyVista".

    Parameters
    ----------
    merge : bool, optional
        If ``True``, merge the meshes of the individual letters into a single
        mesh.  If ``False``, return a dictionary where the keys are the letters
        and the values are the respective meshes.
    depth : float, optional
        The depth of the extrusion for each letter in the mesh.

    Returns
    -------
    pyvista.PolyData or dict[str, pyvista.PolyData]
        If merge is ``True``, returns a single merged mesh containing all the
        letters in "PyVista". If merge is ``False``, returns a dictionary where
        the keys are the letters and the values are the respective meshes.

    """
    mesh_letters = pyvista.PolyData() if merge else {}  # type: ignore[var-annotated]

    # spacing between letters
    space_factor = 0.9
    width = 0
    for letter in LOGO_TITLE:
        mesh_letter = text_3d(letter, depth=depth)
        this_letter_width = mesh_letter.points[:, 0].max()
        mesh_letter.translate([width * space_factor, 0, 0.0], inplace=True)
        width += this_letter_width
        if merge:
            mesh_letters += mesh_letter
        else:
            mesh_letters[letter] = mesh_letter

    return mesh_letters


def logo_voxel(density=0.03):
    """Create a voxelized PyVista logo.

    Parameters
    ----------
    density : float, default: 0.03
        Density of the voxelization.

    Returns
    -------
    pyvista.UnstructuredGrid
        Voxelized PyVista logo as an unstructured grid.

    """
    return _voxelize_legacy(text_3d(LOGO_TITLE, depth=0.3), density=density)


def logo_basic():
    """Create a basic pyvista logo.

    Returns
    -------
    pyvista.UnstructuredGrid
        Grid containing the pyvista letters.

    Examples
    --------
    Plot the basic pyvista logo.

    >>> from pyvista import demos
    >>> logo = demos.logo_basic()
    >>> cpos = logo.plot(smooth_shading=True)

    Add scalars and plot the logo.

    >>> logo['x_coord'] = logo.points[:, 0]
    >>> cpos = logo.plot(
    ...     scalars='x_coord',
    ...     cmap='Spectral',
    ...     smooth_shading=True,
    ...     cpos='xy',
    ... )

    """
    return logo_letters(merge=True).compute_normals(split_vertices=True)


@_deprecate_positional_args
def plot_logo(  # noqa: PLR0917
    window_size=None,
    off_screen=None,
    screenshot=None,
    cpos=None,
    just_return_plotter=False,  # noqa: FBT002
    show_note=False,  # noqa: FBT002
    **kwargs,
):
    """Plot the stylized PyVista logo.

    Parameters
    ----------
    window_size : sequence[int], optional
        Size of the window in the format ``[width, height]``.
    off_screen : bool, optional
        Renders off screen when ``True``.
    screenshot : str, optional
        Save screenshot to path when specified.
    cpos : list or str, optional
        Camera position to use.
    just_return_plotter : bool, default: False
        Return the plotter instance without rendering.
    show_note : bool, default: False
        Show a text in the plot when ``True``.
    **kwargs : dict, optional
        Additional keyword arguments.

    Returns
    -------
    Plotter or camera position
        Returns the plotter instance if ``just_return_plotter`` is ``True``,
        otherwise returns the camera position if ``screenshot`` is specified,
        otherwise shows the plot.

    Examples
    --------
    >>> from pyvista import demos
    >>> cpos = demos.plot_logo()

    """
    # initialize plotter
    if window_size is None:
        window_size = [960, 400]
    plotter = pyvista.Plotter(window_size=window_size, off_screen=off_screen)

    mesh_letters = logo_letters()

    # letter 'P'
    p_mesh = mesh_letters['P'].compute_normals(split_vertices=True)
    plotter.add_mesh(p_mesh, color='#376fa0', smooth_shading=True)

    # letter 'y'
    y_mesh = mesh_letters['y'].compute_normals(split_vertices=True)
    plotter.add_mesh(y_mesh, color='#ffd040', smooth_shading=True)

    # letter 'V'
    v_grid = _voxelize_legacy(mesh_letters['V'], density=0.08)
    v_grid_atom = atomize(v_grid)
    v_grid_atom['scalars'] = v_grid_atom.points[:, 0]
    v_grid_atom_surf = v_grid_atom.extract_surface()
    faces = v_grid_atom_surf.faces.reshape(-1, 5).copy()
    faces[:, 1:] = faces[:, 1:][:, ::-1]
    v_grid_atom_surf.faces = faces
    plotter.add_mesh(
        v_grid_atom_surf,
        scalars='scalars',
        show_edges=True,
        cmap='winter',
        show_scalar_bar=False,
    )

    # letter 'i'
    i_grid = _voxelize_legacy(mesh_letters['i'], density=0.1)

    plotter.add_mesh(
        i_grid.extract_surface(),
        style='points',
        color='r',
        render_points_as_spheres=True,
        point_size=14,
    )
    plotter.add_mesh(i_grid, style='wireframe', color='k', line_width=4)

    # letter 's'
    mesh = mesh_letters['s']
    mesh['scalars'] = mesh.points[:, 0]
    plotter.add_mesh(
        mesh,
        scalars='scalars',
        style='wireframe',
        line_width=2,
        cmap='gist_heat',
        backface_culling=True,
        render_lines_as_tubes=True,
        show_scalar_bar=False,
    )

    # letter 't'
    mesh = mesh_letters['t'].clean().compute_normals()
    scalars = mesh.points[:, 0]
    plotter.add_mesh(mesh, scalars=scalars, show_edges=True, cmap='autumn', show_scalar_bar=False)

    # letter 'a'
    grid = examples.download_letter_a()
    grid.points[:, 0] += mesh_letters['a'].center[0] - grid.center[0]

    # select some cells from grid
    cells = grid.cells.reshape(-1, 5)
    mask = grid.points[cells[:, 1:], 2] < 0.2
    mask = mask.all(1)

    a_part = grid.extract_cells(mask)

    cells = a_part.cells.reshape(-1, 5)
    scalars = grid.points[cells[:, 1], 1]
    plotter.add_mesh(
        a_part, scalars=scalars, show_edges=True, cmap='Greens', show_scalar_bar=False
    )

    if show_note:
        text = text_3d('You can move me!', depth=0.1)
        text.points *= 0.1
        text.translate([4.0, -0.3, 0], inplace=True)
        plotter.add_mesh(text, color='black')

    # finalize plot and show it
    plotter.set_background(kwargs.pop('background', 'white'))
    plotter.camera_position = 'xy'
    if 'zoom' in kwargs:
        plotter.camera.zoom(kwargs.pop('zoom'))

    # plotter.remove_scalar_bar()
    plotter.enable_anti_aliasing()

    if just_return_plotter:
        return plotter

    if screenshot:  # pragma: no cover
        plotter.show(cpos=cpos, auto_close=False)
        plotter.screenshot(screenshot, True)
        cpos_final = plotter.camera_position
        plotter.close()
        return cpos_final
    else:
        return plotter.show(cpos=cpos, **kwargs)


def logo_atomized(density=0.05, scale=0.6, depth=0.05):
    """Generate a voxelized pyvista logo with intra-cell spacing.

    Parameters
    ----------
    density : float, default: 0.05
        The spacing between voxels in the generated PyVista logo.
    scale : float, default: 0.6
        The scaling factor for the generated PyVista logo.
    depth : float, default: 0.05
        The depth of the generated PyVista logo.

    Returns
    -------
    pyvista.UnstructuredGrid
        A merged UnstructuredGrid representing the voxelized PyVista logo.

    """
    mesh_letters = logo_letters(depth=depth)
    grids = []
    for letter in mesh_letters.values():
        grid = _voxelize_legacy(letter, density=density)
        grids.append(atomize(grid, scale=scale))

    return grids[0].merge(grids[1:])