diff --git a/README.md b/README.md index 3849d6c1..e24fa2ca 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,13 @@ git clone https://github.com/marcomusy/vtkplotter.git cd vtkplotter/examples python tutorial.py ``` -**More than 100 examples can be found in directories** _(scroll down to see the screenshots):_
+**More than 150 examples can be found in directories** _(scroll down to see the screenshots):_
[**examples/basic**](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic)
[**examples/advanced**](https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced)
[**examples/volumetric**](https://github.com/marcomusy/vtkplotter/blob/master/examples/volumetric)
[**examples/simulations**](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations)
-[**examples/other**](https://github.com/marcomusy/vtkplotter/blob/master/examples/other).
+[**examples/other**](https://github.com/marcomusy/vtkplotter/blob/master/examples/other)
+[**examples/other/dolfin**](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin).
| | | |:-----------------------------------------------------------------------------------------------------------------:|:-----| diff --git a/docs/source/conf.py b/docs/source/conf.py index 66789e6b..223220ad 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,8 +35,15 @@ copyright = '2019, M. Musy' author = 'Marco Musy' -# The short X.Y version -version = '2019.1.4' +# package version +try: + VERSIONFILE = "../../vtkplotter/version.py" + verstrline = open(VERSIONFILE, "rt").read() + verstr = verstrline.split('=')[1].replace('\n','').replace("'","") +except: + verstr='unknown_version' + +version = verstr # -- General configuration --------------------------------------------------- diff --git a/examples/README.md b/examples/README.md index d18f82e5..385eae00 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,9 +21,15 @@ vp = Plotter(title='first example') # (The actual mesh corresponds to the outer shape of # an embryonic mouse limb at about 11 days of gestation). # Choose a tomato color for the internal surface of the mesh, and no transparency. -vp.load('data/270.vtk', c='b', bc='tomato') # c=(R,G,B), letter or color name, b=blue +vp.load(datadir+'270.vtk', c='b', bc='tomato') # c=(R,G,B), letter or color name, b=blue vp.show() # picks what was stored in python list vp.actors # Press Esc to close the window and exit python session, or q to continue + +# The same result can be achieved in an even simpler way: +from vtkplotter import * +mesh = load(datadir+'270.vtk', c='b', bc='tomato') +# a Plotter instance is automatically generated +mesh.show() ``` ![tut1](https://user-images.githubusercontent.com/32848391/50738980-d9227400-11d9-11e9-8a7c-14b2abc4d41f.jpg) @@ -33,9 +39,9 @@ vp.show() # picks what was stored in python list vp.actors # by default use their file names as legend entries. # No need to use any variables, as actors are stored internally in vp.actors: vp = Plotter(title='3 shapes') -vp.load('data/250.vtk', c=(1,0.4,0), alpha=0.3) # set opacity to 30% -vp.load('data/270.vtk', c=(1,0.6,0), alpha=0.3) -vp.load('data/290.vtk', c=(1,0.8,0), alpha=0.3) +vp.load(datadir+'250.vtk', c=(1,0.4,0), alpha=0.3) # set opacity to 30% +vp.load(datadir+'270.vtk', c=(1,0.6,0), alpha=0.3) +vp.load(datadir+'290.vtk', c=(1,0.8,0), alpha=0.3) print('Loaded vtkActors: ', len(vp.actors)) vp.show() ``` @@ -88,15 +94,15 @@ vp.show(Cylinder(), at=8, legend='cylinder', interactive=1) # Draw a bunch of objects from various mesh formats. Loading is automatic. vp = Plotter(shape=(3,3), title='mesh formats') # split window in 3 rows and 3 columns vp.sharecam = False # each object can be moved independently -vp.show('data/beethoven.ply', at=0, c=0, axes=0) # dont show axes -vp.show('data/cow.byu', at=1, c=1, zoom=1.15) # make it 15% bigger -vp.show('data/limb.pcd', at=2, c=2) -vp.show('data/ring.gmsh', at=3, c=3, wire=1) # show mesh as wireframe -vp.show('data/images/dog.jpg',at=4) # 2d images can be loaded the same way -vp.show('data/shuttle.obj', at=5, c=5) -vp.show('data/shapes/man.vtk',at=6, c=6, axes=2) # show negative axes segments -vp.show('data/teapot.xyz', at=7, c=7, axes=3) # hide negative axes -vp.show('data/pulley.vtu', at=8, c=8, interactive=1) # try to click object and press k +vp.show(datadir+'beethoven.ply', at=0, c=0, axes=0) # dont show axes +vp.show(datadir+'cow.byu', at=1, c=1, zoom=1.15) # make it 15% bigger +vp.show(datadir+'limb.pcd', at=2, c=2) +vp.show(datadir+'ring.gmsh', at=3, c=3, wire=1) # show mesh as wireframe +vp.show(datadir+'images/dog.jpg',at=4) # 2d images can be loaded the same way +vp.show(datadir+'shuttle.obj', at=5, c=5) +vp.show(datadir+'shapes/man.vtk',at=6, c=6, axes=2) # show negative axes segments +vp.show(datadir+'teapot.xyz', at=7, c=7, axes=3) # hide negative axes +vp.show(datadir+'pulley.vtu', at=8, c=8, interactive=1) # try to click object and press k ``` ![tut7](https://user-images.githubusercontent.com/32848391/50738975-d889dd80-11d9-11e9-97a1-647a9a044718.jpg) @@ -105,7 +111,7 @@ vp.show('data/pulley.vtu', at=8, c=8, interactive=1) # try to click object an # Increase the number of vertices of a mesh using subdivide(). # Show the mesh before and after in two separate renderers defined by shape=(1,2) vp = Plotter(shape=(1,2), axes=0) # dont show axes -a1 = vp.load('data/beethoven.ply') +a1 = vp.load(datadir+'beethoven.ply') coords1 = a1.coordinates() # get coordinates of mesh vertices pts1 = vp.points(coords1, r=4, c='g', legend='#points = '+str(len(coords1))) @@ -125,7 +131,7 @@ vp.show([a2, pts2], at=1, interactive=True) # point at x=500 and has normal (0, 0.3, -1). # Wildcards can be used to load multiple files or entire directories: vp = Plotter(title='Cut a surface with a plane') -vp.load('data/2*0.vtk', c='orange', bc='aqua') +vp.load(datadir+'2*0.vtk', c='orange', bc='aqua') for a in vp.actors: a.cutWithPlane(origin=(500,0,0), normal=(0,0.3,-1), showcut=True) vp.show() @@ -143,7 +149,7 @@ vp.renderer # holds the current vtkRenderer vp.renderers # holds the list of renderers vp.interactor # holds the vtkWindowInteractor object vp.interactive # (True) allows to interact with renderer after show() -vp.camera # holds the current vtkCamera +vp.camera # holds the current vtkCamera object vp.sharecam # (True) share the same camera in multiple renderers ``` ​ @@ -159,10 +165,9 @@ actor.rotate(angle, axis) # rotate actor around axis actor.color(name) # sets/gets color actor.alpha(value) # sets/gets opacity actor.N() # get number of vertex points defining the actor's mesh -actor.polydata() # get the actor's mesh polydata in its current transformation +actor.polydata() # get the actor's mesh vtkPolyData in its current transformation actor.coordinates() # get a copy of vertex points coordinates (copy=False to get references) -actor.normals() # get the list of normals at the vertices of the surface -actor.clone() # get a copy of actor +actor.clone() # generate a copy of actor ... ``` diff --git a/examples/advanced/geodesic.py b/examples/advanced/geodesic.py index db9142ef..387300e5 100644 --- a/examples/advanced/geodesic.py +++ b/examples/advanced/geodesic.py @@ -15,4 +15,4 @@ doc = Text(__doc__, c="w") -show(s, Earth(lw=1), doc, paths, viewup="z", verbose=0) +show(s, Earth(lw=1), doc, paths, viewup="z") diff --git a/examples/advanced/mesh_smoothers.py b/examples/advanced/mesh_smoothers.py index 0023269b..d2b00b8d 100644 --- a/examples/advanced/mesh_smoothers.py +++ b/examples/advanced/mesh_smoothers.py @@ -2,7 +2,7 @@ Mesh smoothing with two different VTK methods. See also analogous Plotter method smoothMLS2D() -in exammples/advanced/moving_least_squares2D.py +in exammples/advanced/moving_least_squares2D.py """ print(__doc__) from vtkplotter import Plotter, datadir diff --git a/examples/advanced/moving_least_squares1D.py b/examples/advanced/moving_least_squares1D.py index 6233d60c..ea249531 100644 --- a/examples/advanced/moving_least_squares1D.py +++ b/examples/advanced/moving_least_squares1D.py @@ -1,11 +1,11 @@ """ This example shows how to use a variant of a 1 dimensional -Moving Least Squares (MLS) algorithm to project a cloud +Moving Least Squares (MLS) algorithm to project a cloud of unordered points to become a smooth line. The parameter f controls the size of the local regression. The input actor's polydata is modified by the method so more than one pass is possible. -If showNLines>0 an actor is built demonstrating the +If showNLines>0 an actor is built demonstrating the details of the regression for some random points """ from __future__ import division, print_function diff --git a/examples/advanced/recosurface.py b/examples/advanced/recosurface.py index 51bec40a..b55ff63f 100644 --- a/examples/advanced/recosurface.py +++ b/examples/advanced/recosurface.py @@ -41,5 +41,5 @@ vp.show(act_pts1, at=2) # reconstructed surface from point cloud -act_reco = recoSurface(act_pts1, bins=128).legend("surf reco") +act_reco = recoSurface(act_pts1, bins=128).legend("surf reco") vp.show(act_reco, at=3, axes=7, interactive=1) diff --git a/examples/advanced/splitmesh.py b/examples/advanced/splitmesh.py index bfaef07b..5e88e575 100644 --- a/examples/advanced/splitmesh.py +++ b/examples/advanced/splitmesh.py @@ -1,5 +1,5 @@ """ -Split a mesh by connectivity and order the pieces +Split a mesh by connectivity and order the pieces by increasing area. """ print(__doc__) diff --git a/examples/advanced/thinplate_grid.py b/examples/advanced/thinplate_grid.py index f6fd8ad2..6e10d514 100644 --- a/examples/advanced/thinplate_grid.py +++ b/examples/advanced/thinplate_grid.py @@ -32,4 +32,4 @@ apts = Points(ptsource, r=5, c="r") arrs = Arrows(ptsource, pttarget) -show(warped, apts, arrs, Text(__doc__), axes=9, viewup="z", verbose=0, bg="w") +show(warped, apts, arrs, Text(__doc__), axes=9, viewup="z", bg="w") diff --git a/examples/basic/README.md b/examples/basic/README.md index a2128a94..87d5a929 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -57,6 +57,8 @@ python example.py | | | | [![mesh_custom](https://user-images.githubusercontent.com/32848391/51390972-20d9c180-1b31-11e9-955d-025f1ef24cb7.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/mesh_custom.py)
`mesh_custom.py` | Build a custom color map to specify the color for each vertex of a mesh. | | | | +| [![mesh_map2cell](https://user-images.githubusercontent.com/32848391/56600859-0153a880-65fa-11e9-88be-34fd96b18e9a.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/mesh_map2cell.py)
`mesh_map2cell.py` | Map a scalar which is defined on the vertices to the mesh cells. | +| | | | [![mesh_threshold](https://user-images.githubusercontent.com/32848391/51807663-4762cf80-228a-11e9-9d0c-184bb11a97bf.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/mesh_threshold.py)
`mesh_threshold.py` | Extracts the cells where scalar value satisfies a threshold criterion. | | | | | [![mirror](https://user-images.githubusercontent.com/32848391/50738855-bf346180-11d8-11e9-97a0-c9aaae6ce052.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/mirror.py)
`mirror.py` | Mirror-reflect a mesh with respect to one of the cartesian axes. | diff --git a/examples/basic/buildpolydata.py b/examples/basic/buildpolydata.py index f4b29619..0e76439a 100644 --- a/examples/basic/buildpolydata.py +++ b/examples/basic/buildpolydata.py @@ -1,6 +1,6 @@ """ -Load a mesh, extract the vertex coordinates, -and build a new vtkPolyData object. +Load a mesh, extract the vertex coordinates, +and build a new vtkPolyData object. Faces (vertex connectivity) can be specified too. (press p to increase point size) diff --git a/examples/basic/buttons.py b/examples/basic/buttons.py index e872ce11..1c5113bc 100644 --- a/examples/basic/buttons.py +++ b/examples/basic/buttons.py @@ -1,5 +1,5 @@ """ -Add a square button with N possible internal states +Add a square button with N possible internal states to a rendering window that calls an external function. Available fonts: arial, courier, times """ diff --git a/examples/basic/connCells.py b/examples/basic/connCells.py index 5f3aa9ee..89e53e9f 100644 --- a/examples/basic/connCells.py +++ b/examples/basic/connCells.py @@ -32,6 +32,6 @@ show(pactor, d, piece, Point(p, c='r'), interactive=0) tomerge.append(piece) - + show(mergeActors(tomerge).clean(), interactive=1) diff --git a/examples/basic/distance2mesh.py b/examples/basic/distance2mesh.py index 90c46b8b..b4bbadc3 100644 --- a/examples/basic/distance2mesh.py +++ b/examples/basic/distance2mesh.py @@ -1,5 +1,5 @@ """ -Computes the (signed) distance +Computes the (signed) distance from one mesh to another. """ from vtkplotter import Sphere, Cube, show, Text @@ -13,4 +13,4 @@ #print(s1.scalars("Distance")) -show(s1, s2, Text(__doc__)) \ No newline at end of file +show(s1, s2, Text(__doc__)) diff --git a/examples/basic/flatarrow.py b/examples/basic/flatarrow.py index 2bae0833..105b3d8e 100644 --- a/examples/basic/flatarrow.py +++ b/examples/basic/flatarrow.py @@ -9,5 +9,5 @@ l2 = [[sin(x)+c+0.1, -cos(x)+s + x/15, x] for x in arange(0,3, 0.1)] FlatArrow(l1, l2, c=i, tipSize=1, tipWidth=1) - + show(collection(), viewup="z", axes=1, bg="w") diff --git a/examples/basic/glyphs.py b/examples/basic/glyphs.py index 9e8e891b..36e10890 100644 --- a/examples/basic/glyphs.py +++ b/examples/basic/glyphs.py @@ -23,7 +23,7 @@ scaleByVectorSize=True, ) -show(s, gsphere1, t, at=0, N=2, verbose=0) +show(s, gsphere1, t, at=0, N=2) ####################################### diff --git a/examples/basic/glyphs_arrows.py b/examples/basic/glyphs_arrows.py new file mode 100644 index 00000000..b215f830 --- /dev/null +++ b/examples/basic/glyphs_arrows.py @@ -0,0 +1,29 @@ +""" +Draw color arrows. +""" +from vtkplotter import * +import numpy as np + +s1 = Sphere(r=10, res=8).c('white').wire() +s2 = Sphere(r=20, res=8).c('white').wire().alpha(0.1).pos(0,4,0) + +coords1 = s1.coordinates() # get the vertices coords +coords2 = s2.coordinates() + +# color can be a colormap which maps arrrow sizes +a1 = Arrows(coords1, coords2, c='coolwarm', alpha=0.4) +a1.addScalarBar() + + +# get a list of random rgb colors +nrs = np.random.randint(0, 10, len(coords1)) +cols = getColor(nrs) + +a2 = Arrows(coords1, coords2, c=cols, scale=0.5) + +t1 = Text('color arrows by size\nusing a color map') +t2 = Text('color arrows by an array\nand scale them') + +# draw 2 group of actors on two renderers +show([[s1, s2, a1, t1], [s1, s2, a2, t2]], N=2) + diff --git a/examples/basic/keypress.py b/examples/basic/keypress.py index 783a5571..b5350013 100644 --- a/examples/basic/keypress.py +++ b/examples/basic/keypress.py @@ -1,7 +1,7 @@ """ This example shows how to implement a custom function that is triggered by pressing a keyboard button when the rendering window is in interactive mode. - Every time a key is pressed the picked point of the mesh is used + Every time a key is pressed the picked point of the mesh is used to add a sphere and some info is printed. """ from vtkplotter import Plotter, printc, Sphere, Text, datadir diff --git a/examples/basic/largestregion.py b/examples/basic/largestregion.py index a4357cdb..a93af9d2 100644 --- a/examples/basic/largestregion.py +++ b/examples/basic/largestregion.py @@ -1,5 +1,5 @@ """ -Extract the mesh region that +Extract the mesh region that has the largest connected surface """ from vtkplotter import * diff --git a/examples/basic/latex.py b/examples/basic/latex.py index ed241c6a..251f2e79 100644 --- a/examples/basic/latex.py +++ b/examples/basic/latex.py @@ -1,18 +1,20 @@ -from vtkplotter import Latex, Cube, Point, show +from vtkplotter import Latex, Point, show # https://matplotlib.org/tutorials/text/mathtext.html latex1 = r'x= \frac{ - b \pm \sqrt {b^2 - 4ac} }{2a}' -latex2 = r'$\mathcal{A}\mathrm{sin}(2 \omega t)$' +latex2 = r'\mathcal{A}\mathrm{sin}(2 \omega t)' latex3 = r'I(Y | X)=\sum_{x \in \mathcal{X}, y \in \mathcal{Y}} p(x, y) \log \left(\frac{p(x)}{p(x, y)}\right)' latex4 = r'\Gamma_{\epsilon}(x)=\left[1-e^{-2 \pi \epsilon}\right]^{1-x} \prod_{n=0}^{\infty} \frac{1-\exp (-2 \pi \epsilon(n+1))}{1-\exp (-2 \pi \epsilon(x+n))}' latex5 = r'\left( \begin{array}{l}{c t^{\prime}} \\ {x^{\prime}} \\ {y^{\prime}} \\ {z^{\prime}}\end{array}\right)=\left( \begin{array}{cccc}{\gamma} & {-\gamma \beta} & {0} & {0} \\ {-\gamma \beta} & {\gamma} & {0} & {0} \\ {0} & {0} & {1} & {0} \\ {0} & {0} & {0} & {1}\end{array}\right) \left( \begin{array}{l}{c t} \\ {x} \\ {y} \\ {z}\end{array}\right)' latex6 = r'\mathrm{CO}_{2}+6 \mathrm{H}_{2} \mathrm{O} \rightarrow \mathrm{C}_{6} \mathrm{H}_{12} \mathrm{O}_{6}+6 \mathrm{O}_{2}' -latex7 = r'x~\mathrm{(arb. units)}' +latex7 = r'x \mathrm{(arb. units)}' -p = Point() -c = Cube().wire() +l = Latex(latex4, s=1, c='white', bg='', alpha=0.9, usetex=False, fromweb=False) +l.crop(0.3, 0.3) # crop top and bottom 30% +l.pos(2,0,0) -l = Latex(latex5, s=1, c='white', bg='', alpha=0.9, fromweb=False).addPos(4,0,-1) +p = Point() +box = l.box() # return the bounding box of an actor -show(p, c, l, axes=8) \ No newline at end of file +show(p, l, box, axes=8) diff --git a/examples/basic/markpoint.py b/examples/basic/markpoint.py index ef9d3b06..824f83d2 100644 --- a/examples/basic/markpoint.py +++ b/examples/basic/markpoint.py @@ -1,5 +1,6 @@ """ -Mark a specific point on a mesh with some text. +Mark a specific point +on a mesh with some text. """ from vtkplotter import Sphere, Point, show, Text diff --git a/examples/basic/mesh_bands.py b/examples/basic/mesh_bands.py index 6f1a5686..e3e6d25b 100644 --- a/examples/basic/mesh_bands.py +++ b/examples/basic/mesh_bands.py @@ -6,7 +6,7 @@ from vtkplotter import show, Hyperboloid, Torus, Text from numpy import linspace -doc = Text(__doc__, c="w", bg="lg") +doc = Text(__doc__, c="k", bg="lg") hyp = Hyperboloid() @@ -18,4 +18,4 @@ transp = linspace(1, 0.5, len(scalars)) # set transparencies from 1 -> .5 tor.pointColors(scalars, alpha=transp, bands=3, cmap="winter") -show(hyp, tor, doc, viewup="z", depthpeeling=1, axes=2, verbose=0) +show(hyp, tor, doc, viewup="z", depthpeeling=1, axes=2) diff --git a/examples/basic/mesh_coloring.py b/examples/basic/mesh_coloring.py index c27ce09a..2ed4ca58 100644 --- a/examples/basic/mesh_coloring.py +++ b/examples/basic/mesh_coloring.py @@ -5,7 +5,7 @@ """ print(__doc__) -from vtkplotter import Plotter, Text, datadir +from vtkplotter import * import numpy as np vp = Plotter(N=3) diff --git a/examples/basic/mesh_custom.py b/examples/basic/mesh_custom.py index 96c5a818..8b81b727 100644 --- a/examples/basic/mesh_custom.py +++ b/examples/basic/mesh_custom.py @@ -3,7 +3,7 @@ individual cell or point of an actor's mesh. Keyword depthpeeling may improve the rendering of transparent objects. """ -from vtkplotter import load, Text, show, datadir +from vtkplotter import * doc = Text(__doc__, pos=1, c="w") @@ -13,16 +13,19 @@ # let the scalar be the z coordinate of the mesh vertices scals = man.coordinates()[:, 2] -# custom color map with optional opacity (here: 1, 0.2 and 0.8) -mymap = ["darkblue", "cyan 0.2", (1, 0, 0, 0.8)] +# custom color map with specified opacities +#mymap = ["darkblue", "cyan", (1, 0, 0)] +#alphas = [0.8, 0.4, 0.2] -# - or by predefined color numbers: -# mymap = [i for i in range(10)] +# - OR by predefined color numbers: +mymap = [i for i in range(10)] +alphas = [i/10. for i in range(10)] -# - or by generating a palette betwwen 2 colors: +# - OR by generating a palette betwwen 2 colors: # from vtkplotter.colors import makePalette -# mymap = makePalette('pink', 'green', N=500, hsv=True) +#mymap = makePalette('pink', 'green', N=500, hsv=True) +#alphas = 1 -man.pointColors(scals, cmap=mymap).addScalarBar() +man.pointColors(scals, cmap=mymap, alpha=alphas).addScalarBar() show(man, doc, viewup="z", axes=8, depthpeeling=1) diff --git a/examples/basic/mesh_map2cell.py b/examples/basic/mesh_map2cell.py new file mode 100644 index 00000000..820c93ce --- /dev/null +++ b/examples/basic/mesh_map2cell.py @@ -0,0 +1,24 @@ +"""How to transform/map an array +which is defined on the vertices of a mesh +to its cells with mapPointsToCells() +""" +from vtkplotter import * + +mesh1 = load(datadir+'shapes/icosahedron.vtk') + +# let the scalar be the z coordinate of the mesh vertices +scals = mesh1.coordinates()[:, 2] + +mesh1.lineWidth(0.1).addPointScalars(scals, name='scals') +mesh2 = mesh1.clone().addScalarBar().mapPointsToCells() + +doc = Text(__doc__, pos=8, c="w") +msg1 = Text("Scalar originally defined on points..", pos=5, c="w") +msg2 = Text("..is interpolated to cells.", pos=5, c="w") + +printInfo(mesh1) +printInfo(mesh2) + +show(mesh1, msg1, doc, at=0, N=2, axes=1, viewup="z") +show(mesh2, msg2, at=1, interactive=True) + diff --git a/examples/basic/mesh_threshold.py b/examples/basic/mesh_threshold.py index 81dd50f5..b5ee6474 100644 --- a/examples/basic/mesh_threshold.py +++ b/examples/basic/mesh_threshold.py @@ -1,8 +1,8 @@ """ -Extracts the cells where scalar value +Extracts the cells where scalar value satisfies a threshold criterion. """ -from vtkplotter import load, Text, show, datadir +from vtkplotter import * doc = Text(__doc__) @@ -16,5 +16,7 @@ # make a copy and threshold the mesh cutman = man.clone().threshold(scals, vmin=36.9, vmax=37.5) +printInfo(cutman) + # distribute the actors on 2 renderers show([[man, doc], cutman], N=2, elevation=-30, axes=0) diff --git a/examples/basic/multiwindows.py b/examples/basic/multiwindows.py index bb0d3deb..aa0189d5 100644 --- a/examples/basic/multiwindows.py +++ b/examples/basic/multiwindows.py @@ -1,7 +1,7 @@ """ Example of drawing objects on different windows and/or subwindows within the same window. -We split the main window in a 25 subwindows and draw something +We split the main window in a 25 subwindows and draw something in specific windows numbers. Then open an independent window and draw a shape on it. """ @@ -23,13 +23,13 @@ c = vp1.load(datadir+"shapes/atc.ply") # show a text in each renderer -for i in range(22): - txt = Text("renderer\nnr."+str(i), c=i, s=0.5, font='arial') - vp1.show(txt, at=i) +for i in range(25): + txt = Text("renderer\nnr."+str(i), c='k', s=0.5, font='arial') + vp1.show(txt, at=i, interactive=0) vp1.show(a, at=22) vp1.show(b, at=23) -vp1.show(c, at=24) +vp1.show(c, at=24, interactive=0) ############################################################ diff --git a/examples/basic/shrink.py b/examples/basic/shrink.py index 54202811..ce05a72f 100644 --- a/examples/basic/shrink.py +++ b/examples/basic/shrink.py @@ -1,5 +1,5 @@ """ -Shrink the triangulation of a mesh +Shrink the triangulation of a mesh to make the inside visible. """ from vtkplotter import load, Sphere, show, Text, datadir diff --git a/examples/basic/text_just.py b/examples/basic/text_just.py index 2a03d415..8c610286 100644 --- a/examples/basic/text_just.py +++ b/examples/basic/text_just.py @@ -11,4 +11,4 @@ to = Point([1, 2, 0], c="r") # mark text origin ax = Point([0, 0, 0]) # mark axes origin -show(tx, to, ax, axes=8, verbose=0) +show(tx, to, ax, axes=8) diff --git a/examples/other/dolfin/README.md b/examples/other/dolfin/README.md index b4975429..0a878040 100644 --- a/examples/other/dolfin/README.md +++ b/examples/other/dolfin/README.md @@ -17,9 +17,13 @@ python example.py # on mac OSX try 'pythonw' instead |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----| | [![showmesh](https://user-images.githubusercontent.com/32848391/53026243-d2d31900-3462-11e9-9dde-518218c241b6.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex01_show-mesh.py)
`ex01_show-mesh.py` | Show dolfin meshes in different ways. | | | | +| [![submesh](https://user-images.githubusercontent.com/32848391/56675428-4e984e80-66bc-11e9-90b0-43dde7e4cc29.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/demo_submesh.py)
`demo_submesh.py` | How to extract matching sub meshes from a common mesh. | +| | | +| [![pi_estimate](https://user-images.githubusercontent.com/32848391/56675429-4e984e80-66bc-11e9-9217-a0652a8e74fe.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/pi_estimate.py)
`pi_estimate.py` | Estimate _pi_ by integrating a circle surface. Latex formulas can be added to the renderer directly. | +| | | | [![tet_mesh](https://user-images.githubusercontent.com/32848391/53026244-d2d31900-3462-11e9-835a-1fa9d66d3dae.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex02_tetralize-mesh.py)
`ex02_tetralize-mesh.py` | Tetrahedral meshes generation with package _mshr_. | | | | -| [![poisson](https://user-images.githubusercontent.com/32848391/53044914-95838100-348c-11e9-8e55-e4732b742006.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex03_poisson.py)
`ex03_poisson.py` | Solving Poisson equation with Dirichlet conditions. | +| [![poisson](https://user-images.githubusercontent.com/32848391/54925524-bec18200-4f0e-11e9-9eab-29fd61ef3b8e.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex03_poisson.py)
`ex03_poisson.py` | Solving Poisson equation with Dirichlet conditions. | | | | | [![mixpoisson](https://user-images.githubusercontent.com/32848391/53045761-b220b880-348e-11e9-840f-94c5c0e86668.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex04_mixed-poisson.py)
`ex04_mixed-poisson.py` | Solving Poisson equation using a mixed (two-field) formulation. | | | | @@ -38,4 +42,9 @@ python example.py # on mac OSX try 'pythonw' instead | [![ft02](https://user-images.githubusercontent.com/32848391/55499287-ed91d380-5645-11e9-8e9a-e31e2e3b1649.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ft02_poisson_membrane.py)
`ft02_poisson_membrane.py` | Deflection of a membrane by a gaussian load. | | | | | [![ft04](https://user-images.githubusercontent.com/32848391/55578167-88a5ae80-5715-11e9-84ea-bdab54099887.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ft04_heat_gaussian.py)
`ft04_heat_gaussian.py` | Diffusion of a Gaussian hill on a square domain. | - +| | | +| [![cahn](https://user-images.githubusercontent.com/32848391/56664730-edb34b00-66a8-11e9-9bf3-73431f2a98ac.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/demo_cahn-hilliard.py)
`demo_cahn-hilliard.py` | Solution of a particular nonlinear time-dependent fourth-order equation, known as the Cahn-Hilliard equation. | +| | | +| [![navier-stokes_lshape](https://user-images.githubusercontent.com/32848391/56671156-6bc91f00-66b4-11e9-8c58-e6b71e2ad1d0.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/navier-stokes_lshape.py)
`navier-stokes_lshape.py` | Solve the incompressible Navier-Stokes equations on an L-shaped domain using Chorin's splitting method. | +| | | +| [![turing_pattern](https://user-images.githubusercontent.com/32848391/56056437-77cfeb00-5d5c-11e9-9887-828e5745d547.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/turing_pattern.py)
`turing_pattern.py` | Solve a reaction-diffusion problem on a 2D domain. | diff --git a/examples/other/dolfin/collisions.py b/examples/other/dolfin/collisions.py index 12127441..404acd36 100644 --- a/examples/other/dolfin/collisions.py +++ b/examples/other/dolfin/collisions.py @@ -1,12 +1,12 @@ ''' compute_collision() will compute the collision of all the entities with a Point while compute_first_collision() will always return its first entry. -Especially if a point is on an element edge this can be tricky. +Especially if a point is on an element edge this can be tricky. You may also want to compare with the Cell.contains(Point) tool. ''' # Script by Rudy at https://fenicsproject.discourse.group/t/ # any-function-to-determine-if-the-point-is-in-the-mesh/275/3 -import dolfin +import dolfin from vtkplotter.dolfin import shapes, plot, printc n = 4 diff --git a/examples/other/dolfin/demo_cahn-hilliard.py b/examples/other/dolfin/demo_cahn-hilliard.py new file mode 100644 index 00000000..1d20da4d --- /dev/null +++ b/examples/other/dolfin/demo_cahn-hilliard.py @@ -0,0 +1,107 @@ +""" +Solution of a particular nonlinear time-dependent +fourth-order equation, known as the Cahn-Hilliard equation. +""" +#https://fenicsproject.org/olddocs/dolfin/1.4.0/python/demo/documented +import random +from dolfin import * +set_log_level(30) + + +# Class representing the intial conditions +class InitialConditions(UserExpression): + def __init__(self, **kwargs): + random.seed(2 + MPI.rank(MPI.comm_world)) + super().__init__(**kwargs) + def eval(self, values, x): + values[0] = 0.63 + 0.02 * (0.5 - random.random()) + values[1] = 0.0 + def value_shape(self): return (2,) + +# Class for interfacing with the Newton solver +class CahnHilliardEquation(NonlinearProblem): + def __init__(self, a, L): + NonlinearProblem.__init__(self) + self.L = L + self.a = a + def F(self, b, x): assemble(self.L, tensor=b) + def J(self, A, x): assemble(self.a, tensor=A) + +# Model parameters +lmbda = 1.0e-02 # surface parameter +dt = 5.0e-06 # time step +# time stepping family, +# e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson +theta = 0.5 + +# Form compiler options +parameters["form_compiler"]["optimize"] = True +parameters["form_compiler"]["cpp_optimize"] = True + +# Create mesh and define function spaces +mesh = UnitSquareMesh(60, 60) +# mesh = UnitSquareMesh.create(60, 60, CellType.Type.triangle) +# V = FunctionSpace(mesh, "Lagrange", 1) +P1 = FiniteElement("Lagrange", mesh.ufl_cell(), 1) +ME = FunctionSpace(mesh, P1 * P1) + +# Define trial and test functions +du = TrialFunction(ME) +q, v = TestFunctions(ME) + +# Define functions +u = Function(ME) # current solution +u0 = Function(ME) # solution from previous converged step + +# Split mixed functions +dc, dmu = split(du) +c, mu = split(u) +c0, mu0 = split(u0) + +# Create intial conditions and interpolate +u_init = InitialConditions(degree=1) +u.interpolate(u_init) +u0.interpolate(u_init) + +# Compute the chemical potential df/dc +c = variable(c) +f = 100 * c ** 2 * (1 - c) ** 2 +mu_mid = (1 - theta) * mu0 + theta * mu + +# Weak statement of the equations +L0 = c * q - c0 * q + dt * dot(grad(mu_mid), grad(q)) +L1 = mu * v - diff(f, c) * v - lmbda * dot(grad(c), grad(v)) +L = (L0 + L1) * dx + +# Compute directional derivative about u in the direction of du +a = derivative(L, u, du) + +# Create nonlinear problem and Newton solver +problem = CahnHilliardEquation(a, L) +solver = NewtonSolver() +solver.parameters["linear_solver"] = "lu" +solver.parameters["convergence_criterion"] = "incremental" +solver.parameters["relative_tolerance"] = 1e-6 + +# Step in time +from vtkplotter.dolfin import plot + +t = 0 +T = 10*dt +while t < T: + t += dt + u0.vector()[:] = u.vector() + solver.solve(problem, u.vector()) + + plot(u.split()[0], + z=t*2e4, + add=True, # do not clear the canvas + style=0, + lw=0, + scalarbar='h', + elevation=-3, # move camera a bit + azimuth=1, + text='time: '+str(t*2e4), + interactive=0 ) + +plot() diff --git a/examples/other/dolfin/demo_submesh.py b/examples/other/dolfin/demo_submesh.py new file mode 100644 index 00000000..2fec3276 --- /dev/null +++ b/examples/other/dolfin/demo_submesh.py @@ -0,0 +1,40 @@ +""" +how to extract matching +sub meshes from a common mesh. +""" +from dolfin import * + +class Structure(SubDomain): + def inside(self, x, on_boundary): + return x[0] > 1.4 - DOLFIN_EPS and x[0] < 1.6 \ + + DOLFIN_EPS and x[1] < 0.6 + DOLFIN_EPS + +mesh = RectangleMesh(Point(0.0, 0.0), Point(3.0, 1.0), 60, 20) + +# Create sub domain markers and mark everaything as 0 +sub_domains = MeshFunction("size_t", mesh, mesh.topology().dim()) +sub_domains.set_all(0) + +# Mark structure domain as 1 +structure = Structure() +structure.mark(sub_domains, 1) + +# Extract sub meshes +fluid_mesh = SubMesh(mesh, sub_domains, 0) +structure_mesh = SubMesh(mesh, sub_domains, 1) + +# Move structure mesh +for x in structure_mesh.coordinates(): + x[0] += 0.1*x[0]*x[1] + +# Move fluid mesh according to structure mesh +ALE.move(fluid_mesh, structure_mesh) +fluid_mesh.smooth() + +############################################# +from vtkplotter.dolfin import plot + +plot(fluid_mesh, text=__doc__, interactive=False) +plot(structure_mesh, c='tomato', add=True) +plot() + diff --git a/examples/other/dolfin/elastodynamics.py b/examples/other/dolfin/elastodynamics.py index 5add1001..c6bd03c7 100644 --- a/examples/other/dolfin/elastodynamics.py +++ b/examples/other/dolfin/elastodynamics.py @@ -181,12 +181,13 @@ def local_project(v, V, u=None): return ################################################################### time loop -from vtkplotter.dolfin import plot, ProgressBar, screenshot, shapes +from vtkplotter.dolfin import * +from vtkplotter import Box # add a frame box -box = shapes.Box(length=1, width=1, height=1).pos(0.5,0,0).wireframe() +box = Box(length=1, width=1, height=1).pos(0.5,0,0).wireframe() -tex = shapes.Latex(r'\nabla \cdot \sigma+\rho b=\rho \ddot{u}', s=.2).pos(.4,.4,-.5) +tex = Latex(r'\nabla \cdot \sigma+\rho b=\rho \ddot{u}', s=.2).pos(.4,.4,-.5) pb = ProgressBar(0, len(np.diff(time)), c='blue') @@ -222,20 +223,19 @@ def local_project(v, V, u=None): E_tot = E_elas+E_kin+E_damp #-E_ext energies[i+1, :] = np.array([E_elas, E_kin, E_damp, E_tot]) - plot(u, box, tex, - mode='warped mesh', - style='matplotlib', + plot(u, box, tex, + mode='warped mesh', + style='matplotlib', axes=0, # no axes - scalarbar=False, + scalarbar=False, azimuth=1, # at each iteration add an angle to rotate scene - text=__doc__, + text=__doc__, # add this file header interactive=False) #screenshot('bar'+str(i)+'.png') # uncomment to save screenshots pb.print("Time: "+str(t)+" seconds") -plot(interactive=True) +plot() -# \nabla \cdot \sigma+\rho b=\rho \ddot{u} diff --git a/examples/other/dolfin/ex01_show-mesh.py b/examples/other/dolfin/ex01_show-mesh.py index ffb3a2c4..0bc47475 100644 --- a/examples/other/dolfin/ex01_show-mesh.py +++ b/examples/other/dolfin/ex01_show-mesh.py @@ -5,12 +5,12 @@ import dolfin from vtkplotter.dolfin import plot, datadir - mesh1 = dolfin.Mesh(datadir + "dolfin_fine.xml") -plot(mesh1) +plot(mesh1) -# show another light-green mesh in a new plotter window, +# show another light-green mesh in a new plotter window, # show file header too as an additional text comment mesh2 = dolfin.UnitCubeMesh(8,8,8) + plot(mesh2, text=__doc__, color='lg', newPlotter=True) diff --git a/examples/other/dolfin/ex03_poisson.py b/examples/other/dolfin/ex03_poisson.py index b430a8ca..1a24e77d 100644 --- a/examples/other/dolfin/ex03_poisson.py +++ b/examples/other/dolfin/ex03_poisson.py @@ -30,9 +30,9 @@ f = r'-\nabla^{2} u=f' ########################################################### vtkplotter -from vtkplotter.dolfin import plot, Text, Latex, clear +from vtkplotter.dolfin import plot, Latex, clear -l = Latex(f, s=.2, c='w').addPos(.6,.6,.1) +l = Latex(f, s=0.2, c='w').addPos(.6,.6,.1) plot(u, l, cmap='jet', scalarbar='h', text=__doc__) diff --git a/examples/other/dolfin/ex04_mixed-poisson.py b/examples/other/dolfin/ex04_mixed-poisson.py index 2756961b..e7636494 100644 --- a/examples/other/dolfin/ex04_mixed-poisson.py +++ b/examples/other/dolfin/ex04_mixed-poisson.py @@ -1,5 +1,5 @@ """ -Solving Poisson equation using +Solving Poisson equation using a mixed (two-field) formulation. """ # needs python3 diff --git a/examples/other/dolfin/ex05_non-matching-meshes.py b/examples/other/dolfin/ex05_non-matching-meshes.py index cc877379..22bfdf22 100644 --- a/examples/other/dolfin/ex05_non-matching-meshes.py +++ b/examples/other/dolfin/ex05_non-matching-meshes.py @@ -1,5 +1,5 @@ """ -Interpolate functions between +Interpolate functions between finite element spaces on non-matching meshes. """ # https://fenicsproject.org/docs/dolfin/2018.1.0/python/demos diff --git a/examples/other/dolfin/ex06_elasticity2.py b/examples/other/dolfin/ex06_elasticity2.py index b62ec717..2b4b5cf2 100644 --- a/examples/other/dolfin/ex06_elasticity2.py +++ b/examples/other/dolfin/ex06_elasticity2.py @@ -39,7 +39,7 @@ from vtkplotter.dolfin import plot, printc # print out some funny text -printc("""~idea Try out plot options: +printc("""~idea Try out plot options: ~pin color='gold' ~pin alpha=0.2, depthpeeling=True ~pin mode='mesh warp lines', lw=.05""", c='blue') diff --git a/examples/other/dolfin/ex07_stokes-iterative.py b/examples/other/dolfin/ex07_stokes-iterative.py index f639e37b..88b0bd08 100644 --- a/examples/other/dolfin/ex07_stokes-iterative.py +++ b/examples/other/dolfin/ex07_stokes-iterative.py @@ -71,7 +71,7 @@ def top_bottom(x, on_boundary): from vtkplotter.dolfin import plot, printHistogram # Plot u and p solutions on N=2 synced renderers -plot(u, mode='mesh arrows', at=0, N=2, legend='velocity', bg='white', +plot(u, mode='mesh arrows', at=0, N=2, legend='velocity', bg='white', scale=0.1, wireframe=1, lw=0.03, alpha=0.5, scalarbar=False) printHistogram(pressures, title='pressure histo', logscale=True, c=1) diff --git a/examples/other/dolfin/ft04_heat_gaussian.py b/examples/other/dolfin/ft04_heat_gaussian.py index dd3d108f..07abe425 100644 --- a/examples/other/dolfin/ft04_heat_gaussian.py +++ b/examples/other/dolfin/ft04_heat_gaussian.py @@ -44,8 +44,9 @@ def boundary(x, on_boundary): ############################################################# vtkplotter from vtkplotter.dolfin import plot, Latex -f = r'\frac{\partial u}{\partial t}=\nabla^{2} u+f~\mathrm{in}~\Omega\times(0,T]' -formula = Latex(f, pos=(-.2,-1, .1), s=.5, bg='w', alpha=.7) +f = r'\frac{\partial u}{\partial t}=\nabla^2 u+f~\mathrm{in}~\Omega\times(0,T]' +formula = Latex(f, pos=(-.4,-.8, .1), s=0.6, c='w') +formula.crop(0.2, 0.4) # crop top and bottom 20% and 40% # Time-stepping u = Function(V) @@ -55,9 +56,9 @@ def boundary(x, on_boundary): solve(a == L, u, bc) # Plot solution - plot(u, formula, interactive=False, scalarbar=False) + plot(u, formula, scalarbar=False, interactive=False) # Update previous solution u_n.assign(u) -plot(u, interactive=True) +plot() diff --git a/examples/other/dolfin/ft07_navier_stokes_channel.py b/examples/other/dolfin/ft07_navier_stokes_channel.py index 007ea1c4..fed34fac 100644 --- a/examples/other/dolfin/ft07_navier_stokes_channel.py +++ b/examples/other/dolfin/ft07_navier_stokes_channel.py @@ -7,7 +7,6 @@ div(u) = 0 """ from fenics import * -import numpy as np T = 10.0 # final time num_steps = 90 # number of time steps @@ -110,13 +109,13 @@ def sigma(u, p): p_n.assign(p_) # Plot solution - plot(u_, + plot(u_, cmap='plasma', scalarbar=False, bg='w', + text=__doc__, axes=7, # bottom ruler - interactive=False, - ) + interactive=False) -plot(u_, cmap='plasma', text=__doc__, interactive=True) +plot() diff --git a/examples/other/dolfin/ft08_navier_stokes_cylinder.py b/examples/other/dolfin/ft08_navier_stokes_cylinder.py index 887bbee4..198148f5 100644 --- a/examples/other/dolfin/ft08_navier_stokes_cylinder.py +++ b/examples/other/dolfin/ft08_navier_stokes_cylinder.py @@ -9,7 +9,6 @@ from __future__ import print_function, division from fenics import * from mshr import * -import numpy as np T = 10.0 # final time num_steps = 50 # number of time steps @@ -147,12 +146,12 @@ def sigma(u, p): print('n:',n) # Plot solution - plot(u_, + plot(u_, cmap='bone', - scalarbar=False, bg='w', + text=__doc__, axes=0, # no axes - interactive=False, - ) + scalarbar='h', + interactive=False) -plot(u_, cmap='bone', scalarbar='h', title='Velocity', text=__doc__, interactive=True) +plot() diff --git a/examples/other/dolfin/ft09_reaction_system.py b/examples/other/dolfin/ft09_reaction_system.py index 39edceb5..49e7e6f4 100644 --- a/examples/other/dolfin/ft09_reaction_system.py +++ b/examples/other/dolfin/ft09_reaction_system.py @@ -1,6 +1,6 @@ """Convection-diffusion-reaction for a system describing the concentration of three species A, B, C undergoing a simple -first-order reaction A + B --> C with first-order decay of C. +first-order reaction A + B --> C with first-order decay of C. The velocity is given by the flow field w from the demo navier_stokes_cylinder.py. @@ -9,6 +9,7 @@ u_3' + w . nabla(u_3) - div(eps*grad(u_3)) = f_3 + K*u_1*u_2 - K*u_3 """ +print(__doc__) from fenics import * set_log_level(30) @@ -79,7 +80,7 @@ solve(F == 0, u) _u_1, _u_2, _u_3 = u.split() - + # Update previous solution u_n.assign(u) @@ -91,7 +92,6 @@ scalarbar=False, bg='w', axes=0, - text=__doc__, zoom=2, interactive=False) diff --git a/examples/other/dolfin/navier-stokes_lshape.py b/examples/other/dolfin/navier-stokes_lshape.py new file mode 100644 index 00000000..92886950 --- /dev/null +++ b/examples/other/dolfin/navier-stokes_lshape.py @@ -0,0 +1,116 @@ +""" +Solve the incompressible Navier-Stokes equations +on an L-shaped domain using Chorin's splitting method. +""" +from dolfin import * +from vtkplotter.dolfin import ProgressBar, plot, datadir + +# Print log messages only from the root process in parallel +parameters["std_out_all_processes"] = False + +# Load mesh from file +mesh = Mesh(datadir + "lshape.xml.gz") + +# Define function spaces (P2-P1) +V = VectorFunctionSpace(mesh, "Lagrange", 2) +Q = FunctionSpace(mesh, "Lagrange", 1) + +# Define trial and test functions +u = TrialFunction(V) +p = TrialFunction(Q) +v = TestFunction(V) +q = TestFunction(Q) + +# Set parameter values +dt = 0.01 +T = 3 +nu = 0.01 + +# Define time-dependent pressure boundary condition +p_in = Expression("sin(3.0*t)", t=0.0, degree=2) + +# Define boundary conditions +noslip = DirichletBC(V, (0, 0), "on_boundary && \ + (x[0] < DOLFIN_EPS | x[1] < DOLFIN_EPS | \ + (x[0] > 0.5 - DOLFIN_EPS && x[1] > 0.5 - DOLFIN_EPS))") +inflow = DirichletBC(Q, p_in, "x[1] > 1.0 - DOLFIN_EPS") +outflow = DirichletBC(Q, 0, "x[0] > 1.0 - DOLFIN_EPS") +bcu = [noslip] +bcp = [inflow, outflow] + +# Create functions +u0 = Function(V) +u1 = Function(V) +p1 = Function(Q) + +# Define coefficients +k = Constant(dt) +f = Constant((0, 0)) + +# Tentative velocity step +F1 = (1/k)*inner(u - u0, v)*dx + inner(grad(u0)*u0, v)*dx + \ + nu*inner(grad(u), grad(v))*dx - inner(f, v)*dx +a1 = lhs(F1) +L1 = rhs(F1) + +# Pressure update +a2 = inner(grad(p), grad(q))*dx +L2 = -(1/k)*div(u1)*q*dx + +# Velocity update +a3 = inner(u, v)*dx +L3 = inner(u1, v)*dx - k*inner(grad(p1), v)*dx + +# Assemble matrices +A1 = assemble(a1) +A2 = assemble(a2) +A3 = assemble(a3) + +# Use amg preconditioner if available +prec = "amg" if has_krylov_solver_preconditioner("amg") else "default" + +# Use nonzero guesses - essential for CG with non-symmetric BC +parameters['krylov_solver']['nonzero_initial_guess'] = True + +# Time-stepping +pb = ProgressBar(0, T, dt, c='green') +for t in pb.range(): + + # Update pressure boundary condition + p_in.t = t + + # Compute tentative velocity step + b1 = assemble(L1) + [bc.apply(A1, b1) for bc in bcu] + solve(A1, u1.vector(), b1, "bicgstab", "default") + + # Pressure correction + b2 = assemble(L2) + [bc.apply(A2, b2) for bc in bcp] + [bc.apply(p1.vector()) for bc in bcp] + solve(A2, p1.vector(), b2, "bicgstab", prec) + + # Velocity correction + b3 = assemble(L3) + [bc.apply(A3, b3) for bc in bcu] + solve(A3, u1.vector(), b3, "bicgstab", "default") + + # Move to next time step + u0.assign(u1) + t += dt + + # Plot solution + plot(u1, + mode='mesh and arrows', + text="Velocity of fluid", + bg='w', + cmap='jet', + scale=0.3, # unit conversion factor + scalarbar=False, + resetcam=False, + interactive=False) + pb.print() + +plot() + + diff --git a/examples/other/dolfin/noplot/ex02_tetralize-mesh.py b/examples/other/dolfin/noplot/ex02_tetralize-mesh.py index f7588ab9..0eae8beb 100644 --- a/examples/other/dolfin/noplot/ex02_tetralize-mesh.py +++ b/examples/other/dolfin/noplot/ex02_tetralize-mesh.py @@ -1,5 +1,5 @@ """ -Tetrahedral meshes generation with +Tetrahedral meshes generation with package mshr and dolfin. You can then visualize the file with: diff --git a/examples/other/dolfin/noplot/ex04_mixed-poisson.py b/examples/other/dolfin/noplot/ex04_mixed-poisson.py index e2c348f0..d55829f9 100644 --- a/examples/other/dolfin/noplot/ex04_mixed-poisson.py +++ b/examples/other/dolfin/noplot/ex04_mixed-poisson.py @@ -1,5 +1,5 @@ """ -How to solve Poisson equation using +How to solve Poisson equation using a mixed (two-field) formulation. """ # https://fenicsproject.org/docs/dolfin/2018.1.0/python/demos/mixed-poisson diff --git a/examples/other/dolfin/noplot/ex05_non-matching-meshes.py b/examples/other/dolfin/noplot/ex05_non-matching-meshes.py index 89434c63..6bfc1d7a 100644 --- a/examples/other/dolfin/noplot/ex05_non-matching-meshes.py +++ b/examples/other/dolfin/noplot/ex05_non-matching-meshes.py @@ -1,5 +1,5 @@ """ -Interpolate functions between +Interpolate functions between finite element spaces on non-matching meshes. """ # https://fenicsproject.org/docs/dolfin/2018.1.0/python/demos diff --git a/examples/other/dolfin/pi_estimate.py b/examples/other/dolfin/pi_estimate.py index 800ca2f8..337694da 100644 --- a/examples/other/dolfin/pi_estimate.py +++ b/examples/other/dolfin/pi_estimate.py @@ -11,10 +11,10 @@ mesh = generate_mesh(domain, res) A = assemble(Constant(1) * dx(domain=mesh)) printc("resolution = %i, \t A-pi = %.5e" % (res, A-pi)) - + printc('~pi is about', A, c='yellow') l = Latex(r'\mathrm{Area}(r)=\pi=\int\int_D 1 \cdot d(x,y)', s=0.3) l.crop(0.3,0.3).z(0.1) # crop invisible top and bottom and set at z=0.1 -plot(mesh, l, alpha=0.4, style=1, axes=3) +plot(mesh, l, alpha=0.4, ztitle='', style=1, axes=3) diff --git a/examples/other/dolfin/run_all.sh b/examples/other/dolfin/run_all.sh index 87d171eb..9d603d13 100755 --- a/examples/other/dolfin/run_all.sh +++ b/examples/other/dolfin/run_all.sh @@ -4,6 +4,33 @@ printf "\033c" echo Running examples in directory dolfin/ +########################## +echo Running ascalarbar.py +python ascalarbar.py + +echo Running collisions.py +python collisions.py + +echo Running calc_surface_area.py +python calc_surface_area.py + +echo Running markmesh.py +python markmesh.py + +echo Running pi_estimate.py +python pi_estimate.py + +echo Running submesh_boundary.py +python submesh_boundary.py + +echo Running demo_submesh.py +python demo_submesh.py + +echo Running elastodynamics.py +python elastodynamics.py + + +###################################### echo Running ex01_show-mesh.py python ex01_show-mesh.py @@ -29,30 +56,7 @@ echo Running ex07_stokes-iterative.py python ex07_stokes-iterative.py - -########################## -echo Running ascalarbar.py -python ascalarbar.py - -echo Running collisions.py -python collisions.py - -echo Running calc_surface_area.py -python calc_surface_area.py - -echo Running markmesh.py -python markmesh.py - -echo Running elastodynamics.py -python elastodynamics.py - -echo Running pi_estimate.py -python pi_estimate.py - -echo Running stokes.py -python stokes.py - - +###################################### echo Running ft02_poisson_membrane.py python ft02_poisson_membrane.py @@ -68,6 +72,15 @@ python ft08_navier_stokes_cylinder.py echo Running ft09_reaction_system.py python ft09_reaction_system.py +echo Running stokes.py +python stokes.py + +echo Running demo_cahn-hilliard.py +python demo_cahn-hilliard.py + +echo Running turing.py +python turing.py + ###################################### echo diff --git a/examples/other/dolfin/stokes.py b/examples/other/dolfin/stokes.py index 14cff90b..e42b344e 100644 --- a/examples/other/dolfin/stokes.py +++ b/examples/other/dolfin/stokes.py @@ -50,6 +50,6 @@ f = r'-\nabla \cdot(\nabla u+p I)=f ~\mathrm{in}~\Omega' formula = Latex(f, pos=(0.55,0.45,-.05), s=0.1) -plot(u, formula, at=0, N=2, text="velocity", mode='mesh and arrows', +plot(u, formula, at=0, N=2, text="velocity", mode='mesh and arrows', scale=.03, wire=1, scalarbar=False, style=1) plot(p, at=1, text="pressure", cmap='jet') diff --git a/examples/other/dolfin/submesh_boundary.py b/examples/other/dolfin/submesh_boundary.py new file mode 100644 index 00000000..f129bbf5 --- /dev/null +++ b/examples/other/dolfin/submesh_boundary.py @@ -0,0 +1,34 @@ +""" +Extract submesh boundaries. +""" +# https://fenicsproject.discourse.group/t/output-parts-of-boundary/537 +from fenics import * +from mshr import * +from numpy import array +from numpy.linalg import norm + +domain = Box(Point(0,0,0), Point(10,10,10)) - Sphere(Point(5,5,5), 3) +mesh = generate_mesh(domain, 32) +exterior = BoundaryMesh(mesh, "exterior") + + +def inSphere(x): + v = x - array([5, 5, 5]) + return norm(v) < 3 + 1e2 * DOLFIN_EPS + +class SphereDomain(SubDomain): + def inside(self, x, on_boundary): + return inSphere(x) + +class BoxDomain(SubDomain): + def inside(self, x, on_boundary): + return not inSphere(x) + +sph = SubMesh(exterior, SphereDomain()) +box = SubMesh(exterior, BoxDomain()) + + +from vtkplotter.dolfin import plot + +plot(sph, at=0, N=2, c='red', text=__doc__) +plot(box, at=1, wireframe=True) diff --git a/examples/other/dolfin/turing_pattern.py b/examples/other/dolfin/turing_pattern.py new file mode 100644 index 00000000..eb496a01 --- /dev/null +++ b/examples/other/dolfin/turing_pattern.py @@ -0,0 +1,83 @@ +from dolfin import * +from numpy.random import random +set_log_level(30) + + +class TuringPattern(NonlinearProblem): + def __init__(self, a, L): + NonlinearProblem.__init__(self) + self.L = L + self.a = a + def F(self, b, x): assemble(self.L, tensor=b) + def J(self, A, x): assemble(self.a, tensor=A) + +mesh = UnitSquareMesh(48, 48) + +U = FiniteElement("CG", mesh.ufl_cell(), 2) + +W = FunctionSpace(mesh, U * U) + +du = TrialFunction(W) +q, p = TestFunctions(W) + +w = Function(W) +w0 = Function(W) + +# Split mixed functions +dact, dhib = split(du) +act, hib = split(w) +act0, hib0 = split(w0) + +dt = 0.04 +T = 20.0 + +class IC(UserExpression): + def __init__(self, **kwargs): + super().__init__(**kwargs) + def eval(self, values, x): + values[0] = 1.0*random() +0.25 + values[1] = 1.0*random() +0.25 + def value_shape(self): return (2,) + +w_init = IC(element=W.ufl_element(), degree=2) +w.interpolate(w_init) +w0.interpolate(w_init) + +L0 = act*q - act0*q \ + + dt*0.0005*inner(grad(act), grad(q)) \ + - dt*inner(act*act*hib,q) \ + + 1.0*dt*inner(act,q) +L1 = hib*p -hib0*p \ + + dt*0.1*inner(grad(hib), grad(p)) \ + + dt*inner(act*act*hib, p) \ + - dt*inner(Constant(1.0),p) +L = (L0 + L1) *dx + +# Compute directional derivative about u in the direction of du +a = derivative(L, w, du) + +problem = TuringPattern(a, L) +solver = NewtonSolver() +solver.parameters["linear_solver"] = "lu" +solver.parameters["convergence_criterion"] = "incremental" +solver.parameters["relative_tolerance"] = 1e-2 + + +########################################### time steps +from vtkplotter.dolfin import plot, printc + +t = 0 +printc('~bomb Press Esc to quit.', c='y', invert=True) +while t < T: + t += dt + w0.vector()[:] = w.vector() + solver.solve(problem, w.vector()) + + plot(w.split()[0], + style=4, + lw=0, + scalarbar='h', + text='time: '+str(t), + interactive=0 ) + +plot() diff --git a/examples/other/makeVideo.py b/examples/other/makeVideo.py index 9432c630..3ccf9222 100644 --- a/examples/other/makeVideo.py +++ b/examples/other/makeVideo.py @@ -1,6 +1,6 @@ """ Make a video (needs ffmpeg) - Set offscreen=True to only produce the video + Set offscreen=True to only produce the video without any graphical window showing """ print(__doc__) diff --git a/examples/run_all.sh b/examples/run_all.sh index 0f93221c..14abe3e0 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -102,6 +102,9 @@ python basic/mesh_threshold.py echo Running basic/mesh_modify.py python basic/mesh_modify.py +echo Running basic/mesh_map2cell.py +python basic/mesh_map2cell.py + echo Running basic/pca.py python basic/pca.py @@ -157,7 +160,7 @@ echo Running basic/tube.py python basic/tube.py echo Running basic/boolean.py -python basic/boolean.py # fails for vtk version<7 +python basic/boolean.py echo Running basic/annotations.py python basic/annotations.py @@ -168,6 +171,9 @@ python basic/markpoint.py echo Running basic/glyphs.py python basic/glyphs.py +echo Running basic/glyphs_arrows.py +python basic/glyphs_arrows.py + #################################### advanced echo Running advanced/fatlimb.py diff --git a/examples/simulations/gyroscope2.py b/examples/simulations/gyroscope2.py index f7a39797..0bafb87c 100644 --- a/examples/simulations/gyroscope2.py +++ b/examples/simulations/gyroscope2.py @@ -2,8 +2,8 @@ A gyroscope sitting on a pedestal. The analysis is in terms of Lagrangian mechanics. -The Lagrangian variables are polar angle theta, -azimuthal angle phi, and spin angle psi. +The Lagrangian variables are polar angle theta, +azimuthal angle phi, and spin angle psi. """ # (adapted from http://www.glowscript.org) from __future__ import division, print_function diff --git a/examples/simulations/multiple_pendulum.py b/examples/simulations/multiple_pendulum.py index af255bd1..0fb878c3 100644 --- a/examples/simulations/multiple_pendulum.py +++ b/examples/simulations/multiple_pendulum.py @@ -89,7 +89,7 @@ dist2 = (bob_x[i] - bob_x[j]) ** 2 + (bob_y[i] - bob_y[j]) ** 2 if dist2 < DiaSq: # are colliding Ddist = np.sqrt(dist2) - 2 * R - tau = versor(bob_x[j] - bob_x[i], bob_y[j] - bob_y[i]) + tau = versor([bob_x[j] - bob_x[i], bob_y[j] - bob_y[i],0]) DR = Ddist / 2 * tau bob_x[i] += DR[0] # DR.x bob_y[i] += DR[1] # DR.y diff --git a/examples/simulations/particle_simulator.py b/examples/simulations/particle_simulator.py index f3b4e7c1..39eb6702 100644 --- a/examples/simulations/particle_simulator.py +++ b/examples/simulations/particle_simulator.py @@ -71,7 +71,7 @@ def __init__(self, pos, charge, mass, radius, color, vel, fixed, negligible): radius: radius of the particle, in meters. No effect on simulation color: color of the particle. If None, a default color will be chosen vel: initial velocity vector, in m/s - fixed: if True, particle will remain fixed in place + fixed: if True, particle will remain fixed in place negligible: assume charge is small wrt other charges to speed up calculation """ self.pos = vector(pos) diff --git a/examples/simulations/turing.py b/examples/simulations/turing.py index 0c1f657e..09e7e154 100644 --- a/examples/simulations/turing.py +++ b/examples/simulations/turing.py @@ -1,7 +1,7 @@ """ Scalar values are read from a file and represented on a green scale on a mesh as a function of time. -The difference between one time point and the next +The difference between one time point and the next is shown as a blue component. """ from __future__ import division, print_function diff --git a/examples/simulations/wave_equation.py b/examples/simulations/wave_equation.py index a2fb5be4..0c2ee71e 100644 --- a/examples/simulations/wave_equation.py +++ b/examples/simulations/wave_equation.py @@ -1,6 +1,6 @@ """ Simulate a discrete collection of oscillators -We will use this as a model of a vibrating string and +We will use this as a model of a vibrating string and compare two methods of integration: Euler and Runge-Kutta4. For too large values of dt the simple Euler can diverge. """ diff --git a/examples/tutorial.py b/examples/tutorial.py index 8dd8f560..e0589c5a 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -16,7 +16,6 @@ from __future__ import division, print_function from random import gauss, uniform as u from vtkplotter import * -from vtkplotter import datadir print(__doc__) diff --git a/examples/volumetric/imageOperations.py b/examples/volumetric/imageOperations.py index 5f8a0035..e2badda6 100644 --- a/examples/volumetric/imageOperations.py +++ b/examples/volumetric/imageOperations.py @@ -1,6 +1,6 @@ """ Perform other simple mathematical operation between 3d images. -Possible operations are: +, -, /, 1/x, sin, cos, exp, log, abs, **2, sqrt, +Possible operations are: +, -, /, 1/x, sin, cos, exp, log, abs, **2, sqrt, min, max, atan, atan2, median, mag, dot, gradient, divergence, laplacian. Alphas defines the opacity transfer function in the scalar range. """ diff --git a/examples/volumetric/interpolateVolume.py b/examples/volumetric/interpolateVolume.py index 2bae7a6b..ce70b5c0 100644 --- a/examples/volumetric/interpolateVolume.py +++ b/examples/volumetric/interpolateVolume.py @@ -1,53 +1,25 @@ """ Generate a voxel dataset (vtkImageData) by interpolating a scalar -which is only known on a scattered set of points. -This is obtained by using RBF (radial basis function). +which is only known on a scattered set of points or mesh. +Available interpolation kernels are: shepard, gaussian, voronoi, linear """ -# @Author: Giovanni Dalmasso -from __future__ import print_function -import vtk +# Author: Giovanni Dalmasso from vtkplotter import * import numpy as np -bins = 30 # nr. of voxels per axis -npts = 60 # nr. of points of known scalar value +npts = 60 # nr. of points of known scalar value +coords = np.random.rand(npts, 3) # range is [0, 1] +scals = np.abs(coords[:, 2]) # let the scalar be the z of point itself -img = vtk.vtkImageData() -img.SetDimensions(bins, bins, bins) # range is [0, bins-1] -img.AllocateScalars(vtk.VTK_FLOAT, 1) +apts = Points(coords).addPointScalars(scals,'scals') -coords = np.random.rand(npts, 3) # range is [0, 1] -scals = np.abs(coords[:, 2]) # let the scalar be the z of point itself -fact = 1.0 / (bins - 1) # conversion factor btw the 2 ranges +img = interpolateToImageData(apts, kernel='shepard', radius=0.3) -vp = Plotter(verbose=0, bg="white", axes=1) -vp.ztitle = "z == scalar value" -cloud = Points(coords) +printHistogram(img, bins=25, c='b') -# fill the vtkImageData object -pb = ProgressBar(0, bins, c=4) -for iz in pb.range(): - pb.print() - for iy in range(bins): - for ix in range(bins): +#write(img, 'imgcube.vti') - pt = vector(ix, iy, iz) * fact - closestPointsIDs = cloud.closestPoint(pt, N=5, returnIds=True) - - num, den = 0, 0 - for i in closestPointsIDs: # work out RBF manually on N points - invdist = 1 / (mag2(coords[i] - pt) + 1e-06) - num += scals[i] * invdist - den += invdist - img.SetScalarComponentFromFloat(ix, iy, iz, 0, num / den) - -# vp.write(img, 'imgcube.tif') # or .vti - -# set colors and transparencies along the scalar range vol = Volume(img, c=["r", "g", "b"], alphas=[0.4, 0.8]) # vtkVolume -act = Points(coords / fact) - -printHistogram(vol, bins=25, c='b') -vp.show(vol, act, Text(__doc__), viewup="z") +show(apts, vol, Text(__doc__), bg="white", axes=8) diff --git a/examples/volumetric/mesh2volume.py b/examples/volumetric/mesh2volume.py index 74b874a4..cf530c93 100644 --- a/examples/volumetric/mesh2volume.py +++ b/examples/volumetric/mesh2volume.py @@ -6,6 +6,8 @@ """ from vtkplotter import * +doc = Text(__doc__, c="k") + s = load(datadir+"shapes/bunny.obj").normalize().wire() img = actor2ImageData(s, spacing=(0.02, 0.02, 0.02)) @@ -14,6 +16,6 @@ iso = isosurface(img, smoothing=0.9).color("b") -show(v, s.scale(1.05), Text(__doc__, c="k"), at=0, N=2, bg="w", verbose=0) +show(v, s.scale(1.05), doc, at=0, N=2, bg="w") show(iso, at=1, interactive=1) diff --git a/examples/volumetric/probeLine.py b/examples/volumetric/probeLine.py index 091813f0..0670c188 100644 --- a/examples/volumetric/probeLine.py +++ b/examples/volumetric/probeLine.py @@ -17,4 +17,4 @@ # print(a.scalars(0)) # numpy scalars can be access here # print(a.scalars('vtkValidPointMask')) # the mask of valid points -show(lines + [Text(__doc__)], axes=4, verbose=0, bg="w") +show(lines, Text(__doc__), axes=4, bg="w") diff --git a/examples/volumetric/probePlane.py b/examples/volumetric/probePlane.py index 2b367ad6..7b1623f6 100644 --- a/examples/volumetric/probePlane.py +++ b/examples/volumetric/probePlane.py @@ -13,4 +13,4 @@ planes.append(a) # print(max(a.scalars(0))) # access scalars this way, 0 means first -show(planes + [Text(__doc__)], axes=4, verbose=0, bg="w") +show(planes, Text(__doc__), axes=4, bg="w") diff --git a/examples/volumetric/probePoints.py b/examples/volumetric/probePoints.py index 1b32d4a4..ec39a7a0 100644 --- a/examples/volumetric/probePoints.py +++ b/examples/volumetric/probePoints.py @@ -10,6 +10,7 @@ apts = probePoints(img, pts).pointSize(3) +#print(apts.scalars()) # check the list of point/cell scalars scals = apts.scalars(0) printHistogram(scals, minbin=1, horizontal=1, c='g') diff --git a/examples/volumetric/readVolumeAsIsoSurface.py b/examples/volumetric/readVolumeAsIsoSurface.py index edfb8498..71e87a73 100644 --- a/examples/volumetric/readVolumeAsIsoSurface.py +++ b/examples/volumetric/readVolumeAsIsoSurface.py @@ -31,4 +31,4 @@ a3 = load(datadir+"embryo.slc", c="g", smoothing=1, connectivity=1) # newPlotter triggers the instantiation of a new Plotter object -vp2 = show(a3, verbose=0, pos=(300, 300), newPlotter=True) +vp2 = show(a3, pos=(300, 300), newPlotter=True) diff --git a/examples/volumetric/read_vti.py b/examples/volumetric/read_vti.py index b17c9540..e8606272 100644 --- a/examples/volumetric/read_vti.py +++ b/examples/volumetric/read_vti.py @@ -25,4 +25,4 @@ iso = load("data/vase.vti", threshold=140).wire(True).alpha(0.1) # show command creates and returns an instance of class Plotter -show(vol, iso, Text(__doc__), verbose=0, bg="w") +show(vol, iso, Text(__doc__), bg="w") diff --git a/examples/volumetric/signedDistance.py b/examples/volumetric/signedDistance.py index 17484e5f..9a6e8b7e 100644 --- a/examples/volumetric/signedDistance.py +++ b/examples/volumetric/signedDistance.py @@ -1,7 +1,7 @@ """ A mixed example with class vtkSignedDistance: -generate a scalar field by the signed distance from a polydata, +generate a scalar field by the signed distance from a polydata, save it to stack.tif file, then extract an isosurface from the 3d image. """ diff --git a/setup.py b/setup.py index e5054510..f2b4adba 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,29 @@ from setuptools import setup +try: + VERSIONFILE = "vtkplotter/version.py" + verstrline = open(VERSIONFILE, "rt").read() + verstr = verstrline.split('=')[1].replace('\n','').replace("'","") +except: + verstr='unknown_version' + +############################################################## setup( name='vtkplotter', - version='2019.1.4', # change also in vtkplotter/__init__.py and docs/source/conf.py + version=verstr, packages=['vtkplotter'], scripts=['bin/vtkplotter', 'bin/vtkconvert'], install_requires=['vtk'], - description='''A python module for scientific visualization, + description='''A python module for scientific visualization, analysis and animation of 3D objects and point clouds based on VTK.''', - long_description="""A python module for scientific visualization, + long_description="""A python module for scientific visualization, analysis and animation of 3D objects and point clouds based on VTK. Check out https://vtkplotter.embl.es for documentation.""", author='Marco Musy', author_email='marco.musy@gmail.com', license='MIT', url='https://github.com/marcomusy/vtkplotter', - keywords='vtk 3D visualization mesh', + keywords='vtk 3D visualization mesh numpy', classifiers=['Intended Audience :: Science/Research', 'Intended Audience :: Education', 'Intended Audience :: Information Technology', @@ -35,18 +43,18 @@ ############################################################## -# # check version number here and in vtkplotter/__init__.py docs/source/conf.py - # # check examples +# change version in vtkplotter/version.py # cd ~/Projects/vtkplotter/ -# pip install . +# pip install . # ( sudo -H pip install . ) # cd examples # ./run_all.sh # cd other/dolfin -# ./run_all.sh +# ./run_all.sh # check vtkconvert: # vtkconvert data/290.vtk -to ply +# check on python2 the same # git status diff --git a/vtkplotter/__init__.py b/vtkplotter/__init__.py index f35dd23f..6c23cabc 100644 --- a/vtkplotter/__init__.py +++ b/vtkplotter/__init__.py @@ -1,7 +1,7 @@ """ .. image:: https://user-images.githubusercontent.com/32848391/46815773-dc919500-cd7b-11e8-8e80-8b83f760a303.png -A python module for scientific visualization, +A python module for scientific visualization, analysis and animation of 3D objects and point clouds based on VTK. .. note:: **Please check out the** `git repository `_. @@ -21,24 +21,25 @@ 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 `_. +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 `_, +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. **Have you found this software useful for your research? Please cite it as:** - -M. Musy, G. Dalmasso, & B. Sullivan. -"`vtkplotter`, a python module for scientific visualization, -analysis and animation of 3D objects and point clouds based on VTK." (version v8.9.0). Zenodo, + +M. Musy, et al., +"`vtkplotter`, a python module for scientific visualization, +analysis and animation of 3D objects and point clouds based on VTK." (version v8.9.0). Zenodo, `doi: 10.5281/zenodo.2561402 `_, 10 February 2019. """ from __future__ import print_function +from vtkplotter.version import _version __author__ = "Marco Musy" __license__ = "MIT" @@ -46,7 +47,7 @@ __email__ = "marco.musy@embl.es" __status__ = "dev" __website__ = "https://github.com/marcomusy/vtkplotter" -__version__ = "2019.1.4" ### defined also above, in setup.py and docs/source/conf.py +__version__ = _version from vtkplotter.plotter import * from vtkplotter.analysis import * diff --git a/vtkplotter/actors.py b/vtkplotter/actors.py index 2cb39ff5..4c313de1 100644 --- a/vtkplotter/actors.py +++ b/vtkplotter/actors.py @@ -9,7 +9,7 @@ __doc__ = ( """ -Submodule extending the ``vtkActor``, ``vtkVolume`` +Submodule extending the ``vtkActor``, ``vtkVolume`` and ``vtkImageActor`` objects functionality. """ + docs._defs @@ -45,13 +45,16 @@ def mergeActors(actors, tol=0): def collection(): """ Return the list of actor which have been created so far, - even without having assigned them a name. - Useful in loops. E.g.: + without having to assign them a name. + Useful in loops. - >>> from vtkplotter import Cone, collect, show - >>> for i in range(10): - >>> Cone(pos=[3*i, 0, 0], axis=[i, i-5, 0]) - >>> show(collection()) + :Example: + .. code-block:: python + + from vtkplotter import Cone, collection, show + for i in range(10): + Cone(pos=[3*i, 0, 0], axis=[i, i-5, 0]) + show(collection()) """ return settings.collectable_actors @@ -111,6 +114,7 @@ def isosurface(image, smoothing=0, threshold=None, connectivity=False): return a + ################################################# classes class Prop(object): """Adds functionality to ``Actor``, ``Assembly``, @@ -129,6 +133,7 @@ def __init__(self): self._time = 0 self._legend = None self.scalarbar = None + self.renderedAt = set() def show( self, @@ -470,6 +475,20 @@ def off(self): """Switch off actor visibility. Object is not removed.""" self.VisibilityOff() return self + + def box(self): + """Return the bounding box as a new ``Actor``. + + .. 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, width, height, c='gray').wire() + if isinstance(self.GetProperty(), vtk.vtkProperty): + oa.SetProperty(self.GetProperty()) + return oa #################################################### @@ -515,6 +534,7 @@ def __init__( self.point_locator = None self.cell_locator = None self.line_locator = None + self.scalarbar_actor = None self._bfprop = None # backface property holder self.GetProperty().SetInterpolationToFlat() @@ -540,7 +560,8 @@ def __init__( prp = self.GetProperty() if settings.renderPointsAsSpheres: - prp.RenderPointsAsSpheresOn() + if hasattr(prp, 'RenderPointsAsSpheresOn'): + prp.RenderPointsAsSpheresOn() if alpha is not None: prp.SetOpacity(alpha) @@ -584,6 +605,10 @@ def __add__(self, actors): return actors return Assembly([self, actors]) + #def __str__(self): + # utils.printInfo(self) + # return "" + def pickable(self, value=None): """Set/get pickable property of actor.""" if value is None: @@ -594,7 +619,7 @@ def pickable(self, value=None): def updateMesh(self, polydata): """ - Change or modify the polygonal mesh for the actor with a new one. + Overwrite the polygonal mesh of the actor with a new one. """ self.poly = polydata self.mapper.SetInputData(polydata) @@ -896,7 +921,7 @@ def bc(self, backColor=False): def lineWidth(self, lw=None): - """Set/get width of mesh edges. Same as lw().""" + """Set/get width of mesh edges. Same as `lw()`.""" if lw is not None: if lw == 0: self.GetProperty().EdgeVisibilityOff() @@ -908,7 +933,7 @@ def lineWidth(self, lw=None): return self def lw(self, lineWidth=None): - """Set/get width of mesh edges. Same as lineWidth()""" + """Set/get width of mesh edges. Same as `lineWidth()`.""" return self.lineWidth(lineWidth) def clean(self, tol=None): @@ -1086,7 +1111,7 @@ def distanceToMesh(self, actor, signed=False, negate=False): ''' Computes the (signed) distance from one mesh to another. - Example: |distance2mesh.py|_ + .. hint:: |distance2mesh| |distance2mesh.py|_ ''' poly1 = self.polydata() poly2 = actor.polydata() @@ -1131,14 +1156,9 @@ def clone(self, transformed=True): return cloned - def transformPolydata(self, transformation): - """Obsolete: use transformMesh().""" - print("~noentry Obsolete transformPolydata(): use transformMesh().\n") - return self.transformMesh(transformation) - def transformMesh(self, transformation): """ - Apply this transformation to the polygonal `mesh`, + Apply this transformation to the polygonal `mesh`, not to the actor's transformation, which is reset. :param transformation: ``vtkTransform`` or ``vtkMatrix4x4`` object. @@ -1240,8 +1260,8 @@ def shrink(self, fraction=0.85): Example: .. code-block:: python - from vtkplotter import load, Sphere, show - pot = load('data/shapes/teapot.vtk').shrink(0.75) + from vtkplotter import * + pot = load(datadir + 'shapes/teapot.vtk').shrink(0.75) s = Sphere(r=0.2).pos(0,0,-0.5) show(pot, s) @@ -1259,7 +1279,7 @@ def stretch(self, q1, q2): .. hint:: |aspring| |aspring.py|_ - .. note:: for ``Actors`` like helices, Line, cylinders, cones etc., + .. note:: for ``Actors`` like helices, Line, cylinders, cones etc., two attributes ``actor.base``, and ``actor.top`` are already defined. """ if self.base is None: @@ -1290,11 +1310,6 @@ def stretch(self, q1, q2): self.updateTrail() return self - def cutPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False): - """Deprecated: use cutWithPlane""" - colors.printc("~targetcut Plane deprecated: use cutWithPlane()", c=1, box="-") - return self.cutWithPlane(origin, normal, showcut) - 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. @@ -1302,6 +1317,16 @@ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False): :param origin: the cutting plane goes through this point :param normal: normal of the cutting plane :param showcut: if `True` show the cut off part of the mesh as thin wireframe. + + :Example: + .. code-block:: python + + from vtkplotter import Cube + + cube = Cube().cutWithPlane(normal=(1,1,1)) + cube.bc('pink').show() + + |cutcube| .. hint:: |trail| |trail.py|_ """ @@ -1477,6 +1502,33 @@ def triangle(self, verts=True, lines=True): tf.Update() return self.updateMesh(tf.GetOutput()) + def mapCellsToPoints(self): + """ + Transform cell data (i.e., data specified per cell) + into point data (i.e., data specified at cell points). + The method of transformation is based on averaging the data values + of all cells using a particular point. + """ + c2p = vtk.vtkCellDataToPointData() + c2p.SetInputData(self.polydata(False)) + c2p.Update() + 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. + + .. hint:: |mesh_map2cell| |mesh_map2cell.py|_ + + """ + p2c = vtk.vtkPointDataToCellData() + p2c.SetInputData(self.polydata(False)) + p2c.Update() + return self.updateMesh(p2c.GetOutput()) + def pointColors(self, scalars, cmap="jet", alpha=1, bands=None, vmin=None, vmax=None): """ Set individual point colors by providing a list of scalar values and a color map. @@ -1530,12 +1582,11 @@ def pointColors(self, scalars, cmap="jet", alpha=1, bands=None, vmin=None, vmax= lut.Build() for i, c in enumerate(cmap): col = colors.getColor(c) - if len(col) == 4: - r, g, b, a = col + r, g, b = col + if useAlpha: + lut.SetTableValue(i, r, g, b, alpha[i]) else: - r, g, b = col - a = 1 - lut.SetTableValue(i, r, g, b, a) + lut.SetTableValue(i, r, g, b, alpha) elif isinstance(cmap, vtk.vtkLookupTable): sname = "pointColors_lut" @@ -1605,18 +1656,18 @@ def cellColors(self, scalars, cmap="jet", alpha=1, bands=None, vmin=None, vmax=N lut = vtk.vtkLookupTable() # build the look-up table + if utils.isSequence(cmap): sname = "cellColors_custom" lut.SetNumberOfTableValues(len(cmap)) lut.Build() for i, c in enumerate(cmap): col = colors.getColor(c) - if len(col) == 4: - r, g, b, a = col + r, g, b = col + if useAlpha: + lut.SetTableValue(i, r, g, b, alpha[i]) else: - r, g, b = col - a = 1 - lut.SetTableValue(i, r, g, b, a) + lut.SetTableValue(i, r, g, b, alpha) elif isinstance(cmap, vtk.vtkLookupTable): sname = "cellColors_lut" @@ -1871,6 +1922,13 @@ def addGaussNoise(self, sigma): Add gaussian noise. :param float sigma: sigma is expressed in percent of the diagonal size of actor. + + :Example: + .. code-block:: python + + from vtkplotter import Sphere + + Sphere().addGaussNoise(1.0).show() """ sz = self.diagonalSize() pts = self.coordinates() @@ -1940,11 +1998,13 @@ def smoothWSinc(self, niter=15, passBand=0.1, edgeAngle=15, featureAngle=60): def fillHoles(self, size=None): - """Identifies and fills holes in input mesh. - Holes are identified by locating boundary edges, linking them together into loops, - and then triangulating the resulting loops. + """Identifies and fills holes in input mesh. + Holes are identified by locating boundary edges, linking them together into loops, + and then triangulating the resulting loops. :param float size: approximate limit to the size of the hole that can be filled. + + Example: |fillholes.py|_ """ fh = vtk.vtkFillHolesFilter() if not size: @@ -1959,6 +2019,8 @@ def fillHoles(self, size=None): def write(self, filename="mesh.vtk", binary=True): """ Write actor's polydata in its current transformation to file. + + """ import vtkplotter.vtkio as vtkio @@ -1969,7 +2031,7 @@ def write(self, filename="mesh.vtk", binary=True): ### stuff that is not returning the input (sometimes modified) actor ### def normalAt(self, i): - """Calculate normal at vertex point `i`.""" + """Return the normal vector at vertex point `i`.""" normals = self.polydata(True).GetPointData().GetNormals() return np.array(normals.GetTuple(i)) @@ -2015,10 +2077,10 @@ def polydata(self, transformed=True): def coordinates(self, transformed=True, copy=True): """ - Return the list of vertex coordinates of the input mesh. + Return the list of vertex coordinates of the input mesh. Same as `actor.getPoints()`. :param bool transformed: if `False` ignore any previous trasformation applied to the mesh. - :param bool copy: if `False` return the reference to the points + :param bool copy: if `False` return the reference to the points so that they can be modified in place. .. hint:: |align1.py|_ @@ -2063,7 +2125,7 @@ def move(self, u_function): def getTransform(self): """ Check if ``info['transform']`` exists and returns it. - Otherwise return current user transformation + Otherwise return current user transformation (where the actor is currently placed). """ if "transform" in self.info.keys(): @@ -2091,7 +2153,9 @@ def setTransform(self, T): def isInside(self, point, tol=0.0001): - """Return True if point is inside a polydata closed surface.""" + """ + Return True if point is inside a polydata closed surface. + """ poly = self.polydata(True) points = vtk.vtkPoints() points.InsertNextPoint(point) @@ -2106,7 +2170,11 @@ def isInside(self, point, tol=0.0001): return sep.IsInside(0) def insidePoints(self, points, invert=False, tol=1e-05): - """Return the sublist of points that are inside a polydata closed surface.""" + """ + Return the sublist of points that are inside a polydata closed surface. + + .. hint:: |pca| |pca.py|_ + """ poly = self.polydata(True) # check if the stl file is closed @@ -2245,9 +2313,20 @@ def connectedCells(self, index, returnIds=False): def intersectWithLine(self, p0, p1): - """Return the list of points intersecting the actor along segment p0 and p1. + """Return the list of points intersecting the actor + along the segment defined by two points `p0` and `p1`. + + :Example: + .. code-block:: python - .. hint:: |spherical_harmonics1.py|_ |spherical_harmonics2.py|_ + from vtkplotter import * + s = Spring(alpha=0.2) + pts = s.intersectWithLine([0,0,0], [1,0.1,0]) + ln = Line([0,0,0], [1,0.1,0], c='blue') + ps = Points(pts, r=10, c='r') + show(s, ln, ps, bg='white') + + |intline| """ if not self.line_locator: line_locator = vtk.vtkOBBTree() @@ -2347,7 +2426,7 @@ def alpha(self, a=None): else: return self.GetProperty().GetOpacity() - def crop(self, top=None, bottom=None, left=None, right=None): + def crop(self, top=None, bottom=None, right=None, left=None): """Crop image. :param float top: fraction to crop from the top margin @@ -2357,7 +2436,7 @@ def crop(self, top=None, bottom=None, left=None, right=None): """ extractVOI = vtk.vtkExtractVOI() extractVOI.SetInputData(self.GetInput()) - extractVOI.IncludeBoundaryOn () + extractVOI.IncludeBoundaryOn() d = self.GetInput().GetDimensions() bx0, bx1, by0, by1 = 0, d[0]-1, 0, d[1]-1 @@ -2368,7 +2447,7 @@ def crop(self, top=None, bottom=None, left=None, right=None): extractVOI.SetVOI(bx0, bx1, by0, by1, 0, 0) extractVOI.Update() img = extractVOI.GetOutput() - img.SetOrigin(-bx0, -by0, 0) + #img.SetOrigin(-bx0, -by0, 0) self.GetMapper().SetInputData(img) self.GetMapper().Modified() return self @@ -2420,10 +2499,10 @@ def __init__(self, img, c="blue", alphas=(0.0, 0.4, 0.9, 1)): self.mapper.SetBlendModeToMaximumIntensity() self.mapper.UseJitteringOn() self.mapper.SetInputData(img) - colors.printc("scalar range is", np.round(img.GetScalarRange(), 4), c="b", bold=0) + #colors.printc("scalar range is", np.round(img.GetScalarRange(), 4), c="b", bold=0) smin, smax = img.GetScalarRange() if smax > 1e10: - print("~lightning Warning, high scalar range detected:", smax) + print("Warning, high scalar range detected:", smax) smax = abs(10 * smin) + 0.1 print(" reset to:", smax) @@ -2433,8 +2512,8 @@ def __init__(self, img, c="blue", alphas=(0.0, 0.4, 0.9, 1)): r, g, b = colors.getColor(ci) xalpha = smin + (smax - smin) * i / (len(c) - 1) colorTransferFunction.AddRGBPoint(xalpha, r, g, b) - colors.printc('\tcolor at', round(xalpha, 1), - '\tset to', colors.getColorName((r, g, b)), c='b', bold=0) + #colors.printc('\tcolor at', round(xalpha, 1), + # '\tset to', colors.getColorName((r, g, b)), c='b', bold=0) else: # Create transfer mapping scalar value to color r, g, b = colors.getColor(c) @@ -2447,7 +2526,7 @@ def __init__(self, img, c="blue", alphas=(0.0, 0.4, 0.9, 1)): xalpha = smin + (smax - smin) * i / (len(alphas) - 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) + #colors.printc(" alpha at", round(xalpha, 1), "\tset to", al, c="b", bold=0) # The property describes how the data will look volumeProperty = vtk.vtkVolumeProperty() @@ -2467,6 +2546,92 @@ def __init__(self, img, c="blue", alphas=(0.0, 0.4, 0.9, 1)): self.SetMapper(self.mapper) self.SetProperty(volumeProperty) + def imagedata(self): + """Return the ``vtkImagaData`` object.""" return self.image + + + def threshold(self, vmin=None, vmax=None, replaceWith=None): + """ + Binary or continuous volume thresholding. + """ + th = vtk.vtkImageThreshold() + th.SetInputData(self.image) + + if vmin is not None and vmax is not None: + th.ThresholdBetween(vmin, vmax) + elif vmin is not None: + th.ThresholdByUpper(vmin) + elif vmax is not None: + th.ThresholdByLower(vmax) + + if replaceWith: + th.ReplaceOutOn() + th.SetOutValue(replaceWith) + th.Update() + + self.image = th.GetOutput() + self.mapper.SetInputData(self.image) + self.mapper.Modified() + return self + + def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=None): + """Crop a ``Volume``. + + :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) + """ + extractVOI = vtk.vtkExtractVOI() + extractVOI.SetInputData(self.image) + extractVOI.IncludeBoundaryOn() + + d = self.image.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() + img = extractVOI.GetOutput() + #img.SetOrigin(-bx0, -by0, -bz0) + self.GetMapper().SetInputData(img) + self.GetMapper().Modified() + return self + + + def getVoxelsScalar(self, vmin=None, vmax=None): + """ + Return voxel content as a ``numpy.array``. + + :param float vmin: rescale scalar content to match `vmin` and `vmax` range. + :param float vmax: rescale scalar content to match `vmin` and `vmax` range. + """ + nx, ny, nz = self.image.GetDimensions() + + voxdata = np.zeros([nx, ny, nz]) + lsx = range(nx) + lsy = range(ny) + lsz = range(nz) + renorm = vmin is not None and vmax is not None + for i in lsx: + for j in lsy: + for k in lsz: + s = self.image.GetScalarComponentAsFloat(i, j, k, 0) + if renorm: + s = (s - vmin) / (vmax - vmin) + voxdata[i, j, k] = s + return voxdata + + + + + diff --git a/vtkplotter/addons.py b/vtkplotter/addons.py index f9adcf2c..c0f396be 100644 --- a/vtkplotter/addons.py +++ b/vtkplotter/addons.py @@ -6,23 +6,52 @@ import vtkplotter.shapes as shapes from vtkplotter.actors import Assembly, Actor import vtkplotter.utils as utils -import vtkplotter.vtkio as vtkio import vtkplotter.settings as settings +import vtkplotter.docs as docs import numpy import vtk -__all__ = [] +__doc__ = ( + """ +Additional objects like axes, legends etc.. +""" + + docs._defs +) + +__all__ = [ + "addScalarBar", + "addScalarBar3D", + "addSlider2D", + "addSlider3D", + "addButton", + "addCutterTool", + "addIcon", + "addAxes", + "addFrame", + "addLegend", + ] def addScalarBar(actor=None, c=None, title="", horizontal=False, vmin=None, vmax=None): + """Add a 2D scalar bar for the specified actor. + + If `actor` is ``None`` will add it to the last actor in ``self.actors``. + .. hint:: |mesh_bands| |mesh_bands.py|_ + """ vp = settings.plotter_instance + if actor is None: actor = vp.lastActor() + if not hasattr(actor, "mapper"): - colors.printc("~timesError in addScalarBar: input is not a Actor.", c=1) + colors.printc("~times Error in addScalarBar: input is not a Actor.", c=1) return None + if vp and vp.renderer and actor.scalarbar_actor: + vp.renderer.RemoveActor(actor.scalarbar) + + lut = actor.mapper.GetLookupTable() if not lut: return None @@ -96,6 +125,7 @@ def addScalarBar(actor=None, c=None, title="", horizontal=False, vmin=None, vmax sb.PickableOff() vp.renderer.AddActor(sb) vp.scalarbars.append(sb) + actor.scalarbar_actor = sb vp.renderer.Render() return sb @@ -110,13 +140,28 @@ def addScalarBar3D( nlabels=9, ncols=256, cmap=None, - c="k", + c=None, alpha=1, ): + """Draw a 3D scalar bar. + + ``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. + .. hint:: |scalbar| |mesh_coloring.py|_ + """ from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk vp = settings.plotter_instance + if c is None: # automatic black or white + c = (0.8, 0.8, 0.8) + if numpy.sum(colors.getColor(vp.backgrcol)) > 1.5: + c = (0.2, 0.2, 0.2) + c = colors.getColor(c) + gap = 0.4 # space btw nrs and scale vtkscalars_name = "" if obj is None: @@ -143,7 +188,7 @@ def addScalarBar3D( cmap = vtkscalars_name # build the color scale part - scale = shapes.Grid([-sx * gap, 0, 0], c="w", alpha=alpha, sx=sx, sy=sy, resx=1, resy=ncols) + scale = shapes.Grid([-sx * gap, 0, 0], c=c, alpha=alpha, sx=sx, sy=sy, resx=1, resy=ncols) scale.GetProperty().SetRepresentationToSurface() cscals = scale.cellCenters()[:, 1] @@ -195,12 +240,26 @@ def _cellColors(scale, scalars, cmap, alpha): vp.renderers[at].Render() sact.PickableOff() vp.scalarbars.append(sact) + if isinstance(obj, Actor): + obj.scalarbar_actor = sact return sact def addSlider2D(sliderfunc, xmin, xmax, value=None, pos=4, s=.04, title='', c=None, showValue=True): - + """Add a slider widget which can call an external custom function. + + :param sliderfunc: external function to be called by the widget + :param float xmin: lower value + :param float xmax: upper value + :param float value: current value + :param list pos: position corner number: horizontal [1-4] or vertical [11-14] + it can also be specified by corners coordinates [(x1,y1), (x2,y2)] + :param str title: title text + :param bool showValue: if true current value is shown + + .. hint:: |sliders| |sliders.py|_ + """ vp = settings.plotter_instance if c is None: # automatic black or white c = (0.8, 0.8, 0.8) @@ -313,6 +372,22 @@ def addSlider3D( c=None, showValue=True, ): + """Add a 3D slider widget which can call an external custom function. + + :param sliderfunc: external function to be called by the widget + :param list pos1: first position coordinates + :param list pos2: second position coordinates + :param float xmin: lower value + :param float xmax: upper value + :param float value: initial value + :param float s: label scaling factor + :param str title: title text + :param c: slider color + :param float rotation: title rotation around slider axis + :param bool showValue: if True current value is shown + + .. hint:: |sliders3d| |sliders3d.py|_ + """ vp = settings.plotter_instance if c is None: # automatic black or white c = (0.8, 0.8, 0.8) @@ -371,7 +446,7 @@ def addButton( states=("On", "Off"), c=("w", "w"), bc=("dg", "dr"), - pos=[20, 40], + pos=(20, 40), size=24, font="arial", bold=False, @@ -379,11 +454,26 @@ def addButton( alpha=1, angle=0, ): - + """Add a button to the renderer window. + + :param list states: a list of possible states ['On', 'Off'] + :param c: a list of colors for each state + :param bc: a list of background colors for each state + :param pos: 2D position in pixels from left-bottom corner + :param size: size of button font + :param str font: font type (arial, courier, times) + :param bool bold: bold face (False) + :param bool italic: italic face (False) + :param float alpha: opacity level + :param float angle: anticlockwise rotation in degrees + + .. hint:: |buttons| |buttons.py|_ + """ vp = settings.plotter_instance if not vp.renderer: colors.printc("~timesError: Use addButton() after rendering the scene.", c=1) return + import vtkplotter.vtkio as vtkio bu = vtkio.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle) vp.renderer.AddActor2D(bu.actor) vp.window.Render() @@ -392,7 +482,10 @@ def addButton( def addCutterTool(actor): + """Create handles to cut away parts of a mesh. + .. hint:: |cutter| |cutter.py|_ + """ if isinstance(actor, vtk.vtkVolume): return _addVolumeCutterTool(actor) elif isinstance(actor, vtk.vtkImageData): @@ -513,7 +606,14 @@ def ClipVolumeRender(obj, event): def addIcon(iconActor, 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, + or it can be a tuple (x,y) as a fraction of the renderer size. + :param float size: size of the square inset. + + .. hint:: |icon| |icon.py|_ + """ vp = settings.plotter_instance if not vp.renderer: colors.printc("~lightningWarning: Use addIcon() after first rendering the scene.", c=3) @@ -543,7 +643,22 @@ def addIcon(iconActor, pos=3, size=0.08): def addAxes(axtype=None, c=None): - + """Draw axes on scene. Available axes types: + + :param int axtype: + + - 0, no axes, + - 1, draw three gray grid walls + - 2, show cartesian axes from (0,0,0) + - 3, show positive range of cartesian axes from (0,0,0) + - 4, show a triad at bottom left + - 5, show a cube at bottom left + - 6, mark the corners of the bounding box + - 7, draw a simple ruler at the bottom of the window + - 8, show the ``vtkCubeAxesActor`` object + - 9, show the bounding box outLine + - 10, show three circles representing the maximum bounding box + """ vp = settings.plotter_instance if axtype is not None: vp.axes = axtype # overrride @@ -619,13 +734,13 @@ def addAxes(axtype=None, c=None): if len(vp.xtitle) == 1: # add axis length info xtitle = vp.xtitle + " /" + utils.precision(sizes[0], 4) wpos = [1 - (len(vp.xtitle) + 1) / 40, off, 0] - xt = shapes.Text(xtitle, pos=wpos, normal=(0, 0, 1), + xt = shapes.Text(xtitle, pos=wpos, normal=(0, 0, 1), s=0.025, c=c, justify="bottom-right") if vp.ytitle: if min_bns[2] <= 0 and max_bns[3] > 0: # mark y origin oy = shapes.Cube([0, -min_bns[2] / sizes[1], 0], side=0.008, c=c) - yt = shapes.Text(vp.ytitle, pos=(0, 0, 0), normal=(0, 0, 1), + yt = shapes.Text(vp.ytitle, pos=(0, 0, 0), normal=(0, 0, 1), s=0.025, c=c, justify="bottom-right") if len(vp.ytitle) == 1: wpos = [off, 1 - (len(vp.ytitle) + 1) / 40, 0] @@ -637,7 +752,7 @@ def addAxes(axtype=None, c=None): if vp.ztitle: if min_bns[4] <= 0 and max_bns[5] > 0: # mark z origin oz = shapes.Cube([0, 0, -min_bns[4] / sizes[2]], side=0.008, c=c) - zt = shapes.Text(vp.ztitle, pos=(0, 0, 0), normal=(1, -1, 0), + zt = shapes.Text(vp.ztitle, pos=(0, 0, 0), normal=(1, -1, 0), s=0.025, c=c, justify="bottom-right") if len(vp.ztitle) == 1: wpos = [off * 0.6, off * 0.6, 1 - (len(vp.ztitle) + 1) / 40] @@ -924,8 +1039,6 @@ def addFrame(c=None, alpha=0.5, bg=None, lw=0.5): mapper.SetTransformCoordinate(cs) fractor = vtk.vtkActor2D() - fractor.GetPositionCoordinate().SetCoordinateSystemToNormalizedViewport() - fractor.GetPosition2Coordinate().SetCoordinateSystemToNormalizedViewport() fractor.GetPositionCoordinate().SetValue(0, 0) fractor.GetPosition2Coordinate().SetValue(1, 1) fractor.SetMapper(mapper) diff --git a/vtkplotter/analysis.py b/vtkplotter/analysis.py index 8f043180..5976a1ef 100644 --- a/vtkplotter/analysis.py +++ b/vtkplotter/analysis.py @@ -28,7 +28,6 @@ "delaunay3D", "normalLines", "extractLargestRegion", - "align", "alignLandmarks", "alignICP", "alignProcrustes", @@ -60,6 +59,8 @@ "extractSurface", "geometry", "voronoi3D", + "connectedPoints", + "interpolateToImageData", ] @@ -422,7 +423,7 @@ def histogram2D(xvalues, yvalues, bins=12, norm=1, c="g", alpha=1, fill=False): def delaunay2D(plist, mode='xy', tol=None): """ Create a mesh from points in the XY plane. - If `mode='fit'` then the filter computes a best fitting + If `mode='fit'` then the filter computes a best fitting plane and projects the points onto it. .. hint:: |delaunay2d| |delaunay2d.py|_ @@ -505,16 +506,16 @@ def extractLargestRegion(actor): def alignLandmarks(source, target, rigid=False): """ - Find best matching of source points towards target + Find best matching of source points towards target in the mean least square sense, in one single step. """ lmt = vtk.vtkLandmarkTransform() ss = source.polydata().GetPoints() st = target.polydata().GetPoints() if source.N() != target.N(): - vc.printc('~times Error in alignLandmarks(): Source and Target with != nr of points!', + vc.printc('~times Error in alignLandmarks(): Source and Target with != nr of points!', source.N(), target.N(), c=1) - exit() + exit() lmt.SetSourceLandmarks(ss) lmt.SetTargetLandmarks(st) if rigid: @@ -532,12 +533,6 @@ def alignLandmarks(source, target, rigid=False): return actor -def align(source, target, iters=100, rigid=False): - """Obsolete: use alignICP().""" - vc.printc("~noentry Obsolete align(): use alignICP().", c=1) - return alignICP(source, target, iters, rigid) - - def alignICP(source, target, iters=100, rigid=False): """ Return a copy of source actor which is aligned to @@ -739,7 +734,7 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False): """ try: from scipy.stats import f - except: + except ImportError: vc.printc("~times Error in Ellipsoid(): scipy not installed. Skip.", c=1) return None if isinstance(points, vtk.vtkActor): @@ -868,8 +863,7 @@ def smoothMLS2D(actor, f=0.2, decimate=1, recursive=0, showNPlanes=0): :param recursive: move points while algorithm proceedes. :param showNPlanes: build an actor showing the fitting plane for N random points. - .. hint:: |mesh_smoothers| - |mesh_smoothers.py|_ + .. hint:: |mesh_smoothers| |mesh_smoothers.py|_ |moving_least_squares2D| |moving_least_squares2D.py|_ @@ -1052,7 +1046,7 @@ def probePoints(img, pts): """ Takes a ``vtkImageData`` and probes its scalars at the specified points in space. """ - src = vtk.vtkProgrammableSource() + src = vtk.vtkProgrammableSource() def readPoints(): output = src.GetPolyDataOutput() points = vtk.vtkPoints() @@ -1118,13 +1112,13 @@ def probePlane(img, origin=(0, 0, 0), normal=(1, 0, 0)): return cutActor -def imageOperation(image1, operation="+", image2=None): +def imageOperation(image1, operation, image2=None): """ - Perform operations with ``vtkImageData`` objects. + Perform operations with ``vtkImageData`` objects. `image2` can be a constant value. - Possible operations are: ``+``, ``-``, ``/``, ``1/x``, ``sin``, ``cos``, ``exp``, ``log``, + Possible operations are: ``+``, ``-``, ``/``, ``1/x``, ``sin``, ``cos``, ``exp``, ``log``, ``abs``, ``**2``, ``sqrt``, ``min``, ``max``, ``atan``, ``atan2``, ``median``, ``mag``, ``dot``, ``gradient``, ``divergence``, ``laplacian``. @@ -1389,7 +1383,7 @@ def thinPlateSpline(actor, sourcePts, targetPts, userFunctions=(None, None)): """ `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. + 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 is saved in ``actor.info['transform']``. @@ -1514,60 +1508,83 @@ def meshQuality(actor, measure=6): return qactor -#def connectedPoints(point, actor, mode, radius, seeds, regions, vrange, normal): -## https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html -# # Extract all regions -# cpf0 = vtk.vtkConnectedPointsFilter() -# cpf0.SetInputData(actor.polydata()) -# cpf0.SetRadius(radius) -# cpf0.Update() -# -# # Extract point seeded regions -# cpf1 = vtk.vtkConnectedPointsFilter() -# cpf1.SetInputData(actor.polydata()) -# cpf1.SetRadius(radius) -# cpf1.SetExtractionModeToPointSeededRegions() -# cpf1.AddSeed(0) -# cpf1.AddSeed(2*100) -# cpf1.Update() -# -# # Test largest region -# cpf2 = vtk.vtkConnectedPointsFilter() -# cpf2.SetInputData(actor.polydata()) -# cpf2.SetRadius(radius) -# cpf2.SetExtractionModeToLargestRegion() -# cpf2.Update() -# -# # Test specified regions -# cpf3 = vtk.vtkConnectedPointsFilter() -# cpf3.SetInputData(actor.polydata()) -# cpf3.SetRadius(radius) -# cpf3.SetExtractionModeToSpecifiedRegions() -# cpf3.AddSpecifiedRegion(1) -# cpf3.AddSpecifiedRegion(3) -# cpf3.Update() -# -# # Extract all regions with scalar connectivity -# cpf4 = vtk.vtkConnectedPointsFilter() -# cpf4.SetInputData(actor.polydata()) -# cpf4.SetRadius(radius); -# cpf4.SetExtractionModeToLargestRegion() -# cpf4.ScalarConnectivityOn() -# cpf4.SetScalarRange(0, 0.99) -# cpf4.Update() -# -# # Extract point seeded regions -# cpf5 = vtk.vtkConnectedPointsFilter() -# cpf5.SetInputData(actor.polydata()) -# cpf5.SetRadius(radius) -# cpf5.SetExtractionModeToLargestRegion() -# cpf5.ScalarConnectivityOn() -# cpf5.SetScalarRange(0, 0.99) -# cpf5.AlignedNormalsOn() -# cpf5.SetNormalAngle(12.5) -# cpf5.Update() -# -# return None +def connectedPoints(actor, 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. + The default operation is to segment the points into "connected" regions where the connection + is determined by an appropriate distance measure. Each region is given a region id. + + Optionally, the filter can output the largest connected region of points; a particular region + (via id specification); those regions that are seeded using a list of input point ids; + or the region of points closest to a specified position. + + The key parameter of this filter is the radius defining a sphere around each point which defines + a local neighborhood: any other points in the local neighborhood are assumed connected to the point. + Note that the radius is defined in absolute terms. + + Other parameters are used to further qualify what it means to be a neighboring point. + For example, scalar range and/or point normals can be used to further constrain the neighborhood. + Also the extraction mode defines how the filter operates. + By default, all regions are extracted but it is possible to extract particular regions; + the region closest to a seed point; seeded regions; or the largest region found while processing. + By default, all regions are extracted. + + On output, all points are labeled with a region number. + However note that the number of input and output points may not be the same: + if not extracting all regions then the output size may be less than the input size. + + :param float radius: radius variable specifying a local sphere used to define local point neighborhood + :param int mode: + + - 0, Extract all regions + - 1, Extract point seeded regions + - 2, Extract largest region + - 3, Test specified regions + - 4, Extract all regions with scalar connectivity + - 5, Extract point seeded regions + + :param list regions: a list of non-negative regions id to extract + :param list vrange: scalar range to use to extract points based on scalar connectivity + :param list seeds: a list of non-negative point seed ids + :param list angle: points are connected if the angle between their normals is + within this angle threshold (expressed in degrees). + """ + # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html + cpf = vtk.vtkConnectedPointsFilter() + cpf.SetInputData(actor.polydata()) + cpf.SetRadius(radius) + if mode == 0: # Extract all regions + pass + + elif mode == 1: # Extract point seeded regions + cpf.SetExtractionModeToPointSeededRegions() + for s in seeds: + cpf.AddSeed(s) + + elif mode == 2: # Test largest region + cpf.SetExtractionModeToLargestRegion() + + elif mode == 3: # Test specified regions + cpf.SetExtractionModeToSpecifiedRegions() + for r in regions: + cpf.AddSpecifiedRegion(r) + + elif mode == 4: # Extract all regions with scalar connectivity + cpf.SetExtractionModeToLargestRegion() + cpf.ScalarConnectivityOn() + cpf.SetScalarRange(vrange[0], vrange[1]) + + elif mode == 5: # Extract point seeded regions + cpf.SetExtractionModeToLargestRegion() + cpf.ScalarConnectivityOn() + cpf.SetScalarRange(vrange[0], vrange[1]) + cpf.AlignedNormalsOn() + cpf.SetNormalAngle(angle) + + cpf.Update() + + return Actor(cpf.GetOutput()) def splitByConnectivity(actor, maxdepth=100): @@ -1715,7 +1732,7 @@ def convexHull(actor_or_list, alphaConstant=0): def actor2ImageData(actor, spacing=(1, 1, 1)): """ Convert a mesh it into volume representation as ``vtkImageData`` - where the foreground (exterior) voxels are 1 and the background + where the foreground (exterior) voxels are 1 and the background (interior) voxels are 0. Internally the ``vtkPolyDataToImageStencil`` class is used. @@ -1849,11 +1866,11 @@ def voronoi3D(nuclei, bbfactor=1, tol=None): p = tuple(map(float, ls[i][1:-1].split(','))) aid = sourcePoints.InsertNextPoint(p[0], p[1], p[2]) if tol: - bp = np.array([p[0]-b[0], p[0]-b[1], + bp = np.array([p[0]-b[0], p[0]-b[1], p[1]-b[2], p[1]-b[3], p[2]-b[4], p[2]-b[5]]) bp = np.abs(bp) < tol - if np.any(bp): + if np.any(bp): ids.append(None) else: ids.append(aid) @@ -1863,7 +1880,7 @@ def voronoi3D(nuclei, bbfactor=1, tol=None): # fill polygon elements if None in ids: continue - + faces = [] for j in range(n+3, len(ls)): face = tuple(map(int, ls[j][1:-1].split(','))) @@ -1889,4 +1906,66 @@ def voronoi3D(nuclei, bbfactor=1, tol=None): return voro +def interpolateToImageData(actor, kernel='shepard', radius=None, + bounds=None, nullValue=None, + dims=(20,20,20)): + """ + Generate a voxel dataset (vtkImageData) by interpolating a scalar + which is only known on a scattered set of points or mesh. + Available interpolation kernels are: shepard, gaussian, voronoi, linear. + + :param str kernel: interpolation kernel type [shepard] + :param float radius: radius of the local search + :param list bounds: bounding box of the output vtkImageData object + :param list dims: dimensions of the output vtkImageData object + :param float nullValue: value to be assigned to invalid points + """ + + output = actor.polydata() + + # Create a probe volume + probe = vtk.vtkImageData() + probe.SetDimensions(dims) + if bounds is None: + bounds = output.GetBounds() + probe.SetOrigin(bounds[0],bounds[2],bounds[4]) + probe.SetSpacing((bounds[1]-bounds[0])/(dims[0]-1), + (bounds[3]-bounds[2])/(dims[1]-1), + (bounds[5]-bounds[4])/(dims[2]-1)) + + if radius is None: + radius = min(bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4])/3 + + locator = vtk.vtkPointLocator() + locator.SetDataSet(output) + locator.BuildLocator() + + if kernel == 'shepard': + kern = vtk.vtkShepardKernel() + kern.SetPowerParameter(2) + kern.SetRadius(radius) + elif kernel == 'gaussian': + kern = vtk.vtkGaussianKernel() + kern.SetRadius(radius) + elif kernel == 'voronoi': + kern = vtk.vtkVoronoiKernel() + elif kernel == 'linear': + kern = vtk.vtkLinearKernel() + kern.SetRadius(radius) + else: + print('Error in interpolateToImageData, available kernels are:') + print(' [shepard, gaussian, voronoi, linear]') + exit() + + interpolator = vtk.vtkPointInterpolator() + interpolator.SetInputData(probe) + interpolator.SetSourceData(output) + interpolator.SetKernel(kern) + interpolator.SetLocator(locator) + if nullValue is not None: + interpolator.SetNullValue(nullValue) + else: + interpolator.SetNullPointsStrategyToClosestPoint() + interpolator.Update() + return interpolator.GetOutput() diff --git a/vtkplotter/colors.py b/vtkplotter/colors.py index c92577ec..81d55f7e 100644 --- a/vtkplotter/colors.py +++ b/vtkplotter/colors.py @@ -45,7 +45,7 @@ } except: _mapscales = None - pass # see below, this is dealt with in colorMap() + # see below, this is dealt with in colorMap() ######################################################### @@ -177,7 +177,7 @@ def _isSequence(arg): def getColor(rgb=None, hsv=None): """ - Convert a color to (r,g,b) format from many input formats. + Convert a color or list of colors to (r,g,b) format from many input formats. :param bool hsv: if set to `True`, rgb is assumed as (hue, saturation, value). @@ -193,6 +193,13 @@ def getColor(rgb=None, hsv=None): .. hint:: |colorcubes| |colorcubes.py|_ """ + #recursion, return a list if input is list of colors: + if _isSequence(rgb) and len(rgb) > 3: + seqcol = [] + for sc in rgb: + seqcol.append(getColor(sc)) + return seqcol + if str(rgb).isdigit(): rgb = int(rgb) @@ -208,7 +215,7 @@ def getColor(rgb=None, hsv=None): if len(c) == 3: return list(np.array(c) / 255.0) # RGB else: - return [c[0] / 255.0, c[1] / 255.0, c[2] / 255.0, c[3]] # RGBA + return (c[0] / 255.0, c[1] / 255.0, c[2] / 255.0, c[3]) # RGBA elif isinstance(c, str): # is string c = c.replace(",", " ").replace("/", " ").replace("alpha=", "") @@ -220,7 +227,7 @@ def getColor(rgb=None, hsv=None): else: print("Unknow color nickname:", c) print("Available abbreviations:", color_nicks) - return [0.5, 0.5, 0.5] + return (0.5, 0.5, 0.5) if c.lower() in colors.keys(): # matplotlib name color c = colors[c.lower()] @@ -236,8 +243,8 @@ def getColor(rgb=None, hsv=None): rgbh = np.array(rgb255) / 255.0 if np.sum(rgbh) > 3: print("Error in getColor(): Wrong hex color", c) - return [0.5, 0.5, 0.5] - return list(rgbh) + return (0.5, 0.5, 0.5) + return tuple(rgbh) elif isinstance(c, int): # color number if c >= 0: @@ -251,8 +258,8 @@ def getColor(rgb=None, hsv=None): else: return colors2[int(-c) % 10] - print("Unknown color:", c) - return [0.5, 0.5, 0.5] + #print("Unknown color:", c) + return (0.5, 0.5, 0.5) def getColorName(c): @@ -306,6 +313,7 @@ def colorMap(value, name="jet", vmin=None, vmax=None): from vtkplotter import colorMap import matplotlib.cm as cm print( colorMap(0.2, cm.flag, 0, 1) ) + (1.0, 0.809016994374948, 0.6173258487801733) """ if not _mapscales: @@ -351,7 +359,7 @@ def colorMap(value, name="jet", vmin=None, vmax=None): def makePalette(color1, color2, N, hsv=True): """ - Generate N colors starting from `color1` to `color2` + Generate N colors starting from `color1` to `color2` by linear interpolation HSV in or RGB spaces. :param int N: number of output colors. @@ -380,7 +388,7 @@ def makeLUTfromCTF(sclist, N=None): Use a Color Transfer Function to generate colors in a vtk lookup table. See `here `_. - :param list sclist: a list in the form ``[(scalar1, [r,g,b]), (scalar2, 'blue'), ...]``. + :param list sclist: a list in the form ``[(scalar1, [r,g,b]), (scalar2, 'blue'), ...]``. :return: the lookup table object ``vtkLookupTable``. This can be fed into ``colorMap``. """ ctf = vtk.vtkColorTransferFunction() @@ -625,8 +633,10 @@ def printc(*strings, **keys): printc('anything', c='red', bold=False, end='' ) printc('anything', 455.5, vtkObject, c='green') printc(299792.48, c=4) # 4 is blue + + .. hint:: |colorprint.py|_ - .. hint:: |colorprint| |colorprint.py|_ + |colorprint| """ end = keys.pop("end", "\n") @@ -761,7 +771,7 @@ def printHistogram(data, bins=10, height=10, logscale=False, minbin=0, :param bool char: use boldface :param str title: histogram title - :Example: + :Example: .. code-block:: python from vtkplotter import printHistogram @@ -772,7 +782,7 @@ def printHistogram(data, bins=10, height=10, logscale=False, minbin=0, |printhisto| """ - # Adapted from http://pyinsci.blogspot.com/2009/10/ascii-histograms.html + # Adapted from http://pyinsci.blogspot.com/2009/10/ascii-histograms.html if not horizontal: # better aspect ratio bins *= 2 @@ -800,9 +810,9 @@ def printHistogram(data, bins=10, height=10, logscale=False, minbin=0, from vtk.util.numpy_support import vtk_to_numpy data = vtk_to_numpy(arr) - - h = np.histogram(data, bins=bins) - + + h = np.histogram(data, bins=bins) + if minbin: hi = h[0][minbin:-1] else: @@ -812,7 +822,7 @@ def printHistogram(data, bins=10, height=10, logscale=False, minbin=0, char = "*" # python2 hack if char == u"\U00002589" and horizontal: char = u"\U00002586" - + entrs = "\t(entries=" + str(len(data)) + ")" if logscale: h0 = np.log10(hi+1) @@ -831,7 +841,7 @@ def _v(): for l in reversed(range(1, height + 1)): line = "" if l == height: - line = "%s " % maxh0 + line = "%s " % maxh0 else: line = " |" + " " * (len(str(maxh0))-3) for c in bars: diff --git a/vtkplotter/data/lshape.xml.gz b/vtkplotter/data/lshape.xml.gz new file mode 100644 index 00000000..e280aa4e Binary files /dev/null and b/vtkplotter/data/lshape.xml.gz differ diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py index e072f227..5d752986 100644 --- a/vtkplotter/docs.py +++ b/vtkplotter/docs.py @@ -23,15 +23,6 @@ """ from __future__ import division, print_function -#from vtkplotter.settings import enableDolfin -#if enableDolfin: -# try: -# import dolfin -# except ModuleNotFoundError: -# print("\nDolfin/Fenics package not found. Install it or set:") -# print("vtkplotter.enableDolfin = False\nAbort.\n") -# exit() - def onelinetip(): import vtk, sys @@ -75,6 +66,8 @@ def tips(): msg += "| Middle-click to pan scene |\n" msg += "| Right-click to zoom scene in or out |\n" msg += "| Cntrl-click to rotate scene perpendicularly |\n" + msg += "|------ |\n" + msg += "|Check out documentation at: https://vtkplotter.embl.es |\n" msg += "--------------------------------------------------------------\n" colors.printc(msg, dim=1) @@ -111,7 +104,6 @@ def tips(): .. |fillholes.py| replace:: fillholes.py .. _fillholes.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/fillholes.py - :width: 250 px .. |quadratic_morphing.py| replace:: quadratic_morphing.py .. _quadratic_morphing.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/quadratic_morphing.py @@ -441,8 +433,8 @@ def tips(): .. |tannerhelland| replace:: tannerhelland .. _tannerhelland: http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code -.. |colorprint.py| replace:: colorprint.py -.. _colorprint.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/colorprint.py +.. |colorprint.py| replace:: printc.py +.. _colorprint.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/printc.py .. |colorprint| image:: https://user-images.githubusercontent.com/32848391/50739010-2bfc2b80-11da-11e9-94de-011e50a86e61.jpg :target: colorprint.py_ :alt: colorprint.py @@ -634,6 +626,13 @@ def tips(): :target: glyphs.py_ :alt: glyphs.py +.. |glyphs_arrow.py| replace:: glyphs_arrow.py +.. _glyphs_arrow.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/glyphs_arrow.py +.. |glyphs_arrow| image:: https://user-images.githubusercontent.com/32848391/55897850-a1a0da80-5bc1-11e9-81e0-004c8f396b43.jpg + :width: 250 px + :target: glyphs_arrow.py_ + :alt: glyphs_arrow.py + .. |interpolateField.py| replace:: interpolateField.py .. _interpolateField.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/interpolateField.py .. |interpolateField| image:: https://user-images.githubusercontent.com/32848391/52416117-25b6e300-2ae9-11e9-8d86-575b97e543c0.png @@ -688,18 +687,18 @@ def tips(): :alt: sliders3d.py .. |ex01_showmesh.py| replace:: ex01_showmesh.py -.. _ex01_showmesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex01_showmesh.py +.. _ex01_showmesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex01_show-mesh.py .. |ex01_showmesh| image:: https://user-images.githubusercontent.com/32848391/53026243-d2d31900-3462-11e9-9dde-518218c241b6.jpg :width: 250 px :target: ex01_showmesh.py_ :alt: ex01_showmesh.py -.. |ex02_tetralize_mesh.py| replace:: ex02_tetralize_mesh.py -.. _ex02_tetralize_mesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex02_tetralize_mesh.py -.. |ex02_tetralize_mesh| image:: https://user-images.githubusercontent.com/32848391/53026244-d2d31900-3462-11e9-835a-1fa9d66d3dae.png +.. |ex02_tetralize-mesh.py| replace:: ex02_tetralize-mesh.py +.. _ex02_tetralize-mesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex02_tetralize-mesh.py +.. |ex02_tetralize-mesh| image:: https://user-images.githubusercontent.com/32848391/53026244-d2d31900-3462-11e9-835a-1fa9d66d3dae.png :width: 250 px - :target: ex02_tetralize_mesh.py_ - :alt: ex02_tetralize_mesh.py + :target: ex02_tetralize-mesh.py_ + :alt: ex02_tetralize-mesh.py .. |ex06_elasticity1.py| replace:: ex06_elasticity1.py .. _ex06_elasticity1.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex06_elasticity1.py @@ -728,16 +727,13 @@ def tips(): .. |pmatrix| image:: https://user-images.githubusercontent.com/32848391/55098070-6da3c080-50bd-11e9-8f2b-be94a3f01831.png :width: 250 px - -.. |stokes.py| replace:: stokes.py -.. _stokes.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/stokes.py -.. |stokes| image:: https://user-images.githubusercontent.com/32848391/55098209-aba0e480-50bd-11e9-8842-42d3f0b2d9c8.png - :width: 250 px - :target: stokes.py_ - :alt: stokes.py .. |distance2mesh.py| replace:: distance2mesh.py .. _distance2mesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/distance2mesh.py +.. |distance2mesh| image:: https://user-images.githubusercontent.com/32848391/55965881-b5a71380-5c77-11e9-8680-5bddceab813a.png + :width: 250 px + :target: distance2mesh.py_ + :alt: distance2mesh.py .. |pendulum.py| replace:: pendulum.py .. _pendulum.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/pendulum.py @@ -754,9 +750,94 @@ def tips(): :alt: latex.py .. |ft04_heat_gaussian.py| replace:: ft04_heat_gaussian.py -.. _.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ft04_heat_gaussian.py +.. _ft04_heat_gaussian.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ft04_heat_gaussian.py .. |ft04_heat_gaussian| image:: https://user-images.githubusercontent.com/32848391/55578167-88a5ae80-5715-11e9-84ea-bdab54099887.gif :width: 250 px :target: ft04_heat_gaussian.py_ :alt: ft04_heat_gaussian.py + +.. |scalbar| image:: https://user-images.githubusercontent.com/32848391/55964528-2ac51980-5c75-11e9-9357-8c13d753a612.png + :width: 250 px + +.. |cutcube| image:: https://user-images.githubusercontent.com/32848391/55965516-08cc9680-5c77-11e9-8d23-720f6c088ea2.png + :width: 200 px + +.. |intline| image:: https://user-images.githubusercontent.com/32848391/55967065-eee08300-5c79-11e9-8933-265e1bab9f7e.png + :width: 350 px + + +.. |turing_pattern.py| replace:: turing_pattern.py +.. _turing_pattern.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/turing_pattern.py +.. |turing_pattern| image:: https://user-images.githubusercontent.com/32848391/56056437-77cfeb00-5d5c-11e9-9887-828e5745d547.gif + :width: 250 px + :target: turing_pattern.py_ + :alt: turing_pattern.py + +.. |demo_cahn-hilliard.py| replace:: demo_cahn-hilliard.py +.. _demo_cahn-hilliard.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/demo_cahn-hilliard.py +.. |demo_cahn-hilliard| image:: https://user-images.githubusercontent.com/32848391/56664730-edb34b00-66a8-11e9-9bf3-73431f2a98ac.gif + :width: 250 px + :target: demo_cahn-hilliard.py_ + :alt: demo_cahn-hilliard.py + + +.. |navier-stokes_lshape.py| replace:: navier-stokes_lshape.py +.. _navier-stokes_lshape.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/navier-stokes_lshape.py +.. |navier-stokes_lshape| image:: https://user-images.githubusercontent.com/32848391/56671156-6bc91f00-66b4-11e9-8c58-e6b71e2ad1d0.gif + :width: 250 px + :target: navier-stokes_lshape.py_ + :alt: navier-stokes_lshape.py + + +.. |mesh_map2cell.py| replace:: mesh_map2cell.py +.. _mesh_map2cell.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/mesh_map2cell.py +.. |mesh_map2cell| image:: https://user-images.githubusercontent.com/32848391/56600859-0153a880-65fa-11e9-88be-34fd96b18e9a.png + :width: 250 px + :target: mesh_map2cell.py_ + :alt: mesh_map2cell.py + + +.. |ex03_poisson.py| replace:: ex03_poisson.py +.. _ex03_poisson.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex03_poisson.py +.. |ex03_poisson| image:: https://user-images.githubusercontent.com/32848391/54925524-bec18200-4f0e-11e9-9eab-29fd61ef3b8e.png + :width: 250 px + :target: ex03_poisson.py_ + :alt: ex03_poisson.py + +.. |elastodynamics.py| replace:: elastodynamics.py +.. _elastodynamics.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/elastodynamics.py +.. |elastodynamics| image:: https://user-images.githubusercontent.com/32848391/54932788-bd4a8680-4f1b-11e9-9326-33645171a45e.gif + :width: 250 px + :target: elastodynamics.py_ + :alt: elastodynamics.py + +.. |ft02_poisson_membrane.py| replace:: ft02_poisson_membrane.py +.. _ft02_poisson_membrane.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ft02_poisson_membrane.py +.. |ft02_poisson_membrane| image:: https://user-images.githubusercontent.com/32848391/55499287-ed91d380-5645-11e9-8e9a-e31e2e3b1649.jpg + :width: 250 px + :target: ft02_poisson_membrane.py_ + :alt: ft02_poisson_membrane.py + + +.. |stokes.py| replace:: stokes.py +.. _stokes.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/stokes.py +.. |stokes| image:: https://user-images.githubusercontent.com/32848391/55098209-aba0e480-50bd-11e9-8842-42d3f0b2d9c8.png + :width: 250 px + :target: stokes.py_ + :alt: stokes.py + +.. |demo_submesh.py| replace:: demo_submesh.py +.. _demo_submesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/demo_submesh.py +.. |demo_submesh| image:: https://user-images.githubusercontent.com/32848391/56675428-4e984e80-66bc-11e9-90b0-43dde7e4cc29.png + :width: 250 px + :target: demo_submesh.py_ + :alt: demo_submesh.py + +.. |pi_estimate.py| replace:: pi_estimate.py +.. _pi_estimate.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/pi_estimate.py +.. |pi_estimate| image:: https://user-images.githubusercontent.com/32848391/56675429-4e984e80-66bc-11e9-9217-a0652a8e74fe.png + :width: 250 px + :target: pi_estimate.py_ + :alt: pi_estimate.py + """ diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py index 9e404d68..88d02ebc 100644 --- a/vtkplotter/dolfin.py +++ b/vtkplotter/dolfin.py @@ -2,6 +2,7 @@ # from __future__ import division, print_function +import vtk from vtk.util.numpy_support import numpy_to_vtk import numpy as np @@ -9,7 +10,6 @@ import vtkplotter.utils as utils import vtkplotter.docs as docs -import vtkplotter.colors as colors from vtkplotter.colors import printc, printHistogram import vtkplotter.settings as settings @@ -29,7 +29,7 @@ __doc__ = ( """ -FEniCS/Dolfin support submodule. +`FEniCS/Dolfin https://fenicsproject.org`_ support submodule. Install with commands (e.g. in Anaconda): @@ -51,8 +51,29 @@ .. image:: https://user-images.githubusercontent.com/32848391/53026243-d2d31900-3462-11e9-9dde-518218c241b6.jpg -Find many more examples in +Find many more examples in `vtkplotter/examples/dolfin `_ + + +Image Gallery +============= + +*(click on the thumbnail to get to the script)* + ++--------------------------------+--------------------------------+ +| |ex03_poisson| | |ex02_tetralize-mesh| | ++--------------------------------+--------------------------------+ +| |demo_submesh| | |pi_estimate| | ++--------------------------------+--------------------------------+ +| |ex06_elasticity1| | |ex06_elasticity2| | ++--------------------------------+--------------------------------+ +| |ft04_heat_gaussian| | |demo_cahn-hilliard| | ++--------------------------------+--------------------------------+ +| |navier-stokes_lshape| | |turing_pattern| | ++--------------------------------+--------------------------------+ +| |elastodynamics| | |ft02_poisson_membrane| | ++--------------------------------+--------------------------------+ + """ + docs._defs ) @@ -102,8 +123,8 @@ def _inputsort(obj): V = dolfin.FunctionSpace(mesh, "CG", 1) elif r == 1: # maybe wrong: V = dolfin.VectorFunctionSpace(mesh, "CG", 1, dim=r) - else: # very wrong: - V = dolfin.TensorFunctionSpace(mesh, "CG", 1, shape=(r,r)) +# else: # very wrong: +# V = dolfin.TensorFunctionSpace(mesh, "CG", 1, shape=(r,r)) u = dolfin.Function(V) v2d = dolfin.vertex_to_dof_map(V) u.vector()[v2d] = ob.array() @@ -113,7 +134,7 @@ def _inputsort(obj): return None # tdim = mesh.topology().dim() -# d = ob.dim() +# d = ob.dim() # if tdim == 2 and d == 2: # import matplotlib.tri as tri # xy = mesh.coordinates() @@ -148,7 +169,7 @@ def _inputsort(obj): def plot(*inputobj, **options): """ - Plot the object(s) provided. + Plot the object(s) provided. Input can be: ``vtkActor``, ``vtkVolume``, ``dolfin.Mesh``, ``dolfin.MeshFunction*``, ``dolfin.Expression`` or ``dolfin.Function``. @@ -164,20 +185,23 @@ def plot(*inputobj, **options): - `arrows`, mesh displacements are plotted as scaled arrows. - `lines`, mesh displacements are plotted as scaled lines. - `tensors`, to be implemented - + + :param bool add: add the input objects without clearing the previous ones + :param float density: show only a subset of lines or arrows [0-1] :param bool wire[frame]: visualize mesh as wireframe [False] :param c[olor]: set mesh color [None] :param float alpha: set object's transparency [1] :param float lw: line width of the mesh (set to zero to hide mesh) [0.5] :param float ps: set point size of mesh vertices [None] + :param float z: add a constant to z-coordinate (useful to show 2D slices as function of time) :param str legend: add a legend to the top-right of window [None] :param bool scalarbar: add a scalarbar to the window ['vertical'] :param float vmin: set the minimum for the range of the scalar [None] :param float vmax: set the maximum for the range of the scalar [None] :param float scale: add a scaling factor to arrows and lines sizes [1] :param str cmap: choose a color map for scalars - :param int bands: group colors in `n` bands - :param shading: mesh shading ['flat', 'phong', 'gouraud'] + :param int bands: group colors in `n` bands + :param str shading: mesh shading ['flat', 'phong', 'gouraud'] :param str text: add a gray text comment to the top-left of the window [None] :param bool newPlotter: spawn a new instance of Plotter class, pops up a new window @@ -216,7 +240,7 @@ def plot(*inputobj, **options): :param bool sharecam: if False each renderer will have an independent vtkCamera :param bool interactive: if True will stop after show() to allow interaction w/ window :param bool depthpeeling: depth-peel volumes along with the translucent geometry - :param bool offscreen: if True will not show the rendering window + :param bool offscreen: if True will not show the rendering window :param float zoom: camera zooming factor :param viewup: camera view-up direction ['x','y','z', or a vector direction] @@ -228,7 +252,7 @@ def plot(*inputobj, **options): .. hint:: |ex01_showmesh| |ex01_showmesh.py|_ - |ex02_tetralize_mesh| |ex02_tetralize_mesh.py|_ + |ex02_tetralize-mesh| |ex02_tetralize-mesh.py|_ |ex06_elasticity1| |ex06_elasticity1.py|_ @@ -238,12 +262,16 @@ def plot(*inputobj, **options): if len(inputobj) == 0: if settings.plotter_instance: settings.plotter_instance.interactor.Start() - return + return settings.plotter_instance mesh, u = _inputsort(inputobj) mode = options.pop("mode", 'mesh') + ttime = options.pop("z", None) + #density = options.pop("density", None) #todo + add = options.pop("add", False) + wire = options.pop("wire", False) wireframe = options.pop("wireframe", None) if wireframe is not None: @@ -257,19 +285,23 @@ def plot(*inputobj, **options): alpha = options.pop("alpha", 1) lw = options.pop("lw", 0.5) ps = options.pop("ps", None) - legend = options.pop("legend", None) + legend = options.pop("legend", None) scbar = options.pop("scalarbar", 'v') vmin = options.pop("vmin", None) vmax = options.pop("vmax", None) - cmap = options.pop("cmap", None) - bands = options.pop("bands", None) + cmap = options.pop("cmap", None) + bands = options.pop("bands", None) scale = options.pop("scale", 1) shading = options.pop("shading", None) text = options.pop("text", None) style = options.pop("style", 'vtk') + + settings.xtitle = options.pop("xtitle", 'x') + settings.ytitle = options.pop("ytitle", 'y') + settings.ztitle = options.pop("ztitle", 'z') # change some default to emulate matplotlib behaviour - options['verbose'] = False # dont disturb + options['verbose'] = False # dont disturb if style == 0 or style == 'vtk': font = 'courier' axes = options.pop('axes', None) @@ -304,7 +336,7 @@ def plot(*inputobj, **options): cmap = 'coolwarm' elif style == 3 or style == 'meshlab': font = 'courier' - bg = options.pop('bg', None) + bg = options.pop('bg', None) if bg is None: options['bg'] = (8, 8, 16) options['bg2'] = (117, 117, 234) @@ -324,19 +356,24 @@ def plot(*inputobj, **options): options['bg'] = (217, 255, 238) else: options['bg'] = bg - axes = options.pop('axes', None) + axes = options.pop('axes', None) if axes is None: options['axes'] = 8 else: options['axes'] = axes # put back if cmap is None: - cmap = 'gray' + cmap = 'binary' ################################################################# actors = [] + if add and settings.plotter_instance: + actors = settings.plotter_instance.actors + if 'mesh' in mode or 'color' in mode: actor = MeshActor(u, mesh, wire=wire) + if ttime: + actor.z(ttime) if legend: actor.legend(legend) if c: @@ -363,16 +400,15 @@ def plot(*inputobj, **options): delta = [u(p) for p in mesh.coordinates()] #delta = u.compute_vertex_values(mesh) # needs reshape if u.value_rank() > 0: # wiil show the size of the vector - actor.pointColors(utils.mag(delta), + actor.pointColors(utils.mag(delta), cmap=cmap, bands=bands, vmin=vmin, vmax=vmax) else: actor.pointColors(delta, cmap=cmap, bands=bands, vmin=vmin, vmax=vmax) - if scbar: - if c is None: - if 'h' in scbar: - actor.addScalarBar(horizontal=True, vmin=vmin, vmax=vmax) - else: - actor.addScalarBar(horizontal=False, vmin=vmin, vmax=vmax) + if scbar and c is None: + if 'h' in scbar: + actor.addScalarBar(horizontal=True, vmin=vmin, vmax=vmax) + else: + actor.addScalarBar(horizontal=False, vmin=vmin, vmax=vmax) if 'warp' in mode or 'displace' in mode: if delta is None: @@ -405,7 +441,7 @@ def plot(*inputobj, **options): ################################################################# if 'tensor' in mode: - pass + pass #todo ################################################################# @@ -413,25 +449,25 @@ def plot(*inputobj, **options): inputtype = str(type(ob)) if 'vtk' in inputtype: actors.append(ob) - - if text: - bgc = (0.6, 0.6, 0.6) - if 'bg' in options.keys(): - bgc = colors.getColor(options['bg']) - if sum(bgc) < 1.5: - bgc = 'w'#(0.9, 0.9, 0.9) - else: - bgc = (0.1, 0.1, 0.1) - actors.append(Text(text, c=bgc, font=font)) + + if text: + textact = Text(text, font=font) + actors.append(textact) - if 'at' in options.keys() and not 'interactive' in options.keys(): + if 'at' in options.keys() and 'interactive' not in options.keys(): if settings.plotter_instance: N = settings.plotter_instance.shape[0]*settings.plotter_instance.shape[1] if options['at'] == N-1: options['interactive'] = True - - vp = show(actors, **options) - return vp + + if settings.plotter_instance: + for a2 in settings.collectable_actors: + if isinstance(a2, vtk.vtkCornerAnnotation): + if 0 in a2.renderedAt: # remove old message + settings.plotter_instance.removeActor(a2) + break + + return show(actors, **options) ################################################################################### @@ -502,7 +538,7 @@ def MeshPoints(*inputobj, **options): plist = np.insert(plist, 2, 0, axis=1) # make it 3d if len(plist[0]) == 1: # coords are 1d.. not good.. plist = np.insert(plist, 1, 0, axis=1) # make it 3d - plist = np.insert(plist, 2, 0, axis=1) + plist = np.insert(plist, 2, 0, axis=1) actor = shapes.Points(plist, r=r, c=c, alpha=alpha) @@ -524,10 +560,10 @@ def MeshLines(*inputobj, **options): Build the line segments between two lists of points `startPoints` and `endPoints`. `startPoints` can be also passed in the form ``[[point1, point2], ...]``. - A dolfin ``Mesh`` that was deformed/modified by a function can be + A dolfin ``Mesh`` that was deformed/modified by a function can be passed together as inputs. - :param float scale: apply a rescaling factor to the length + :param float scale: apply a rescaling factor to the length """ scale = options.pop("scale", 1) lw = options.pop("lw", 1) @@ -561,7 +597,7 @@ def MeshArrows(*inputobj, **options): Build arrows representing displacements. :param float s: cross-section size of the arrow - :param float rescale: apply a rescaling factor to the length + :param float rescale: apply a rescaling factor to the length """ s = options.pop("s", None) scale = options.pop("scale", 1) @@ -588,17 +624,22 @@ def MeshArrows(*inputobj, **options): actor.u = u actor.u_values = u_values return actor - -def MeshTensors(*inputobj, **options): - """Not yet implemented.""" + +#def MeshTensors(*inputobj, **options): +# """Not yet implemented.""" # c = options.pop("c", "gray") # alpha = options.pop("alpha", 1) # mesh, u = _inputsort(inputobj) - return +# return + + + + + + -# -*- coding: utf-8 -*- # Copyright (C) 2008-2012 Joachim B. Haga and Fredrik Valdmanis # # This file is part of DOLFIN. diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py index e6c2371e..e16e3de9 100644 --- a/vtkplotter/plotter.py +++ b/vtkplotter/plotter.py @@ -116,8 +116,8 @@ def show(*actors, **options elif len(actors) == 1: actors = actors[0] else: - actors = utils.flatten(actors) - + actors = utils.flatten(actors) + if settings.plotter_instance and newPlotter == False: vp = settings.plotter_instance else: @@ -136,12 +136,6 @@ def show(*actors, **options colors.printc(' you may need to specify e.g. at=0', c=1) exit() at = range(len(actors)) - -# if settings.plotter_instance: -# prevcam = settings.plotter_instance.camera -# prevsharecam = settings.plotter_instance.sharecam -# else: -# prevcam = False vp = Plotter( shape=shape, @@ -159,10 +153,6 @@ def show(*actors, **options interactive=interactive, offscreen=offscreen, ) - -# if prevcam: -# vp.camera = prevcam - if utils.isSequence(at): for i, a in enumerate(actors): @@ -185,6 +175,7 @@ def show(*actors, **options actors, at=at, zoom=zoom, + resetcam=resetcam, viewup=viewup, azimuth=azimuth, elevation=elevation, @@ -257,15 +248,15 @@ class Plotter: :param list shape: shape of the grid of renderers in format (rows, columns). Ignored if N is specified. :param int N: number of desired renderers arranged in a grid automatically. - :param list pos: (x,y) position in pixels of top-left corneer of the rendering window + :param list pos: (x,y) position in pixels of top-left corneer of the rendering window on the screen :param size: size of the rendering window. If 'auto', guess it based on screensize. - :param screensize: physical size of the monitor screen + :param screensize: physical size of the monitor screen :param bg: background color or specify jpg image file name with path :param bg2: background color of a gradient towards the top - :param int axes: + :param int axes: - - 0, no axes, + - 0, no axes - 1, draw three gray grid walls - 2, show cartesian axes from (0,0,0) - 3, show positive range of cartesian axes from (0,0,0) @@ -273,19 +264,19 @@ class Plotter: - 5, show a cube at bottom left - 6, mark the corners of the bounding box - 7, draw a simple ruler at the bottom of the window - - 8, show the ``vtkCubeAxesActor`` object, + - 8, show the ``vtkCubeAxesActor`` object - 9, show the bounding box outLine, - 10, show three circles representing the maximum bounding box. :param bool infinity: if True fugue point is set at infinity (no perspective effects) :param bool sharecam: if False each renderer will have an independent vtkCamera :param bool interactive: if True will stop after show() to allow interaction w/ window - :param bool offscreen: if True will not show the rendering window + :param bool offscreen: if True will not show the rendering window :param bool depthpeeling: depth-peel volumes along with the translucent geometry |multiwindows| """ - + def __init__( self, shape=(1, 1), @@ -328,9 +319,6 @@ def __init__( self.interactive = interactive # allows to interact with renderer self.axes = axes # show axes type nr. self.title = title # window title - self.xtitle = "x" # x axis label and units - self.ytitle = "y" # y axis label and units - self.ztitle = "z" # z axis label and units self.sharecam = sharecam # share the same camera if multiple renderers self.infinity = infinity # ParallelProjection On or Off self._legend = [] # list of legend entries for actors @@ -362,6 +350,10 @@ def __init__( self.mouseLeftClickFunction = None self.mouseMiddleClickFunction = None self.mouseRightClickFunction = None + + self.xtitle = settings.xtitle # x axis label and units + self.ytitle = settings.ytitle # y axis label and units + self.ztitle = settings.ztitle # z axis label and units # sort out screen size self.window = vtk.vtkRenderWindow() @@ -532,7 +524,7 @@ def load( threshold=None, connectivity=False, ): - """ + """ Returns a ``vtkActor`` from reading a file, directory or ``vtkPolyData``. :param c: color in RGB format, hex, symbol or name @@ -555,6 +547,37 @@ def load( return acts + def getVolumes(self, obj=None, renderer=None): + """ + Return the list of the rendered Volumes. + """ + if renderer is None: + renderer = self.renderer + elif isinstance(renderer, int): + renderer = self.renderers.index(renderer) + else: + return [] + + if obj is None or isinstance(obj, int): + if obj is None: + acs = renderer.GetVolumes() + elif obj >= len(self.renderers): + colors.printc("~timesError in getVolumes: non existing renderer", obj, c=1) + return [] + else: + acs = self.renderers[obj].GetVolumes() + vols = [] + acs.InitTraversal() + for i in range(acs.GetNumberOfItems()): + a = acs.GetNextItem() + if a.GetPickable(): + r = self.renderers.index(renderer) + if a == self.axes_exist[r]: + continue + vols.append(a) + return vols + + def getActors(self, obj=None, renderer=None): """ Return an actors list. @@ -562,7 +585,7 @@ def getActors(self, obj=None, renderer=None): If ``obj`` is: ``None``, return actors of current renderer - ``int``, return actors in given renderer number + ``int``, return actors in given renderer number ``vtkAssembly`` return the contained actors @@ -574,8 +597,7 @@ def getActors(self, obj=None, renderer=None): renderer = self.renderer elif isinstance(renderer, int): renderer = self.renderers.index(renderer) - - if not renderer: + else: return [] if obj is None or isinstance(obj, int): @@ -611,7 +633,7 @@ def getActors(self, obj=None, renderer=None): elif isinstance(obj, str): # search the actor by the legend name actors = [] for a in self.actors: - if hasattr(a, "_legend") and obj in a._legend and a.GetPickable(): + if hasattr(a, "_legend") and obj in a._legend: actors.append(a) return actors @@ -736,7 +758,7 @@ def addScalarBar3D( nlabels=9, ncols=256, cmap=None, - c="k", + c=None, alpha=1, ): """Draw a 3D scalar bar. @@ -747,7 +769,7 @@ def addScalarBar3D( - 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. - .. hint:: |mesh_coloring| |mesh_coloring.py|_ + .. hint:: |scalbar| |mesh_coloring.py|_ """ return addons.addScalarBar3D(obj, at, pos, normal, sx, sy, nlabels, ncols, cmap, c, alpha) @@ -809,7 +831,7 @@ def addButton( states=("On", "Off"), c=("w", "w"), bc=("dg", "dr"), - pos=[20, 40], + pos=(20, 40), size=24, font="arial", bold=False, @@ -898,11 +920,11 @@ def show( """ Render a list of actors. - Allowed input objects are: ``filename``, ``vtkPolyData``, ``vtkActor``, + Allowed input objects are: ``filename``, ``vtkPolyData``, ``vtkActor``, ``vtkActor2D``, ``vtkImageActor``, ``vtkAssembly`` or ``vtkVolume``. If filename is given, its type is guessed based on its extension. - Supported formats are: + Supported formats are: `vtu, vts, vtp, ply, obj, stl, 3ds, xml, neutral, gmsh, pcd, xyz, txt, byu, tif, slc, vti, mhd, png, jpg`. @@ -927,7 +949,7 @@ def show( :param float azimuth/elevation/roll: move camera accordingly :param str viewup: either ['x', 'y', 'z'] or a vector to set vertical direction :param bool resetcam: re-adjust camera position to fit objects - :param bool interactive: pause and interact with window (True) + :param bool interactive: pause and interact with window (True) or continue execution (False) :param float rate: maximum rate of `show()` in Hertz :param int interactorStyle: set the type of interaction @@ -977,7 +999,12 @@ def scan(wannabeacts): if a.trail and not a.trail in self.actors: scannedacts.append(a.trail) elif isinstance(a, vtk.vtkActor2D): - scannedacts.append(a) + if isinstance(a, vtk.vtkCornerAnnotation): + for a2 in settings.collectable_actors: + if isinstance(a2, vtk.vtkCornerAnnotation): + if at in a2.renderedAt: # remove old message + self.removeActor(a2) + scannedacts.append(a) elif isinstance(a, vtk.vtkImageActor): scannedacts.append(a) elif isinstance(a, vtk.vtkVolume): @@ -1091,18 +1118,28 @@ def scan(wannabeacts): self.renderer.AddVolume(ia) else: self.renderer.AddActor(ia) + if hasattr(ia, 'renderedAt'): + ia.renderedAt.add(at) else: colors.printc("~lightning Warning: Invalid actor in actors list, skip.", c=5) + # remove the ones that are not in actors2show for ia in self.getActors(at): if ia not in actors2show: self.renderer.RemoveActor(ia) - + if hasattr(ia, 'renderedAt'): + ia.renderedAt.discard(at) + + for c in self.scalarbars: + self.renderer.RemoveActor(c) + if hasattr(c, 'renderedAt'): + c.renderedAt.discard(at) + if self.axes is not None: addons.addAxes() - + addons.addLegend() - + if self.showFrame and len(self.renderers) > 1: addons.addFrame() @@ -1153,12 +1190,12 @@ def scan(wannabeacts): ): if len(a.scalarbar) == 5: # addScalarBar s1, s2, s3, s4, s5 = a.scalarbar - sb = self.addScalarBar(a, s1, s2, s3, s4, s5) + sb = addons.addScalarBar(a, s1, s2, s3, s4, s5) scbflag = True a.scalarbar = sb # save scalarbar actor elif len(a.scalarbar) == 10: # addScalarBar3D s0, s1, s2, s3, s4, s5, s6, s7, s8 = a.scalarbar - sb = self.addScalarBar3D(a, at, s0, s1, s2, s3, s4, s5, s6, s7, s8) + sb = addons.addScalarBar3D(a, at, s0, s1, s2, s3, s4, s5, s6, s7, s8) scbflag = True a.scalarbar = sb # save scalarbar actor if scbflag: @@ -1218,6 +1255,9 @@ def removeActor(self, a): return if self.renderer: self.renderer.RemoveActor(a) + if hasattr(a, 'renderedAt'): + ir = self.renderers.index(self.renderer) + a.renderedAt.discard(ir) if a in self.actors: i = self.actors.index(a) del self.actors[i] @@ -1230,10 +1270,14 @@ def clear(self, actors=()): for a in actors: self.removeActor(a) else: - settings.collectable_actors = [] + for a in settings.collectable_actors: + self.removeActor(a) + settings.collectable_actors = [] self.actors = [] for a in self.getActors(): self.renderer.RemoveActor(a) + for a in self.getVolumes(): + self.renderer.RemoveVolume(a) for s in self.sliders: s.EnabledOff() for b in self.buttons: @@ -1242,3 +1286,7 @@ def clear(self, actors=()): w.EnabledOff() for c in self.scalarbars: self.renderer.RemoveActor(c) + + + + diff --git a/vtkplotter/settings.py b/vtkplotter/settings.py index 52670ece..9467c05f 100644 --- a/vtkplotter/settings.py +++ b/vtkplotter/settings.py @@ -24,6 +24,9 @@ # allow to interact with scene during interactor.Start() execution allowInteraction = True +# usetex, matplotlib latex compiler +usetex = False + # Qt embedding usingQt = False @@ -34,6 +37,12 @@ # http://math.lbl.gov/voro++ voro_path = '/usr/local/bin' +# axes titles +xtitle = 'x' +ytitle = 'y' +ztitle = 'z' + + ##################### _cdir = os.path.dirname(__file__) @@ -44,6 +53,9 @@ datadir = _cdir + "/data/" +##################### +collectable_actors = [] + ##################### def _init(): diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py index f81e1792..85565753 100644 --- a/vtkplotter/shapes.py +++ b/vtkplotter/shapes.py @@ -146,19 +146,42 @@ def _colorPoints(plist, cols, r, alpha): return actor -def Glyph(actor, glyphObj, orientationArray="", scaleByVectorSize=False, c="gold", alpha=1): +def Glyph(actor, glyphObj, orientationArray="", + scaleByVectorSize=False, c=None, alpha=1): """ At each vertex of a mesh, another mesh - a `'glyph'` - is shown with various orientation options and coloring. - :param orientationArray: list of vectors, ``vtkAbstractArray`` - or the name of an already existing points array. + Color can be specfied as a colormap which maps the size of the orientation + vectors in `orientationArray`. + + :param orientationArray: list of vectors, ``vtkAbstractArray`` + or the name of an already existing points array. :type orientationArray: list, str, vtkAbstractArray :param bool scaleByVectorSize: glyph mesh is scaled by the size of the vectors. .. hint:: |glyphs| |glyphs.py|_ + + |glyphs_arrow| |glyphs_arrow.py|_ """ + cmap = None + # user passing a color map to map orientationArray sizes + if c in list(colors._mapscales.keys()): + cmap = c + c = None + + # 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 = colors.getColor(col) + ucols.InsertNextTuple3(cl[0]*255, cl[1]*255, cl[2]*255) + actor.polydata().GetPointData().SetScalars(ucols) + c = None + if isinstance(glyphObj, Actor): glyphObj = glyphObj.clean().polydata() @@ -186,26 +209,33 @@ def Glyph(actor, glyphObj, orientationArray="", scaleByVectorSize=False, c="gold elif utils.isSequence(orientationArray): # passing a list actor.addPointVectors(orientationArray, "glyph_vectors") gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") - gly.SetVectorModeToUseVector() else: # passing a name gly.SetInputArrayToProcess(0, 0, 0, 0, orientationArray) gly.SetVectorModeToUseVector() + if cmap: + gly.SetColorModeToColorByVector () + else: + gly.SetColorModeToColorByScalar () - if utils.isSequence(c) and len(c) != 3: - ucols = vtk.vtkUnsignedCharArray() - ucols.SetNumberOfComponents(3) - ucols.SetName("glyphRGB") - for col in c: - cl = colors.getColor(col) - ucols.InsertNextTuple3(cl[0]*255, cl[1]*255, cl[2]*255) - actor.polydata().GetPointData().SetScalars(ucols) - gly.SetScaleModeToDataScalingOff() - gly.Update() pd = gly.GetOutput() actor = Actor(pd, c, alpha) + + if cmap: + lut = vtk.vtkLookupTable() + lut.SetNumberOfTableValues(512) + lut.Build() + for i in range(512): + r, g, b = colors.colorMap(i, cmap, 0, 512) + lut.SetTableValue(i, r, g, b, 1) + actor.mapper.SetLookupTable(lut) + actor.mapper.ScalarVisibilityOn() + actor.mapper.SetScalarModeToUsePointData() + rng = pd.GetPointData().GetScalars().GetRange() + actor.mapper.SetScalarRange(rng[0], rng[1]) + actor.GetProperty().SetInterpolationToFlat() settings.collectable_actors.append(actor) return actor @@ -235,8 +265,7 @@ def Line(p0, p1=None, lw=1, c="r", alpha=1, dotted=False): ppoints = vtk.vtkPoints() # Generate the polyline dim = len((p0[0])) if dim == 2: - for i in range(len(p0)): - p = p0[i] + for i, p in enumerate(p0): ppoints.InsertPoint(i, p[0], p[1], 0) else: ppoints.SetData(numpy_to_vtk(p0, deep=True)) @@ -448,7 +477,7 @@ def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1): v = (sm1-sm2)/3*tipWidth p1 = sm1+v - p2 = sm2-v + p2 = sm2-v pm1 = (sm1+sm2)/2 pm2 = (np.array(line1[-2])+np.array(line2[-2]))/2 pm12 = pm1-pm2 @@ -521,52 +550,39 @@ def Arrows(startPoints, endPoints=None, s=None, scale=1, c="r", alpha=1, res=12) Build arrows between two lists of points `startPoints` and `endPoints`. `startPoints` can be also passed in the form ``[[point1, point2], ...]``. - A dolfin ``Mesh`` that was deformed/modified by a function can be - passed together as inputs. + Color can be specfied as a colormap which maps the size of the arrows. - :param float s: cross-section size of the arrow - :param float scale: apply a rescaling factor to the length - """ + :param float s: fix aspect-ratio of the arrow and scale its cross section + :param float scale: apply a rescaling factor to the length + :param c: color or array of colors + :param str cmap: color arrows by size using this color map + :param float alpha: set transparency + :param int res: set arrow resolution - if endPoints is not None: - startPoints = list(zip(startPoints, endPoints)) - - polyapp = vtk.vtkAppendPolyData() - for twopts in startPoints: - startPoint, endPoint = twopts - axis = np.array(endPoint) - np.array(startPoint) - length = np.linalg.norm(axis) - if length: - 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) - t = vtk.vtkTransform() - t.Translate(startPoint) - t.RotateZ(phi * 57.3) - t.RotateY(theta * 57.3) - t.RotateY(-90) # put it along Z - if s: - sz = 800.0 * s - t.Scale(length*scale, sz*scale, sz*scale) - else: - t.Scale(length*scale, length*scale, length*scale) - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputConnection(arr.GetOutputPort()) - tf.SetTransform(t) - polyapp.AddInputConnection(tf.GetOutputPort()) - polyapp.Update() - - actor = Actor(polyapp.GetOutput(), c, alpha) - settings.collectable_actors.append(actor) - return actor + .. hint:: |glyphs_arrow| |glyphs_arrow.py|_ + """ + startPoints = np.array(startPoints) + if endPoints is None: + strt = startPoints[:,0] + endPoints = startPoints[:,1] + startPoints = strt + + arr = vtk.vtkArrowSource() + arr.SetShaftResolution(res) + arr.SetTipResolution(res) + if s: + sz = 0.02 * s + arr.SetTipRadius(sz*2) + arr.SetShaftRadius(sz) + arr.SetTipLength(sz * 10) + arr.Update() + pts = Points(startPoints) + orients = (endPoints - startPoints) * scale + arrg = Glyph(pts, arr.GetOutput(), + orientationArray=orients, scaleByVectorSize=True, + c=c, alpha=alpha) + settings.collectable_actors.append(arrg) + return arrg def Polygon(pos=(0, 0, 0), normal=(0, 0, 1), nsides=6, r=1, c="coral", @@ -576,7 +592,7 @@ def Polygon(pos=(0, 0, 0), normal=(0, 0, 1), nsides=6, r=1, c="coral", :param followcam: if `True` the text will auto-orient itself to the active camera. A ``vtkCamera`` object can also be passed. - :type followcam: bool, vtkCamera + :type followcam: bool, vtkCamera |Polygon| """ @@ -760,8 +776,8 @@ def Spheres(centers, r=1, c="r", alpha=1, res=8): ucols.SetName("colors") for i, p in enumerate(centers): vpts.SetPoint(i, p) - cc = np.array(colors.getColor(c[i])) * 255 - ucols.InsertNextTuple3(cc[0], cc[1], cc[2]) + cx, cy, cz = colors.getColor(c[i]) + ucols.InsertNextTuple3(cx * 255, cy * 255, cz * 255) pd.GetPointData().SetScalars(ucols) glyph.ScalingOff() elif risseq: @@ -837,7 +853,8 @@ def Earth(pos=(0, 0, 0), r=1, lw=1): return ass -def Ellipsoid(pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), c="c", alpha=1, res=24): +def Ellipsoid(pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3), + c="c", alpha=1, res=24): """ Build a 3D ellipsoid centered at position `pos`. @@ -933,7 +950,7 @@ def Plane(pos=(0, 0, 0), normal=(0, 0, 1), sx=1, sy=None, c="g", bc="darkgreen", """ 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: @@ -1195,7 +1212,7 @@ def Paraboloid(pos=(0, 0, 0), r=1, height=1, axis=(0, 0, 1), c="cyan", alpha=1, Full volumetric expression is: :math:`F(x,y,z)=a_0x^2+a_1y^2+a_2z^2+a_3xy+a_4yz+a_5xz+ a_6x+a_7y+a_8z+a_9` - |paraboloid| + |paraboloid| """ quadric = vtk.vtkQuadric() quadric.SetCoefficients(1, 1, 0, 0, 0, 0, 0, 0, height / 4, 0) @@ -1286,7 +1303,7 @@ def Text( s=1, depth=0.1, justify="bottom-left", - c=(0.6, 0.6, 0.6), + c=None, alpha=1, bc=None, bg=None, @@ -1297,7 +1314,18 @@ def Text( Returns a ``vtkActor`` that shows a 3D text. :param pos: position in 3D space, - if an integer is passed [1,8], place a 2D text in one of the 4 corners. + if an integer is passed [1,8], + a 2D text is placed in one of the 4 corners: + + 1, bottom-left + 2, bottom-right + 3, top-left + 4, top-right + 5, bottom-middle + 6, middle-right + 7, middle-left + 8, top-middle + :type pos: list, int :param float s: size of text. :param float depth: text thickness. @@ -1314,15 +1342,25 @@ def Text( |markpoint| |markpoint.py|_ |annotations.py|_ Read a text file and shows it in the rendering window. - """ + """ + if c is None: # automatic black or white + if settings.plotter_instance and settings.plotter_instance.renderer: + c = (0.9, 0.9, 0.9) + if np.sum(settings.plotter_instance.renderer.GetBackground()) > 1.5: + c = (0.1, 0.1, 0.1) + else: + c = (0.6, 0.6, 0.6) + if isinstance(pos, int): if pos > 8: pos = 8 if pos < 1: pos = 1 + ca = vtk.vtkCornerAnnotation() ca.SetNonlinearFontScaleFactor(s / 3) ca.SetText(pos - 1, str(txt)) + ca.PickableOff() cap = ca.GetTextProperty() cap.SetColor(colors.getColor(c)) @@ -1338,6 +1376,8 @@ def Text( cap.SetBackgroundOpacity(alpha * 0.5) cap.SetFrameColor(bgcol) cap.FrameOn() + + setattr(ca, 'renderedAt', set()) settings.collectable_actors.append(ca) return ca @@ -1416,10 +1456,11 @@ def Latex( bg=None, alpha=1, res=30, + usetex=False, fromweb=False, ): """ - Latex formulas renderer. + Render Latex formulas. :param str formula: latex text string :param list pos: position coordinates in space @@ -1427,85 +1468,91 @@ def Latex( :param c: face color :param bg: background color box :param int res: dpi resolution - :param fromweb: retrieve the latex image from online server + :param bool usetex: use latex compiler of matplotlib + :param fromweb: retrieve the latex image from online server (codecogs) .. hint:: |latex| |latex.py|_ """ try: - import matplotlib.pyplot as plt - from PIL import Image #, ImageChops - from vtkplotter.actors import ImageActor - import io, os - except: - colors.printc('~times Latex Error: matplotlib and/or pillow not installed?', c=1) - return None - - def build_img_web(formula, tfile): - import requests - if c == 'k': - ct = 'Black' - else: - ct = 'White' - wsite = 'http://latex.codecogs.com/png.latex' - r = requests.get(wsite+'?\dpi{200} \huge \color{'+ct+'} ' + formula) - f = open(tfile, 'wb') - f.write(r.content) - f.close() - - def build_img_plt(formula): - buf = io.BytesIO() - plt.rc('text', usetex=True) - plt.axis('off') - col = colors.getColor(c) - if bg: - bx = dict(boxstyle="square", ec=col, fc=colors.getColor(bg)) - else: - bx = None - plt.text(0.5, 0.5, f'${formula}$', - size=res, - color=col, - alpha=alpha, - ha="center", - va="center", - bbox=bx) - plt.savefig(buf, format='png', transparent=True, bbox_inches='tight', pad_inches=0) - plt.close() - im = Image.open(buf) - return im - - if fromweb: - build_img_web(formula, 'lateximg.png') - else: - try: - build_img_plt(formula).save('lateximg.png') - except RuntimeError as err: - colors.printc(err, c=1) - colors.printc('dvipng not installed? Try > sudo apt install dvipng' , c=1) - return None - - picr = vtk.vtkPNGReader() - picr.SetFileName('lateximg.png') - picr.Update() - vactor = ImageActor() - vactor.SetInputData(picr.GetOutput()) - 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) - nax = np.linalg.norm(normal) - if nax: - normal = np.array(normal) / nax - theta = np.arccos(normal[2]) - phi = np.arctan2(normal[1], normal[0]) - vactor.SetScale(0.25/res*s, 0.25/res*s, 0.25/res*s) - vactor.RotateZ(phi * 57.3) - vactor.RotateY(theta * 57.3) - vactor.SetPosition(pos) - os.unlink('lateximg.png') - return vactor - + + #def _Latex(formula, pos, normal, c, s, bg, alpha, res, usetex, fromweb): + 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: + colors.printc('Latex error. Web site unavailable?', wsite, c=1) + return None + def build_img_plt(formula, tfile): + import matplotlib.pyplot as plt + plt.rc('text', usetex=usetex) + formula1 = '$'+formula+'$' + plt.axis('off') + col = colors.getColor(c) + if bg: + bx = dict(boxstyle="square", ec=col, fc=colors.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 ImageActor + + picr = vtk.vtkPNGReader() + picr.SetFileName('_lateximg.png') + picr.Update() + vactor = ImageActor() + vactor.SetInputData(picr.GetOutput()) + 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) + nax = np.linalg.norm(normal) + if nax: + normal = np.array(normal) / nax + theta = np.arccos(normal[2]) + phi = np.arctan2(normal[1], normal[0]) + vactor.SetScale(0.25/res*s, 0.25/res*s, 0.25/res*s) + vactor.RotateZ(phi * 57.3) + vactor.RotateY(theta * 57.3) + vactor.SetPosition(pos) + try: + import os + os.unlink('_lateximg.png') + except FileNotFoundError: + pass + return vactor + + except: + colors.printc('Error in Latex()\n', formula, c=1) + colors.printc(' latex or dvipng not installed?', c=1) + colors.printc(' Try: usetex=False' , c=1) + colors.printc(' Try: sudo apt install dvipng' , c=1) + return None + + diff --git a/vtkplotter/utils.py b/vtkplotter/utils.py index e6c8c4ed..30c6f820 100644 --- a/vtkplotter/utils.py +++ b/vtkplotter/utils.py @@ -43,15 +43,6 @@ def isSequence(arg): return False -# def flatten(lst): -# '''Flatten out a list''' -# flat_list = [] -# for sublist in lst: -# for item in sublist: -# flat_list.append(item) -# return flat_list - - def flatten(list_to_flatten): """Flatten out a list.""" @@ -452,6 +443,7 @@ def printvtkactor(actor, tab=""): 7: "(ruler at the bottom of the window)", 8: "(the vtkCubeAxesActor object)", 9: "(the bounding box outline)", + 10: "(circles of maximum bounding box range)", } bns, totpt = [], 0 for a in obj.actors: @@ -532,3 +524,10 @@ def makeBands(inputlist, numberOfBands): break return np.array(newlist) + + + + + + + \ No newline at end of file diff --git a/vtkplotter/version.py b/vtkplotter/version.py new file mode 100644 index 00000000..d473e7a5 --- /dev/null +++ b/vtkplotter/version.py @@ -0,0 +1 @@ +_version='2019.1.5' diff --git a/vtkplotter/vtkio.py b/vtkplotter/vtkio.py index c02bc64d..fe8c0915 100644 --- a/vtkplotter/vtkio.py +++ b/vtkplotter/vtkio.py @@ -10,6 +10,7 @@ from vtkplotter.actors import Actor, Assembly, ImageActor, isosurface import vtkplotter.docs as docs import vtkplotter.settings as settings +import vtkplotter.addons as addons __doc__ = ( """ @@ -57,7 +58,7 @@ def load( threshold=None, connectivity=False, ): - """ + """ Returns a ``vtkActor`` from reading a file, directory or ``vtkPolyData``. :param c: color in RGB format, hex, symbol or name @@ -119,7 +120,7 @@ def _loadFile(filename, c, alpha, wire, bc, texture, smoothing, threshold, conne actor = loadGmesh(filename, c, alpha, wire, bc) elif fl.endswith(".pcd"): # PCL point-cloud format actor = loadPCD(filename, c, alpha) - elif fl.endswith(".off"): + elif fl.endswith(".off"): actor = loadOFF(filename, c, alpha, wire, bc) elif fl.endswith(".3ds"): # 3ds point-cloud format actor = load3DS(filename) @@ -221,7 +222,7 @@ def loadMultiBlockData(filename, unpack=True): acts = [] for i in range(mb.GetNumberOfBlocks()): b = mb.GetBlock(i) - if isinstance(b, (vtk.vtkPolyData, + if isinstance(b, (vtk.vtkPolyData, vtk.vtkImageData, vtk.vtkUnstructuredGrid, vtk.vtkStructuredGrid, @@ -524,8 +525,6 @@ def loadImageData(filename, spacing=()): reader.SetFileName(filename) reader.Update() image = reader.GetOutput() - print(filename, "scalar range:", image.GetScalarRange()) - #colors.printHistogram() if len(spacing) == 3: image.SetSpacing(spacing[0], spacing[1], spacing[2]) return image @@ -584,7 +583,7 @@ def write(objct, fileoutput, binary=True): g = vtk.vtkMultiBlockDataGroupFilter() for ob in objct: g.AddInputData(ob) - g.Update() + g.Update() mb = g.GetOutputDataObject(0) wri = vtk.vtkXMLMultiBlockDataWriter() wri.SetInputData(mb) @@ -792,7 +791,12 @@ def buildPolyData(vertices, faces=None, indexOffset=0): ########################################################## def screenshot(filename="screenshot.png"): - """Save a screenshot of the current rendering window.""" + """ + Save a screenshot of the current rendering window. + """ + if not settings.plotter_instance.window: + colors.printc('~bomb screenshot(): Rendering window is not present, skip.', c=1) + return w2if = vtk.vtkWindowToImageFilter() w2if.ShouldRerenderOff() w2if.SetInput(settings.plotter_instance.window) @@ -1051,7 +1055,7 @@ def switch(self): # ############################################################### Mouse Events def _mouse_enter(iren, event): - x, y = iren.GetEventPosition() + #x, y = iren.GetEventPosition() #print('_mouse_enter mouse at', x, y) for ivp in settings.plotter_instances: @@ -1396,7 +1400,7 @@ def _keypress(iren, event): ia.GetProperty().SetColor(colors.colors1[(i + vp.icol) % 10]) ia.GetMapper().ScalarVisibilityOff() vp.icol += 1 - vp.addLegend() + addons.addLegend() elif key == "2": if vp.clickedActor and hasattr(vp.clickedActor, "GetProperty"): @@ -1408,7 +1412,7 @@ def _keypress(iren, event): ia.GetProperty().SetColor(colors.colors2[(i + vp.icol) % 10]) ia.GetMapper().ScalarVisibilityOff() vp.icol += 1 - vp.addLegend() + addons.addLegend() elif key == "3": c = colors.getColor("gold") @@ -1420,7 +1424,7 @@ def _keypress(iren, event): ia.GetProperty().SetColor(c) ia.GetProperty().SetOpacity(alpha) ia.GetMapper().ScalarVisibilityOff() - vp.addLegend() + addons.addLegend() elif key == "4": bgc = numpy.array(vp.renderer.GetBackground()).sum() / 3 @@ -1449,7 +1453,7 @@ def _keypress(iren, event): else: vp.renderer.RemoveActor(vp.axes_exist[clickedr]) vp.axes_exist[clickedr] = None - vp.addAxes(axtype=asso[key], c=None) + addons.addAxes(axtype=asso[key], c=None) vp.interactor.Render() elif key in ["k", "K"]: @@ -1556,12 +1560,12 @@ def _keypress(iren, event): vp.renderer.AddActor(vp.justremoved) vp.renderer.Render() vp.justremoved = None - vp.addLegend() + addons.addLegend() elif key == "X": if vp.clickedActor: if not vp.cutterWidget: - vp.addCutterTool(vp.clickedActor) + addons.addCutterTool(vp.clickedActor) else: fname = "clipped.vtk" confilter = vtk.vtkPolyDataConnectivityFilter() @@ -1587,9 +1591,9 @@ def _keypress(iren, event): else: for a in vp.actors: if isinstance(a, vtk.vtkVolume): - vp.addCutterTool(a) + addons.addCutterTool(a) return - + colors.printc("Click an actor and press X to open the cutter box widget.", c=4) elif key == "i": # print info