From 9eea132af9a28859b495f593dcae4aabe4fd5faf Mon Sep 17 00:00:00 2001 From: marcomusy Date: Wed, 8 Jan 2020 20:11:25 +0100 Subject: [PATCH] 2020.0.1 --- .circleci/config.yml | 37 +- MANIFEST.in | 3 - README.md | 36 +- bin/vtkplotter | 126 +- bin/{vtkconvert => vtkplotter-convert} | 0 docs/logos/sharpelab.py | 32 - docs/news.txt | 12 +- setup.py | 16 +- tests/{ => common}/run_all.sh | 2 +- tests/{ => common}/test_actors.py | 42 +- tests/dolfin/run_all.sh | 8 + tests/dolfin/test_aaa.py | 188 -- vtkplotter/__init__.py | 54 +- vtkplotter/addons.py | 98 +- vtkplotter/analysis.py | 447 ++-- vtkplotter/animation.py | 24 +- vtkplotter/assembly.py | 91 + vtkplotter/backends.py | 16 +- vtkplotter/base.py | 723 +++++++ vtkplotter/colors.py | 2 +- vtkplotter/docs.py | 2 + vtkplotter/dolfin.py | 35 +- vtkplotter/{actors.py => mesh.py} | 2590 ++++++------------------ vtkplotter/picture.py | 106 + vtkplotter/plot2d.py | 34 +- vtkplotter/plotter.py | 132 +- vtkplotter/settings.py | 5 +- vtkplotter/shapes.py | 2503 +++++++++++------------ vtkplotter/utils.py | 40 +- vtkplotter/version.py | 2 +- vtkplotter/volume.py | 633 ++++++ vtkplotter/vtkio.py | 87 +- 32 files changed, 4053 insertions(+), 4073 deletions(-) rename bin/{vtkconvert => vtkplotter-convert} (100%) delete mode 100644 docs/logos/sharpelab.py rename tests/{ => common}/run_all.sh (71%) rename tests/{ => common}/test_actors.py (89%) create mode 100755 tests/dolfin/run_all.sh delete mode 100644 tests/dolfin/test_aaa.py create mode 100644 vtkplotter/assembly.py create mode 100644 vtkplotter/base.py rename vtkplotter/{actors.py => mesh.py} (56%) create mode 100644 vtkplotter/picture.py create mode 100644 vtkplotter/volume.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 90f6edf7..bc8d0a6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,35 @@ version: 2 jobs: - build-real: + + build-dolfin-stable: + docker: + - image: quay.io/fenicsproject/dolfinx:dev-env-real + environment: + MPLBACKEND: "agg" + DEBIAN_FRONTEND: "noninteractive" + + steps: + - checkout + + - run: + name: install vtkplotter + command: | + pip3 install vtk + pip3 install . + + - run: + name: Run tests + command: | + cd + cd project/tests/dolfin + source run_all.sh + + - store_artifacts: + path: test-reports + destination: test-reports + + build-dolfinx: docker: - image: quay.io/fenicsproject/dolfinx:dev-env-real environment: @@ -42,17 +70,16 @@ jobs: name: Run tests command: | cd - cd project/tests - pwd - ls + cd project/tests/dolfinx source run_all.sh - store_artifacts: path: test-reports destination: test-reports +###################################################### workflows: version: 2 build-stuff: jobs: - - build-real + - build-dolfinx diff --git a/MANIFEST.in b/MANIFEST.in index e5921802..c197fd9d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,3 @@ include vtkplotter/* include vtkplotter/textures/* include vtkplotter/fonts/* include vtkplotter/fonts/licenses/* -include vtkplotter/data/* -include vtkplotter/data/images/* -include vtkplotter/data/timecourse1d/* diff --git a/README.md b/README.md index c7e0a05e..1517e222 100644 --- a/README.md +++ b/README.md @@ -59,52 +59,52 @@ in a program, whilst mantaining access to the full range of VTK native classes. It includes a [**large set of working examples**](https://github.com/marcomusy/vtkplotter-examples/tree/master/vtkplotter_examples) for all the following functionalities: - - Import meshes from VTK format, STL, Wavefront OBJ, 3DS, XML, Neutral, GMSH, OFF, PCD (PointCloud), volumetric TIFF stacks, DICOM, SLC, MHD, 2D images PNG, JPEG. + - Import meshes from VTK format, STL, Wavefront OBJ, 3DS, Dolfin-XML, Neutral, GMSH, OFF, PCD (PointCloud), volumetric TIFF stacks, DICOM, SLC, MHD, 2D images PNG, JPEG. - Export meshes as ASCII or binary to VTK, STL, OBJ, PLY formats. - - Mesh analysis through the built-in methods of VTK package. Additional analysis tools like *Moving Least Squares*, mesh morphing. - - Tools to visualize and edit meshes (cutting a mesh with another mesh, slicing, normalizing, moving vertex positions, etc..). Interactive cutter widget. + - Mesh analysis through the built-in methods of VTK. Additional analysis tools like *Moving Least Squares*, mesh morphing and more.. + - Tools to visualize and edit meshes (cutting a mesh with another mesh, slicing, normalizing, moving vertex positions, etc..). - Split mesh based on surface connectivity. Extract the largest connected area. - - Calculate mass properties, like area, volume, center of mass, average size etc. + - Calculate areas, volumes, center of mass, average sizes etc. - Calculate vertex and face normals, curvatures, feature edges. Fill mesh holes. - Subdivide faces of a mesh, increasing the number of vertex points. Mesh simplification. - Coloring and thresholding of meshes based on associated scalar or vectorial data. - - Point-surface operations: find nearest points, determine if a point lies inside or outside a mesh. - - Create primitive objects like: spheres, arrows, cubes, torus, ellipsoids... - - Generate *glyphs* (associating a mesh to each vertex of a source mesh). - - Create animations easily by just defining the position of the displayed objects in the 3D scene. Add trailing lines and shadows to moving objects is also supported. + - Point-surface operations: find nearest points, determine if a point lies inside or outside of a mesh. + - Create primitive shapes: spheres, arrows, cubes, torus, ellipsoids... + - Generate *glyphs* (associate a mesh to every vertex of a source mesh). + - Create animations easily by just setting the position of the displayed objects in the 3D scene. Add trailing lines and shadows to moving objects is supported. - Straightforward support for multiple *sync-ed* or independent renderers in the same window. - Registration (alignment) of meshes with different techniques. - - Mesh smoothing with *Laplacian* and *WindowedSinc* algorithms. + - Mesh smoothing. - Delaunay triangulation in 2D and 3D. - Generate meshes by joining nearby lines in space. - Find the closest path from one point to another, travelling along the edges of a mesh. - - Find the intersection of a mesh with a line (or with another mesh). + - Find the intersection of a mesh with lines, planes or other meshes. - Analysis of *Point Clouds*: - *Moving Least Squares* smoothing of 2D, 3D and 4D clouds - - Fit lines, planes and spheres and ellipses in space + - Fit lines, planes, spheres and ellipses in space - Identify outliers in a distribution of points - Decimate a cloud to a uniform distribution. - Histogramming and function plotting in 1D and 2D. - Interpolate scalar and vectorial fields with *Radial Basis Functions* and *Thin Plate Splines*. - Analysis of volumetric datasets: - Isosurfacing of volumes - - Direct maximum projection rendering + - Composite and maximum projection volumetric rendering - Generate volumetric signed-distance data from an input surface mesh - Probe a volume with lines and planes - Generate stream-lines and stream-tubes from vectorial fields - Add sliders and buttons to interact with the scene and the individual objects. - Fully customizable axis style. - - Draw `latex`-formatted formulas on the rending window. - - Examples using [SHTools](https://shtools.oca.eu/shtools) package for *spherical harmonics* expansion of a mesh shape. + - Visualization of tensors. + - Draw `latex`-formatted formulas in the rending window. - Integration with the *Qt5* framework. + - Examples using [SHTools](https://shtools.oca.eu/shtools) package for *spherical harmonics* expansion of a mesh shape. - Support for [FEniCS/Dolfin](https://fenicsproject.org/) platform for visualization of finite-element calculations. - - Visualization of tensors. + - Interoperability with the [trimesh](https://trimsh.org/) library. - Export a 3D scene and embed it into a [web page](https://vtkplotter.embl.es/examples/fenics_elasticity.html). - Embed the 3D rendering in a *jupyter* notebook with [K3D](https://github.com/K3D-tools/K3D-jupyter) (can export an interactive 3D-snapshot page [here](https://vtkplotter.embl.es/examples/K3D_snapshot.html)). - - Interoperability with the [trimesh](https://trimsh.org/) library. -### Command Line Interface +### ➜ Command Line Interface Visualize a mesh from a terminal window with: ```bash vtkplotter mesh.obj @@ -123,7 +123,7 @@ To visualize multiple files or files time-sequences try `-n` or `-s` options. Us |`vtkplotter head.vti` |`vtkplotter -s *.vtk` |`vtkplotter `
`--slicer embr.slc` | `vtkplotter --lego embryo.slc`| |![isohead](https://user-images.githubusercontent.com/32848391/56972083-a7f3f800-6b6a-11e9-9cb3-1047b69dcad2.gif)| ![viz_raycast](https://user-images.githubusercontent.com/32848391/58336919-f7b1a080-7e44-11e9-9106-f574371093a8.gif) | ![viz_slicer](https://user-images.githubusercontent.com/32848391/56972084-a7f3f800-6b6a-11e9-98c4-dc4ffec70a5e.png) |![lego](https://user-images.githubusercontent.com/32848391/56969949-71b47980-6b66-11e9-8251-4bbdb275cb22.jpg) | -### Graphic User Interface +### ➜ Graphic User Interface A Graphic User Interface is available (mainly useful to *Windows 10* users): ![gui](https://user-images.githubusercontent.com/32848391/63259840-c861d280-c27f-11e9-9c2a-99d0fae85313.png) diff --git a/bin/vtkplotter b/bin/vtkplotter index ab90f041..d82e3d7e 100755 --- a/bin/vtkplotter +++ b/bin/vtkplotter @@ -1,7 +1,9 @@ #!/usr/bin/env python # from __future__ import division, print_function -from vtkplotter import Actor, Volume, Plotter, ProgressBar +from vtkplotter import Plotter, ProgressBar +from vtkplotter.mesh import Mesh +from vtkplotter.volume import Volume from vtkplotter import settings, printc, getColor, humansort, __version__ from vtkplotter import vtkio, utils, datadir import vtk @@ -418,7 +420,7 @@ def draw_scene(): ic = args.color cf.SetValue(0, threshold) cf.Update() - act = Actor(cf.GetOutput(), c=ic, alpha=args.alpha).wireframe(args.wireframe) + act = Mesh(cf.GetOutput(), c=ic, alpha=args.alpha).wireframe(args.wireframe) if args.flat: act.flat() else: @@ -448,7 +450,7 @@ def draw_scene(): cf.ComputeScalarsOff() cf.SetValue(0, wval) cf.Update() - a = Actor(cf.GetOutput(), ic, alpha=act.alpha()) + a = Mesh(cf.GetOutput(), ic, alpha=act.alpha()) bacts.update({wval_2: a}) # store it #print('generated', wval_2, wval, len(bacts.keys())) @@ -517,7 +519,7 @@ def draw_scene(): if isinstance(actor, vtk.vtkActor): - if isinstance(actor, Actor): + if isinstance(actor, Mesh): actors.append(actor) actor.wireframe(wire) actor.lighting(args.lighting) @@ -960,64 +962,120 @@ else: ######################################################################### pr.add_argument("--lego", help="Voxel rendering for 3D image files", action="store_true") pr.add_argument("--cmap", help="Voxel rendering color map name", default='jet', metavar='') pr.add_argument("--mode", help="Voxel rendering composite mode", default=0, metavar='') - pr.add_argument("--run", help="Run example from vtkplotter-examples", default='', metavar='') + pr.add_argument("-r", "--run", help="Run example from vtkplotter-examples", metavar='') pr.add_argument("--list", help="List examples in vtkplotter-examples", action="store_true") args = pr.parse_args() if args.run: import glob exfiles = [f for f in glob.glob(datadir.replace('data','') + "**/*.py", recursive=True)] - matching = [s for s in exfiles if (args.run in s and "__" not in s and "version.py" not in s)] - if len(matching) > 1: - printc("Matching scripts are:", matching[:10], 'Running first only.', c='y', bold=0, italic=1) - if len(matching) == 0: - printc("No matching example found containing:", args.run, c=1) - printc("Use vtkplotter --list to show available scripts.", c=1) + matching = [s for s in exfiles if (args.run.lower() in os.path.basename(s).lower() and "__" not in s)] + matching = list(sorted(matching)) + nmat = len(matching) + if nmat == 0: + printc("No matching example found containing string:", args.run, c=1) + printc(" Use vtkplotter --list to show available scripts.", c=1) + printc(" Current datadir is:", datadir, c=1) exit(1) - print("Running", matching[0]) - os.system('python ' + matching[0]) + + if nmat > 1: + printc("\nSelect one of", nmat, "matching scripts:", c='y', italic=1) + + for mat in matching[:25]: + printc(os.path.basename(mat).replace('.py',''), c='y', italic=1, end=' ') + with open(mat) as fm: + lline = ''.join(fm.readlines(60)) + lline = lline.replace('\n','').replace('\'','').replace('\"','').replace('-','') + line = lline[:56] #cut + if line.startswith('from'): line='' + if line.startswith('import'): line='' + if len(lline) > len(line): + line += '..' + if len(line)>5: + printc('-', line, c='y', bold=0, italic=1) + else: + print() + + if nmat>25: + printc('...', c='y') + + if nmat > 1: + exit(0) + + if args.no_camera_share: # -i option to dump the full code + print() + with open(matching[0]) as fm: + codedump = fm.readlines() + for line in codedump: + printc(line.strip(), c='cyan', italic=1, bold=0) + print() + + printc("(in", os.path.dirname(matching[0])+')', c='y', bold=0, italic=1) + os.system('python3 ' + matching[0]) elif args.list: + print() printc("Available examples are:", box='-') expath = datadir.replace('data','') import glob - exfiles = [(f, os.path.basename(f)) for f in glob.glob(expath + "**/*.py", recursive=True)] + exfiles = [(f, os.path.basename(f)) + for f in glob.glob(expath + "**/*.py", recursive=True)] + nl = 4 + + if not len(exfiles): + printc("vtkplotter-example not installed?") + printc("> pip install -U git+https://github.com/marcomusy/vtkplotter-examples") + exit() - printc("basic examples:", c='g', bold=1) + printc("Basic examples:", c='g', bold=1, underline=1) + scs = [] for f,bn in exfiles: if "basic" in f: - printc(bn, c='g', bold=0, end=' ') + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='g', bold=0) - printc("\nadvanced examples:", c='y', bold=1) + printc("Advanced examples:", c='y', bold=1, underline=1) + scs = [] for f,bn in exfiles: if "advanced" in f: - printc(bn, c='y', bold=0, end=' ') + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='y', bold=0) - printc("\nvolumetric examples:", c='b', bold=1) + printc("Volumetric examples:", c='b', bold=1, underline=1) + scs = [] for f,bn in exfiles: if "volumetric" in f: - printc(bn, c='b', bold=0, end=' ') + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='b', bold=0) - printc("\ndolfin examples:", c='r', bold=1) + printc("Other examples:", c='cyan', bold=1, underline=1) + scs = [] for f,bn in exfiles: - if "dolfin" in f: - printc(bn, c='r', bold=0, end=' ') + if "other" in f and "dolfin" not in f and "trimesh" not in f: + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='cyan', bold=0) - printc("\ntrimesh examples:", c='m', bold=1) + printc(" Dolfin examples:", c='r', bold=1, underline=1) + scs = [] for f,bn in exfiles: - if "trimesh" in f: - printc(bn, c='m', bold=0, end=' ') + if "dolfin" in f: + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='r', bold=0) - printc("\nother examples:", c='w', bold=1) + printc(" Trimesh examples:", c='m', bold=1, underline=1) + scs = [] for f,bn in exfiles: - if "other" in f and "dolfin" not in f and "trimesh" not in f: - printc(bn, c='w', bold=0, end=' ') + if "trimesh" in f: + lb = ' ' if (len(scs)+1)%nl else '\n' + scs.append(lb+bn.replace('.py','')) + printc("".join(scs), c='m', bold=0) print() + else: draw_scene() - - - - - diff --git a/bin/vtkconvert b/bin/vtkplotter-convert similarity index 100% rename from bin/vtkconvert rename to bin/vtkplotter-convert diff --git a/docs/logos/sharpelab.py b/docs/logos/sharpelab.py deleted file mode 100644 index d3163759..00000000 --- a/docs/logos/sharpelab.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Sharpe lab animated logo. -""" -from vtkplotter import * - -exa = Polygon().scale(4.1).pos(5.25, 4.8, 0).off() -box = Box([10, 5, 0], 20, 20, 15).alpha(0) -his = hexHistogram([-1, 1], [-1, 1]).getActors() - -exah, cmh = [], [] -for h in his: - cm = h.centerOfMass() - if exa.isInside(cm): - h.c('green').shrink(0.94) - exah.append(h) - cmh.append(cm) -exah[13].c('red') - -t1 = Text("Sharpe Lab", (9.1, 5.0, -0.1), c="k") -t2 = Text("EMBL Barcelona", (9.2, 3.4, -0.1), c="dg") - -for ti in reversed(range(100)): - t = ti / 100. - for j, h in enumerate(exah): - cx, cy, _ = cmh[j] - [4,5,0] - h.pos(cos(cy*t) *t*2, sin(cx*t)*t*2, t*cx/2).alpha((1-t)**3) - t1.alpha((1-t)**4) - t2.scale([(1-t)*0.67, (1-t)*0.75, (1-t)*0.75]).alpha((1-t)**2) - show(box, exa, exah, t1, t2, - resetcam=0, elevation=0, bg="w", axes=0, interactive=0) - -interactive() diff --git a/docs/news.txt b/docs/news.txt index 3de602ce..b74734e7 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -1,4 +1,4 @@ -20xx/xx/xx +2020/01/08 - added type axes=11 (horizontal grid) - fix bug by @m-albert on Volume from numpy object - fix typos in docs @@ -7,9 +7,15 @@ - add testing directory with circleCI - examples and package are now split into two separate repos - added possibility to run any example from CLI `vtkplotter --run shrink.py` + or list them all with `vtkplotter --list` +- reshuffled methods by splitting actors.py module +- general improvements and stability +- some methods are now deprecated: getPoints becomes points() etc. +- Actor() becomes Mesh() +- added advanced/centerline.py examples +- bump version to 2020.0.1 - -2019/21/19 +2019/11/19 - added PDV paraview file reading. - PDB protein data bank file reader - added error bars in plotxy() diff --git a/setup.py b/setup.py index 4cb45fc7..eff0bd9a 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name='vtkplotter', version=verstr, packages=['vtkplotter'], - scripts=['bin/vtkplotter', 'bin/vtkconvert'], + scripts=['bin/vtkplotter', 'bin/vtkplotter-convert'], install_requires=['vtk'], description='''A python module for scientific visualization, analysis and animation of 3D objects and point clouds based on VTK.''', @@ -37,11 +37,15 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7' + 'Programming Language :: Python :: 3.8' ], include_package_data=True ) + + + ############################################################## # # check examples # change version in vtkplotter/version.py @@ -50,12 +54,12 @@ # remove trailing spaces # pip install . -# cd ~/Projects/vtkplotter-examples/ -# cd vtkplotter_examples && ./run_all.sh -# python prove3D/test_filetypes.py +# cd ~/Projects/vtkplotter-examples/vtkplotter_examples +# ./run_all.sh +# python prove/test_filetypes.py # check vtkconvert: -# vtkconvert vtkplotter/data/290.vtk -to ply; vtkplotter vtkplotter/data/290.ply +# vtkconvert data/290.vtk -to ply; vtkplotter data/290.ply # check on python2 the same stuff is ok # cd ~/Projects/vtkplotter/ @@ -66,7 +70,7 @@ # cd ~/Projects/vtkplotter-examples/ # jupyter notebook > /dev/null 2>&1 -# cd ~/Projects/vtkplotter/ +# cd ~/Projects/vtkplotter-examples/ # rm -rf v*examples/*/.ipynb_checkpoints v*examples/*/*/.ipynb_checkpoints .ipynb_checkpoints/ # rm -rf v*examples/other/dolfin/navier_stokes_cylinder/ v*examples/other/dolfin/shuttle.xml # rm v*examples/other/trimesh/featuretype.STL v*examples/other/trimesh/machinist.XAML diff --git a/tests/run_all.sh b/tests/common/run_all.sh similarity index 71% rename from tests/run_all.sh rename to tests/common/run_all.sh index a43a639d..5b745942 100755 --- a/tests/run_all.sh +++ b/tests/common/run_all.sh @@ -1,7 +1,7 @@ #!/bin/bash # source run_all.sh # -for f in test_*.py #dolfin/test_*.py +for f in test_*.py do echo "Processing $f script.." python3 $f diff --git a/tests/test_actors.py b/tests/common/test_actors.py similarity index 89% rename from tests/test_actors.py rename to tests/common/test_actors.py index b640d884..004b75ce 100644 --- a/tests/test_actors.py +++ b/tests/common/test_actors.py @@ -7,16 +7,16 @@ sphere = Sphere(res=24) carr = cone.cellCenters()[:, 2] -parr = cone.getPoints()[:, 0] +parr = cone.points()[:, 0] cone.addCellScalars(carr, 'carr') cone.addPointScalars(parr, 'parr') carr = sphere.cellCenters()[:, 2] -parr = sphere.getPoints()[:, 0] +parr = sphere.points()[:, 0] sphere.addCellScalars(carr, 'carr') sphere.addPointScalars(parr, 'parr') -sphere.addPointVectors(np.sin(sphere.getPoints()), 'pvectors') +sphere.addPointVectors(np.sin(sphere.points()), 'pvectors') #sphere.addIDs() sphere.addElevationScalars() @@ -81,16 +81,16 @@ ###################################### rotate cr = cone.pos(0,0,0).clone().rotate(90, axis=(0, 1, 0)) -assert np.max(cr.coordinates()[:,2]) < 1.01 +assert np.max(cr.points()[:,2]) < 1.01 ###################################### orientation cr = cone.pos(0,0,0).clone().orientation(newaxis=(1, 1, 0)) -assert np.max(cr.coordinates()[:,2]) < 1.01 +assert np.max(cr.points()[:,2]) < 1.01 # scale cr.scale(5) -assert np.max(cr.coordinates()[:,2]) > 4.99 +assert np.max(cr.points()[:,2]) > 4.99 ###################################### orientation @@ -128,23 +128,19 @@ assert isinstance(cone+sphere, vtk.vtkAssembly) -###################################### getPoint, setPoint -print('Test getPoint, setPoint') +###################################### points() +print('Test points') -assert len(sphere.getPoint(0)) -s2 = sphere.clone().setPoint(10, [1,2,3]) -assert np.allclose(s2.getPoint(10), [1,2,3]) - -pts = sphere.getPoints() +s2 = sphere.clone() +pts = sphere.points() pts2 = pts + [1,2,3] -pts3 = s2.setPoints(pts2).getPoints() +pts3 = s2.points(pts2).points() assert np.allclose(pts2, pts3) ###################################### faces print('Test faces', np.array(sphere.faces()).shape ) assert np.array(sphere.faces()).shape == (2112, 3) -assert sphere.getPolygons().shape[0] == 8448 ###################################### texture @@ -226,7 +222,7 @@ ###################################### crop print('Test crop') c2 = cone.clone().crop(left=0.5) -assert np.min(c2.coordinates()[:,0]) > -0.001 +assert np.min(c2.points()[:,0]) > -0.001 ###################################### subdivide @@ -267,10 +263,10 @@ asse = cone+sphere ###################################### getActors -print('Test getActors') -assert len(asse.getActors()) ==2 -assert asse.getActor(0) == cone -assert asse.getActor(1) == sphere +print('Test getMeshes') +assert len(asse.getMeshes()) ==2 +assert asse.getMesh(0) == cone +assert asse.getMesh(1) == sphere assert 4.1 < asse.diagonalSize() < 4.2 @@ -278,7 +274,7 @@ ############################################################################ Volume X, Y, Z = np.mgrid[:30, :30, :30] scalar_field = ((X-15)**2 + (Y-15)**2 + (Z-15)**2)/225 -print('\nTest Volume, scalar min, max =', np.min(scalar_field), np.max(scalar_field)) +print('Test Volume, scalar min, max =', np.min(scalar_field), np.max(scalar_field)) vol = Volume(scalar_field) volarr = vol.getPointArray() @@ -294,7 +290,3 @@ lego = vol.legosurface(vmin=0.3, vmax=0.5) assert 2610 < lego.N() < 2630 - - - - diff --git a/tests/dolfin/run_all.sh b/tests/dolfin/run_all.sh new file mode 100755 index 00000000..5b745942 --- /dev/null +++ b/tests/dolfin/run_all.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# source run_all.sh +# +for f in test_*.py +do + echo "Processing $f script.." + python3 $f +done diff --git a/tests/dolfin/test_aaa.py b/tests/dolfin/test_aaa.py deleted file mode 100644 index 2ac73bed..00000000 --- a/tests/dolfin/test_aaa.py +++ /dev/null @@ -1,188 +0,0 @@ -# Copyright (C) 2014 Garth N. Wells -# -# This file is part of DOLFIN (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -# This demo solves the equations of static linear elasticity for a -# pulley subjected to centripetal accelerations. The solver uses -# smoothed aggregation algebraic multigrid. - -from contextlib import ExitStack - -import numpy as np -from petsc4py import PETSc - -import dolfin -from dolfin import (MPI, BoxMesh, DirichletBC, Function, VectorFunctionSpace, - cpp) -from dolfin.cpp.mesh import CellType -from dolfin.fem import apply_lifting, assemble_matrix, assemble_vector, set_bc -#from dolfin.io import XDMFFile -from dolfin.la import VectorSpaceBasis -from ufl import (Identity, SpatialCoordinate, TestFunction, TrialFunction, - as_vector, dx, grad, inner, sym, tr) - - -def build_nullspace(V): - """Function to build null space for 3D elasticity""" - - # Create list of vectors for null space - index_map = V.dofmap.index_map - nullspace_basis = [cpp.la.create_vector(index_map) for i in range(6)] - - with ExitStack() as stack: - vec_local = [stack.enter_context(x.localForm()) for x in nullspace_basis] - basis = [np.asarray(x) for x in vec_local] - - # Build translational null space basis - V.sub(0).dofmap.set(basis[0], 1.0) - V.sub(1).dofmap.set(basis[1], 1.0) - V.sub(2).dofmap.set(basis[2], 1.0) - - # Build rotational null space basis - V.sub(0).set_x(basis[3], -1.0, 1) - V.sub(1).set_x(basis[3], 1.0, 0) - V.sub(0).set_x(basis[4], 1.0, 2) - V.sub(2).set_x(basis[4], -1.0, 0) - V.sub(2).set_x(basis[5], 1.0, 1) - V.sub(1).set_x(basis[5], -1.0, 2) - - # Create vector space basis and orthogonalize - basis = VectorSpaceBasis(nullspace_basis) - basis.orthonormalize() - - _x = [basis[i] for i in range(6)] - nsp = PETSc.NullSpace() - nsp.create(_x) - return nsp - - -# Load mesh from file -# mesh = Mesh(MPI.comm_world) -# XDMFFile(MPI.comm_world, "../pulley.xdmf").read(mesh) - -mesh = UnitCubeMesh(MPI.comm_world, 3, 3, 3) -#mesh = BoxMesh( -# MPI.comm_world, [np.array([0.0, 0.0, 0.0]), -# np.array([2.0, 1.0, 1.0])], [12, 12, 12], -# CellType.tetrahedron, dolfin.cpp.mesh.GhostMode.none) -cmap = dolfin.fem.create_coordinate_map(mesh.ufl_domain()) -mesh.geometry.coord_mapping = cmap - -def boundary(x): - return np.logical_or(x[0] < 10.0 * np.finfo(float).eps, - x[0] > 1.0 - 10.0 * np.finfo(float).eps) - - -# Rotation rate and mass density -omega = 300.0 -rho = 10.0 - -# Loading due to centripetal acceleration (rho*omega^2*x_i) -x = SpatialCoordinate(mesh) -f = as_vector((rho * omega**2 * x[0], rho * omega**2 * x[1], 0.0)) - -# Elasticity parameters -E = 1.0e9 -nu = 0.0 -mu = E / (2.0 * (1.0 + nu)) -lmbda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)) - -def sigma(v): - return 2.0 * mu * sym(grad(v)) + lmbda * tr(sym(grad(v))) * Identity( - len(v)) - - -# Create function space -V = VectorFunctionSpace(mesh, ("Lagrange", 1)) - -# Define variational problem -u = TrialFunction(V) -v = TestFunction(V) -a = inner(sigma(u), grad(v)) * dx -L = inner(f, v) * dx - -u0 = Function(V) -with u0.vector.localForm() as bc_local: - bc_local.set(0.0) - -# Set up boundary condition on inner surface -bc = DirichletBC(V, u0, boundary) - -# Assemble system, applying boundary conditions and preserving symmetry) -A = assemble_matrix(a, [bc]) -A.assemble() - -b = assemble_vector(L) -apply_lifting(b, [a], [[bc]]) -b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE) -set_bc(b, [bc]) - -# Create solution function -u = Function(V) - -# Create near null space basis (required for smoothed aggregation AMG). -null_space = build_nullspace(V) - -# Attach near nullspace to matrix -A.setNearNullSpace(null_space) - -# Set solver options -opts = PETSc.Options() -opts["ksp_type"] = "cg" -opts["ksp_rtol"] = 1.0e-12 -opts["pc_type"] = "gamg" - -# Use Chebyshev smoothing for multigrid -opts["mg_levels_ksp_type"] = "chebyshev" -opts["mg_levels_pc_type"] = "jacobi" - -# Improve estimate of eigenvalues for Chebyshev smoothing -opts["mg_levels_esteig_ksp_type"] = "cg" -opts["mg_levels_ksp_chebyshev_esteig_steps"] = 20 - -# Create CG Krylov solver and turn convergence monitoring on -solver = PETSc.KSP().create(MPI.comm_world) -solver.setFromOptions() - -# Set matrix operator -solver.setOperators(A) - -# Compute solution -solver.setMonitor(lambda ksp, its, rnorm: print("Iteration: {}, rel. residual: {}".format(its, rnorm))) -solver.solve(b, u.vector) -#solver.view() - - - - -############################### Plot solution -from vtkplotter.dolfin import plot - -plot(u, mode="displaced mesh", - scalarbar=False, - axes=1, - bg='white', - viewup='z', - offscreen=1) - -################################################################################# -import numpy as np -from vtkplotter import settings, screenshot -actor = settings.plotter_instance.actors[0] -solution = actor.scalars(0) - -screenshot('elasticbeam.png') - -print('ArrayNames', actor.getArrayNames()) -print('min', 'mean', 'max, N:') -print(np.min(solution), np.mean(solution), np.max(solution), len(solution)) - - - -# Plot solution -# import matplotlib.pyplot as plt -# import dolfin.plotting -# dolfin.plotting.plot(u) -# plt.show() \ No newline at end of file diff --git a/vtkplotter/__init__.py b/vtkplotter/__init__.py index e95b20f7..2349e3cf 100644 --- a/vtkplotter/__init__.py +++ b/vtkplotter/__init__.py @@ -8,40 +8,14 @@ A full list of examples can be found in directories: - - `examples/basic `_ , - - `examples/advanced `_ , - - `examples/volumetric `_, + - `examples/basic `_ + - `examples/advanced `_ + - `examples/volumetric `_ - `examples/simulations `_ - `examples/plotting2d `_ - - `examples/others `_. + - `examples/others `_ - `examples/others/dolfin `_. - - `examples/others/trimesh `_. - - -Publications where ``vtkplotter`` has been used so far: - -1. Diego, X. *et al,*: *"Key features of Turing systems are determined purely by network topology"*, -`Physical Review X 20 June 2018 `_. - -2. M. Musy, K. Flaherty, J. Raspopovic, A. Robert-Moreno, J. T. Richtsmeier, J. Sharpe: -*"A Quantitative Method for Staging Mouse Limb Embryos based on Limb Morphometry"*, -Development 2018, `doi: 10.1242/dev.154856 `_, -5 April 2018. - -3. G. Dalmasso *et al.*, "Evolution in space and time of 3D volumetric images", in preparation. - -4. M. Musy, G. Dalmasso, J. Sharpe and N. Sime, "`vtkplotter`: plotting in FEniCS with python", -`link `_. -Poster at the FEniCS'2019 Conference, -Carnegie Institution for Science Department of Terrestrial Magnetism, Washington DC, June 2019. - -**Have you found this software useful for your research? Please cite it as:** - -M. Musy, et al., -"`vtkplotter`, a python module for scientific visualization, -analysis and animation of 3D objects and point clouds based on VTK.". Zenodo, -`doi: 10.5281/zenodo.2561402 `_, -10 February 2019. + - `examples/others/trimesh `_ """ from __future__ import print_function @@ -59,15 +33,20 @@ from vtkplotter.plot2d import * from vtkplotter.shapes import * from vtkplotter.vtkio import * -from vtkplotter.actors import * + +from vtkplotter.base import ActorBase +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh, merge, Actor # Actor is obsolete +from vtkplotter.picture import Picture +from vtkplotter.volume import Volume + from vtkplotter.utils import * from vtkplotter.colors import * import vtkplotter.settings as settings from vtkplotter.settings import datadir, embedWindow -# hack to make docs work -# need to uncomment this and Prop in actors.py to generate documentation html -#from vtkplotter.dolfin import _inputsort +# hack: need to uncomment this to generate documentation html +from vtkplotter.dolfin import _inputsort from numpy import sin, cos, sqrt, exp, log, dot, cross, array, arange @@ -81,10 +60,9 @@ # colors : - -############### +########################################################################### settings._init() -############### - +########################################################################### ## deprecations ############################################################ #def isolines(*args, **kargs): diff --git a/vtkplotter/addons.py b/vtkplotter/addons.py index 0c3e7a9b..dd3bfac9 100644 --- a/vtkplotter/addons.py +++ b/vtkplotter/addons.py @@ -3,7 +3,8 @@ """ from __future__ import division, print_function from vtkplotter.colors import printc, getColor -from vtkplotter.actors import Assembly, Actor +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh from vtkplotter.utils import precision, mag, isSequence import vtkplotter.shapes as shapes import vtkplotter.settings as settings @@ -53,7 +54,7 @@ def addLight( :param float intensity: intensity between 0 and 1. :param bool removeOthers: remove all other lights in the scene :param bool showsource: if `True`, will show a representation - of the source of light as an extra Actor + of the source of light as an extra Mesh .. hint:: |lights.py|_ """ @@ -79,7 +80,7 @@ def addLight( ##################################################################### -def addScalarBar(actor, +def addScalarBar(mesh, pos=(0.8,0.05), title="", titleXOffset=0, @@ -90,43 +91,43 @@ def addScalarBar(actor, horizontal=False, vmin=None, vmax=None, ): - """Add a 2D scalar bar for the specified actor. + """Add a 2D scalar bar for the specified mesh. .. hint:: |mesh_coloring| |mesh_coloring.py|_ |scalarbars.py|_ """ vp = settings.plotter_instance - if not hasattr(actor, "mapper"): - printc("~times addScalarBar(): input is invalid,", type(actor), c=1) + if not hasattr(mesh, "mapper"): + printc("~times addScalarBar(): input is invalid,", type(mesh), c=1) return None if vp and vp.renderer: c = (0.9, 0.9, 0.9) if np.sum(vp.renderer.GetBackground()) > 1.5: c = (0.1, 0.1, 0.1) - if isinstance(actor.scalarbar, vtk.vtkActor): - vp.renderer.RemoveActor(actor.scalarbar) - elif isinstance(actor.scalarbar, Assembly): - for a in actor.scalarbar.getActors(): + if isinstance(mesh.scalarbar, vtk.vtkActor): + vp.renderer.RemoveActor(mesh.scalarbar) + elif isinstance(mesh.scalarbar, Assembly): + for a in mesh.scalarbar.getMeshes(): vp.renderer.RemoveActor(a) if c is None: c = 'gray' - if isinstance(actor, Actor): - lut = actor.mapper().GetLookupTable() + if isinstance(mesh, Mesh): + lut = mesh.mapper().GetLookupTable() if not lut: return None - vtkscalars = actor._polydata.GetPointData().GetScalars() + vtkscalars = mesh._polydata.GetPointData().GetScalars() if vtkscalars is None: - vtkscalars = actor._polydata.GetCellData().GetScalars() + vtkscalars = mesh._polydata.GetCellData().GetScalars() if not vtkscalars: return None rng = list(vtkscalars.GetRange()) if vmin is not None: rng[0] = vmin if vmax is not None: rng[1] = vmax - actor.mapper().SetScalarRange(rng) + mesh.mapper().SetScalarRange(rng) - elif isinstance(actor, 'Volume'): + elif isinstance(mesh, 'Volume'): # to be implemented pass @@ -174,7 +175,7 @@ def addScalarBar(actor, sctxt.SetBold(0) sctxt.SetFontSize(titleFontSize) sb.PickableOff() - actor.scalarbar = sb + mesh.scalarbar = sb return sb @@ -202,8 +203,8 @@ def addScalarBar3D( ``obj`` input can be: - a list of numbers, - a list of two numbers in the form `(min, max)`, - - a ``vtkActor`` already containing a set of scalars associated to vertices or cells, - - if ``None`` the last actor in the list of actors will be used. + - a ``Mesh`` already containing a set of scalars associated to vertices or cells, + - if ``None`` the last mesh in the list of meshs will be used. :param float sx: thickness of scalarbar :param float sy: length of scalarbar @@ -227,11 +228,11 @@ def addScalarBar3D( if c is None: c = 'gray' c = getColor(c) - if isinstance(obj, Actor): + if isinstance(obj, Mesh): if cmap is None: lut = obj.mapper().GetLookupTable() if not lut: - print("Error in ScalarBar3D: actor has no active scalar array.", [obj]) + print("Error in ScalarBar3D: mesh has no active scalar array.", [obj]) return None else: lut = cmap @@ -240,7 +241,7 @@ def addScalarBar3D( elif isSequence(obj): vmin, vmax = np.min(obj), np.max(obj) else: - print("Error in ScalarBar3D(): input must be Actor or list.", type(obj)) + print("Error in ScalarBar3D(): input must be Mesh or list.", type(obj)) raise RuntimeError() # build the color scale part @@ -280,7 +281,7 @@ def addScalarBar3D( sact.RotateY(np.rad2deg(theta)) sact.SetPosition(pos) sact.PickableOff() - if isinstance(obj, Actor): + if isinstance(obj, Mesh): obj.scalarbar = sact return sact @@ -520,16 +521,16 @@ def addButton( return bu -def addCutterTool(actor): +def addCutterTool(mesh): """Create handles to cut away parts of a mesh. |cutter| |cutter.py|_ """ - if isinstance(actor, vtk.vtkVolume): - return _addVolumeCutterTool(actor) - elif isinstance(actor, vtk.vtkImageData): + if isinstance(mesh, vtk.vtkVolume): + return _addVolumeCutterTool(mesh) + elif isinstance(mesh, vtk.vtkImageData): from vtkplotter import Volume - return _addVolumeCutterTool(Volume(actor)) + return _addVolumeCutterTool(Volume(mesh)) vp = settings.plotter_instance if not vp.renderer: @@ -537,8 +538,8 @@ def addCutterTool(actor): vp.show(interactive=0) vp.interactive = save_int - vp.clickedActor = actor - apd = actor.polydata() + vp.clickedActor = mesh + apd = mesh.polydata() planes = vtk.vtkPlanes() planes.SetBounds(apd.GetBounds()) @@ -552,15 +553,15 @@ def addCutterTool(actor): clipper.Update() cpoly = clipper.GetOutput() - act0 = Actor(cpoly, alpha=actor.alpha()) # the main cut part - act0.mapper().SetLookupTable(actor.mapper().GetLookupTable()) - act0.mapper().SetScalarRange(actor.mapper().GetScalarRange()) + act0 = Mesh(cpoly, alpha=mesh.alpha()) # the main cut part + act0.mapper().SetLookupTable(mesh.mapper().GetLookupTable()) + act0.mapper().SetScalarRange(mesh.mapper().GetScalarRange()) - act1 = Actor() + act1 = Mesh() act1.mapper().SetInputConnection(clipper.GetClippedOutputPort()) # needs OutputPort?? act1.alpha(0.04).color((0.5,0.5,0.5)).wireframe() - vp.renderer.RemoveActor(actor) + vp.renderer.RemoveActor(mesh) vp.renderer.AddActor(act0) vp.renderer.AddActor(act1) vp.renderer.ResetCamera() @@ -582,8 +583,8 @@ def selectPolygons(vobj, event): vp.cutterWidget = boxWidget vp.clickedActor = act0 - if actor in vp.actors: - ia = vp.actors.index(actor) + if mesh in vp.actors: + ia = vp.actors.index(mesh) vp.actors[ia] = act0 printc("Mesh Cutter Tool:", c="m", invert=1) @@ -611,7 +612,6 @@ def _addVolumeCutterTool(vol): vp.cutterWidget = boxWidget vp.renderer.AddVolume(vol) - vp.interactor.Render() planes = vtk.vtkPlanes() def clipVolumeRender(obj, event): @@ -631,7 +631,7 @@ def clipVolumeRender(obj, event): printc("Volume Cutter Tool:", c="m", invert=1) printc(" Move gray handles to cut parts of the volume", c="m") - vp.renderer.ResetCamera() + vp.interactor.Render() boxWidget.On() vp.interactor.Start() @@ -639,7 +639,7 @@ def clipVolumeRender(obj, event): vp.widgets.append(boxWidget) ##################################################################### -def addIcon(iconActor, pos=3, size=0.08): +def addIcon(mesh, pos=3, size=0.08): """Add an inset icon mesh into the renderer. :param pos: icon position in the range [1-4] indicating one of the 4 corners, @@ -655,7 +655,7 @@ def addIcon(iconActor, pos=3, size=0.08): vp.show(interactive=0) vp.interactive = save_int widget = vtk.vtkOrientationMarkerWidget() - widget.SetOrientationMarker(iconActor) + widget.SetOrientationMarker(mesh) widget.SetInteractor(vp.interactor) if isSequence(pos): widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size) @@ -671,13 +671,13 @@ def addIcon(iconActor, pos=3, size=0.08): widget.EnabledOn() widget.InteractiveOff() vp.widgets.append(widget) - if iconActor in vp.actors: - vp.actors.remove(iconActor) + if mesh in vp.actors: + vp.actors.remove(mesh) return widget ##################################################################### def computeVisibleBounds(): - """Calculate max actors bounds and sizes.""" + """Calculate max meshs bounds and sizes.""" bns = [] for a in settings.plotter_instance.actors: if a and a.GetPickable(): @@ -805,7 +805,7 @@ def addAxes(axtype=None, c=None): # for j in range(m-1): # for i in range(n-1): # faces.append([i+j*n, i+j*n+1, i+1+(j+1)*n, i+(j+1)*n]) -# return Actor([verts, faces]).lw(gridLineWidth).orientation(normal).pos(pos) +# return Mesh([verts, faces]).lw(gridLineWidth).orientation(normal).pos(pos) if isinstance(vp.axes, dict): axes = vp.axes @@ -1375,7 +1375,7 @@ def addAxes(axtype=None, c=None): largestact = a sz = d if isinstance(largestact, Assembly): - ocf.SetInputData(largestact.getActor(0).GetMapper().GetInput()) + ocf.SetInputData(largestact.getMesh(0).GetMapper().GetInput()) else: ocf.SetInputData(largestact.GetMapper().GetInput()) ocf.Update() @@ -1446,7 +1446,7 @@ def addAxes(axtype=None, c=None): src.SetYLength(vbb[3] - vbb[2]) src.SetZLength(vbb[5] - vbb[4]) src.Update() - ca = Actor(src.GetOutput(), c, 0.5).wireframe(True) + ca = Mesh(src.GetOutput(), c, 0.5).wireframe(True) ca.pos((vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2) ca.PickableOff() vp.renderer.AddActor(ca) @@ -1590,9 +1590,9 @@ def addLegend(): if isinstance(a, vtk.vtkLegendBoxActor): vp.renderer.RemoveActor(a) - actors = vp.getActors() + meshs = vp.getMeshes() acts, texts = [], [] - for i, a in enumerate(actors): + for i, a in enumerate(meshs): if i < len(vp._legend) and vp._legend[i] != "": if isinstance(vp._legend[i], str): texts.append(vp._legend[i]) diff --git a/vtkplotter/analysis.py b/vtkplotter/analysis.py index d802d73f..76263621 100644 --- a/vtkplotter/analysis.py +++ b/vtkplotter/analysis.py @@ -7,7 +7,10 @@ import vtkplotter.utils as utils import vtkplotter.colors as colors import vtkplotter.shapes as shapes -from vtkplotter.actors import Actor, Assembly, Volume + +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh +from vtkplotter.volume import Volume __doc__ = ( """ @@ -48,7 +51,7 @@ "pointSampler", "geodesic", "convexHull", - "actor2Volume", + "mesh2Volume", "splitByConnectivity", "projectSphereFilter", "extractSurface", @@ -95,7 +98,7 @@ def delaunay2D(plist, mode='xy', tol=None): if mode=='fit': delny.SetProjectionPlaneMode(vtk.VTK_BEST_FITTING_PLANE) delny.Update() - return Actor(delny.GetOutput()) + return Mesh(delny.GetOutput()) def delaunay3D(dataset, alpha=0, tol=None, boundary=True): @@ -110,7 +113,7 @@ def delaunay3D(dataset, alpha=0, tol=None, boundary=True): return deln.GetOutput() -def extrude(actor, zshift=1, rotation=0, dR=0, cap=True, res=1): +def extrude(mesh, zshift=1, rotation=0, dR=0, cap=True, res=1): """ Sweep a polygonal data creating a "skirt" from free edges and lines, and lines from vertices. The input dataset is swept around the z-axis to create new polygonal primitives. @@ -137,24 +140,24 @@ def extrude(actor, zshift=1, rotation=0, dR=0, cap=True, res=1): |extrude| |extrude.py|_ """ rf = vtk.vtkRotationalExtrusionFilter() - rf.SetInputData(actor.polydata()) + rf.SetInputData(mesh.polydata()) rf.SetResolution(res) rf.SetCapping(cap) rf.SetAngle(rotation) rf.SetTranslation(zshift) rf.SetDeltaRadius(dR) rf.Update() - act = Actor(rf.GetOutput(), c=actor.c(), alpha=actor.alpha()) + act = Mesh(rf.GetOutput(), c=mesh.c(), alpha=mesh.alpha()) return act.computeNormals(cells=False).phong() -def normalLines(actor, ratio=1, atCells=True): +def normalLines(mesh, ratio=1, atCells=True): """ - Build an ``Actor`` made of the normals at cells shown as lines. + Build an ``Mesh`` made of the normals at cells shown as lines. if `atCells` is `False` normals are shown at vertices. """ - poly = actor.computeNormals().polydata() + poly = mesh.computeNormals().polydata() if atCells: centers = vtk.vtkCellCenters() @@ -182,16 +185,16 @@ def normalLines(actor, ratio=1, atCells=True): glyph.SetScaleFactor(sc) glyph.OrientOn() glyph.Update() - glyphActor = Actor(glyph.GetOutput()) + glyphActor = Mesh(glyph.GetOutput()) glyphActor.mapper().SetScalarModeToUsePointFieldData() glyphActor.PickableOff() prop = vtk.vtkProperty() - prop.DeepCopy(actor.GetProperty()) + prop.DeepCopy(mesh.GetProperty()) glyphActor.SetProperty(prop) return glyphActor -def extractLargestRegion(actor): +def extractLargestRegion(mesh): """ Keep only the largest connected part of a mesh and discard all the smaller pieces. @@ -200,19 +203,19 @@ def extractLargestRegion(actor): conn = vtk.vtkConnectivityFilter() conn.SetExtractionModeToLargestRegion() conn.ScalarConnectivityOff() - if isinstance(actor, vtk.vtkActor): - poly = actor.GetMapper().GetInput() - prop = actor.GetProperty() - elif isinstance(actor, vtk.vtkPolyData): - poly = actor + if isinstance(mesh, vtk.vtkActor): + poly = mesh.GetMapper().GetInput() + prop = mesh.GetProperty() + elif isinstance(mesh, vtk.vtkPolyData): + poly = mesh prop = None conn.SetInputData(poly) conn.Update() epoly = conn.GetOutput() - eact = Actor(epoly) + eact = Mesh(epoly) if prop: pr = vtk.vtkProperty() - pr.DeepCopy(actor.GetProperty()) + pr.DeepCopy(mesh.GetProperty()) eact.SetProperty(pr) return eact @@ -238,18 +241,18 @@ def alignLandmarks(source, target, rigid=False): tf.SetInputData(source.polydata()) tf.SetTransform(lmt) tf.Update() - actor = Actor(tf.GetOutput()) - actor.info["transform"] = lmt + mesh = Mesh(tf.GetOutput()) + mesh.info["transform"] = lmt pr = vtk.vtkProperty() pr.DeepCopy(source.GetProperty()) - actor.SetProperty(pr) - return actor + mesh.SetProperty(pr) + return mesh def alignICP(source, target, iters=100, rigid=False): """ - Return a copy of source actor which is aligned to - target actor through the `Iterative Closest Point` algorithm. + Return a copy of source mesh which is aligned to + target mesh through the `Iterative Closest Point` algorithm. The core of the algorithm is to match each vertex in one surface with the closest surface point on the other, then apply the transformation @@ -259,9 +262,9 @@ def alignICP(source, target, iters=100, rigid=False): |align1| |align2| """ - if isinstance(source, Actor): + if isinstance(source, Mesh): source = source.polydata() - if isinstance(target, Actor): + if isinstance(target, Mesh): target = target.polydata() icp = vtk.vtkIterativeClosestPointTransform() @@ -277,9 +280,9 @@ def alignICP(source, target, iters=100, rigid=False): icpTransformFilter.SetTransform(icp) icpTransformFilter.Update() poly = icpTransformFilter.GetOutput() - actor = Actor(poly) + mesh = Mesh(poly) - # actor.info['transform'] = icp.GetLandmarkTransform() # not working! + # mesh.info['transform'] = icp.GetLandmarkTransform() # not working! # do it manually... sourcePoints = vtk.vtkPoints() targetPoints = vtk.vtkPoints() @@ -297,14 +300,14 @@ def alignICP(source, target, iters=100, rigid=False): landmarkTransform.SetTargetLandmarks(targetPoints) if rigid: landmarkTransform.SetModeToRigidBody() - actor.info["transform"] = landmarkTransform + mesh.info["transform"] = landmarkTransform - return actor + return mesh def alignProcrustes(sources, rigid=False): """ - Return an ``Assembly`` of aligned source actors with + Return an ``Assembly`` of aligned source meshes with the `Procrustes` algorithm. The output ``Assembly`` is normalized in size. `Procrustes` algorithm takes N set of points and aligns them in a least-squares sense @@ -332,9 +335,9 @@ def alignProcrustes(sources, rigid=False): acts = [] for i, s in enumerate(sources): poly = procrustes.GetOutput().GetBlock(i) - actor = Actor(poly) - actor.SetProperty(s.GetProperty()) - acts.append(actor) + mesh = Mesh(poly) + mesh.SetProperty(s.GetProperty()) + acts.append(mesh) assem = Assembly(acts) assem.info["transform"] = procrustes.GetLandmarkTransform() return assem @@ -345,7 +348,7 @@ def fitLine(points): """ Fits a line through points. - Extra info is stored in ``actor.info['slope']``, ``actor.info['center']``, ``actor.info['variances']``. + Extra info is stored in ``mesh.info['slope']``, ``mesh.info['center']``, ``mesh.info['variances']``. |fitline| |fitline.py|_ """ @@ -372,7 +375,7 @@ def fitPlane(points): """ Fits a plane to a set of points. - Extra info is stored in ``actor.info['normal']``, ``actor.info['center']``, ``actor.info['variance']``. + Extra info is stored in ``mesh.info['normal']``, ``mesh.info['center']``, ``mesh.info['variance']``. .. hint:: Example: |fitplanes.py|_ """ @@ -395,7 +398,7 @@ def fitSphere(coords): """ Fits a sphere to a set of points. - Extra info is stored in ``actor.info['radius']``, ``actor.info['center']``, ``actor.info['residue']``. + Extra info is stored in ``mesh.info['radius']``, ``mesh.info['center']``, ``mesh.info['residue']``. .. hint:: Example: |fitspheres1.py|_ @@ -435,8 +438,8 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): :param float pvalue: ellypsoid will contain the specified fraction of points. :param bool pcaAxes: if `True`, show the 3 PCA semi axes. - Extra info is stored in ``actor.info['sphericity']``, - ``actor.info['va']``, ``actor.info['vb']``, ``actor.info['vc']`` + Extra info is stored in ``mesh.info['sphericity']``, + ``mesh.info['va']``, ``mesh.info['vb']``, ``mesh.info['vc']`` (sphericity is equal to 0 for a perfect sphere). .. hint:: Examples: |pca.py|_ |cell_main.py|_ @@ -449,8 +452,8 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): colors.printc("~times Error in Ellipsoid(): scipy not installed. Skip.", c=1) return None - if isinstance(points, Actor): - coords = points.coordinates() + if isinstance(points, Mesh): + coords = points.points() else: coords = points if len(coords) == 0: @@ -481,8 +484,8 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): ftra.SetTransform(vtra) ftra.SetInputConnection(elliSource.GetOutputPort()) ftra.Update() - actor_elli = Actor(ftra.GetOutput(), "c", 0.5).phong() - actor_elli.GetProperty().BackfaceCullingOn() + mesh_elli = Mesh(ftra.GetOutput(), "c", 0.5).phong() + mesh_elli.GetProperty().BackfaceCullingOn() if pcaAxes: axs = [] for ax in ([1, 0, 0], [0, 1, 0], [0, 0, 1]): @@ -494,10 +497,10 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): t.SetTransform(vtra) t.SetInputData(l.GetOutput()) t.Update() - axs.append(Actor(t.GetOutput(), "c", 0.5).lineWidth(3)) - finact = Assembly([actor_elli] + axs) + axs.append(Mesh(t.GetOutput(), "c", 0.5).lineWidth(3)) + finact = Assembly([mesh_elli] + axs) else: - finact = actor_elli + finact = mesh_elli finact.info["sphericity"] = sphericity finact.info["va"] = ua finact.info["vb"] = ub @@ -505,22 +508,22 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): return finact -def smoothMLS1D(actor, f=0.2, radius=None, showNLines=0): +def smoothMLS1D(mesh, f=0.2, radius=None, showNLines=0): """ - Smooth actor or points with a `Moving Least Squares` variant. - The list ``actor.info['variances']`` contain the residue calculated for each point. - Input actor's polydata is modified. + Smooth mesh or points with a `Moving Least Squares` variant. + The list ``mesh.info['variances']`` contain the residue calculated for each point. + Input mesh's polydata is modified. :param float f: smoothing factor - typical range is [0,2]. - :param int showNLines: build an actor showing the fitting line for N random points. + :param int showNLines: build a mesh showing the fitting line for N random points. .. hint:: |moving_least_squares1D.py|_ |skeletonize.py|_ |moving_least_squares1D| |skeletonize| """ - if isinstance(actor, Assembly): - actor = actor.getActors()[0] - coords = actor.coordinates() + if isinstance(mesh, Assembly): + mesh = mesh.getMeshes()[0] + coords = mesh.points() ncoords = len(coords) Ncp = int(ncoords * f / 10) nshow = int(ncoords) @@ -534,7 +537,7 @@ def smoothMLS1D(actor, f=0.2, radius=None, showNLines=0): variances, newline, acts = [], [], [] for i, p in enumerate(coords): - points = actor.closestPoint(p, N=Ncp, radius=radius) + points = mesh.closestPoint(p, N=Ncp, radius=radius) if len(points) < 4: continue @@ -551,33 +554,33 @@ def smoothMLS1D(actor, f=0.2, radius=None, showNLines=0): acts += [fline, iapts] pcloud = shapes.Points(newline, c="r", alpha=0.5) - pcloud.GetProperty().SetPointSize(actor.GetProperty().GetPointSize()) + pcloud.GetProperty().SetPointSize(mesh.GetProperty().GetPointSize()) if showNLines: asse = Assembly([pcloud] + acts) asse.info["variances"] = np.array(variances) - return asse # NB: a demo actor is returned + return asse # NB: a demo mesh is returned else: pcloud.info["variances"] = np.array(variances) return pcloud -def smoothMLS2D(actor, f=0.2, radius=None, decimate=1, showNPlanes=0): +def smoothMLS2D(mesh, f=0.2, radius=None, decimate=1, showNPlanes=0): """ - Smooth actor or points with a `Moving Least Squares` algorithm variant. - The list ``actor.info['variances']`` contains the residue calculated for each point. + Smooth mesh or points with a `Moving Least Squares` algorithm variant. + The list ``mesh.info['variances']`` contains the residue calculated for each point. :param float f: smoothing factor - typical range is [0,2]. Ignored if ``radius`` is set. :param float radius: radius search in absolute units. If set then ``f`` is ignored. :param int decimate: decimation integer factor. - :param showNPlanes: build a demo actor showing the fitting plane for N random points. + :param showNPlanes: build a demo object showing the fitting plane for N random points. .. hint:: |moving_least_squares2D.py|_ |recosurface.py|_ |moving_least_squares2D| |recosurface| """ - if isinstance(actor, Assembly): - actor = actor.getActors()[0] - coords = actor.coordinates() + if isinstance(mesh, Assembly): + mesh = mesh.getMeshes()[0] + coords = mesh.points() ncoords = len(coords) Ncp = int(ncoords * f / 100) nshow = int(ncoords / decimate) @@ -600,7 +603,7 @@ def smoothMLS2D(actor, f=0.2, radius=None, decimate=1, showNPlanes=0): if i % decimate: continue - points = actor.closestPoint(p, N=Ncp, radius=radius) + points = mesh.closestPoint(p, N=Ncp, radius=radius) if radius and len(points) < 5: continue @@ -619,7 +622,7 @@ def smoothMLS2D(actor, f=0.2, radius=None, decimate=1, showNPlanes=0): acts += [plane, iapts] pcloud = shapes.Points(newpts, c="r", alpha=0.5, r=2) - pcloud.GetProperty().SetPointSize(actor.GetProperty().GetPointSize()) + pcloud.GetProperty().SetPointSize(mesh.GetProperty().GetPointSize()) if showNPlanes: asse = Assembly([pcloud] + acts) @@ -631,11 +634,11 @@ def smoothMLS2D(actor, f=0.2, radius=None, decimate=1, showNPlanes=0): return pcloud -def smoothMLS3D(actors, neighbours=10): +def smoothMLS3D(meshs, neighbours=10): """ - A time sequence of actors is being smoothed in 4D (3D + time) + A time sequence of point clouds (Mesh) is being smoothed in 4D (3D + time) using a `MLS (Moving Least Squares)` algorithm variant. - The time associated to an actor must be specified in advance with ``actor.time()`` method. + The time associated to an mesh must be specified in advance with ``mesh.time()`` method. Data itself can suggest a meaningful time separation based on the spatial distribution of points. @@ -646,14 +649,14 @@ def smoothMLS3D(actors, neighbours=10): from scipy.spatial import KDTree coords4d = [] - for a in actors: # build the list of 4d coordinates - coords3d = a.coordinates() + for a in meshs: # build the list of 4d coordinates + coords3d = a.points() n = len(coords3d) pttimes = [[a.time()]] * n coords4d += np.append(coords3d, pttimes, axis=1).tolist() - avedt = float(actors[-1].time() - actors[0].time()) / len(actors) - print("Average time separation between actors dt =", round(avedt, 3)) + avedt = float(meshs[-1].time() - meshs[0].time()) / len(meshs) + print("Average time separation between meshes dt =", round(avedt, 3)) coords4d = np.array(coords4d) newcoords4d = [] @@ -694,7 +697,7 @@ def smoothMLS3D(actors, neighbours=10): return act -def recoSurface(points, bins=256): +def recoSurface(pts, bins=256): """ Surface reconstruction from a scattered cloud of points. @@ -703,19 +706,19 @@ def recoSurface(points, bins=256): |recosurface| |recosurface.py|_ """ - if isinstance(points, vtk.vtkActor): - points = points.coordinates() - N = len(points) + if isinstance(pts, vtk.vtkActor): + pts = pts.points() + N = len(pts) if N < 50: print("recoSurface: Use at least 50 points.") return None - points = np.array(points) + pts = np.array(pts) ptsSource = vtk.vtkPointSource() ptsSource.SetNumberOfPoints(N) ptsSource.Update() vpts = ptsSource.GetOutput().GetPoints() - for i, p in enumerate(points): + for i, p in enumerate(pts): vpts.SetPoint(i, p) polyData = ptsSource.GetOutput() @@ -750,7 +753,7 @@ def recoSurface(points, bins=256): surface.ComputeGradientsOff() surface.SetInputConnection(distance.GetOutputPort()) surface.Update() - return Actor(surface.GetOutput(), "gold").bc("tomato") + return Mesh(surface.GetOutput(), "gold").bc("tomato") def cluster(points, radius): @@ -758,7 +761,7 @@ def cluster(points, radius): Clustering of points in space. `radius` is the radius of local search. - Individual subsets can be accessed through ``actor.clusters``. + Individual subsets can be accessed through ``mesh.clusters``. |clustering| |clustering.py|_ """ @@ -791,9 +794,9 @@ def cluster(points, radius): for i, aset in enumerate(sets): acts.append(shapes.Points(aset, c=i)) - actor = Assembly(acts) + asse = Assembly(acts) - actor.info["clusters"] = sets + asse.info["clusters"] = sets print("Nr. of extracted clusters", Nc) if Nc > 10: print("First ten:") @@ -802,8 +805,8 @@ def cluster(points, radius): print("...") break print("Cluster #" + str(i) + ", N =", len(sets[i])) - print("Access individual clusters through attribute: actor.cluster") - return actor + print("Access individual clusters through attribute: obj.info['cluster']") + return asse def removeOutliers(points, radius): @@ -841,11 +844,10 @@ def removeOutliers(points, radius): if not isactor: return outpts - actor = shapes.Points(outpts) - return actor # return same obj for concatenation + return shapes.Points(outpts) -def booleanOperation(actor1, operation, actor2): +def booleanOperation(mesh1, operation, mesh2): """Volumetric union, intersection and subtraction of surfaces. :param str operation: allowed operations: ``'plus'``, ``'intersect'``, ``'minus'``. @@ -853,8 +855,8 @@ def booleanOperation(actor1, operation, actor2): |boolean| |boolean.py|_ """ bf = vtk.vtkBooleanOperationPolyDataFilter() - poly1 = actor1.computeNormals().polydata() - poly2 = actor2.computeNormals().polydata() + poly1 = mesh1.computeNormals().polydata() + poly2 = mesh2.computeNormals().polydata() if operation.lower() == "plus" or operation.lower() == "+": bf.SetOperationToUnion() elif operation.lower() == "intersect": @@ -865,31 +867,29 @@ def booleanOperation(actor1, operation, actor2): bf.SetInputData(0, poly1) bf.SetInputData(1, poly2) bf.Update() - actor = Actor(bf.GetOutput(), c=None) - return actor + mesh = Mesh(bf.GetOutput(), c=None) + return mesh -def surfaceIntersection(actor1, actor2, tol=1e-06): - """Intersect 2 surfaces and return a line actor. +def surfaceIntersection(mesh1, mesh2, tol=1e-06): + """Intersect 2 surfaces and return a line mesh. .. hint:: |surfIntersect.py|_ """ bf = vtk.vtkIntersectionPolyDataFilter() - poly1 = actor1.GetMapper().GetInput() - poly2 = actor2.GetMapper().GetInput() + poly1 = mesh1.GetMapper().GetInput() + poly2 = mesh2.GetMapper().GetInput() bf.SetInputData(0, poly1) bf.SetInputData(1, poly2) bf.Update() - actor = Actor(bf.GetOutput(), "k", 1) - actor.GetProperty().SetLineWidth(3) - return actor + mesh = Mesh(bf.GetOutput(), "k", 1) + mesh.GetProperty().SetLineWidth(3) + return mesh -def _getimg(obj): - if isinstance(obj, vtk.vtkVolume): +def _getinput(obj): + if isinstance(obj, (vtk.vtkVolume, vtk.vtkActor)): return obj.GetMapper().GetInput() - elif isinstance(obj, vtk.vtkImageData): - return obj else: return obj @@ -899,10 +899,10 @@ def probePoints(vol, pts): Takes a ``Volume`` and probes its scalars at the specified points in space. Note that a mask is also output with valid/invalid points which can be accessed - with `actor.scalars()`. + with `mesh.getPointArray()`. """ - if isinstance(pts, Actor): - pts = pts.coordinates() + if isinstance(pts, Mesh): + pts = pts.points() def readPoints(): output = src.GetPolyDataOutput() @@ -921,13 +921,13 @@ def readPoints(): src = vtk.vtkProgrammableSource() src.SetExecuteMethod(readPoints) src.Update() - img = _getimg(vol) + img = _getinput(vol) probeFilter = vtk.vtkProbeFilter() probeFilter.SetSourceData(img) probeFilter.SetInputConnection(src.GetOutputPort()) probeFilter.Update() - pact = Actor(probeFilter.GetOutput()) + pact = Mesh(probeFilter.GetOutput()) pact.mapper().SetScalarRange(img.GetScalarRange()) #del src # to avoid memory leaks, incompatible with python2 return pact @@ -943,13 +943,13 @@ def probeLine(vol, p1, p2, res=100): line.SetResolution(res) line.SetPoint1(p1) line.SetPoint2(p2) - img = _getimg(vol) + img = _getinput(vol) probeFilter = vtk.vtkProbeFilter() probeFilter.SetSourceData(img) probeFilter.SetInputConnection(line.GetOutputPort()) probeFilter.Update() - lact = Actor(probeFilter.GetOutput()) + lact = Mesh(probeFilter.GetOutput()) lact.mapper().SetScalarRange(img.GetScalarRange()) #del line # to avoid memory leaks, incompatible with python2 return lact @@ -957,11 +957,11 @@ def probeLine(vol, p1, p2, res=100): def probePlane(vol, origin=(0, 0, 0), normal=(1, 0, 0)): """ - Takes a ``Volume`` and probes its scalars on a plane. + Takes a ``Volume`` or other vtk datasets and probes its scalars on a plane. |probePlane| |probePlane.py|_ """ - img = _getimg(vol) + img = _getinput(vol) plane = vtk.vtkPlane() plane.SetOrigin(origin) plane.SetNormal(normal) @@ -970,9 +970,9 @@ def probePlane(vol, origin=(0, 0, 0), normal=(1, 0, 0)): planeCut.SetInputData(img) planeCut.SetCutFunction(plane) planeCut.Update() - cutActor = Actor(planeCut.GetOutput(), c=None) # ScalarVisibilityOn - cutActor.mapper().SetScalarRange(img.GetPointData().GetScalars().GetRange()) - return cutActor + cutmesh = Mesh(planeCut.GetOutput(), c=None) # ScalarVisibilityOn + cutmesh.mapper().SetScalarRange(img.GetScalarRange()) + return cutmesh def resampleArrays(source, target, tol=None): @@ -1011,8 +1011,8 @@ def volumeOperation(volume1, operation, volume2=None): |volumeOperations| |volumeOperations.py|_ """ op = operation.lower() - image1 = _getimg(volume1) - image2 = _getimg(volume2) + image1 = _getinput(volume1) + image2 = _getinput(volume2) if op in ["median"]: mf = vtk.vtkImageMedian3D() @@ -1116,14 +1116,14 @@ def volumeOperation(volume1, operation, volume2=None): return Volume(mat.GetOutput()) -def thinPlateSpline(actor, sourcePts, targetPts, userFunctions=(None, None), sigma=1): +def thinPlateSpline(mesh, sourcePts, targetPts, userFunctions=(None, None), sigma=1): """ `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set of source and target landmarks. Any point on the mesh close to a source landmark will be moved to a place close to the corresponding target landmark. The points in between are interpolated smoothly using Bookstein's Thin Plate Spline algorithm. - Transformation object can be retrieved with ``actor.getTransform()``. + Transformation object can be retrieved with ``mesh.getTransform()``. :param userFunctions: You may supply both the function and its derivative with respect to r. @@ -1157,12 +1157,12 @@ def thinPlateSpline(actor, sourcePts, targetPts, userFunctions=(None, None), sig transform.SetSourceLandmarks(ptsou) transform.SetTargetLandmarks(pttar) - tfa = transformFilter(actor.polydata(), transform) + tfa = transformFilter(mesh.polydata(), transform) tfa.info["transform"] = transform return tfa -def warpMeshToPoint(actor, point, factor=0.1, absolute=True): +def warpMeshToPoint(mesh, point, factor=0.1, absolute=True): """ Modify the mesh coordinates by moving the vertices towards a specified point. @@ -1183,40 +1183,40 @@ def warpMeshToPoint(actor, point, factor=0.1, absolute=True): |warpto| """ warpTo = vtk.vtkWarpTo() - warpTo.SetInputData(actor.polydata()) + warpTo.SetInputData(mesh.polydata()) warpTo.SetPosition(point) warpTo.SetScaleFactor(factor) warpTo.SetAbsolute(absolute) warpTo.Update() prop = vtk.vtkProperty() - prop.DeepCopy(actor.GetProperty()) - a = Actor(warpTo.GetOutput()) + prop.DeepCopy(mesh.GetProperty()) + a = Mesh(warpTo.GetOutput()) a.SetProperty(prop) return a -def transformFilter(actor, transformation): +def transformFilter(mesh, transformation): """ - Transform a ``vtkActor`` and return a new object. + Transform a ``Mesh`` and return a new object. """ tf = vtk.vtkTransformPolyDataFilter() tf.SetTransform(transformation) prop = None - if isinstance(actor, vtk.vtkPolyData): - tf.SetInputData(actor) + if isinstance(mesh, vtk.vtkPolyData): + tf.SetInputData(mesh) else: - tf.SetInputData(actor.polydata()) + tf.SetInputData(mesh.polydata()) prop = vtk.vtkProperty() - prop.DeepCopy(actor.GetProperty()) + prop.DeepCopy(mesh.GetProperty()) tf.Update() - tfa = Actor(tf.GetOutput()) + tfa = Mesh(tf.GetOutput()) if prop: tfa.SetProperty(prop) return tfa -def meshQuality(actor, measure=6): +def meshQuality(mesh, measure=6): """ Calculate functions of quality of the elements of a triangular mesh. See class `vtkMeshQuality `_ @@ -1258,10 +1258,10 @@ def meshQuality(actor, measure=6): |meshquality| |meshquality.py|_ """ - mesh = actor.GetMapper().GetInput() + poly = mesh.GetMapper().GetInput() qf = vtk.vtkMeshQuality() - qf.SetInputData(mesh) + qf.SetInputData(poly) qf.SetTriangleQualityMeasure(measure) qf.SaveCellQualityOn() qf.Update() @@ -1269,12 +1269,12 @@ def meshQuality(actor, measure=6): pd = vtk.vtkPolyData() pd.ShallowCopy(qf.GetOutput()) - qactor = Actor(pd) - qactor.mapper().SetScalarRange(pd.GetScalarRange()) - return qactor + qmesh = Mesh(pd) + qmesh.mapper().SetScalarRange(pd.GetScalarRange()) + return qmesh -def connectedPoints(actor, radius, mode=0, regions=(), vrange=(0,1), seeds=(), angle=0): +def connectedPoints(mesh, radius, mode=0, regions=(), vrange=(0,1), seeds=(), angle=0): """ Extracts and/or segments points from a point cloud based on geometric distance measures (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. @@ -1318,7 +1318,7 @@ def connectedPoints(actor, radius, mode=0, regions=(), vrange=(0,1), seeds=(), a """ # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html cpf = vtk.vtkConnectedPointsFilter() - cpf.SetInputData(actor.polydata()) + cpf.SetInputData(mesh.polydata()) cpf.SetRadius(radius) if mode == 0: # Extract all regions pass @@ -1350,10 +1350,10 @@ def connectedPoints(actor, radius, mode=0, regions=(), vrange=(0,1), seeds=(), a cpf.Update() - return Actor(cpf.GetOutput()) + return Mesh(cpf.GetOutput()) -def splitByConnectivity(actor, maxdepth=1000): +def splitByConnectivity(mesh, maxdepth=1000): """ Split a mesh by connectivity and order the pieces by increasing area. @@ -1361,18 +1361,18 @@ def splitByConnectivity(actor, maxdepth=1000): |splitmesh| |splitmesh.py|_ """ - actor.addIDs() - pd = actor.polydata() + mesh.addIDs() + pd = mesh.polydata() cf = vtk.vtkConnectivityFilter() cf.SetInputData(pd) cf.SetExtractionModeToAllRegions() cf.ColorRegionsOn() cf.Update() cpd = cf.GetOutput() - a = Actor(cpd) + a = Mesh(cpd) alist = [] - for t in range(max(a.scalars("RegionId")) - 1): + for t in range(max(a.getPointArray("RegionId")) - 1): if t == maxdepth: break suba = a.clone().threshold("RegionId", t - 0.1, t + 0.1) @@ -1389,15 +1389,15 @@ def splitByConnectivity(actor, maxdepth=1000): return blist -def pointSampler(actor, distance=None): +def pointSampler(mesh, distance=None): """ Algorithm to generate points the specified distance apart. """ - poly = actor.polydata(True) + poly = mesh.polydata(True) pointSampler = vtk.vtkPolyDataPointSampler() if not distance: - distance = actor.diagonalSize() / 100.0 + distance = mesh.diagonalSize() / 100.0 pointSampler.SetDistance(distance) # pointSampler.GenerateVertexPointsOff() # pointSampler.GenerateEdgePointsOff() @@ -1406,14 +1406,14 @@ def pointSampler(actor, distance=None): pointSampler.SetInputData(poly) pointSampler.Update() - uactor = Actor(pointSampler.GetOutput()) + umesh = Mesh(pointSampler.GetOutput()) prop = vtk.vtkProperty() - prop.DeepCopy(actor.GetProperty()) - uactor.SetProperty(prop) - return uactor + prop.DeepCopy(mesh.GetProperty()) + umesh.SetProperty(prop) + return umesh -def geodesic(actor, start, end): +def geodesic(mesh, start, end): """ Dijkstra algorithm to compute the graph geodesic. Takes as input a polygonal mesh and performs a single source shortest path calculation. @@ -1429,13 +1429,13 @@ def geodesic(actor, start, end): dijkstra = vtk.vtkDijkstraGraphGeodesicPath() if utils.isSequence(start): - cc = actor.coordinates() + cc = mesh.points() pa = shapes.Points(cc) start = pa.closestPoint(start, returnIds=True) end = pa.closestPoint(end, returnIds=True) dijkstra.SetInputData(pa.polydata()) else: - dijkstra.SetInputData(actor.polydata()) + dijkstra.SetInputData(mesh.polydata()) dijkstra.SetStartVertex(start) dijkstra.SetEndVertex(end) @@ -1449,39 +1449,39 @@ def geodesic(actor, start, end): for i in range(length): arr[i] = weights.GetTuple(i)[0] - dactor = Actor(dijkstra.GetOutput()) + dmesh = Mesh(dijkstra.GetOutput()) prop = vtk.vtkProperty() - prop.DeepCopy(actor.GetProperty()) + prop.DeepCopy(mesh.GetProperty()) prop.SetLineWidth(3) prop.SetOpacity(1) - dactor.SetProperty(prop) - dactor.info["CumulativeWeights"] = arr - return dactor + dmesh.SetProperty(prop) + dmesh.info["CumulativeWeights"] = arr + return dmesh -def convexHull(actor_or_list, alphaConstant=0): +def convexHull(mesh_or_list, alphaConstant=0): """ Create a 2D/3D Delaunay triangulation of input points. - :param actor_or_list: can be either an ``Actor`` or a list of 3D points. + :param mesh_or_list: can be either an ``Mesh`` or a list of 3D points. :param float alphaConstant: For a non-zero alpha value, only verts, edges, faces, or tetra contained within the circumsphere (of radius alpha) will be output. Otherwise, only tetrahedra will be output. |convexHull| |convexHull.py|_ """ - if utils.isSequence(actor_or_list): - actor = shapes.Points(actor_or_list) + if utils.isSequence(mesh_or_list): + mesh = shapes.Points(mesh_or_list) else: - actor = actor_or_list - apoly = actor.clean().polydata() + mesh = mesh_or_list + apoly = mesh.clean().polydata() triangleFilter = vtk.vtkTriangleFilter() triangleFilter.SetInputData(apoly) triangleFilter.Update() poly = triangleFilter.GetOutput() - if np.count_nonzero(actor.coordinates()[:,2]): + if np.count_nonzero(mesh.points()[:,2]): delaunay = vtk.vtkDelaunay3D() # Create the convex hull of the pointcloud else: delaunay = vtk.vtkDelaunay2D() @@ -1494,10 +1494,10 @@ def convexHull(actor_or_list, alphaConstant=0): surfaceFilter = vtk.vtkDataSetSurfaceFilter() surfaceFilter.SetInputConnection(delaunay.GetOutputPort()) surfaceFilter.Update() - return Actor(surfaceFilter.GetOutput()) + return Mesh(surfaceFilter.GetOutput()) -def actor2Volume(actor, spacing=(1, 1, 1)): +def mesh2Volume(mesh, spacing=(1, 1, 1)): """ Convert a mesh it into a ``Volume`` where the foreground (exterior) voxels value is 1 and the background @@ -1507,7 +1507,7 @@ def actor2Volume(actor, spacing=(1, 1, 1)): |mesh2volume| |mesh2volume.py|_ """ # https://vtk.org/Wiki/VTK/Examples/Cxx/PolyData/PolyDataToImageData - pd = actor.polydata() + pd = mesh.polydata() whiteImage = vtk.vtkImageData() bounds = pd.GetBounds() @@ -1555,27 +1555,27 @@ def actor2Volume(actor, spacing=(1, 1, 1)): def extractSurface(volume, radius=0.5): """Generate the zero-crossing isosurface from truncated signed distance volume in input. - Output is an ``Actor`` object. + Output is an ``Mesh`` object. """ - img = _getimg(volume) + img = _getinput(volume) fe = vtk.vtkExtractSurface() fe.SetInputData(img) fe.SetRadius(radius) fe.Update() - return Actor(fe.GetOutput()).phong() + return Mesh(fe.GetOutput()).phong() -def projectSphereFilter(actor): +def projectSphereFilter(mesh): """ Project a spherical-like object onto a plane. |projectsphere| |projectsphere.py|_ """ - poly = actor.polydata() + poly = mesh.polydata() psf = vtk.vtkProjectSphereFilter() psf.SetInputData(poly) psf.Update() - return Actor(psf.GetOutput()) + return Mesh(psf.GetOutput()) def voronoi3D(nuclei, bbfactor=1, tol=None): @@ -1655,7 +1655,7 @@ def voronoi3D(nuclei, bbfactor=1, tol=None): poly = vtk.vtkPolyData() poly.SetPoints(sourcePoints) poly.SetPolys(sourcePolygons) - voro = Actor(poly).alpha(0.5) + voro = Mesh(poly).alpha(0.5) voro.info['cells'] = cells voro.info['areas'] = areas voro.info['volumes'] = volumes @@ -1675,12 +1675,12 @@ def extractCellsByType(obj, types=(7,)): for ct in types: ef.AddCellType(ct) ef.Update() - return Actor(ef.GetOutput()) + return Mesh(ef.GetOutput()) def pointCloudFrom(obj, useCellData=False): """ - Build a `Actor` object from any VTK dataset as a point cloud. + Build a `Mesh` object from any VTK dataset as a point cloud. :param bool useCellData: if True cell data is interpolated at point positions. """ @@ -1703,10 +1703,10 @@ def pointCloudFrom(obj, useCellData=False): arr = obj.GetPointData().GetArray(name) poly.GetPointData().AddArray(arr) - return Actor(poly, c=None) + return Mesh(poly, c=None) -def interpolateToVolume(actor, kernel='shepard', radius=None, +def interpolateToVolume(mesh, kernel='shepard', radius=None, bounds=None, nullValue=None, dims=(20,20,20)): """ @@ -1722,10 +1722,10 @@ def interpolateToVolume(actor, kernel='shepard', radius=None, |interpolateVolume| |interpolateVolume.py|_ """ - if isinstance(actor, vtk.vtkPolyData): - output = actor + if isinstance(mesh, vtk.vtkPolyData): + output = mesh else: - output = actor.polydata() + output = mesh.polydata() # Create a probe volume probe = vtk.vtkImageData() @@ -1774,7 +1774,7 @@ def interpolateToVolume(actor, kernel='shepard', radius=None, return Volume(interpolator.GetOutput()) -def interpolateToStructuredGrid(actor, kernel=None, radius=None, +def interpolateToStructuredGrid(mesh, kernel=None, radius=None, bounds=None, nullValue=None, dims=None): """ Generate a volumetric dataset (vtkStructuredData) by interpolating a scalar @@ -1787,10 +1787,10 @@ def interpolateToStructuredGrid(actor, kernel=None, radius=None, :param list dims: dimensions of the output vtkStructuredGrid object :param float nullValue: value to be assigned to invalid points """ - if isinstance(actor, vtk.vtkPolyData): - output = actor + if isinstance(mesh, vtk.vtkPolyData): + output = mesh else: - output = actor.polydata() + output = mesh.polydata() if dims is None: dims = (20,20,20) @@ -1896,7 +1896,8 @@ def streamLines(domain, probe, Otherwise, the integration terminates upon exiting the field domain. :param domain: the vtk object that contains the vector field - :param Actor,list probe: the Actor that probes the domain. Its coordinates will + :param str activeVectors: name of the vector array + :param Mesh,list probe: the Mesh that probes the domain. Its coordinates will be the seeds for the streamlines, can also be an array of positions. :param str integrator: Runge-Kutta integrator, either 'rk2', 'rk4' of 'rk45' :param float initialStepSize: initial step size of integration @@ -1957,7 +1958,7 @@ def streamLines(domain, probe, if utils.isSequence(probe): pts = probe else: - pts = probe.clean().coordinates() + pts = probe.clean().points() src = vtk.vtkProgrammableSource() def readPoints(): output = src.GetPolyDataOutput() @@ -2032,7 +2033,7 @@ def readPoints(): vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS, vname) streamTube.Update() - sta = Actor(streamTube.GetOutput(), c=None) + sta = Mesh(streamTube.GetOutput(), c=None) scals = grid.GetPointData().GetScalars() if scals: @@ -2044,7 +2045,7 @@ def readPoints(): sta.phong() return sta - sta = Actor(output, c=None) + sta = Mesh(output, c=None) if lw is not None and len(tubes)==0 and not ribbons: sta.lw(lw) @@ -2057,7 +2058,7 @@ def readPoints(): return sta -def densifyCloud(actor, targetDistance, closestN=6, radius=0, maxIter=None, maxN=None): +def densifyCloud(mesh, targetDistance, closestN=6, radius=0, maxIter=None, maxN=None): """Adds new points to an input point cloud. The new points are created in such a way that all points in any local neighborhood are within a target distance of one another. @@ -2084,7 +2085,7 @@ def densifyCloud(actor, targetDistance, closestN=6, radius=0, maxIter=None, maxN def readPoints(): output = src.GetPolyDataOutput() points = vtk.vtkPoints() - pts = actor.coordinates() + pts = mesh.points() for p in pts: x, y, z = p points.InsertNextPoint(x, y, z) @@ -2134,7 +2135,7 @@ def frequencyPassFilter(volume, lowcutoff=None, highcutoff=None, order=1): |idealpass| """ #https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass - img = _getimg(volume) + img = _getinput(volume) fft = vtk.vtkImageFFT() fft.SetInputData(img) fft.Update() @@ -2167,17 +2168,17 @@ def frequencyPassFilter(volume, lowcutoff=None, highcutoff=None, order=1): return Volume(butterworthReal.GetOutput()) -def implicitModeller(actor, distance=0.05, res=(110,40,20), bounds=(), maxdist=None, outer=True): +def implicitModeller(mesh, distance=0.05, res=(110,40,20), bounds=(), maxdist=None, outer=True): """Finds the surface at the specified distance from the input one""" if not len(bounds): - bounds = actor.bounds() + bounds = mesh.bounds() if not maxdist: - maxdist = actor.diagonalSize()/2 + maxdist = mesh.diagonalSize()/2 imp = vtk.vtkImplicitModeller() - imp.SetInputData(actor.polydata()) + imp.SetInputData(mesh.polydata()) imp.SetSampleDimensions(res) imp.SetMaximumDistance(maxdist) imp.SetModelBounds(bounds) @@ -2189,10 +2190,10 @@ def implicitModeller(actor, distance=0.05, res=(110,40,20), bounds=(), maxdist=N if outer: poly = extractLargestRegion(poly) - return Actor(poly, c='lb') + return Mesh(poly, c='lb') -def signedDistanceFromPointCloud(actor, maxradius=None, bounds=None, dims=(20,20,20)): +def signedDistanceFromPointCloud(mesh, maxradius=None, bounds=None, dims=(20,20,20)): """ Compute signed distances over a volume from an input point cloud. The output is a ``Volume`` object whose voxels contains the signed distance from @@ -2203,11 +2204,11 @@ def signedDistanceFromPointCloud(actor, maxradius=None, bounds=None, dims=(20,20 :param list dims: dimensions (nr. of voxels) of the output volume. """ if bounds is None: - bounds = actor.GetBounds() + bounds = mesh.GetBounds() if maxradius is None: - maxradius = actor.diagonalSize()/10. + maxradius = mesh.diagonalSize()/10. dist = vtk.vtkSignedDistance() - dist.SetInputData(actor.polydata(True)) + dist.SetInputData(mesh.polydata(True)) dist.SetRadius(maxradius) dist.SetBounds(bounds) dist.SetDimensions(dims) @@ -2215,7 +2216,7 @@ def signedDistanceFromPointCloud(actor, maxradius=None, bounds=None, dims=(20,20 return Volume(dist.GetOutput()) -def volumeFromMesh(actor, bounds=None, dims=(20,20,20), signed=True, negate=False): +def volumeFromMesh(mesh, bounds=None, dims=(20,20,20), signed=True, negate=False): """ Compute signed distances over a volume from an input mesh. The output is a ``Volume`` object whose voxels contains the signed distance from @@ -2227,7 +2228,7 @@ def volumeFromMesh(actor, bounds=None, dims=(20,20,20), signed=True, negate=Fals See example script: |volumeFromMesh.py|_ """ if bounds is None: - bounds = actor.GetBounds() + bounds = mesh.GetBounds() sx = (bounds[1]-bounds[0])/dims[0] sy = (bounds[3]-bounds[2])/dims[1] sz = (bounds[5]-bounds[4])/dims[2] @@ -2239,7 +2240,7 @@ def volumeFromMesh(actor, bounds=None, dims=(20,20,20), signed=True, negate=Fals img.AllocateScalars(vtk.VTK_FLOAT, 1) imp = vtk.vtkImplicitPolyDataDistance() - imp.SetInput(actor.polydata()) + imp.SetInput(mesh.polydata()) b4 = bounds[4] r2 = range(dims[2]) @@ -2259,7 +2260,7 @@ def volumeFromMesh(actor, bounds=None, dims=(20,20,20), signed=True, negate=Fals return Volume(img) -def computeNormalsWithPCA(actor, n=20, orientationPoint=None, negate=False): +def computeNormalsWithPCA(mesh, n=20, orientationPoint=None, negate=False): """Generate point normals using PCA (principal component analysis). Basically this estimates a local tangent plane around each sample point p by considering a small neighborhood of points around p, and fitting a plane @@ -2272,7 +2273,7 @@ def computeNormalsWithPCA(actor, n=20, orientationPoint=None, negate=False): :param bool negate: flip all normals """ - poly = actor.polydata() + poly = mesh.polydata() pcan = vtk.vtkPCANormalEstimation() pcan.SetInputData(poly) pcan.SetSampleSize(n) @@ -2290,13 +2291,13 @@ def computeNormalsWithPCA(actor, n=20, orientationPoint=None, negate=False): out = pcan.GetOutput() vnorm = out.GetPointData().GetNormals() - newact = actor.clone() + newact = mesh.clone() newact.polydata().GetPointData().SetNormals(vnorm) newact.polydata().GetPointData().Modified() return newact -def pointDensity(actor, dims=(30,30,30), bounds=None, radius=None, computeGradient=False): +def pointDensity(mesh, dims=(30,30,30), bounds=None, radius=None, computeGradient=False): """Generate a density field from a point cloud. Output is a ``Volume``. The local neighborhood is specified as a `radius` around each sample position (each voxel). The density is normalized to the upper value of the scalar range. @@ -2304,17 +2305,17 @@ def pointDensity(actor, dims=(30,30,30), bounds=None, radius=None, computeGradie See example script: |pointDensity.py|_ """ pdf = vtk.vtkPointDensityFilter() - pdf.SetInputData(actor.polydata()) + pdf.SetInputData(mesh.polydata()) pdf.SetSampleDimensions(dims) pdf.SetDensityEstimateToFixedRadius() #pdf.SetDensityFormToVolumeNormalized() pdf.SetDensityFormToNumberOfPoints () if radius is None: - radius = actor.diagonalSize()/20 + radius = mesh.diagonalSize()/20 pdf.SetRadius(radius) pdf.SetComputeGradient(computeGradient) if bounds is None: - bounds = actor.GetBounds() + bounds = mesh.GetBounds() pdf.SetModelBounds(bounds) pdf.Update() img = pdf.GetOutput() @@ -2328,7 +2329,7 @@ def erodeVolume(vol, neighbours=(2,2,2)): See example script: |erode_dilate.py|_ """ - img = _getimg(vol) + img = _getinput(vol) ver = vtk.vtkImageContinuousErode3D() ver.SetInputData(img) ver.SetKernelSize(neighbours[0], neighbours[1], neighbours[2]) @@ -2342,7 +2343,7 @@ def dilateVolume(vol, neighbours=(2,2,2)): See example script: |erode_dilate.py|_ """ - img = _getimg(vol) + img = _getinput(vol) ver = vtk.vtkImageContinuousDilate3D() ver.SetInputData(img) ver.SetKernelSize(neighbours[0], neighbours[1], neighbours[2]) @@ -2364,7 +2365,7 @@ def euclideanDistanceVolume(vol, anisotropy=False, maxDistance=None): See example script: |euclDist.py|_ """ - img = _getimg(vol) + img = _getinput(vol) euv = vtk.vtkImageEuclideanDistance() euv.SetInputData(img) euv.SetConsiderAnisotropy(anisotropy) @@ -2378,16 +2379,16 @@ def euclideanDistanceVolume(vol, anisotropy=False, maxDistance=None): def volumeToPoints(vol): """Extract all image voxels as points. - This function takes an input ``Volume`` and creates an ``Actor`` + This function takes an input ``Volume`` and creates an ``Mesh`` that contains the points and the point attributes. See example script: |vol2points.py|_ """ - img = _getimg(vol) + img = _getinput(vol) v2p = vtk.vtkImageToPoints() v2p.SetInputData(img) v2p.Update() - return Actor(v2p.GetOutput()) + return Mesh(v2p.GetOutput()) def volumeCorrelation(vol1, vol2, dim=2): @@ -2397,8 +2398,8 @@ def volumeCorrelation(vol1, vol2, dim=2): The output size will match the size of the first input. The second input is considered the correlation kernel. """ - img1 = _getimg(vol1) - img2 = _getimg(vol2) + img1 = _getinput(vol1) + img2 = _getinput(vol2) imc = vtk.vtkImageCorrelation() imc.SetInput1Data(img1) imc.SetInput2Data(img2) diff --git a/vtkplotter/animation.py b/vtkplotter/animation.py index 8e9bc49d..794b24aa 100644 --- a/vtkplotter/animation.py +++ b/vtkplotter/animation.py @@ -102,16 +102,16 @@ def _parse(self, objs, t, duration): def switchOn(self, acts=None, t=None, duration=None): - """Switch on the input list of actors.""" + """Switch on the input list of meshes.""" return self.fadeIn(acts, t, 0) def switchOff(self, acts=None, t=None, duration=None): - """Switch off the input list of actors.""" + """Switch off the input list of meshes.""" return self.fadeOut(acts, t, 0) def fadeIn(self, acts=None, t=None, duration=None): - """Gradually switch on the input list of actors by increasing opacity.""" + """Gradually switch on the input list of meshes by increasing opacity.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) for tt in rng: @@ -125,7 +125,7 @@ def fadeIn(self, acts=None, t=None, duration=None): return self def fadeOut(self, acts=None, t=None, duration=None): - """Gradually switch off the input list of actors by increasing transparency.""" + """Gradually switch off the input list of meshes by increasing transparency.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) for tt in rng: @@ -140,7 +140,7 @@ def fadeOut(self, acts=None, t=None, duration=None): def changeAlphaBetween(self, alpha1, alpha2, acts=None, t=None, duration=None): - """Gradually change transparency for the input list of actors.""" + """Gradually change transparency for the input list of meshes.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) for tt in rng: @@ -153,7 +153,7 @@ def changeAlphaBetween(self, alpha1, alpha2, acts=None, t=None, duration=None): def changeColor(self, c, acts=None, t=None, duration=None): - """Gradually change color for the input list of actors.""" + """Gradually change color for the input list of meshes.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) @@ -174,7 +174,7 @@ def changeColor(self, c, acts=None, t=None, duration=None): def changeBackColor(self, c, acts=None, t=None, duration=None): - """Gradually change backface color for the input list of actors. + """Gradually change backface color for the input list of meshes. An initial backface color should be set in advance.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) @@ -199,7 +199,7 @@ def changeBackColor(self, c, acts=None, t=None, duration=None): def changeToWireframe(self, acts=None, t=None): - """Switch representation to wireframe for the input list of actors at time `t`.""" + """Switch representation to wireframe for the input list of meshes at time `t`.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, None) self.events.append((t, self.changeToWireframe, acts, True)) @@ -209,7 +209,7 @@ def changeToWireframe(self, acts=None, t=None): return self def changeToSurface(self, acts=None, t=None): - """Switch representation to surface for the input list of actors at time `t`.""" + """Switch representation to surface for the input list of meshes at time `t`.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, None) self.events.append((t, self.changeToSurface, acts, False)) @@ -220,7 +220,7 @@ def changeToSurface(self, acts=None, t=None): def changeLineWidth(self, lw, acts=None, t=None, duration=None): - """Gradually change line width of the mesh edges for the input list of actors.""" + """Gradually change line width of the mesh edges for the input list of meshes.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) for tt in rng: @@ -236,7 +236,7 @@ def changeLineWidth(self, lw, acts=None, t=None, duration=None): def changeLineColor(self, c, acts=None, t=None, duration=None): - """Gradually change line color of the mesh edges for the input list of actors.""" + """Gradually change line color of the mesh edges for the input list of meshes.""" if self.bookingMode: acts, t, duration, rng = self._parse(acts, t, duration) col2 = getColor(c) @@ -256,7 +256,7 @@ def changeLineColor(self, c, acts=None, t=None, duration=None): def changeLighting(self, style, acts=None, t=None, duration=None): - """Gradually change the lighting style for the input list of actors. + """Gradually change the lighting style for the input list of meshes. Allowed styles are: [metallic, plastic, shiny, glossy, default]. """ diff --git a/vtkplotter/assembly.py b/vtkplotter/assembly.py new file mode 100644 index 00000000..b05c3f51 --- /dev/null +++ b/vtkplotter/assembly.py @@ -0,0 +1,91 @@ +from __future__ import division, print_function + +import numpy as np +import vtk +import vtkplotter.docs as docs +from vtkplotter.base import ActorBase + +__doc__ = ( + """ +Submodule extending the ``vtkAssembly`` object functionality. +""" + + docs._defs +) + +__all__ = ["Assembly"] + + +################################################# +class Assembly(vtk.vtkAssembly, ActorBase): + """Group many meshes as a single new mesh as a ``vtkAssembly``. + + |gyroscope1| |gyroscope1.py|_ + """ + + def __init__(self, meshs): + + vtk.vtkAssembly.__init__(self) + ActorBase.__init__(self) + + self.actors = meshs + + if len(meshs) and hasattr(meshs[0], "base"): + self.base = meshs[0].base + self.top = meshs[0].top + else: + self.base = None + self.top = None + + for a in meshs: + if a: + self.AddPart(a) + + def __add__(self, meshs): + if isinstance(meshs, list): + for a in meshs: + self.AddPart(self) + elif isinstance(meshs, vtk.vtkAssembly): + acts = meshs.getMeshes() + for a in acts: + self.AddPart(a) + else: # meshs=one mesh + self.AddPart(meshs) + return self + + + def getActors(self): + """Obsolete, use getMeshes() instead.""" + print("WARNING: getActors() is obsolete, use getMeshes() instead.") + return self.getMeshes() + + + def getMeshes(self): + """Unpack the list of ``Mesh`` objects from a ``Assembly``.""" + return self.actors + + def getMesh(self, i): + """Get `i-th` ``Mesh`` object from a ``Assembly``.""" + return self.actors[i] + + def diagonalSize(self): + """Return the maximum diagonal size of the ``Mesh`` objects in ``Assembly``.""" + szs = [a.diagonalSize() for a in self.actors] + return np.max(szs) + + def lighting(self, style='', ambient=None, diffuse=None, + specular=None, specularPower=None, specularColor=None, enabled=True): + """Set the lighting type to all ``Mesh`` in the ``Assembly`` object. + + :param str style: preset style, can be `[metallic, plastic, shiny, glossy]` + :param float ambient: ambient fraction of emission [0-1] + :param float diffuse: emission of diffused light in fraction [0-1] + :param float specular: fraction of reflected light [0-1] + :param float specularPower: precision of reflection [1-100] + :param color specularColor: color that is being reflected by the surface + :param bool enabled: enable/disable all surface light emission + """ + for a in self.actors: + a.lighting(style, ambient, diffuse, + specular, specularPower, specularColor, enabled) + return self + diff --git a/vtkplotter/backends.py b/vtkplotter/backends.py index 8e2b3607..75777403 100644 --- a/vtkplotter/backends.py +++ b/vtkplotter/backends.py @@ -4,7 +4,9 @@ import os import vtkplotter.colors as colors -from vtkplotter.actors import Actor, Volume, Assembly +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh +from vtkplotter.volume import Volume import vtkplotter.settings as settings import vtkplotter.addons as addons import vtkplotter.utils as utils @@ -38,7 +40,7 @@ def getNotebookBackend(actors2show, zoom, viewup): for ia in actors2show: if isinstance(ia, Assembly): #unpack assemblies - acass = ia.getActors() + acass = ia.getMeshes() for ac in acass: if ac.polydata().GetNumberOfPolys(): polys2show.append(ac.polydata()) @@ -49,7 +51,7 @@ def getNotebookBackend(actors2show, zoom, viewup): pointcols.append(ac.color()) pointalphas.append(ac.alpha()) - elif isinstance(ia, Actor): + elif isinstance(ia, Mesh): if ia.polydata().GetNumberOfPolys(): polys2show.append(ia.polydata()) polycols.append(ia.color()) @@ -88,7 +90,7 @@ def getNotebookBackend(actors2show, zoom, viewup): actors2show2 = [] for ia in actors2show: if isinstance(ia, vtk.vtkAssembly): #unpack assemblies - acass = ia.getActors() + acass = ia.getMeshes() #for a in acass: # a.SetScale(ia.GetScale()) actors2show2 += acass @@ -139,7 +141,7 @@ def getNotebookBackend(actors2show, zoom, viewup): kobj = None kcmap= None - if isinstance(ia, Actor) and ia.N(): + if isinstance(ia, Mesh) and ia.N(): iap = ia.GetProperty() #ia.clean().triangle().computeNormals() @@ -200,7 +202,7 @@ def getNotebookBackend(actors2show, zoom, viewup): sqsize = numpy.sqrt(numpy.dot(sizes, sizes)) if ia.NPoints() == ia.NCells(): - kobj = k3d.points(ia.coordinates().astype(numpy.float32), + kobj = k3d.points(ia.points().astype(numpy.float32), color=colors.rgb2int(iap.GetColor()), colors=kcols, opacity=iap.GetOpacity(), @@ -209,7 +211,7 @@ def getNotebookBackend(actors2show, zoom, viewup): #compression_level=9, ) else: - kobj = k3d.line(ia.coordinates().astype(numpy.float32), + kobj = k3d.line(ia.points().astype(numpy.float32), color=colors.rgb2int(iap.GetColor()), colors=kcols, opacity=iap.GetOpacity(), diff --git a/vtkplotter/base.py b/vtkplotter/base.py new file mode 100644 index 00000000..24e04f32 --- /dev/null +++ b/vtkplotter/base.py @@ -0,0 +1,723 @@ +from __future__ import division, print_function + +import numpy as np +import vtk +import vtkplotter.colors as colors +import vtkplotter.docs as docs +import vtkplotter.settings as settings +import vtkplotter.utils as utils +from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy + +__doc__ = ( + """ +Submodule extending the ``vtkActor``, ``vtkVolume`` +and ``vtkImageActor`` objects functionality. +""" + + docs._defs +) + +__all__ = ['ActorBase'] + +#################################################### +# classes +class ActorBase(object): + """Adds functionality to ``Mesh(vtkActor)``, ``Assembly``, + ``Volume`` and ``Picture`` objects. + + .. warning:: Do not use this class to instance objects, use the above ones. + """ + + def __init__(self): + + self.filename = "" + self.name = "" + self.trail = None + self.trailPoints = [] + self.trailSegmentSize = 0 + self.trailOffset = None + self.shadow = None + self.shadowX = None + self.shadowY = None + self.shadowZ = None + self.units = None + self.top = None + self.base = None + self.info = dict() + self._time = 0 + self._legend = None + self.scalarbar = None + self.renderedAt = set() + self.picked3d = None + self.cmap = None + self.flagText = None + self._mapper = None + + def mapper(self, newMapper=None): + """Return the ``vtkMapper`` data object, or update it with a new one.""" + if newMapper: + self.SetMapper(newMapper) + if self._mapper: + iptdata = self._mapper.GetInput() + if iptdata: + newMapper.SetInputData(self._mapper.GetInput()) + self._mapper = newMapper + self._mapper.Modified() + return self._mapper + + def inputdata(self): + """Return the VTK input data object.""" + if self._mapper: + return self._mapper.GetInput() + return self.GetMapper().GetInput() + + + def show(self, **options): + """ + Create on the fly an instance of class ``Plotter`` or use the last existing one to + show one single object. + + This is meant as a shortcut. If more than one object needs to be visualised + please use the syntax `show([mesh1, mesh2, volume, ...], options)`. + + :param bool newPlotter: if set to `True`, a call to ``show`` will instantiate + a new ``Plotter`` object (a new window) instead of reusing the first created. + See e.g.: |readVolumeAsIsoSurface.py|_ + :return: the current ``Plotter`` class instance. + + .. note:: E.g.: + + .. code-block:: python + + from vtkplotter import * + s = Sphere() + s.show(at=1, N=2) + c = Cube() + c.show(at=0, interactive=True) + """ + from vtkplotter.plotter import show + return show(self, **options) + + + def N(self): + """Retrieve number of points. Shortcut for `NPoints()`.""" + return self.inputdata().GetNumberOfPoints() + + def NPoints(self): + """Retrieve number of points. Same as `N()`.""" + return self.inputdata().GetNumberOfPoints() + + def NCells(self): + """Retrieve number of cells.""" + return self.inputdata().GetNumberOfCells() + + + def pickable(self, value=None): + """Set/get pickable property of mesh.""" + if value is None: + return self.GetPickable() + else: + self.SetPickable(value) + return self + + + def legend(self, txt=None): + """Set/get ``Mesh`` legend text. + + :param str txt: legend text. + + Size and positions can be modified by setting attributes + ``Plotter.legendSize``, ``Plotter.legendBC`` and ``Plotter.legendPos``. + + .. hint:: |fillholes.py|_ + """ + if txt: + self._legend = txt + else: + return self._legend + return self + + def flag(self, text=None): + """Add a flag label which becomes visible when hovering the object with mouse. + Can be later disabled by setting `flag(False)`. + """ + if text is None: + if self.filename: + text = self.filename.split('/')[-1] + elif self.name: + text = self.name + else: + text = "" + self.flagText = text + return self + + + def time(self, t=None): + """Set/get object's absolute time.""" + if t is None: + return self._time + self._time = t + return self # return itself to concatenate methods + + def pos(self, x=None, y=None, z=None): + """Set/Get object position.""" + if x is None: + return np.array(self.GetPosition()) + if z is None: # assume p_x is of the form (x,y,z) + if y is not None: # assume x and y are given so z=0 + z=0 + else: # assume p_x is of the form (x,y,z) + x, y, z = x + self.SetPosition(x, y, z) + + if self.trail: + self.updateTrail() + if self.shadow: + self._updateShadow() + return self # return itself to concatenate methods + + def addPos(self, dp_x=None, dy=None, dz=None): + """Add vector to current object position.""" + p = np.array(self.GetPosition()) + if dz is None: # assume dp_x is of the form (x,y,z) + self.SetPosition(p + dp_x) + else: + self.SetPosition(p + [dp_x, dy, dz]) + if self.trail: + self.updateTrail() + if self.shadow: + self._updateShadow() + return self + + def x(self, position=None): + """Set/Get object position along x axis.""" + p = self.GetPosition() + if position is None: + return p[0] + self.SetPosition(position, p[1], p[2]) + if self.trail: + self.updateTrail() + if self.shadow: + self._updateShadow() + return self + + def y(self, position=None): + """Set/Get object position along y axis.""" + p = self.GetPosition() + if position is None: + return p[1] + self.SetPosition(p[0], position, p[2]) + if self.trail: + self.updateTrail() + if self.shadow: + self._updateShadow() + return self + + def z(self, position=None): + """Set/Get object position along z axis.""" + p = self.GetPosition() + if position is None: + return p[2] + self.SetPosition(p[0], p[1], position) + if self.trail: + self.updateTrail() + if self.shadow: + self._updateShadow() + return self + + def rotate(self, angle, axis=(1, 0, 0), axis_point=(0, 0, 0), rad=False): + """Rotate around an arbitrary `axis` passing through `axis_point`.""" + if rad: + anglerad = angle + else: + anglerad = np.deg2rad(angle) + axis = utils.versor(axis) + a = np.cos(anglerad / 2) + b, c, d = -axis * np.sin(anglerad / 2) + aa, bb, cc, dd = a * a, b * b, c * c, d * d + bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d + R = np.array( + [ + [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], + [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], + [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], + ] + ) + rv = np.dot(R, self.GetPosition() - np.array(axis_point)) + axis_point + + if rad: + angle *= 180.0 / np.pi + # this vtk method only rotates in the origin of the object: + self.RotateWXYZ(angle, axis[0], axis[1], axis[2]) + self.SetPosition(rv) + if self.trail: + self.updateTrail() + if self.shadow: + self.addShadow(self.shadowX, self.shadowY, self.shadowZ, + self.shadow.GetProperty().GetColor(), + self.shadow.GetProperty().GetOpacity()) + return self + + def rotateX(self, angle, rad=False): + """Rotate around x-axis. If angle is in radians set ``rad=True``. + + NB: mesh.rotateX(12).rotateY(14) will rotate FIRST around Y THEN around X. + """ + if rad: + angle *= 180 / np.pi + self.RotateX(angle) + if self.trail: + self.updateTrail() + if self.shadow: + self.addShadow(self.shadowX, self.shadowY, self.shadowZ, + self.shadow.GetProperty().GetColor(), + self.shadow.GetProperty().GetOpacity()) + return self + + def rotateY(self, angle, rad=False): + """Rotate around y-axis. If angle is in radians set ``rad=True``. + + NB: mesh.rotateX(12).rotateY(14) will rotate FIRST around Y THEN around X. + """ + if rad: + angle *= 180.0 / np.pi + self.RotateY(angle) + if self.trail: + self.updateTrail() + if self.shadow: + self.addShadow(self.shadowX, self.shadowY, self.shadowZ, + self.shadow.GetProperty().GetColor(), + self.shadow.GetProperty().GetOpacity()) + return self + + def rotateZ(self, angle, rad=False): + """Rotate around z-axis. If angle is in radians set ``rad=True``. + + NB: mesh.rotateX(12).rotateZ(14) will rotate FIRST around Z THEN around X. + """ + if rad: + angle *= 180.0 / np.pi + self.RotateZ(angle) + if self.trail: + self.updateTrail() + if self.shadow: + self.addShadow(self.shadowX, self.shadowY, self.shadowZ, + self.shadow.GetProperty().GetColor(), + self.shadow.GetProperty().GetOpacity()) + return self + +# def rotateX(self, angle, rad=False): +# """Rotate around x-axis. If angle is in radians set ``rad=True``.""" +# if rad: +# angle *= 180 / np.pi +# ipos = np.array(self.GetPosition()) +# self.SetPosition(0,0,0) +# T = vtk.vtkTransform() +# T.SetMatrix(self.GetMatrix()) +# T.PostMultiply() +# T.RotateX(angle) +# T.Translate(ipos) +# self.SetUserTransform(T) +# if self.trail: +# self.updateTrail() +# if self.shadow: +# self.addShadow(self.shadowX, self.shadowY, self.shadowZ, +# self.shadow.GetProperty().GetColor(), +# self.shadow.GetProperty().GetOpacity()) +# return self +# def origin(self, o=None): +# """Set/get mesh's origin coordinates. Default is (0,0,0). +# Can be used to define an offset.""" +# if o is None: +# return np.array(self.GetOrigin()) +# self.SetOrigin(o) +# return self # return itself to concatenate methods + + + def orientation(self, newaxis=None, rotation=0, rad=False): + """ + Set/Get object orientation. + + :param rotation: If != 0 rotate object around newaxis. + :param rad: set to True if angle is in radians. + + |gyroscope2| |gyroscope2.py|_ + """ + if rad: + rotation *= 180.0 / np.pi + if self.top is None or self.base is None: + initaxis = (0,0,1) + else: + initaxis = utils.versor(self.top - self.base) + if newaxis is None: + return initaxis + newaxis = utils.versor(newaxis) + pos = np.array(self.GetPosition()) + crossvec = np.cross(initaxis, newaxis) + angle = np.arccos(np.dot(initaxis, newaxis)) + T = vtk.vtkTransform() + T.PostMultiply() + T.Translate(-pos) + if rotation: + T.RotateWXYZ(rotation, initaxis) + T.RotateWXYZ(np.rad2deg(angle), crossvec) + T.Translate(pos) + self.SetUserTransform(T) + if self.trail: + self.updateTrail() + if self.shadow: + self.addShadow(self.shadowX, self.shadowY, self.shadowZ, + self.shadow.GetProperty().GetColor(), + self.shadow.GetProperty().GetOpacity()) + return self + + + def scale(self, s=None): + """Set/get object's scaling factor. + + :param s: scaling factor(s). + :type s: float, list + + .. note:: if `s==(sx,sy,sz)` scale differently in the three coordinates.""" + if s is None: + return np.array(self.GetScale()) + self.SetScale(s) + return self + + def print(self): + """Print ``Mesh``, ``Assembly``, ``Volume`` or ``Image`` infos.""" + utils.printInfo(self) + return self + + def on(self): + """Switch on object visibility. Object is not removed.""" + self.VisibilityOn() + return self + + def off(self): + """Switch off object visibility. Object is not removed.""" + self.VisibilityOff() + return self + + def lighting(self, style='', ambient=None, diffuse=None, + specular=None, specularPower=None, specularColor=None, + enabled=True): + """ + Set the ambient, diffuse, specular and specularPower lighting constants. + + :param str,int style: preset style, can be `[metallic, plastic, shiny, glossy, ambient]` + :param float ambient: ambient fraction of emission [0-1] + :param float diffuse: emission of diffused light in fraction [0-1] + :param float specular: fraction of reflected light [0-1] + :param float specularPower: precision of reflection [1-100] + :param color specularColor: color that is being reflected by the surface + :param bool enabled: enable/disable all surface light emission + + |wikiphong| + + |specular| |specular.py|_ + """ + pr = self.GetProperty() + + if style: + if hasattr(pr, "GetColor"): # could be Volume + c = pr.GetColor() + else: + c = (1,1,0.99) + mpr = self._mapper + if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility(): + c = (1,1,0.99) + if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] + elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] + elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] + elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)] + elif style=='ambient' : pars = [1.0, 0.0, 0.0, 0, (1,1,1)] + elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] + else: + colors.printc("Error in lighting(): Available styles are", c=1) + colors.printc(" [default, metallic, plastic, shiny, glossy, ambient]", c=1) + raise RuntimeError() + pr.SetAmbient(pars[0]) + pr.SetDiffuse(pars[1]) + pr.SetSpecular(pars[2]) + pr.SetSpecularPower(pars[3]) + if hasattr(pr, "GetColor"): pr.SetSpecularColor(pars[4]) + + if ambient is not None: pr.SetAmbient(ambient) + if diffuse is not None: pr.SetDiffuse(diffuse) + if specular is not None: pr.SetSpecular(specular) + if specularPower is not None: pr.SetSpecularPower(specularPower) + if specularColor is not None: pr.SetSpecularColor(colors.getColor(specularColor)) + if not enabled: pr.LightingOff() + return self + + def box(self, scale=1): + """Return the bounding box as a new ``Mesh``. + + :param float scale: box size can be scaled by a factor + + .. hint:: |latex.py|_ + """ + b = self.GetBounds() + from vtkplotter.shapes import Box + pos = (b[0]+b[1])/2, (b[3]+b[2])/2, (b[5]+b[4])/2 + length, width, height = b[1]-b[0], b[3]-b[2], b[5]-b[4] + oa = Box(pos, length*scale, width*scale, height*scale, c='gray') + if isinstance(self.GetProperty(), vtk.vtkProperty): + pr = vtk.vtkProperty() + pr.DeepCopy(self.GetProperty()) + oa.SetProperty(pr) + oa.wireframe() + return oa + + def bounds(self): + """Get the bounds of the data object.""" + return self.GetMapper().GetInput().GetBounds() + + def printHistogram(self, bins=10, height=10, logscale=False, minbin=0, + horizontal=False, char=u"\U00002589", + c=None, bold=True, title='Histogram'): + """ + Ascii histogram printing. + Input can also be ``Volume`` or ``Mesh``. + Returns the raw data before binning (useful when passing vtk objects). + + :param int bins: number of histogram bins + :param int height: height of the histogram in character units + :param bool logscale: use logscale for frequencies + :param int minbin: ignore bins before minbin + :param bool horizontal: show histogram horizontally + :param str char: character to be used + :param str,int c: ascii color + :param bool char: use boldface + :param str title: histogram title + + :Example: + .. code-block:: python + + from vtkplotter import printHistogram + import numpy as np + d = np.random.normal(size=1000) + data = printHistogram(d, c='blue', logscale=True, title='my scalars') + data = printHistogram(d, c=1, horizontal=1) + print(np.mean(data)) # data here is same as d + + |printhisto| + """ + utils.printHistogram(self, bins, height, logscale, minbin, + horizontal, char, c, bold, title) + return self + + def printInfo(self): + """Print information about a vtk object.""" + utils.printInfo(self) + return self + + + def c(self, color=False): + """ + Shortcut for `color()`. + If None is passed as input, will use colors from current active scalars. + """ + return self.color(color) + + + def getTransform(self): + """ + Check if ``info['transform']`` exists and returns it. + Otherwise return current user transformation + (where the object is currently placed). + """ + if "transform" in self.info.keys(): + T = self.info["transform"] + return T + else: + T = self.GetMatrix() + tr = vtk.vtkTransform() + tr.SetMatrix(T) + return tr + + def setTransform(self, T): + """ + Transform object position and orientation. + """ + if isinstance(T, vtk.vtkMatrix4x4): + self.SetUserMatrix(T) + else: + try: + self.SetUserTransform(T) + except TypeError: + colors.printc('~times Error in setTransform():', + 'consider transformPolydata() instead.', c=1) + return self + + + def getArrayNames(self): + from vtk.numpy_interface import dataset_adapter + wrapped = dataset_adapter.WrapDataObject(self.GetMapper().GetInput()) + return {"PointData":wrapped.PointData.keys(), "CellData":wrapped.CellData.keys()} + + def getPointArray(self, name=0): + """Return point array content as a ``numpy.array``. + This can be identified either as a string or by an integer number.""" + + data = None + if hasattr(self, '_polydata') and self._polydata: + data = self._polydata + self.mapper().ScalarVisibilityOn() + arr = data.GetPointData().GetArray(name) + if not arr: + return None + + if isinstance(name, int): + name = data.GetPointData().GetArrayName(name) + data.GetPointData().SetActiveScalars(name) + self.mapper().SetScalarModeToUsePointData() + if settings.autoResetScalarRange: + self.mapper().SetScalarRange(arr.GetRange()) + + elif hasattr(self, '_imagedata') and self._imagedata: + data = self._imagedata + arr = data.GetPointData().GetArray(name) + if not arr: + return None + + return vtk_to_numpy(arr) + + def getCellArray(self, name=0): + """Return cell array content as a ``numpy.array``.""" + data = None + if hasattr(self, '_polydata') and self._polydata: + data = self._polydata + self.mapper().ScalarVisibilityOn() + arr = data.GetCellData().GetArray(name) + if not arr: + return None + + if isinstance(name, int): + name = data.GetCellData().GetArrayName(name) + data.GetCellData().SetActiveScalars(name) + self.mapper().SetScalarModeToUseCellData() + if settings.autoResetScalarRange: + self.mapper().SetScalarRange(arr.GetRange()) + + elif hasattr(self, '_imagedata') and self._imagedata: + data = self._imagedata + arr = data.GetCellData().GetArray(name) + if not arr: + return None + + return vtk_to_numpy(arr) + + + def addPointScalars(self, scalars, name): + """ + Add point scalars and assign it a name. + + |mesh_coloring| |mesh_coloring.py|_ + """ + data = self.inputdata() + if len(scalars) != data.GetNumberOfPoints(): + colors.printc('~times addPointScalars(): Number of scalars != nr. of points', + len(scalars), data.GetNumberOfPoints(), c=1) + raise RuntimeError() + + arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) + arr.SetName(name) + data.GetPointData().AddArray(arr) + data.GetPointData().SetActiveScalars(name) + self._mapper.SetArrayName(name) + if settings.autoResetScalarRange: + self._mapper.SetScalarRange(np.min(scalars), np.max(scalars)) + self._mapper.SetScalarModeToUsePointData() + self._mapper.ScalarVisibilityOn() + return self + + def addCellScalars(self, scalars, name): + """ + Add cell scalars and assign it a name. + """ + data = self.inputdata() + if isinstance(scalars, str): + scalars = vtk_to_numpy(data.GetPointData().GetArray(scalars)) + + if len(scalars) != data.GetNumberOfCells(): + colors.printc("addCellScalars() Error: Number of scalars != nr. of cells", + len(scalars), data.GetNumberOfCells(), c=1) + raise RuntimeError() + + arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) + arr.SetName(name) + data.GetCellData().AddArray(arr) + data.GetCellData().SetActiveScalars(name) + self._mapper.SetArrayName(name) + if settings.autoResetScalarRange: + self._mapper.SetScalarRange(np.min(scalars), np.max(scalars)) + self._mapper.SetScalarModeToUseCellData() + self._mapper.ScalarVisibilityOn() + return self + + def addPointVectors(self, vectors, name): + """ + Add a point vector field to the object and assign it a name. + """ + data = self.inputdata() + if len(vectors) != data.GetNumberOfPoints(): + colors.printc('addPointVectors Error: Number of vectors != nr. of points', + len(vectors), data.GetNumberOfPoints(), c=1) + raise RuntimeError() + arr = vtk.vtkFloatArray() + arr.SetNumberOfComponents(3) + arr.SetName(name) + for v in vectors: + arr.InsertNextTuple(v) + data.GetPointData().AddArray(arr) + data.GetPointData().SetActiveVectors(name) + return self + + def addCellVectors(self, vectors, name): + """ + Add a vector field to each object cell and assign it a name. + """ + data = self.inputdata() + if len(vectors) != data.GetNumberOfCells(): + colors.printc('addPointVectors Error: Number of vectors != nr. of cells', + len(vectors), data.GetNumberOfCells(), c=1) + raise RuntimeError() + arr = vtk.vtkFloatArray() + arr.SetNumberOfComponents(3) + arr.SetName(name) + for v in vectors: + arr.InsertNextTuple(v) + data.GetCellData().AddArray(arr) + data.GetCellData().SetActiveVectors(name) + return self + + + def mapCellsToPoints(self): + """ + Transform cell data (i.e., data specified per cell) + into point data (i.e., data specified at each vertex). + The method of transformation is based on averaging the data values + of all cells using a particular point. + """ + c2p = vtk.vtkCellDataToPointData() + c2p.SetInputData(self.inputdata()) + c2p.Update() + self._mapper.SetScalarModeToUsePointData() + return self._update(c2p.GetOutput()) + + def mapPointsToCells(self): + """ + Transform point data (i.e., data specified per point) + into cell data (i.e., data specified per cell). + The method of transformation is based on averaging the data values + of all points defining a particular cell. + + |mesh_map2cell| |mesh_map2cell.py|_ + """ + p2c = vtk.vtkPointDataToCellData() + p2c.SetInputData(self.polydata(False)) + p2c.Update() + self._mapper.SetScalarModeToUseCellData() + return self._update(p2c.GetOutput()) + diff --git a/vtkplotter/colors.py b/vtkplotter/colors.py index 07770941..dc9b9004 100644 --- a/vtkplotter/colors.py +++ b/vtkplotter/colors.py @@ -762,7 +762,7 @@ def _has_colors(stream): def printc(*strings, **keys): """ - Print to terminal in colors. (python3 only). + Print to terminal in colors (python3 only). Available colors are: black, red, green, yellow, blue, magenta, cyan, white. diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py index 7a646d60..39e60d5b 100644 --- a/vtkplotter/docs.py +++ b/vtkplotter/docs.py @@ -123,7 +123,9 @@ def tips(): _defs = """ .. |tutorial.py| replace:: tutorial.py .. _tutorial.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/tutorial.py + .. |tutorial_subdivide| image:: https://user-images.githubusercontent.com/32848391/46819341-ca1b5980-cd83-11e8-97b7-12b053d76aac.png + .. |tutorial_spline| image:: https://user-images.githubusercontent.com/32848391/35976041-15781de8-0cdf-11e8-997f-aeb725bc33cc.png :width: 250 px :target: tutorial.py_ diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py index 9bd9c8ce..57a80a0b 100644 --- a/vtkplotter/dolfin.py +++ b/vtkplotter/dolfin.py @@ -15,7 +15,7 @@ import vtkplotter.settings as settings from vtkplotter.settings import datadir, embedWindow -from vtkplotter.actors import Actor +from vtkplotter.mesh import Mesh from vtkplotter.vtkio import load, screenshot, Video, exportWindow @@ -25,19 +25,15 @@ from vtkplotter.plotter import show, clear, Plotter from vtkplotter.plotter import closeWindow, closePlotter, interactive +# Install fenics with commands (e.g. in Anaconda3): +# conda install -c conda-forge fenics +# pip install vtkplotter +# Or follow instructions `here. `_ + __doc__ = ( """ `FEniCS/Dolfin `_ support submodule. -Install with commands (e.g. in Anaconda3): - - .. code-block:: bash - - conda install -c conda-forge fenics - pip install vtkplotter - - Or follow instructions `here. `_ - Basic example: .. code-block:: python @@ -101,8 +97,6 @@ +-------------------------------------------------+-------------------------------------------------+ | Customizing axes style and appearance |The wave equation in arbitrary nr. of dimensions | +-------------------------------------------------+-------------------------------------------------+ - -|fenics_logo| """ + docs._defs ) @@ -307,7 +301,7 @@ def plot(*inputobj, **options): """ Plot the object(s) provided. - Input can be any combination of: ``Actor``, ``Volume``, ``dolfin.Mesh``, + Input can be any combination of: ``Mesh``, ``Volume``, ``dolfin.Mesh``, ``dolfin.MeshFunction``, ``dolfin.Expression`` or ``dolfin.Function``. :return: the current ``Plotter`` class instance. @@ -686,14 +680,14 @@ def plot(*inputobj, **options): actor.addScalarBar(horizontal=False, vmin=vmin, vmax=vmax) if warpZfactor: - scals = actor.scalars(0) + scals = actor.getPointArray(0) if len(scals): - pts_act = actor.getPoints(copy=False) + pts_act = actor.points(copy=False) pts_act[:, 2] = scals*warpZfactor*scaleMeshFactors[2] if warpYfactor: - scals = actor.scalars(0) + scals = actor.getPointArray(0) if len(scals): - pts_act = actor.getPoints(copy=False) + pts_act = actor.points(copy=False) pts_act[:, 1] = scals*warpYfactor*scaleMeshFactors[1] if len(isolns) > 0: @@ -768,7 +762,7 @@ def plot(*inputobj, **options): ################################################################################### -class MeshActor(Actor): +class MeshActor(Mesh): """MeshActor, a vtkActor derived object for dolfin support.""" def __init__(self, *inputobj, **options): @@ -796,7 +790,7 @@ def __init__(self, *inputobj, **options): poly = utils.buildPolyData(coords, meshc.cells(), fast=fast) - Actor.__init__(self, + Mesh.__init__(self, poly, c=c, alpha=alpha, @@ -843,7 +837,7 @@ def move(self, u=None, deltas=None): def MeshPoints(*inputobj, **options): """ - Build a point ``Actor`` for a list of points. + Build a point object of type ``Mesh`` for a list of points. :param float r: point radius. :param c: color name, number, or list of [R,G,B] colors of same length as plist. @@ -968,4 +962,3 @@ def MeshArrows(*inputobj, **options): actor.u = u actor.u_values = u_values return actor - diff --git a/vtkplotter/actors.py b/vtkplotter/mesh.py similarity index 56% rename from vtkplotter/actors.py rename to vtkplotter/mesh.py index f88c3284..2b00b070 100644 --- a/vtkplotter/actors.py +++ b/vtkplotter/mesh.py @@ -7,28 +7,31 @@ import vtkplotter.docs as docs import vtkplotter.settings as settings import vtkplotter.utils as utils + +from vtkplotter.base import ActorBase + from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy __doc__ = ( """ -Submodule extending the ``vtkActor``, ``vtkVolume`` -and ``vtkImageActor`` objects functionality. +Submodule extending the ``vtkActor`` object functionality. """ + docs._defs ) -__all__ = [ - 'Prop', # only for docs - 'Actor', - 'Assembly', - 'Picture', - 'Volume', - 'merge', -] +__all__ = ["Mesh", "merge"] + + +#################################################### +def Actor(*args, **kargs): + """``Actor`` class is obsolete: use ``Mesh`` instead, with same syntax.""" + colors.printc("WARNING: Actor() is obsolete, use Mesh() instead, with same syntax.", box='=', c=1) + #raise RuntimeError() + return Mesh(*args, **kargs) -def merge(*actors): +def merge(*meshs): """ - Build a new actor formed by the fusion of the polygonal meshes of the input objects. + Build a new mesh formed by the fusion of the polygonal meshes of the input objects. Similar to Assembly, but in this case the input objects become a single mesh. .. hint:: |thinplate_grid.py|_ |value-iteration.py|_ @@ -36,9 +39,9 @@ def merge(*actors): |thinplate_grid| |value-iteration| """ acts = [] - for a in utils.flatten(actors): + for a in utils.flatten(meshs): if isinstance(a, vtk.vtkAssembly): - acts += a.getActors() + acts += a.getMeshes() elif a: acts += [a] @@ -52,1095 +55,289 @@ def merge(*actors): polylns.AddInputData(a.polydata()) polylns.Update() pd = polylns.GetOutput() - return Actor(pd) - + return Mesh(pd) -# classes -class Prop(object): - """Adds functionality to ``Actor``, ``Assembly``, - ``Volume`` and ``Picture`` objects. - .. warning:: Do not use this class to instance objects, use the above ones. +#################################################### +class Mesh(vtk.vtkFollower, ActorBase): """ + Build an instance of object ``Mesh`` derived from ``vtkActor``. - def __init__(self): - - self.filename = "" - self.name = "" - self.trail = None - self.trailPoints = [] - self.trailSegmentSize = 0 - self.trailOffset = None - self.shadow = None - self.shadowX = None - self.shadowY = None - self.shadowZ = None - self.units = None - self.top = None - self.base = None - self.info = dict() - self._time = 0 - self._legend = None - self.scalarbar = None - self.renderedAt = set() - self.picked3d = None - self.cmap = None - self.flagText = None - self._mapper = None - - def inputdata(self): - """Return the VTK input data object.""" - if self._mapper: - return self._mapper.GetInput() - return self.GetMapper().GetInput() - - def mapper(self, newMapper=None): - """Return the ``vtkMapper`` data object, or update it with a new one.""" - if newMapper: - self.SetMapper(newMapper) - if self._mapper: - iptdata = self._mapper.GetInput() - if iptdata: - newMapper.SetInputData(self._mapper.GetInput()) - self._mapper = newMapper - self._mapper.Modified() - return self._mapper - - def N(self): - """Retrieve number of points. Shortcut for `NPoints()`.""" - return self.inputdata().GetNumberOfPoints() + Input can be ``vtkPolyData``, ``vtkActor``, or a python list of [vertices, faces]. - def NPoints(self): - """Retrieve number of points. Same as `N()`.""" - return self.inputdata().GetNumberOfPoints() + If input is any of ``vtkUnstructuredGrid``, ``vtkStructuredGrid`` or ``vtkRectilinearGrid`` + the geometry is extracted. + In this case the original VTK data structures can be accessed with: ``mesh.inputdata()``. - def NCells(self): - """Retrieve number of cells.""" - return self.inputdata().GetNumberOfCells() + Finally input can be a list of vertices and their connectivity (faces of the polygonal mesh). + For point clouds - e.i. no faces - just substitute the `faces` list with ``None``. + E.g.: `Mesh( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` - def show(self, **options): - """ - Create on the fly an instance of class ``Plotter`` or use the last existing one to - show one single object. + :param c: color in RGB format, hex, symbol or name + :param float alpha: opacity value + :param bool wire: show surface as wireframe + :param bc: backface color of internal surface + :param str texture: jpg file name or surface texture name + :param bool computeNormals: compute point and cell normals at creation - Allowed input objects are: ``Actor`` or ``Volume``. + .. hint:: A mesh can be built from vertices and their connectivity. See e.g.: - This is meant as a shortcut. If more than one object needs to be visualised - please use the syntax `show([actor1, actor2,...], options)`. + |buildmesh| |buildmesh.py|_ + """ - :param bool newPlotter: if set to `True`, a call to ``show`` will instantiate - a new ``Plotter`` object (a new window) instead of reusing the first created. - See e.g.: |readVolumeAsIsoSurface.py|_ - :return: the current ``Plotter`` class instance. + def __init__( + self, + inputobj=None, + c=None, + alpha=1, + computeNormals=False, + ): + vtk.vtkActor.__init__(self) + ActorBase.__init__(self) - .. note:: E.g.: + self._polydata = None + self._mapper = vtk.vtkPolyDataMapper() - .. code-block:: python + self._mapper.SetInterpolateScalarsBeforeMapping(settings.interpolateScalarsBeforeMapping) - from vtkplotter import * - s = Sphere() - s.show(at=1, N=2) - c = Cube() - c.show(at=0, interactive=True) - """ - from vtkplotter.plotter import show - return show(self, **options) + if settings.usePolygonOffset: + self._mapper.SetResolveCoincidentTopologyToPolygonOffset() + pof, pou = settings.polygonOffsetFactor, settings.polygonOffsetUnits + self._mapper.SetResolveCoincidentTopologyPolygonOffsetParameters(pof, pou) + inputtype = str(type(inputobj)) + # print('inputtype',inputtype) - def pickable(self, value=None): - """Set/get pickable property of actor.""" - if value is None: - return self.GetPickable() + if inputobj is None: + self._polydata = vtk.vtkPolyData() + elif isinstance(inputobj, Mesh) or isinstance(inputobj, vtk.vtkActor): + polyCopy = vtk.vtkPolyData() + polyCopy.DeepCopy(inputobj.GetMapper().GetInput()) + self._polydata = polyCopy + self._mapper.SetInputData(polyCopy) + self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) + pr = vtk.vtkProperty() + pr.DeepCopy(inputobj.GetProperty()) + self.SetProperty(pr) + elif "PolyData" in inputtype: + if inputobj.GetNumberOfCells() == 0: + carr = vtk.vtkCellArray() + for i in range(inputobj.GetNumberOfPoints()): + carr.InsertNextCell(1) + carr.InsertCellPoint(i) + inputobj.SetVerts(carr) + self._polydata = inputobj # cache vtkPolyData and mapper for speed + elif "structured" in inputtype.lower() or "RectilinearGrid" in inputtype: + if settings.visibleGridEdges: + gf = vtk.vtkExtractEdges() + gf.SetInputData(inputobj) + else: + gf = vtk.vtkGeometryFilter() + gf.SetInputData(inputobj) + gf.Update() + self._polydata = gf.GetOutput() + elif "trimesh" in inputtype: + tact = utils.trimesh2vtk(inputobj, alphaPerCell=False) + self._polydata = tact.polydata() + elif "meshio" in inputtype: + if inputobj.cells: # assume [vertices, faces] + mcells =[] + if 'triangle' in inputobj.cells.keys(): + mcells += inputobj.cells['triangle'].tolist() + if 'quad' in inputobj.cells.keys(): + mcells += inputobj.cells['quad'].tolist() + self._polydata = utils.buildPolyData(inputobj.points, mcells) + else: + self._polydata = utils.buildPolyData(inputobj.points, None) + if inputobj.point_data: + vptdata = numpy_to_vtk(inputobj.point_data, deep=True) + self._polydata.SetPointData(vptdata) + if inputobj.cell_data: + vcldata = numpy_to_vtk(inputobj.cell_data, deep=True) + self._polydata.SetPointData(vcldata) + elif utils.isSequence(inputobj): + if len(inputobj) == 2: # assume [vertices, faces] + self._polydata = utils.buildPolyData(inputobj[0], inputobj[1]) + else: + self._polydata = utils.buildPolyData(inputobj, None) + elif hasattr(inputobj, "GetOutput"): # passing vtk object + if hasattr(inputobj, "Update"): inputobj.Update() + self._polydata = inputobj.GetOutput() else: - self.SetPickable(value) - return self + colors.printc("Error: cannot build mesh from type:\n", inputtype, c=1) + raise RuntimeError() + self.SetMapper(self._mapper) - def legend(self, txt=None): - """Set/get ``Actor`` legend text. + if settings.computeNormals is not None: + computeNormals = settings.computeNormals - :param str txt: legend text. + if self._polydata: + if computeNormals: + pdnorm = vtk.vtkPolyDataNormals() + pdnorm.SetInputData(self._polydata) + pdnorm.ComputePointNormalsOn() + pdnorm.ComputeCellNormalsOn() + pdnorm.FlipNormalsOff() + pdnorm.ConsistencyOn() + pdnorm.Update() + self._polydata = pdnorm.GetOutput() - Size and positions can be modified by setting attributes - ``Plotter.legendSize``, ``Plotter.legendBC`` and ``Plotter.legendPos``. + if self._mapper: + self._mapper.SetInputData(self._polydata) - .. hint:: |fillholes.py|_ - """ - if txt: - self._legend = txt - else: - return self._legend - return self + self.point_locator = None + self.cell_locator = None + self.line_locator = None + self._bfprop = None # backface property holder + self._scals_idx = 0 # index of the active scalar changed from CLI + self._ligthingnr = 0 - def flag(self, text=None): - """Add a flag label which becomes visible when hovering the object with mouse. - Can be later disabled by setting `flag(False)`. - """ - if text is None: - if self.filename: - text = self.filename.split('/')[-1] - elif self.name: - text = self.name - else: - text = "" - self.flagText = text - return self + prp = self.GetProperty() + prp.SetInterpolationToPhong() + if settings.renderPointsAsSpheres: + if hasattr(prp, 'RenderPointsAsSpheresOn'): + prp.RenderPointsAsSpheresOn() - def time(self, t=None): - """Set/get actor's absolute time.""" - if t is None: - return self._time - self._time = t - return self # return itself to concatenate methods - - def pos(self, x=None, y=None, z=None): - """Set/Get actor position.""" - if x is None: - return np.array(self.GetPosition()) - if z is None: # assume p_x is of the form (x,y,z) - if y is not None: # assume x and y are given so z=0 - z=0 - else: # assume p_x is of the form (x,y,z) - x, y, z = x - self.SetPosition(x, y, z) + if settings.renderLinesAsTubes: + if hasattr(prp, 'RenderLinesAsTubesOn'): + prp.RenderLinesAsTubesOn() - if self.trail: - self.updateTrail() - if self.shadow: - self._updateShadow() - return self # return itself to concatenate methods - - def addPos(self, dp_x=None, dy=None, dz=None): - """Add vector to current actor position.""" - p = np.array(self.GetPosition()) - if dz is None: # assume dp_x is of the form (x,y,z) - self.SetPosition(p + dp_x) - else: - self.SetPosition(p + [dp_x, dy, dz]) - if self.trail: - self.updateTrail() - if self.shadow: - self._updateShadow() - return self + # set the color by c or by scalar + if self._polydata: - def x(self, position=None): - """Set/Get actor position along x axis.""" - p = self.GetPosition() - if position is None: - return p[0] - self.SetPosition(position, p[1], p[2]) - if self.trail: - self.updateTrail() - if self.shadow: - self._updateShadow() - return self + arrexists = False - def y(self, position=None): - """Set/Get actor position along y axis.""" - p = self.GetPosition() - if position is None: - return p[1] - self.SetPosition(p[0], position, p[2]) - if self.trail: - self.updateTrail() - if self.shadow: - self._updateShadow() - return self + if c is None: + ptdata = self._polydata.GetPointData() + cldata = self._polydata.GetCellData() + exclude = ['normals', 'tcoord'] - def z(self, position=None): - """Set/Get actor position along z axis.""" - p = self.GetPosition() - if position is None: - return p[2] - self.SetPosition(p[0], p[1], position) - if self.trail: - self.updateTrail() - if self.shadow: - self._updateShadow() - return self + if cldata.GetNumberOfArrays(): + for i in range(cldata.GetNumberOfArrays()): + iarr = cldata.GetArray(i) + if iarr: + icname = iarr.GetName() + if icname and all(s not in icname.lower() for s in exclude): + cldata.SetActiveScalars(icname) + self._mapper.ScalarVisibilityOn() + self._mapper.SetScalarModeToUseCellData() + self._mapper.SetScalarRange(iarr.GetRange()) + arrexists = True + break # stop at first good one - def rotate(self, angle, axis=(1, 0, 0), axis_point=(0, 0, 0), rad=False): - """Rotate ``Actor`` around an arbitrary `axis` passing through `axis_point`.""" - if rad: - anglerad = angle - else: - anglerad = np.deg2rad(angle) - axis = utils.versor(axis) - a = np.cos(anglerad / 2) - b, c, d = -axis * np.sin(anglerad / 2) - aa, bb, cc, dd = a * a, b * b, c * c, d * d - bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d - R = np.array( - [ - [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], - [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], - [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], - ] - ) - rv = np.dot(R, self.GetPosition() - np.array(axis_point)) + axis_point - - if rad: - angle *= 180.0 / np.pi - # this vtk method only rotates in the origin of the actor: - self.RotateWXYZ(angle, axis[0], axis[1], axis[2]) - self.SetPosition(rv) - if self.trail: - self.updateTrail() - if self.shadow: - self.addShadow(self.shadowX, self.shadowY, self.shadowZ, - self.shadow.GetProperty().GetColor(), - self.shadow.GetProperty().GetOpacity()) - return self + # point come after so it has priority + if ptdata.GetNumberOfArrays(): + for i in range(ptdata.GetNumberOfArrays()): + iarr = ptdata.GetArray(i) + if iarr: + ipname = iarr.GetName() + if ipname and all(s not in ipname.lower() for s in exclude): + ptdata.SetActiveScalars(ipname) + self._mapper.ScalarVisibilityOn() + self._mapper.SetScalarModeToUsePointData() + self._mapper.SetScalarRange(iarr.GetRange()) + arrexists = True + break - def rotateX(self, angle, rad=False): - """Rotate around x-axis. If angle is in radians set ``rad=True``. + if arrexists == False: + if c is None: + c = "gold" + c = colors.getColor(c) + prp.SetColor(c) + prp.SetAmbient(0.1) + prp.SetDiffuse(1) + prp.SetSpecular(.05) + prp.SetSpecularPower(5) + self._mapper.ScalarVisibilityOff() - NB: actor.rotateX(12).rotateY(14) will rotate FIRST around Y THEN around X. - """ - if rad: - angle *= 180 / np.pi - self.RotateX(angle) - if self.trail: - self.updateTrail() - if self.shadow: - self.addShadow(self.shadowX, self.shadowY, self.shadowZ, - self.shadow.GetProperty().GetColor(), - self.shadow.GetProperty().GetOpacity()) - return self + if alpha is not None: + prp.SetOpacity(alpha) - def rotateY(self, angle, rad=False): - """Rotate around y-axis. If angle is in radians set ``rad=True``. - NB: actor.rotateX(12).rotateY(14) will rotate FIRST around Y THEN around X. - """ - if rad: - angle *= 180.0 / np.pi - self.RotateY(angle) - if self.trail: - self.updateTrail() - if self.shadow: - self.addShadow(self.shadowX, self.shadowY, self.shadowZ, - self.shadow.GetProperty().GetColor(), - self.shadow.GetProperty().GetOpacity()) - return self + ############################################### + def __add__(self, meshs): + from vtkplotter.assembly import Assembly + if isinstance(meshs, list): + alist = [self] + for l in meshs: + if isinstance(l, vtk.vtkAssembly): + alist += l.getMeshes() + else: + alist += l + return Assembly(alist) + elif isinstance(meshs, vtk.vtkAssembly): + meshs.AddPart(self) + return meshs + return Assembly([self, meshs]) - def rotateZ(self, angle, rad=False): - """Rotate around z-axis. If angle is in radians set ``rad=True``. + def __str__(self): + utils.printInfo(self) + return "" - NB: actor.rotateX(12).rotateZ(14) will rotate FIRST around Z THEN around X. - """ - if rad: - angle *= 180.0 / np.pi - self.RotateZ(angle) - if self.trail: - self.updateTrail() - if self.shadow: - self.addShadow(self.shadowX, self.shadowY, self.shadowZ, - self.shadow.GetProperty().GetColor(), - self.shadow.GetProperty().GetOpacity()) + def _update(self, polydata): + """Overwrite the polygonal mesh with a new vtkPolyData.""" + self._polydata = polydata + self._mapper.SetInputData(polydata) + self._mapper.Modified() return self -# def rotateX(self, angle, rad=False): -# """Rotate around x-axis. If angle is in radians set ``rad=True``.""" -# if rad: -# angle *= 180 / np.pi -# ipos = np.array(self.GetPosition()) -# self.SetPosition(0,0,0) -# T = vtk.vtkTransform() -# T.SetMatrix(self.GetMatrix()) -# T.PostMultiply() -# T.RotateX(angle) -# T.Translate(ipos) -# self.SetUserTransform(T) -# if self.trail: -# self.updateTrail() -# if self.shadow: -# self.addShadow(self.shadowX, self.shadowY, self.shadowZ, -# self.shadow.GetProperty().GetColor(), -# self.shadow.GetProperty().GetOpacity()) -# return self -# def origin(self, o=None): -# """Set/get actor's origin coordinates. Default is (0,0,0). -# Can be used to define an offset.""" -# if o is None: -# return np.array(self.GetOrigin()) -# self.SetOrigin(o) -# return self # return itself to concatenate methods - - - def orientation(self, newaxis=None, rotation=0, rad=False): - """ - Set/Get actor orientation. - - :param rotation: If != 0 rotate actor around newaxis. - :param rad: set to True if angle is in radians. - - |gyroscope2| |gyroscope2.py|_ - """ - if rad: - rotation *= 180.0 / np.pi - if self.top is None or self.base is None: - initaxis = (0,0,1) - else: - initaxis = utils.versor(self.top - self.base) - if newaxis is None: - return initaxis - newaxis = utils.versor(newaxis) - pos = np.array(self.GetPosition()) - crossvec = np.cross(initaxis, newaxis) - angle = np.arccos(np.dot(initaxis, newaxis)) - T = vtk.vtkTransform() - T.PostMultiply() - T.Translate(-pos) - if rotation: - T.RotateWXYZ(rotation, initaxis) - T.RotateWXYZ(np.rad2deg(angle), crossvec) - T.Translate(pos) - self.SetUserTransform(T) - if self.trail: - self.updateTrail() - if self.shadow: - self.addShadow(self.shadowX, self.shadowY, self.shadowZ, - self.shadow.GetProperty().GetColor(), - self.shadow.GetProperty().GetOpacity()) - return self + def getPoints(self, transformed=True, copy=False): + """Obsolete, use points() instead.""" + colors.printc("WARNING: getPoints() is obsolete, use points() instead.", box='=', c=1) + return self.points(transformed=transformed, copy=copy) - - def scale(self, s=None): - """Set/get actor's scaling factor. - - :param s: scaling factor(s). - :type s: float, list - - .. note:: if `s==(sx,sy,sz)` scale differently in the three coordinates.""" - if s is None: - return np.array(self.GetScale()) - self.SetScale(s) - return self - - def addShadow(self, x=None, y=None, z=None, c=(0.5, 0.5, 0.5), alpha=1): - """ - Generate a shadow out of an ``Actor`` on one of the three Cartesian planes. - The output is a new ``Actor`` representing the shadow. - This new actor is accessible through `actor.shadow`. - By default the actor is placed on the bottom/back wall of the bounding box. - - :param float x,y,z: identify the plane to cast the shadow to ['x', 'y' or 'z']. - The shadow will lay on the orthogonal plane to the specified axis at the - specified value of either x, y or z. - - |shadow| |shadow.py|_ - - |airplanes| |airplanes.py|_ - """ - if x is not None: - self.shadowX = x - shad = self.clone().projectOnPlane('x').x(x) - elif y is not None: - self.shadowY = y - shad = self.clone().projectOnPlane('y').y(y) - elif z is not None: - self.shadowZ = z - shad = self.clone().projectOnPlane('z').z(z) - else: - print('Error in addShadow(): must set x, y or z to a float!') - return self - shad.c(c).alpha(alpha).wireframe(False) - shad.flat().backFaceCulling() - shad.GetProperty().LightingOff() - self.shadow = shad - return self - - def _updateShadow(self): - p = self.GetPosition() - if self.shadowX is not None: - self.shadow.SetPosition(self.shadowX, p[1], p[2]) - elif self.shadowY is not None: - self.shadow.SetPosition(p[0], self.shadowY, p[2]) - elif self.shadowZ is not None: - self.shadow.SetPosition(p[0], p[1], self.shadowZ) - return self - - def addTrail(self, offset=None, maxlength=None, n=50, c=None, alpha=None, lw=2): - """Add a trailing line to actor. - This new actor is accessible through `actor.trail`. - - :param offset: set an offset vector from the object center. - :param maxlength: length of trailing line in absolute units - :param n: number of segments to control precision - :param lw: line width of the trail - - .. hint:: See examples: |trail.py|_ |airplanes.py|_ - - |trail| - """ - if maxlength is None: - maxlength = self.diagonalSize() * 20 - if maxlength == 0: - maxlength = 1 - - if self.trail is None: - pos = self.GetPosition() - self.trailPoints = [None] * n - self.trailSegmentSize = maxlength / n - self.trailOffset = offset - - ppoints = vtk.vtkPoints() # Generate the polyline - poly = vtk.vtkPolyData() - ppoints.SetData(numpy_to_vtk([pos] * n)) - poly.SetPoints(ppoints) - lines = vtk.vtkCellArray() - lines.InsertNextCell(n) - for i in range(n): - lines.InsertCellPoint(i) - poly.SetPoints(ppoints) - poly.SetLines(lines) - mapper = vtk.vtkPolyDataMapper() - - if c is None: - if hasattr(self, "GetProperty"): - col = self.GetProperty().GetColor() - else: - col = (0.1, 0.1, 0.1) - else: - col = colors.getColor(c) - - if alpha is None: - alpha = 1 - if hasattr(self, "GetProperty"): - alpha = self.GetProperty().GetOpacity() - - mapper.SetInputData(poly) - tline = Actor(poly, c=col, alpha=alpha) - tline.SetMapper(mapper) - tline.GetProperty().SetLineWidth(lw) - self.trail = tline # holds the vtkActor - return self - - def updateTrail(self): - currentpos = np.array(self.GetPosition()) - if self.trailOffset: - currentpos += self.trailOffset - lastpos = self.trailPoints[-1] - if lastpos is None: # reset list - self.trailPoints = [currentpos] * len(self.trailPoints) - return - if np.linalg.norm(currentpos - lastpos) < self.trailSegmentSize: - return - - self.trailPoints.append(currentpos) # cycle - self.trailPoints.pop(0) - - tpoly = self.trail.polydata() - tpoly.GetPoints().SetData(numpy_to_vtk(self.trailPoints)) - return self - - def print(self): - """Print ``Actor``, ``Assembly``, ``Volume`` or ``Image`` infos.""" - utils.printInfo(self) - return self - - def on(self): - """Switch on actor visibility. Object is not removed.""" - self.VisibilityOn() - return self - - def off(self): - """Switch off actor visibility. Object is not removed.""" - self.VisibilityOff() - return self - - def lighting(self, style='', ambient=None, diffuse=None, - specular=None, specularPower=None, specularColor=None, - enabled=True): - """ - Set the ambient, diffuse, specular and specularPower lighting constants. - - :param str,int style: preset style, can be `[metallic, plastic, shiny, glossy, ambient]` - :param float ambient: ambient fraction of emission [0-1] - :param float diffuse: emission of diffused light in fraction [0-1] - :param float specular: fraction of reflected light [0-1] - :param float specularPower: precision of reflection [1-100] - :param color specularColor: color that is being reflected by the surface - :param bool enabled: enable/disable all surface light emission - - |wikiphong| - - |specular| |specular.py|_ - """ - pr = self.GetProperty() - - if style: - if hasattr(pr, "GetColor"): # could be Volume - c = pr.GetColor() - else: - c = (1,1,0.99) - mpr = self._mapper - if hasattr(mpr, 'GetScalarVisibility') and mpr.GetScalarVisibility(): - c = (1,1,0.99) - if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] - elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] - elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] - elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, (1,1,0.99)] - elif style=='ambient' : pars = [1.0, 0.0, 0.0, 0, (1,1,1)] - elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] - else: - colors.printc("Error in lighting(): Available styles are", c=1) - colors.printc(" [default, metallic, plastic, shiny, glossy, ambient]", c=1) - raise RuntimeError() - pr.SetAmbient(pars[0]) - pr.SetDiffuse(pars[1]) - pr.SetSpecular(pars[2]) - pr.SetSpecularPower(pars[3]) - if hasattr(pr, "GetColor"): pr.SetSpecularColor(pars[4]) - - if ambient is not None: pr.SetAmbient(ambient) - if diffuse is not None: pr.SetDiffuse(diffuse) - if specular is not None: pr.SetSpecular(specular) - if specularPower is not None: pr.SetSpecularPower(specularPower) - if specularColor is not None: pr.SetSpecularColor(colors.getColor(specularColor)) - if not enabled: pr.LightingOff() - return self - - def box(self, scale=1): - """Return the bounding box as a new ``Actor``. - - :param float scale: box size can be scaled by a factor - - .. hint:: |latex.py|_ - """ - b = self.GetBounds() - from vtkplotter.shapes import Box - pos = (b[0]+b[1])/2, (b[3]+b[2])/2, (b[5]+b[4])/2 - length, width, height = b[1]-b[0], b[3]-b[2], b[5]-b[4] - oa = Box(pos, length*scale, width*scale, height*scale, c='gray') - if isinstance(self.GetProperty(), vtk.vtkProperty): - pr = vtk.vtkProperty() - pr.DeepCopy(self.GetProperty()) - oa.SetProperty(pr) - oa.wireframe() - return oa - - def bounds(self): - """Get the bounds of the data object.""" - return self.GetMapper().GetInput().GetBounds() - - def printHistogram(self, bins=10, height=10, logscale=False, minbin=0, - horizontal=False, char=u"\U00002589", - c=None, bold=True, title='Histogram'): - """ - Ascii histogram printing. - Input can also be ``Volume`` or ``Actor``. - Returns the raw data before binning (useful when passing vtk objects). - - :param int bins: number of histogram bins - :param int height: height of the histogram in character units - :param bool logscale: use logscale for frequencies - :param int minbin: ignore bins before minbin - :param bool horizontal: show histogram horizontally - :param str char: character to be used - :param str,int c: ascii color - :param bool char: use boldface - :param str title: histogram title - - :Example: - .. code-block:: python - - from vtkplotter import printHistogram - import numpy as np - d = np.random.normal(size=1000) - data = printHistogram(d, c='blue', logscale=True, title='my scalars') - data = printHistogram(d, c=1, horizontal=1) - print(np.mean(data)) # data here is same as d - - |printhisto| - """ - utils.printHistogram(self, bins, height, logscale, minbin, - horizontal, char, c, bold, title) - return self - - def printInfo(self): - """Print information about a vtk object.""" - utils.printInfo(self) - return self - - def c(self, color=False): - """ - Shortcut for `color()`. - If None is passed as input, will use colors from current active scalars. - """ - return self.color(color) - - - def getTransform(self): - """ - Check if ``info['transform']`` exists and returns it. - Otherwise return current user transformation - (where the object is currently placed). - """ - if "transform" in self.info.keys(): - T = self.info["transform"] - return T - else: - T = self.GetMatrix() - tr = vtk.vtkTransform() - tr.SetMatrix(T) - return tr - - def setTransform(self, T): - """ - Transform object position and orientation wrt to its polygonal mesh, - which remains unmodified. - """ - if isinstance(T, vtk.vtkMatrix4x4): - self.SetUserMatrix(T) - else: - try: - self.SetUserTransform(T) - except TypeError: - colors.printc('~times Error in setTransform():', - 'consider transformPolydata() instead.', c=1) - return self - - - def getArrayNames(self): - from vtk.numpy_interface import dataset_adapter - wrapped = dataset_adapter.WrapDataObject(self.GetMapper().GetInput()) - return {"PointData":wrapped.PointData.keys(), "CellData":wrapped.CellData.keys()} - - def getPointArray(self, name=0): - """Return point array content as a ``numpy.array``. - This can be identified either as a string or by an integer number.""" - data = None - if hasattr(self, '_polydata') and self._polydata: - data = self._polydata - elif hasattr(self, '_image') and self._image: - data = self._image - return vtk_to_numpy(data.GetPointData().GetArray(name)) - - def getCellArray(self, name=0): - """Return cell array content as a ``numpy.array``.""" - data = None - if hasattr(self, '_polydata') and self._polydata: - data = self._polydata - elif hasattr(self, '_image') and self._image: - data = self._image - return vtk_to_numpy(data.GetCellData().GetArray(name)) - - def addPointScalars(self, scalars, name): - """ - Add point scalars and assigning it a name. - - |mesh_coloring| |mesh_coloring.py|_ - """ - data = self.inputdata() - if len(scalars) != data.GetNumberOfPoints(): - colors.printc('~times addPointScalars(): Number of scalars != nr. of points', - len(scalars), data.GetNumberOfPoints(), c=1) - raise RuntimeError() - - arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) - arr.SetName(name) - data.GetPointData().AddArray(arr) - data.GetPointData().SetActiveScalars(name) - self._mapper.SetArrayName(name) - if settings.autoResetScalarRange: - self._mapper.SetScalarRange(np.min(scalars), np.max(scalars)) - self._mapper.SetScalarModeToUsePointData() - self._mapper.ScalarVisibilityOn() - return self - - def addCellScalars(self, scalars, name): - """ - Add cell scalars and assigning it a name. - """ - data = self.inputdata() - if isinstance(scalars, str): - scalars = vtk_to_numpy(data.GetPointData().GetArray(scalars)) - - if len(scalars) != data.GetNumberOfCells(): - colors.printc("~times addCellScalars() Number of scalars != nr. of cells", - len(scalars), data.GetNumberOfCells(), c=1) - raise RuntimeError() - - arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) - arr.SetName(name) - data.GetCellData().AddArray(arr) - data.GetCellData().SetActiveScalars(name) - self._mapper.SetArrayName(name) - if settings.autoResetScalarRange: - self._mapper.SetScalarRange(np.min(scalars), np.max(scalars)) - self._mapper.SetScalarModeToUseCellData() - self._mapper.ScalarVisibilityOn() - return self - - def mapCellsToPoints(self): - """ - Transform cell data (i.e., data specified per cell) - into point data (i.e., data specified at each vertex). - The method of transformation is based on averaging the data values - of all cells using a particular point. - """ - c2p = vtk.vtkCellDataToPointData() - c2p.SetInputData(self.inputdata()) - c2p.Update() - self._mapper.SetScalarModeToUsePointData() - if isinstance(self, Volume): - return self.updateVolume(c2p.GetOutput()) - else: - return self.updateMesh(c2p.GetOutput()) - - def mapPointsToCells(self): - """ - Transform point data (i.e., data specified per point) - into cell data (i.e., data specified per cell). - The method of transformation is based on averaging the data values - of all points defining a particular cell. - - |mesh_map2cell| |mesh_map2cell.py|_ - """ - p2c = vtk.vtkPointDataToCellData() - p2c.SetInputData(self.polydata(False)) - p2c.Update() - self._mapper.SetScalarModeToUseCellData() - if isinstance(self, Volume): - return self.updateVolume(p2c.GetOutput()) - else: - return self.updateMesh(p2c.GetOutput()) - - -#################################################### -# Actor inherits from vtkActor and Prop -class Actor(vtk.vtkFollower, Prop): - """ - Build an instance of object ``Actor`` derived from ``vtkActor``. - - Input can be ``vtkPolyData``, ``vtkActor``, or a python list of [vertices, faces]. - - If input is any of ``vtkUnstructuredGrid``, ``vtkStructuredGrid`` or ``vtkRectilinearGrid`` - the goemetry is extracted. - In this case the original VTK data structures can be accessed with: ``actor.inputdata()``. - - Finally input can be a list of vertices and their connectivity (faces of the polygonal mesh). - For point clouds - e.i. no faces - just substitute the `faces` list with ``None``. - - E.g.: `Actor( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` - - :param c: color in RGB format, hex, symbol or name - :param float alpha: opacity value - :param bool wire: show surface as wireframe - :param bc: backface color of internal surface - :param str texture: jpg file name or surface texture name - :param bool computeNormals: compute point and cell normals at creation - - .. hint:: A mesh can be built from vertices and their connectivity. See e.g.: - - |buildmesh| |buildmesh.py|_ - """ - - def __init__( - self, - inputobj=None, - c=None, - alpha=1, - computeNormals=False, - ): - vtk.vtkActor.__init__(self) - Prop.__init__(self) - - self._polydata = None - self._mapper = vtk.vtkPolyDataMapper() - - self._mapper.SetInterpolateScalarsBeforeMapping(settings.interpolateScalarsBeforeMapping) - - if settings.usePolygonOffset: - self._mapper.SetResolveCoincidentTopologyToPolygonOffset() - pof, pou = settings.polygonOffsetFactor, settings.polygonOffsetUnits - self._mapper.SetResolveCoincidentTopologyPolygonOffsetParameters(pof, pou) - - inputtype = str(type(inputobj)) - # print('inputtype',inputtype) - - if inputobj is None: - self._polydata = vtk.vtkPolyData() - elif "Actor" in inputtype: - polyCopy = vtk.vtkPolyData() - polyCopy.DeepCopy(inputobj.GetMapper().GetInput()) - self._polydata = polyCopy - self._mapper.SetInputData(polyCopy) - self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) - pr = vtk.vtkProperty() - pr.DeepCopy(inputobj.GetProperty()) - self.SetProperty(pr) - elif "PolyData" in inputtype: - if inputobj.GetNumberOfCells() == 0: - carr = vtk.vtkCellArray() - for i in range(inputobj.GetNumberOfPoints()): - carr.InsertNextCell(1) - carr.InsertCellPoint(i) - inputobj.SetVerts(carr) - self._polydata = inputobj # cache vtkPolyData and mapper for speed - elif "structured" in inputtype.lower() or "RectilinearGrid" in inputtype: - if settings.visibleGridEdges: - gf = vtk.vtkExtractEdges() - gf.SetInputData(inputobj) - else: - gf = vtk.vtkGeometryFilter() - gf.SetInputData(inputobj) - gf.Update() - self._polydata = gf.GetOutput() - elif "trimesh" in inputtype: - tact = utils.trimesh2vtk(inputobj, alphaPerCell=False) - self._polydata = tact.polydata() - elif "meshio" in inputtype: - if inputobj.cells: # assume [vertices, faces] - mcells =[] - if 'triangle' in inputobj.cells.keys(): - mcells += inputobj.cells['triangle'].tolist() - if 'quad' in inputobj.cells.keys(): - mcells += inputobj.cells['quad'].tolist() - self._polydata = utils.buildPolyData(inputobj.points, mcells) - else: - self._polydata = utils.buildPolyData(inputobj.points, None) - if inputobj.point_data: - vptdata = numpy_to_vtk(inputobj.point_data, deep=True) - self._polydata.SetPointData(vptdata) - if inputobj.cell_data: - vcldata = numpy_to_vtk(inputobj.cell_data, deep=True) - self._polydata.SetPointData(vcldata) - elif utils.isSequence(inputobj): - if len(inputobj) == 2: # assume [vertices, faces] - self._polydata = utils.buildPolyData(inputobj[0], inputobj[1]) - else: - self._polydata = utils.buildPolyData(inputobj, None) - elif hasattr(inputobj, "GetOutput"): # passing vtk object - if hasattr(inputobj, "Update"): inputobj.Update() - self._polydata = inputobj.GetOutput() - else: - colors.printc("Error: cannot build Actor from type:\n", inputtype, c=1) - raise RuntimeError() - - self.SetMapper(self._mapper) - - if settings.computeNormals is not None: - computeNormals = settings.computeNormals - - if self._polydata: - if computeNormals: - pdnorm = vtk.vtkPolyDataNormals() - pdnorm.SetInputData(self._polydata) - pdnorm.ComputePointNormalsOn() - pdnorm.ComputeCellNormalsOn() - pdnorm.FlipNormalsOff() - pdnorm.ConsistencyOn() - pdnorm.Update() - self._polydata = pdnorm.GetOutput() - - if self._mapper: - self._mapper.SetInputData(self._polydata) - - self.point_locator = None - self.cell_locator = None - self.line_locator = None - self._bfprop = None # backface property holder - self._scals_idx = 0 # index of the active scalar changed from CLI - self._ligthingnr = 0 - - prp = self.GetProperty() - prp.SetInterpolationToPhong() - - if settings.renderPointsAsSpheres: - if hasattr(prp, 'RenderPointsAsSpheresOn'): - prp.RenderPointsAsSpheresOn() - - if settings.renderLinesAsTubes: - if hasattr(prp, 'RenderLinesAsTubesOn'): - prp.RenderLinesAsTubesOn() - - # set the color by c or by scalar - if self._polydata: - - arrexists = False - - if c is None: - ptdata = self._polydata.GetPointData() - cldata = self._polydata.GetCellData() - exclude = ['normals', 'tcoord'] - - if cldata.GetNumberOfArrays(): - for i in range(cldata.GetNumberOfArrays()): - iarr = cldata.GetArray(i) - if iarr: - icname = iarr.GetName() - if icname and all(s not in icname.lower() for s in exclude): - cldata.SetActiveScalars(icname) - self._mapper.ScalarVisibilityOn() - self._mapper.SetScalarModeToUseCellData() - self._mapper.SetScalarRange(iarr.GetRange()) - arrexists = True - break # stop at first good one - - # point come after so it has priority - if ptdata.GetNumberOfArrays(): - for i in range(ptdata.GetNumberOfArrays()): - iarr = ptdata.GetArray(i) - if iarr: - ipname = iarr.GetName() - if ipname and all(s not in ipname.lower() for s in exclude): - ptdata.SetActiveScalars(ipname) - self._mapper.ScalarVisibilityOn() - self._mapper.SetScalarModeToUsePointData() - self._mapper.SetScalarRange(iarr.GetRange()) - arrexists = True - break - - if arrexists == False: - if c is None: - c = "gold" - c = colors.getColor(c) - prp.SetColor(c) - prp.SetAmbient(0.1) - prp.SetDiffuse(1) - prp.SetSpecular(.05) - prp.SetSpecularPower(5) - self._mapper.ScalarVisibilityOff() - - if alpha is not None: - prp.SetOpacity(alpha) - - - ############################################### - def __add__(self, actors): - if isinstance(actors, list): - alist = [self] - for l in actors: - if isinstance(l, vtk.vtkAssembly): - alist += l.getActors() - else: - alist += l - return Assembly(alist) - elif isinstance(actors, vtk.vtkAssembly): - actors.AddPart(self) - return actors - return Assembly([self, actors]) - - def __str__(self): - utils.printInfo(self) - return "" - - def updateMesh(self, polydata): - """ - Overwrite the polygonal mesh of the actor with a new one. - """ - self._polydata = polydata - self._mapper.SetInputData(polydata) - self._mapper.Modified() - return self - - - def getPoint(self, i): - """ - Retrieve specific `i-th` point coordinates in mesh. - Actor transformation is reset to its mesh position/orientation. - - :param int i: index of vertex point. - - .. warning:: if used in a loop this can slow down the execution by a lot. - - .. seealso:: ``actor.getPoints()`` - """ - poly = self.polydata(True) - p = [0, 0, 0] - poly.GetPoints().GetPoint(i, p) - return np.array(p) - - def setPoint(self, i, p): - """ - Set specific `i-th` point coordinates in mesh. - Actor transformation is reset to its original mesh position/orientation. - - :param int i: index of vertex point. - :param list p: new coordinates of mesh point. - - .. warning:: if used in a loop this can slow down the execution by a lot. - - .. seealso:: ``actor.Points()`` - """ - poly = self.polydata(False) - poly.GetPoints().SetPoint(i, p) - poly.GetPoints().Modified() - poly.GetPoints().GetData().Modified() - # reset actor to identity matrix position/rotation: - self.PokeMatrix(vtk.vtkMatrix4x4()) - return self - - def getPoints(self, transformed=True, copy=False): - """ - Return the list of vertex coordinates of the input mesh. - Same as `actor.coordinates()`. - - :param bool transformed: if `False` ignore any previous transformation - applied to the mesh. - :param bool copy: if `False` return the reference to the points - so that they can be modified in place. - """ - poly = self.polydata(transformed) - if copy: - return np.array(vtk_to_numpy(poly.GetPoints().GetData())) - else: - return vtk_to_numpy(poly.GetPoints().GetData()) - - def setPoints(self, pts): - """ - Set specific points coordinates in mesh. Input is a python list. - Actor transformation is reset to its mesh position/orientation. - - :param list pts: new coordinates of mesh vertices. - """ - vpts = self._polydata.GetPoints() - vpts.SetData(numpy_to_vtk(np.ascontiguousarray(pts), deep=True)) - self._polydata.GetPoints().Modified() - # reset actor to identity matrix position/rotation: - self.PokeMatrix(vtk.vtkMatrix4x4()) - return self + def setPoints(self, pts): + """Obsolete, use points(pts) instead.""" + colors.printc("WARNING: setPoints(pts) is obsolete, use points(pts) instead.", box='=', c=1) + return self.points(pts) def coordinates(self, transformed=True, copy=False): + """Obsolete, use points() instead.""" + colors.printc("WARNING: coordinates() is obsolete, use points() instead.", box='=', c=1) + return self.points(transformed=transformed, copy=copy) + + def points(self, pts=None, transformed=True, copy=False): """ - Return the list of vertex coordinates of the input mesh. - Same as `actor.getPoints()`. + Set/Get the vertex coordinates of the mesh. + Argument can be an index, a set of indices + or a complete new set of points to update the mesh. :param bool transformed: if `False` ignore any previous transformation applied to the mesh. :param bool copy: if `False` return the reference to the points so that they can be modified in place, otherwise a copy is built. """ - return self.getPoints(transformed, copy) + if pts is None: # getter + poly = self.polydata(transformed) + if copy: + return np.array(vtk_to_numpy(poly.GetPoints().GetData())) + else: + return vtk_to_numpy(poly.GetPoints().GetData()) + + elif (utils.isSequence(pts) and not utils.isSequence(pts[0])) or isinstance(pts, (int, np.integer)): + #passing a list of indices or a single index + return vtk_to_numpy(self.polydata(transformed).GetPoints().GetData())[pts] + + else: # setter + vpts = self._polydata.GetPoints() + vpts.SetData(numpy_to_vtk(np.ascontiguousarray(pts), deep=True)) + self._polydata.GetPoints().Modified() + # reset mesh to identity matrix position/rotation: + self.PokeMatrix(vtk.vtkMatrix4x4()) + return self def faces(self): - """Get cell connettivity ids as a python ``list``. + """Get cell polygonal connectivity ids as a python ``list``. The output format is: [[id0 ... idn], [id0 ... idm], etc]. - - Same as `getConnectivity()`. - """ - return self.getConnectivity() - - def getPolygons(self): - """Get cell connettivity ids as a 1D array. The vtk format is: - [nids1, id0 ... idn, niids2, id0 ... idm, etc]. - """ - return vtk_to_numpy(self.polydata().GetPolys().GetData()) - - def getConnectivity(self): - """Get cell connettivity ids as a python ``list``. The format is: - [[id0 ... idn], [id0 ... idm], etc]. - - Same as `faces()`. """ - arr1d = self.getPolygons() + #Get cell connettivity ids as a 1D array. The vtk format is: + # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. + arr1d = vtk_to_numpy(self.polydata(False).GetPolys().GetData()) if len(arr1d) == 0: - arr1d = vtk_to_numpy(self.polydata().GetStrips().GetData()) + arr1d = vtk_to_numpy(self.polydata(False).GetStrips().GetData()) #conn = arr1d.reshape(ncells, int(len(arr1d)/len(arr1d))) #return conn[:, 1:] @@ -1150,15 +347,17 @@ def getConnectivity(self): conn = [] n = len(arr1d) for idummy in range(n): - cell = [] - for k in range(arr1d[i]): - cell.append(arr1d[i+k+1]) +# cell = [] +# for k in range(arr1d[i]): +# cell.append(arr1d[i+k+1]) + cell = [arr1d[i+k+1] for k in range(arr1d[i])] conn.append(cell) i += arr1d[i]+1 if i >= n: break return conn # cannot always make a numpy array of it! + def addScalarBar(self, pos=(0.8,0.05), title="", @@ -1171,7 +370,7 @@ def addScalarBar(self, vmin=None, vmax=None, ): """ - Add a 2D scalar bar to actor. + Add a 2D scalar bar to mesh. |mesh_bands| |mesh_bands.py|_ """ @@ -1208,7 +407,7 @@ def addScalarBar3D( cmap=None, ): """ - Draw a 3D scalar bar to actor. + Draw a 3D scalar bar to mesh. |mesh_coloring| |mesh_coloring.py|_ """ @@ -1235,7 +434,7 @@ def texture(self, tname, repeat=True, edgeClamp=False, ): - """Assign a texture to actor from image file or predefined texture `tname`. + """Assign a texture to mesh from image file or predefined texture `tname`. If tname is ``None`` texture is disabled. :param bool interpolate: turn on/off linear interpolation of the texture map when rendering. @@ -1316,9 +515,13 @@ def texture(self, tname, return self - def deletePoints(self, indices): + def deletePoints(self, indices, renamePoints=False): """Delete a list of vertices identified by their index. + :param bool renamePoints: if True, point indices and faces are renamed. + If False, vertices are not really deleted and faces indices will + stay unchanged (default, faster). + |deleteMeshPoints| |deleteMeshPoints.py|_ """ cellIds = vtk.vtkIdList() @@ -1329,11 +532,29 @@ def deletePoints(self, indices): self._polydata.DeleteCell(cellIds.GetId(j)) # flag cell self._polydata.RemoveDeletedCells() + + if renamePoints: + coords = self.points(transformed=False) + faces = self.faces() + pts_inds = np.unique(faces) # flattened array + + newfaces = [] + for f in faces: + newface=[] + for i in f: + idx = np.where(pts_inds==i)[0][0] + newface.append(idx) + newfaces.append(newface) + + newpoly = utils.buildPolyData(coords[pts_inds], newfaces) + return self._update(newpoly) + self._mapper.Modified() return self + def computeNormals(self, points=True, cells=True): - """Compute cell and vertex normals for the actor's mesh. + """Compute cell and vertex normals for the mesh. .. warning:: Mesh gets modified, output can have a different nr. of vertices. """ @@ -1345,7 +566,7 @@ def computeNormals(self, points=True, cells=True): pdnorm.FlipNormalsOff() pdnorm.ConsistencyOn() pdnorm.Update() - return self.updateMesh(pdnorm.GetOutput()) + return self._update(pdnorm.GetOutput()) def reverse(self, cells=True, normals=False): """ @@ -1370,10 +591,10 @@ def reverse(self, cells=True, normals=False): rev.ReverseNormalsOff() rev.SetInputData(poly) rev.Update() - return self.updateMesh(rev.GetOutput()) + return self._update(rev.GetOutput()) def alpha(self, opacity=None): - """Set/get actor's transparency. Same as `actor.opacity()`.""" + """Set/get mesh's transparency. Same as `mesh.opacity()`.""" if opacity is None: return self.GetProperty().GetOpacity() @@ -1388,11 +609,11 @@ def alpha(self, opacity=None): return self def opacity(self, alpha=None): - """Set/get actor's transparency. Same as `actor.alpha()`.""" + """Set/get mesh's transparency. Same as `mesh.alpha()`.""" return self.alpha(alpha) def wireframe(self, value=True): - """Set actor's representation as wireframe or solid surface. + """Set mesh's representation as wireframe or solid surface. Same as `wireframe()`.""" if value: self.GetProperty().SetRepresentationToWireframe() @@ -1430,7 +651,7 @@ def frontFaceCulling(self, value=True): return self def pointSize(self, ps=None): - """Set/get actor's point size of vertices. Same as `ps()`""" + """Set/get mesh's point size of vertices. Same as `ps()`""" if ps is not None: if isinstance(self, vtk.vtkAssembly): cl = vtk.vtkPropCollection() @@ -1447,12 +668,12 @@ def pointSize(self, ps=None): return self def ps(self, pointSize=None): - """Set/get actor's point size of vertices. Same as `pointSize()`""" + """Set/get mesh's point size of vertices. Same as `pointSize()`""" return self.pointSize(pointSize) def color(self, c=False): """ - Set/get actor's color. + Set/get mesh's color. If None is passed as input, will use colors from active scalars. Same as `c()`. """ @@ -1482,7 +703,7 @@ def color(self, c=False): def backColor(self, bc=None): """ - Set/get actor's backface color. + Set/get mesh's backface color. """ backProp = self.GetBackfaceProperty() @@ -1505,7 +726,7 @@ def backColor(self, bc=None): return self def bc(self, backColor=False): - """Shortcut for `actor.backColor()`. """ + """Shortcut for `mesh.backColor()`. """ return self.backColor(backColor) def lineWidth(self, lw=None): @@ -1543,7 +764,7 @@ def lc(self, lineColor=None): def clean(self, tol=None): """ - Clean actor's polydata. Can also be used to decimate a mesh if ``tol`` is large. + Clean mesh polydata. Can also be used to decimate a mesh if ``tol`` is large. If ``tol=None`` only removes coincident points. :param tol: defines how far should be the points from each other @@ -1562,7 +783,7 @@ def clean(self, tol=None): if tol: cleanPolyData.SetTolerance(tol) cleanPolyData.Update() - return self.updateMesh(cleanPolyData.GetOutput()) + return self._update(cleanPolyData.GetOutput()) def quantize(self, binSize): """ @@ -1580,20 +801,20 @@ def quantize(self, binSize): qp.SetInputData(poly) qp.SetQFactor(binSize) qp.Update() - return self.updateMesh(qp.GetOutput()) + return self._update(qp.GetOutput()) def xbounds(self): - """Get the actor bounds `[xmin,xmax]`.""" + """Get the mesh bounds `[xmin,xmax]`.""" b = self.polydata(True).GetBounds() return (b[0], b[1]) def ybounds(self): - """Get the actor bounds `[ymin,ymax]`.""" + """Get the mesh bounds `[ymin,ymax]`.""" b = self.polydata(True).GetBounds() return (b[2], b[3]) def zbounds(self): - """Get the actor bounds `[zmin,zmax]`.""" + """Get the mesh bounds `[zmin,zmax]`.""" b = self.polydata(True).GetBounds() return (b[4], b[5]) @@ -1601,7 +822,7 @@ def averageSize(self): """Calculate the average size of a mesh. This is the mean of the vertex distances from the center of mass.""" cm = self.centerOfMass() - coords = self.coordinates(copy=False) + coords = self.points(copy=False) if not len(coords): return 0 s, c = 0.0, 0.0 @@ -1613,17 +834,17 @@ def averageSize(self): return s / c def diagonalSize(self): - """Get the length of the diagonal of actor bounding box.""" + """Get the length of the diagonal of mesh bounding box.""" b = self.polydata().GetBounds() return np.sqrt((b[1] - b[0]) ** 2 + (b[3] - b[2]) ** 2 + (b[5] - b[4]) ** 2) def maxBoundSize(self): - """Get the maximum dimension in x, y or z of the actor bounding box.""" + """Get the maximum dimension in x, y or z of the mesh bounding box.""" b = self.polydata(True).GetBounds() return max(abs(b[1] - b[0]), abs(b[3] - b[2]), abs(b[5] - b[4])) def centerOfMass(self): - """Get the center of mass of actor. + """Get the center of mass of mesh. |fatlimb| |fatlimb.py|_ """ @@ -1634,7 +855,7 @@ def centerOfMass(self): return np.array(c) def volume(self, value=None): - """Get/set the volume occupied by actor.""" + """Get/set the volume occupied by mesh.""" mass = vtk.vtkMassProperties() mass.SetGlobalWarningDisplay(0) mass.SetInputData(self.polydata()) @@ -1643,7 +864,7 @@ def volume(self, value=None): if value is not None: if not v: colors.printc("~bomb Volume is zero: cannot rescale.", c=1, end="") - colors.printc(" Consider adding actor.triangle()", c=1) + colors.printc(" Consider adding mesh.triangle()", c=1) return self self.scale(value / v) return self @@ -1651,7 +872,7 @@ def volume(self, value=None): return v def area(self, value=None): - """Get/set the surface area of actor. + """Get/set the surface area of mesh. .. hint:: |largestregion.py|_ """ @@ -1663,7 +884,7 @@ def area(self, value=None): if value is not None: if not ar: colors.printc("~bomb Area is zero: cannot rescale.", c=1, end="") - colors.printc(" Consider adding actor.triangle()", c=1) + colors.printc(" Consider adding mesh.triangle()", c=1) return self self.scale(value / ar) return self @@ -1777,14 +998,14 @@ def findCellsWithin(self, xbounds=(), ybounds=(), zbounds=(), c=None): return np.array(cids) - def distanceToMesh(self, actor, signed=False, negate=False): + def distanceToMesh(self, mesh, signed=False, negate=False): ''' Computes the (signed) distance from one mesh to another. |distance2mesh| |distance2mesh.py|_ ''' poly1 = self.polydata() - poly2 = actor.polydata() + poly2 = mesh.polydata() df = vtk.vtkDistancePolyDataFilter() df.ComputeSecondDistanceOff() df.SetInputData(0, poly1) @@ -1808,7 +1029,7 @@ def distanceToMesh(self, actor, signed=False, negate=False): def clone(self, transformed=True): """ - Clone a ``Actor(vtkActor)`` and make an exact copy of it. + Clone a ``Mesh(vtkActor)`` and make an exact copy of it. :param transformed: if `False` ignore any previous transformation applied to the mesh. @@ -1818,7 +1039,7 @@ def clone(self, transformed=True): polyCopy = vtk.vtkPolyData() polyCopy.DeepCopy(poly) - cloned = Actor() + cloned = Mesh() cloned._polydata = polyCopy cloned._mapper.SetInputData(polyCopy) cloned._mapper.SetScalarVisibility(self._mapper.GetScalarVisibility()) @@ -1840,8 +1061,8 @@ def clone(self, transformed=True): def transformMesh(self, transformation): """ - Apply this transformation to the polygonal `mesh`, - not to the actor's transformation, which is reset. + Apply this transformation to the polygonal data, + not to the mesh transformation, which is reset. :param transformation: ``vtkTransform`` or ``vtkMatrix4x4`` object. """ @@ -1856,14 +1077,14 @@ def transformMesh(self, transformation): tf.SetInputData(self.polydata()) tf.Update() self.PokeMatrix(vtk.vtkMatrix4x4()) # identity - return self.updateMesh(tf.GetOutput()) + return self._update(tf.GetOutput()) def normalize(self): """ - Shift actor's center of mass at origin and scale its average size to unit. + Shift mesh center of mass at origin and scale its average size to unit. """ cm = self.centerOfMass() - coords = self.coordinates() + coords = self.points() if not len(coords): return pts = coords - cm @@ -1876,11 +1097,11 @@ def normalize(self): tf.SetInputData(self._polydata) tf.SetTransform(t) tf.Update() - return self.updateMesh(tf.GetOutput()) + return self._update(tf.GetOutput()) def mirror(self, axis="x"): """ - Mirror the actor polydata along one of the cartesian axes. + Mirror the mesh along one of the cartesian axes. .. note:: ``axis='n'``, will flip only mesh normals. @@ -1928,11 +1149,11 @@ def mirror(self, axis="x"): pdnorm.FlipNormalsOff() pdnorm.ConsistencyOn() pdnorm.Update() - return self.updateMesh(pdnorm.GetOutput()) + return self._update(pdnorm.GetOutput()) def flipNormals(self): """ - Flip all mesh normals. Same as `actor.mirror('n')`. + Flip all mesh normals. Same as `mesh.mirror('n')`. """ return self.mirror("n") @@ -1954,19 +1175,19 @@ def shrink(self, fraction=0.85): shrink.SetInputData(poly) shrink.SetShrinkFactor(fraction) shrink.Update() - return self.updateMesh(shrink.GetOutput()) + return self._update(shrink.GetOutput()) def stretch(self, q1, q2): - """Stretch actor between points `q1` and `q2`. Mesh is not affected. + """Stretch mesh between points `q1` and `q2`. Mesh is not affected. |aspring| |aspring.py|_ - .. note:: for ``Actors`` like helices, Line, cylinders, cones etc., - two attributes ``actor.base``, and ``actor.top`` are already defined. + .. note:: for ``Mesh`` objects like helices, Line, cylinders, cones etc., + two attributes ``mesh.base``, and ``mesh.top`` are already defined. """ if self.base is None: colors.printc('~times Error in stretch(): Please define vectors', c='r') - colors.printc(' actor.base and actor.top at creation.', c='r') + colors.printc(' mesh.base and mesh.top at creation.', c='r') raise RuntimeError() p1, p2 = self.base, self.top @@ -1997,7 +1218,7 @@ def stretch(self, q1, q2): return self def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=None): - """Crop an ``Actor`` object. Input object is modified. + """Crop an ``Mesh`` object. Input object is modified. :param float top: fraction to crop from the top plane (positive z) :param float bottom: fraction to crop from the bottom plane (negative z) @@ -2036,12 +1257,12 @@ def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=No clipper.SetValue(0) clipper.Update() - self.updateMesh(clipper.GetOutput()) + self._update(clipper.GetOutput()) return self def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False): """ - Takes a ``vtkActor`` and cuts it with the plane defined by a point and a normal. + Cut the mesh with the plane defined by a point and a normal. :param origin: the cutting plane goes through this point :param normal: normal of the cutting plane @@ -2082,14 +1303,15 @@ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False): clipper.SetValue(0) clipper.Update() - self.updateMesh(clipper.GetOutput()).computeNormals() + self._update(clipper.GetOutput()).computeNormals() if showcut: + from vtkplotter.assembly import Assembly c = self.GetProperty().GetColor() cpoly = clipper.GetClippedOutput() - restActor = Actor(cpoly, c, 0.05).wireframe(True) - restActor.SetUserMatrix(self.GetMatrix()) - asse = Assembly([self, restActor]) + restmesh = Mesh(cpoly, c, 0.05).wireframe(True) + restmesh.SetUserMatrix(self.GetMatrix()) + asse = Assembly([self, restmesh]) self = asse return asse else: @@ -2097,9 +1319,9 @@ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False): def cutWithMesh(self, mesh, invert=False): """ - Cut an ``Actor`` mesh with another ``vtkPolyData`` or ``Actor``. + Cut an ``Mesh`` mesh with another ``vtkPolyData`` or ``Mesh``. - :param bool invert: if True return cut off part of actor. + :param bool invert: if True return cut off part of mesh. .. hint:: |cutWithMesh.py|_ |cutAndCap.py|_ @@ -2107,7 +1329,7 @@ def cutWithMesh(self, mesh, invert=False): """ if isinstance(mesh, vtk.vtkPolyData): polymesh = mesh - elif isinstance(mesh, Actor): + elif isinstance(mesh, Mesh): polymesh = mesh.polydata() else: polymesh = mesh.GetMapper().GetInput() @@ -2137,15 +1359,15 @@ def cutWithMesh(self, mesh, invert=False): clipper.SetInsideOut(not invert) clipper.SetValue(0.0) clipper.Update() - return self.updateMesh(clipper.GetOutput()) + return self._update(clipper.GetOutput()) def cutWithPointLoop(self, points, invert=False): """ - Cut an ``Actor`` mesh with a set of points forming a closed loop. + Cut an ``Mesh`` object with a set of points forming a closed loop. """ - if isinstance(points, Actor): + if isinstance(points, Mesh): vpts = points.polydata().GetPoints() - points = points.coordinates() + points = points.points() else: vpts = vtk.vtkPoints() for p in points: @@ -2164,12 +1386,12 @@ def cutWithPointLoop(self, points, invert=False): clipper.SetInsideOut(not invert) clipper.SetValue(0.0) clipper.Update() - return self.updateMesh(clipper.GetOutput()) + return self._update(clipper.GetOutput()) def cap(self, returnCap=False): """ - Generate a "cap" on a clipped actor, or caps sharp edges. + Generate a "cap" on a clipped mesh, or caps sharp edges. |cutAndCap| |cutAndCap.py|_ """ @@ -2196,13 +1418,13 @@ def cap(self, returnCap=False): tf.Update() if returnCap: - return Actor(tf.GetOutput()) + return Mesh(tf.GetOutput()) else: polyapp = vtk.vtkAppendPolyData() polyapp.AddInputData(poly) polyapp.AddInputData(tf.GetOutput()) polyapp.Update() - return self.updateMesh(polyapp.GetOutput()).clean() + return self._update(polyapp.GetOutput()).clean() def threshold(self, scalars, vmin=None, vmax=None, useCells=False): """ @@ -2219,7 +1441,7 @@ def threshold(self, scalars, vmin=None, vmax=None, useCells=False): if utils.isSequence(scalars): self.addPointScalars(scalars, "threshold") scalars = "threshold" - elif self.scalars(scalars) is None: + elif self.getPointArray(scalars) is None: colors.printc("~times No scalars found with name/nr:", scalars, c=1) raise RuntimeError() @@ -2243,11 +1465,11 @@ def threshold(self, scalars, vmin=None, vmax=None, useCells=False): gf = vtk.vtkGeometryFilter() gf.SetInputData(thres.GetOutput()) gf.Update() - return self.updateMesh(gf.GetOutput()) + return self._update(gf.GetOutput()) def triangle(self, verts=True, lines=True): """ - Converts actor polygons and strips to triangles. + Converts mesh polygons and strips to triangles. :param bool verts: if True, break input vertex cells into individual vertex cells (one point per cell). If False, the input vertex cells will be ignored. @@ -2259,7 +1481,7 @@ def triangle(self, verts=True, lines=True): tf.SetPassVerts(verts) tf.SetInputData(self._polydata) tf.Update() - return self.updateMesh(tf.GetOutput()) + return self._update(tf.GetOutput()) def pointColors(self, scalars_or_colors, cmap="jet", alpha=1, mode='scalars', @@ -2561,24 +1783,6 @@ def _cellColors1By1(self, acolors, alphas, alphaPerCell): self._mapper.ScalarVisibilityOn() return self - def addPointVectors(self, vectors, name): - """ - Add a point vector field to the actor's polydata assigning it a name. - """ - poly = self.polydata(False) - if len(vectors) != poly.GetNumberOfPoints(): - colors.printc('~times addPointVectors Error: Number of vectors != nr. of points', - len(vectors), poly.GetNumberOfPoints(), c=1) - raise RuntimeError() - arr = vtk.vtkFloatArray() - arr.SetNumberOfComponents(3) - arr.SetName(name) - for v in vectors: - arr.InsertNextTuple(v) - poly.GetPointData().AddArray(arr) - poly.GetPointData().SetActiveVectors(name) - return self - def addIDs(self, asfield=False): """ Generate point and cell ids. @@ -2594,11 +1798,11 @@ def addIDs(self, asfield=False): else: ids.FieldDataOff() ids.Update() - return self.updateMesh(ids.GetOutput()) + return self._update(ids.GetOutput()) def addCurvatureScalars(self, method=0, lut=None): """ - Add scalars to ``Actor`` that contains the + Add scalars to ``Mesh`` that contains the curvature calculated in three different ways. :param int method: 0-gaussian, 1-mean, 2-max, 3-min curvature. @@ -2629,7 +1833,7 @@ def addCurvatureScalars(self, method=0, lut=None): def addElevationScalars(self, lowPoint=(), highPoint=(), vrange=(), lut=None): """ - Add to ``Actor`` a scalar array that contains distance along a specified direction. + Add to ``Mesh`` a scalar array that contains distance along a specified direction. :param list low: one end of the line (small scalar values). Default (0,0,0). :param list high: other end of the line (large scalar values). Default (0,0,1). @@ -2668,16 +1872,13 @@ def addElevationScalars(self, lowPoint=(), highPoint=(), vrange=(), lut=None): return self def scalars(self, name_or_idx=None, datatype="point"): - """ - Retrieve point or cell scalars using array name or index number, - and set it as the active one. - - If no input is given return the list of names of existing arrays. - - :param str datatype: search given name in point-data or cell-data - - .. hint:: |mesh_coloring.py|_ - """ + """Obsolete. Use methods getArrayNames(), getPointArray(), getCellArray(), + addPointScalars(), addCellScalars or addPointVectors() instead.""" + colors.printc("WARNING: scalars() is obsolete!", c=1) + colors.printc(" : Use getArrayNames(), getPointArray(), getCellArray(),", c=1) + colors.printc(" : addPointScalars() or addPointVectors() instead.", c=1) + #raise RuntimeError + poly = self.polydata(False) # no argument: return list of available arrays @@ -2757,7 +1958,7 @@ def subdivide(self, N=1, method=0): sdf.SetNumberOfSubdivisions(N) sdf.SetInputData(originalMesh) sdf.Update() - return self.updateMesh(sdf.GetOutput()) + return self._update(sdf.GetOutput()) def decimate(self, fraction=0.5, N=None, method='quadric', boundaries=False): """ @@ -2801,13 +2002,13 @@ def decimate(self, fraction=0.5, N=None, method='quadric', boundaries=False): decimate.SetInputData(poly) decimate.SetTargetReduction(1 - fraction) decimate.Update() - return self.updateMesh(decimate.GetOutput()) + return self._update(decimate.GetOutput()) def addGaussNoise(self, sigma): """ Add gaussian noise. - :param float sigma: sigma is expressed in percent of the diagonal size of actor. + :param float sigma: sigma is expressed in percent of the diagonal size of mesh. :Example: .. code-block:: python @@ -2817,7 +2018,7 @@ def addGaussNoise(self, sigma): Sphere().addGaussNoise(1.0).show() """ sz = self.diagonalSize() - pts = self.coordinates() + pts = self.points() n = len(pts) ns = np.random.randn(n, 3) * sigma * sz / 100 vpts = vtk.vtkPoints() @@ -2855,7 +2056,7 @@ def smoothLaplacian(self, niter=15, relaxfact=0.1, edgeAngle=15, featureAngle=60 smoothFilter.FeatureEdgeSmoothingOn() smoothFilter.GenerateErrorScalarsOn() smoothFilter.Update() - return self.updateMesh(smoothFilter.GetOutput()) + return self._update(smoothFilter.GetOutput()) def smoothWSinc(self, niter=15, passBand=0.1, edgeAngle=15, featureAngle=60): """ @@ -2884,7 +2085,7 @@ def smoothWSinc(self, niter=15, passBand=0.1, edgeAngle=15, featureAngle=60): smoothFilter.FeatureEdgeSmoothingOn() smoothFilter.BoundarySmoothingOn() smoothFilter.Update() - return self.updateMesh(smoothFilter.GetOutput()) + return self._update(smoothFilter.GetOutput()) def fillHoles(self, size=None): """Identifies and fills holes in input mesh. @@ -2902,21 +2103,14 @@ def fillHoles(self, size=None): fh.SetHoleSize(size) fh.SetInputData(self._polydata) fh.Update() - return self.updateMesh(fh.GetOutput()) + return self._update(fh.GetOutput()) def write(self, filename="mesh.vtk", binary=True): - """ - Write actor's polydata in its current transformation to file. - - - """ + """Write mesh to file.""" import vtkplotter.vtkio as vtkio return vtkio.write(self, filename, binary) - ######################################################################## - ### stuff that is not returning the input (sometimes modified) actor ### - def normalAt(self, i): """Return the normal vector at vertex point `i`.""" normals = self.polydata(True).GetPointData().GetNormals() @@ -2931,14 +2125,21 @@ def normals(self, cells=False): vtknormals = self.polydata().GetCellData().GetNormals() else: vtknormals = self.polydata().GetPointData().GetNormals() + if not vtknormals: + self.computeNormals(cells=cells) + if cells: + vtknormals = self.polydata().GetCellData().GetNormals() + else: + vtknormals = self.polydata().GetPointData().GetNormals() + return vtk_to_numpy(vtknormals) def polydata(self, transformed=True): """ - Returns the ``vtkPolyData`` object of an ``Actor``. + Returns the ``vtkPolyData`` object of a ``Mesh``. .. note:: If ``transformed=True`` returns a copy of polydata that corresponds - to the current actor's position in space. + to the current mesh's position in space. """ if not transformed: if not self._polydata: @@ -2953,7 +2154,7 @@ def polydata(self, transformed=True): return self._polydata else: # otherwise make a copy that corresponds to - # the actual position in space of the actor + # the actual position in space of the mesh transform = vtk.vtkTransform() transform.SetMatrix(M) tp = vtk.vtkTransformPolyDataFilter() @@ -2979,7 +2180,7 @@ def isInside(self, point, tol=0.0001): sep.Update() return sep.IsInside(0) - def insidePoints(self, points, invert=False, tol=1e-05): + def insidePoints(self, pts, invert=False, tol=1e-05): """ Return the sublist of points that are inside a polydata closed surface. @@ -2987,12 +2188,12 @@ def insidePoints(self, points, invert=False, tol=1e-05): """ poly = self.polydata(True) - if isinstance(points, Actor): - pointsPolydata = points.polydata(True) - points = points.coordinates() + if isinstance(pts, Mesh): + pointsPolydata = pts.polydata(True) + pts = pts.points() else: vpoints = vtk.vtkPoints() - vpoints.SetData(numpy_to_vtk(points, deep=True)) + vpoints.SetData(numpy_to_vtk(pts, deep=True)) pointsPolydata = vtk.vtkPolyData() pointsPolydata.SetPoints(vpoints) @@ -3003,7 +2204,7 @@ def insidePoints(self, points, invert=False, tol=1e-05): sep.Update() mask1, mask2 = [], [] - for i, p in enumerate(points): + for i, p in enumerate(pts): if sep.IsInside(i): mask1.append(p) else: @@ -3025,7 +2226,7 @@ def cellCenters(self): def boundaries(self, boundaryEdges=True, featureAngle=65, nonManifoldEdges=True): """ - Return an ``Actor`` that shows the boundary lines of an input mesh. + Return a ``Mesh`` that shows the boundary lines of an input mesh. :param bool boundaryEdges: Turn on/off the extraction of boundary edges. :param float featureAngle: Specify the feature angle for extracting feature edges. @@ -3044,7 +2245,7 @@ def boundaries(self, boundaryEdges=True, featureAngle=65, nonManifoldEdges=True) fe.SetNonManifoldEdges(nonManifoldEdges) fe.ColoringOff() fe.Update() - return Actor(fe.GetOutput(), c="p").lw(5) + return Mesh(fe.GetOutput(), c="p").lw(5) def connectedVertices(self, index, returnIds=False): """Find all vertices connected to an input vertex specified by its index. @@ -3111,10 +2312,10 @@ def connectedCells(self, index, returnIds=False): gf = vtk.vtkGeometryFilter() gf.SetInputData(extractSelection.GetOutput()) gf.Update() - return Actor(gf.GetOutput()).lw(1) + return Mesh(gf.GetOutput()).lw(1) def intersectWithLine(self, p0, p1): - """Return the list of points intersecting the actor + """Return the list of points intersecting the mesh along the segment defined by two points `p0` and `p1`. :Example: @@ -3127,84 +2328,202 @@ def intersectWithLine(self, p0, p1): ps = Points(pts, r=10, c='r') show(s, ln, ps, bg='white') - |intline| - """ - if not self.line_locator: - self.line_locator = vtk.vtkOBBTree() - self.line_locator.SetDataSet(self.polydata()) - self.line_locator.BuildLocator() + |intline| + """ + if not self.line_locator: + self.line_locator = vtk.vtkOBBTree() + self.line_locator.SetDataSet(self.polydata()) + self.line_locator.BuildLocator() + + intersectPoints = vtk.vtkPoints() + self.line_locator.IntersectWithLine(p0, p1, intersectPoints, None) + pts = [] + for i in range(intersectPoints.GetNumberOfPoints()): + intersection = [0, 0, 0] + intersectPoints.GetPoint(i, intersection) + pts.append(intersection) + return pts + + def projectOnPlane(self, direction='z'): + """ + Project the mesh on one of the Cartesian planes. + """ + coords = self.points(transformed=True) + if 'z' == direction: + coords[:, 2] = self.GetOrigin()[2] + self.z(self.zbounds()[0]) + elif 'x' == direction: + coords[:, 0] = self.GetOrigin()[0] + self.x(self.xbounds()[0]) + elif 'y' == direction: + coords[:, 1] = self.GetOrigin()[1] + self.y(self.ybounds()[0]) + else: + colors.printc("~times Error in projectOnPlane(): unknown direction", direction, c=1) + raise RuntimeError() + self.alpha(0.1).polydata(False).GetPoints().Modified() + return self + + def silhouette(self, direction=None, borderEdges=True, featureAngle=None): + """ + Return a new line ``Mesh`` which corresponds to the outer `silhouette` + of the input as seen along a specified `direction`, this can also be + a ``vtkCamera`` object. + + :param list direction: viewpoint direction vector. + If *None* this is guessed by looking at the minimum + of the sides of the bounding box. + :param bool borderEdges: enable or disable generation of border edges + :param float borderEdges: minimal angle for sharp edges detection. + If set to `False` the functionality is disabled. + + |silhouette| |silhouette.py|_ + """ + sil = vtk.vtkPolyDataSilhouette() + sil.SetInputData(self.polydata()) + if direction is None: + b = self.GetBounds() + i = np.argmin([b[1]-b[0], b[3]-b[2], b[5]-b[4]]) + d = ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sil.SetVector(d[i]) + sil.SetDirectionToSpecifiedVector() + elif isinstance(direction, vtk.vtkCamera): + sil.SetCamera(direction) + else: + sil.SetVector(direction) + sil.SetDirectionToSpecifiedVector() + if featureAngle is not None: + sil.SetEnableFeatureAngle(1) + sil.SetFeatureAngle(featureAngle) + if featureAngle == False: + sil.SetEnableFeatureAngle(0) + + sil.SetBorderEdges(borderEdges) + sil.Update() + return Mesh(sil.GetOutput()).lw(2).c('k') + + + def addShadow(self, x=None, y=None, z=None, c=(0.5, 0.5, 0.5), alpha=1): + """ + Generate a shadow out of an ``Mesh`` on one of the three Cartesian planes. + The output is a new ``Mesh`` representing the shadow. + This new mesh is accessible through `mesh.shadow`. + By default the shadow mesh is placed on the bottom/back wall of the bounding box. + + :param float x,y,z: identify the plane to cast the shadow to ['x', 'y' or 'z']. + The shadow will lay on the orthogonal plane to the specified axis at the + specified value of either x, y or z. + + |shadow| |shadow.py|_ + + |airplanes| |airplanes.py|_ + """ + if x is not None: + self.shadowX = x + shad = self.clone().projectOnPlane('x').x(x) + elif y is not None: + self.shadowY = y + shad = self.clone().projectOnPlane('y').y(y) + elif z is not None: + self.shadowZ = z + shad = self.clone().projectOnPlane('z').z(z) + else: + print('Error in addShadow(): must set x, y or z to a float!') + return self + shad.c(c).alpha(alpha).wireframe(False) + shad.flat().backFaceCulling() + shad.GetProperty().LightingOff() + self.shadow = shad + return self + + def _updateShadow(self): + p = self.GetPosition() + if self.shadowX is not None: + self.shadow.SetPosition(self.shadowX, p[1], p[2]) + elif self.shadowY is not None: + self.shadow.SetPosition(p[0], self.shadowY, p[2]) + elif self.shadowZ is not None: + self.shadow.SetPosition(p[0], p[1], self.shadowZ) + return self + + def addTrail(self, offset=None, maxlength=None, n=50, c=None, alpha=None, lw=2): + """Add a trailing line to mesh. + This new mesh is accessible through `mesh.trail`. + + :param offset: set an offset vector from the object center. + :param maxlength: length of trailing line in absolute units + :param n: number of segments to control precision + :param lw: line width of the trail + + .. hint:: See examples: |trail.py|_ |airplanes.py|_ + + |trail| + """ + if maxlength is None: + maxlength = self.diagonalSize() * 20 + if maxlength == 0: + maxlength = 1 + + if self.trail is None: + from vtkplotter.mesh import Mesh + pos = self.GetPosition() + self.trailPoints = [None] * n + self.trailSegmentSize = maxlength / n + self.trailOffset = offset + + ppoints = vtk.vtkPoints() # Generate the polyline + poly = vtk.vtkPolyData() + ppoints.SetData(numpy_to_vtk([pos] * n)) + poly.SetPoints(ppoints) + lines = vtk.vtkCellArray() + lines.InsertNextCell(n) + for i in range(n): + lines.InsertCellPoint(i) + poly.SetPoints(ppoints) + poly.SetLines(lines) + mapper = vtk.vtkPolyDataMapper() + + if c is None: + if hasattr(self, "GetProperty"): + col = self.GetProperty().GetColor() + else: + col = (0.1, 0.1, 0.1) + else: + col = colors.getColor(c) - intersectPoints = vtk.vtkPoints() - self.line_locator.IntersectWithLine(p0, p1, intersectPoints, None) - pts = [] - for i in range(intersectPoints.GetNumberOfPoints()): - intersection = [0, 0, 0] - intersectPoints.GetPoint(i, intersection) - pts.append(intersection) - return pts + if alpha is None: + alpha = 1 + if hasattr(self, "GetProperty"): + alpha = self.GetProperty().GetOpacity() - def projectOnPlane(self, direction='z'): - """ - Project the mesh on one of the Cartesian planes. - """ - coords = self.coordinates(transformed=True) - if 'z' == direction: - coords[:, 2] = self.GetOrigin()[2] - self.z(self.zbounds()[0]) - elif 'x' == direction: - coords[:, 0] = self.GetOrigin()[0] - self.x(self.xbounds()[0]) - elif 'y' == direction: - coords[:, 1] = self.GetOrigin()[1] - self.y(self.ybounds()[0]) - else: - colors.printc("~times Error in projectOnPlane(): unknown direction", direction, c=1) - raise RuntimeError() - self.alpha(0.1).polydata(False).GetPoints().Modified() + mapper.SetInputData(poly) + tline = Mesh(poly, c=col, alpha=alpha) + tline.SetMapper(mapper) + tline.GetProperty().SetLineWidth(lw) + self.trail = tline # holds the vtkActor return self - def silhouette(self, direction=None, borderEdges=True, featureAngle=None): - """ - Return a new line ``Actor`` which corresponds to the outer `silhouette` - of the input as seen along a specified `direction`, this can also be - a ``vtkCamera`` object. - - :param list direction: viewpoint direction vector. - If *None* this is guessed by looking at the minimum - of the sides of the bounding box. - :param bool borderEdges: enable or disable generation of border edges - :param float borderEdges: minimal angle for sharp edges detection. - If set to `False` the functionality is disabled. + def updateTrail(self): + currentpos = np.array(self.GetPosition()) + if self.trailOffset: + currentpos += self.trailOffset + lastpos = self.trailPoints[-1] + if lastpos is None: # reset list + self.trailPoints = [currentpos] * len(self.trailPoints) + return + if np.linalg.norm(currentpos - lastpos) < self.trailSegmentSize: + return - |silhouette| |silhouette.py|_ - """ - sil = vtk.vtkPolyDataSilhouette() - sil.SetInputData(self.polydata()) - if direction is None: - b = self.GetBounds() - i = np.argmin([b[1]-b[0], b[3]-b[2], b[5]-b[4]]) - d = ((1, 0, 0), (0, 1, 0), (0, 0, 1)) - sil.SetVector(d[i]) - sil.SetDirectionToSpecifiedVector() - elif isinstance(direction, vtk.vtkCamera): - sil.SetCamera(direction) - else: - sil.SetVector(direction) - sil.SetDirectionToSpecifiedVector() - if featureAngle is not None: - sil.SetEnableFeatureAngle(1) - sil.SetFeatureAngle(featureAngle) - if featureAngle == False: - sil.SetEnableFeatureAngle(0) + self.trailPoints.append(currentpos) # cycle + self.trailPoints.pop(0) - sil.SetBorderEdges(borderEdges) - sil.Update() - return Actor(sil.GetOutput()).lw(2).c('k') + tpoly = self.trail.polydata() + tpoly.GetPoints().SetData(numpy_to_vtk(self.trailPoints)) + return self def followCamera(self, cam=None): - """ - Actor object will follow camera movements and stay locked to it. + """Mesh object will follow camera movements and stay locked to it. :param vtkCamera cam: if `None` the text will auto-orient itself to the active camera. A ``vtkCamera`` object can also be passed. @@ -3225,7 +2544,7 @@ def followCamera(self, cam=None): def isolines(self, n=10, vmin=None, vmax=None): """ - Return a new ``Actor`` representing the isolines of the active scalars. + Return a new ``Mesh`` representing the isolines of the active scalars. :param int n: number of isolines in the range :param float vmin: minimum of the range @@ -3245,774 +2564,7 @@ def isolines(self, n=10, vmin=None, vmax=None): bcf.GenerateValues(n, vmin, vmax) bcf.Update() zpoly = bcf.GetContourEdgesOutput() - zbandsact = Actor(zpoly, c="k") + zbandsact = Mesh(zpoly, c="k") zbandsact.GetProperty().SetLineWidth(1.5) return zbandsact - -################################################# -class Assembly(vtk.vtkAssembly, Prop): - """Group many actors as a single new actor as a ``vtkAssembly``. - - |gyroscope1| |gyroscope1.py|_ - """ - - def __init__(self, actors): - - vtk.vtkAssembly.__init__(self) - Prop.__init__(self) - - self.actors = actors - - if len(actors) and hasattr(actors[0], "base"): - self.base = actors[0].base - self.top = actors[0].top - else: - self.base = None - self.top = None - - for a in actors: - if a: - self.AddPart(a) - - def __add__(self, actors): - if isinstance(actors, list): - for a in actors: - self.AddPart(self) - elif isinstance(actors, vtk.vtkAssembly): - acts = actors.getActors() - for a in acts: - self.AddPart(a) - else: # actors=one actor - self.AddPart(actors) - return self - - def getActors(self): - """Unpack a list of ``vtkActor`` objects from a ``vtkAssembly``.""" - return self.actors - - def getActor(self, i): - """Get `i-th` ``vtkActor`` object from a ``vtkAssembly``.""" - return self.actors[i] - - def diagonalSize(self): - """Return the maximum diagonal size of the ``Actors`` of the ``Assembly``.""" - szs = [a.diagonalSize() for a in self.actors] - return np.max(szs) - - def lighting(self, style='', ambient=None, diffuse=None, - specular=None, specularPower=None, specularColor=None, enabled=True): - """Set the lighting type to all ``Actor`` in the ``Assembly`` object. - - :param str style: preset style, can be `[metallic, plastic, shiny, glossy]` - :param float ambient: ambient fraction of emission [0-1] - :param float diffuse: emission of diffused light in fraction [0-1] - :param float specular: fraction of reflected light [0-1] - :param float specularPower: precision of reflection [1-100] - :param color specularColor: color that is being reflected by the surface - :param bool enabled: enable/disable all surface light emission - """ - for a in self.actors: - a.lighting(style, ambient, diffuse, - specular, specularPower, specularColor, enabled) - return self - - -################################################# -class Picture(vtk.vtkImageActor, Prop): - """ - Derived class of ``vtkImageActor``. Used to represent 2D pictures. - Can be instantiated with a path file name or with a numpy array. - - |rotateImage| |rotateImage.py|_ - """ - - def __init__(self, obj=None): - vtk.vtkImageActor.__init__(self) - Prop.__init__(self) - - if utils.isSequence(obj) and len(obj): - iac = vtk.vtkImageAppendComponents() - for i in range(3): - #arr = np.flip(np.flip(array[:,:,i], 0), 0).ravel() - arr = np.flip(obj[:,:,i], 0).ravel() - varb = numpy_to_vtk(arr, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR) - imgb = vtk.vtkImageData() - imgb.SetDimensions(obj.shape[1], obj.shape[0], 1) - imgb.GetPointData().SetScalars(varb) - iac.AddInputData(0, imgb) - iac.Update() - self.SetInputData(iac.GetOutput()) - #self.mirror() - - elif isinstance(obj, str): - if ".png" in obj: - picr = vtk.vtkPNGReader() - elif ".jpg" in obj or ".jpeg" in obj: - picr = vtk.vtkJPEGReader() - elif ".bmp" in obj: - picr = vtk.vtkBMPReader() - picr.SetFileName(obj) - picr.Update() - self.SetInputData(picr.GetOutput()) - - - def alpha(self, a=None): - """Set/get picture's transparency.""" - if a is not None: - self.GetProperty().SetOpacity(a) - return self - else: - return self.GetProperty().GetOpacity() - - def crop(self, top=None, bottom=None, right=None, left=None): - """Crop picture. - - :param float top: fraction to crop from the top margin - :param float bottom: fraction to crop from the bottom margin - :param float left: fraction to crop from the left margin - :param float right: fraction to crop from the right margin - """ - extractVOI = vtk.vtkExtractVOI() - extractVOI.SetInputData(self.GetInput()) - extractVOI.IncludeBoundaryOn() - - d = self.GetInput().GetDimensions() - bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 - if left is not None: bx0 = int((d[0]-1)*left) - if right is not None: bx1 = int((d[0]-1)*(1-right)) - if bottom is not None: by0 = int((d[1]-1)*bottom) - if top is not None: by1 = int((d[1]-1)*(1-top)) - extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) - extractVOI.Update() - img = extractVOI.GetOutput() - self.GetMapper().SetInputData(img) - self.GetMapper().Modified() - return self - - def mirror(self, axis="x"): - """Mirror picture along x or y axis.""" - ff = vtk.vtkImageFlip() - ff.SetInputData(self.inputdata()) - if axis.lower() == "x": - ff.SetFilteredAxis(0) - elif axis.lower() == "y": - ff.SetFilteredAxis(1) - else: - colors.printc("~times Error in mirror(): mirror must be set to x or y.", c=1) - raise RuntimeError() - ff.Update() - self.GetMapper().SetInputData(ff.GetOutput()) - self.GetMapper().Modified() - return self - - -########################################################################## -class Volume(vtk.vtkVolume, Prop): - """Derived class of ``vtkVolume``. - Can be initialized with a numpy object, see e.g.: |numpy2volume.py|_ - - :param c: sets colors along the scalar range, or a matplotlib color map name - :type c: list, str - :param alphas: sets transparencies along the scalar range - :type c: float, list - :param list origin: set volume origin coordinates - :param list spacing: voxel dimensions in x, y and z. - :param list shape: specify the shape. - :param str mapperType: either 'gpu', 'opengl_gpu', 'fixed' or 'smart' - - :param int mode: define the volumetric rendering style: - - - 0, Composite rendering - - 1, maximum projection rendering - - 2, minimum projection - - 3, average projection - - 4, additive mode - - .. hint:: if a `list` of values is used for `alphas` this is interpreted - as a transfer function along the range of the scalar. - - |read_vti| |read_vti.py|_ - """ - - def __init__(self, inputobj, - c=('b','lb','lg','y','r'), - alpha=(0.0, 0.0, 0.2, 0.4, 0.8, 1), - alphaGradient=None, - mode=0, - origin=None, - spacing=None, - shape=None, - mapperType='gpu', - ): - - vtk.vtkVolume.__init__(self) - Prop.__init__(self) - - inputtype = str(type(inputobj)) - #colors.printc('Volume inputtype', inputtype) - - if inputobj is None: - img = vtk.vtkImageData() - elif utils.isSequence(inputobj): - if "ndarray" not in inputtype: - inputobj = np.array(inputobj) - - varr = numpy_to_vtk(inputobj.ravel(order='F'), - deep=True, array_type=vtk.VTK_FLOAT) - varr.SetName('input_scalars') - - img = vtk.vtkImageData() - if shape is not None: - img.SetDimensions(shape) - else: - img.SetDimensions(inputobj.shape) - img.GetPointData().SetScalars(varr) - - elif "ImageData" in inputtype: - img = inputobj - elif "UniformGrid" in inputtype: - img = inputobj - elif "UnstructuredGrid" in inputtype: - img = inputobj - mapperType = 'tetra' - elif hasattr(inputobj, "GetOutput"): # passing vtk object, try extract imagdedata - if hasattr(inputobj, "Update"): - inputobj.Update() - img = inputobj.GetOutput() - else: - colors.printc("Volume(): cannot understand input type:\n", inputtype, c=1) - return - - if 'gpu' in mapperType: - self._mapper = vtk.vtkGPUVolumeRayCastMapper() - elif 'opengl_gpu' in mapperType: - self._mapper = vtk.vtkOpenGLGPUVolumeRayCastMapper() - elif 'smart' in mapperType: - self._mapper = vtk.vtkSmartVolumeMapper() - elif 'fixed' in mapperType: - self._mapper = vtk.vtkFixedPointVolumeRayCastMapper() - elif 'tetra' in mapperType: - self._mapper = vtk.vtkProjectedTetrahedraMapper() - elif 'unstr' in mapperType: - self._mapper = vtk.vtkUnstructuredGridVolumeRayCastMapper() - - if origin is not None: - img.SetOrigin(origin) - if spacing is not None: - img.SetSpacing(spacing) - if shape is not None: - img.SetDimensions(shape) - - self._image = img - self._mapper.SetInputData(img) - self.SetMapper(self._mapper) - self.mode(mode).color(c).alpha(alpha).alphaGradient(alphaGradient) - # remember stuff: - self._mode = mode - self._color = c - self._alpha = alpha - self._alphaGrad = alphaGradient - - def updateVolume(self, img): - """ - Overwrite the polygonal mesh of the actor with a new one. - """ - self._image = img - self._mapper.SetInputData(img) - self._mapper.Modified() - return self - - def mode(self, mode=None): - """Define the volumetric rendering style. - - - 0, Composite rendering - - 1, maximum projection rendering - - 2, minimum projection - - 3, average projection - - 4, additive mode - """ - if mode is None: - return self._mapper.GetBlendMode() - - volumeProperty = self.GetProperty() - self._mapper.SetBlendMode(mode) - self._mode = mode - if mode == 0: - volumeProperty.ShadeOn() - self.lighting('shiny') - self.jittering(True) - elif mode == 1: - volumeProperty.ShadeOff() - self.jittering(True) - return self - - def jittering(self, status=None): - """If `jittering` is `True`, each ray traversal direction will be perturbed slightly - using a noise-texture to get rid of wood-grain effects. - """ - if hasattr(self._mapper, 'SetUseJittering'): - if status is None: - return self._mapper.GetUseJittering() - self._mapper.SetUseJittering(status) - return self - return None - - def imagedata(self): - """Return the underlying ``vtkImagaData`` object.""" - return self._image - - def dimensions(self): - """Return the nr. of voxels in the 3 dimensions.""" - return self._image.GetDimensions() - - def spacing(self, s=None): - """Set/get the voxels size in the 3 dimensions.""" - if s is not None: - self._image.SetSpacing(s) - self._mapper.Modified() - return self - else: - return np.array(self._image.GetSpacing()) - - def permuteAxes(self, x, y ,z): - """Reorder the axes of the Volume by specifying - the input axes which are supposed to become the new X, Y, and Z.""" - imp = vtk.vtkImagePermute() - imp.SetFilteredAxes(x,y,z) - imp. SetInputData(self.imagedata()) - imp.Update() - return self.updateVolume(imp.GetOutput()) - - def resample(self, newSpacing, interpolation=1): - """ - Resamples a ``Volume`` to be larger or smaller. - - This method modifies the spacing of the input. - Linear interpolation is used to resample the data. - - :param list newSpacing: a list of 3 new spacings for the 3 axes. - :param int interpolation: 0=nearest_neighbor, 1=linear, 2=cubic - """ - rsp = vtk.vtkImageResample() - oldsp = self.GetSpacing() - for i in range(3): - if oldsp[i] != newSpacing[i]: - rsp.SetAxisOutputSpacing(i, newSpacing[i]) - rsp.InterpolateOn() - rsp.SetInterpolationMode(interpolation) - rsp.OptimizationOn() - rsp.Update() - return self.updateVolume(rsp.GetOutput()) - - - def color(self, col): - """Assign a color or a set of colors to a volume along the range of the scalar value. - A single constant color can also be assigned. - Any matplotlib color map name is also accepted, e.g. ``volume.color('jet')``. - - E.g.: say that your voxel scalar runs from -3 to 6, - and you want -3 to show red and 1.5 violet and 6 green, then just set: - - ``volume.color(['red', 'violet', 'green'])`` - """ - smin, smax = self._image.GetScalarRange() - volumeProperty = self.GetProperty() - ctf = vtk.vtkColorTransferFunction() - self._color = col - - if utils.isSequence(col): - for i, ci in enumerate(col): - r, g, b = colors.getColor(ci) - xalpha = smin + (smax - smin) * i / (len(col) - 1) - ctf.AddRGBPoint(xalpha, r, g, b) - #colors.printc('\tcolor at', round(xalpha, 1), - # '\tset to', colors.getColorName((r, g, b)), c='b', bold=0) - elif isinstance(col, str): - if col in colors.colors.keys() or col in colors.color_nicks.keys(): - r, g, b = colors.getColor(col) - ctf.AddRGBPoint(smin, r,g,b) # constant color - ctf.AddRGBPoint(smax, r,g,b) - elif colors._mapscales: - for x in np.linspace(smin, smax, num=64, endpoint=True): - r,g,b = colors.colorMap(x, name=col, vmin=smin, vmax=smax) - ctf.AddRGBPoint(x, r, g, b) - elif isinstance(col, int): - r, g, b = colors.getColor(col) - ctf.AddRGBPoint(smin, r,g,b) # constant color - ctf.AddRGBPoint(smax, r,g,b) - else: - colors.printc("volume.color(): unknown input type:", col, c=1) - - volumeProperty.SetColor(ctf) - volumeProperty.SetInterpolationTypeToLinear() - #volumeProperty.SetInterpolationTypeToNearest() - return self - - def alpha(self, alpha): - """Assign a set of tranparencies to a volume along the range of the scalar value. - A single constant value can also be assigned. - - E.g.: say alpha=(0.0, 0.3, 0.9, 1) and the scalar range goes from -10 to 150. - Then all voxels with a value close to -10 will be completely transparent, voxels at 1/4 - of the range will get an alpha equal to 0.3 and voxels with value close to 150 - will be completely opaque. - """ - volumeProperty = self.GetProperty() - smin, smax = self._image.GetScalarRange() - opacityTransferFunction = vtk.vtkPiecewiseFunction() - self._alpha = alpha - - if utils.isSequence(alpha): - for i, al in enumerate(alpha): - xalpha = smin + (smax - smin) * i / (len(alpha) - 1) - # Create transfer mapping scalar value to opacity - opacityTransferFunction.AddPoint(xalpha, al) - #colors.printc("alpha at", round(xalpha, 1), "\tset to", al, c="b", bold=0) - else: - opacityTransferFunction.AddPoint(smin, alpha) # constant alpha - opacityTransferFunction.AddPoint(smax, alpha) - - volumeProperty.SetScalarOpacity(opacityTransferFunction) - volumeProperty.SetInterpolationTypeToLinear() - return self - - def alphaGradient(self, alphaGrad): - """ - Assign a set of tranparencies to a volume's gradient - along the range of the scalar value. - A single constant value can also be assigned. - The gradient function is used to decrease the opacity - in the "flat" regions of the volume while maintaining the opacity - at the boundaries between material types. The gradient is measured - as the amount by which the intensity changes over unit distance. - - |read_vti| |read_vti.py|_ - """ - self._alphaGrad = alphaGrad - volumeProperty = self.GetProperty() - if alphaGrad is None: - volumeProperty.DisableGradientOpacityOn() - return self - else: - volumeProperty.DisableGradientOpacityOff() - - #smin, smax = self._image.GetScalarRange() - smin, smax = 0, 255 - gotf = vtk.vtkPiecewiseFunction() - if utils.isSequence(alphaGrad): - for i, al in enumerate(alphaGrad): - xalpha = smin + (smax - smin) * i / (len(alphaGrad) - 1) - # Create transfer mapping scalar value to gradient opacity - gotf.AddPoint(xalpha, al) - #colors.printc("alphaGrad at", round(xalpha, 1), "\tset to", al, c="b", bold=0) - else: - gotf.AddPoint(smin, alphaGrad) # constant alphaGrad - gotf.AddPoint(smax, alphaGrad) - - volumeProperty.SetGradientOpacity(gotf) - volumeProperty.SetInterpolationTypeToLinear() - return self - - def threshold(self, vmin=None, vmax=None, replaceWith=0): - """ - Binary or continuous volume thresholding. - Find the voxels that contain the value below/above or inbetween - [vmin, vmax] and replaces it with the provided value (default is 0). - """ - th = vtk.vtkImageThreshold() - th.SetInputData(self.imagedata()) - - if vmin is not None and vmax is not None: - th.ThresholdBetween(vmin, vmax) - elif vmin is not None: - th.ThresholdByLower(vmin) - elif vmax is not None: - th.ThresholdByUpper(vmax) - - th.SetInValue(replaceWith) - th.Update() - return self.updateVolume(th.GetOutput()) - - def crop(self, - top=None, bottom=None, - right=None, left=None, - front=None, back=None, VOI=()): - """Crop a ``Volume`` object. - - :param float top: fraction to crop from the top plane (positive z) - :param float bottom: fraction to crop from the bottom plane (negative z) - :param float front: fraction to crop from the front plane (positive y) - :param float back: fraction to crop from the back plane (negative y) - :param float right: fraction to crop from the right plane (positive x) - :param float left: fraction to crop from the left plane (negative x) - :param list VOI: extract Volume Of Interest expressed in voxel numbers - - Eg.: vol.crop(VOI=(xmin, xmax, ymin, ymax, zmin, zmax)) # all integers nrs - """ - extractVOI = vtk.vtkExtractVOI() - extractVOI.SetInputData(self.imagedata()) - - if len(VOI): - extractVOI.SetVOI(VOI) - else: - d = self.imagedata().GetDimensions() - bx0, bx1, by0, by1, bz0, bz1 = 0, d[0]-1, 0, d[1]-1, 0, d[2]-1 - if left is not None: bx0 = int((d[0]-1)*left) - if right is not None: bx1 = int((d[0]-1)*(1-right)) - if back is not None: by0 = int((d[1]-1)*back) - if front is not None: by1 = int((d[1]-1)*(1-front)) - if bottom is not None: bz0 = int((d[2]-1)*bottom) - if top is not None: bz1 = int((d[2]-1)*(1-top)) - extractVOI.SetVOI(bx0, bx1, by0, by1, bz0, bz1) - extractVOI.Update() - return self.updateVolume(extractVOI.GetOutput()) - - def append(self, volumes, axis='z', preserveExtents=False): - """ - Take the components from multiple inputs and merges them into one output. - Except for the append axis, all inputs must have the same extent. - All inputs must have the same number of scalar components. - The output has the same origin and spacing as the first input. - The origin and spacing of all other inputs are ignored. - All inputs must have the same scalar type. - - :param int,str axis: axis expanded to hold the multiple images. - :param bool preserveExtents: if True, the extent of the inputs is used to place - the image in the output. The whole extent of the output is the union of the input - whole extents. Any portion of the output not covered by the inputs is set to zero. - The origin and spacing is taken from the first input. - - .. code-block:: python - - from vtkplotter import load, datadir - vol = load(datadir+'embryo.tif') - vol.append(vol, axis='x').show() - """ - ima = vtk.vtkImageAppend() - ima.SetInputData(self.imagedata()) - if not utils.isSequence(volumes): - volumes = [volumes] - for volume in volumes: - if isinstance(volume, vtk.vtkImageData): - ima.AddInputData(volume) - else: - ima.AddInputData(volume.imagedata()) - ima.SetPreserveExtents(preserveExtents) - if axis == "x": - axis = 0 - elif axis == "y": - axis = 1 - elif axis == "z": - axis = 2 - ima.SetAppendAxis(axis) - ima.Update() - return self.updateVolume(ima.GetOutput()) - - def cutWithPlane(self, origin=(0,0,0), normal=(1,0,0)): - """ - Cuts ``Volume`` with the plane defined by a point and a normal - creating a tetrahedral mesh object. - Makes sense only if the plane is not along any of the cartesian planes, - otherwise use ``crop()`` which is way faster. - - :param origin: the cutting plane goes through this point - :param normal: normal of the cutting plane - """ - plane = vtk.vtkPlane() - plane.SetOrigin(origin) - plane.SetNormal(normal) - - clipper = vtk.vtkClipVolume() - clipper.SetInputData(self._image) - clipper.SetClipFunction(plane) - clipper.GenerateClipScalarsOff() - clipper.GenerateClippedOutputOff() - clipper.Mixed3DCellGenerationOff() # generate only tets - clipper.SetValue(0) - clipper.Update() - - vol = Volume(clipper.GetOutput()).color(self._color) - return vol #self.updateVolume(clipper.GetOutput()) - - - def resize(self, *newdims): - """Increase or reduce the number of voxels of a Volume with interpolation.""" - old_dims = np.array(self.imagedata().GetDimensions()) - old_spac = np.array(self.imagedata().GetSpacing()) - rsz = vtk.vtkImageResize() - rsz.SetResizeMethodToOutputDimensions() - rsz.SetInputData(self.imagedata()) - rsz.SetOutputDimensions(newdims) - rsz.Update() - self._image = rsz.GetOutput() - new_spac = old_spac * old_dims/newdims # keep aspect ratio - self._image.SetSpacing(new_spac) - return self.updateVolume(self._image) - - def normalize(self): - """Normalize that scalar components for each point.""" - norm = vtk.vtkImageNormalize() - norm.SetInputData(self.imagedata()) - norm.Update() - return self.updateVolume(norm.GetOutput()) - - def scaleVoxels(self, scale=1): - """Scale the voxel content by factor `scale`.""" - rsl = vtk.vtkImageReslice() - rsl.SetInputData(self.imagedata()) - rsl.SetScalarScale(scale) - rsl.Update() - return self.updateVolume(rsl.GetOutput()) - - def mirror(self, axis="x"): - """ - Mirror flip along one of the cartesian axes. - - .. note:: ``axis='n'``, will flip only mesh normals. - - |mirror| |mirror.py|_ - """ - img = self.imagedata() - - ff = vtk.vtkImageFlip() - ff.SetInputData(img) - if axis.lower() == "x": - ff.SetFilteredAxis(0) - elif axis.lower() == "y": - ff.SetFilteredAxis(1) - elif axis.lower() == "z": - ff.SetFilteredAxis(2) - else: - colors.printc("~times Error in mirror(): mirror must be set to x, y, z or n.", c=1) - raise RuntimeError() - ff.Update() - return self.updateVolume(ff.GetOutput()) - - def xSlice(self, i): - """Extract the slice at index `i` of volume along x-axis.""" - vslice = vtk.vtkImageDataGeometryFilter() - vslice.SetInputData(self.imagedata()) - nx, ny, nz = self.imagedata().GetDimensions() - if i>nx-1: - i=nx-1 - vslice.SetExtent(i,i, 0,ny, 0,nz) - vslice.Update() - return Actor(vslice.GetOutput()) - - def ySlice(self, j): - """Extract the slice at index `j` of volume along y-axis.""" - vslice = vtk.vtkImageDataGeometryFilter() - vslice.SetInputData(self.imagedata()) - nx, ny, nz = self.imagedata().GetDimensions() - if j>ny-1: - j=ny-1 - vslice.SetExtent(0,nx, j,j, 0,nz) - vslice.Update() - return Actor(vslice.GetOutput()) - - def zSlice(self, k): - """Extract the slice at index `i` of volume along z-axis.""" - vslice = vtk.vtkImageDataGeometryFilter() - vslice.SetInputData(self.imagedata()) - nx, ny, nz = self.imagedata().GetDimensions() - if k>nz-1: - k=nz-1 - vslice.SetExtent(0,nx, 0,ny, k,k) - vslice.Update() - return Actor(vslice.GetOutput()) - - - def isosurface(self, threshold=True, connectivity=False): - """Return an ``Actor`` isosurface extracted from the ``Volume`` object. - - :param threshold: value or list of values to draw the isosurface(s) - :type threshold: float, list - :param bool connectivity: if True only keeps the largest portion of the polydata - - |isosurfaces| |isosurfaces.py|_ - """ - scrange = self._image.GetScalarRange() - cf = vtk.vtkContourFilter() - cf.SetInputData(self._image) - cf.UseScalarTreeOn() - cf.ComputeScalarsOn() - cf.ComputeNormalsOn() - - if utils.isSequence(threshold): - cf.SetNumberOfContours(len(threshold)) - for i, t in enumerate(threshold): - cf.SetValue(i, t) - cf.Update() - else: - if threshold is True: - threshold = (2 * scrange[0] + scrange[1]) / 3.0 - print('automatic threshold set to ' + utils.precision(threshold, 3), end=' ') - print('in [' + utils.precision(scrange[0], 3) + ', ' + utils.precision(scrange[1], 3)+']') - cf.SetValue(0, threshold) - cf.Update() - - clp = vtk.vtkCleanPolyData() - clp.SetInputConnection(cf.GetOutputPort()) - clp.Update() - poly = clp.GetOutput() - - if connectivity: - conn = vtk.vtkPolyDataConnectivityFilter() - conn.SetExtractionModeToLargestRegion() - conn.SetInputData(poly) - conn.Update() - poly = conn.GetOutput() - - a = Actor(poly, c=None).phong() - a._mapper.SetScalarRange(scrange[0], scrange[1]) - return a - - - def legosurface(self, vmin=None, vmax=None, cmap='afmhot_r'): - """ - Represent a ``Volume`` as lego blocks (voxels). - By default colors correspond to the volume's scalar. - Returns an ``Actor``. - - :param float vmin: the lower threshold, voxels below this value are not shown. - :param float vmax: the upper threshold, voxels above this value are not shown. - :param str cmap: color mapping of the scalar associated to the voxels. - - |legosurface| |legosurface.py|_ - """ - dataset = vtk.vtkImplicitDataSet() - dataset.SetDataSet(self._image) - window = vtk.vtkImplicitWindowFunction() - window.SetImplicitFunction(dataset) - - srng = list(self._image.GetScalarRange()) - if vmin is not None: - srng[0] = vmin - if vmax is not None: - srng[1] = vmax - window.SetWindowRange(srng) - - extract = vtk.vtkExtractGeometry() - extract.SetInputData(self._image) - extract.SetImplicitFunction(window) - extract.ExtractInsideOff() - extract.ExtractBoundaryCellsOff() - extract.Update() - - gf = vtk.vtkGeometryFilter() - gf.SetInputData(extract.GetOutput()) - gf.Update() - - a = Actor(gf.GetOutput()).lw(0.1).flat() - - scalars = np.array(a.scalars(0), dtype=np.float) - - if cmap: - a.pointColors(scalars, vmin=self._image.GetScalarRange()[0], cmap=cmap) - a.mapPointsToCells() - return a - - - - diff --git a/vtkplotter/picture.py b/vtkplotter/picture.py new file mode 100644 index 00000000..a8b0e9db --- /dev/null +++ b/vtkplotter/picture.py @@ -0,0 +1,106 @@ +from __future__ import division, print_function + +import numpy as np +import vtk +import vtkplotter.colors as colors +import vtkplotter.docs as docs +import vtkplotter.utils as utils +from vtk.util.numpy_support import numpy_to_vtk +from vtkplotter.base import ActorBase + +__doc__ = ( + """ +Submodule extending the ``vtkImageActor`` object functionality. +""" + + docs._defs +) + +__all__ = ["Picture"] + + +################################################# +class Picture(vtk.vtkImageActor, ActorBase): + """ + Derived class of ``vtkImageActor``. Used to represent 2D pictures. + Can be instantiated with a path file name or with a numpy array. + + |rotateImage| |rotateImage.py|_ + """ + def __init__(self, obj=None): + vtk.vtkImageActor.__init__(self) + ActorBase.__init__(self) + + if utils.isSequence(obj) and len(obj): + iac = vtk.vtkImageAppendComponents() + for i in range(3): + #arr = np.flip(np.flip(array[:,:,i], 0), 0).ravel() + arr = np.flip(obj[:,:,i], 0).ravel() + varb = numpy_to_vtk(arr, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR) + imgb = vtk.vtkImageData() + imgb.SetDimensions(obj.shape[1], obj.shape[0], 1) + imgb.GetPointData().SetScalars(varb) + iac.AddInputData(0, imgb) + iac.Update() + self.SetInputData(iac.GetOutput()) + #self.mirror() + + elif isinstance(obj, str): + if ".png" in obj: + picr = vtk.vtkPNGReader() + elif ".jpg" in obj or ".jpeg" in obj: + picr = vtk.vtkJPEGReader() + elif ".bmp" in obj: + picr = vtk.vtkBMPReader() + picr.SetFileName(obj) + picr.Update() + self.SetInputData(picr.GetOutput()) + + + def alpha(self, a=None): + """Set/get picture's transparency.""" + if a is not None: + self.GetProperty().SetOpacity(a) + return self + else: + return self.GetProperty().GetOpacity() + + def crop(self, top=None, bottom=None, right=None, left=None): + """Crop picture. + + :param float top: fraction to crop from the top margin + :param float bottom: fraction to crop from the bottom margin + :param float left: fraction to crop from the left margin + :param float right: fraction to crop from the right margin + """ + extractVOI = vtk.vtkExtractVOI() + extractVOI.SetInputData(self.GetInput()) + extractVOI.IncludeBoundaryOn() + + d = self.GetInput().GetDimensions() + bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 + if left is not None: bx0 = int((d[0]-1)*left) + if right is not None: bx1 = int((d[0]-1)*(1-right)) + if bottom is not None: by0 = int((d[1]-1)*bottom) + if top is not None: by1 = int((d[1]-1)*(1-top)) + extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) + extractVOI.Update() + img = extractVOI.GetOutput() + self.GetMapper().SetInputData(img) + self.GetMapper().Modified() + return self + + def mirror(self, axis="x"): + """Mirror picture along x or y axis.""" + ff = vtk.vtkImageFlip() + ff.SetInputData(self.inputdata()) + if axis.lower() == "x": + ff.SetFilteredAxis(0) + elif axis.lower() == "y": + ff.SetFilteredAxis(1) + else: + colors.printc("~times Error in mirror(): mirror must be set to x or y.", c=1) + raise RuntimeError() + ff.Update() + self.GetMapper().SetInputData(ff.GetOutput()) + self.GetMapper().Modified() + return self diff --git a/vtkplotter/plot2d.py b/vtkplotter/plot2d.py index 806fb81e..e990f91a 100644 --- a/vtkplotter/plot2d.py +++ b/vtkplotter/plot2d.py @@ -8,8 +8,8 @@ import vtkplotter.utils as utils import vtkplotter.colors as colors import vtkplotter.shapes as shapes -from vtkplotter.actors import Actor, Assembly, merge - +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh, merge __doc__ = ( """ @@ -364,7 +364,7 @@ def histogram( ): """ Build a histogram from a list of values in n bins. - The resulting object is a 2D actor. + The resulting object is an ``Assembly``. :param int bins: number of bins. :param list vrange: restrict the range of the histogram. @@ -490,7 +490,7 @@ def hexHistogram( values = np.append(values, zs, axis=1) pointsPolydata.GetPoints().SetData(numpy_to_vtk(values, deep=True)) - cloud = Actor(pointsPolydata) + cloud = Mesh(pointsPolydata) col = None if c is not None: @@ -527,7 +527,7 @@ def hexHistogram( tf.Update() if c is None: col = i - h = Actor(tf.GetOutput(), c=col, alpha=alpha).flat() + h = Mesh(tf.GetOutput(), c=col, alpha=alpha).flat() h.GetProperty().SetSpecular(0) h.GetProperty().SetDiffuse(1) h.PickableOff() @@ -882,10 +882,10 @@ def polarPlot( filling = None if fill: faces = [] - coords = [[0, 0, 0]] + lines.coordinates().tolist() + coords = [[0, 0, 0]] + lines.points().tolist() for i in range(1, lines.N()): faces.append([0, i, i + 1]) - filling = Actor([coords, faces]).c(c).alpha(alpha) + filling = Mesh([coords, faces]).c(c).alpha(alpha) back = None if showDisc: @@ -1014,11 +1014,11 @@ def fxy( return None if zlimits[0]: - tmpact1 = Actor(poly) + tmpact1 = Mesh(poly) a = tmpact1.cutWithPlane((0, 0, zlimits[0]), (0, 0, 1)) poly = a.polydata() if zlimits[1]: - tmpact2 = Actor(poly) + tmpact2 = Mesh(poly) a = tmpact2.cutWithPlane((0, 0, zlimits[1]), (0, 0, -1)) poly = a.polydata() @@ -1028,16 +1028,16 @@ def fxy( elev.Update() poly = elev.GetOutput() - actor = Actor(poly, c, alpha).computeNormals().lighting("plastic") + mesh = Mesh(poly, c, alpha).computeNormals().lighting("plastic") if c is None: - actor.scalars("Elevation") + mesh.getPointArray("Elevation") if bc: - actor.bc(bc) + mesh.bc(bc) - actor.texture(texture) + mesh.texture(texture) - acts = [actor] + acts = [mesh] if zlevels: elevation = vtk.vtkElevationFilter() elevation.SetInputData(poly) @@ -1052,11 +1052,11 @@ def fxy( bcf.GenerateValues(zlevels, elevation.GetScalarRange()) bcf.Update() zpoly = bcf.GetContourEdgesOutput() - zbandsact = Actor(zpoly, "k", alpha).lw(0.5) + zbandsact = Mesh(zpoly, "k", alpha).lw(0.5) acts.append(zbandsact) if showNan and len(todel): - bb = actor.GetBounds() + bb = mesh.GetBounds() if bb[4] <= 0 and bb[5] >= 0: zm = 0.0 else: @@ -1069,4 +1069,4 @@ def fxy( if len(acts) > 1: return Assembly(acts) else: - return actor + return mesh diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py index c8c722f4..0d31be0a 100644 --- a/vtkplotter/plotter.py +++ b/vtkplotter/plotter.py @@ -8,7 +8,10 @@ import vtkplotter.vtkio as vtkio import vtkplotter.utils as utils import vtkplotter.colors as colors -from vtkplotter.actors import Actor, Assembly, Volume, Picture +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh +from vtkplotter.picture import Picture +from vtkplotter.volume import Volume import vtkplotter.docs as docs import vtkplotter.settings as settings import vtkplotter.addons as addons @@ -825,7 +828,7 @@ def remove(self, actors, render=True): #################################################### def load(self, inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True): """ - Load Actors and Volumes from file. + Load Mesh and Volume objects from file. The output will depend on the file extension. See examples below. :param c: color in RGB format, hex, symbol or name @@ -841,15 +844,15 @@ def load(self, inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=Tr from vtkplotter import datadir, load, show - # Return an Actor + # Return an Mesh g = load(datadir+'ring.gmsh') show(g) - # Return a list of 2 Actors + # Return a list of 2 Mesh g = load([datadir+'250.vtk', datadir+'290.vtk']) show(g) - # Return a list of actors by reaading all files in a directory + # Return a list of meshes by reading all files in a directory # (if directory contains DICOM files then a Volume is returned) g = load(datadir+'timecourse1d/') show(g) @@ -859,7 +862,7 @@ def load(self, inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=Tr g.c(['y','lb','w']).alpha((0.0, 0.4, 0.9, 1)) show(g) - # Return an Actor from a SLC volume with automatic thresholding + # Return a Mesh from a SLC volume with automatic thresholding g = load(datadir+'embryo.slc', threshold=True) show(g) """ @@ -908,19 +911,23 @@ def getVolumes(self, obj=None, renderer=None): vols.append(a) return vols - def getActors(self, obj=None, renderer=None): + """Obsolete. Please use getMeshes()""" + colors.printc("getActors is obsolete, use getMeshes() instead.", box='=', c=1) + raise RuntimeError + + def getMeshes(self, obj=None, renderer=None): """ - Return an actors list (which may include Volume objects too). + Return a list of Meshes (which may include Volume objects too). If ``obj`` is: - ``None``, return actors of current renderer + ``None``, return meshes of current renderer - ``int``, return actors in given renderer number + ``int``, return meshes in given renderer number - ``vtkAssembly`` return the contained actors + ``vtkAssembly`` return the contained meshes - ``string``, return actors matching legend name + ``string``, return meshes matching legend name :param int,vtkRenderer renderer: specify which renederer to look into. """ @@ -935,7 +942,7 @@ def getActors(self, obj=None, renderer=None): if obj is None: acs = renderer.GetActors() elif obj >= len(self.renderers): - colors.printc("~timesError in getActors: non existing renderer", obj, c=1) + colors.printc("~timesError in getMeshes: non existing renderer", obj, c=1) return [] else: acs = self.renderers[obj].GetActors() @@ -973,7 +980,7 @@ def getActors(self, obj=None, renderer=None): return [obj] if self.verbose: - colors.printc("~lightning Warning in getActors: unexpected input type", obj, c=1) + colors.printc("~lightning Warning in getMeshes: unexpected input type", obj, c=1) return [] @@ -1029,7 +1036,7 @@ def addLight(self, pos, focalPoint=(0, 0, 0), deg=180, c='white', :param float intensity: intensity between 0 and 1. :param bool removeOthers: remove all other lights in the scene :param bool showsource: if `True`, will show a representation - of the source of light as an extra Actor + of the source of light as an extra Mesh .. hint:: |lights.py|_ """ @@ -1118,14 +1125,14 @@ def addButton( """ return addons.addButton(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle) - def addCutterTool(self, actor): + def addCutterTool(self, mesh): """Create handles to cut away parts of a mesh. |cutter| |cutter.py|_ """ - return addons.addCutterTool(actor) + return addons.addCutterTool(mesh) - def addIcon(self, iconActor, pos=3, size=0.08): + def addIcon(self, icon, pos=3, size=0.08): """Add an inset icon mesh into the same renderer. :param pos: icon position in the range [1-4] indicating one of the 4 corners, @@ -1134,7 +1141,7 @@ def addIcon(self, iconActor, pos=3, size=0.08): |icon| |icon.py|_ """ - return addons.addIcon(iconActor, pos, size) + return addons.addIcon(icon, pos, size) def addAxes(self, axtype=None, c=None): """Draw axes on scene. Available axes types: @@ -1367,7 +1374,7 @@ def scan(wannabeacts): scannedacts.append(Volume(a)) elif isinstance(a, vtk.vtkPolyData): - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) elif isinstance(a, vtk.vtkBillboardTextActor3D): scannedacts.append(a) @@ -1377,17 +1384,17 @@ def scan(wannabeacts): scannedacts.append(out) elif isinstance(a, vtk.vtkUnstructuredGrid): - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) elif isinstance(a, vtk.vtkStructuredGrid): - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) elif isinstance(a, vtk.vtkRectilinearGrid): - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) elif isinstance(a, vtk.vtkMultiBlockDataSet): for i in range(a.GetNumberOfBlocks()): b = a.GetBlock(i) if isinstance(b, vtk.vtkPolyData): - scannedacts.append(Actor(b)) + scannedacts.append(Mesh(b)) elif isinstance(b, vtk.vtkImageData): scannedacts.append(Volume(b)) @@ -1401,10 +1408,10 @@ def scan(wannabeacts): scannedacts.append(trimesh2vtk(a)) elif "meshio" in str(type(a)): - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) elif hasattr(a, "GetOutput"): # passing vtk algorithm - scannedacts.append(Actor(a)) + scannedacts.append(Mesh(a)) else: colors.printc("~!? Cannot understand input in show():", type(a), c=1) @@ -1550,14 +1557,14 @@ def scan(wannabeacts): # remove the ones that are not in actors2show (and their scalarbar if any) - for ia in self.getActors(at) + self.getVolumes(at): + for ia in self.getMeshes(at) + self.getVolumes(at): if ia not in actors2show: self.renderer.RemoveActor(ia) if hasattr(ia, 'scalarbar') and ia.scalarbar: if isinstance(ia.scalarbar, vtk.vtkActor): self.renderer.RemoveActor(ia.scalarbar) elif isinstance(ia.scalarbar, Assembly): - for a in ia.scalarbar.getActors(): + for a in ia.scalarbar.getMeshes(): self.renderer.RemoveActor(a) if hasattr(ia, 'renderedAt'): ia.renderedAt.discard(at) @@ -1693,7 +1700,7 @@ def scan(wannabeacts): return self - def showInset(self, *actors, **options): #pos=3, size=0.1, c='r', draggable=True): + def showInset(self, *actors, **options): """Add a draggable inset space into a renderer. :param pos: icon position in the range [1-4] indicating one of the 4 corners, @@ -1753,7 +1760,7 @@ def clear(self, actors=()): self.remove(a) settings.collectable_actors = [] self.actors = [] - for a in self.getActors(): + for a in self.getMeshes(): self.renderer.RemoveActor(a) for a in self.getVolumes(): self.renderer.RemoveVolume(a) @@ -1906,14 +1913,14 @@ def _keypress(self, iren, event): sys.exit(0) elif key == "m": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): self.clickedActor.GetProperty().SetOpacity(0.02) bfp = self.clickedActor.GetBackfaceProperty() if bfp and hasattr(self.clickedActor, "_bfprop"): self.clickedActor._bfprop = bfp # save it self.clickedActor.SetBackfaceProperty(None) else: - for a in self.getActors(): + for a in self.getMeshes(): if a.GetPickable(): a.GetProperty().SetOpacity(0.02) bfp = a.GetBackfaceProperty() @@ -1922,7 +1929,7 @@ def _keypress(self, iren, event): a.SetBackfaceProperty(None) elif key == "comma": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): ap = self.clickedActor.GetProperty() aal = max([ap.GetOpacity() * 0.75, 0.01]) ap.SetOpacity(aal) @@ -1931,7 +1938,7 @@ def _keypress(self, iren, event): self.clickedActor._bfprop = bfp self.clickedActor.SetBackfaceProperty(None) else: - for a in self.getActors(): + for a in self.getMeshes(): if a.GetPickable(): ap = a.GetProperty() aal = max([ap.GetOpacity() * 0.75, 0.01]) @@ -1942,7 +1949,7 @@ def _keypress(self, iren, event): a.SetBackfaceProperty(None) elif key == "period": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): ap = self.clickedActor.GetProperty() aal = min([ap.GetOpacity() * 1.25, 1.0]) ap.SetOpacity(aal) @@ -1951,7 +1958,7 @@ def _keypress(self, iren, event): # put back self.clickedActor.SetBackfaceProperty(self.clickedActor._bfprop) else: - for a in self.getActors(): + for a in self.getMeshes(): if a.GetPickable(): ap = a.GetProperty() aal = min([ap.GetOpacity() * 1.25, 1.0]) @@ -1960,22 +1967,22 @@ def _keypress(self, iren, event): a.SetBackfaceProperty(a._bfprop) elif key == "slash": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): self.clickedActor.GetProperty().SetOpacity(1) if hasattr(self.clickedActor, "_bfprop") and self.clickedActor._bfprop: self.clickedActor.SetBackfaceProperty(self.clickedActor._bfprop) else: - for a in self.getActors(): + for a in self.getMeshes(): if a.GetPickable(): a.GetProperty().SetOpacity(1) if hasattr(a, "_bfprop") and a._bfprop: a.clickedActor.SetBackfaceProperty(a._bfprop) elif key == "P": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): acts = [self.clickedActor] else: - acts = self.getActors() + acts = self.getMeshes() for ia in acts: if ia.GetPickable(): try: @@ -1987,10 +1994,10 @@ def _keypress(self, iren, event): pass elif key == "p": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): acts = [self.clickedActor] else: - acts = self.getActors() + acts = self.getMeshes() for ia in acts: if ia.GetPickable(): try: @@ -2001,10 +2008,10 @@ def _keypress(self, iren, event): pass elif key == "w": - if self.clickedActor and self.clickedActor in self.getActors(): + if self.clickedActor and self.clickedActor in self.getMeshes(): self.clickedActor.GetProperty().SetRepresentationToWireframe() else: - for a in self.getActors(): + for a in self.getMeshes(): if a and a.GetPickable(): if a.GetProperty().GetRepresentation() == 1: # toggle a.GetProperty().SetRepresentationToSurface() @@ -2074,10 +2081,10 @@ def _keypress(self, iren, event): return if key == "s": - if self.clickedActor and self.clickedActor in self.getActors(): + if self.clickedActor and self.clickedActor in self.getMeshes(): self.clickedActor.GetProperty().SetRepresentationToSurface() else: - for a in self.getActors(): + for a in self.getMeshes(): if a and a.GetPickable(): a.GetProperty().SetRepresentationToSurface() @@ -2093,7 +2100,7 @@ def _keypress(self, iren, event): self.clickedActor.GetMapper().ScalarVisibilityOff() self.clickedActor.GetProperty().SetColor(colors.colors1[(self.icol) % 10]) else: - for i, ia in enumerate(self.getActors()): + for i, ia in enumerate(self.getMeshes()): if not ia.GetPickable(): continue ia.GetProperty().SetColor(colors.colors1[(i + self.icol) % 10]) @@ -2106,7 +2113,7 @@ def _keypress(self, iren, event): self.clickedActor.GetMapper().ScalarVisibilityOff() self.clickedActor.GetProperty().SetColor(colors.colors2[(self.icol) % 10]) else: - for i, ia in enumerate(self.getActors()): + for i, ia in enumerate(self.getMeshes()): if not ia.GetPickable(): continue ia.GetProperty().SetColor(colors.colors2[(i + self.icol) % 10]) @@ -2115,7 +2122,7 @@ def _keypress(self, iren, event): elif key == "3": c = colors.getColor("gold") - acs = self.getActors() + acs = self.getMeshes() if len(acs) == 0: return alpha = 1.0 / len(acs) for ia in acs: @@ -2127,16 +2134,15 @@ def _keypress(self, iren, event): addons.addLegend() elif key == "4": - for ia in self.getActors(): + for ia in self.getMeshes(): if not ia.GetPickable(): continue - if isinstance(ia, Actor): - iascals = ia.scalars() + if isinstance(ia, Mesh): + iascals = ia.getPointArray() if len(iascals): stype, sname = iascals[ia._scals_idx] if sname and "Normals" not in sname.lower(): # exclude normals - ia.scalars( ia._scals_idx ) - ia.GetMapper().ScalarVisibilityOn() + ia.getPointArray( ia._scals_idx ) colors.printc("..active scalars set to:", sname, "\ttype:", stype, c='g', bold=0) ia._scals_idx += 1 @@ -2203,10 +2209,10 @@ def _keypress(self, iren, event): self.window.Render() elif key == "l": - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): acts = [self.clickedActor] else: - acts = self.getActors() + acts = self.getMeshes() for ia in acts: if not ia.GetPickable(): continue @@ -2219,10 +2225,10 @@ def _keypress(self, iren, event): pass elif key == "k": # lightings - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): acts = [self.clickedActor] else: - acts = self.getActors() + acts = self.getMeshes() shds = ('default', 'metallic', 'plastic', @@ -2239,10 +2245,10 @@ def _keypress(self, iren, event): pass elif key == "K": # shading - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): acts = [self.clickedActor] else: - acts = self.getActors() + acts = self.getMeshes() for ia in acts: if ia.GetPickable(): ia.computeNormals() @@ -2255,7 +2261,7 @@ def _keypress(self, iren, event): elif key == "n": # show normals to an actor from vtkplotter.analysis import normalLines - if self.clickedActor in self.getActors(): + if self.clickedActor in self.getMeshes(): if self.clickedActor.GetPickable(): self.renderer.AddActor(normalLines(self.clickedActor)) iren.Render() @@ -2265,7 +2271,7 @@ def _keypress(self, iren, event): elif key == "x": if self.justremoved is None: - if self.clickedActor in self.getActors() \ + if self.clickedActor in self.getMeshes() \ or isinstance(self.clickedActor, vtk.vtkAssembly): self.justremoved = self.clickedActor self.renderer.RemoveActor(self.clickedActor) @@ -2290,7 +2296,7 @@ def _keypress(self, iren, event): if isinstance(self.clickedActor, vtk.vtkActor): confilter.SetInputData(self.clickedActor.GetMapper().GetInput()) elif isinstance(self.clickedActor, vtk.vtkAssembly): - act = self.clickedActor.getActors()[0] + act = self.clickedActor.getMeshes()[0] confilter.SetInputData(act.GetMapper().GetInput()) else: confilter.SetInputData(self.clickedActor.polydata(True)) diff --git a/vtkplotter/settings.py b/vtkplotter/settings.py index f3505988..80cc1c08 100644 --- a/vtkplotter/settings.py +++ b/vtkplotter/settings.py @@ -1,5 +1,5 @@ """ -Global settings. +General settings. .. code-block:: python @@ -330,6 +330,3 @@ def _init(): warnings.simplefilter(action="ignore", category=FutureWarning) embedWindow() - - - diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py index b6f6db87..7191a675 100644 --- a/vtkplotter/shapes.py +++ b/vtkplotter/shapes.py @@ -5,12 +5,11 @@ from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy import vtkplotter.utils as utils from vtkplotter.colors import printc, getColor, colorMap, _mapscales -from vtkplotter.actors import Actor, Assembly +from vtkplotter.mesh import Mesh +from vtkplotter.picture import Picture import vtkplotter.docs as docs -__doc__ = ( - """ -Submodule to generate basic geometric shapes. +__doc__ = ("""Submodule to generate basic geometric shapes. """ + docs._defs ) @@ -69,56 +68,55 @@ def Marker(symbol, c='lb', alpha=1, s=0.1, filled=True): symbol = symbs[s] if symbol == '.': - actor = Polygon(nsides=24, r=s*0.75) + mesh = Polygon(nsides=24, r=s*0.75) elif symbol == 'p': - actor = Polygon(nsides=5, r=s) + mesh = Polygon(nsides=5, r=s) elif symbol == '*': - actor = Star(r1=0.7*s, r2=s, line=not filled) + mesh = Star(r1=0.7*s, r2=s, line=not filled) elif symbol == 'h': - actor = Polygon(nsides=6, r=s) + mesh = Polygon(nsides=6, r=s) elif symbol == 'D': - actor = Polygon(nsides=4, r=s) + mesh = Polygon(nsides=4, r=s) elif symbol == 'd': - actor = Polygon(nsides=4, r=s*1.1).scale([0.5,1,1]) + mesh = Polygon(nsides=4, r=s*1.1).scale([0.5,1,1]) elif symbol == 'o': - actor = Polygon(nsides=24, r=s*0.75) + mesh = Polygon(nsides=24, r=s*0.75) elif symbol == 'v': - actor = Polygon(nsides=3, r=s).rotateZ(180) + mesh = Polygon(nsides=3, r=s).rotateZ(180) elif symbol == '^': - actor = Polygon(nsides=3, r=s) + mesh = Polygon(nsides=3, r=s) elif symbol == '>': - actor = Polygon(nsides=3, r=s).rotateZ(-90) + mesh = Polygon(nsides=3, r=s).rotateZ(-90) elif symbol == '<': - actor = Polygon(nsides=3, r=s).rotateZ(90) + mesh = Polygon(nsides=3, r=s).rotateZ(90) elif symbol == 's': - actor = Polygon(nsides=4, r=s).rotateZ(45) + mesh = Polygon(nsides=4, r=s).rotateZ(45) elif symbol == 'x': - actor = Text('+', pos=(0,0,0), s=s*2.6, justify='center', depth=0) - actor.rotateZ(45) + mesh = Text('+', pos=(0,0,0), s=s*2.6, justify='center', depth=0) + mesh.rotateZ(45) elif symbol == 'a': - actor = Text('*', pos=(0,0,0), s=s*3, justify='center', depth=0) + mesh = Text('*', pos=(0,0,0), s=s*3, justify='center', depth=0) else: - actor = Text(symbol, pos=(0,0,0), s=s*2, justify='center', depth=0) - actor.flat().lighting('ambient').wireframe(not filled).c(c).alpha(alpha) - actor.name = "Marker" - return actor + mesh = Text(symbol, pos=(0,0,0), s=s*2, justify='center', depth=0) + mesh.flat().lighting('ambient').wireframe(not filled).c(c).alpha(alpha) + mesh.name = "Marker" + return mesh def Point(pos=(0, 0, 0), r=12, c="red", alpha=1): - """Create a simple point actor.""" + """Create a simple point.""" if len(pos) == 2: pos = (pos[0], pos[1], 0) if isinstance(pos, vtk.vtkActor): pos = pos.GetPosition() - actor = Points([pos], r, c, alpha) - actor.name = "Point" - return actor - + mesh = Points([pos], r, c, alpha) + mesh.name = "Point" + return mesh -def Points(plist, r=5, c="gold", alpha=1): +class Points(Mesh): """ - Build a point ``Actor`` for a list of 2D/3D points. - Both shapes (N, 3) or (3, N) are accepted as input - if N>3. + Build a ``Mesh`` made of only vertex points for a list of 2D/3D points. + Both shapes (N, 3) or (3, N) are accepted as input, if N>3. For very large point clouds a list of colors and alpha can be assigned to each point in the form `c=[(R,G,B,A), ... ]` where `0 <= R < 256, ... 0 <= A < 256`. @@ -132,106 +130,110 @@ def Points(plist, r=5, c="gold", alpha=1): |lorenz| """ - ################ interpret the input format: - if isinstance(plist, Actor): - plist = plist.coordinates() - n = len(plist) - if n == 0: - return None - elif n == 3: # assume plist is in the format [all_x, all_y, all_z] - if utils.isSequence(plist[0]) and len(plist[0]) > 3: - plist = np.stack((plist[0], plist[1], plist[2]), axis=1) - elif n == 2: # assume plist is in the format [all_x, all_y, 0] - if utils.isSequence(plist[0]) and len(plist[0]) > 3: - plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1) - - if len(plist[0]) == 2: #make it 3d - plist = np.c_[np.array(plist), np.zeros(len(plist))] - ################ - - if ((utils.isSequence(c) and (len(c)>3 or (utils.isSequence(c[0]) and len(c[0])==4))) - or utils.isSequence(alpha) ): - actor = _PointsColors(plist, r, c, alpha) - else: - n = len(plist) # refresh - sourcePoints = vtk.vtkPoints() - sourceVertices = vtk.vtkCellArray() - is3d = len(plist[0]) > 2 - if is3d: # its faster - for pt in plist: - aid = sourcePoints.InsertNextPoint(pt) - sourceVertices.InsertNextCell(1) - sourceVertices.InsertCellPoint(aid) - else: - for pt in plist: - aid = sourcePoints.InsertNextPoint(pt[0], pt[1], 0) - sourceVertices.InsertNextCell(1) - sourceVertices.InsertCellPoint(aid) + def __init__(self, plist, r=5, c="gold", alpha=1): + + ################ interpret user input format: + if isinstance(plist, Mesh): + plist = plist.points() + + n = len(plist) + + if n == 0: + return None + elif n == 3: # assume plist is in the format [all_x, all_y, all_z] + if utils.isSequence(plist[0]) and len(plist[0]) > 3: + plist = np.stack((plist[0], plist[1], plist[2]), axis=1) + elif n == 2: # assume plist is in the format [all_x, all_y, 0] + if utils.isSequence(plist[0]) and len(plist[0]) > 3: + plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1) + + if len(plist[0]) == 2: # make it 3d + plist = np.c_[np.array(plist), np.zeros(len(plist))] + ################ + + if ((utils.isSequence(c) and (len(c)>3 or (utils.isSequence(c[0]) and len(c[0])==4))) + or utils.isSequence(alpha) ): + + cols = c + + n = len(plist) + if n != len(cols): + printc("~times mismatch in Points() colors", n, len(cols), c=1) + raise RuntimeError() + src = vtk.vtkPointSource() + src.SetNumberOfPoints(n) + src.Update() + vgf = vtk.vtkVertexGlyphFilter() + vgf.SetInputData(src.GetOutput()) + vgf.Update() + pd = vgf.GetOutput() + pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True)) + + ucols = vtk.vtkUnsignedCharArray() + ucols.SetNumberOfComponents(4) + ucols.SetName("pointsRGBA") + if utils.isSequence(alpha): + if len(alpha) != n: + printc("~times mismatch in Points() alphas", n, len(alpha), c=1) + raise RuntimeError() + alphas = alpha + alpha = 1 + else: + alphas = (alpha,) * n + + if utils.isSequence(cols): + c = None + if len(cols[0]) == 4: + for i in range(n): # FAST + rc,gc,bc,ac = cols[i] + ucols.InsertNextTuple4(rc, gc, bc, ac) + else: + for i in range(n): # SLOW + rc,gc,bc = getColor(cols[i]) + ucols.InsertNextTuple4(rc*255, gc*255, bc*255, alphas[i]*255) + else: + c = cols - pd = vtk.vtkPolyData() - pd.SetPoints(sourcePoints) - pd.SetVerts(sourceVertices) + pd.GetPointData().SetScalars(ucols) + Mesh.__init__(self, pd, c, alpha) + self.flat().pointSize(r) + self.mapper().ScalarVisibilityOn() - if n == 1: # passing just one point - pd.GetPoints().SetPoint(0, [0, 0, 0]) else: - pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True)) - actor = Actor(pd, c, alpha) - actor.GetProperty().SetPointSize(r) - if n == 1: - actor.SetPosition(plist[0]) - - settings.collectable_actors.append(actor) - actor.name = "Points" - return actor - -def _PointsColors(plist, r, cols, alpha): - n = len(plist) - if n != len(cols): - printc("~times mismatch in Points() colors", n, len(cols), c=1) - raise RuntimeError() - src = vtk.vtkPointSource() - src.SetNumberOfPoints(n) - src.Update() - vgf = vtk.vtkVertexGlyphFilter() - vgf.SetInputData(src.GetOutput()) - vgf.Update() - pd = vgf.GetOutput() - pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True)) - - ucols = vtk.vtkUnsignedCharArray() - ucols.SetNumberOfComponents(4) - ucols.SetName("pointsRGBA") - if utils.isSequence(alpha): - if len(alpha) != n: - printc("~times mismatch in Points() alphas", n, len(alpha), c=1) - raise RuntimeError() - alphas = alpha - alpha = 1 - else: - alphas = (alpha,) * n - - if utils.isSequence(cols): - c = None - if len(cols[0]) == 4: - for i in range(n): # FAST - rc,gc,bc,ac = cols[i] - ucols.InsertNextTuple4(rc, gc, bc, ac) - else: - for i in range(n): # SLOW - rc,gc,bc = getColor(cols[i]) - ucols.InsertNextTuple4(rc*255, gc*255, bc*255, alphas[i]*255) - else: - c = cols - pd.GetPointData().SetScalars(ucols) - actor = Actor(pd, c, alpha).flat().pointSize(r) - actor.mapper().ScalarVisibilityOn() - return actor + n = len(plist) # refresh + sourcePoints = vtk.vtkPoints() + sourceVertices = vtk.vtkCellArray() + is3d = len(plist[0]) > 2 + if is3d: # its faster + for pt in plist: + aid = sourcePoints.InsertNextPoint(pt) + sourceVertices.InsertNextCell(1) + sourceVertices.InsertCellPoint(aid) + else: + for pt in plist: + aid = sourcePoints.InsertNextPoint(pt[0], pt[1], 0) + sourceVertices.InsertNextCell(1) + sourceVertices.InsertCellPoint(aid) + + pd = vtk.vtkPolyData() + pd.SetPoints(sourcePoints) + pd.SetVerts(sourceVertices) + + if n == 1: # passing just one point + pd.GetPoints().SetPoint(0, [0, 0, 0]) + else: + pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True)) + Mesh.__init__(self, pd, c, alpha) + self.GetProperty().SetPointSize(r) + if n == 1: + self.SetPosition(plist[0]) + + settings.collectable_actors.append(self) + self.name = "Points" -def Glyph(actor, glyphObj, orientationArray=None, - scaleByVectorSize=False, tol=0, c=None, alpha=1): +class Glyph(Mesh): """ At each vertex of a mesh, another mesh - a `'glyph'` - is shown with various orientation options and coloring. @@ -250,90 +252,90 @@ def Glyph(actor, glyphObj, orientationArray=None, |glyphs| |glyphs_arrows| """ - cmap = None - # user passing a color map to map orientationArray sizes - if c in list(_mapscales.cmap_d.keys()): - cmap = c - c = None - - if tol: - actor = actor.clone().clean(tol) - poly = actor.polydata() - - # user is passing an array of point colors - if utils.isSequence(c) and len(c) > 3: - ucols = vtk.vtkUnsignedCharArray() - ucols.SetNumberOfComponents(3) - ucols.SetName("glyphRGB") - for col in c: - cl = getColor(col) - ucols.InsertNextTuple3(cl[0]*255, cl[1]*255, cl[2]*255) - poly.GetPointData().SetScalars(ucols) - c = None - - if isinstance(glyphObj, Actor): - glyphObj = glyphObj.clean().polydata() - - gly = vtk.vtkGlyph3D() - gly.SetInputData(poly) - gly.SetSourceData(glyphObj) - gly.SetColorModeToColorByScalar() - gly.SetRange(actor.mapper().GetScalarRange()) - - if orientationArray is not None: - gly.OrientOn() - gly.SetScaleFactor(1) - - if scaleByVectorSize: - gly.SetScaleModeToScaleByVector() - else: - gly.SetScaleModeToDataScalingOff() - if isinstance(orientationArray, str): - if orientationArray.lower() == "normals": - gly.SetVectorModeToUseNormal() - else: # passing a name - gly.SetInputArrayToProcess(0, 0, 0, 0, orientationArray) + def __init__(self, mesh, glyphObj, orientationArray=None, + scaleByVectorSize=False, tol=0, c=None, alpha=1): + cmap = None + # user passing a color map to map orientationArray sizes + if c in list(_mapscales.cmap_d.keys()): + cmap = c + c = None + + if tol: + mesh = mesh.clone().clean(tol) + poly = mesh.polydata() + + # user is passing an array of point colors + if utils.isSequence(c) and len(c) > 3: + ucols = vtk.vtkUnsignedCharArray() + ucols.SetNumberOfComponents(3) + ucols.SetName("glyphRGB") + for col in c: + cl = getColor(col) + ucols.InsertNextTuple3(cl[0]*255, cl[1]*255, cl[2]*255) + poly.GetPointData().SetScalars(ucols) + c = None + + if isinstance(glyphObj, Mesh): + glyphObj = glyphObj.clean().polydata() + + gly = vtk.vtkGlyph3D() + gly.SetInputData(poly) + gly.SetSourceData(glyphObj) + gly.SetColorModeToColorByScalar() + gly.SetRange(mesh.mapper().GetScalarRange()) + + if orientationArray is not None: + gly.OrientOn() + gly.SetScaleFactor(1) + + if scaleByVectorSize: + gly.SetScaleModeToScaleByVector() + else: + gly.SetScaleModeToDataScalingOff() + + if isinstance(orientationArray, str): + if orientationArray.lower() == "normals": + gly.SetVectorModeToUseNormal() + else: # passing a name + gly.SetInputArrayToProcess(0, 0, 0, 0, orientationArray) + gly.SetVectorModeToUseVector() + elif isinstance(orientationArray, vtk.vtkAbstractArray): + poly.GetPointData().AddArray(orientationArray) + poly.GetPointData().SetActiveVectors("glyph_vectors") + gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") gly.SetVectorModeToUseVector() - elif isinstance(orientationArray, vtk.vtkAbstractArray): - poly.GetPointData().AddArray(orientationArray) - poly.GetPointData().SetActiveVectors("glyph_vectors") - gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") - gly.SetVectorModeToUseVector() - elif utils.isSequence(orientationArray) and not tol: # passing a list - actor.addPointVectors(orientationArray, "glyph_vectors") - gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") - - if cmap: - gly.SetColorModeToColorByVector() - else: - gly.SetColorModeToColorByScalar() + elif utils.isSequence(orientationArray) and not tol: # passing a list + mesh.addPointVectors(orientationArray, "glyph_vectors") + gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") - gly.Update() - - gactor = Actor(gly.GetOutput(), c, alpha).flat() - - if cmap: - lut = vtk.vtkLookupTable() - lut.SetNumberOfTableValues(512) - lut.Build() - for i in range(512): - r, g, b = colorMap(i, cmap, 0, 512) - lut.SetTableValue(i, r, g, b, 1) - gactor.mapper().SetLookupTable(lut) - gactor.mapper().ScalarVisibilityOn() - gactor.mapper().SetScalarModeToUsePointData() - rng = gly.GetOutput().GetPointData().GetScalars().GetRange() - gactor.mapper().SetScalarRange(rng[0], rng[1]) + if cmap: + gly.SetColorModeToColorByVector() + else: + gly.SetColorModeToColorByScalar() - settings.collectable_actors.append(gactor) - gactor.name = "Glyph" - return gactor + gly.Update() + Mesh.__init__(self, gly.GetOutput(), c, alpha) + self.flat() -def Tensors(domain, source='ellipsoid', useEigenValues=True, isSymmetric=True, - threeAxes=False, scale=1, maxScale=None, length=None, - c=None, alpha=1): + if cmap: + lut = vtk.vtkLookupTable() + lut.SetNumberOfTableValues(512) + lut.Build() + for i in range(512): + r, g, b = colorMap(i, cmap, 0, 512) + lut.SetTableValue(i, r, g, b, 1) + self.mapper().SetLookupTable(lut) + self.mapper().ScalarVisibilityOn() + self.mapper().SetScalarModeToUsePointData() + rng = gly.GetOutput().GetPointData().GetScalars().GetRange() + self.mapper().SetScalarRange(rng[0], rng[1]) + + settings.collectable_actors.append(self) + self.name = "Glyph" + +class Tensors(Mesh): """Geometric representation of tensors defined on a domain or set of points. Tensors can be scaled and/or rotated according to the source at eache input point. Scaling and rotation is controlled by the eigenvalues/eigenvectors of the symmetrical part @@ -346,7 +348,7 @@ def Tensors(domain, source='ellipsoid', useEigenValues=True, isSymmetric=True, which is 1/2*(T+T.transposed()). :param str source: preset type of source shape - ['ellipsoid', 'cylinder', 'cube' or any specified ``Actor``] + ['ellipsoid', 'cylinder', 'cube' or any specified ``Mesh``] :param bool useEigenValues: color source glyph using the eigenvalues or by scalars. @@ -368,59 +370,62 @@ def Tensors(domain, source='ellipsoid', useEigenValues=True, isSymmetric=True, |tensors| |tensors.py|_ |tensor_grid.py|_ """ - if isinstance(source, Actor): - src = source.normalize().polydata(False) - else: - if 'ellip' in source: - src = vtk.vtkSphereSource() - src.SetPhiResolution(24) - src.SetThetaResolution(12) - elif 'cyl' in source: - src = vtk.vtkCylinderSource() - src.SetResolution(48) - src.CappingOn() - elif source == 'cube': - src = vtk.vtkCubeSource() - src.Update() - tg = vtk.vtkTensorGlyph() - if isinstance(domain, vtk.vtkPolyData): - tg.SetInputData(domain) - else: - tg.SetInputData(domain.GetMapper().GetInput()) - tg.SetSourceData(src.GetOutput()) + def __init__(self, domain, source='ellipsoid', useEigenValues=True, isSymmetric=True, + threeAxes=False, scale=1, maxScale=None, length=None, + c=None, alpha=1): + if isinstance(source, Mesh): + src = source.normalize().polydata(False) + else: + if 'ellip' in source: + src = vtk.vtkSphereSource() + src.SetPhiResolution(24) + src.SetThetaResolution(12) + elif 'cyl' in source: + src = vtk.vtkCylinderSource() + src.SetResolution(48) + src.CappingOn() + elif source == 'cube': + src = vtk.vtkCubeSource() + src.Update() + + tg = vtk.vtkTensorGlyph() + if isinstance(domain, vtk.vtkPolyData): + tg.SetInputData(domain) + else: + tg.SetInputData(domain.GetMapper().GetInput()) + tg.SetSourceData(src.GetOutput()) - if c is None: - tg.ColorGlyphsOn() - else: - tg.ColorGlyphsOff() + if c is None: + tg.ColorGlyphsOn() + else: + tg.ColorGlyphsOff() - tg.SetSymmetric(int(isSymmetric)) + tg.SetSymmetric(int(isSymmetric)) - if length is not None: - tg.SetLength(length) - if useEigenValues: - tg.ExtractEigenvaluesOn() - tg.SetColorModeToEigenvalues() - else: - tg.SetColorModeToScalars() - tg.SetThreeGlyphs(threeAxes) - tg.ScalingOn() - tg.SetScaleFactor(scale) - if maxScale is None: - tg.ClampScalingOn() - maxScale = scale*10 - tg.SetMaxScaleFactor(maxScale) - tg.Update() - tgn = vtk.vtkPolyDataNormals() - tgn.SetInputData(tg.GetOutput()) - tgn.Update() - actor = Actor(tgn.GetOutput(), c, alpha) - actor.name = "Tensors" - return actor - - -def Line(p0, p1=None, c="r", alpha=1, lw=1, dotted=False, res=None): + if length is not None: + tg.SetLength(length) + if useEigenValues: + tg.ExtractEigenvaluesOn() + tg.SetColorModeToEigenvalues() + else: + tg.SetColorModeToScalars() + tg.SetThreeGlyphs(threeAxes) + tg.ScalingOn() + tg.SetScaleFactor(scale) + if maxScale is None: + tg.ClampScalingOn() + maxScale = scale*10 + tg.SetMaxScaleFactor(maxScale) + tg.Update() + tgn = vtk.vtkPolyDataNormals() + tgn.SetInputData(tg.GetOutput()) + tgn.Update() + Mesh.__init__(self, tgn.GetOutput(), c, alpha) + self.name = "Tensors" + + +class Line(Mesh): """ Build the line segment between points `p0` and `p1`. If `p0` is a list of points returns the line connecting them. @@ -433,53 +438,55 @@ def Line(p0, p1=None, c="r", alpha=1, lw=1, dotted=False, res=None): :param bool dotted: draw a dotted line :param int res: number of intermediate points in the segment """ - if isinstance(p0, vtk.vtkActor): p0 = p0.GetPosition() - if isinstance(p1, vtk.vtkActor): p1 = p1.GetPosition() - # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: - if len(p0) > 3: - if not utils.isSequence(p0[0]) and not utils.isSequence(p1[0]) and len(p0)==len(p1): - # assume input is 2D xlist, ylist - p0 = np.stack((p0, p1), axis=1) - p1 = None - - # detect if user is passing a list of points: - if utils.isSequence(p0[0]): - ppoints = vtk.vtkPoints() # Generate the polyline - dim = len((p0[0])) - if dim == 2: - for i, p in enumerate(p0): - ppoints.InsertPoint(i, p[0], p[1], 0) - else: - ppoints.SetData(numpy_to_vtk(p0, deep=True)) - lines = vtk.vtkCellArray() # Create the polyline. - lines.InsertNextCell(len(p0)) - for i in range(len(p0)): - lines.InsertCellPoint(i) - poly = vtk.vtkPolyData() - poly.SetPoints(ppoints) - poly.SetLines(lines) - else: # or just 2 points to link - lineSource = vtk.vtkLineSource() - lineSource.SetPoint1(p0) - lineSource.SetPoint2(p1) - if res: - lineSource.SetResolution(res) - lineSource.Update() - poly = lineSource.GetOutput() - - actor = Actor(poly, c, alpha).lw(lw) - if dotted: - actor.GetProperty().SetLineStipplePattern(0xF0F0) - actor.GetProperty().SetLineStippleRepeatFactor(1) - actor.base = np.array(p0) - actor.top = np.array(p1) - settings.collectable_actors.append(actor) - actor.name = "Line" - return actor - - -def DashedLine(p0, p1=None, spacing=None, c="red", alpha=1, lw=1): + def __init__(self, p0, p1=None, c="r", alpha=1, lw=1, dotted=False, res=None): + if isinstance(p0, vtk.vtkActor): p0 = p0.GetPosition() + if isinstance(p1, vtk.vtkActor): p1 = p1.GetPosition() + + # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: + if len(p0) > 3: + if not utils.isSequence(p0[0]) and not utils.isSequence(p1[0]) and len(p0)==len(p1): + # assume input is 2D xlist, ylist + p0 = np.stack((p0, p1), axis=1) + p1 = None + + # detect if user is passing a list of points: + if utils.isSequence(p0[0]): + ppoints = vtk.vtkPoints() # Generate the polyline + dim = len((p0[0])) + if dim == 2: + for i, p in enumerate(p0): + ppoints.InsertPoint(i, p[0], p[1], 0) + else: + ppoints.SetData(numpy_to_vtk(p0, deep=True)) + lines = vtk.vtkCellArray() # Create the polyline. + lines.InsertNextCell(len(p0)) + for i in range(len(p0)): + lines.InsertCellPoint(i) + poly = vtk.vtkPolyData() + poly.SetPoints(ppoints) + poly.SetLines(lines) + else: # or just 2 points to link + lineSource = vtk.vtkLineSource() + lineSource.SetPoint1(p0) + lineSource.SetPoint2(p1) + if res: + lineSource.SetResolution(res) + lineSource.Update() + poly = lineSource.GetOutput() + + Mesh.__init__(self, poly, c, alpha) + self.lw(lw) + if dotted: + self.GetProperty().SetLineStipplePattern(0xF0F0) + self.GetProperty().SetLineStippleRepeatFactor(1) + self.base = np.array(p0) + self.top = np.array(p1) + settings.collectable_actors.append(self) + self.name = "Line" + + +class DashedLine(Mesh): """ Build a dashed line segment between points `p0` and `p1`. If `p0` is a list of points returns the line connecting them. @@ -491,61 +498,63 @@ def DashedLine(p0, p1=None, spacing=None, c="red", alpha=1, lw=1): :param float alpha: transparency in range [0,1]. :param lw: line width. """ - if isinstance(p0, vtk.vtkActor): p0 = p0.GetPosition() - if isinstance(p1, vtk.vtkActor): p1 = p1.GetPosition() - - # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: - if len(p0) > 3: - if not utils.isSequence(p0[0]) and not utils.isSequence(p1[0]) and len(p0)==len(p1): - # assume input is 2D xlist, ylist - p0 = np.stack((p0, p1), axis=1) - p1 = None - - # detect if user is passing a list of points: - if utils.isSequence(p0[0]): - listp = p0 - else: # or just 2 points to link - listp = [p0, p1] - - if not spacing: - spacing = np.linalg.norm(np.array(listp[-1]) - listp[0])/50 - - polylns = vtk.vtkAppendPolyData() - for ipt in range(1, len(listp)): - p0 = np.array(listp[ipt-1]) - p1 = np.array(listp[ipt]) - v = p1-p0 - n1 = int(np.linalg.norm(v)/spacing) - if not n1: continue - - for i in range(1, n1+2): - if (i-1)/n1>1: - continue - - if i%2: - q0 = p0 + (i-1)/n1*v - if i/n1>1: - q1 = p1 - else: - q1 = p0 + i/n1*v - lineSource = vtk.vtkLineSource() - lineSource.SetPoint1(q0) - lineSource.SetPoint2(q1) - lineSource.Update() - polylns.AddInputData(lineSource.GetOutput()) - - polylns.Update() - poly = polylns.GetOutput() - - actor = Actor(poly, c, alpha).lw(lw) - actor.base = np.array(p0) - actor.top = np.array(p1) - settings.collectable_actors.append(actor) - actor.name = "DashedLine" - return actor - - -def Lines(startPoints, endPoints=None, c='gray', alpha=1, lw=1, dotted=False, scale=1): + def __init__(self, p0, p1=None, spacing=None, c="red", alpha=1, lw=1): + + if isinstance(p0, vtk.vtkActor): p0 = p0.GetPosition() + if isinstance(p1, vtk.vtkActor): p1 = p1.GetPosition() + + # detect if user is passing a 2D list of points as p0=xlist, p1=ylist: + if len(p0) > 3: + if not utils.isSequence(p0[0]) and not utils.isSequence(p1[0]) and len(p0)==len(p1): + # assume input is 2D xlist, ylist + p0 = np.stack((p0, p1), axis=1) + p1 = None + + # detect if user is passing a list of points: + if utils.isSequence(p0[0]): + listp = p0 + else: # or just 2 points to link + listp = [p0, p1] + + if not spacing: + spacing = np.linalg.norm(np.array(listp[-1]) - listp[0])/50 + + polylns = vtk.vtkAppendPolyData() + for ipt in range(1, len(listp)): + p0 = np.array(listp[ipt-1]) + p1 = np.array(listp[ipt]) + v = p1-p0 + n1 = int(np.linalg.norm(v)/spacing) + if not n1: continue + + for i in range(1, n1+2): + if (i-1)/n1>1: + continue + + if i%2: + q0 = p0 + (i-1)/n1*v + if i/n1>1: + q1 = p1 + else: + q1 = p0 + i/n1*v + lineSource = vtk.vtkLineSource() + lineSource.SetPoint1(q0) + lineSource.SetPoint2(q1) + lineSource.Update() + polylns.AddInputData(lineSource.GetOutput()) + + polylns.Update() + poly = polylns.GetOutput() + + Mesh.__init__(self, poly, c, alpha) + self.lw(lw) + self.base = np.array(p0) + self.top = np.array(p1) + settings.collectable_actors.append(self) + self.name = "DashedLine" + + +class Lines(Mesh): """ Build the line segments between two lists of points `startPoints` and `endPoints`. `startPoints` can be also passed in the form ``[[point1, point2], ...]``. @@ -556,37 +565,40 @@ def Lines(startPoints, endPoints=None, c='gray', alpha=1, lw=1, dotted=False, sc .. hint:: |fitspheres2.py|_ """ - if endPoints is not None: - startPoints = np.stack((startPoints, endPoints), axis=1) + def __init__(self, startPoints, endPoints=None, + c='gray', alpha=1, lw=1, dotted=False, scale=1): - polylns = vtk.vtkAppendPolyData() - for twopts in startPoints: - lineSource = vtk.vtkLineSource() - lineSource.SetPoint1(twopts[0]) + if endPoints is not None: + startPoints = np.stack((startPoints, endPoints), axis=1) - if scale != 1: - vers = (np.array(twopts[1]) - twopts[0]) * scale - pt2 = np.array(twopts[0]) + vers - else: - pt2 = twopts[1] + polylns = vtk.vtkAppendPolyData() + for twopts in startPoints: + lineSource = vtk.vtkLineSource() + lineSource.SetPoint1(twopts[0]) - lineSource.SetPoint2(pt2) - polylns.AddInputConnection(lineSource.GetOutputPort()) - polylns.Update() + if scale != 1: + vers = (np.array(twopts[1]) - twopts[0]) * scale + pt2 = np.array(twopts[0]) + vers + else: + pt2 = twopts[1] + + lineSource.SetPoint2(pt2) + polylns.AddInputConnection(lineSource.GetOutputPort()) + polylns.Update() - actor = Actor(polylns.GetOutput(), c, alpha).lw(lw) - if dotted: - actor.GetProperty().SetLineStipplePattern(0xF0F0) - actor.GetProperty().SetLineStippleRepeatFactor(1) + Mesh.__init__(self, polylns.GetOutput(), c, alpha) + self.lw(lw) + if dotted: + self.GetProperty().SetLineStipplePattern(0xF0F0) + self.GetProperty().SetLineStippleRepeatFactor(1) - settings.collectable_actors.append(actor) - actor.name = "Lines" - return actor + settings.collectable_actors.append(self) + self.name = "Lines" -def Spline(points, smooth=0.5, degree=2, s=2, res=None): +class Spline(Mesh): """ - Return an ``Actor`` for a spline which does not necessarly + Return an ``Mesh`` for a spline which does not necessarly passing exactly through all the input points. Needs to import `scypi`. @@ -600,37 +612,37 @@ def Spline(points, smooth=0.5, degree=2, s=2, res=None): |tutorial_spline| |tutorial.py|_ """ - from scipy.interpolate import splprep, splev - if res is None: - res = len(points)*20 - - points = np.array(points) - - minx, miny, minz = np.min(points, axis=0) - maxx, maxy, maxz = np.max(points, axis=0) - maxb = max(maxx - minx, maxy - miny, maxz - minz) - smooth *= maxb / 2 # must be in absolute units - - tckp, _ = splprep(points.T, task=0, s=smooth, k=degree) # find the knots - # evaluate spLine, including interpolated points: - xnew, ynew, znew = splev(np.linspace(0, 1, res), tckp) - - ppoints = vtk.vtkPoints() # Generate the polyline for the spline - profileData = vtk.vtkPolyData() - ppoints.SetData(numpy_to_vtk(np.c_[xnew, ynew, znew], deep=True)) - lines = vtk.vtkCellArray() # Create the polyline - lines.InsertNextCell(res) - for i in range(res): - lines.InsertCellPoint(i) - profileData.SetPoints(ppoints) - profileData.SetLines(lines) - actline = Actor(profileData) - actline.GetProperty().SetLineWidth(s) - actline.base = np.array(points[0]) - actline.top = np.array(points[-1]) - settings.collectable_actors.append(actline) - actline.name = "Spline" - return actline + def __init__(self, points, smooth=0.5, degree=2, s=2, res=None): + from scipy.interpolate import splprep, splev + if res is None: + res = len(points)*20 + + points = np.array(points) + + minx, miny, minz = np.min(points, axis=0) + maxx, maxy, maxz = np.max(points, axis=0) + maxb = max(maxx - minx, maxy - miny, maxz - minz) + smooth *= maxb / 2 # must be in absolute units + + tckp, _ = splprep(points.T, task=0, s=smooth, k=degree) # find the knots + # evaluate spLine, including interpolated points: + xnew, ynew, znew = splev(np.linspace(0, 1, res), tckp) + + ppoints = vtk.vtkPoints() # Generate the polyline for the spline + profileData = vtk.vtkPolyData() + ppoints.SetData(numpy_to_vtk(np.c_[xnew, ynew, znew], deep=True)) + lines = vtk.vtkCellArray() # Create the polyline + lines.InsertNextCell(res) + for i in range(res): + lines.InsertCellPoint(i) + profileData.SetPoints(ppoints) + profileData.SetLines(lines) + Mesh.__init__(self, profileData) + self.GetProperty().SetLineWidth(s) + self.base = np.array(points[0]) + self.top = np.array(points[-1]) + settings.collectable_actors.append(self) + self.name = "Spline" def KSpline(points, @@ -676,15 +688,15 @@ def KSpline(points, z = zspline.Evaluate(pos) ln.append((x,y,z)) - actor = Line(ln, c='gray') - actor.base = np.array(points[0]) - actor.top = np.array(points[-1]) - settings.collectable_actors.append(actor) - actor.name = "KSpline" - return actor + mesh = Line(ln, c='gray') + mesh.base = np.array(points[0]) + mesh.top = np.array(points[-1]) + settings.collectable_actors.append(mesh) + mesh.name = "KSpline" + return mesh -def Tube(points, r=1, c="r", alpha=1, res=12): +class Tube(Mesh): """Build a tube along the line defined by a set of points. :param r: constant radius or list of radii. @@ -696,121 +708,123 @@ def Tube(points, r=1, c="r", alpha=1, res=12): |ribbon| |tube| """ - ppoints = vtk.vtkPoints() # Generate the polyline - ppoints.SetData(numpy_to_vtk(points, deep=True)) - lines = vtk.vtkCellArray() - lines.InsertNextCell(len(points)) - for i in range(len(points)): - lines.InsertCellPoint(i) - polyln = vtk.vtkPolyData() - polyln.SetPoints(ppoints) - polyln.SetLines(lines) - - tuf = vtk.vtkTubeFilter() - tuf.CappingOn() - tuf.SetNumberOfSides(res) - tuf.SetInputData(polyln) - if utils.isSequence(r): - arr = numpy_to_vtk(np.ascontiguousarray(r), deep=True) - arr.SetName("TubeRadius") - polyln.GetPointData().AddArray(arr) - polyln.GetPointData().SetActiveScalars("TubeRadius") - tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() - else: - tuf.SetRadius(r) - - usingColScals = False - if utils.isSequence(c) and len(c) != 3: - usingColScals = True - cc = vtk.vtkUnsignedCharArray() - cc.SetName("TubeColors") - cc.SetNumberOfComponents(3) - cc.SetNumberOfTuples(len(c)) - for i, ic in enumerate(c): - r, g, b = getColor(ic) - cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) - polyln.GetPointData().AddArray(cc) - c = None - tuf.Update() - - actor = Actor(tuf.GetOutput(), c, alpha, computeNormals=0).phong() - if usingColScals: - actor.mapper().SetScalarModeToUsePointFieldData() - actor.mapper().ScalarVisibilityOn() - actor.mapper().SelectColorArray("TubeColors") - actor.mapper().Modified() - - actor.base = np.array(points[0]) - actor.top = np.array(points[-1]) - settings.collectable_actors.append(actor) - actor.name = "Tube" - return actor - - -def Ribbon(line1, line2, c="m", alpha=1, res=(200, 5)): + def __init__(self, points, r=1, cap=True, c=None, alpha=1, res=12): + + ppoints = vtk.vtkPoints() # Generate the polyline + ppoints.SetData(numpy_to_vtk(points, deep=True)) + lines = vtk.vtkCellArray() + lines.InsertNextCell(len(points)) + for i in range(len(points)): + lines.InsertCellPoint(i) + polyln = vtk.vtkPolyData() + polyln.SetPoints(ppoints) + polyln.SetLines(lines) + + tuf = vtk.vtkTubeFilter() + tuf.SetCapping(cap) + tuf.SetNumberOfSides(res) + tuf.SetInputData(polyln) + if utils.isSequence(r): + arr = numpy_to_vtk(np.ascontiguousarray(r), deep=True) + arr.SetName("TubeRadius") + polyln.GetPointData().AddArray(arr) + polyln.GetPointData().SetActiveScalars("TubeRadius") + tuf.SetVaryRadiusToVaryRadiusByAbsoluteScalar() + else: + tuf.SetRadius(r) + + usingColScals = False + if utils.isSequence(c) and len(c) != 3: + usingColScals = True + cc = vtk.vtkUnsignedCharArray() + cc.SetName("TubeColors") + cc.SetNumberOfComponents(3) + cc.SetNumberOfTuples(len(c)) + for i, ic in enumerate(c): + r, g, b = getColor(ic) + cc.InsertTuple3(i, int(255 * r), int(255 * g), int(255 * b)) + polyln.GetPointData().AddArray(cc) + c = None + tuf.Update() + + Mesh.__init__(self, tuf.GetOutput(), c, alpha) + self.phong() + if usingColScals: + self.mapper().SetScalarModeToUsePointFieldData() + self.mapper().ScalarVisibilityOn() + self.mapper().SelectColorArray("TubeColors") + self.mapper().Modified() + + self.base = np.array(points[0]) + self.top = np.array(points[-1]) + settings.collectable_actors.append(self) + self.name = "Tube" + +class Ribbon(Mesh): """Connect two lines to generate the surface inbetween. |ribbon| |ribbon.py|_ """ - if isinstance(line1, Actor): - line1 = line1.coordinates() - if isinstance(line2, Actor): - line2 = line2.coordinates() - - ppoints1 = vtk.vtkPoints() # Generate the polyline1 - ppoints1.SetData(numpy_to_vtk(line1, deep=True)) - lines1 = vtk.vtkCellArray() - lines1.InsertNextCell(len(line1)) - for i in range(len(line1)): - lines1.InsertCellPoint(i) - poly1 = vtk.vtkPolyData() - poly1.SetPoints(ppoints1) - poly1.SetLines(lines1) - - ppoints2 = vtk.vtkPoints() # Generate the polyline2 - ppoints2.SetData(numpy_to_vtk(line2, deep=True)) - lines2 = vtk.vtkCellArray() - lines2.InsertNextCell(len(line2)) - for i in range(len(line2)): - lines2.InsertCellPoint(i) - poly2 = vtk.vtkPolyData() - poly2.SetPoints(ppoints2) - poly2.SetLines(lines2) - - # build the lines - lines1 = vtk.vtkCellArray() - lines1.InsertNextCell(poly1.GetNumberOfPoints()) - for i in range(poly1.GetNumberOfPoints()): - lines1.InsertCellPoint(i) - - polygon1 = vtk.vtkPolyData() - polygon1.SetPoints(ppoints1) - polygon1.SetLines(lines1) - - lines2 = vtk.vtkCellArray() - lines2.InsertNextCell(poly2.GetNumberOfPoints()) - for i in range(poly2.GetNumberOfPoints()): - lines2.InsertCellPoint(i) - - polygon2 = vtk.vtkPolyData() - polygon2.SetPoints(ppoints2) - polygon2.SetLines(lines2) - - mergedPolyData = vtk.vtkAppendPolyData() - mergedPolyData.AddInputData(polygon1) - mergedPolyData.AddInputData(polygon2) - mergedPolyData.Update() - - rsf = vtk.vtkRuledSurfaceFilter() - rsf.CloseSurfaceOff() - rsf.SetRuledModeToResample() - rsf.SetResolution(res[0], res[1]) - rsf.SetInputData(mergedPolyData.GetOutput()) - rsf.Update() - actor = Actor(rsf.GetOutput(), c, alpha) - settings.collectable_actors.append(actor) - actor.name = "Ribbon" - return actor + def __init__(self, line1, line2, c="m", alpha=1, res=(200,5)): + + if isinstance(line1, Mesh): + line1 = line1.points() + if isinstance(line2, Mesh): + line2 = line2.points() + + ppoints1 = vtk.vtkPoints() # Generate the polyline1 + ppoints1.SetData(numpy_to_vtk(line1, deep=True)) + lines1 = vtk.vtkCellArray() + lines1.InsertNextCell(len(line1)) + for i in range(len(line1)): + lines1.InsertCellPoint(i) + poly1 = vtk.vtkPolyData() + poly1.SetPoints(ppoints1) + poly1.SetLines(lines1) + + ppoints2 = vtk.vtkPoints() # Generate the polyline2 + ppoints2.SetData(numpy_to_vtk(line2, deep=True)) + lines2 = vtk.vtkCellArray() + lines2.InsertNextCell(len(line2)) + for i in range(len(line2)): + lines2.InsertCellPoint(i) + poly2 = vtk.vtkPolyData() + poly2.SetPoints(ppoints2) + poly2.SetLines(lines2) + + # build the lines + lines1 = vtk.vtkCellArray() + lines1.InsertNextCell(poly1.GetNumberOfPoints()) + for i in range(poly1.GetNumberOfPoints()): + lines1.InsertCellPoint(i) + + polygon1 = vtk.vtkPolyData() + polygon1.SetPoints(ppoints1) + polygon1.SetLines(lines1) + + lines2 = vtk.vtkCellArray() + lines2.InsertNextCell(poly2.GetNumberOfPoints()) + for i in range(poly2.GetNumberOfPoints()): + lines2.InsertCellPoint(i) + + polygon2 = vtk.vtkPolyData() + polygon2.SetPoints(ppoints2) + polygon2.SetLines(lines2) + + mergedPolyData = vtk.vtkAppendPolyData() + mergedPolyData.AddInputData(polygon1) + mergedPolyData.AddInputData(polygon2) + mergedPolyData.Update() + + rsf = vtk.vtkRuledSurfaceFilter() + rsf.CloseSurfaceOff() + rsf.SetRuledModeToResample() + rsf.SetResolution(res[0], res[1]) + rsf.SetInputData(mergedPolyData.GetOutput()) + rsf.Update() + Mesh.__init__(self, rsf.GetOutput(), c, alpha) + settings.collectable_actors.append(self) + self.name = "Ribbon" def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1): @@ -818,8 +832,8 @@ def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1): |flatarrow| |flatarrow.py|_ """ - if isinstance(line1, Actor): line1 = line1.coordinates() - if isinstance(line2, Actor): line2 = line2.coordinates() + if isinstance(line1, Mesh): line1 = line1.points() + if isinstance(line2, Mesh): line2 = line2.points() sm1, sm2 = np.array(line1[-1]), np.array(line2[-1]) @@ -837,14 +851,13 @@ def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1): line2.append(tip) resm = max(100, len(line1)) - actor = Ribbon(line1, line2, alpha=alpha, c=c, res=(resm, 1)).phong() + mesh = Ribbon(line1, line2, alpha=alpha, c=c, res=(resm, 1)).phong() settings.collectable_actors.pop() - settings.collectable_actors.append(actor) - actor.name = "FlatArrow" - return actor + settings.collectable_actors.append(mesh) + mesh.name = "FlatArrow" + return mesh - -def Arrow(startPoint, endPoint, s=None, c="r", alpha=1, res=12): +class Arrow(Mesh): """ Build a 3D arrow from `startPoint` to `endPoint` of section size `s`, expressed as the fraction of the window size. @@ -854,47 +867,49 @@ def Arrow(startPoint, endPoint, s=None, c="r", alpha=1, res=12): |OrientedArrow| """ - # in case user is passing actors - if isinstance(startPoint, vtk.vtkActor): startPoint = startPoint.GetPosition() - if isinstance(endPoint, vtk.vtkActor): endPoint = endPoint.GetPosition() + def __init__(self, startPoint, endPoint, s=None, c="r", alpha=1, res=12): - axis = np.array(endPoint) - np.array(startPoint) - length = np.linalg.norm(axis) - if length: - axis = axis / length - theta = np.arccos(axis[2]) - phi = np.arctan2(axis[1], axis[0]) - arr = vtk.vtkArrowSource() - arr.SetShaftResolution(res) - arr.SetTipResolution(res) - if s: - sz = 0.02 - arr.SetTipRadius(sz) - arr.SetShaftRadius(sz / 1.75) - arr.SetTipLength(sz * 15) - arr.Update() - t = vtk.vtkTransform() - t.RotateZ(np.rad2deg(phi)) - t.RotateY(np.rad2deg(theta)) - t.RotateY(-90) # put it along Z - if s: - sz = 800.0 * s - t.Scale(length, sz, sz) - else: - t.Scale(length, length, length) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(arr.GetOutput()) - tf.SetTransform(t) - tf.Update() - - actor = Actor(tf.GetOutput(), c, alpha).phong() - actor.SetPosition(startPoint) - actor.DragableOff() - actor.base = np.array(startPoint) - actor.top = np.array(endPoint) - settings.collectable_actors.append(actor) - actor.name = "Arrow" - return actor + # in case user is passing meshs + if isinstance(startPoint, vtk.vtkActor): startPoint = startPoint.GetPosition() + if isinstance(endPoint, vtk.vtkActor): endPoint = endPoint.GetPosition() + + axis = np.array(endPoint) - np.array(startPoint) + length = np.linalg.norm(axis) + if length: + axis = axis / length + theta = np.arccos(axis[2]) + phi = np.arctan2(axis[1], axis[0]) + arr = vtk.vtkArrowSource() + arr.SetShaftResolution(res) + arr.SetTipResolution(res) + if s: + sz = 0.02 + arr.SetTipRadius(sz) + arr.SetShaftRadius(sz / 1.75) + arr.SetTipLength(sz * 15) + arr.Update() + t = vtk.vtkTransform() + t.RotateZ(np.rad2deg(phi)) + t.RotateY(np.rad2deg(theta)) + t.RotateY(-90) # put it along Z + if s: + sz = 800.0 * s + t.Scale(length, sz, sz) + else: + t.Scale(length, length, length) + tf = vtk.vtkTransformPolyDataFilter() + tf.SetInputData(arr.GetOutput()) + tf.SetTransform(t) + tf.Update() + + Mesh.__init__(self, tf.GetOutput(), c, alpha) + self.phong() + self.SetPosition(startPoint) + self.DragableOff() + self.base = np.array(startPoint) + self.top = np.array(endPoint) + settings.collectable_actors.append(self) + self.name = "Arrow" def Arrows(startPoints, endPoints=None, s=None, scale=1, c="r", alpha=1, res=12): @@ -912,8 +927,8 @@ def Arrows(startPoints, endPoints=None, s=None, scale=1, c="r", alpha=1, res=12) |glyphs_arrows| |glyphs_arrows.py|_ """ - if isinstance(startPoints, Actor): startPoints = startPoints.coordinates() - if isinstance(endPoints, Actor): endPoints = endPoints.coordinates() + if isinstance(startPoints, Mesh): startPoints = startPoints.points() + if isinstance(endPoints, Mesh): endPoints = endPoints.points() startPoints = np.array(startPoints) if endPoints is None: strt = startPoints[:,0] @@ -939,33 +954,34 @@ def Arrows(startPoints, endPoints=None, s=None, scale=1, c="r", alpha=1, res=12) return arrg -def Polygon(pos=(0, 0, 0), nsides=6, r=1, c="coral", alpha=1): +class Polygon(Mesh): """ - Build a 2D polygon of `nsides` of radius `r`. + Build a polygon in the `xy` plane of `nsides` of radius `r`. |Polygon| """ - ps = vtk.vtkRegularPolygonSource() - ps.SetNumberOfSides(nsides) - ps.SetRadius(r) - ps.Update() - actor = Actor(ps.GetOutput(), c, alpha) - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Polygon" - return actor + def __init__(self, pos=(0, 0, 0), nsides=6, r=1, c="coral", alpha=1): + ps = vtk.vtkRegularPolygonSource() + ps.SetNumberOfSides(nsides) + ps.SetRadius(r) + ps.Update() + Mesh.__init__(self, ps.GetOutput(), c, alpha) + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Polygon" -def Circle(pos=(0, 0, 0), r=1, fill=False, c="grey", alpha=1, res=120): +class Circle(Polygon): """ - Build a circle of radius `r`. + Build a Circle of radius `r`. """ - if len(pos) == 2: - pos = (pos[0], pos[1], 0) - pl = Polygon(pos, nsides=res, r=r) - pl.wireframe(not fill).alpha(alpha).c(c) - pl.name = "Circle" - return pl + def __init__(self, pos=(0,0,0), r=1, fill=False, c="grey", alpha=1, res=120): + + if len(pos) == 2: + pos = (pos[0], pos[1], 0) + Polygon.__init__(self, pos, nsides=res, r=r) + self.wireframe(not fill).alpha(alpha).c(c) + self.name = "Circle" def Star(pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="lb", alpha=1): @@ -991,7 +1007,7 @@ def Star(pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="lb", alpha=1): if line: apts.append(pts[0]) - actor = Line(apts).c(c).alpha(alpha) + mesh = Line(apts).c(c).alpha(alpha) else: apts.append((0,0,0)) cells=[] @@ -999,65 +1015,45 @@ def Star(pos=(0, 0, 0), n=5, r1=0.7, r2=1.0, line=False, c="lb", alpha=1): cell = [2*n, i, i+1] cells.append(cell) cells.append([2*n, i+1, 0]) - actor = Actor([apts, cells], c, alpha) + mesh = Mesh([apts, cells], c, alpha) - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Star" - return actor + mesh.SetPosition(pos) + settings.collectable_actors.append(mesh) + mesh.name = "Star" + return mesh -def Rectangle(p1=(0, 0, 0), p2=(2, 1, 0), lw=1, c="g", alpha=1): - """Build a rectangle in the xy plane identified by two corner points.""" - p1 = np.array(p1) - p2 = np.array(p2) - pos = (p1 + p2) / 2 - length = abs(p2[0] - p1[0]) - height = abs(p2[1] - p1[1]) - actor = Plane(pos, [0, 0, 1], length, height, c, alpha) - actor.name = "Rectangle" - return actor - -def Disc( - pos=(0, 0, 0), - r1=0.5, - r2=1, - c="coral", - alpha=1, - res=12, - resphi=None, -): +class Disc(Mesh): """ Build a 2D disc of inner radius `r1` and outer radius `r2`. |Disk| """ - ps = vtk.vtkDiskSource() - ps.SetInnerRadius(r1) - ps.SetOuterRadius(r2) - ps.SetRadialResolution(res) - if not resphi: - resphi = 6 * res - ps.SetCircumferentialResolution(resphi) - ps.Update() - actor = Actor(ps.GetOutput(), c, alpha).flat() - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Disc" - return actor - - -def Arc( - center, - point1, - point2=None, - normal=None, - angle=None, - invert=False, - c="grey", - alpha=1, - res=48, -): + def __init__(self, + pos=(0, 0, 0), + r1=0.5, + r2=1, + c="coral", + alpha=1, + res=12, + resphi=None, + ): + ps = vtk.vtkDiskSource() + ps.SetInnerRadius(r1) + ps.SetOuterRadius(r2) + ps.SetRadialResolution(res) + if not resphi: + resphi = 6 * res + ps.SetCircumferentialResolution(resphi) + ps.Update() + Mesh.__init__(self, ps.GetOutput(), c, alpha) + self.flat() + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Disc" + + +class Arc(Mesh): """ Build a 2D circular arc between points `point1` and `point2`. If `normal` is specified then `center` is ignored, and @@ -1067,47 +1063,62 @@ def Arc( Arc spans the shortest angular sector point1 and point2, if invert=True, then the opposite happens. """ - ar = vtk.vtkArcSource() - if point2 is not None: - ar.UseNormalAndAngleOff() - ar.SetPoint1(point1) - ar.SetPoint2(point2) - ar.SetCenter(center) - elif normal is not None and angle is not None: - ar.UseNormalAndAngleOn() - ar.SetAngle(angle) - ar.SetPolarVector(point1) - ar.SetNormal(normal) - else: - printc("Error in Arc(): incorrect input.") - return None - ar.SetNegative(invert) - ar.SetResolution(res) - ar.Update() - actor = Actor(ar.GetOutput(), c, alpha).flat().lw(2) - settings.collectable_actors.append(actor) - actor.name = "Arc" - return actor - - -def Sphere(pos=(0, 0, 0), r=1, c="r", alpha=1, res=24): + def __init__(self, + center, + point1, + point2=None, + normal=None, + angle=None, + invert=False, + c="grey", + alpha=1, + res=48, + ): + ar = vtk.vtkArcSource() + if point2 is not None: + ar.UseNormalAndAngleOff() + ar.SetPoint1(point1) + ar.SetPoint2(point2) + ar.SetCenter(center) + elif normal is not None and angle is not None: + ar.UseNormalAndAngleOn() + ar.SetAngle(angle) + ar.SetPolarVector(point1) + ar.SetNormal(normal) + else: + printc("Error in Arc(): incorrect input.") + return None + ar.SetNegative(invert) + ar.SetResolution(res) + ar.Update() + Mesh.__init__(self, ar.GetOutput(), c, alpha) + self.flat().lw(2) + settings.collectable_actors.append(self) + self.name = "Arc" + + +class Sphere(Mesh): """Build a sphere at position `pos` of radius `r`. |Sphere| """ - ss = vtk.vtkSphereSource() - ss.SetRadius(r) - ss.SetThetaResolution(2 * res) - ss.SetPhiResolution(res) - ss.Update() - actor = Actor(ss.GetOutput(), c, alpha).phong() - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Sphere" - return actor + def __init__(self, pos=(0, 0, 0), r=1, c="r", alpha=1, res=24): + + ss = vtk.vtkSphereSource() + ss.SetRadius(r) + ss.SetThetaResolution(2 * res) + ss.SetPhiResolution(res) + ss.Update() + Mesh.__init__(self, ss.GetOutput(), c, alpha) -def Spheres(centers, r=1, c="r", alpha=1, res=8): + self.phong() + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Sphere" + + +class Spheres(Mesh): """ Build a (possibly large) set of spheres at `centers` of radius `r`. @@ -1115,127 +1126,112 @@ def Spheres(centers, r=1, c="r", alpha=1, res=8): |manyspheres| |manyspheres.py|_ """ - - cisseq = False - if utils.isSequence(c): - cisseq = True - - if cisseq: - if len(centers) > len(c): - printc("~times Mismatch in Spheres() colors", len(centers), len(c), c=1) + def __init__(self, centers, r=1, c="r", alpha=1, res=8): + + cisseq = False + if utils.isSequence(c): + cisseq = True + + if cisseq: + if len(centers) > len(c): + printc("~times Mismatch in Spheres() colors", len(centers), len(c), c=1) + raise RuntimeError() + if len(centers) != len(c): + printc("~lightningWarning: mismatch in Spheres() colors", len(centers), len(c)) + + risseq = False + if utils.isSequence(r): + risseq = True + + if risseq: + if len(centers) > len(r): + printc("times Mismatch in Spheres() radius", len(centers), len(r), c=1) + raise RuntimeError() + if len(centers) != len(r): + printc("~lightning Warning: mismatch in Spheres() radius", len(centers), len(r)) + if cisseq and risseq: + printc("~noentry Limitation: c and r cannot be both sequences.", c=1) raise RuntimeError() - if len(centers) != len(c): - printc("~lightningWarning: mismatch in Spheres() colors", len(centers), len(c)) - risseq = False - if utils.isSequence(r): - risseq = True + src = vtk.vtkSphereSource() + if not risseq: + src.SetRadius(r) + src.SetPhiResolution(res) + src.SetThetaResolution(2 * res) + src.Update() - if risseq: - if len(centers) > len(r): - printc("times Mismatch in Spheres() radius", len(centers), len(r), c=1) - raise RuntimeError() - if len(centers) != len(r): - printc("~lightning Warning: mismatch in Spheres() radius", len(centers), len(r)) - if cisseq and risseq: - printc("~noentry Limitation: c and r cannot be both sequences.", c=1) - raise RuntimeError() - - src = vtk.vtkSphereSource() - if not risseq: - src.SetRadius(r) - src.SetPhiResolution(res) - src.SetThetaResolution(2 * res) - src.Update() - - psrc = vtk.vtkPointSource() - psrc.SetNumberOfPoints(len(centers)) - psrc.Update() - pd = psrc.GetOutput() - vpts = pd.GetPoints() - - glyph = vtk.vtkGlyph3D() - glyph.SetSourceConnection(src.GetOutputPort()) - - if cisseq: - glyph.SetColorModeToColorByScalar() - ucols = vtk.vtkUnsignedCharArray() - ucols.SetNumberOfComponents(3) - ucols.SetName("colors") - for i, p in enumerate(centers): - vpts.SetPoint(i, p) - cx, cy, cz = getColor(c[i]) - ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) - pd.GetPointData().SetScalars(ucols) - glyph.ScalingOff() - elif risseq: - glyph.SetScaleModeToScaleByScalar() - urads = vtk.vtkFloatArray() - urads.SetName("scales") - for i, p in enumerate(centers): - vpts.SetPoint(i, p) - urads.InsertNextValue(r[i]) - pd.GetPointData().SetScalars(urads) - else: - for i, p in enumerate(centers): - vpts.SetPoint(i, p) + psrc = vtk.vtkPointSource() + psrc.SetNumberOfPoints(len(centers)) + psrc.Update() + pd = psrc.GetOutput() + vpts = pd.GetPoints() + + glyph = vtk.vtkGlyph3D() + glyph.SetSourceConnection(src.GetOutputPort()) + + if cisseq: + glyph.SetColorModeToColorByScalar() + ucols = vtk.vtkUnsignedCharArray() + ucols.SetNumberOfComponents(3) + ucols.SetName("colors") + for i, p in enumerate(centers): + vpts.SetPoint(i, p) + cx, cy, cz = getColor(c[i]) + ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) + pd.GetPointData().SetScalars(ucols) + glyph.ScalingOff() + elif risseq: + glyph.SetScaleModeToScaleByScalar() + urads = vtk.vtkFloatArray() + urads.SetName("scales") + for i, p in enumerate(centers): + vpts.SetPoint(i, p) + urads.InsertNextValue(r[i]) + pd.GetPointData().SetScalars(urads) + else: + for i, p in enumerate(centers): + vpts.SetPoint(i, p) - glyph.SetInputData(pd) - glyph.Update() + glyph.SetInputData(pd) + glyph.Update() - actor = Actor(glyph.GetOutput(), alpha=alpha).phong() - if cisseq: - actor.mapper().ScalarVisibilityOn() - else: - actor.mapper().ScalarVisibilityOff() - actor.GetProperty().SetColor(getColor(c)) - settings.collectable_actors.append(actor) - actor.name = "Spheres" - return actor + Mesh.__init__(self, glyph.GetOutput(), alpha=alpha) + self.phong() + if cisseq: + self.mapper().ScalarVisibilityOn() + else: + self.mapper().ScalarVisibilityOff() + self.GetProperty().SetColor(getColor(c)) + settings.collectable_actors.append(self) + self.name = "Spheres" -def Earth(pos=(0, 0, 0), r=1, lw=0): - """Build a textured actor representing the Earth. +class Earth(Mesh): + """Build a textured mesh representing the Earth. |geodesic| |geodesic.py|_ """ - import os - - tss = vtk.vtkTexturedSphereSource() - tss.SetRadius(r) - tss.SetThetaResolution(72) - tss.SetPhiResolution(36) - earthActor = Actor(tss, c="w") - atext = vtk.vtkTexture() - pnmReader = vtk.vtkPNMReader() - cdir = os.path.dirname(__file__) - if cdir == "": - cdir = "." - fn = settings.textures_path + "earth.ppm" - pnmReader.SetFileName(fn) - atext.SetInputConnection(pnmReader.GetOutputPort()) - atext.InterpolateOn() - earthActor.SetTexture(atext) - if not lw: - earthActor.SetPosition(pos) - settings.collectable_actors.append(earthActor) - earthActor.name = "Earth" - return earthActor - else: - es = vtk.vtkEarthSource() - es.SetOnRatio(2) - es.SetRadius(r / 0.995) - earth2Actor = Actor(es) - earth2Actor.GetProperty().SetLineWidth(lw) - asse = Assembly([earthActor, earth2Actor]) - asse.SetPosition(pos) - settings.collectable_actors.append(asse) - asse.name = "Earth" - return asse + def __init__(self, pos=(0, 0, 0), r=1): + tss = vtk.vtkTexturedSphereSource() + tss.SetRadius(r) + tss.SetThetaResolution(72) + tss.SetPhiResolution(36) + + Mesh.__init__(self, tss, c="w") + + atext = vtk.vtkTexture() + pnmReader = vtk.vtkPNMReader() + fn = settings.textures_path + "earth.ppm" + pnmReader.SetFileName(fn) + atext.SetInputConnection(pnmReader.GetOutputPort()) + atext.InterpolateOn() + self.SetTexture(atext) + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Earth" -def Ellipsoid(pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), - c="c", alpha=1, res=24): +class Ellipsoid(Mesh): """ Build a 3D ellipsoid centered at position `pos`. @@ -1243,56 +1239,49 @@ def Ellipsoid(pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), |projectsphere| """ - elliSource = vtk.vtkSphereSource() - elliSource.SetThetaResolution(res) - elliSource.SetPhiResolution(res) - elliSource.Update() - l1 = np.linalg.norm(axis1) - l2 = np.linalg.norm(axis2) - l3 = np.linalg.norm(axis3) - axis1 = np.array(axis1) / l1 - axis2 = np.array(axis2) / l2 - axis3 = np.array(axis3) / l3 - angle = np.arcsin(np.dot(axis1, axis2)) - theta = np.arccos(axis3[2]) - phi = np.arctan2(axis3[1], axis3[0]) - - t = vtk.vtkTransform() - t.PostMultiply() - t.Scale(l1, l2, l3) - t.RotateX(np.rad2deg(angle)) - t.RotateY(np.rad2deg(theta)) - t.RotateZ(np.rad2deg(phi)) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(elliSource.GetOutput()) - tf.SetTransform(t) - tf.Update() - pd = tf.GetOutput() - - actor = Actor(pd, c, alpha).phong() - actor.GetProperty().BackfaceCullingOn() - actor.SetPosition(pos) - actor.base = -np.array(axis1) / 2 + pos - actor.top = np.array(axis1) / 2 + pos - settings.collectable_actors.append(actor) - actor.name = "Ellipsoid" - return actor - - -def Grid( - pos=(0, 0, 0), - normal=(0, 0, 1), - sx=1, - sy=1, - sz=(0,), - c="gray", - alpha=1, - lw=1, - resx=10, - resy=10, -): + def __init__(self, pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), + c="c", alpha=1, res=24): + + elliSource = vtk.vtkSphereSource() + elliSource.SetThetaResolution(res) + elliSource.SetPhiResolution(res) + elliSource.Update() + l1 = np.linalg.norm(axis1) + l2 = np.linalg.norm(axis2) + l3 = np.linalg.norm(axis3) + axis1 = np.array(axis1) / l1 + axis2 = np.array(axis2) / l2 + axis3 = np.array(axis3) / l3 + angle = np.arcsin(np.dot(axis1, axis2)) + theta = np.arccos(axis3[2]) + phi = np.arctan2(axis3[1], axis3[0]) + + t = vtk.vtkTransform() + t.PostMultiply() + t.Scale(l1, l2, l3) + t.RotateX(np.rad2deg(angle)) + t.RotateY(np.rad2deg(theta)) + t.RotateZ(np.rad2deg(phi)) + tf = vtk.vtkTransformPolyDataFilter() + tf.SetInputData(elliSource.GetOutput()) + tf.SetTransform(t) + tf.Update() + pd = tf.GetOutput() + + Mesh.__init__(self, pd, c, alpha) + self.phong() + + self.GetProperty().BackfaceCullingOn() + self.SetPosition(pos) + self.base = -np.array(axis1) / 2 + pos + self.top = np.array(axis1) / 2 + pos + settings.collectable_actors.append(self) + self.name = "Ellipsoid" + + +class Grid(Mesh): """Return an even or uneven 2D grid at `z=0`. - + :param float,list sx: if a float is provided it is interpreted as the total size along x, if a list of coords is provided they are interpreted as the vertices of the grid along x. In this case keyword `resx` is ignored (see example below). @@ -1305,133 +1294,154 @@ def Grid( :Example: .. code-block:: python - from vtkplotter import * + from vtkplotter import * xcoords = arange(0, 2, 0.2) ycoords = arange(0, 1, 0.2) sqrtx = sqrt(xcoords) grid = Grid(sx=sqrtx, sy=ycoords) grid.show(axes=8) """ - if utils.isSequence(sx) and utils.isSequence(sy): - verts = [] - for y in sy: - for x in sx: - verts.append([x, y, 0]) - faces = [] - n = len(sx) - m = len(sy) - for j in range(m-1): - j1n = (j+1)*n - for i in range(n-1): - faces.append([i+j*n, i+1+j*n, i+1+j1n, i+j1n]) - actor = Actor([verts, faces], c, alpha).orientation(normal) - else: + def __init__(self, + pos=(0, 0, 0), + normal=(0, 0, 1), + sx=1, + sy=1, + sz=(0,), + c="gray", + alpha=1, + lw=1, + resx=10, + resy=10, + ): + + if utils.isSequence(sx) and utils.isSequence(sy): + verts = [] + for y in sy: + for x in sx: + verts.append([x, y, 0]) + faces = [] + n = len(sx) + m = len(sy) + for j in range(m-1): + j1n = (j+1)*n + for i in range(n-1): + faces.append([i+j*n, i+1+j*n, i+1+j1n, i+j1n]) + + Mesh.__init__(self, [verts, faces], c, alpha) + self.orientation(normal) + + else: + ps = vtk.vtkPlaneSource() + ps.SetResolution(resx, resy) + ps.Update() + poly0 = ps.GetOutput() + t0 = vtk.vtkTransform() + t0.Scale(sx, sy, 1) + tf0 = vtk.vtkTransformPolyDataFilter() + tf0.SetInputData(poly0) + tf0.SetTransform(t0) + tf0.Update() + poly = tf0.GetOutput() + axis = np.array(normal) / np.linalg.norm(normal) + theta = np.arccos(axis[2]) + phi = np.arctan2(axis[1], axis[0]) + t = vtk.vtkTransform() + t.PostMultiply() + t.RotateY(np.rad2deg(theta)) + t.RotateZ(np.rad2deg(phi)) + tf = vtk.vtkTransformPolyDataFilter() + tf.SetInputData(poly) + tf.SetTransform(t) + tf.Update() + Mesh.__init__(self, tf.GetOutput(), c, alpha) + + self.wireframe().lw(lw) + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Grid" + + +class Plane(Mesh): + """ + Draw a plane of size `sx` and `sy` oriented perpendicular to vector `normal` + and so that it passes through point `pos`. + + |Plane| + """ + def __init__(self, pos=(0, 0, 0), normal=(0, 0, 1), sx=1, sy=None, c="g", alpha=1): + + if sy is None: + sy = sx ps = vtk.vtkPlaneSource() - ps.SetResolution(resx, resy) - ps.Update() - poly0 = ps.GetOutput() - t0 = vtk.vtkTransform() - t0.Scale(sx, sy, 1) - tf0 = vtk.vtkTransformPolyDataFilter() - tf0.SetInputData(poly0) - tf0.SetTransform(t0) - tf0.Update() - poly = tf0.GetOutput() + ps.SetResolution(1, 1) + tri = vtk.vtkTriangleFilter() + tri.SetInputConnection(ps.GetOutputPort()) + tri.Update() + poly = tri.GetOutput() axis = np.array(normal) / np.linalg.norm(normal) theta = np.arccos(axis[2]) phi = np.arctan2(axis[1], axis[0]) t = vtk.vtkTransform() t.PostMultiply() + t.Scale(sx, sy, 1) t.RotateY(np.rad2deg(theta)) t.RotateZ(np.rad2deg(phi)) tf = vtk.vtkTransformPolyDataFilter() tf.SetInputData(poly) tf.SetTransform(t) tf.Update() - actor = Actor(tf.GetOutput(), c, alpha) - actor.wireframe().lw(lw) - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Grid" - return actor + Mesh.__init__(self, tf.GetOutput(), c, alpha) + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Plane" + self.top = np.array(normal) + self.bottom = np.array([0,0,0]) +def Rectangle(p1=(0, 0, 0), p2=(2, 1, 0), lw=1, c="g", alpha=1): + """Build a rectangle in the xy plane identified by two corner points.""" + p1 = np.array(p1) + p2 = np.array(p2) + pos = (p1 + p2) / 2 + length = abs(p2[0] - p1[0]) + height = abs(p2[1] - p1[1]) + mesh = Plane(pos, [0,0,1], length, height, c, alpha) + mesh.name = "Rectangle" + return mesh -def Plane(pos=(0, 0, 0), normal=(0, 0, 1), sx=1, sy=None, c="g", alpha=1): - """ - Draw a plane of size `sx` and `sy` oriented perpendicular to vector `normal` - and so that it passes through point `pos`. - |Plane| - """ - if sy is None: - sy = sx - ps = vtk.vtkPlaneSource() - ps.SetResolution(1, 1) - tri = vtk.vtkTriangleFilter() - tri.SetInputConnection(ps.GetOutputPort()) - tri.Update() - poly = tri.GetOutput() - axis = np.array(normal) / np.linalg.norm(normal) - theta = np.arccos(axis[2]) - phi = np.arctan2(axis[1], axis[0]) - t = vtk.vtkTransform() - t.PostMultiply() - t.Scale(sx, sy, 1) - t.RotateY(np.rad2deg(theta)) - t.RotateZ(np.rad2deg(phi)) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(poly) - tf.SetTransform(t) - tf.Update() - actor = Actor(tf.GetOutput(), c, alpha) - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Plane" - return actor - - -def Box(pos=(0,0,0), length=1, width=2, height=3, size=(), c="g", alpha=1): +class Box(Mesh): """ Build a box of dimensions `x=length, y=width and z=height`. Alternatively dimensions can be defined by setting `size` keyword with a tuple. |aspring| |aspring.py|_ """ - if len(size): - length, width, height = size - src = vtk.vtkCubeSource() - src.SetXLength(length) - src.SetYLength(width) - src.SetZLength(height) - src.Update() - pd = src.GetOutput() - actor = Actor(pd, c, alpha) - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Box" - return actor + def __init__(self, pos=(0,0,0), length=1, width=2, height=3, size=(), c="g", alpha=1): + if len(size): + length, width, height = size + src = vtk.vtkCubeSource() + src.SetXLength(length) + src.SetYLength(width) + src.SetZLength(height) + src.Update() + pd = src.GetOutput() + Mesh.__init__(self, pd, c, alpha) + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Box" def Cube(pos=(0, 0, 0), side=1, c="g", alpha=1): """Build a cube of size `side`. |colorcubes| |colorcubes.py|_ """ - actor = Box(pos, side, side, side, (), c, alpha) - actor.name = "Cube" - return actor + mesh = Box(pos, side, side, side, (), c, alpha) + mesh.name = "Cube" + return mesh -def Spring( - startPoint=(0, 0, 0), - endPoint=(1, 0, 0), - coils=20, - r=0.1, - r2=None, - thickness=None, - c="grey", - alpha=1, -): +class Spring(Mesh): """ Build a spring of specified nr of `coils` between `startPoint` and `endPoint`. @@ -1442,52 +1452,62 @@ def Spring( |aspring| |aspring.py|_ """ - diff = endPoint - np.array(startPoint) - length = np.linalg.norm(diff) - if not length: - return None - if not r: - r = length / 20 - trange = np.linspace(0, length, num=50 * coils) - om = 6.283 * (coils - 0.5) / length - if not r2: - r2 = r - pts = [] - for t in trange: - f = (length - t) / length - rd = r * f + r2 * (1 - f) - pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) - - pts = [[0, 0, 0]] + pts + [[0, 0, length]] - diff = diff / length - theta = np.arccos(diff[2]) - phi = np.arctan2(diff[1], diff[0]) - sp = Line(pts).polydata(False) - t = vtk.vtkTransform() - t.RotateZ(np.rad2deg(phi)) - t.RotateY(np.rad2deg(theta)) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(sp) - tf.SetTransform(t) - tf.Update() - tuf = vtk.vtkTubeFilter() - tuf.SetNumberOfSides(12) - tuf.CappingOn() - tuf.SetInputData(tf.GetOutput()) - if not thickness: - thickness = r / 10 - tuf.SetRadius(thickness) - tuf.Update() - actor = Actor(tuf.GetOutput(), c, alpha).phong() - actor.SetPosition(startPoint) - actor.base = np.array(startPoint) - actor.top = np.array(endPoint) - settings.collectable_actors.append(actor) - actor.name = "Spring" - return actor - - -def Cylinder(pos=(0,0,0), r=1, height=2, axis=(0,0,1), c="teal", alpha=1, res=24): + def __init__(self, + startPoint=(0, 0, 0), + endPoint=(1, 0, 0), + coils=20, + r=0.1, + r2=None, + thickness=None, + c="grey", + alpha=1, + ): + diff = endPoint - np.array(startPoint) + length = np.linalg.norm(diff) + if not length: + return None + if not r: + r = length / 20 + trange = np.linspace(0, length, num=50 * coils) + om = 6.283 * (coils - 0.5) / length + if not r2: + r2 = r + pts = [] + for t in trange: + f = (length - t) / length + rd = r * f + r2 * (1 - f) + pts.append([rd * np.cos(om * t), rd * np.sin(om * t), t]) + + pts = [[0, 0, 0]] + pts + [[0, 0, length]] + diff = diff / length + theta = np.arccos(diff[2]) + phi = np.arctan2(diff[1], diff[0]) + sp = Line(pts).polydata(False) + t = vtk.vtkTransform() + t.RotateZ(np.rad2deg(phi)) + t.RotateY(np.rad2deg(theta)) + tf = vtk.vtkTransformPolyDataFilter() + tf.SetInputData(sp) + tf.SetTransform(t) + tf.Update() + tuf = vtk.vtkTubeFilter() + tuf.SetNumberOfSides(12) + tuf.CappingOn() + tuf.SetInputData(tf.GetOutput()) + if not thickness: + thickness = r / 10 + tuf.SetRadius(thickness) + tuf.Update() + Mesh.__init__(self, tuf.GetOutput(), c, alpha) + self.phong() + self.SetPosition(startPoint) + self.base = np.array(startPoint) + self.top = np.array(endPoint) + settings.collectable_actors.append(self) + self.name = "Spring" + + +class Cylinder(Mesh): """ Build a cylinder of specified height and radius `r`, centered at `pos`. @@ -1496,100 +1516,101 @@ def Cylinder(pos=(0,0,0), r=1, height=2, axis=(0,0,1), c="teal", alpha=1, res=24 |Cylinder| """ + def __init__(self, pos=(0,0,0), r=1, height=2, axis=(0,0,1), c="teal", alpha=1, res=24): - if utils.isSequence(pos[0]): # assume user is passing pos=[base, top] - base = np.array(pos[0]) - top = np.array(pos[1]) - pos = (base + top) / 2 - height = np.linalg.norm(top - base) - axis = top - base - axis = utils.versor(axis) - else: - axis = utils.versor(axis) - base = pos - axis * height / 2 - top = pos + axis * height / 2 - - cyl = vtk.vtkCylinderSource() - cyl.SetResolution(res) - cyl.SetRadius(r) - cyl.SetHeight(height) - cyl.Update() - - theta = np.arccos(axis[2]) - phi = np.arctan2(axis[1], axis[0]) - t = vtk.vtkTransform() - t.PostMultiply() - t.RotateX(90) # put it along Z - t.RotateY(np.rad2deg(theta)) - t.RotateZ(np.rad2deg(phi)) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(cyl.GetOutput()) - tf.SetTransform(t) - tf.Update() - pd = tf.GetOutput() - - actor = Actor(pd, c, alpha).phong() - actor.SetPosition(pos) - actor.base = base + pos - actor.top = top + pos - settings.collectable_actors.append(actor) - actor.name = "Cylinder" - return actor - - -def Cone(pos=(0,0,0), r=1, height=3, axis=(0,0,1), c="dg", alpha=1, res=48): + if utils.isSequence(pos[0]): # assume user is passing pos=[base, top] + base = np.array(pos[0]) + top = np.array(pos[1]) + pos = (base + top) / 2 + height = np.linalg.norm(top - base) + axis = top - base + axis = utils.versor(axis) + else: + axis = utils.versor(axis) + base = pos - axis * height / 2 + top = pos + axis * height / 2 + + cyl = vtk.vtkCylinderSource() + cyl.SetResolution(res) + cyl.SetRadius(r) + cyl.SetHeight(height) + cyl.Update() + + theta = np.arccos(axis[2]) + phi = np.arctan2(axis[1], axis[0]) + t = vtk.vtkTransform() + t.PostMultiply() + t.RotateX(90) # put it along Z + t.RotateY(np.rad2deg(theta)) + t.RotateZ(np.rad2deg(phi)) + tf = vtk.vtkTransformPolyDataFilter() + tf.SetInputData(cyl.GetOutput()) + tf.SetTransform(t) + tf.Update() + pd = tf.GetOutput() + + Mesh.__init__(self, pd, c, alpha) + self.phong() + self.SetPosition(pos) + self.base = base + pos + self.top = top + pos + settings.collectable_actors.append(self) + self.name = "Cylinder" + + +class Cone(Mesh): """ Build a cone of specified radius `r` and `height`, centered at `pos`. |Cone| """ - con = vtk.vtkConeSource() - con.SetResolution(res) - con.SetRadius(r) - con.SetHeight(height) - con.SetDirection(axis) - con.Update() - actor = Actor(con.GetOutput(), c, alpha).phong() - actor.SetPosition(pos) - v = utils.versor(axis) * height / 2 - actor.base = pos - v - actor.top = pos + v - settings.collectable_actors.append(actor) - actor.name = "Cone" - return actor - - -def Pyramid(pos=(0,0,0), s=1, height=1, axis=(0,0,1), c="dg", alpha=1): + def __init__(self, pos=(0,0,0), r=1, height=3, axis=(0,0,1), c="dg", alpha=1, res=48): + con = vtk.vtkConeSource() + con.SetResolution(res) + con.SetRadius(r) + con.SetHeight(height) + con.SetDirection(axis) + con.Update() + Mesh.__init__(self, con.GetOutput(), c, alpha) + self.phong() + self.SetPosition(pos) + v = utils.versor(axis) * height / 2 + self.base = pos - v + self.top = pos + v + settings.collectable_actors.append(self) + self.name = "Cone" + +class Pyramid(Cone): """ Build a pyramid of specified base size `s` and `height`, centered at `pos`. """ - actor = Cone(pos, s, height, axis, c, alpha, 4) - actor.name = "Pyramid" - return actor + def __init__(self, pos=(0,0,0), s=1, height=1, axis=(0,0,1), c="dg", alpha=1): + Cone.__init__(self, pos, s, height, axis, c, alpha, 4) + self.name = "Pyramid" -def Torus(pos=(0, 0, 0), r=1, thickness=0.2, c="khaki", alpha=1, res=30): +class Torus(Mesh): """ Build a torus of specified outer radius `r` internal radius `thickness`, centered at `pos`. |gas| |gas.py|_ """ - rs = vtk.vtkParametricTorus() - rs.SetRingRadius(r) - rs.SetCrossSectionRadius(thickness) - pfs = vtk.vtkParametricFunctionSource() - pfs.SetParametricFunction(rs) - pfs.SetUResolution(res * 3) - pfs.SetVResolution(res) - pfs.Update() - actor = Actor(pfs.GetOutput(), c, alpha).phong() - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Torus" - return actor - - -def Paraboloid(pos=(0,0,0), r=1, height=1, c="cyan", alpha=1, res=50): + def __init__(self, pos=(0, 0, 0), r=1, thickness=0.2, c="khaki", alpha=1, res=30): + rs = vtk.vtkParametricTorus() + rs.SetRingRadius(r) + rs.SetCrossSectionRadius(thickness) + pfs = vtk.vtkParametricFunctionSource() + pfs.SetParametricFunction(rs) + pfs.SetUResolution(res * 3) + pfs.SetVResolution(res) + pfs.Update() + Mesh.__init__(self, pfs.GetOutput(), c, alpha) + self.phong() + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Torus" + +class Paraboloid(Mesh): """ Build a paraboloid of specified height and radius `r`, centered at `pos`. @@ -1599,29 +1620,31 @@ def Paraboloid(pos=(0,0,0), r=1, height=1, c="cyan", alpha=1, res=50): |paraboloid| """ - quadric = vtk.vtkQuadric() - quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) - # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 - # + a3*x*y + a4*y*z + a5*x*z - # + a6*x + a7*y + a8*z +a9 - sample = vtk.vtkSampleFunction() - sample.SetSampleDimensions(res, res, res) - sample.SetImplicitFunction(quadric) - contours = vtk.vtkContourFilter() - contours.SetInputConnection(sample.GetOutputPort()) - contours.GenerateValues(1, 0.01, 0.01) - contours.Update() + def __init__(self, pos=(0,0,0), r=1, height=1, c="cyan", alpha=1, res=50): + quadric = vtk.vtkQuadric() + quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) + # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 + # + a3*x*y + a4*y*z + a5*x*z + # + a6*x + a7*y + a8*z +a9 + sample = vtk.vtkSampleFunction() + sample.SetSampleDimensions(res, res, res) + sample.SetImplicitFunction(quadric) - actor = Actor(contours.GetOutput(), c, alpha).flipNormals().phong() - actor.mapper().ScalarVisibilityOff() - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Paraboloid" - return actor + contours = vtk.vtkContourFilter() + contours.SetInputConnection(sample.GetOutputPort()) + contours.GenerateValues(1, 0.01, 0.01) + contours.Update() + Mesh.__init__(self, contours.GetOutput(), c, alpha) + self.flipNormals().phong() + self.mapper().ScalarVisibilityOff() + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Paraboloid" -def Hyperboloid(pos=(0,0,0), a2=1, value=0.5, height=1, c="m", alpha=1, res=100): + +class Hyperboloid(Mesh): """ Build a hyperboloid of specified aperture `a2` and `height`, centered at `pos`. @@ -1630,26 +1653,27 @@ def Hyperboloid(pos=(0,0,0), a2=1, value=0.5, height=1, c="m", alpha=1, res=100) |mesh_bands| |mesh_bands.py|_ """ - q = vtk.vtkQuadric() - q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) - # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 - # + a3*x*y + a4*y*z + a5*x*z - # + a6*x + a7*y + a8*z +a9 - sample = vtk.vtkSampleFunction() - sample.SetSampleDimensions(res, res, res) - sample.SetImplicitFunction(q) - - contours = vtk.vtkContourFilter() - contours.SetInputConnection(sample.GetOutputPort()) - contours.GenerateValues(1, value, value) - contours.Update() - - actor = Actor(contours.GetOutput(), c, alpha).flipNormals().phong() - actor.mapper().ScalarVisibilityOff() - actor.SetPosition(pos) - settings.collectable_actors.append(actor) - actor.name = "Hyperboloid" - return actor + def __init__(self, pos=(0,0,0), a2=1, value=0.5, height=1, c="m", alpha=1, res=100): + q = vtk.vtkQuadric() + q.SetCoefficients(2, 2, -1 / a2, 0, 0, 0, 0, 0, 0, 0) + # F(x,y,z) = a0*x^2 + a1*y^2 + a2*z^2 + # + a3*x*y + a4*y*z + a5*x*z + # + a6*x + a7*y + a8*z +a9 + sample = vtk.vtkSampleFunction() + sample.SetSampleDimensions(res, res, res) + sample.SetImplicitFunction(q) + + contours = vtk.vtkContourFilter() + contours.SetInputConnection(sample.GetOutputPort()) + contours.GenerateValues(1, value, value) + contours.Update() + + Mesh.__init__(self, contours.GetOutput(), c, alpha) + self.flipNormals().phong() + self.mapper().ScalarVisibilityOff() + self.SetPosition(pos) + settings.collectable_actors.append(self) + self.name = "Hyperboloid" def Text( @@ -1665,7 +1689,7 @@ def Text( font="courier", ): """ - Returns an ``Actor`` that shows a 2D/3D text. + Returns a polygonal ``Mesh`` that shows a 2D/3D text. :param pos: position in 3D space, a 2D text is placed in one of the 8 positions: @@ -1840,27 +1864,17 @@ def Text( extrude.SetScaleFactor(depth*dy) extrude.Update() tpoly = extrude.GetOutput() - ttactor = Actor(tpoly, c, alpha) + ttmesh = Mesh(tpoly, c, alpha) if bc is not None: - ttactor.backColor(bc) + ttmesh.backColor(bc) - ttactor.SetPosition(pos) - settings.collectable_actors.append(ttactor) - ttactor.name = "Text" - return ttactor + ttmesh.SetPosition(pos) + settings.collectable_actors.append(ttmesh) + ttmesh.name = "Text" + return ttmesh -def Latex( - formula, - pos=(0, 0, 0), - c='k', - s=1, - bg=None, - alpha=1, - res=30, - usetex=False, - fromweb=False, -): +class Latex(Picture): """ Render Latex formulas. @@ -1872,85 +1886,91 @@ def Latex( :param bool usetex: use latex compiler of matplotlib :param fromweb: retrieve the latex image from online server (codecogs) - You can access the latex formula from the `Actor` object with `actor.info['formula']`. + You can access the latex formula from the `Mesh` object with `mesh.info['formula']`. |latex| |latex.py|_ """ - vactor = None - try: + def __init__(self, + formula, + pos=(0, 0, 0), + c='k', + s=1, + bg=None, + alpha=1, + res=30, + usetex=False, + fromweb=False, + ): + try: - def build_img_web(formula, tfile): - import requests - if c == 'k': - ct = 'Black' + def build_img_web(formula, tfile): + import requests + if c == 'k': + ct = 'Black' + else: + ct = 'White' + wsite = 'http://latex.codecogs.com/png.latex' + try: + r = requests.get(wsite+'?\dpi{100} \huge \color{'+ct+'} ' + formula) + f = open(tfile, 'wb') + f.write(r.content) + f.close() + except requests.exceptions.ConnectionError: + printc('Latex error. Web site unavailable?', wsite, c=1) + + def build_img_plt(formula, tfile): + import matplotlib.pyplot as plt + + plt.rc('text', usetex=usetex) + + formula1 = '$'+formula+'$' + plt.axis('off') + col = getColor(c) + if bg: + bx = dict(boxstyle="square", ec=col, fc=getColor(bg)) + else: + bx = None + plt.text(0.5, 0.5, formula1, + size=res, + color=col, + alpha=alpha, + ha="center", + va="center", + bbox=bx) + plt.savefig('_lateximg.png', format='png', + transparent=True, bbox_inches='tight', pad_inches=0) + plt.close() + + if fromweb: + build_img_web(formula, '_lateximg.png') else: - ct = 'White' - wsite = 'http://latex.codecogs.com/png.latex' + build_img_plt(formula, '_lateximg.png') + + Picture.__init__(self, '_lateximg.png') + self.info['formula'] = formula + self.alpha(alpha) + b = self.GetBounds() + xm, ym = (b[1]+b[0])/200*s, (b[3]+b[2])/200*s + self.SetOrigin(-xm, -ym, 0) + self.SetScale(0.25/res*s, 0.25/res*s, 0.25/res*s) + self.SetPosition(pos) try: - r = requests.get(wsite+'?\dpi{100} \huge \color{'+ct+'} ' + formula) - f = open(tfile, 'wb') - f.write(r.content) - f.close() - except requests.exceptions.ConnectionError: - printc('Latex error. Web site unavailable?', wsite, c=1) - return None - - def build_img_plt(formula, tfile): - import matplotlib.pyplot as plt + import os + os.unlink('_lateximg.png') + except: + pass - plt.rc('text', usetex=usetex) - - formula1 = '$'+formula+'$' - plt.axis('off') - col = getColor(c) - if bg: - bx = dict(boxstyle="square", ec=col, fc=getColor(bg)) - else: - bx = None - plt.text(0.5, 0.5, formula1, - size=res, - color=col, - alpha=alpha, - ha="center", - va="center", - bbox=bx) - plt.savefig('_lateximg.png', format='png', - transparent=True, bbox_inches='tight', pad_inches=0) - plt.close() - - if fromweb: - build_img_web(formula, '_lateximg.png') - else: - build_img_plt(formula, '_lateximg.png') - - from vtkplotter.actors import Picture - - vactor = Picture('_lateximg.png') - vactor.info['formula'] = formula - vactor.alpha(alpha) - b = vactor.GetBounds() - xm, ym = (b[1]+b[0])/200*s, (b[3]+b[2])/200*s - vactor.SetOrigin(-xm, -ym, 0) - vactor.SetScale(0.25/res*s, 0.25/res*s, 0.25/res*s) - vactor.SetPosition(pos) - try: - import os - os.unlink('_lateximg.png') except: - pass - - except: - printc('Error in Latex()\n', formula, c=1) - printc(' latex or dvipng not installed?', c=1) - printc(' Try: usetex=False' , c=1) - printc(' Try: sudo apt install dvipng' , c=1) + printc('Error in Latex()\n', formula, c=1) + printc(' latex or dvipng not installed?', c=1) + printc(' Try: usetex=False' , c=1) + printc(' Try: sudo apt install dvipng' , c=1) - settings.collectable_actors.append(vactor) - vactor.name = "Latex" - return vactor + settings.collectable_actors.append(self) + self.name = "Latex" -def ParametricShape(name, c='powderblue', alpha=1, res=51): +class ParametricShape(Mesh): """ A set of built-in shapes for illustration purposes. @@ -1972,65 +1992,64 @@ def ParametricShape(name, c='powderblue', alpha=1, res=51): |paramshapes| """ - shapes = ['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', - 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', - 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', - 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere'] - - if isinstance(name, int): - name = name%len(shapes) - name = shapes[name] - - if name == 'Boy': ps = vtk.vtkParametricBoy() - elif name == 'ConicSpiral': ps = vtk.vtkParametricConicSpiral() - elif name == 'CrossCap': ps = vtk.vtkParametricCrossCap() - elif name == 'Dini': ps = vtk.vtkParametricDini() - elif name == 'Enneper': ps = vtk.vtkParametricEnneper() - elif name == 'Figure8Klein': ps = vtk.vtkParametricFigure8Klein() - elif name == 'Klein': ps = vtk.vtkParametricKlein() - elif name == 'Mobius': - ps = vtk.vtkParametricMobius() - ps.SetRadius(2.0) - ps.SetMinimumV(-0.5) - ps.SetMaximumV(0.5) - elif name == 'RandomHills': - ps = vtk.vtkParametricRandomHills() - ps.AllowRandomGenerationOn() - ps.SetRandomSeed(1) - ps.SetNumberOfHills(25) - elif name == 'Roman': ps = vtk.vtkParametricRoman() - elif name == 'SuperEllipsoid': - ps = vtk.vtkParametricSuperEllipsoid() - ps.SetN1(0.5) - ps.SetN2(0.4) - elif name == 'BohemianDome': - ps = vtk.vtkParametricBohemianDome() - ps.SetA(5.0) - ps.SetB(1.0) - ps.SetC(2.0) - elif name == 'Bour': ps = vtk.vtkParametricBour() - elif name == 'CatalanMinimal': ps = vtk.vtkParametricCatalanMinimal() - elif name == 'Henneberg': ps = vtk.vtkParametricHenneberg() - elif name == 'Kuen': - ps = vtk.vtkParametricKuen() - ps.SetDeltaV0(0.001) - elif name == 'PluckerConoid': ps = vtk.vtkParametricPluckerConoid() - elif name == 'Pseudosphere': ps = vtk.vtkParametricPseudosphere() - else: - printc("Error in ParametricShape: unknown name", name, c=1) - printc("Available shape names:\n", shapes) - return None - - pfs = vtk.vtkParametricFunctionSource() - pfs.SetParametricFunction(ps) - pfs.SetUResolution(res) - pfs.SetVResolution(res) - pfs.SetWResolution(res) - pfs.Update() - - actor = Actor(pfs.GetOutput(), c, alpha) - if name != 'Kuen': actor.normalize() - settings.collectable_actors.append(actor) - actor.name = name - return actor - + def __init__(self, name, c='powderblue', alpha=1, res=51): + shapes = ['Boy', 'ConicSpiral', 'CrossCap', 'Dini', 'Enneper', + 'Figure8Klein', 'Klein', 'Mobius', 'RandomHills', 'Roman', + 'SuperEllipsoid', 'BohemianDome', 'Bour', 'CatalanMinimal', + 'Henneberg', 'Kuen', 'PluckerConoid', 'Pseudosphere'] + + if isinstance(name, int): + name = name%len(shapes) + name = shapes[name] + + if name == 'Boy': ps = vtk.vtkParametricBoy() + elif name == 'ConicSpiral': ps = vtk.vtkParametricConicSpiral() + elif name == 'CrossCap': ps = vtk.vtkParametricCrossCap() + elif name == 'Dini': ps = vtk.vtkParametricDini() + elif name == 'Enneper': ps = vtk.vtkParametricEnneper() + elif name == 'Figure8Klein': ps = vtk.vtkParametricFigure8Klein() + elif name == 'Klein': ps = vtk.vtkParametricKlein() + elif name == 'Mobius': + ps = vtk.vtkParametricMobius() + ps.SetRadius(2.0) + ps.SetMinimumV(-0.5) + ps.SetMaximumV(0.5) + elif name == 'RandomHills': + ps = vtk.vtkParametricRandomHills() + ps.AllowRandomGenerationOn() + ps.SetRandomSeed(1) + ps.SetNumberOfHills(25) + elif name == 'Roman': ps = vtk.vtkParametricRoman() + elif name == 'SuperEllipsoid': + ps = vtk.vtkParametricSuperEllipsoid() + ps.SetN1(0.5) + ps.SetN2(0.4) + elif name == 'BohemianDome': + ps = vtk.vtkParametricBohemianDome() + ps.SetA(5.0) + ps.SetB(1.0) + ps.SetC(2.0) + elif name == 'Bour': ps = vtk.vtkParametricBour() + elif name == 'CatalanMinimal': ps = vtk.vtkParametricCatalanMinimal() + elif name == 'Henneberg': ps = vtk.vtkParametricHenneberg() + elif name == 'Kuen': + ps = vtk.vtkParametricKuen() + ps.SetDeltaV0(0.001) + elif name == 'PluckerConoid': ps = vtk.vtkParametricPluckerConoid() + elif name == 'Pseudosphere': ps = vtk.vtkParametricPseudosphere() + else: + printc("Error in ParametricShape: unknown name", name, c=1) + printc("Available shape names:\n", shapes) + return None + + pfs = vtk.vtkParametricFunctionSource() + pfs.SetParametricFunction(ps) + pfs.SetUResolution(res) + pfs.SetVResolution(res) + pfs.SetWResolution(res) + pfs.Update() + + Mesh.__init__(self, pfs.GetOutput(), c, alpha) + if name != 'Kuen': self.normalize() + settings.collectable_actors.append(self) + self.name = name diff --git a/vtkplotter/utils.py b/vtkplotter/utils.py index 65438410..8850f12f 100644 --- a/vtkplotter/utils.py +++ b/vtkplotter/utils.py @@ -188,17 +188,17 @@ def geometry(obj, extent=None): For example, this filter will extract the outer surface of a volume or structured grid dataset. - Returns an ``Actor`` object. + Returns a ``Mesh`` object. :param list extent: set a `[xmin,xmax, ymin,ymax, zmin,zmax]` bounding box to clip data. """ - from vtkplotter.actors import Actor + from vtkplotter.mesh import Mesh gf = vtk.vtkGeometryFilter() gf.SetInputData(obj) if extent is not None: gf.SetExtent(extent) gf.Update() - return Actor(gf.GetOutput()) + return Mesh(gf.GetOutput()) def buildPolyData(vertices, faces=None, lines=None, indexOffset=0, fast=True): @@ -408,7 +408,7 @@ def linInterpolate(x, rangeX, rangeY): E.g. if x runs in rangeX=[x0,x1] and I want it to run in rangeY=[y0,y1] then y = linInterpolate(x, rangeX, rangeY) will interpolate x onto rangeY. - + |linInterpolate| |linInterpolate.py|_ """ if isSequence(x): @@ -968,7 +968,7 @@ def printvtkactor(actor, tab=""): bns.append(b) if len(bns) == 0: return - acts = obj.getActors() + acts = obj.getMeshes() colors.printc("_" * 65, c="c", bold=0) colors.printc("Plotter", invert=1, dim=1, c="c", end=" ") otit = obj.title @@ -1006,7 +1006,7 @@ def printvtkactor(actor, tab=""): bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) colors.printc(" z=(" + bz1 + ", " + bz2 + ")", c="b", bold=0) - colors.printc(" Click actor and press i for Actor info.", c="c") + colors.printc(" Click mesh and press i for info.", c="c") else: colors.printc("_" * 65, c="g", bold=0) @@ -1019,7 +1019,7 @@ def printHistogram(data, bins=10, height=10, logscale=False, minbin=0, c=None, bold=True, title='Histogram'): """ Ascii histogram printing. - Input can also be ``Volume`` or ``Actor``. + Input can also be ``Volume`` or ``Mesh``. Returns the raw data before binning (useful when passing vtk objects). :param int bins: number of histogram bins @@ -1327,22 +1327,22 @@ def vtkCameraToK3D(vtkcam): #Check the example gallery in: examples/other/trimesh> ########################################################################### -def vtk2trimesh(actor): +def vtk2trimesh(mesh): """ - Convert vtk ``Actor`` to ``Trimesh`` object. + Convert ``vtkplotter.Mesh`` to ``Trimesh.Mesh`` object. """ - if isSequence(actor): + if isSequence(mesh): tms = [] - for a in actor: + for a in mesh: tms.append(vtk2trimesh(a)) return tms from trimesh import Trimesh - lut = actor.mapper().GetLookupTable() + lut = mesh.mapper().GetLookupTable() - tris = actor.faces() - carr = actor.scalars('CellColors', datatype='cell') + tris = mesh.faces() + carr = mesh.getCellArray('CellColors') ccols = None if carr is not None and len(carr)==len(tris): ccols = [] @@ -1351,8 +1351,8 @@ def vtk2trimesh(actor): ccols.append((r*255, g*255, b*255, a*255)) ccols = np.array(ccols, dtype=np.int16) - points = actor.coordinates() - varr = actor.scalars('VertexColors', datatype='point') + points = mesh.points() + varr = mesh.getPointArray('VertexColors') vcols = None if varr is not None and len(varr)==len(points): vcols = [] @@ -1370,7 +1370,7 @@ def vtk2trimesh(actor): def trimesh2vtk(inputobj, alphaPerCell=False): """ - Convert ``Trimesh`` object to ``Actor(vtkActor)`` or ``Assembly`` object. + Convert ``Trimesh`` object to ``Mesh(vtkActor)`` or ``Assembly`` object. """ if isSequence(inputobj): vms = [] @@ -1383,11 +1383,11 @@ def trimesh2vtk(inputobj, alphaPerCell=False): inputobj_type = str(type(inputobj)) if "Trimesh" in inputobj_type or "primitives" in inputobj_type: - from vtkplotter import Actor + from vtkplotter import Mesh faces = inputobj.faces poly = buildPolyData(inputobj.vertices, faces) - tact = Actor(poly) + tact = Mesh(poly) if inputobj.visual.kind == "face": trim_c = inputobj.visual.face_colors else: @@ -1426,7 +1426,7 @@ def trimesh2vtk(inputobj, alphaPerCell=False): elif "path" in inputobj_type: from vtkplotter.shapes import Line - from vtkplotter.actors import Assembly + from vtkplotter.assembly import Assembly lines = [] for e in inputobj.entities: diff --git a/vtkplotter/version.py b/vtkplotter/version.py index 1262fb52..e9fd90c5 100644 --- a/vtkplotter/version.py +++ b/vtkplotter/version.py @@ -1 +1 @@ -_version='2019.4.11' +_version='2020.0.1' diff --git a/vtkplotter/volume.py b/vtkplotter/volume.py new file mode 100644 index 00000000..d6b37760 --- /dev/null +++ b/vtkplotter/volume.py @@ -0,0 +1,633 @@ +from __future__ import division, print_function + +import numpy as np +import vtk +import vtkplotter.colors as colors +import vtkplotter.docs as docs +import vtkplotter.utils as utils +from vtk.util.numpy_support import numpy_to_vtk +from vtkplotter.base import ActorBase +from vtkplotter.mesh import Mesh + +__doc__ = ( + """ +Submodule extending the ``vtkVolume`` object functionality. +""" + + docs._defs +) + +__all__ = ["Volume"] + + +########################################################################## +class Volume(vtk.vtkVolume, ActorBase): + """Derived class of ``vtkVolume``. + Can be initialized with a numpy object, see e.g.: |numpy2volume.py|_ + + :param c: sets colors along the scalar range, or a matplotlib color map name + :type c: list, str + :param alphas: sets transparencies along the scalar range + :type c: float, list + :param list origin: set volume origin coordinates + :param list spacing: voxel dimensions in x, y and z. + :param list shape: specify the shape. + :param str mapperType: either 'gpu', 'opengl_gpu', 'fixed' or 'smart' + + :param int mode: define the volumetric rendering style: + + - 0, Composite rendering + - 1, maximum projection rendering + - 2, minimum projection + - 3, average projection + - 4, additive mode + + .. hint:: if a `list` of values is used for `alphas` this is interpreted + as a transfer function along the range of the scalar. + + |read_vti| |read_vti.py|_ + """ + + def __init__(self, inputobj, + c=('b','lb','lg','y','r'), + alpha=(0.0, 0.0, 0.2, 0.4, 0.8, 1), + alphaGradient=None, + mode=0, + origin=None, + spacing=None, + shape=None, + mapperType='gpu', + ): + + vtk.vtkVolume.__init__(self) + ActorBase.__init__(self) + + inputtype = str(type(inputobj)) + #colors.printc('Volume inputtype', inputtype) + + if inputobj is None: + img = vtk.vtkImageData() + + elif utils.isSequence(inputobj): + if "ndarray" not in inputtype: + inputobj = np.array(inputobj) + + varr = numpy_to_vtk(inputobj.ravel(order='F'), + deep=True, array_type=vtk.VTK_FLOAT) + varr.SetName('input_scalars') + + img = vtk.vtkImageData() + if shape is not None: + img.SetDimensions(shape) + else: + img.SetDimensions(inputobj.shape) + img.GetPointData().SetScalars(varr) + + #to convert rgb to numpy + # img_scalar = data.GetPointData().GetScalars() + # dims = data.GetDimensions() + # n_comp = img_scalar.GetNumberOfComponents() + # temp = numpy_support.vtk_to_numpy(img_scalar) + # numpy_data = temp.reshape(dims[1],dims[0],n_comp) + # numpy_data = numpy_data.transpose(0,1,2) + # numpy_data = np.flipud(numpy_data) + + elif "ImageData" in inputtype: + img = inputobj + elif "UniformGrid" in inputtype: + img = inputobj + elif "UnstructuredGrid" in inputtype: + img = inputobj + mapperType = 'tetra' + elif hasattr(inputobj, "GetOutput"): # passing vtk object, try extract imagdedata + if hasattr(inputobj, "Update"): + inputobj.Update() + img = inputobj.GetOutput() + else: + colors.printc("Volume(): cannot understand input type:\n", inputtype, c=1) + return + + if 'gpu' in mapperType: + self._mapper = vtk.vtkGPUVolumeRayCastMapper() + elif 'opengl_gpu' in mapperType: + self._mapper = vtk.vtkOpenGLGPUVolumeRayCastMapper() + elif 'smart' in mapperType: + self._mapper = vtk.vtkSmartVolumeMapper() + elif 'fixed' in mapperType: + self._mapper = vtk.vtkFixedPointVolumeRayCastMapper() + elif 'tetra' in mapperType: + self._mapper = vtk.vtkProjectedTetrahedraMapper() + elif 'unstr' in mapperType: + self._mapper = vtk.vtkUnstructuredGridVolumeRayCastMapper() + + if origin is not None: + img.SetOrigin(origin) + if spacing is not None: + img.SetSpacing(spacing) + if shape is not None: + img.SetDimensions(shape) + + self._imagedata = img + self._mapper.SetInputData(img) + self.SetMapper(self._mapper) + self.mode(mode).color(c).alpha(alpha).alphaGradient(alphaGradient) + # remember stuff: + self._mode = mode + self._color = c + self._alpha = alpha + self._alphaGrad = alphaGradient + + def _update(self, img): + self._imagedata = img + self._mapper.SetInputData(img) + self._mapper.Modified() + return self + + def mode(self, mode=None): + """Define the volumetric rendering style. + + - 0, Composite rendering + - 1, maximum projection rendering + - 2, minimum projection + - 3, average projection + - 4, additive mode + """ + if mode is None: + return self._mapper.GetBlendMode() + + volumeProperty = self.GetProperty() + self._mapper.SetBlendMode(mode) + self._mode = mode + if mode == 0: + volumeProperty.ShadeOn() + self.lighting('shiny') + self.jittering(True) + elif mode == 1: + volumeProperty.ShadeOff() + self.jittering(True) + return self + + def jittering(self, status=None): + """If `jittering` is `True`, each ray traversal direction will be perturbed slightly + using a noise-texture to get rid of wood-grain effects. + """ + if hasattr(self._mapper, 'SetUseJittering'): + if status is None: + return self._mapper.GetUseJittering() + self._mapper.SetUseJittering(status) + return self + return None + + def imagedata(self): + """Return the underlying ``vtkImagaData`` object.""" + return self._imagedata + + def dimensions(self): + """Return the nr. of voxels in the 3 dimensions.""" + return self._imagedata.GetDimensions() + + def spacing(self, s=None): + """Set/get the voxels size in the 3 dimensions.""" + if s is not None: + self._imagedata.SetSpacing(s) + self._mapper.Modified() + return self + else: + return np.array(self._imagedata.GetSpacing()) + + def permuteAxes(self, x, y ,z): + """Reorder the axes of the Volume by specifying + the input axes which are supposed to become the new X, Y, and Z.""" + imp = vtk.vtkImagePermute() + imp.SetFilteredAxes(x,y,z) + imp. SetInputData(self.imagedata()) + imp.Update() + return self._update(imp.GetOutput()) + + def resample(self, newSpacing, interpolation=1): + """ + Resamples a ``Volume`` to be larger or smaller. + + This method modifies the spacing of the input. + Linear interpolation is used to resample the data. + + :param list newSpacing: a list of 3 new spacings for the 3 axes. + :param int interpolation: 0=nearest_neighbor, 1=linear, 2=cubic + """ + rsp = vtk.vtkImageResample() + oldsp = self.GetSpacing() + for i in range(3): + if oldsp[i] != newSpacing[i]: + rsp.SetAxisOutputSpacing(i, newSpacing[i]) + rsp.InterpolateOn() + rsp.SetInterpolationMode(interpolation) + rsp.OptimizationOn() + rsp.Update() + return self._update(rsp.GetOutput()) + + + def color(self, col): + """Assign a color or a set of colors to a volume along the range of the scalar value. + A single constant color can also be assigned. + Any matplotlib color map name is also accepted, e.g. ``volume.color('jet')``. + + E.g.: say that your voxel scalar runs from -3 to 6, + and you want -3 to show red and 1.5 violet and 6 green, then just set: + + ``volume.color(['red', 'violet', 'green'])`` + """ + smin, smax = self._imagedata.GetScalarRange() + volumeProperty = self.GetProperty() + ctf = vtk.vtkColorTransferFunction() + self._color = col + + if utils.isSequence(col): + for i, ci in enumerate(col): + r, g, b = colors.getColor(ci) + xalpha = smin + (smax - smin) * i / (len(col) - 1) + ctf.AddRGBPoint(xalpha, r, g, b) + #colors.printc('\tcolor at', round(xalpha, 1), + # '\tset to', colors.getColorName((r, g, b)), c='b', bold=0) + elif isinstance(col, str): + if col in colors.colors.keys() or col in colors.color_nicks.keys(): + r, g, b = colors.getColor(col) + ctf.AddRGBPoint(smin, r,g,b) # constant color + ctf.AddRGBPoint(smax, r,g,b) + elif colors._mapscales: + for x in np.linspace(smin, smax, num=64, endpoint=True): + r,g,b = colors.colorMap(x, name=col, vmin=smin, vmax=smax) + ctf.AddRGBPoint(x, r, g, b) + elif isinstance(col, int): + r, g, b = colors.getColor(col) + ctf.AddRGBPoint(smin, r,g,b) # constant color + ctf.AddRGBPoint(smax, r,g,b) + else: + colors.printc("volume.color(): unknown input type:", col, c=1) + + volumeProperty.SetColor(ctf) + volumeProperty.SetInterpolationTypeToLinear() + #volumeProperty.SetInterpolationTypeToNearest() + return self + + def alpha(self, alpha): + """Assign a set of tranparencies to a volume along the range of the scalar value. + A single constant value can also be assigned. + + E.g.: say alpha=(0.0, 0.3, 0.9, 1) and the scalar range goes from -10 to 150. + Then all voxels with a value close to -10 will be completely transparent, voxels at 1/4 + of the range will get an alpha equal to 0.3 and voxels with value close to 150 + will be completely opaque. + """ + volumeProperty = self.GetProperty() + smin, smax = self._imagedata.GetScalarRange() + opacityTransferFunction = vtk.vtkPiecewiseFunction() + self._alpha = alpha + + if utils.isSequence(alpha): + for i, al in enumerate(alpha): + xalpha = smin + (smax - smin) * i / (len(alpha) - 1) + # Create transfer mapping scalar value to opacity + opacityTransferFunction.AddPoint(xalpha, al) + #colors.printc("alpha at", round(xalpha, 1), "\tset to", al, c="b", bold=0) + else: + opacityTransferFunction.AddPoint(smin, alpha) # constant alpha + opacityTransferFunction.AddPoint(smax, alpha) + + volumeProperty.SetScalarOpacity(opacityTransferFunction) + volumeProperty.SetInterpolationTypeToLinear() + return self + + def alphaGradient(self, alphaGrad): + """ + Assign a set of tranparencies to a volume's gradient + along the range of the scalar value. + A single constant value can also be assigned. + The gradient function is used to decrease the opacity + in the "flat" regions of the volume while maintaining the opacity + at the boundaries between material types. The gradient is measured + as the amount by which the intensity changes over unit distance. + + |read_vti| |read_vti.py|_ + """ + self._alphaGrad = alphaGrad + volumeProperty = self.GetProperty() + if alphaGrad is None: + volumeProperty.DisableGradientOpacityOn() + return self + else: + volumeProperty.DisableGradientOpacityOff() + + #smin, smax = self._imagedata.GetScalarRange() + smin, smax = 0, 255 + gotf = vtk.vtkPiecewiseFunction() + if utils.isSequence(alphaGrad): + for i, al in enumerate(alphaGrad): + xalpha = smin + (smax - smin) * i / (len(alphaGrad) - 1) + # Create transfer mapping scalar value to gradient opacity + gotf.AddPoint(xalpha, al) + #colors.printc("alphaGrad at", round(xalpha, 1), "\tset to", al, c="b", bold=0) + else: + gotf.AddPoint(smin, alphaGrad) # constant alphaGrad + gotf.AddPoint(smax, alphaGrad) + + volumeProperty.SetGradientOpacity(gotf) + volumeProperty.SetInterpolationTypeToLinear() + return self + + def threshold(self, vmin=None, vmax=None, replaceWith=0): + """ + Binary or continuous volume thresholding. + Find the voxels that contain the value below/above or inbetween + [vmin, vmax] and replaces it with the provided value (default is 0). + """ + th = vtk.vtkImageThreshold() + th.SetInputData(self.imagedata()) + + if vmin is not None and vmax is not None: + th.ThresholdBetween(vmin, vmax) + elif vmin is not None: + th.ThresholdByLower(vmin) + elif vmax is not None: + th.ThresholdByUpper(vmax) + + th.SetInValue(replaceWith) + th.Update() + return self._update(th.GetOutput()) + + def crop(self, + top=None, bottom=None, + right=None, left=None, + front=None, back=None, VOI=()): + """Crop a ``Volume`` object. + + :param float top: fraction to crop from the top plane (positive z) + :param float bottom: fraction to crop from the bottom plane (negative z) + :param float front: fraction to crop from the front plane (positive y) + :param float back: fraction to crop from the back plane (negative y) + :param float right: fraction to crop from the right plane (positive x) + :param float left: fraction to crop from the left plane (negative x) + :param list VOI: extract Volume Of Interest expressed in voxel numbers + + Eg.: vol.crop(VOI=(xmin, xmax, ymin, ymax, zmin, zmax)) # all integers nrs + """ + extractVOI = vtk.vtkExtractVOI() + extractVOI.SetInputData(self.imagedata()) + + if len(VOI): + extractVOI.SetVOI(VOI) + else: + d = self.imagedata().GetDimensions() + bx0, bx1, by0, by1, bz0, bz1 = 0, d[0]-1, 0, d[1]-1, 0, d[2]-1 + if left is not None: bx0 = int((d[0]-1)*left) + if right is not None: bx1 = int((d[0]-1)*(1-right)) + if back is not None: by0 = int((d[1]-1)*back) + if front is not None: by1 = int((d[1]-1)*(1-front)) + if bottom is not None: bz0 = int((d[2]-1)*bottom) + if top is not None: bz1 = int((d[2]-1)*(1-top)) + extractVOI.SetVOI(bx0, bx1, by0, by1, bz0, bz1) + extractVOI.Update() + return self._update(extractVOI.GetOutput()) + + def append(self, volumes, axis='z', preserveExtents=False): + """ + Take the components from multiple inputs and merges them into one output. + Except for the append axis, all inputs must have the same extent. + All inputs must have the same number of scalar components. + The output has the same origin and spacing as the first input. + The origin and spacing of all other inputs are ignored. + All inputs must have the same scalar type. + + :param int,str axis: axis expanded to hold the multiple images. + :param bool preserveExtents: if True, the extent of the inputs is used to place + the image in the output. The whole extent of the output is the union of the input + whole extents. Any portion of the output not covered by the inputs is set to zero. + The origin and spacing is taken from the first input. + + .. code-block:: python + + from vtkplotter import load, datadir + vol = load(datadir+'embryo.tif') + vol.append(vol, axis='x').show() + """ + ima = vtk.vtkImageAppend() + ima.SetInputData(self.imagedata()) + if not utils.isSequence(volumes): + volumes = [volumes] + for volume in volumes: + if isinstance(volume, vtk.vtkImageData): + ima.AddInputData(volume) + else: + ima.AddInputData(volume.imagedata()) + ima.SetPreserveExtents(preserveExtents) + if axis == "x": + axis = 0 + elif axis == "y": + axis = 1 + elif axis == "z": + axis = 2 + ima.SetAppendAxis(axis) + ima.Update() + return self._update(ima.GetOutput()) + + def cutWithPlane(self, origin=(0,0,0), normal=(1,0,0)): + """ + Cuts ``Volume`` with the plane defined by a point and a normal + creating a tetrahedral mesh object. + Makes sense only if the plane is not along any of the cartesian planes, + otherwise use ``crop()`` which is way faster. + + :param origin: the cutting plane goes through this point + :param normal: normal of the cutting plane + """ + plane = vtk.vtkPlane() + plane.SetOrigin(origin) + plane.SetNormal(normal) + + clipper = vtk.vtkClipVolume() + clipper.SetInputData(self._imagedata) + clipper.SetClipFunction(plane) + clipper.GenerateClipScalarsOff() + clipper.GenerateClippedOutputOff() + clipper.Mixed3DCellGenerationOff() # generate only tets + clipper.SetValue(0) + clipper.Update() + + vol = Volume(clipper.GetOutput()).color(self._color) + return vol #self._update(clipper.GetOutput()) + + + def resize(self, *newdims): + """Increase or reduce the number of voxels of a Volume with interpolation.""" + old_dims = np.array(self.imagedata().GetDimensions()) + old_spac = np.array(self.imagedata().GetSpacing()) + rsz = vtk.vtkImageResize() + rsz.SetResizeMethodToOutputDimensions() + rsz.SetInputData(self.imagedata()) + rsz.SetOutputDimensions(newdims) + rsz.Update() + self._imagedata = rsz.GetOutput() + new_spac = old_spac * old_dims/newdims # keep aspect ratio + self._imagedata.SetSpacing(new_spac) + return self._update(self._imagedata) + + def normalize(self): + """Normalize that scalar components for each point.""" + norm = vtk.vtkImageNormalize() + norm.SetInputData(self.imagedata()) + norm.Update() + return self._update(norm.GetOutput()) + + def scaleVoxels(self, scale=1): + """Scale the voxel content by factor `scale`.""" + rsl = vtk.vtkImageReslice() + rsl.SetInputData(self.imagedata()) + rsl.SetScalarScale(scale) + rsl.Update() + return self._update(rsl.GetOutput()) + + def mirror(self, axis="x"): + """ + Mirror flip along one of the cartesian axes. + + .. note:: ``axis='n'``, will flip only mesh normals. + + |mirror| |mirror.py|_ + """ + img = self.imagedata() + + ff = vtk.vtkImageFlip() + ff.SetInputData(img) + if axis.lower() == "x": + ff.SetFilteredAxis(0) + elif axis.lower() == "y": + ff.SetFilteredAxis(1) + elif axis.lower() == "z": + ff.SetFilteredAxis(2) + else: + colors.printc("~times Error in mirror(): mirror must be set to x, y, z or n.", c=1) + raise RuntimeError() + ff.Update() + return self._update(ff.GetOutput()) + + def xSlice(self, i): + """Extract the slice at index `i` of volume along x-axis.""" + vslice = vtk.vtkImageDataGeometryFilter() + vslice.SetInputData(self.imagedata()) + nx, ny, nz = self.imagedata().GetDimensions() + if i>nx-1: + i=nx-1 + vslice.SetExtent(i,i, 0,ny, 0,nz) + vslice.Update() + return Mesh(vslice.GetOutput()) + + def ySlice(self, j): + """Extract the slice at index `j` of volume along y-axis.""" + vslice = vtk.vtkImageDataGeometryFilter() + vslice.SetInputData(self.imagedata()) + nx, ny, nz = self.imagedata().GetDimensions() + if j>ny-1: + j=ny-1 + vslice.SetExtent(0,nx, j,j, 0,nz) + vslice.Update() + return Mesh(vslice.GetOutput()) + + def zSlice(self, k): + """Extract the slice at index `i` of volume along z-axis.""" + vslice = vtk.vtkImageDataGeometryFilter() + vslice.SetInputData(self.imagedata()) + nx, ny, nz = self.imagedata().GetDimensions() + if k>nz-1: + k=nz-1 + vslice.SetExtent(0,nx, 0,ny, k,k) + vslice.Update() + return Mesh(vslice.GetOutput()) + + + def isosurface(self, threshold=True, connectivity=False): + """Return an ``Mesh`` isosurface extracted from the ``Volume`` object. + + :param threshold: value or list of values to draw the isosurface(s) + :type threshold: float, list + :param bool connectivity: if True only keeps the largest portion of the polydata + + |isosurfaces| |isosurfaces.py|_ + """ + scrange = self._imagedata.GetScalarRange() + cf = vtk.vtkContourFilter() + cf.SetInputData(self._imagedata) + cf.UseScalarTreeOn() + cf.ComputeScalarsOn() + cf.ComputeNormalsOn() + + if utils.isSequence(threshold): + cf.SetNumberOfContours(len(threshold)) + for i, t in enumerate(threshold): + cf.SetValue(i, t) + cf.Update() + else: + if threshold is True: + threshold = (2 * scrange[0] + scrange[1]) / 3.0 + print('automatic threshold set to ' + utils.precision(threshold, 3), end=' ') + print('in [' + utils.precision(scrange[0], 3) + ', ' + utils.precision(scrange[1], 3)+']') + cf.SetValue(0, threshold) + cf.Update() + + clp = vtk.vtkCleanPolyData() + clp.SetInputConnection(cf.GetOutputPort()) + clp.Update() + poly = clp.GetOutput() + + if connectivity: + conn = vtk.vtkPolyDataConnectivityFilter() + conn.SetExtractionModeToLargestRegion() + conn.SetInputData(poly) + conn.Update() + poly = conn.GetOutput() + + a = Mesh(poly, c=None).phong() + a._mapper.SetScalarRange(scrange[0], scrange[1]) + return a + + + def legosurface(self, vmin=None, vmax=None, cmap='afmhot_r'): + """ + Represent a ``Volume`` as lego blocks (voxels). + By default colors correspond to the volume's scalar. + Returns an ``Mesh``. + + :param float vmin: the lower threshold, voxels below this value are not shown. + :param float vmax: the upper threshold, voxels above this value are not shown. + :param str cmap: color mapping of the scalar associated to the voxels. + + |legosurface| |legosurface.py|_ + """ + dataset = vtk.vtkImplicitDataSet() + dataset.SetDataSet(self._imagedata) + window = vtk.vtkImplicitWindowFunction() + window.SetImplicitFunction(dataset) + + srng = list(self._imagedata.GetScalarRange()) + if vmin is not None: + srng[0] = vmin + if vmax is not None: + srng[1] = vmax + window.SetWindowRange(srng) + + extract = vtk.vtkExtractGeometry() + extract.SetInputData(self._imagedata) + extract.SetImplicitFunction(window) + extract.ExtractInsideOff() + extract.ExtractBoundaryCellsOff() + extract.Update() + + gf = vtk.vtkGeometryFilter() + gf.SetInputData(extract.GetOutput()) + gf.Update() + + a = Mesh(gf.GetOutput()).lw(0.1).flat() + + scalars = np.array(a.getPointArray(0), dtype=np.float) + + if cmap: + a.pointColors(scalars, vmin=self._imagedata.GetScalarRange()[0], cmap=cmap) + a.mapPointsToCells() + return a diff --git a/vtkplotter/vtkio.py b/vtkplotter/vtkio.py index db4e27f7..a0de5c33 100644 --- a/vtkplotter/vtkio.py +++ b/vtkplotter/vtkio.py @@ -6,7 +6,12 @@ import vtkplotter.utils as utils import vtkplotter.colors as colors -from vtkplotter.actors import Actor, Volume, Assembly, Picture + +from vtkplotter.assembly import Assembly +from vtkplotter.mesh import Mesh +from vtkplotter.picture import Picture +from vtkplotter.volume import Volume + import vtkplotter.docs as docs import vtkplotter.settings as settings @@ -23,8 +28,8 @@ "gunzip", "loadStructuredPoints", "loadStructuredGrid", - "loadUnStructuredGrid", "loadRectilinearGrid", + "loadUnStructuredGrid", "write", "save", "exportWindow", @@ -36,7 +41,7 @@ def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True): """ - Load ``Actor`` and ``Volume`` from file. + Load ``Mesh`` and ``Volume`` objects from file. The output will depend on the file extension. See examples below. @@ -50,7 +55,7 @@ def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True): :param list alpha: can be a list of any length of tranparencies. This list represents the transparency transfer function values equally spaced along the range of the volumetric scalar. :param float threshold: value to draw the isosurface, False by default to return a ``Volume``. - If set to True will return an ``Actor`` with automatic choice of the isosurfacing threshold. + If set to True will return an ``Mesh`` with automatic choice of the isosurfacing threshold. :param list spacing: specify the voxel spacing in the three dimensions :param bool unpack: only for multiblock data, if True returns a flat list of objects. @@ -59,15 +64,15 @@ def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True): from vtkplotter import datadir, load, show - # Return an Actor + # Return a Mesh object g = load(datadir+'250.vtk') show(g) - # Return a list of 2 Actors + # Return a list of 2 meshes g = load([datadir+'250.vtk', datadir+'270.vtk']) show(g) - # Return a list of actors by reading all files in a directory + # Return a list of meshes by reading all files in a directory # (if directory contains DICOM files then a Volume is returned) g = load(datadir+'timecourse1d/') show(g) @@ -77,7 +82,7 @@ def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True): g.c(['y','lb','w']).alpha((0.0, 0.4, 0.9, 1)) show(g) - # Return an Actor from a SLC volume with automatic thresholding + # Return a Mesh from a SLC volume with automatic thresholding g = load(datadir+'embryo.slc', threshold=True) show(g) """ @@ -288,7 +293,7 @@ def _load_file(filename, c, alpha, threshold, spacing, unpack): colors.printc("~noentry Unable to load", filename, c=1) return None - actor = Actor(routput, c, alpha) + actor = Mesh(routput, c, alpha) if fl.endswith(".txt") or fl.endswith(".xyz"): actor.GetProperty().SetPointSize(4) @@ -438,7 +443,7 @@ def loadOFF(filename): ids += [int(xx) for xx in ts[1:]] faces.append(ids) - return Actor(utils.buildPolyData(vertices, faces)) + return Mesh(utils.buildPolyData(vertices, faces)) def loadGeoJSON(filename): @@ -448,12 +453,12 @@ def loadGeoJSON(filename): jr = vtk.vtkGeoJSONReader() jr.SetFileName(filename) jr.Update() - return Actor(jr.GetOutput()) + return Mesh(jr.GetOutput()) def loadDolfin(filename, exterior=False): """Reads a `Fenics/Dolfin` file format (.xml or .xdmf). - Return an ``Actor(vtkActor)`` object.""" + Return an ``Mesh(vtkActor)`` object.""" import sys if sys.version_info[0] < 3: return _loadDolfin_old(filename) @@ -470,16 +475,16 @@ def loadDolfin(filename, exterior=False): bm = dolfin.BoundaryMesh(m, "exterior") if exterior: - poly = utils.buildPolyData(bm.coordinates(), bm.cells(), fast=True) + poly = utils.buildPolyData(bm.points(), bm.cells(), fast=True) else: - polyb = utils.buildPolyData(bm.coordinates(), bm.cells(), fast=True) - polym = utils.buildPolyData(m.coordinates(), m.cells(), fast=True) + polyb = utils.buildPolyData(bm.points(), bm.cells(), fast=True) + polym = utils.buildPolyData(m.points(), m.cells(), fast=True) app = vtk.vtkAppendPolyData() app.AddInputData(polym) app.AddInputData(polyb) app.Update() poly = app.GetOutput() - return Actor(poly).lw(0.1) + return Mesh(poly).lw(0.1) def _loadDolfin_old(filename, exterior='dummy'): @@ -527,7 +532,7 @@ def _loadDolfin_old(filename, exterior='dummy'): connectivity.append([v0, v1, v2, v3]) poly = utils.buildPolyData(coords, connectivity) - return Actor(poly) + return Mesh(poly) def loadPVD(filename): @@ -569,11 +574,11 @@ def loadPDB(filename, bondScale=1, hydrogenBondScale=1, coilWidth=0.3, helixWidt prf.SetHelixWidth(helixWidth) prf.SetInputData(rr.GetOutput()) prf.Update() - return Actor(prf.GetOutput()) + return Mesh(prf.GetOutput()) def loadNeutral(filename): - """Reads a `Neutral` tetrahedral file format. Return an ``Actor(vtkActor)`` object.""" + """Reads a `Neutral` tetrahedral file format. Return an ``Mesh(vtkActor)`` object.""" f = open(filename, "r") lines = f.readlines() f.close() @@ -597,11 +602,11 @@ def loadNeutral(filename): idolf_tets.append([v0, v1, v2, v3]) poly = utils.buildPolyData(coords, idolf_tets) - return Actor(poly) + return Mesh(poly) def loadGmesh(filename): - """Reads a `gmesh` file format. Return an ``Actor(vtkActor)`` object.""" + """Reads a `gmesh` file format. Return an ``Mesh(vtkActor)`` object.""" f = open(filename, "r") lines = f.readlines() f.close() @@ -631,11 +636,12 @@ def loadGmesh(filename): elements.append([int(ele[-3]), int(ele[-2]), int(ele[-1])]) poly = utils.buildPolyData(node_coords, elements, indexOffset=1) - return Actor(poly) + return Mesh(poly) def loadPCD(filename): - """Return ``vtkActor`` from `Point Cloud` file format. Return an ``Actor(vtkActor)`` object.""" + """Return a ``Mesh`` made of only vertex points + from `Point Cloud` file format. Return an ``Mesh(vtkActor)`` object.""" f = open(filename, "r") lines = f.readlines() f.close() @@ -656,7 +662,7 @@ def loadPCD(filename): if expN != N: colors.printc("~!? Mismatch in pcd file", expN, len(pts), c="red") poly = utils.buildPolyData(pts) - return Actor(poly).pointSize(4) + return Mesh(poly).pointSize(4) def loadNumpy(inobj): @@ -694,7 +700,7 @@ def _buildactor(d): lines = d['lines'] poly = utils.buildPolyData(vertices, cells, lines) - act = Actor(poly) + act = Mesh(poly) loadcommon(act, d) act.mapper().ScalarVisibilityOff() @@ -702,12 +708,12 @@ def _buildactor(d): for csc, cscname in d['celldata']: act.addCellScalars(csc, cscname) if 'normal' not in cscname.lower(): - act.scalars(cscname) # activate + act.getCellArray(cscname) # activate if 'pointdata' in keys: for psc, pscname in d['pointdata']: act.addPointScalars(psc, pscname) if 'normal' not in pscname.lower(): - act.scalars(pscname) # activate + act.getPointArray(pscname) # activate prp = act.GetProperty() if 'specular' in keys: prp.SetSpecular(d['specular']) @@ -808,7 +814,7 @@ def fillcommon(obj, adict): def _doactor(obj, adict): - adict['points'] = obj.coordinates(transformed=0).astype(np.float32) + adict['points'] = obj.points(transformed=0).astype(np.float32) poly = obj.polydata() adict['cells'] = None adict['lines'] = None @@ -831,11 +837,10 @@ def _doactor(obj, adict): if poly.GetPointData().GetScalars(): adict['activedata'] = ['pointdata', poly.GetPointData().GetScalars().GetName()] - for itype, iname in obj.scalars(): - if itype == 'PointData': - adict['pointdata'].append([obj.getPointArray(iname), iname]) - if itype == 'CellData': - adict['celldata'].append([obj.getCellArray(iname), iname]) + for iname in obj.getArrayNames()['PointData']: + adict['pointdata'].append([obj.getPointArray(iname), iname]) + for iname in obj.getArrayNames()['CellData']: + adict['celldata'].append([obj.getCellArray(iname), iname]) prp = obj.GetProperty() adict['alpha'] = prp.GetOpacity() @@ -862,17 +867,17 @@ def _doactor(obj, adict): ############################ adict['type'] = 'unknown' - if isinstance(obj, Actor): + if isinstance(obj, Mesh): adict['type'] = 'mesh' _doactor(obj, adict) elif isinstance(obj, Assembly): adict['type'] = 'assembly' adict['actors'] = [] - for a in obj.getActors(): + for a in obj.getMeshes(): assdict = dict() - if not isinstance(a, Actor): #normal vtkActor - b = Actor(a) # promote it to a Actor + if not isinstance(a, Mesh): #normal vtkActor + b = Mesh(a) # promote it to a Actor pra = vtk.vtkProperty() pra.DeepCopy(a.GetProperty()) b.SetProperty(pra) @@ -960,7 +965,7 @@ def write(objct, fileoutput, binary=True): - vtk, vti, npy, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp. """ obj = objct - if isinstance(obj, Actor): # picks transformation + if isinstance(obj, Mesh): # picks transformation obj = objct.polydata(True) elif isinstance(obj, (vtk.vtkActor, vtk.vtkVolume)): obj = objct.GetMapper().GetInput() @@ -1029,7 +1034,7 @@ def write(objct, fileoutput, binary=True): return dicts2save elif ".xml" in fr: # write tetrahedral dolfin xml - vertices = objct.coordinates().astype(str) + vertices = objct.points().astype(str) faces = np.array(objct.faces()).astype(str) ncoords = vertices.shape[0] outF = open(fileoutput, "w") @@ -1173,7 +1178,7 @@ def exportWindow(fileoutput, binary=False, speed=None, html=True): sdict['interactorStyle'] = settings.interactorStyle sdict['useParallelProjection'] = settings.useParallelProjection sdict['objects'] = [] - for a in vp.getActors() + vp.getVolumes(): + for a in vp.getMeshes() + vp.getVolumes(): sdict['objects'].append(_np_dump(a)) np.save(fileoutput, [sdict]) @@ -1259,7 +1264,7 @@ def importWindow(fileinput, mtlFile=None, texturePath=None): actors.InitTraversal() for i in range(actors.GetNumberOfItems()): vactor = actors.GetNextActor() - act = Actor(vactor) + act = Mesh(vactor) act_tu = vactor.GetTexture() if act_tu: act_tu.InterpolateOn()