diff --git a/bin/vtkplotter b/bin/vtkplotter index 750995e5..68fec0a0 100755 --- a/bin/vtkplotter +++ b/bin/vtkplotter @@ -281,7 +281,7 @@ def draw_scene(): vp.window.AddObserver("AbortCheckEvent", CheckAbort) # add histogram of scalar - from vtkplotter import histogram + from vtkplotter.analysis import histogram2D dims = img.GetDimensions() nvx = min(100000, dims[0] * dims[1] * dims[2]) @@ -292,7 +292,7 @@ def draw_scene(): d = img.GetScalarComponentAsFloat(ix, iy, iz, 0) data.append(d) - plot = histogram( + plot = histogram2D( data, bins=40, logscale=1, c="gray", bg="gray", pos=(0.78, 0.065) ) plot.GetPosition2Coordinate().SetValue(0.197, 0.20, 0) diff --git a/examples/advanced/fitplanes.py b/examples/advanced/fitplanes.py index b27be4b9..75f6a5dd 100644 --- a/examples/advanced/fitplanes.py +++ b/examples/advanced/fitplanes.py @@ -26,7 +26,7 @@ vp += Arrow(cn, cn + v / 15.0, c="g") variances.append(plane.info["variance"]) -vp += histogram(variances, title="variance", c="g") +vp += histogram(variances, c="g").scale([30,.03,30]).pos(-0.5, -1.2, -.6) vp += Text(__doc__, pos=1) vp.show(viewup="z") diff --git a/examples/advanced/fitspheres1.py b/examples/advanced/fitspheres1.py index f52cdfc6..f1d864a4 100644 --- a/examples/advanced/fitspheres1.py +++ b/examples/advanced/fitspheres1.py @@ -4,8 +4,6 @@ For some of these point we show the fitting sphere. Red lines join the center of the sphere to the surface point. Blue points are the N points used for fitting. -Green histogram is the distribution of residuals from the fitting. -Red histogram is the distribution of the curvatures (1/r**2). Fitted radius can be accessed from actor.info['radius']. """ from __future__ import division, print_function @@ -16,7 +14,6 @@ # load mesh and increase by a lot (N=2) the nr of surface vertices s = vp.load(datadir+"cow.vtk").alpha(0.3).subdivide(N=2) -reds, invr = [], [] for i, p in enumerate(s.getPoints()): if i % 1000: continue # skip most points @@ -27,11 +24,6 @@ vp += sph vp += Points(pts) vp += Line(sph.info["center"], p, lw=2) - reds.append(sph.info["residue"]) - invr.append(1 / sph.info["radius"] ** 2) - -vp += histogram(reds, title="residue", bins=12, c="g", pos=3) -vp += histogram(invr, title="1/r**2", bins=12, c="r", pos=4) vp += Text(__doc__) vp.show(viewup="z") diff --git a/examples/basic/README.md b/examples/basic/README.md index 9994e828..ba8374df 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -41,7 +41,7 @@ python example.py | | | | [![fxy](https://user-images.githubusercontent.com/32848391/50738863-bfccf800-11d8-11e9-882d-7b217aceb55a.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/fxy.py)
`fxy.py` | Draw a surface representing a function _f(x, y)_ defined as a string/formula or as a reference to an external already existing function.
Red points indicate where the function does not exist. | | | | -| [![histo2d](https://user-images.githubusercontent.com/32848391/50738861-bfccf800-11d8-11e9-9698-c0b9dccdba4d.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/histo2D.py)
`histo2D.py` | Make a histogram of two variables with hexagonal binning. | +| [![histo2d](https://user-images.githubusercontent.com/32848391/50738861-bfccf800-11d8-11e9-9698-c0b9dccdba4d.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/histo2D.py)
`histoHexagonal.py` | Make a histogram of two variables with hexagonal binning. | | | | | [![keypress](https://user-images.githubusercontent.com/32848391/50738860-bfccf800-11d8-11e9-96ca-dab2bb7adae3.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/keypress.py)
`keypress.py` | How to implement a custom function that is triggered by pressing a keyboard button when the rendering window is in interactive mode.
In the example, every time a key is pressed the picked point of the mesh is used to add a sphere and some info is printed. | | | | diff --git a/examples/basic/align2.ipynb b/examples/basic/align2.ipynb index 66f7ea29..9b560784 100644 --- a/examples/basic/align2.ipynb +++ b/examples/basic/align2.ipynb @@ -8,12 +8,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c36f73641ab1421fab19091a99c5f175", + "model_id": "4177c602a52e48148251a3635bd676ac", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Plot(antialias=3, axes=['x', 'y', 'z'], background_color=16777215, camera=[2.1504162283002834, 4.9575156559299…" + "Plot(antialias=3, axes=['x', 'y', 'z'], background_color=16777215, camera=[2.238617303212521, 5.12911044410189…" ] }, "metadata": {}, diff --git a/examples/basic/connVtx.ipynb b/examples/basic/connVtx.ipynb index 7ce76776..80692d63 100644 --- a/examples/basic/connVtx.ipynb +++ b/examples/basic/connVtx.ipynb @@ -8,7 +8,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6afe2573e4ed40789d86033940337f3c", + "model_id": "179089faa27c43a3aee25c8405ceb299", "version_major": 2, "version_minor": 0 }, @@ -61,7 +61,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/examples/basic/donutPlot.py b/examples/basic/donutPlot.py new file mode 100644 index 00000000..a89c142e --- /dev/null +++ b/examples/basic/donutPlot.py @@ -0,0 +1,10 @@ +from vtkplotter import donutPlot + +title = "A donut plot" +fractions = [0.1, 0.2, 0.3, 0.1, 0.3] +colors = [ 1, 2, 3, 4, 'white'] +labels = ["stuff1", "stuff2", "compA", "compB", ""] + +dn = donutPlot(fractions, c=colors, labels=labels, title=title) + +dn.show(axes=None, bg='w') diff --git a/examples/basic/fxy.ipynb b/examples/basic/fxy.ipynb index 0b5e9438..57d03632 100644 --- a/examples/basic/fxy.ipynb +++ b/examples/basic/fxy.ipynb @@ -2,18 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1a76cbee3deb4be691a8ef693570473c", + "model_id": "3f3e2d4fa8d146b8ad69c3d498145f2a", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Plot(antialias=3, axes=['x', 'y', 'z'], background_color=16777215, camera=[3.794147738309014, 3.79414773830901…" + "Plot(antialias=3, axes=['x', 'y', 'z'], background_color=16777215, camera=[3.760363867861654, 3.76036386786165…" ] }, "metadata": {}, @@ -21,16 +21,17 @@ } ], "source": [ - "\"\"\"Example for fxy() method.\n", - "Draw a surface representing the 3D function specified as a string\n", - "or as a reference to an external already existing function.\n", - "Red points indicate where the function does not exist.\"\"\"\n", + "\"\"\"\n", + "Draw a surface representing a 2-var function specified \n", + "as a string or as a reference to an external existing function.\n", + "\"\"\"\n", "from vtkplotter import Plotter, fxy, sin, cos\n", "\n", "def my_z(x, y):\n", " return sin(2 * x * y) * cos(3 * y) / 2\n", "\n", - "f1 = fxy(my_z, zlevels=0, showNan=False).c('lightblue')\n", + "f1 = fxy(my_z, zlevels=0).c('lightblue')\n", + "#f1 = fxy(\"sin(2*x*y)*cos(3*y)/2\", zlevels=0).c('lightblue')\n", "\n", "f1.show(viewup='z')" ] @@ -59,7 +60,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/examples/basic/fxy.py b/examples/basic/fxy.py index 968a7ce4..bddaebde 100644 --- a/examples/basic/fxy.py +++ b/examples/basic/fxy.py @@ -1,26 +1,22 @@ """ -Example for function() method. -Draw a surface representing the 3D function specified as a string -or as a reference to an external already existing function. +Draw a surface representing a 2-var function specified +as a string or as a reference to an external existing function. Red points indicate where the function does not exist. """ print(__doc__) -from vtkplotter import Plotter, fxy, sin, cos, show +from vtkplotter import fxy, sin, cos, show def my_z(x, y): return sin(2 * x * y) * cos(3 * y) / 2 - -# draw at renderer nr.0 the first actor, show it with a texture # an existing function z(x,y) can be passed: f1 = fxy(my_z) # red dots are shown where the function does not exist (y>x): -# if vp is set to verbose, sympy calculates derivatives and prints them: f2 = fxy("sin(3*x)*log(x-y)/3") # specify x and y ranges and z vertical limits: -f3 = fxy("log(x**2+y**2 - 1)", x=[-2, 2], y=[-2, 2], zlimits=[-1, 1.5]) +f3 = fxy("log(x**2+y**2-1)", x=[-2,2], y=[-1,8], zlimits=[-1,None]) show(f1, f2, f3, N=3, axes=1, sharecam=False, bg="w") diff --git a/examples/basic/histo2D.py b/examples/basic/histo2D.py deleted file mode 100644 index 183fedb1..00000000 --- a/examples/basic/histo2D.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -2D histogram with hexagonal binning. -""" -from vtkplotter import * -import numpy as np - -vp = Plotter(axes=1, verbose=0, bg="w") -vp.xtitle = "x gaussian, s=1.0" -vp.ytitle = "y gaussian, s=1.5" -vp.ztitle = "dN/dx/dy" - -N = 20000 -x = np.random.randn(N) * 1.0 -y = np.random.randn(N) * 1.5 - -histo = histogram2D(x, y, bins=10, fill=True) - -pts = Points([x, y, np.zeros(N)+6], c="black", alpha=0.01) - -f = r'f(x, y)=A \exp \left(-\left(\frac{\left(x-x_{o}\right)^{2}}' -f+= r'{2 \sigma_{x}^{2}}+\frac{\left(y-y_{o}\right)^{2}}' -f+= r'{2 \sigma_{y}^{2}}\right)\right)' - -formula = Latex(f, c='k', s=1.5).rotateZ(90).rotateX(90).pos(1,-1,1) - -vp.show(histo, pts, formula, Text(__doc__), viewup="z") diff --git a/examples/basic/histoHexagonal.py b/examples/basic/histoHexagonal.py new file mode 100644 index 00000000..1611ef3a --- /dev/null +++ b/examples/basic/histoHexagonal.py @@ -0,0 +1,25 @@ +"""2D histogram with hexagonal binning.""" +from vtkplotter import * +import numpy as np + +N = 2000 +x = np.random.randn(N) * 1.0 +y = np.random.randn(N) * 1.5 + +# hexagonal histogram +histo = hexHistogram(x, y, bins=10, fill=True, cmap='terrain') + +# scatter plot: +pts = Points([x, y, np.zeros(N)+6], c="black", alpha=0.05) + +f = r'f(x, y)=A \exp \left(-\left(\frac{\left(x-x_{o}\right)^{2}}' +f+= r'{2 \sigma_{x}^{2}}+\frac{\left(y-y_{o}\right)^{2}}' +f+= r'{2 \sigma_{y}^{2}}\right)\right)' +formula = Latex(f, c='k', s=1.5).rotateZ(90).rotateX(90).pos(1,-1,1) + +#settings.useParallelProjection = True +settings.xtitle = "x gaussian, s=1.0" +settings.ytitle = "y gaussian, s=1.5" +settings.ztitle = "dN/dx/dy" + +show(histo, pts, formula, Text(__doc__), axes=1, verbose=0, bg="white") diff --git a/examples/basic/histoPolar.py b/examples/basic/histoPolar.py new file mode 100644 index 00000000..6a574a40 --- /dev/null +++ b/examples/basic/histoPolar.py @@ -0,0 +1,41 @@ +from vtkplotter import polarHistogram, Hyperboloid, show +import numpy as np +np.random.seed(3) + + +################################################################## +radhisto = polarHistogram(np.random.rand(200)*6.28, + title="random orientations", + bins=10, + #c='orange', #uniform color + labels=["label"+str(i) for i in range(10)], + ) + +show(radhisto, at=0, N=2, axes=0, sharecam=False, bg="white") + + +################################################################## +hyp = Hyperboloid(res=20).cutWithPlane().rotateY(-90) +hyp.color('grey').alpha(0.3) + +# select 10 random indeces of points on the surface +idx = np.random.randint(0, hyp.NPoints(), size=10) + +radhistos = [] +for i in idx: + #generate a random histogram + rh = polarHistogram(np.random.randn(100), + bins=12, + r1=0.2, # inner radius + phigap=1.0, # leave a space btw phi bars + cmap='viridis_r', + showDisc=False, + showAngles=False, + showErrors=False, + ) + rh.scale(0.15) # scale histogram to make it small + rh.pos(hyp.getPoint(i)) # set its position on the surface + rh.orientation(hyp.normalAt(i)) # orient it along normal + radhistos.append(rh) + +show(hyp, radhistos, at=1, interactive=True) \ No newline at end of file diff --git a/examples/basic/histogram.py b/examples/basic/histogram.py new file mode 100644 index 00000000..4856193b --- /dev/null +++ b/examples/basic/histogram.py @@ -0,0 +1,14 @@ +from vtkplotter import * +import numpy as np + +data1 = np.random.randn(500)*3+10 +data2 = np.random.randn(500)*2+ 7 + +h1 = histogram(data1, fill=True, outline=False, errors=True) +h2 = histogram(data2, fill=False, lc='firebrick', lw=4) +h2.z(0.1) # put h2 in front of h1 + +h1.scale([1, 0.2, 1]) # set a common y-scaling factor +h2.scale([1, 0.2, 1]) + +show(h1, h2, bg='white', axes=1) \ No newline at end of file diff --git a/examples/basic/multiwindows.py b/examples/basic/multiwindows.py index 7a4a2fb8..3de796b7 100644 --- a/examples/basic/multiwindows.py +++ b/examples/basic/multiwindows.py @@ -1,41 +1,50 @@ """ 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 -in specific windows numbers. +We split the main window in many subwindows and draw +somethingon specific windows numbers. Then open an independent window and draw a shape on it. """ print(__doc__) +from vtkplotter import * -from vtkplotter import Plotter, Text, datadir - - +########################################################################## # this is one instance of the class Plotter with 5 raws and 5 columns -vp1 = Plotter(shape=(5, 5), axes=0, bg="white") -# having set shape=(n,m), script execution after the show() is not held +vp1 = Plotter(shape=(5,5), axes=0, bg="white") # set a different background color for a specific subwindow (the last one) vp1.renderers[24].SetBackground(0.8, 0.9, 0.9) # use vtk method SetBackground() # load the actors and give them a name -a = vp1.load(datadir+"airboat.vtk").legend("some legend") +a = vp1.load(datadir+"airboat.vtk") b = vp1.load(datadir+"cessna.vtk", c="red") c = vp1.load(datadir+"atc.ply") # show a text in each renderer 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) + txt = Text("renderer\nnr."+str(i), c='k', font='arial', s=1) + vp1.show(txt, at=i) -vp1.show(a, at=22) +vp1.show(a, at= 6) vp1.show(b, at=23) -vp1.show(c, at=24, interactive=0) +vp1.show(c, at=24) -############################################################ +########################################################################## # declare a second independent instance of the class Plotter -vp2 = Plotter(pos=(500, 250), bg=(0.9, 0.9, 1)) # blue-ish background +# shape can also be given as a string, e.g.: +# shape="2/6" means 2 renderers above and 6 below +# shape="3|1" means 3 renderers on the left and one on the right + +s = load(datadir+'pumpkin.vtk') + +#settings.multiRenderingSplittingPosition = 0.5 + +vp2 = Plotter(pos=(500, 250), shape='2/6') -vp2.load(datadir+"porsche.ply").legend("an other window") +for i in range(len(vp2.renderers)): + s2 = s.clone().color(i) + txt = Text('renderer #'+str(i)) + vp2.show(s2, txt, at=i) -vp2.show() # show and interact with mouse and keyboard +interactive() diff --git a/examples/basic/polarPlot.py b/examples/basic/polarPlot.py new file mode 100644 index 00000000..7690abc0 --- /dev/null +++ b/examples/basic/polarPlot.py @@ -0,0 +1,15 @@ +from vtkplotter import polarPlot, show +import numpy as np + +title = "A (splined) polar plot" +angles = [ 0, 20, 60, 160, 200, 250, 300, 340] +distances = [0.1, 0.2, 0.3, 0.5, 0.6, 0.4, 0.2, 0.1] + +dn1 = polarPlot([angles, distances], deg=True, spline=False, + c='green', alpha=0.5, title=title, vmax=0.6) + +dn2 = polarPlot([angles, np.array(distances)/2], deg=True, + c='tomato', alpha=1, showDisc=False, vmax=0.6) +dn2.z(0.01) # set z above 0, so that plot is visible + +show(dn1, dn2, axes=None, bg='white') diff --git a/examples/notebooks/sphere.ipynb b/examples/notebooks/sphere.ipynb index 01650693..320e4c6d 100644 --- a/examples/notebooks/sphere.ipynb +++ b/examples/notebooks/sphere.ipynb @@ -8,7 +8,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "62ea648105d74f76ac8d8d4bc9f8bb99", + "model_id": "6328d5a8448840f88ee1d5aa771fb292", "version_major": 2, "version_minor": 0 }, diff --git a/examples/notebooks/test_itkwidgets.ipynb b/examples/notebooks/test_itkwidgets.ipynb index 4d6d389c..c2719798 100644 --- a/examples/notebooks/test_itkwidgets.ipynb +++ b/examples/notebooks/test_itkwidgets.ipynb @@ -2,20 +2,107 @@ "cells": [ { "cell_type": "code", - "execution_count": 9, - "metadata": { - "scrolled": true - }, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies for this example\n", + "# Note: This does not include itkwidgets, itself\n", + "import sys\n", + "!{sys.executable} -m pip install vtkplotter" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from vtkplotter import *\n", + "\n", + "embedWindow('itkwidgets')\n", + "\n", + "plt = Plotter()\n", + "\n", + "#cyl = Cylinder()\n", + "#sph = Sphere().pos(1.5,1,1)\n", + "#sph.pointColors(sph.getPoints()[:,2])\n", + "embryo = load(datadir+'embryo.slc')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#plt += sph # test vtkActors\n", + "#plt += cyl\n", + "#plt += cyl+sph # test vtkAssembly\n", + "plt += embryo # test vtkVolume" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m_________________________________________________________________\u001b[0m\n", + "\u001b[7m\u001b[1m\u001b[34mvtkVolume\u001b[0m \n", + "\u001b[1m\u001b[34m position: \u001b[0m\u001b[34m(0.0, 0.0, 0.0)\u001b[0m\n", + "\u001b[1m\u001b[34m dimensions: \u001b[0m\u001b[34m(256, 256, 256)\u001b[0m\n", + "\u001b[1m\u001b[34m spacing: \u001b[0m\u001b[34m(1.0, 1.0, 1.0)\u001b[0m\n", + "\u001b[1m\u001b[34m data dimension: \u001b[0m\u001b[34m3\u001b[0m\n", + "\u001b[1m\u001b[34m memory size: \u001b[0m\u001b[34m16 Mb\u001b[0m\n", + "\u001b[1m\u001b[34m scalar #bytes: \u001b[0m\u001b[34m1\u001b[0m\n", + "\u001b[1m\u001b[34m bounds: \u001b[0m\u001b[34mx=(0, 255)\u001b[0m\u001b[34m y=(0, 255)\u001b[0m\u001b[34m z=(0, 255)\u001b[0m\n", + "\u001b[1m\u001b[34m scalar range: \u001b[0m\u001b[34m(0.0, 197.0)\u001b[0m\n", + "\u001b[34m(logscale) Histogram\t(entries=100000)\n", + " 4.97\n", + "0.00 | ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "20.50 | ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "41.00 | ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "61.50 | ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "82.00 | ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "102.50| ▆▆▆▆▆▆▆▆▆▆▆▆▆▆\n", + "123.00| ▆▆▆▆▆▆▆▆▆▆\n", + "143.50| ▆▆▆▆▆▆\n", + "\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "(Volume)0x7f819f83e768" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "embryo.printInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6eb4c867b65746d6a476565a977866dd", + "model_id": "18be23cdb7594dc4ba945bccd5900b30", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Viewer(cmap='jet', geometries=[{'vtkClass': 'vtkPolyData', 'points': {'vtkClass': 'vtkPoints', 'name': '_point…" + "Viewer(cmap='jet', geometries=[], gradient_opacity=0.01, point_sets=[], rendered_image= binmax: + binmax = ne + + if cmap is not None: + for h in hexs: + z = h.GetBounds()[5] + col = colors.colorMap(z, cmap, 0, binmax) + h.color(col) + + asse = Assembly(hexs) + asse.SetScale(1.2 / n * dx, 1/m*dy, norm/binmax * (dx+dy) / 4) + asse.SetPosition(xmin, ymin, 0) + return asse + + +def polarHistogram(values, + title="", + bins=10, + r1=0.25, + r2=1, + phigap=3, + rgap=0.05, + lpos=1, + lsize=0.05, + c=None, + bc="k", + alpha=1, + cmap=None, + deg=False, + vmin=None, + vmax=None, + labels=(), + showDisc=True, + showLines=True, + showAngles=True, + showErrors=False, + ): + """ + Polar histogram with errorbars. + + :param str title: histogram title + :param int bins: number of bins in phi + :param float r1: inner radius + :param float r2: outer radius + :param float phigap: gap angle btw 2 radial bars, in degrees + :param float rgap: gap factor along radius of numeric angle labels + :param float lpos: label gap factor along radius + :param float lsize: label size + :param c: color of the histogram bars, can be a list of length `bins`. + :param bc: color of the frame and labels + :param alpha: alpha of the frame + :param str cmap: color map name + :param bool deg: input array is in degrees + :param float vmin: minimum value of the radial axis + :param float vmax: maximum value of the radial axis + :param list labels: list of labels, must be of length `bins` + :param bool showDisc: show the outer ring axis + :param bool showLines: show lines to the origin + :param bool showAngles: show angular values + :param bool showErrors: show error bars + + |histoPolar| |histoPolar.py|_ + """ + k = 180 / np.pi + if deg: + values = np.array(values)/k + + dp = np.pi/bins + vals = [] + for v in values: # normalize range + t = np.arctan2(np.sin(v), np.cos(v)) + if t<0: + t += 2 * np.pi + vals.append(t-dp) + + histodata, edges = np.histogram(vals, bins=bins, + range=(-dp,2*np.pi-dp)) + thetas = [] + for i in range(bins): + thetas.append((edges[i] + edges[i+1]) / 2) + + if vmin is None: + vmin = np.min(histodata) + if vmax is None: + vmax = np.max(histodata) + + errors = np.sqrt(histodata) + r2e = r1+r2 + if showErrors: + r2e += np.max(errors)/vmax*1.5 + + back=None + if showDisc: + back = shapes.Disc(r1=r2e, r2=r2e*1.01, c=bc, res=1, resphi=360) + back.z(-0.01).lighting(diffuse=0, ambient=1).alpha(alpha) + + slices = [] + lines = [] + angles = [] + labs = [] + errbars = [] + + for i, t in enumerate(thetas): + r = histodata[i]/vmax*r2 + d = shapes.Disc((0,0,0), r1, r1+r, res=1, resphi=360) + delta = dp - np.pi/2 - phigap/k + d.cutWithPlane(normal=(np.cos(t+delta), np.sin(t+delta), 0)) + d.cutWithPlane(normal=(np.cos(t-delta), np.sin(t-delta), 0)) + if cmap is not None: + cslice = colors.colorMap(histodata[i], cmap, vmin, vmax) + d.color(cslice) + else: + if c is None: + d.color(i) + elif utils.isSequence(c) and len(c)==bins: + d.color(c[i]) + else: + d.color(c) + slices.append(d) + + ct, st = np.cos(t), np.sin(t) + + if showErrors: + showLines = False + err = np.sqrt(histodata[i])/vmax*r2 + errl = shapes.Line(((r1+r-err)*ct, (r1+r-err)*st, 0.01), + ((r1+r+err)*ct, (r1+r+err)*st, 0.01)) + errl.alpha(alpha).lw(3).color(bc) + errbars.append(errl) + + if showDisc: + if showLines: + l = shapes.Line((0, 0, -0.01), (r2e*ct*1.03, r2e*st*1.03, -0.01)) + lines.append(l) + elif showAngles: # just the ticks + l = shapes.Line((r2e*ct*0.98, r2e*st*0.98, -0.01), + (r2e*ct*1.03, r2e*st*1.03, -0.01)) + lines.append(l) + + if showAngles: + if 0 <= t < np.pi / 2: + ju = "bottom-left" + elif t == np.pi / 2: + ju = "bottom-center" + elif np.pi / 2 < t <= np.pi: + ju = "bottom-right" + elif np.pi < t < np.pi * 3 / 2: + ju = "top-right" + elif t == np.pi * 3 / 2: + ju = "top-center" + else: + ju = "top-left" + a = shapes.Text(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju) + a.pos(r2e * ct * (1+rgap), r2e * st * (1+rgap), -0.01) + angles.append(a) + + if len(labels)==bins: + lab = shapes.Text(labels[i], (0,0,0), s=lsize, depth=0, justify="center") + lab.pos(r2e * ct * (1 + rgap) * lpos / 2, + r2e * st * (1 + rgap) * lpos / 2, 0.01) + labs.append(lab) + + ti = None + if title: + ti = shapes.Text(title, (0,0,0), s=lsize*2, depth=0, justify="top-center") + ti.pos(0, -r2e*1.15, 0.01) + + mrg = merge(back, lines, angles, labs, ti) + if mrg: + mrg.color(bc).alpha(alpha).lighting(diffuse=0, ambient=1) + rh = Assembly(slices + errbars + [mrg]) + rh.base = np.array([0,0,0]) + rh.top = np.array([0,0,1]) + return rh + +def donutPlot(fractions, + title="", + r1=1.7, + r2=1, + phigap=0, + lpos=0.8, + lsize=0.15, + c=None, + bc="k", + alpha=1, + labels=(), + showDisc=False, +): + """ + Donut plot or pie chart. + + :param str title: plot title + :param float r1: inner radius + :param float r2: outer radius, starting from r1 + :param float phigap: gap angle btw 2 radial bars, in degrees + :param float lpos: label gap factor along radius + :param float lsize: label size + :param c: color of the plot slices + :param bc: color of the disc frame + :param alpha: alpha of the disc frame + :param list labels: list of labels + :param bool showDisc: show the outer ring axis + + |donutPlot| |donutPlot.py|_ + """ + fractions = np.array(fractions) + angles = np.add.accumulate(2*np.pi*fractions) + angles[-1] = 2*np.pi + if angles[-2]>2*np.pi: + print("Error in donutPlot(): fractions must sum to 1.") + raise RuntimeError + + cols = [] + for i,th in enumerate(np.linspace(0,2*np.pi, 360, endpoint=False)): + for ia,a in enumerate(angles): + if th=0: + zm = 0.0 + else: + zm = (bb[4] + bb[5]) / 2 nans = np.array(nans) + [0, 0, zm] - nansact = shapes.Points(nans, c="red", alpha=alpha / 2) + nansact = shapes.Points(nans, r=2, c="red", alpha=alpha) + nansact.GetProperty().RenderPointsAsSpheresOff() acts.append(nansact) if len(acts) > 1: - asse = Assembly(acts) - return asse + return Assembly(acts) else: return actor -def histogram2D(xvalues, yvalues, bins=12, norm=1, fill=True, c=None, alpha=1): - """ - Build a 2D hexagonal histogram from a list of x and y values. - - :param bool bins: nr of bins for the smaller range in x or y. - :param float norm: sets a scaling factor for the z axis. - :param bool fill: draw solid hexagons. - - |histo2D| |histo2D.py|_ - """ - xmin, xmax = np.min(xvalues), np.max(xvalues) - ymin, ymax = np.min(yvalues), np.max(yvalues) - dx, dy = xmax - xmin, ymax - ymin - - if xmax - xmin < ymax - ymin: - n = bins - m = np.rint(dy / dx * n / 1.2 + 0.5).astype(int) - else: - m = bins - n = np.rint(dx / dy * m * 1.2 + 0.5).astype(int) - - src = vtk.vtkPointSource() - src.SetNumberOfPoints(len(xvalues)) - src.Update() - pointsPolydata = src.GetOutput() - - values = list(zip(xvalues, yvalues)) - zs = [[0.0]] * len(values) - values = np.append(values, zs, axis=1) - - pointsPolydata.GetPoints().SetData(numpy_to_vtk(values, deep=True)) - cloud = Actor(pointsPolydata) - - col = None - if c is not None: - col = colors.getColor(c) - - r = 0.47 / n * 1.2 * dx - - hexs, binmax = [], 0 - for i in range(n + 3): - for j in range(m + 2): - cyl = vtk.vtkCylinderSource() - cyl.SetResolution(6) - cyl.CappingOn() - cyl.SetRadius(0.5) - cyl.SetHeight(0.1) - cyl.Update() - t = vtk.vtkTransform() - if not i % 2: - p = (i / 1.33, j / 1.12, 0) - else: - p = (i / 1.33, j / 1.12 + 0.443, 0) - q = (p[0] / n * 1.2 * dx + xmin, p[1] / m * dy + ymin, 0) - ids = cloud.closestPoint(q, radius=r, returnIds=True) - ne = len(ids) - if fill: - t.Translate(p[0], p[1], ne / 2) - t.Scale(1, 1, ne * 10) - else: - t.Translate(p[0], p[1], ne) - t.RotateX(90) # put it along Z - tf = vtk.vtkTransformPolyDataFilter() - tf.SetInputData(cyl.GetOutput()) - tf.SetTransform(t) - tf.Update() - if c is None: - col=i - h = Actor(tf.GetOutput(), c=col, alpha=alpha) - h.flat() - h.GetProperty().SetSpecular(0) - h.GetProperty().SetDiffuse(1) - h.PickableOff() - hexs.append(h) - if ne > binmax: - binmax = ne - - asse = Assembly(hexs) - asse.SetScale(1 / n * 1.2 * dx, 1 / m * dy, norm / binmax * (dx + dy) / 4) - asse.SetPosition(xmin, ymin, 0) - return asse - - def delaunay2D(plist, mode='xy', tol=None): """ Create a mesh from points in the XY plane. @@ -831,7 +1275,7 @@ def smoothMLS1D(actor, f=0.2, radius=None, showNLines=0): newline.append(newp) if showNLines and not i % ndiv: - fline = fitLine(points, lw=4) # fitting plane + fline = fitLine(points) # fitting plane iapts = shapes.Points(points) # blue points acts += [fline, iapts] diff --git a/vtkplotter/animation.py b/vtkplotter/animation.py index 4cf98f64..2cf23afc 100644 --- a/vtkplotter/animation.py +++ b/vtkplotter/animation.py @@ -3,19 +3,23 @@ import vtkplotter.utils as utils from vtkplotter.utils import ProgressBar, linInterpolate from vtkplotter.colors import printc, getColor +import vtkplotter.docs as docs from vtkplotter.vtkio import Video from vtkplotter.plotter import Plotter __doc__ = ( """ -Animation module which allows to animate simultaneously various objects +Submodule which allows to animate simultaneously various objects by specifying event times and durations of different visual effects. See examples `here `_. +|animation1| |animation2| + N.B.: this is still an experimental feature at the moment. """ + + docs._defs ) __all__ = ['Animation'] @@ -350,7 +354,7 @@ def scale(self, acts=None, factor=1, t=None, duration=None): def meshErode(self, act=None, corner=6, t=None, duration=None): - """Erode a mesh by removing cells that are close to one of the 6 corners + """Erode a mesh by removing cells that are close to one of the 8 corners of the bounding box. """ if self.bookingMode: diff --git a/vtkplotter/backends.py b/vtkplotter/backends.py index b4e0557f..79535cbe 100644 --- a/vtkplotter/backends.py +++ b/vtkplotter/backends.py @@ -23,8 +23,12 @@ def getNotebookBackend(actors2show, zoom, viewup): if 'itk' in settings.notebookBackend: from itkwidgets import view - if vp.shape[0] != 1 or vp.shape[1] != 1: + if sum(vp.shape) != 2: colors.printc("Warning: multirendering is not supported in jupyter.", c=1) + +# settings.notebook_plotter = view(actors=actors2show, +# cmap='jet', ui_collapsed=True, +# gradient_opacity=False) polys2show = [] points2show = [] diff --git a/vtkplotter/data/box_with_dent.xml.gz b/vtkplotter/data/box_with_dent.xml.gz new file mode 100644 index 00000000..3b2011d4 Binary files /dev/null and b/vtkplotter/data/box_with_dent.xml.gz differ diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py index 4d68bfc1..73390b17 100644 --- a/vtkplotter/docs.py +++ b/vtkplotter/docs.py @@ -355,11 +355,11 @@ def tips(): :target: fxy.py_ :alt: fxy.py -.. |histo2D.py| replace:: histo2D.py -.. _histo2D.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/histo2D.py -.. |histo2D| image:: https://user-images.githubusercontent.com/32848391/50738861-bfccf800-11d8-11e9-9698-c0b9dccdba4d.jpg +.. |histoHexagonal.py| replace:: histoHexagonal.py +.. _histoHexagonal.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/histoHexagonal.py +.. |histoHexagonal| image:: https://user-images.githubusercontent.com/32848391/50738861-bfccf800-11d8-11e9-9698-c0b9dccdba4d.jpg :width: 250 px - :alt: histo2D.py + :alt: histoHexagonal.py .. |align3.py| replace:: align3.py .. _align3.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/align3.py @@ -1130,5 +1130,39 @@ def tips(): .. |wikiphong| image:: https://upload.wikimedia.org/wikipedia/commons/6/6b/Phong_components_version_4.png +.. |animation1.py| replace:: animation1.py +.. _animation1.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/animation1.py +.. |animation1| image:: https://user-images.githubusercontent.com/32848391/64273764-4b528080-cf42-11e9-90aa-2d88df239871.gif + :width: 300 px + :target: animation1.py_ + :alt: animation1.py + +.. |animation2.py| replace:: animation2.py +.. _animation2.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/animation2.py +.. |animation2| image:: https://user-images.githubusercontent.com/32848391/64273191-1a258080-cf41-11e9-8a18-f192f05f11a9.gif + :width: 300 px + :target: animation2.py_ + :alt: animation2.py + +.. |histoPolar.py| replace:: histoPolar.py +.. _histoPolar.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/histoPolar.py +.. |histoPolar| image:: https://user-images.githubusercontent.com/32848391/64912717-5754f400-d733-11e9-8a1f-612165955f23.png + :width: 250 px + :target: histoPolar.py_ + :alt: histoPolar.py + +.. |polarPlot.py| replace:: polarPlot.py +.. _polarPlot.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/polarPlot.py +.. |polarPlot| image:: https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png + :width: 250 px + :target: polarPlot.py_ + :alt: polarPlot.py + +.. |donutPlot.py| replace:: donutPlot.py +.. _donutPlot.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/donutPlot.py +.. |donutPlot| image:: https://user-images.githubusercontent.com/32848391/64998178-6f6b7580-d8e3-11e9-9bd8-8dfb9ccd90e4.png + :width: 250 px + :target: donutPlot.py_ + :alt: donutPlot.py """ diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py index ecff12fa..9cfb4ac8 100644 --- a/vtkplotter/dolfin.py +++ b/vtkplotter/dolfin.py @@ -622,14 +622,16 @@ def plot(*inputobj, **options): arrs = MeshArrows(u, scale=scale) else: arrs = MeshLines(u, scale=scale) - if legend and not 'mesh' in mode: - arrs.legend(legend) - if c: - arrs.color(c) - arrs.color(c) - if alpha: - arrs.alpha(alpha) - actors.append(arrs) + + if arrs: + if legend and not 'mesh' in mode: + arrs.legend(legend) + if c: + arrs.color(c) + arrs.color(c) + if alpha: + arrs.alpha(alpha) + actors.append(arrs) ################################################################# diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py index 6501ed5a..de5aa655 100644 --- a/vtkplotter/plotter.py +++ b/vtkplotter/plotter.py @@ -438,7 +438,6 @@ def __init__( self.clickedActor = None # holds the actor that has been clicked self.renderer = None # current renderer self.renderers = [] # list of renderers - self.shape = shape self.pos = pos self.interactive = interactive # allows to interact with renderer self.axes = axes # show axes type nr. @@ -535,67 +534,115 @@ def __init__( minl = l shape = lm[ind] - if size == "auto": # figure out a reasonable window size - f = 1.5 - xs = y / f * shape[1] # because y x / f: # shrink - xs = x / f - ys = xs / shape[1] * shape[0] - if ys > y / f: - ys = y / f - xs = ys / shape[0] * shape[1] - self.size = (int(xs), int(ys)) - if shape == (1, 1): - self.size = (int(y / f), int(y / f)) # because y 3: - self.legendSize *= 2 - - image_actor=None - bgname = str(self.backgrcol).lower() - if ".jpg" in bgname or ".jpeg" in bgname or ".png" in bgname: - self.window.SetNumberOfLayers(2) - self.backgroundRenderer = vtk.vtkRenderer() - self.backgroundRenderer.SetLayer(0) - self.backgroundRenderer.InteractiveOff() - self.backgroundRenderer.SetBackground(colors.getColor(bg2)) - image_actor = Picture(self.backgrcol) - self.window.AddRenderer(self.backgroundRenderer) - self.backgroundRenderer.AddActor(image_actor) - - for i in reversed(range(shape[0])): - for j in range(shape[1]): - if settings.useOpenVR: - arenderer = vtk.vtkOpenVRRenderer() + if isinstance(shape, str): + + if size == "auto": + if '|' in shape: + self.size = (800, 1200) + n = int(shape.split('|')[0]) + m = int(shape.split('|')[1]) + rangen = reversed(range(n)) + rangem = reversed(range(m)) else: - arenderer = vtk.vtkRenderer() - arenderer.SetUseHiddenLineRemoval(settings.hiddenLineRemoval) - arenderer.SetLightFollowCamera(settings.lightFollowsCamera) - arenderer.SetUseFXAA(settings.useFXAA) - arenderer.SetUseDepthPeeling(settings.useDepthPeeling) - - if image_actor: - arenderer.SetLayer(1) - + self.size = (1200, 800) + m = int(shape.split('/')[0]) + n = int(shape.split('/')[1]) + rangen = range(n) + rangem = range(m) + + if n>=m: + xsplit = m/(n+m) + else: + xsplit = 1-n/(n+m) + if settings.multiRenderingSplittingPosition: + xsplit = settings.multiRenderingSplittingPosition + + for i in rangen: + arenderer = vtk.vtkRenderer() + arenderer.SetUseHiddenLineRemoval(settings.hiddenLineRemoval) + arenderer.SetLightFollowCamera(settings.lightFollowsCamera) + arenderer.SetUseFXAA(settings.useFXAA) + arenderer.SetUseDepthPeeling(settings.useDepthPeeling) arenderer.SetBackground(colors.getColor(self.backgrcol)) - if bg2: - arenderer.GradientBackgroundOn() - arenderer.SetBackground2(colors.getColor(bg2)) - - x0 = i / shape[0] - y0 = j / shape[1] - x1 = (i + 1) / shape[0] - y1 = (j + 1) / shape[1] - arenderer.SetViewport(y0, x0, y1, x1) + if '|' in shape: arenderer.SetViewport(0, i/n, xsplit, (i+1)/n) + if '/' in shape: arenderer.SetViewport(i/n, 0, (i+1)/n, xsplit ) self.renderers.append(arenderer) self.axes_instances.append(None) + for i in rangem: + arenderer = vtk.vtkRenderer() + arenderer.SetUseHiddenLineRemoval(settings.hiddenLineRemoval) + arenderer.SetLightFollowCamera(settings.lightFollowsCamera) + arenderer.SetUseFXAA(settings.useFXAA) + arenderer.SetUseDepthPeeling(settings.useDepthPeeling) + arenderer.SetBackground(colors.getColor(self.backgrcol)) + if '|' in shape: arenderer.SetViewport(xsplit, i/m, 1, (i+1)/m) + if '/' in shape: arenderer.SetViewport(i/m, xsplit, (i+1)/m, 1) + self.renderers.append(arenderer) + self.axes_instances.append(None) + + else: + + if size == "auto": # figure out a reasonable window size + f = 1.5 + xs = y / f * shape[1] # because y x / f: # shrink + xs = x / f + ys = xs / shape[1] * shape[0] + if ys > y / f: + ys = y / f + xs = ys / shape[0] * shape[1] + self.size = (int(xs), int(ys)) + if shape == (1, 1): + self.size = (int(y / f), int(y / f)) # because y 3: + self.legendSize *= 2 + + image_actor=None + bgname = str(self.backgrcol).lower() + if ".jpg" in bgname or ".jpeg" in bgname or ".png" in bgname: + self.window.SetNumberOfLayers(2) + self.backgroundRenderer = vtk.vtkRenderer() + self.backgroundRenderer.SetLayer(0) + self.backgroundRenderer.InteractiveOff() + self.backgroundRenderer.SetBackground(colors.getColor(bg2)) + image_actor = Picture(self.backgrcol) + self.window.AddRenderer(self.backgroundRenderer) + self.backgroundRenderer.AddActor(image_actor) + + for i in reversed(range(shape[0])): + for j in range(shape[1]): + if settings.useOpenVR: + arenderer = vtk.vtkOpenVRRenderer() + else: + arenderer = vtk.vtkRenderer() + arenderer.SetUseHiddenLineRemoval(settings.hiddenLineRemoval) + arenderer.SetLightFollowCamera(settings.lightFollowsCamera) + arenderer.SetUseFXAA(settings.useFXAA) + arenderer.SetUseDepthPeeling(settings.useDepthPeeling) + + if image_actor: + arenderer.SetLayer(1) + + arenderer.SetBackground(colors.getColor(self.backgrcol)) + if bg2: + arenderer.GradientBackgroundOn() + arenderer.SetBackground2(colors.getColor(bg2)) + + x0 = i / shape[0] + y0 = j / shape[1] + x1 = (i + 1) / shape[0] + y1 = (j + 1) / shape[1] + arenderer.SetViewport(y0, x0, y1, x1) + self.renderers.append(arenderer) + self.axes_instances.append(None) if "full" in size and not offscreen: # full screen self.window.SetFullScreen(True) diff --git a/vtkplotter/settings.py b/vtkplotter/settings.py index e8da19ae..6eec45ab 100644 --- a/vtkplotter/settings.py +++ b/vtkplotter/settings.py @@ -25,6 +25,9 @@ showRendererFrame = True rendererFrameColor = None + # In multirendering mode set the position of the horizontal of vertical splitting [0,1] + multiRenderingSplittingPosition = None + # Use tex, matplotlib latex compiler usetex = False @@ -62,6 +65,7 @@ voro_path = '/usr/local/bin' + Usage example: .. code-block:: python @@ -132,6 +136,9 @@ # Set parallel projection On or Off (place camera to infinity, no perspective effects) useParallelProjection = False +# In multirendering mode set the position of the horizontal of vertical splitting [0,1] +multiRenderingSplittingPosition = None + # Path to Voro++ library, http://math.lbl.gov/voro++ voro_path = '/usr/local/bin' diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py index 5825f58b..b0169d99 100644 --- a/vtkplotter/shapes.py +++ b/vtkplotter/shapes.py @@ -19,6 +19,7 @@ "Point", "Points", "Line", + "DashedLine", "Tube", "Lines", "Spline", @@ -412,6 +413,76 @@ def Line(p0, p1=None, c="r", alpha=1, lw=1, dotted=False, res=None): return actor +def DashedLine(p0, p1=None, spacing=None, c="red", alpha=1, lw=1): + """ + Build a dashed line segment between points `p0` and `p1`. + If `p0` is a list of points returns the line connecting them. + A 2D set of coords can also be passed as p0=[x..], p1=[y..]. + + :param float spacing: physical size of the dash. + :param c: color name, number, or list of [R,G,B] colors. + :type c: int, str, list + :param float alpha: transparency in range [0,1]. + :param lw: line width. + """ + # detect if user is passing a 2D ist of points as p0=xlist, p1=ylist: + if len(p0) > 3: + if not utils.isSequence(p0[0]) and not utils.isSequence(p1[0]) and len(p0)==len(p1): + # assume input is 2D xlist, ylist + p0 = list(zip(p0, p1)) + p1 = None + + # detect if user is passing a list of points: + if utils.isSequence(p0[0]): + listp = p0 + else: # or just 2 points to link + listp = [p0, p1] + + if not spacing: + spacing = np.linalg.norm(np.array(listp[1]) - listp[0])/50 + + polylns = vtk.vtkAppendPolyData() + for ipt in range(1, len(listp)): + p0 = np.array(listp[ipt-1]) + p1 = np.array(listp[ipt]) + v = p1-p0 + n1 = int(np.linalg.norm(v)/spacing) + + for i in range(1, n1+2): + if (i-1)/n1>1: + continue + + if i%2: + q0 = p0 + (i-1)/n1*v + if i/n1>1: + q1 = p1 + else: + q1 = p0 + i/n1*v + lineSource = vtk.vtkLineSource() + lineSource.SetPoint1(q0) + lineSource.SetPoint2(q1) + lineSource.Update() + polylns.AddInputData(lineSource.GetOutput()) +# elif subspacing: +# q2 = p0 + (i+1)/n1*p1 +# w1 = (q2-q1)*(1-spacing2/spacing1)/2 +# w2 = (q2-q1)*(1+spacing2/spacing1)/2 +# lineSource = vtk.vtkLineSource() +# lineSource.SetPoint1(w1) +# lineSource.SetPoint2(w2) +# lineSource.Update() +# polylns.AddInputData(lineSource.GetOutput()) + + polylns.Update() + poly = polylns.GetOutput() + + actor = Actor(poly, c, alpha).lw(lw) + actor.base = np.array(p0) + actor.top = np.array(p1) + settings.collectable_actors.append(actor) + return actor + + def Lines(startPoints, endPoints=None, c=None, alpha=1, lw=1, dotted=False, scale=1): """ Build the line segments between two lists of points `startPoints` and `endPoints`. diff --git a/vtkplotter/version.py b/vtkplotter/version.py index 2eec4196..2f3d333b 100644 --- a/vtkplotter/version.py +++ b/vtkplotter/version.py @@ -1 +1 @@ -_version='2019.4.4' +_version='2019.4.5'