diff --git a/README.md b/README.md
index 1517e222..e1a1bae7 100644
--- a/README.md
+++ b/README.md
@@ -39,14 +39,14 @@ pip install -U vtkplotter
*Windows-10 users* can place this file
[vtkplotter.bat](https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter.bat)
on the desktop to *drag&drop* files to visualize.
-(Need to edit the path of the local Anaconda installation).
+(Need to edit the path of the local python installation).
## 📙 Documentation
Automatically generated documentation can be found [**here**](https://vtkplotter.embl.es).
-#### 📌 Need help?
+##### 📌 Need help?
Have any question, or wish to suggest or ask for a missing feature?
Do not hesitate to open a [**issue**](https://github.com/marcomusy/vtkplotter-examples/issues)
or send an [email](mailto:marco.musy@embl.es).
@@ -56,12 +56,13 @@ or send an [email](mailto:marco.musy@embl.es).
Intuitive and straightforward API which can be combined with VTK seamlessly
in a program, whilst mantaining access to the full range of VTK native classes.
-It includes a [**large set of working examples**](https://github.com/marcomusy/vtkplotter-examples/tree/master/vtkplotter_examples)
-for all the following functionalities:
-
- - Import meshes from VTK format, STL, Wavefront OBJ, 3DS, Dolfin-XML, Neutral, GMSH, OFF, PCD (PointCloud), volumetric TIFF stacks, DICOM, SLC, MHD, 2D images PNG, JPEG.
- - Export meshes as ASCII or binary to VTK, STL, OBJ, PLY formats.
- - Mesh analysis through the built-in methods of VTK. Additional analysis tools like *Moving Least Squares*, mesh morphing and more..
+It includes a **[large set of working examples](https://github.com/marcomusy/vtkplotter-examples/tree/master/vtkplotter_examples)**
+for a wide range of functionalities *(click triangle to expand...)*:
+
+*working with polygonal meshes and point clouds*
+ - Import meshes from VTK format, STL, Wavefront OBJ, 3DS, Dolfin-XML, Neutral, GMSH, OFF, PCD (PointCloud),
+ - Export meshes as ASCII or binary to VTK, STL, OBJ, PLY ... formats.
+ - Analysis tools like *Moving Least Squares*, mesh morphing and more..
- Tools to visualize and edit meshes (cutting a mesh with another mesh, slicing, normalizing, moving vertex positions, etc..).
- Split mesh based on surface connectivity. Extract the largest connected area.
- Calculate areas, volumes, center of mass, average sizes etc.
@@ -79,32 +80,53 @@ for all the following functionalities:
- Generate meshes by joining nearby lines in space.
- Find the closest path from one point to another, travelling along the edges of a mesh.
- Find the intersection of a mesh with lines, planes or other meshes.
+ - Interpolate scalar and vectorial fields with *Radial Basis Functions* and *Thin Plate Splines*.
+ - Add sliders and buttons to interact with the scene and the individual objects.
+ - Visualization of tensors.
- Analysis of *Point Clouds*:
- *Moving Least Squares* smoothing of 2D, 3D and 4D clouds
- Fit lines, planes, spheres and ellipses in space
- Identify outliers in a distribution of points
- Decimate a cloud to a uniform distribution.
- - Histogramming and function plotting in 1D and 2D.
- - Interpolate scalar and vectorial fields with *Radial Basis Functions* and *Thin Plate Splines*.
- - Analysis of volumetric datasets:
- - Isosurfacing of volumes
- - Composite and maximum projection volumetric rendering
- - Generate volumetric signed-distance data from an input surface mesh
- - Probe a volume with lines and planes
- - Generate stream-lines and stream-tubes from vectorial fields
- - Add sliders and buttons to interact with the scene and the individual objects.
- - Fully customizable axis style.
- - Visualization of tensors.
- - Draw `latex`-formatted formulas in the rending window.
- - Integration with the *Qt5* framework.
- - Examples using [SHTools](https://shtools.oca.eu/shtools) package for *spherical harmonics* expansion of a mesh shape.
- - Support for [FEniCS/Dolfin](https://fenicsproject.org/) platform for visualization of finite-element calculations.
- - Interoperability with the [trimesh](https://trimsh.org/) library.
- - Export a 3D scene and embed it into a [web page](https://vtkplotter.embl.es/examples/fenics_elasticity.html).
- - Embed the 3D rendering in a *jupyter* notebook with [K3D](https://github.com/K3D-tools/K3D-jupyter) (can export an interactive 3D-snapshot page [here](https://vtkplotter.embl.es/examples/K3D_snapshot.html)).
-
-
-### ➜ Command Line Interface
+
+
+*working with volumetric data*
+ - Import data from VTK format volumetric TIFF stacks, DICOM, SLC, MHD and more
+ - Import 2D images as PNG, JPEG, BMP
+ - Isosurfacing of volumes
+ - Composite and maximum projection volumetric rendering
+ - Generate volumetric signed-distance data from an input surface mesh
+ - Probe a volume with lines and planes
+ - Generate stream-lines and stream-tubes from vectorial fields
+ - Slice and crop volumes
+ - Support for other volumetric structures (structured and grid data)
+
+
+*plotting and histogramming*
+ - Fully customizable axis styles
+ - 'donut' plots and pie charts
+ - Scatter plots in 2D and 3D
+ - Surface function plotting
+ - 1D customizable histograms
+ - 2D hexagonal histograms
+ - Polar plots and histogramming
+ - Draw `latex`-formatted formulas in the rendering window.
+ - Quiver plots
+ - Point markers analogous to `matplotlib`
+
+
+
+
+Moreover:
+- Integration with the *Qt5* framework.
+- Examples using [SHTools](https://shtools.oca.eu/shtools) package for *spherical harmonics* expansion of a mesh shape.
+- Support for [FEniCS/Dolfin](https://fenicsproject.org/) platform for visualization of finite-element calculations.
+- Interoperability with the [trimesh](https://trimsh.org/) library.
+- Export a 3D scene and embed it into a [web page](https://vtkplotter.embl.es/examples/fenics_elasticity.html).
+- Embed the 3D rendering in a *jupyter* notebook with [K3D](https://github.com/K3D-tools/K3D-jupyter) (can export an interactive 3D-snapshot page [here](https://vtkplotter.embl.es/examples/K3D_snapshot.html)).
+
+
+## ⌨ Command Line Interface
Visualize a mesh from a terminal window with:
```bash
vtkplotter mesh.obj
@@ -112,7 +134,7 @@ vtkplotter mesh.obj
# pcd,xyz,txt,byu,tif,off,slc,vti,mhd,dcm,dem,nrrd,nii,bmp,png,jpg]
```
Voxel-data (_mhd, vti, slc, tiff, dicom etc.._) files can be visualized with options `-g`. E.g.:
-`vtkplotter -g examples/data/embryo.slc`
+`vtkplotter -g embryo.slc`
![isohead](https://user-images.githubusercontent.com/32848391/58336107-5a09a180-7e43-11e9-8c4e-b50e4e95ae71.gif)
@@ -123,7 +145,7 @@ To visualize multiple files or files time-sequences try `-n` or `-s` options. Us
|`vtkplotter head.vti` |`vtkplotter -s *.vtk` |`vtkplotter `
`--slicer embr.slc` | `vtkplotter --lego embryo.slc`|
|![isohead](https://user-images.githubusercontent.com/32848391/56972083-a7f3f800-6b6a-11e9-9cb3-1047b69dcad2.gif)| ![viz_raycast](https://user-images.githubusercontent.com/32848391/58336919-f7b1a080-7e44-11e9-9106-f574371093a8.gif) | ![viz_slicer](https://user-images.githubusercontent.com/32848391/56972084-a7f3f800-6b6a-11e9-98c4-dc4ffec70a5e.png) |![lego](https://user-images.githubusercontent.com/32848391/56969949-71b47980-6b66-11e9-8251-4bbdb275cb22.jpg) |
-### ➜ Graphic User Interface
+### 🖥 Graphic User Interface
A Graphic User Interface is available (mainly useful to *Windows 10* users):
![gui](https://user-images.githubusercontent.com/32848391/63259840-c861d280-c27f-11e9-9c2a-99d0fae85313.png)
@@ -135,7 +157,7 @@ pip install -U git+https://github.com/marcomusy/vtkplotter-examples
vtkplotter --list
vtkplotter --run tube.py
```
-**More than 280 working examples can be found in directories** _(scroll down to see thumbnails):_
+**More than 300 working examples can be found in directories** _(scroll down to see thumbnails):_
[**examples/basic**](https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/basic)
[**examples/advanced**](https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/advanced)
[**examples/volumetric**](https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/volumetric)
diff --git a/bin/vtkplotter b/bin/vtkplotter
index d82e3d7e..35468d66 100755
--- a/bin/vtkplotter
+++ b/bin/vtkplotter
@@ -1044,6 +1044,22 @@ else: #########################################################################
scs.append(lb+bn.replace('.py',''))
printc("".join(scs), c='y', bold=0)
+ printc("Simulation examples:", c='m', bold=1, underline=1)
+ scs = []
+ for f,bn in exfiles:
+ if "simulation" in f:
+ lb = ' ' if (len(scs)+1)%nl else '\n'
+ scs.append(lb+bn.replace('.py',''))
+ printc("".join(scs), c='m', bold=0)
+
+ printc("Plotting 2D examples:", c='w', bold=1, underline=1)
+ scs = []
+ for f,bn in exfiles:
+ if "plotting2d" in f:
+ lb = ' ' if (len(scs)+1)%nl else '\n'
+ scs.append(lb+bn.replace('.py',''))
+ printc("".join(scs), c='w', bold=0)
+
printc("Volumetric examples:", c='b', bold=1, underline=1)
scs = []
for f,bn in exfiles:
diff --git a/docs/README.rst b/docs/README.rst
index b732c9ac..af1bd038 100644
--- a/docs/README.rst
+++ b/docs/README.rst
@@ -20,13 +20,13 @@
.. image:: https://img.shields.io/badge/docs%20by-gendocs-blue.svg
:target: https://gendocs.readthedocs.io/en/latest/
:alt: Documentation Built by gendocs
-
+
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.2561402.svg
:target: https://doi.org/10.5281/zenodo.2561402
-
+
---------------------
-A lightweight python module for scientific visualization, analysis and animation of 3D objects
+A lightweight python module for scientific visualization, analysis and animation of 3D objects
and `point clouds` based on `VTK `_
and `numpy `_.
@@ -52,18 +52,18 @@ Check out the **Git repository** here: https://github.com/marcomusy/vtkplotter-e
*Windows-10 users* can manually place this file
`vtkplotter.bat `_
-on the desktop to *drag&drop* files to visualize.
+on the desktop to *drag&drop* files to visualize.
(Need to edit the path of their local Anaconda installation).
Features:
---------
-Intuitive and straightforward API which can be combined with VTK seamlessly
+Intuitive and straightforward API which can be combined with VTK seamlessly
in a program, whilst mantaining access to the full range of VTK native classes.
-It includes a
-`large set of working examples `_
+It includes a
+`large set of working examples `_
for the all following functionalities:
- Import meshes from VTK format, STL, Wavefront OBJ, 3DS, XML, Neutral, GMSH, PCD (PointCloud), volumetric TIFF stacks, SLC, MHD, 2D images PNG, JPEG.
@@ -76,7 +76,7 @@ for the all following functionalities:
- Subdivide faces of a mesh, increasing the number of vertex points. Mesh simplification.
- Coloring and thresholding of meshes based on associated scalar or vectorial data.
- Point-surface operations: find nearest points, check if a point lies inside or outside a mesh.
-- Create primitive objects like: spheres, arrows, cubes, torus, ellipsoids...
+- Create primitive objects like: spheres, arrows, cubes, torus, ellipsoids...
- Generate *glyphs* (associating a mesh to each vertex of a source mesh).
- Create animations easily by just defining the position of the displayed objects in the 3D scene. Add trailing lines to moving objects automatically.
- Straightforward support for multiple `sync-ed` or independent renderers in the same window.
@@ -87,7 +87,7 @@ for the all following functionalities:
- Find the closest path from one point to another, travelling along the edges of a mesh.
- Find the intersection of a mesh with a line (or with another mesh).
- Analysis of `Point Clouds`:
-
+
- `Moving Least Squares` smoothing of 2D, 3D and 4D clouds
- Fit lines, planes and spheres in space
- Perform PCA (Principal Component Analysis) on point coordinates
@@ -122,14 +122,14 @@ In your python script, load a simple ``3DS`` file and display it:
.. code-block:: python
from vtkplotter import show
-
- show('flamingo.3ds')
+
+ show('flamingo.3ds')
.. image:: https://user-images.githubusercontent.com/32848391/50738813-58af4380-11d8-11e9-84ce-53579c1dba65.png
:alt: flam
Allowed input objects to the ``show()`` command are: \ :raw-html-m2r:`
`
-``filename``, ``vtkPolyData``, ``vtkActor``,
+``filename``, ``vtkPolyData``, ``vtkActor``,
``vtkActor2D``, ``vtkImageActor``, ``vtkAssembly`` or ``vtkVolume``.
@@ -167,7 +167,7 @@ Load and browse a sequence of meshes:
.. code-block:: bash
- vtkplotter -s examples/data/timecourse1d/*.vtk
+ vtkplotter -s examples/data/timecourse1d/*.vtk
.. image:: https://user-images.githubusercontent.com/32848391/58336919-f7b1a080-7e44-11e9-9106-f574371093a8.gif
@@ -203,7 +203,7 @@ More than 280 examples can be found in directories:
Apply a *Moving Least Squares* algorithm to obtain a smooth surface from a to a
-large cloud of scattered points in space
+large cloud of scattered points in space
(`moving_least_squares2D.py `_):
.. image:: https://user-images.githubusercontent.com/32848391/50738808-5816ad00-11d8-11e9-9854-c952be6fb941.jpg
@@ -211,7 +211,7 @@ large cloud of scattered points in space
:alt: rabbit
-Simulation of a gyroscope hanging from a spring
+Simulation of a gyroscope hanging from a spring
(`gyroscope1.py `_):
.. image:: https://user-images.githubusercontent.com/32848391/39766016-85c1c1d6-52e3-11e8-8575-d167b7ce5217.gif
@@ -219,7 +219,7 @@ Simulation of a gyroscope hanging from a spring
:alt: gyro
-Quantum-tunnelling effect integrating the Schroedinger equation with 4th order Runge-Kutta method.
+Quantum-tunnelling effect integrating the Schroedinger equation with 4th order Runge-Kutta method.
The animation shows the evolution of a particle in a box hitting a sinusoidal potential barrier
(`tunnelling2.py `_):
@@ -238,7 +238,7 @@ Visualizing a Turing system of reaction-diffusion between two molecules
-Support for the `FEniCS/dolfin `_ platform for visualization of PDE and
+Support for the `FEniCS/dolfin `_ platform for visualization of PDE and
finite element solutions
(`see here `_.
@@ -249,13 +249,11 @@ finite element solutions
Mesh format conversion
^^^^^^^^^^^^^^^^^^^^^^
-The command ``vtkconvert`` can be used to convert multiple files from a format to a different one:
+The command ``vtkplotter-convert`` can be used to convert multiple files from a format to a different one:
.. code-block:: bash
- Usage: vtkconvert [-h] [-to] [files [files ...]]
+ Usage: vtkplotter-convert [-h] [-to] [files [files ...]]
allowed targets formats: [vtk, vtp, vtu, vts, ply, stl, byu, xml]
- Example: > vtkconvert myfile.vtk -to ply
-
-
+ Example: > vtkplotter-convert myfile.vtk -to ply
diff --git a/docs/logos/fenics_mesh.py b/docs/logos/fenics_mesh.py
index 8ea4d4c2..b4ff43d8 100644
--- a/docs/logos/fenics_mesh.py
+++ b/docs/logos/fenics_mesh.py
@@ -9,12 +9,12 @@
show(tf, bx)
printc('implicitModeller', mytext, "this takes time")
-imp = implicitModeller(mergeActors(tf, bx),
+imp = implicitModeller(mergeActors(tf, bx),
distance=0.04,
- outer=True,
-# res=[50,20,10],
+ outer=True,
+# res=[50,20,10],
res=[110,40,20],
- bounds=[-1.0, 10.0, -1.0, 2.0, -.5, .5],
+ bounds=[-1.0, 10.0, -1.0, 2.0, -.5, .5],
maxdist=0.25,
)
diff --git a/docs/news.txt b/docs/news.txt
index b74734e7..bb84ca13 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -1,3 +1,22 @@
+2020/01/20
+- added shapes Arrow2D and Arrows2D, SphericGrid, CubicGrid
+- completely revised customizable axes=1 type
+ - axes can be created for each object independently
+- quiver plots, scatter plots, error bands plots
+- plotting in spherical coordinates
+- '2d' backend to plot static images in notebooks.
+Available backends:
+```python
+from vtkplotter import *
+# embedWindow('2d')
+# embedWindow('k3d')
+# embedWindow('itk')
+# embedWindow('panel')
+# embedWindow(False)
+```
+- Added wavefront format .OBJ writer
+
+
2020/01/08
- added type axes=11 (horizontal grid)
- fix bug by @m-albert on Volume from numpy object
@@ -15,6 +34,7 @@
- added advanced/centerline.py examples
- bump version to 2020.0.1
+
2019/11/19
- added PDV paraview file reading.
- PDB protein data bank file reader
diff --git a/setup.py b/setup.py
index eff0bd9a..2895d5f5 100644
--- a/setup.py
+++ b/setup.py
@@ -36,8 +36,8 @@
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7'
- 'Programming Language :: Python :: 3.8'
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
],
include_package_data=True
)
@@ -56,10 +56,14 @@
# cd ~/Projects/vtkplotter-examples/vtkplotter_examples
# ./run_all.sh
+# cd ~/Projects/vtkplotter/
# python prove/test_filetypes.py
+# cd ~/Projects/vtkplotter/tests/common
+# ./run_all.sh
+
# check vtkconvert:
-# vtkconvert data/290.vtk -to ply; vtkplotter data/290.ply
+# vtkplotter-convert data/290.vtk -to ply; vtkplotter data/290.ply
# check on python2 the same stuff is ok
# cd ~/Projects/vtkplotter/
@@ -119,7 +123,7 @@
# git clone https://github.com/marcomusy/vtkplotter.git
# cd vtkplotter
# pip3 -v install . --user
-#
+#
# cd
# pip3 install git+https://github.com/FEniCS/fiat.git --upgrade
# pip3 install git+https://github.com/FEniCS/ufl.git --upgrade
diff --git a/tests/dolfin/test_demo_cahn-hilliard.py b/tests/dolfin/test_demo_cahn-hilliard.py
index aad5afbb..3827059b 100644
--- a/tests/dolfin/test_demo_cahn-hilliard.py
+++ b/tests/dolfin/test_demo_cahn-hilliard.py
@@ -85,7 +85,7 @@ def J(self, A, x): assemble(self.a, tensor=A)
solver.parameters["convergence_criterion"] = "incremental"
solver.parameters["relative_tolerance"] = 1e-6
-# Step in time
+# Step in time
t = 0
T = 10*dt
scalarbar = False
@@ -108,7 +108,7 @@ def J(self, A, x): assemble(self.a, tensor=A)
lighting='plastic',
offscreen=1)
break
-
+
#################################################################################
actor = settings.plotter_instance.actors[0]
diff --git a/vtkplotter/__init__.py b/vtkplotter/__init__.py
index 2349e3cf..3f76fd23 100644
--- a/vtkplotter/__init__.py
+++ b/vtkplotter/__init__.py
@@ -8,8 +8,8 @@
A full list of examples can be found in directories:
- - `examples/basic `_
- - `examples/advanced `_
+ - `examples/basic `_
+ - `examples/advanced `_
- `examples/volumetric `_
- `examples/simulations `_
- `examples/plotting2d `_
@@ -43,6 +43,7 @@
from vtkplotter.utils import *
from vtkplotter.colors import *
import vtkplotter.settings as settings
+import vtkplotter.addons as addons
from vtkplotter.settings import datadir, embedWindow
# hack: need to uncomment this to generate documentation html
diff --git a/vtkplotter/addons.py b/vtkplotter/addons.py
index c087595c..54d38927 100644
--- a/vtkplotter/addons.py
+++ b/vtkplotter/addons.py
@@ -5,7 +5,7 @@
from vtkplotter.colors import printc, getColor
from vtkplotter.assembly import Assembly
from vtkplotter.mesh import Mesh
-from vtkplotter.utils import precision, mag, isSequence
+from vtkplotter.utils import precision, mag, isSequence, linInterpolate
import vtkplotter.shapes as shapes
import vtkplotter.settings as settings
import vtkplotter.docs as docs
@@ -28,7 +28,6 @@
"addButton",
"addCutterTool",
"addIcon",
- "addAxes",
"addLegend",
]
@@ -235,7 +234,7 @@ def addScalarBar3D(
return None
else:
lut = cmap
- vmin,vmax = obj.mapper().GetScalarRange()
+ vmin, vmax = obj.mapper().GetScalarRange()
elif isSequence(obj):
vmin, vmax = np.min(obj), np.max(obj)
@@ -694,8 +693,564 @@ def computeVisibleBounds():
sizes = np.array([max_bns[1]-min_bns[0], max_bns[3]-min_bns[2], max_bns[5]-min_bns[4]])
return vbb, sizes, min_bns, max_bns
+
#####################################################################
-def addAxes(axtype=None, c=None):
+def buildAxes(obj,
+ xtitle="x", ytitle="y", ztitle="z",
+ c=None,
+ numberOfDivisions=None,
+ limitRatio=0.04,
+ axesLineWidth=1,
+ gridLineWidth=1,
+ reorientShortTitle=True,
+ titleDepth=0,
+ xTitlePosition=0.95, yTitlePosition=0.95, zTitlePosition=0.95,
+ xTitleOffset=0.05, yTitleOffset=0.05, zTitleOffset=0.05,
+ xTitleJustify="top-right", yTitleJustify="bottom-right", zTitleJustify="bottom-right",
+ xTitleRotation=0, yTitleRotation=90, zTitleRotation=135,
+ xTitleSize=0.025, yTitleSize=0.025, zTitleSize=0.025,
+ xTitleColor=None, yTitleColor=None, zTitleColor=None,
+ xTitleBackfaceColor=None, yTitleBackfaceColor=None, zTitleBackfaceColor=None,
+ xKeepAspectRatio=True, yKeepAspectRatio=True, zKeepAspectRatio=True,
+ xyGrid=True, yzGrid=True, zxGrid=False,
+ xyGrid2=False, yzGrid2=False, zxGrid2=False,
+ xyGridTransparent=False, yzGridTransparent=False, zxGridTransparent=False,
+ xyGrid2Transparent=False, yzGrid2Transparent=False, zxGrid2Transparent=False,
+ xyPlaneColor=None, yzPlaneColor=None, zxPlaneColor=None,
+ xyGridColor=None, yzGridColor=None, zxGridColor=None,
+ xyAlpha=0.05, yzAlpha=0.05, zxAlpha=0.05,
+ xLineColor=None, yLineColor=None, zLineColor=None,
+ xOriginMarkerSize=0, yOriginMarkerSize=0, zOriginMarkerSize=0,
+ xHighlightZero=False, yHighlightZero=False, zHighlightZero=False,
+ xHighlightZeroColor="red", yHighlightZeroColor="green", zHighlightZeroColor="blue",
+ showTicks=True,
+ xTickRadius=0.005, yTickRadius=0.005, zTickRadius=0.005,
+ xTickThickness=0.0025, yTickThickness=0.0025, zTickThickness=0.0025,
+ xTickColor=None, yTickColor=None, zTickColor=None,
+ xMinorTicks=True, yMinorTicks=True, zMinorTicks=True,
+ tipSize=0.01,
+ xLabelSize=0.0175, yLabelSize=0.0175, zLabelSize=0.0175,
+ xLabelOffset=0.015, yLabelOffset=0.015, zLabelOffset=0.01,
+ xPositionsAndLabels=None, yPositionsAndLabels=None, zPositionsAndLabels=None,
+ useGlobal=False,
+ ):
+ """Draw axes on a ``Mesh`` or ``Volume``.
+ Returns a ``Assembly`` object.
+
+ - `xtitle`, ['x'], x-axis title text.
+ - `ytitle`, ['y'], y-axis title text.
+ - `ztitle`, ['z'], z-axis title text.
+ - `numberOfDivisions`,[None], approximate number of divisions on the longest axis
+ - `axesLineWidth`, [1], width of the axes lines
+ - `gridLineWidth`, [1], width of the grid lines
+ - `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
+ - `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
+ - `titleDepth`, [0], extrusion fractional depth of title text
+ - `xyGrid`, [True], show a gridded wall on plane xy
+ - `yzGrid`, [True], show a gridded wall on plane yz
+ - `zxGrid`, [True], show a gridded wall on plane zx
+ - `zxGrid2`, [False], show zx plane on opposite side of the bounding box
+ - `xyGridTransparent` [False], make grid plane completely transparent
+ - `xyGrid2Transparent` [False], make grid plane completely transparent on opposite side box
+ - `xyPlaneColor`, ['gray'], color of the plane
+ - `xyGridColor`, ['gray'], grid line color
+ - `xyAlpha`, [0.15], grid plane opacity
+ - `showTicks`, [True], show major ticks
+ - `xTitlePosition`, [0.32], title fractional positions along axis
+ - `xTitleOffset`, [0.05], title fractional offset distance from axis line
+ - `xTitleJustify`, ["top-right"], title justification
+ - `xTitleRotation`, [0], add a rotation of the axis title
+ - `xLineColor`, [automatic], color of the x-axis
+ - `xTitleColor`, [automatic], color of the axis title
+ - `xTitleBackfaceColor`, [None], color of axis title on its backface
+ - `xTitleSize`, [0.025], size of the axis title
+ - `xHighlightZero`, [True], draw a line highlighting zero position if in range
+ - `xHighlightZeroColor`, [automatic], color of the line highlighting the zero position
+ - `xTickRadius`, [0.005], radius of the major ticks
+ - `xTickThickness`, [0.0025], thickness of the major ticks along their axis
+ - `xTickColor`, [automatic], color of major ticks
+ - `xMinorTicks`, [1], number of minor ticks between two major ticks
+ - `tipSize`, [0.01], size of the arrow tip
+ - `xPositionsAndLabels` [], assign custom tick positions and labels [(pos1, label1), ...]
+ - `xLabelPrecision`, [2], nr. of significative digits to be shown
+ - `xLabelSize`, [0.015], size of the numeric labels along axis
+ - `xLabelOffset`, [0.025], offset of numeric labels
+ - `limitRatio`, [0.04], below this ratio don't plot small axis
+
+ :Example:
+
+ .. code-block:: python
+
+ from vtkplotter import Box, show
+
+ b = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0)
+ bax = buildAxes(b, c='white') # returns Assembly object
+
+ show(b, bax)
+
+ |customAxes| |customAxes.py|_
+ """
+ if c is None: # automatic black or white
+ c = (0.9, 0.9, 0.9)
+ vp = settings.plotter_instance
+ if vp and vp.renderer:
+ bgcol = vp.renderer.GetBackground()
+ else:
+ bgcol = obj.color()
+ if np.sum(bgcol) > 1.5:
+ c = (0.1, 0.1, 0.1)
+ else:
+ c = getColor(c)
+
+ if useGlobal:
+ vbb, ss, min_bns, max_bns = computeVisibleBounds()
+ else:
+ vbb = obj.GetBounds()
+ ss = np.array([vbb[1]-vbb[0], vbb[3]-vbb[2], vbb[5]-vbb[4]])
+ min_bns = vbb
+ max_bns = vbb
+
+ ssmax = max(ss)
+ if not ssmax:
+ return
+ if ss[0]/ssmax < limitRatio:
+ ss[0] = 0
+ xtitle = ''
+ if ss[1]/ssmax < limitRatio:
+ ss[1] = 0
+ ytitle = ''
+ if ss[2]/ssmax < limitRatio:
+ ss[2] = 0
+ ztitle = ''
+
+ if not xTitleColor: xTitleColor = c
+ if not yTitleColor: yTitleColor = c
+ if not zTitleColor: zTitleColor = c
+ if not xyPlaneColor: xyPlaneColor = c
+ if not yzPlaneColor: yzPlaneColor = c
+ if not zxPlaneColor: zxPlaneColor = c
+ if not xyGridColor: xyGridColor = c
+ if not yzGridColor: yzGridColor = c
+ if not zxGridColor: zxGridColor = c
+ if not xLineColor: xLineColor = c
+ if not yLineColor: yLineColor = c
+ if not zLineColor: zLineColor = c
+ if not xTickColor: xTickColor = xLineColor
+ if not yTickColor: yTickColor = yLineColor
+ if not zTickColor: zTickColor = zLineColor
+ if settings.useDepthPeeling:
+ xyGrid = False
+ yzGrid = False
+ zxGrid = False
+ xyGrid2 = False
+ yzGrid2 = False
+ zxGrid2 = False
+
+ #######################################
+ def make_ticks(x0, x1, N, labels=None):
+
+ if x1<=x0:
+ return [], []
+
+ ticks_str, ticks_float = [], []
+
+ if labels is not None:
+ # user is passing custom labels
+ ticks_float.append(0)
+ ticks_str.append('')
+ for tp, ts in labels:
+ if tp == x1:
+ continue
+ ticks_str.append(ts)
+ tickn = linInterpolate(tp, [x0,x1], [0,1])
+ ticks_float.append(tickn)
+
+ else:
+ # automatic
+ if 0<=x0<10:
+ lowBound = 0
+ elif -10 limitRatio or sizes[2]/sizes[0] > limitRatio):
- sizes[0] = 0
- xtitle = ''
- if sizes[1] and (sizes[0]/sizes[1] > limitRatio or sizes[2]/sizes[1] > limitRatio):
- sizes[1] = 0
- ytitle = ''
- if sizes[2] and (sizes[0]/sizes[2] > limitRatio or sizes[1]/sizes[2] > limitRatio):
- sizes[2] = 0
- ztitle = ''
- rats = []
- if sizes[0]: rats += [sizes[1]/sizes[0], sizes[2]/sizes[0]]
- if sizes[1]: rats += [sizes[0]/sizes[1], sizes[2]/sizes[1]]
- if sizes[2]: rats += [sizes[0]/sizes[2], sizes[1]/sizes[2]]
- if not len(rats):
- return
- rats = max(rats)
- if rats == 0:
- return
-
- nrDiv = max(1, int(6.5/rats))
-
- numberOfDivisions = axes.pop('numberOfDivisions', nrDiv) # number of divisions on the shortest axis
-
- axesLineWidth = axes.pop('axesLineWidth', 1)
- gridLineWidth = axes.pop('gridLineWidth', 1)
- reorientShortTitle = axes.pop('reorientShortTitle', True)
- originMarkerSize = axes.pop('originMarkerSize', 0)
- enableLastLabel = axes.pop('enableLastLabel', False)
- titleDepth = axes.pop('titleDepth', 0)
-
- xTitlePosition = axes.pop('xTitlePosition', 0.95) # title fractional positions along axis [0->1]
- yTitlePosition = axes.pop('yTitlePosition', 0.95)
- zTitlePosition = axes.pop('zTitlePosition', 0.95)
-
- xTitleOffset = axes.pop('xTitleOffset', 0.05) # title fractional offsets
- yTitleOffset = axes.pop('yTitleOffset', 0.05) #
- zTitleOffset = axes.pop('zTitleOffset', 0.05) #
-
- xTitleJustify = axes.pop('xTitleJustify', "top-right")
- yTitleJustify = axes.pop('yTitleJustify', "bottom-right")
- zTitleJustify = axes.pop('zTitleJustify', "bottom-right")
-
- xTitleRotation = axes.pop('xTitleRotation', 0)
- yTitleRotation = axes.pop('yTitleRotation', 90)
- zTitleRotation = axes.pop('zTitleRotation', 135)
-
- xTitleSize = axes.pop('xTitleSize', 0.025)
- yTitleSize = axes.pop('yTitleSize', 0.025)
- zTitleSize = axes.pop('zTitleSize', 0.025)
-
- xTitleColor = axes.pop('xTitleColor', c)
- yTitleColor = axes.pop('yTitleColor', c)
- zTitleColor = axes.pop('zTitleColor', c)
-
- xTitleBackfaceColor = axes.pop('xTitleBackfaceColor', None)
- yTitleBackfaceColor = axes.pop('yTitleBackfaceColor', None)
- zTitleBackfaceColor = axes.pop('zTitleBackfaceColor', None)
-
- xKeepAspectRatio = axes.pop('xKeepAspectRatio', True)
- yKeepAspectRatio = axes.pop('yKeepAspectRatio', True)
- zKeepAspectRatio = axes.pop('zKeepAspectRatio', True)
-
- xyGrid = axes.pop('xyGrid', True)
- yzGrid = axes.pop('yzGrid', True)
- zxGrid = axes.pop('zxGrid', False)
- xyGrid2 = axes.pop('xyGrid2', False) # opposite side grid
- yzGrid2 = axes.pop('yzGrid2', False)
- zxGrid2 = axes.pop('zxGrid2', False)
- if settings.plotter_instance and settings.plotter_instance.renderer.GetUseDepthPeeling():
- xyGrid = False
- yzGrid = False
- zxGrid = False
- xyGrid2 = False
- yzGrid2 = False
- zxGrid2 = False
-
- xyGridTransparent = axes.pop('xyGridTransparent', False)
- yzGridTransparent = axes.pop('yzGridTransparent', False)
- zxGridTransparent = axes.pop('zxGridTransparent', False)
- xyGrid2Transparent = axes.pop('xyGrid2Transparent', False)
- yzGrid2Transparent = axes.pop('yzGrid2Transparent', False)
- zxGrid2Transparent = axes.pop('zxGrid2Transparent', False)
-
- xyPlaneColor = axes.pop('xyPlaneColor', c)
- yzPlaneColor = axes.pop('yzPlaneColor', c)
- zxPlaneColor = axes.pop('zxPlaneColor', c)
- xyGridColor = axes.pop('xyGridColor', c)
- yzGridColor = axes.pop('yzGridColor', c)
- zxGridColor = axes.pop('zxGridColor', c)
- xyAlpha = axes.pop('xyAlpha', 0.05)
- yzAlpha = axes.pop('yzAlpha', 0.05)
- zxAlpha = axes.pop('zxAlpha', 0.05)
-
- xLineColor = axes.pop('xLineColor', c)
- yLineColor = axes.pop('yLineColor', c)
- zLineColor = axes.pop('zLineColor', c)
-
- xHighlightZero = axes.pop('xHighlightZero', False)
- yHighlightZero = axes.pop('yHighlightZero', False)
- zHighlightZero = axes.pop('zHighlightZero', False)
- xHighlightZeroColor = axes.pop('xHighlightZeroColor', 'red')
- yHighlightZeroColor = axes.pop('yHighlightZeroColor', 'green')
- zHighlightZeroColor = axes.pop('zHighlightZeroColor', 'blue')
-
- showTicks = axes.pop('showTicks', True)
- xTickRadius = axes.pop('xTickRadius', 0.005)
- yTickRadius = axes.pop('yTickRadius', 0.005)
- zTickRadius = axes.pop('zTickRadius', 0.005)
-
- xTickThickness = axes.pop('xTickThickness', 0.0025)
- yTickThickness = axes.pop('yTickThickness', 0.0025)
- zTickThickness = axes.pop('zTickThickness', 0.0025)
-
- xTickColor = axes.pop('xTickColor', xLineColor)
- yTickColor = axes.pop('yTickColor', yLineColor)
- zTickColor = axes.pop('zTickColor', zLineColor)
-
- xMinorTicks = axes.pop('xMinorTicks', 1)
- yMinorTicks = axes.pop('yMinorTicks', 1)
- zMinorTicks = axes.pop('zMinorTicks', 1)
-
- tipSize = axes.pop('tipSize', 0.01)
-
- xLabelPrecision = axes.pop('xLabelPrecision', 2) # nr. of significant digits
- yLabelPrecision = axes.pop('yLabelPrecision', 2)
- zLabelPrecision = axes.pop('zLabelPrecision', 2)
-
- xLabelSize = axes.pop('xLabelSize', 0.0175)
- yLabelSize = axes.pop('yLabelSize', 0.0175)
- zLabelSize = axes.pop('zLabelSize', 0.0175)
-
- xLabelOffset = axes.pop('xLabelOffset', 0.015)
- yLabelOffset = axes.pop('yLabelOffset', 0.015)
- zLabelOffset = axes.pop('zLabelOffset', 0.01)
-
- ########################
- step = np.min(sizes[np.nonzero(sizes)]) / numberOfDivisions
- rx, ry, rz = np.rint(sizes / step).astype(int)
- if rx==0: xtitle=''
- if ry==0: ytitle=''
- if rz==0: ztitle=''
-
- if enableLastLabel:
- enableLastLabel = 1
+ vp.axes.update({'useGlobal':True})
+ asse = buildAxes(None, **vp.axes)
else:
- enableLastLabel = 0
-
- ################################################ axes lines
- lines = []
- if xtitle: lines.append(shapes.Line([0, 0, 0], [1, 0, 0], c=xLineColor, lw=axesLineWidth))
- if ytitle: lines.append(shapes.Line([0, 0, 0], [0, 1, 0], c=yLineColor, lw=axesLineWidth))
- if ztitle: lines.append(shapes.Line([0, 0, 0], [0, 0, 1], c=zLineColor, lw=axesLineWidth))
-
- ################################################ grid planes
- grids = []
- if xyGrid and xtitle and ytitle:
- gxy = shapes.Grid(pos=(0.5, 0.5, 0), normal=[0, 0, 1], resx=rx, resy=ry)
- gxy.alpha(xyAlpha).wireframe(xyGridTransparent).c(xyPlaneColor).lw(gridLineWidth).lc(xyGridColor)
- grids.append(gxy)
- if yzGrid and ytitle and ztitle:
- gyz = shapes.Grid(pos=(0, 0.5, 0.5), normal=[1, 0, 0], resx=rz, resy=ry)
- gyz.alpha(yzAlpha).wireframe(yzGridTransparent).c(yzPlaneColor).lw(gridLineWidth).lc(yzGridColor)
- grids.append(gyz)
- if zxGrid and ztitle and xtitle:
- gzx = shapes.Grid(pos=(0.5, 0, 0.5), normal=[0, 1, 0], resx=rz, resy=rx)
- gzx.alpha(zxAlpha).wireframe(zxGridTransparent).c(zxPlaneColor).lw(gridLineWidth).lc(zxGridColor)
- grids.append(gzx)
-
- grids2 = []
- if xyGrid2 and xtitle and ytitle:
- gxy2 = shapes.Grid(pos=(0.5, 0.5, 1), normal=[0, 0, 1], resx=rx, resy=ry)
- gxy2.alpha(xyAlpha).wireframe(xyGrid2Transparent).c(xyPlaneColor).lw(gridLineWidth).lc(xyGridColor)
- grids2.append(gxy2)
- if yzGrid2 and ytitle and ztitle:
- gyz2 = shapes.Grid(pos=(1, 0.5, 0.5), normal=[1, 0, 0], resx=rz, resy=ry)
- gyz2.alpha(yzAlpha).wireframe(yzGrid2Transparent).c(yzPlaneColor).lw(gridLineWidth).lc(yzGridColor)
- grids2.append(gyz2)
- if zxGrid2 and ztitle and xtitle:
- gzx2 = shapes.Grid(pos=(0.5, 1, 0.5), normal=[0, 1, 0], resx=rz, resy=rx)
- gzx2.alpha(zxAlpha).wireframe(zxGrid2Transparent).c(zxPlaneColor).lw(gridLineWidth).lc(zxGridColor)
- grids2.append(gzx2)
-
-
- ################################################ zero lines highlights
- highlights = []
- if xyGrid and xtitle and ytitle:
- if xHighlightZero and min_bns[0] <= 0 and max_bns[1] > 0:
- xhl = -min_bns[0] / sizes[0]
- hxy = shapes.Line([xhl,0,0], [xhl,1,0], c=xHighlightZeroColor)
- hxy.alpha(np.sqrt(xyAlpha)).lw(gridLineWidth*2)
- highlights.append(hxy)
- if yHighlightZero and min_bns[2] <= 0 and max_bns[3] > 0:
- yhl = -min_bns[2] / sizes[1]
- hyx = shapes.Line([0,yhl,0], [1,yhl,0], c=yHighlightZeroColor)
- hyx.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
- highlights.append(hyx)
-
- if yzGrid and ytitle and ztitle:
- if yHighlightZero and min_bns[2] <= 0 and max_bns[3] > 0:
- yhl = -min_bns[2] / sizes[1]
- hyz = shapes.Line([0,yhl,0], [0,yhl,1], c=yHighlightZeroColor)
- hyz.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
- highlights.append(hyz)
- if zHighlightZero and min_bns[4] <= 0 and max_bns[5] > 0:
- zhl = -min_bns[4] / sizes[2]
- hzy = shapes.Line([0,0,zhl], [0,1,zhl], c=zHighlightZeroColor)
- hzy.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
- highlights.append(hzy)
-
- if zxGrid and ztitle and xtitle:
- if zHighlightZero and min_bns[4] <= 0 and max_bns[5] > 0:
- zhl = -min_bns[4] / sizes[2]
- hzx = shapes.Line([0,0,zhl], [1,0,zhl], c=zHighlightZeroColor)
- hzx.alpha(np.sqrt(zxAlpha)).lw(gridLineWidth*2)
- highlights.append(hzx)
- if xHighlightZero and min_bns[0] <= 0 and max_bns[1] > 0:
- xhl = -min_bns[0] / sizes[0]
- hxz = shapes.Line([xhl,0,0], [xhl,0,1], c=xHighlightZeroColor)
- hxz.alpha(np.sqrt(zxAlpha)).lw(gridLineWidth*2)
- highlights.append(hxz)
-
- ################################################ aspect ratio scales
- x_aspect_ratio_scale=1
- y_aspect_ratio_scale=1
- z_aspect_ratio_scale=1
- if xtitle:
- if sizes[0] > sizes[1]:
- x_aspect_ratio_scale = (1, sizes[0]/sizes[1], 1)
- else:
- x_aspect_ratio_scale = (sizes[1]/sizes[0], 1, 1)
-
- if ytitle:
- if sizes[0] > sizes[1]:
- y_aspect_ratio_scale = (sizes[0]/sizes[1], 1, 1)
- else:
- y_aspect_ratio_scale = (1, sizes[1]/sizes[0], 1)
-
- if ztitle:
- smean = (sizes[0]+sizes[1])/2
- if smean:
- if sizes[2] > smean:
- zarfact = smean/sizes[2]
- z_aspect_ratio_scale = (zarfact, zarfact*sizes[2]/smean, zarfact)
- else:
- z_aspect_ratio_scale = (smean/sizes[2], 1, 1)
-
- ################################################ axes titles
- titles = []
- if xtitle:
- xt = shapes.Text(xtitle, pos=(0,0,0), s=xTitleSize, bc=xTitleBackfaceColor,
- c=xTitleColor, justify=xTitleJustify, depth=titleDepth)
- if reorientShortTitle and len(ytitle) < 3: # title is short
- wpos = [xTitlePosition, -xTitleOffset +0.02, 0]
- else:
- wpos = [xTitlePosition, -xTitleOffset, 0]
- if xKeepAspectRatio: xt.SetScale(x_aspect_ratio_scale)
- xt.RotateX(xTitleRotation)
- xt.pos(wpos)
- titles.append(xt.lighting(specular=0, diffuse=0, ambient=1))
+ asse = buildAxes(None, useGlobal=True)
- if ytitle:
- yt = shapes.Text(ytitle, pos=(0, 0, 0), s=yTitleSize, bc=yTitleBackfaceColor,
- c=yTitleColor, justify=yTitleJustify, depth=titleDepth)
- if reorientShortTitle and len(ytitle) < 3: # title is short
- wpos = [-yTitleOffset +0.03-0.01*len(ytitle), yTitlePosition, 0]
- if yKeepAspectRatio: yt.SetScale(x_aspect_ratio_scale) #x!
- else:
- wpos = [-yTitleOffset, yTitlePosition, 0]
- if yKeepAspectRatio: yt.SetScale(y_aspect_ratio_scale)
- yt.RotateZ(yTitleRotation)
- yt.pos(wpos)
- titles.append(yt.lighting(specular=0, diffuse=0, ambient=1))
-
- if ztitle:
- zt = shapes.Text(ztitle, pos=(0, 0, 0), s=zTitleSize, bc=zTitleBackfaceColor,
- c=zTitleColor, justify=zTitleJustify, depth=titleDepth)
- if reorientShortTitle and len(ztitle) < 3: # title is short
- wpos = [(-zTitleOffset+0.02-0.003*len(ztitle))/1.42,
- (-zTitleOffset+0.02-0.003*len(ztitle))/1.42, zTitlePosition]
- if zKeepAspectRatio:
- zr2 = (z_aspect_ratio_scale[1], z_aspect_ratio_scale[0], z_aspect_ratio_scale[2])
- zt.SetScale(zr2)
- zt.RotateX(90)
- zt.RotateY(45)
- zt.pos(wpos)
- else:
- if zKeepAspectRatio: zt.SetScale(z_aspect_ratio_scale)
- wpos = [-zTitleOffset/1.42, -zTitleOffset/1.42, zTitlePosition]
- zt.RotateY(-90)
- zt.RotateX(zTitleRotation)
- zt.pos(wpos)
- titles.append(zt.lighting(specular=0, diffuse=0, ambient=1))
-
- ################################################ cube origin ticks
- originmarks = []
- if originMarkerSize:
- if xtitle:
- if min_bns[0] <= 0 and max_bns[1] > 0: # mark x origin
- ox = shapes.Cube([-min_bns[0] / sizes[0], 0, 0], side=originMarkerSize, c=xLineColor)
- originmarks.append(ox.lighting(specular=0, diffuse=0, ambient=1))
-
- if 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=originMarkerSize, c=yLineColor)
- originmarks.append(oy.lighting(specular=0, diffuse=0, ambient=1))
-
- if 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=originMarkerSize, c=zLineColor)
- originmarks.append(oz.lighting(specular=0, diffuse=0, ambient=1))
-
- ################################################ arrow cone
- cones = []
- if tipSize:
- if xtitle:
- cx = shapes.Cone((1,0,0), r=tipSize, height=tipSize*2, axis=(1,0,0), c=xLineColor, res=10)
- cones.append(cx.lighting(specular=0, diffuse=0, ambient=1))
- if ytitle:
- cy = shapes.Cone((0,1,0), r=tipSize, height=tipSize*2, axis=(0,1,0), c=yLineColor, res=10)
- cones.append(cy.lighting(specular=0, diffuse=0, ambient=1))
- if ztitle:
- cz = shapes.Cone((0,0,1), r=tipSize, height=tipSize*2, axis=(0,0,1), c=zLineColor, res=10)
- cones.append(cz.lighting(specular=0, diffuse=0, ambient=1))
-
- ################################################ cylindrical ticks
- ticks = []
- if showTicks:
- if xtitle:
- for coo in range(1 ,rx):
- v = [coo/rx,0,0]
- xds = shapes.Cylinder(v, r=xTickRadius, height=xTickThickness, axis=(1,0,0), res=10)
- ticks.append(xds.c(xTickColor).lighting(specular=0, ambient=1))
- if ytitle:
- for coo in range(1 ,ry):
- v = [0,coo/ry,0]
- yds = shapes.Cylinder(v, r=yTickRadius, height=yTickThickness, axis=(0,1,0), res=10)
- ticks.append(yds.c(yTickColor).lighting(specular=0, ambient=1))
- if ztitle:
- for coo in range(1 ,rz):
- v = [0,0,coo/rz]
- zds = shapes.Cylinder(v, r=zTickRadius, height=zTickThickness, axis=(0,0,1), res=10)
- ticks.append(zds.c(zTickColor).lighting(specular=0, ambient=1))
-
- ################################################ MINOR cylindrical ticks
- minorticks = []
- if xMinorTicks and xtitle:
- xMinorTicks += 1
- for coo in range(1, rx*xMinorTicks):
- v = [coo/rx/xMinorTicks,0,0]
- mxds = shapes.Cylinder(v, r=xTickRadius/1.5, height=xTickThickness, axis=(1,0,0), res=6)
- minorticks.append(mxds.c(xTickColor).lighting(specular=0, ambient=1))
- if yMinorTicks and ytitle:
- yMinorTicks += 1
- for coo in range(1, ry*yMinorTicks):
- v = [0, coo/ry/yMinorTicks,0]
- myds = shapes.Cylinder(v, r=yTickRadius/1.5, height=yTickThickness, axis=(0,1,0), res=6)
- minorticks.append(myds.c(yTickColor).lighting(specular=0, ambient=1))
- if zMinorTicks and ztitle:
- zMinorTicks += 1
- for coo in range(1, rz*zMinorTicks):
- v = [0, 0, coo/rz/zMinorTicks]
- mzds = shapes.Cylinder(v, r=zTickRadius/1.5, height=zTickThickness, axis=(0,0,1), res=6)
- minorticks.append(mzds.c(zTickColor).lighting(specular=0, ambient=1))
-
- ################################################ axes tick NUMERIC labels
- labels = []
- if xLabelSize:
- if xtitle:
- if rx > 12: rx = int(rx/2)
- for ic in range(1, rx+enableLastLabel):
- v = (ic/rx, -xLabelOffset, 0)
- val = v[0]*sizes[0]+min_bns[0]
- if abs(val)>1 and sizes[0]<1: xLabelPrecision = int(xLabelPrecision-np.log10(sizes[0]))
- tval = precision(val, xLabelPrecision, vrange=sizes[0])
- xlab = shapes.Text(tval, pos=v, s=xLabelSize, justify="center-top", depth=0)
- if xKeepAspectRatio: xlab.SetScale(x_aspect_ratio_scale)
- labels.append(xlab.c(xTickColor).lighting(specular=0, ambient=1))
- if yLabelSize:
- if ytitle:
- if ry > 12: ry = int(ry/2)
- for ic in range(1, ry+enableLastLabel):
- v = (-yLabelOffset, ic/ry, 0)
- val = v[1]*sizes[1]+min_bns[2]
- if abs(val)>1 and sizes[1]<1: yLabelPrecision = int(yLabelPrecision-np.log10(sizes[1]))
- tval = precision(val, yLabelPrecision, vrange=sizes[1])
- ylab = shapes.Text(tval, pos=(0,0,0), s=yLabelSize, justify="center-bottom", depth=0)
- if yKeepAspectRatio: ylab.SetScale(y_aspect_ratio_scale)
- ylab.RotateZ(yTitleRotation)
- ylab.pos(v)
- labels.append(ylab.c(yTickColor).lighting(specular=0, ambient=1))
- if zLabelSize:
- if ztitle:
- if rz > 12: rz = int(rz/2)
- for ic in range(1, rz+enableLastLabel):
- v = (-zLabelOffset, -zLabelOffset, ic/rz)
- val = v[2]*sizes[2]+min_bns[4]
- tval = precision(val, zLabelPrecision, vrange=sizes[2])
- if abs(val)>1 and sizes[2]<1: zLabelPrecision = int(zLabelPrecision-np.log10(sizes[2]))
- zlab = shapes.Text(tval, pos=(0,0,0), s=zLabelSize, justify="center-bottom", depth=0)
- if zKeepAspectRatio: zlab.SetScale(z_aspect_ratio_scale)
- zlab.RotateY(-90)
- zlab.RotateX(zTitleRotation)
- zlab.pos(v)
- labels.append(zlab.c(zTickColor).lighting(specular=0, ambient=1))
-
- acts = grids + grids2 + lines + highlights + titles
- acts += minorticks + originmarks + ticks + cones + labels
- for a in acts:
- a.PickableOff()
- asse = Assembly(acts)
- asse.pos(min_bns[0], min_bns[2], min_bns[4])
- asse.SetScale(sizes)
- asse.PickableOff()
vp.renderer.AddActor(asse)
vp.axes_instances[r] = asse
- vp.axes = axes_copy #un-pop
elif vp.axes == 2 or vp.axes == 3:
@@ -1470,10 +1593,10 @@ def addAxes(axtype=None, c=None):
vp.axes_instances[r] = ca
elif vp.axes == 11:
- vbb, sizes = computeVisibleBounds()[0:2]
+ vbb, ss = computeVisibleBounds()[0:2]
xpos, ypos = (vbb[1] + vbb[0]) /2, (vbb[3] + vbb[2]) /2
gr = shapes.Grid((xpos, ypos, vbb[4]),
- sx=sizes[0]*8, sy=sizes[1]*8,
+ sx=ss[0]*8, sy=ss[1]*8,
resx=7, resy=7,
c=c, alpha=0.2)
gr.lighting('ambient').PickableOff()
diff --git a/vtkplotter/analysis.py b/vtkplotter/analysis.py
index 76263621..15859b10 100644
--- a/vtkplotter/analysis.py
+++ b/vtkplotter/analysis.py
@@ -89,7 +89,7 @@ def delaunay2D(plist, mode='xy', tol=None):
"""
pd = vtk.vtkPolyData()
vpts = vtk.vtkPoints()
- vpts.SetData(numpy_to_vtk(plist, deep=True))
+ vpts.SetData(numpy_to_vtk(np.ascontiguousarray(plist), deep=True))
pd.SetPoints(vpts)
delny = vtk.vtkDelaunay2D()
delny.SetInputData(pd)
@@ -442,9 +442,9 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False):
``mesh.info['va']``, ``mesh.info['vb']``, ``mesh.info['vc']``
(sphericity is equal to 0 for a perfect sphere).
- .. hint:: Examples: |pca.py|_ |cell_main.py|_
+ .. hint:: Examples: |pca.py|_ |cell_colony.py|_
- |pca| |cell_main|
+ |pca| |cell_colony|
"""
try:
from scipy.stats import f
@@ -505,6 +505,7 @@ def pcaEllipsoid(points, pvalue=0.95, pcaAxes=False):
finact.info["va"] = ua
finact.info["vb"] = ub
finact.info["vc"] = uc
+ finact.name = "pcaEllipsoid"
return finact
diff --git a/vtkplotter/assembly.py b/vtkplotter/assembly.py
index b05c3f51..44a48a59 100644
--- a/vtkplotter/assembly.py
+++ b/vtkplotter/assembly.py
@@ -52,13 +52,13 @@ def __add__(self, meshs):
self.AddPart(meshs)
return self
-
+
def getActors(self):
"""Obsolete, use getMeshes() instead."""
print("WARNING: getActors() is obsolete, use getMeshes() instead.")
return self.getMeshes()
-
+
def getMeshes(self):
"""Unpack the list of ``Mesh`` objects from a ``Assembly``."""
return self.actors
diff --git a/vtkplotter/backends.py b/vtkplotter/backends.py
index 75777403..f6abde82 100644
--- a/vtkplotter/backends.py
+++ b/vtkplotter/backends.py
@@ -4,7 +4,6 @@
import os
import vtkplotter.colors as colors
-from vtkplotter.assembly import Assembly
from vtkplotter.mesh import Mesh
from vtkplotter.volume import Volume
import vtkplotter.settings as settings
@@ -28,57 +27,10 @@ def getNotebookBackend(actors2show, zoom, viewup):
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)
+ settings.notebook_plotter = view(actors=actors2show,
+ cmap='jet', ui_collapsed=True,
+ gradient_opacity=False)
- polys2show = []
- points2show = []
- imgs2show = []
- polycols, polyalphas, pointcols, pointalphas = [],[],[],[]
-
- for ia in actors2show:
-
- if isinstance(ia, Assembly): #unpack assemblies
- acass = ia.getMeshes()
- for ac in acass:
- if ac.polydata().GetNumberOfPolys():
- polys2show.append(ac.polydata())
- polycols.append(ac.color())
- polyalphas.append(ac.alpha())
- else:
- points2show.append(ac.polydata())
- pointcols.append(ac.color())
- pointalphas.append(ac.alpha())
-
- elif isinstance(ia, Mesh):
- if ia.polydata().GetNumberOfPolys():
- polys2show.append(ia.polydata())
- polycols.append(ia.color())
- polyalphas.append(ia.alpha())
- else:
- points2show.append(ia.polydata())
- pointcols.append(ia.color())
- pointalphas.append(ia.alpha())
-
- elif isinstance(ia, Volume):
- imgs2show.append(ia.imagedata())
-
- img = None
- if len(imgs2show):
- img = imgs2show[0]
-
- settings.notebook_plotter = view(image=img,
- geometries=polys2show,
- geometry_colors=polycols,
- geometry_opacities=polyalphas,
- point_sets=points2show,
- point_set_colors=pointcols,
- point_set_opacities=pointalphas,
- cmap='jet',
- ui_collapsed=True,
- gradient_opacity=False,
- )
####################################################################################
elif settings.notebookBackend == 'k3d':
@@ -263,4 +215,21 @@ def getNotebookBackend(actors2show, zoom, viewup):
settings.notebook_plotter = panel.pane.VTK(vp.window,
width=int(vp.size[0]/1.5),
height=int(vp.size[1]/2))
+
+
+ ####################################################################################
+ elif '2d' in settings.notebookBackend.lower() and hasattr(vp, 'window') and vp.window:
+ import PIL.Image
+ try:
+ import IPython
+ except ImportError:
+ raise Exception('IPython not available.')
+ return
+
+ from vtkplotter.vtkio import screenshot
+ settings.screeshotLargeImage = True
+ nn = screenshot(returnNumpy=True, scale=settings.screeshotScale+2)
+ pil_img = PIL.Image.fromarray(nn)
+ settings.notebook_plotter = IPython.display.display(pil_img)
+
return settings.notebook_plotter
diff --git a/vtkplotter/base.py b/vtkplotter/base.py
index 24e04f32..c39e6aa5 100644
--- a/vtkplotter/base.py
+++ b/vtkplotter/base.py
@@ -70,6 +70,68 @@ def inputdata(self):
return self._mapper.GetInput()
return self.GetMapper().GetInput()
+ def buildAxes(self, **kargs):
+ """Draw axes on a ``Mesh`` or ``Volume``.
+ Returns a ``Assembly`` object.
+
+ - `xtitle`, ['x'], x-axis title text.
+ - `ytitle`, ['y'], y-axis title text.
+ - `ztitle`, ['z'], z-axis title text.
+ - `numberOfDivisions`,[None], approximate number of divisions on the longest axis
+ - `axesLineWidth`, [1], width of the axes lines
+ - `gridLineWidth`, [1], width of the grid lines
+ - `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
+ - `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
+ - `titleDepth`, [0], extrusion fractional depth of title text
+ - `xyGrid`, [True], show a gridded wall on plane xy
+ - `yzGrid`, [True], show a gridded wall on plane yz
+ - `zxGrid`, [True], show a gridded wall on plane zx
+ - `zxGrid2`, [False], show zx plane on opposite side of the bounding box
+ - `xyGridTransparent` [False], make grid plane completely transparent
+ - `xyGrid2Transparent` [False], make grid plane completely transparent on opposite side box
+ - `xyPlaneColor`, ['gray'], color of the plane
+ - `xyGridColor`, ['gray'], grid line color
+ - `xyAlpha`, [0.15], grid plane opacity
+ - `showTicks`, [True], show major ticks
+ - `xTitlePosition`, [0.32], title fractional positions along axis
+ - `xTitleOffset`, [0.05], title fractional offset distance from axis line
+ - `xTitleJustify`, ["top-right"], title justification
+ - `xTitleRotation`, [0], add a rotation of the axis title
+ - `xLineColor`, [automatic], color of the x-axis
+ - `xTitleColor`, [automatic], color of the axis title
+ - `xTitleBackfaceColor`, [None], color of axis title on its backface
+ - `xTitleSize`, [0.025], size of the axis title
+ - `xHighlightZero`, [True], draw a line highlighting zero position if in range
+ - `xHighlightZeroColor`, [automatic], color of the line highlighting the zero position
+ - `xTickRadius`, [0.005], radius of the major ticks
+ - `xTickThickness`, [0.0025], thickness of the major ticks along their axis
+ - `xTickColor`, [automatic], color of major ticks
+ - `xMinorTicks`, [1], number of minor ticks between two major ticks
+ - `tipSize`, [0.01], size of the arrow tip
+ - `xPositionsAndLabels` [], assign custom tick positions and labels [(pos1, label1), ...]
+ - `xLabelPrecision`, [2], nr. of significative digits to be shown
+ - `xLabelSize`, [0.015], size of the numeric labels along axis
+ - `xLabelOffset`, [0.025], offset of numeric labels
+ - `limitRatio`, [0.04], below this ratio don't plot small axis
+
+ :Example:
+
+ .. code-block:: python
+
+ from vtkplotter import Box, show
+
+ b = Box(pos=(1,2,3), length=8, width=9, height=7).alpha(0)
+ bax = buildAxes(b, c='white') # returns Assembly object
+
+ show(b, bax)
+
+ |customAxes| |customAxes.py|_
+
+ |customIndividualAxes| |customIndividualAxes.py|_
+ """
+ from vtkplotter.addons import buildAxes
+ return buildAxes(self, **kargs)
+
def show(self, **options):
"""
diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py
index 39e60d5b..b788e39a 100644
--- a/vtkplotter/docs.py
+++ b/vtkplotter/docs.py
@@ -349,7 +349,7 @@ def tips():
.. |histoHexagonal.py| replace:: histoHexagonal.py
.. _histoHexagonal.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/histoHexagonal.py
-.. |histoHexagonal| image:: https://user-images.githubusercontent.com/32848391/50738861-bfccf800-11d8-11e9-9698-c0b9dccdba4d.jpg
+.. |histoHexagonal| image:: https://user-images.githubusercontent.com/32848391/72434748-b471bc80-379c-11ea-95d7-d70333770582.png
:width: 250 px
:target: histoHexagonal.py_
:alt: histoHexagonal.py
@@ -375,12 +375,12 @@ def tips():
:target: pca.py_
:alt: pca.py
-.. |cell_main.py| replace:: cell_main.py
-.. _cell_main.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/simulations/cell_main.py
-.. |cell_main| image:: https://user-images.githubusercontent.com/32848391/50738947-7335ec80-11d9-11e9-9a45-6053b4eaf9f9.jpg
+.. |cell_colony.py| replace:: cell_colony.py
+.. _cell_colony.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/simulations/cell_colony.py
+.. |cell_colony| image:: https://user-images.githubusercontent.com/32848391/50738947-7335ec80-11d9-11e9-9a45-6053b4eaf9f9.jpg
:width: 250 px
- :target: cell_main.py_
- :alt: cell_main.py
+ :target: cell_colony.py_
+ :alt: cell_colony.py
.. |mesh_smoothers.py| replace:: mesh_smoothers.py
.. _mesh_smoothers.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/advanced/mesh_smoothers.py
@@ -889,8 +889,8 @@ def tips():
.. |isolines.py| replace:: isolines.py
.. _isolines.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/basic/isolines.py
-.. |isolines| image:: https://user-images.githubusercontent.com/32848391/56752570-de0b3380-6788-11e9-8679-6697c6fa7e5a.png
- :width: 250 px
+.. |isolines| image:: https://user-images.githubusercontent.com/32848391/72433087-f00a8780-3798-11ea-9778-991f0abeca70.png
+ :width: 350 px
:target: isolines.py_
:alt: isolines.py
@@ -1194,6 +1194,13 @@ def tips():
.. |warpto| image:: https://user-images.githubusercontent.com/32848391/69259878-3c817e80-0bbf-11ea-9025-03b9f6affccc.png
:width: 350 px
+.. |linInterpolate.py| replace:: linInterpolate.py
+.. _linInterpolate.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/basic/linInterpolate.py
+.. |linInterpolate| image:: https://user-images.githubusercontent.com/32848391/70559826-a621f680-1b87-11ea-89f3-e6b74d8953d9.png
+ :width: 350 px
+ :target: linInterpolate.py_
+ :alt: linInterpolate.py
+
.. |plotxy.py| replace:: plotxy.py
.. _plotxy.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/plotxy.py
.. |plotxy| image:: https://user-images.githubusercontent.com/32848391/69158509-d6c1c380-0ae6-11ea-9dbf-ff5cd396a9a6.png
@@ -1201,12 +1208,57 @@ def tips():
:target: plotxy.py_
:alt: plotxy.py
-.. |linInterpolate.py| replace:: linInterpolate.py
-.. _linInterpolate.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/basic/linInterpolate.py
-.. |linInterpolate| image:: https://user-images.githubusercontent.com/32848391/70559826-a621f680-1b87-11ea-89f3-e6b74d8953d9.png
+.. |quiverPlot.py| replace:: quiverPlot.py
+.. _quiverPlot.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/quiverPlot.py
+.. |quiverPlot| image:: https://user-images.githubusercontent.com/32848391/72261438-199aa600-3615-11ea-870e-e44ca4c4b8d3.png
+ :width: 250 px
+ :target: quiverPlot.py_
+ :alt: quiverPlot.py
+
+.. |sphericPlot.py| replace:: sphericPlot.py
+.. _sphericPlot.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/sphericPlot.py
+.. |sphericPlot| image:: https://user-images.githubusercontent.com/32848391/72433091-f0a31e00-3798-11ea-86bd-6c522e23ec61.png
:width: 350 px
- :target: linInterpolate.py_
- :alt: linInterpolate.py
+ :target: sphericPlot.py_
+ :alt: sphericPlot.py
+
+.. |sphericgrid| image:: https://user-images.githubusercontent.com/32848391/72433092-f0a31e00-3798-11ea-85f7-b2f5fcc31568.png
+ :width: 250 px
+
+.. |histogram2D.py| replace:: histogram2D.py
+.. _histogram2D.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/histogram2D.py
+.. |histogram2D| image:: https://user-images.githubusercontent.com/32848391/72452359-b5671600-37bd-11ea-8b1d-c44d884496ed.png
+ :width: 350 px
+ :target: histogram2D.py_
+ :alt: histogram2D.py
+
+.. |plot_errband.py| replace:: plot_errband.py
+.. _plot_errband.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/plot_errband.py
+.. |plot_errband| image:: https://user-images.githubusercontent.com/32848391/72461569-970a1600-37cf-11ea-8060-a8a6cf657b95.png
+ :width: 350 px
+ :target: plot_errband.py_
+ :alt: plot_errband.py
+
+.. |scatter2.py| replace:: scatter2.py
+.. _scatter2.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/scatter2.py
+.. |scatter2| image:: https://user-images.githubusercontent.com/32848391/72615028-013bcb80-3934-11ea-8ab8-823f1916bc6c.png
+ :width: 350 px
+ :target: scatter2.py_
+ :alt: scatter2.py
+
+.. |scatter3.py| replace:: scatter3.py
+.. _scatter3.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/scatter3.py
+.. |scatter3| image:: https://user-images.githubusercontent.com/32848391/72446102-2d7c0e80-37b3-11ea-8fe4-b27526af574f.png
+ :width: 350 px
+ :target: scatter3.py_
+ :alt: scatter3.py
+
+.. |customIndividualAxes.py| replace:: customIndividualAxes.py
+.. _customIndividualAxes.py: https://github.com/marcomusy/vtkplotter-examples/blob/master/vtkplotter_examples/plotting2d/customIndividualAxes.py
+.. |customIndividualAxes| image:: https://user-images.githubusercontent.com/32848391/72752870-ab7d5280-3bc3-11ea-8911-9ace00211e23.png
+ :width: 350 px
+ :target: customIndividualAxes.py_
+ :alt: customIndividualAxes.py
"""
diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py
index 57a80a0b..4f1fc380 100644
--- a/vtkplotter/dolfin.py
+++ b/vtkplotter/dolfin.py
@@ -379,46 +379,47 @@ def plot(*inputobj, **options):
- 11, show a large grid on the x-y plane (use with zoom=8)
- 12, show polar axes.
- Axes type-1 can be fully customized by passing a dictionary ``axes=dict()`` where:
-
- - `xtitle`, ['x'], x-axis title text.
- - `ytitle`, ['y'], y-axis title text.
- - `ztitle`, ['z'], z-axis title text.
- - `numberOfDivisions`, [automatic], number of divisions on the shortest axis
- - `axesLineWidth`, [1], width of the axes lines
- - `gridLineWidth`, [1], width of the grid lines
- - `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
- - `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
- - `enableLastLabel`, [False], show last numeric label on axes
- - `titleDepth`, [0], extrusion fractional depth of title text
- - `xyGrid`, [True], show a gridded wall on plane xy
- - `yzGrid`, [True], show a gridded wall on plane yz
- - `zxGrid`, [True], show a gridded wall on plane zx
- - `zxGrid2`, [False], show zx plane on opposite side of the bounding box
- - `xyGridTransparent` [False], make grid plane completely transparent
- - `xyGrid2Transparent` [False], make grid plane completely transparent on opposite side box
- - `xyPlaneColor`, ['gray'], color of the plane
- - `xyGridColor`, ['gray'], grid line color
- - `xyAlpha`, [0.15], grid plane opacity
- - `showTicks`, [True], show major ticks
- - `xTitlePosition`, [0.32], title fractional positions along axis
- - `xTitleOffset`, [0.05], title fractional offset distance from axis line
- - `xTitleJustify`, ["top-right"], title justification
- - `xTitleRotation`, [0], add a rotation of the axis title
- - `xLineColor`, [automatic], color of the x-axis
- - `xTitleColor`, [automatic], color of the axis title
- - `xTitleBackfaceColor`, [None], color of axis title on its backface
- - `xTitleSize`, [0.025], size of the axis title
- - `xHighlightZero`, [True], draw a line highlighting zero position if in range
- - `xHighlightZeroColor`, [automatic], color of the line highlighting the zero position
- - `xTickRadius`, [0.005], radius of the major ticks
- - `xTickThickness`, [0.0025], thickness of the major ticks along their axis
- - `xTickColor`, [automatic], color of major ticks
- - `xMinorTicks`, [1], number of minor ticks between two major ticks
- - `tipSize`, [0.01], size of the arrow tip
- - `xLabelPrecision`, [2], nr. of significative digits to be shown
- - `xLabelSize`, [0.015], size of the numeric labels along axis
- - `xLabelOffset`, [0.025], offset of numeric labels
+ Axes type-1 can be fully customized by passing a dictionary ``axes=dict()`` where:
+
+ - `xtitle`, ['x'], x-axis title text.
+ - `ytitle`, ['y'], y-axis title text.
+ - `ztitle`, ['z'], z-axis title text.
+ - `numberOfDivisions`, [automatic], approximate number of divisions on the longest axis
+ - `axesLineWidth`, [1], width of the axes lines
+ - `gridLineWidth`, [1], width of the grid lines
+ - `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
+ - `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
+ - `titleDepth`, [0], extrusion fractional depth of title text
+ - `xyGrid`, [True], show a gridded wall on plane xy
+ - `yzGrid`, [True], show a gridded wall on plane yz
+ - `zxGrid`, [True], show a gridded wall on plane zx
+ - `zxGrid2`, [False], show zx plane on opposite side of the bounding box
+ - `xyGridTransparent` [False], make grid plane completely transparent
+ - `xyGrid2Transparent` [False], make grid plane completely transparent on opposite side box
+ - `xyPlaneColor`, ['gray'], color of the plane
+ - `xyGridColor`, ['gray'], grid line color
+ - `xyAlpha`, [0.15], grid plane opacity
+ - `showTicks`, [True], show major ticks
+ - `xTitlePosition`, [0.32], title fractional positions along axis
+ - `xTitleOffset`, [0.05], title fractional offset distance from axis line
+ - `xTitleJustify`, ["top-right"], title justification
+ - `xTitleRotation`, [0], add a rotation of the axis title
+ - `xLineColor`, [automatic], color of the x-axis
+ - `xTitleColor`, [automatic], color of the axis title
+ - `xTitleBackfaceColor`, [None], color of axis title on its backface
+ - `xTitleSize`, [0.025], size of the axis title
+ - `xHighlightZero`, [True], draw a line highlighting zero position if in range
+ - `xHighlightZeroColor`, [automatic], color of the line highlighting the zero position
+ - `xTickRadius`, [0.005], radius of the major ticks
+ - `xTickThickness`, [0.0025], thickness of the major ticks along their axis
+ - `xTickColor`, [automatic], color of major ticks
+ - `xMinorTicks`, [1], number of minor ticks between two major ticks
+ - `tipSize`, [0.01], size of the arrow tip
+ - `xPositionsAndLabels` [], assign custom tick positions and labels [(pos1, label1), ...]
+ - `xLabelPrecision`, [2], nr. of significative digits to be shown
+ - `xLabelSize`, [0.015], size of the numeric labels along axis
+ - `xLabelOffset`, [0.025], offset of numeric labels
+ - `limitRatio`, [0.04], below this ratio don't plot small axis
: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
@@ -831,7 +832,7 @@ def move(self, u=None, deltas=None):
movedpts = coords + deltas
if movedpts.shape[1] == 2: #2d
movedpts = np.c_[movedpts, np.zeros(movedpts.shape[0])]
- self.polydata(False).GetPoints().SetData(numpy_to_vtk(movedpts))
+ self.polydata(False).GetPoints().SetData(numpy_to_vtk(np.ascontiguousarray(movedpts)))
self._polydata.GetPoints().Modified()
diff --git a/vtkplotter/mesh.py b/vtkplotter/mesh.py
index 85e56f21..43e20ca6 100644
--- a/vtkplotter/mesh.py
+++ b/vtkplotter/mesh.py
@@ -284,21 +284,6 @@ def _update(self, polydata):
self._mapper.Modified()
return self
- def getPoints(self, transformed=True, copy=False):
- """Obsolete, use points() instead."""
- colors.printc("WARNING: getPoints() is obsolete, use points() instead.", box='=', c=1)
- return self.points(transformed=transformed, copy=copy)
-
- def setPoints(self, pts):
- """Obsolete, use points(pts) instead."""
- colors.printc("WARNING: setPoints(pts) is obsolete, use points(pts) instead.", box='=', c=1)
- return self.points(pts)
-
- def coordinates(self, transformed=True, copy=False):
- """Obsolete, use points() instead."""
- colors.printc("WARNING: coordinates() is obsolete, use points() instead.", box='=', c=1)
- return self.points(transformed=transformed, copy=copy)
-
def points(self, pts=None, transformed=True, copy=False):
"""
Set/Get the vertex coordinates of the mesh.
@@ -310,18 +295,27 @@ def points(self, pts=None, transformed=True, copy=False):
:param bool copy: if `False` return the reference to the points
so that they can be modified in place, otherwise a copy is built.
"""
- if pts is None: # getter
+ if pts is None: ### getter
+
poly = self.polydata(transformed)
- if copy:
- return np.array(vtk_to_numpy(poly.GetPoints().GetData()))
+ vpts = poly.GetPoints()
+ if vpts:
+ if copy:
+ return np.array(vtk_to_numpy(vpts.GetData()))
+ else:
+ return vtk_to_numpy(vpts.GetData())
else:
- return vtk_to_numpy(poly.GetPoints().GetData())
+ return np.array([])
elif (utils.isSequence(pts) and not utils.isSequence(pts[0])) or isinstance(pts, (int, np.integer)):
#passing a list of indices or a single index
return vtk_to_numpy(self.polydata(transformed).GetPoints().GetData())[pts]
- else: # setter
+ else: ### setter
+
+ if len(pts) == 3 and len(pts[0]) != 3:
+ # assume plist is in the format [all_x, all_y, all_z]
+ pts = np.stack((pts[0], pts[1], pts[2]), axis=1)
vpts = self._polydata.GetPoints()
vpts.SetData(numpy_to_vtk(np.ascontiguousarray(pts), deep=True))
self._polydata.GetPoints().Modified()
@@ -329,6 +323,7 @@ def points(self, pts=None, transformed=True, copy=False):
self.PokeMatrix(vtk.vtkMatrix4x4())
return self
+
def faces(self):
"""Get cell polygonal connectivity ids as a python ``list``.
The output format is: [[id0 ... idn], [id0 ... idm], etc].
@@ -358,6 +353,30 @@ def faces(self):
return conn # cannot always make a numpy array of it!
+ def lines(self):
+ """Get lines connectivity ids as a numpy array."""
+ #Get cell connettivity ids as a 1D array. The vtk format is:
+ # [nids1, id0 ... idn, niids2, id0 ... idm, etc].
+ arr1d = vtk_to_numpy(self.polydata(False).GetLines().GetData())
+ return arr1d
+
+ # obsolete stuff:
+ def getPoints(self, transformed=True, copy=False):
+ """Obsolete, use points() instead."""
+ colors.printc("WARNING: getPoints() is obsolete, use points() instead.", box='=', c=1)
+ return self.points(transformed=transformed, copy=copy)
+
+ def setPoints(self, pts):
+ """Obsolete, use points(pts) instead."""
+ colors.printc("WARNING: setPoints(pts) is obsolete, use points(pts) instead.", box='=', c=1)
+ return self.points(pts)
+
+ def coordinates(self, transformed=True, copy=False):
+ """Obsolete, use points() instead."""
+ colors.printc("WARNING: coordinates() is obsolete, use points() instead.", box='=', c=1)
+ return self.points(transformed=transformed, copy=copy)
+
+
def addScalarBar(self,
pos=(0.8,0.05),
title="",
@@ -463,7 +482,7 @@ def texture(self, tname,
return self
if tcoords.shape[1] != 2:
colors.printc('Error in texture(): vector must have 2 components', c=1)
- tarr = numpy_to_vtk(tcoords)
+ tarr = numpy_to_vtk(np.ascontiguousarray(tcoords), deep=True)
tarr.SetName('TCoordinates')
pd.GetPointData().SetTCoords(tarr)
pd.GetPointData().Modified()
@@ -1029,7 +1048,7 @@ def distanceToMesh(self, mesh, signed=False, negate=False):
def clone(self, transformed=True):
"""
- Clone a ``Mesh(vtkActor)`` and make an exact copy of it.
+ Clone a ``Mesh`` object to make an exact copy of it.
:param transformed: if `False` ignore any previous transformation applied to the mesh.
@@ -1039,14 +1058,21 @@ def clone(self, transformed=True):
polyCopy = vtk.vtkPolyData()
polyCopy.DeepCopy(poly)
- cloned = Mesh()
+ cloned = Mesh(polyCopy)
cloned._polydata = polyCopy
- cloned._mapper.SetInputData(polyCopy)
- cloned._mapper.SetScalarVisibility(self._mapper.GetScalarVisibility())
- cloned._mapper.SetScalarRange(self._mapper.GetScalarRange())
pr = vtk.vtkProperty()
pr.DeepCopy(self.GetProperty())
cloned.SetProperty(pr)
+
+ cloned._mapper.SetScalarVisibility(self._mapper.GetScalarVisibility())
+ cloned._mapper.SetScalarRange(self._mapper.GetScalarRange())
+ cloned._mapper.SetColorMode(self._mapper.GetColorMode())
+ cloned._mapper.SetUseLookupTableScalarRange(self._mapper.GetUseLookupTableScalarRange())
+ cloned._mapper.SetScalarMode(self._mapper.GetScalarMode())
+ lut = self._mapper.GetLookupTable()
+ if lut:
+ cloned._mapper.SetLookupTable(lut)
+
cloned.base = self.base
cloned.top = self.top
if self.trail:
@@ -1260,7 +1286,7 @@ def crop(self, top=None, bottom=None, right=None, left=None, front=None, back=No
self._update(clipper.GetOutput())
return self
- def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False):
+ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), returnCut=False):
"""
Cut the mesh with the plane defined by a point and a normal.
@@ -1295,7 +1321,7 @@ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False):
clipper = vtk.vtkClipPolyData()
clipper.SetInputData(poly)
clipper.SetClipFunction(plane)
- if showcut:
+ if returnCut:
clipper.GenerateClippedOutputOn()
else:
clipper.GenerateClippedOutputOff()
@@ -1305,15 +1331,12 @@ def cutWithPlane(self, origin=(0, 0, 0), normal=(1, 0, 0), showcut=False):
self._update(clipper.GetOutput()).computeNormals()
- if showcut:
- from vtkplotter.assembly import Assembly
+ if returnCut:
c = self.GetProperty().GetColor()
cpoly = clipper.GetClippedOutput()
- restmesh = Mesh(cpoly, c, 0.05).wireframe(True)
+ restmesh = Mesh(cpoly, c, 0.1).wireframe(True)
restmesh.SetUserMatrix(self.GetMatrix())
- asse = Assembly([self, restmesh])
- self = asse
- return asse
+ return restmesh
else:
return self
@@ -2131,7 +2154,8 @@ def normals(self, cells=False):
vtknormals = self.polydata().GetCellData().GetNormals()
else:
vtknormals = self.polydata().GetPointData().GetNormals()
-
+ if not vtknormals:
+ return np.array([])
return vtk_to_numpy(vtknormals)
def polydata(self, transformed=True):
diff --git a/vtkplotter/plot2d.py b/vtkplotter/plot2d.py
index e990f91a..0457c3ea 100644
--- a/vtkplotter/plot2d.py
+++ b/vtkplotter/plot2d.py
@@ -24,9 +24,12 @@
"cornerPlot",
"cornerHistogram",
"histogram",
+ "histogram2D",
"hexHistogram",
"polarHistogram",
"polarPlot",
+ "sphericPlot",
+ "quiverPlot",
"donutPlot",
]
@@ -49,21 +52,26 @@ def plotxy(
titleSize=None,
ec=None,
lc="k",
+ la=1,
lw=2,
- line=True,
+ line=False,
dashed=False,
splined=False,
- marker=None,
+ errorBand=False,
+ marker='.',
ms=None,
mc=None,
ma=None,
):
- """Draw a 2D plot of variable x vs y.
+ """Draw a 2D line plot, or scatter plot, of variable x vs variable y.
:param list data: input format can be [allx, ally] or [(x1,y1), (x2,y2), ...]
:param list xerrors: set uncertainties for the x variable, shown as error bars.
:param list yerrors: set uncertainties for the y variable, shown as error bars.
+ :param bool errorBand: represent errors on y as a filled error band.
+ Use ``ec`` keyword to modify its color.
+
:param list xlimits: set limits to the range for the x variable
:param list ylimits: set limits to the range for the y variable
:param float xscale: set scaling factor in x. Default is 1.
@@ -80,6 +88,7 @@ def plotxy(
:param float titleSize: size of title
:param str ec: color of error bar, by default the same as marker color
:param str lc: color of line
+ :param float la: transparency of line
:param float lw: width of line
:param bool line: join points with line
:param bool dashed: use a dashed line style
@@ -90,31 +99,54 @@ def plotxy(
:param float ma: opacity of marker
|plotxy| |plotxy.py|_
+
+ |plot_errband| |plot_errband.py|_
+
+ |scatter2| |scatter2.py|_
+
+ |scatter3| |scatter3.py|_
"""
if len(data) == 2 and len(data[0])>1 and len(data[0]) == len(data[1]):
#format is [allx, ally], convert it:
data = np.c_[data[0], data[1]]
+ # crop arrays in x and y
if xlimits is not None:
- cdata = []
+ cdata, cxerrors, cyerrors = [], [], []
x0lim = xlimits[0]
x1lim = xlimits[1]
- for d in data:
- if d[0] > x0lim and d[0] < x1lim:
+ for i,d in enumerate(data):
+ if d[0] >= x0lim and d[0] <= x1lim:
cdata.append(d)
+ if xerrors is not None:
+ cxerrors.append(xerrors[i])
+ if yerrors is not None:
+ cyerrors.append(yerrors[i])
data = cdata
+ if xerrors is not None:
+ xerrors = np.array(cxerrors)
+ if yerrors is not None:
+ yerrors = np.array(cyerrors)
if not len(data):
colors.printc("Error in plotxy(): no points within xlimits", c=1)
return None
if ylimits is not None:
- cdata = []
+ cdata, cxerrors, cyerrors = [], [], []
y0lim = ylimits[0]
y1lim = ylimits[1]
- for d in data:
- if d[1] > y0lim and d[1] < y1lim:
+ for i,d in enumerate(data):
+ if d[1] >= y0lim and d[1] <= y1lim:
cdata.append(d)
+ if xerrors is not None:
+ cxerrors.append(xerrors[i])
+ if yerrors is not None:
+ cyerrors.append(yerrors[i])
data = cdata
+ if xerrors is not None:
+ xerrors = np.array(cxerrors)
+ if yerrors is not None:
+ yerrors = np.array(cyerrors)
if not len(data):
colors.printc("Error in plotxy(): no points within ylimits", c=1)
return None
@@ -145,33 +177,59 @@ def plotxy(
acts = []
if dashed:
- l = shapes.DashedLine(data, lw=lw, spacing=20)
+ l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw, spacing=20)
acts.append(l)
elif splined:
- l = shapes.KSpline(data).lw(lw).c(lc)
+ l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
acts.append(l)
elif line:
- l = shapes.Line(data, lw=lw, c=lc)
+ l = shapes.Line(data, lw=lw, c=lc, alpha=la)
acts.append(l)
+
if marker:
- if ms is None:
- ms = (x1 - x0) / 75.0
+
+ pts = shapes.Points(data)
if mc is None:
mc = lc
- mk = shapes.Marker(marker, s=ms, alpha=ma)
- pts = shapes.Points(data)
- marked = shapes.Glyph(pts, glyphObj=mk, c=mc)
+
+ if utils.isSequence(ms): ### variable point size
+ mk = shapes.Marker(marker, s=1)
+ msv = np.zeros_like(pts.points())
+ msv[:,0] = ms
+ marked = shapes.Glyph(pts, glyphObj=mk, c=mc,
+ orientationArray=msv, scaleByVectorSize=True)
+
+ else: ### fixed point size
+
+ if ms is None:
+ ms = np.sqrt((x1-x0)**2+(y1-y0)**2) / 100.0
+ #print('automatic ms =', ms)
+
+ if utils.isSequence(mc):
+ #print('mc is sequence')
+ mk = shapes.Marker(marker, s=ms)
+ msv = np.zeros_like(pts.points())
+ msv[:,0] = 1
+ marked = shapes.Glyph(pts, glyphObj=mk, c=mc,
+ orientationArray=msv, scaleByVectorSize=True)
+ else:
+ #print('mc is fixed color')
+ mk = shapes.Marker(marker, s=ms)
+ marked = shapes.Glyph(pts, glyphObj=mk, c=mc)
+
+ marked.lighting('ambient').alpha(ma)
acts.append(marked)
+
if ec is None:
if mc is not None:
ec = mc
else:
ec = lc
- offs = (x1-x0)/1000
+ offs = (x1-x0)/1000
- if yerrors is not None:
+ if yerrors is not None and not errorBand:
if len(yerrors) != len(data):
colors.printc("Error in plotxy(yerrors=...): mismatched array length.", c=1)
return None
@@ -183,7 +241,7 @@ def plotxy(
myerrs = merge(errs).c(ec).lw(lw).alpha(alpha)
acts.append(myerrs)
- if xerrors is not None:
+ if xerrors is not None and not errorBand:
if len(xerrors) != len(data):
colors.printc("Error in plotxy(xerrors=...): mismatched array length.", c=1)
return None
@@ -195,6 +253,21 @@ def plotxy(
mxerrs = merge(errs).c(ec).lw(lw).alpha(alpha)
acts.append(mxerrs)
+ if errorBand:
+ epsy = np.zeros_like(data)
+ epsy[:,1] = yerrors/2*yscale
+ data3dup = data+epsy
+ data3dup = np.c_[data3dup, np.zeros_like(yerrors)]
+ data3d_down = data-epsy
+ data3d_down = np.c_[data3d_down, np.zeros_like(yerrors)]
+ band = shapes.Ribbon(data3dup, data3d_down).z(-offs)
+ if ec is None:
+ band.c(lc)
+ else:
+ band.c(ec)
+ band.alpha(la)
+ acts.append(band)
+
x0lim = x0
x1lim = x1
y0lim = y0*yscale
@@ -345,6 +418,144 @@ def cornerHistogram(
return plot
+def fxy(
+ z="sin(3*x)*log(x-y)/3",
+ x=(0, 3),
+ y=(0, 3),
+ zlimits=(None, None),
+ showNan=True,
+ zlevels=10,
+ c="b",
+ bc="aqua",
+ alpha=1,
+ texture="paper",
+ res=(100, 100),
+):
+ """
+ Build a surface representing the function :math:`f(x,y)` specified as a string
+ or as a reference to an external function.
+
+ :param float x: x range of values.
+ :param float y: y range of values.
+ :param float zlimits: limit the z range of the independent variable.
+ :param int zlevels: will draw the specified number of z-levels contour lines.
+ :param bool showNan: show where the function does not exist as red points.
+ :param list res: resolution in x and y.
+
+ |fxy| |fxy.py|_
+
+ Function is: :math:`f(x,y)=\sin(3x) \cdot \log(x-y)/3` in range :math:`x=[0,3], y=[0,3]`.
+ """
+ if isinstance(z, str):
+ try:
+ z = z.replace("math.", "").replace("np.", "")
+ namespace = locals()
+ code = "from math import*\ndef zfunc(x,y): return " + z
+ exec(code, namespace)
+ z = namespace["zfunc"]
+ except:
+ colors.printc("Syntax Error in fxy()", c=1)
+ return None
+
+ ps = vtk.vtkPlaneSource()
+ ps.SetResolution(res[0], res[1])
+ ps.SetNormal([0, 0, 1])
+ ps.Update()
+ poly = ps.GetOutput()
+ dx = x[1] - x[0]
+ dy = y[1] - y[0]
+ todel, nans = [], []
+
+ for i in range(poly.GetNumberOfPoints()):
+ px, py, _ = poly.GetPoint(i)
+ xv = (px + 0.5) * dx + x[0]
+ yv = (py + 0.5) * dy + y[0]
+ try:
+ zv = z(xv, yv)
+ except:
+ zv = 0
+ todel.append(i)
+ nans.append([xv, yv, 0])
+ poly.GetPoints().SetPoint(i, [xv, yv, zv])
+
+ if len(todel):
+ cellIds = vtk.vtkIdList()
+ poly.BuildLinks()
+ for i in todel:
+ poly.GetPointCells(i, cellIds)
+ for j in range(cellIds.GetNumberOfIds()):
+ poly.DeleteCell(cellIds.GetId(j)) # flag cell
+ poly.RemoveDeletedCells()
+ cl = vtk.vtkCleanPolyData()
+ cl.SetInputData(poly)
+ cl.Update()
+ poly = cl.GetOutput()
+
+ if not poly.GetNumberOfPoints():
+ colors.printc("Function is not real in the domain", c=1)
+ return None
+
+ if zlimits[0]:
+ tmpact1 = Mesh(poly)
+ a = tmpact1.cutWithPlane((0, 0, zlimits[0]), (0, 0, 1))
+ poly = a.polydata()
+ if zlimits[1]:
+ tmpact2 = Mesh(poly)
+ a = tmpact2.cutWithPlane((0, 0, zlimits[1]), (0, 0, -1))
+ poly = a.polydata()
+
+ if c is None:
+ elev = vtk.vtkElevationFilter()
+ elev.SetInputData(poly)
+ elev.Update()
+ poly = elev.GetOutput()
+
+ mesh = Mesh(poly, c, alpha).computeNormals().lighting("plastic")
+ if c is None:
+ mesh.getPointArray("Elevation")
+
+ if bc:
+ mesh.bc(bc)
+
+ mesh.texture(texture)
+ #mesh.mapper().SetResolveCoincidentTopologyToPolygonOffset()
+ #mesh.mapper().SetResolveCoincidentTopologyPolygonOffsetParameters(0.1, 0.1)
+
+ acts = [mesh]
+ if zlevels:
+ elevation = vtk.vtkElevationFilter()
+ elevation.SetInputData(poly)
+ bounds = poly.GetBounds()
+ elevation.SetLowPoint(0, 0, bounds[4])
+ elevation.SetHighPoint(0, 0, bounds[5])
+ elevation.Update()
+ bcf = vtk.vtkBandedPolyDataContourFilter()
+ bcf.SetInputData(elevation.GetOutput())
+ bcf.SetScalarModeToValue()
+ bcf.GenerateContourEdgesOn()
+ bcf.GenerateValues(zlevels, elevation.GetScalarRange())
+ bcf.Update()
+ zpoly = bcf.GetContourEdgesOutput()
+ zbandsact = Mesh(zpoly, "k", alpha).lw(2).lighting('ambient')
+ acts.append(zbandsact)
+
+ if showNan and len(todel):
+ bb = mesh.GetBounds()
+ if bb[4] <= 0 and bb[5] >= 0:
+ zm = 0.0
+ else:
+ zm = (bb[4] + bb[5]) / 2
+ nans = np.array(nans) + [0, 0, zm]
+ nansact = shapes.Points(nans, r=2, c="red", alpha=alpha)
+ nansact.GetProperty().RenderPointsAsSpheresOff()
+ acts.append(nansact)
+
+ if len(acts) > 1:
+ return Assembly(acts)
+ else:
+ return mesh
+
+
def histogram(
values,
xtitle="",
@@ -363,7 +574,7 @@ def histogram(
errors=False,
):
"""
- Build a histogram from a list of values in n bins.
+ Build a 1D histogram from a list of values in n bins.
The resulting object is an ``Assembly``.
:param int bins: number of bins.
@@ -429,9 +640,61 @@ def histogram(
if yscale is None:
yscale = 10 / np.sum(fs) * (maxe - mine)
asse.scale([1, yscale, 1])
+ asse.base = np.array([0, 0, 0])
+ asse.top = np.array([0, 0, 1])
return asse
+def histogram2D(xvalues, yvalues=None,
+ xtitle="",
+ ytitle="",
+ bins=(20, 20),
+ cmap="viridis",
+ alpha=1,
+ lw=0.1,
+ scalarbar=True,
+):
+ """Build a 2D Histogram. Returns a ``Mesh`` object.
+
+ Single input data format [(x1,y1), (x2,y2) ...] is also accepted.
+
+ |histogram2D| |histogram2D.py|_
+ """
+ if xtitle:
+ from vtkplotter import settings
+ settings.xtitle = xtitle
+ if ytitle:
+ from vtkplotter import settings
+ settings.ytitle = ytitle
+
+ if isinstance(xvalues, Mesh):
+ xvalues = xvalues.points()
+
+ if yvalues is None:
+ # assume [(x1,y1), (x2,y2) ...] format
+ yvalues = xvalues[:,1]
+ xvalues = xvalues[:,0]
+
+ xmin, xmax = np.min(xvalues), np.max(xvalues)
+ ymin, ymax = np.min(yvalues), np.max(yvalues)
+ dx, dy = xmax - xmin, ymax - ymin
+
+ xedges = np.linspace(xmin, xmax, num=bins[0]+1, endpoint=True)
+ yedges = np.linspace(ymin, ymax, num=bins[1]+1, endpoint=True)
+ H, xedges, yedges = np.histogram2d(xvalues, yvalues, bins=(xedges, yedges))
+
+ g = shapes.Grid(pos=[(xmin+xmax)/2, (ymin+ymax)/2, 0],
+ sx=dx, sy=dy, resx=bins[0], resy=bins[1])
+ g.alpha(alpha).lw(lw).wireframe(0)
+ g.cellColors(np.ravel(H.T, order='C'), cmap=cmap)
+ if scalarbar:
+ g.addScalarBar()
+ g.base = np.array([0, 0, 0])
+ g.top = np.array([0, 0, 1])
+ return g
+
+
+
def hexHistogram(
xvalues,
yvalues,
@@ -457,15 +720,12 @@ def hexHistogram(
"""
if xtitle:
from vtkplotter import settings
-
settings.xtitle = xtitle
if ytitle:
from vtkplotter import settings
-
settings.ytitle = ytitle
if ztitle:
from vtkplotter import settings
-
settings.ztitle = ztitle
xmin, xmax = np.min(xvalues), np.max(xvalues)
@@ -544,6 +804,8 @@ def hexHistogram(
asse = Assembly(hexs)
asse.SetScale(1.2 / n * dx, 1 / m * dy, norm / binmax * (dx + dy) / 4)
asse.SetPosition(xmin, ymin, 0)
+ asse.base = np.array([0, 0, 0])
+ asse.top = np.array([0, 0, 1])
return asse
@@ -936,137 +1198,135 @@ def polarPlot(
return rh
-def fxy(
- z="sin(3*x)*log(x-y)/3",
- x=(0, 3),
- y=(0, 3),
- zlimits=(None, None),
- showNan=True,
- zlevels=10,
- c="b",
- bc="aqua",
- alpha=1,
- texture="paper",
- res=(100, 100),
-):
- """
- Build a surface representing the function :math:`f(x,y)` specified as a string
- or as a reference to an external function.
+def sphericPlot(rfunc, normalize=True, res=25,
+ scalarbar=True, c='grey', alpha=0.1, cmap='jet'):
+ """Surface plotting in spherical coordinates.
- :param float x: x range of values.
- :param float y: y range of values.
- :param float zlimits: limit the z range of the independent variable.
- :param int zlevels: will draw the specified number of z-levels contour lines.
- :param bool showNan: show where the function does not exist as red points.
- :param list res: resolution in x and y.
+ Return an ``Assembly`` of 2 objects, the unit grid
+ sphere (in wireframe representation) and the surface `rho(theta, phi)`.
- |fxy| |fxy.py|_
+ :param function rfunc: handle to a user defined function.
+ :param bool normalize: scale surface to fit inside the unit sphere
+ :param int res: grid resolution
+ :param bool scalarbar: add a 3D scalarbar to the plot for radius
+ :param c: color of the unit grid
+ :param alpha: transparency of the unit grid
+ :param str cmap: color map of the surface
- Function is: :math:`f(x,y)=\sin(3x) \cdot \log(x-y)/3` in range :math:`x=[0,3], y=[0,3]`.
+ |sphericPlot| |sphericPlot.py|_
"""
- if isinstance(z, str):
- try:
- z = z.replace("math.", "").replace("np.", "")
- namespace = locals()
- code = "from math import*\ndef zfunc(x,y): return " + z
- exec(code, namespace)
- z = namespace["zfunc"]
- except:
- colors.printc("Syntax Error in fxy()", c=1)
- return None
+ sg = shapes.SphericGrid(res=res)
+ sg.alpha(alpha).c(c).wireframe()
- ps = vtk.vtkPlaneSource()
- ps.SetResolution(res[0], res[1])
- ps.SetNormal([0, 0, 1])
- ps.Update()
- poly = ps.GetOutput()
- dx = x[1] - x[0]
- dy = y[1] - y[0]
- todel, nans = [], []
+ cgpts = sg.points()
+ x, y, z = cgpts[:,0], cgpts[:,1], cgpts[:,2]
+ r, theta, phi = utils.cart2spher(x, y, z)
- for i in range(poly.GetNumberOfPoints()):
- px, py, _ = poly.GetPoint(i)
- xv = (px + 0.5) * dx + x[0]
- yv = (py + 0.5) * dy + y[0]
+ newr, inans = [], []
+ for i in range(len(r)):
try:
- zv = z(xv, yv)
+ ri = rfunc(theta[i], phi[i])
+ if np.isnan(ri):
+ inans.append(i)
+ newr.append(1)
+ else:
+ newr.append(ri)
except:
- zv = 0
- todel.append(i)
- nans.append([xv, yv, 0])
- poly.GetPoints().SetPoint(i, [xv, yv, zv])
+ inans.append(i)
+ newr.append(1)
- if len(todel):
- cellIds = vtk.vtkIdList()
- poly.BuildLinks()
- for i in todel:
- poly.GetPointCells(i, cellIds)
- for j in range(cellIds.GetNumberOfIds()):
- poly.DeleteCell(cellIds.GetId(j)) # flag cell
- poly.RemoveDeletedCells()
- cl = vtk.vtkCleanPolyData()
- cl.SetInputData(poly)
- cl.Update()
- poly = cl.GetOutput()
+ newr = np.array(newr)
+ if normalize:
+ newr = newr/np.max(newr)
+ newr[inans] = 1
- if not poly.GetNumberOfPoints():
- colors.printc("Function is not real in the domain", c=1)
- return None
+ nanpts = []
+ if len(inans):
+ redpts = utils.spher2cart(newr[inans], theta[inans], phi[inans])
+ nanpts.append(shapes.Points(redpts, r=3, c='r'))
- if zlimits[0]:
- tmpact1 = Mesh(poly)
- a = tmpact1.cutWithPlane((0, 0, zlimits[0]), (0, 0, 1))
- poly = a.polydata()
- if zlimits[1]:
- tmpact2 = Mesh(poly)
- a = tmpact2.cutWithPlane((0, 0, zlimits[1]), (0, 0, -1))
- poly = a.polydata()
+ pts = utils.spher2cart(newr, theta, phi)
- if c is None:
- elev = vtk.vtkElevationFilter()
- elev.SetInputData(poly)
- elev.Update()
- poly = elev.GetOutput()
+ ssurf = sg.clone().points(pts)
+ if len(inans):
+ ssurf.deletePoints(inans)
- mesh = Mesh(poly, c, alpha).computeNormals().lighting("plastic")
- if c is None:
- mesh.getPointArray("Elevation")
+ ssurf.alpha(1).wireframe(0)
- if bc:
- mesh.bc(bc)
+ if scalarbar:
+ xm = np.max([np.max(pts[0]), 1])
+ ym = np.max([np.abs(np.max(pts[1])), 1])
+ ssurf.mapper().SetScalarRange(np.min(newr), np.max(newr))
+ sb3d = ssurf.addScalarBar3D(cmap=cmap, sx=xm*0.07, sy=ym)
+ sb3d.rotateX(90).pos(xm*1.1, 0, -0.5)
+ else:
+ sb3d = None
- mesh.texture(texture)
+ ssurf.pointColors(newr, cmap=cmap)
+ ssurf.computeNormals()
+ sg.pickable(False)
+ return Assembly([ssurf, sg] + nanpts + [sb3d])
- acts = [mesh]
- if zlevels:
- elevation = vtk.vtkElevationFilter()
- elevation.SetInputData(poly)
- bounds = poly.GetBounds()
- elevation.SetLowPoint(0, 0, bounds[4])
- elevation.SetHighPoint(0, 0, bounds[5])
- elevation.Update()
- bcf = vtk.vtkBandedPolyDataContourFilter()
- bcf.SetInputData(elevation.GetOutput())
- bcf.SetScalarModeToValue()
- bcf.GenerateContourEdgesOn()
- bcf.GenerateValues(zlevels, elevation.GetScalarRange())
- bcf.Update()
- zpoly = bcf.GetContourEdgesOutput()
- zbandsact = Mesh(zpoly, "k", alpha).lw(0.5)
- acts.append(zbandsact)
- if showNan and len(todel):
- bb = mesh.GetBounds()
- if bb[4] <= 0 and bb[5] >= 0:
- zm = 0.0
- else:
- zm = (bb[4] + bb[5]) / 2
- nans = np.array(nans) + [0, 0, zm]
- nansact = shapes.Points(nans, r=2, c="red", alpha=alpha)
- nansact.GetProperty().RenderPointsAsSpheresOff()
- acts.append(nansact)
+def quiverPlot(points, vectors,
+ cmap='jet', alpha=1,
+ shaftLength=0.8,
+ shaftWidth=0.05,
+ headLength=0.25,
+ headWidth=0.2,
+ fill=True,
+ scale=1
+ ):
+ """Quiver Plot, display `vectors` at `points` locations.
- if len(acts) > 1:
- return Assembly(acts)
+ Color can be specified as a colormap which maps the size of the arrows.
+
+ :param float shaftLength: fractional shaft length
+ :param float shaftWidth: fractional shaft width
+ :param float headLength: fractional head length
+ :param float headWidth: fractional head width
+ :param bool fill: if False only generate the outline
+
+ :param float scale: apply a rescaling factor to the length
+
+ |quiverPlot| |quiverPlot.py|_
+ """
+ if isinstance(points, Mesh):
+ points = points.points()
else:
- return mesh
+ points = np.array(points)
+ vectors = np.array(vectors)/2
+
+ spts = points - vectors
+ epts = points + vectors
+
+ arrs2d = shapes.Arrows2D(spts, epts, c=cmap,
+ shaftLength=shaftLength,
+ shaftWidth=shaftWidth,
+ headLength=headLength,
+ headWidth=headWidth,
+ fill=fill,
+ scale=scale,
+ alpha=alpha,
+ )
+ arrs2d.pickable(False)
+ return arrs2d
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py
index 0d31be0a..34655a4b 100644
--- a/vtkplotter/plotter.py
+++ b/vtkplotter/plotter.py
@@ -74,7 +74,6 @@ def show(*actors, **options):
- `gridLineWidth`, [1], width of the grid lines
- `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
- `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
- - `enableLastLabel`, [False], show last numeric label on axes
- `titleDepth`, [0], extrusion fractional depth of title text
- `xyGrid`, [True], show a gridded wall on plane xy
- `yzGrid`, [True], show a gridded wall on plane yz
@@ -101,6 +100,7 @@ def show(*actors, **options):
- `xTickColor`, [automatic], color of major ticks
- `xMinorTicks`, [1], number of minor ticks between two major ticks
- `tipSize`, [0.01], size of the arrow tip
+ - `xPositionsAndLabels` [], assign custom tick positions and labels [(pos1, label1), ...]
- `xLabelPrecision`, [2], nr. of significative digits to be shown
- `xLabelSize`, [0.015], size of the numeric labels along axis
- `xLabelOffset`, [0.025], offset of numeric labels
@@ -195,6 +195,9 @@ def show(*actors, **options):
size = options.pop("size", "auto")
screensize = options.pop("screensize", "auto")
title = options.pop("title", "")
+ #xtitle = options.pop("xtitle", "x")
+ #ytitle = options.pop("ytitle", "y")
+ #ztitle = options.pop("ztitle", "z")
bg = options.pop("bg", "blackboard")
bg2 = options.pop("bg2", None)
axes = options.pop("axes", 4)
@@ -215,7 +218,7 @@ def show(*actors, **options):
if len(options):
for op in options:
- colors.printc("Warning: unknown keyword in shdow():", op, c=5)
+ colors.printc("Warning: unknown keyword in show():", op, c=5)
if len(actors) == 0:
actors = None
@@ -259,6 +262,9 @@ def show(*actors, **options):
interactive=interactive,
offscreen=offscreen,
)
+ #vp.xtitle = xtitle
+ #vp.ytitle = ytitle
+ #vp.ztitle = ztitle
# use _vp_to_return because vp.show() can return a k3d/panel plot
if utils.isSequence(at):
@@ -374,7 +380,7 @@ class Plotter:
- `xtitle`, ['x'], x-axis title text.
- `ytitle`, ['y'], y-axis title text.
- `ztitle`, ['z'], z-axis title text.
- - `numberOfDivisions`, [automatic], number of divisions on the shortest axis
+ - `numberOfDivisions`, [automatic], number of divisions on the longest axis
- `axesLineWidth`, [1], width of the axes lines
- `gridLineWidth`, [1], width of the grid lines
- `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
@@ -519,12 +525,19 @@ def __init__(
self.window = vtk.vtkRenderWindow()
############################################################
- if settings.notebookBackend and settings.notebookBackend != "panel":
+ notebookBackend = settings.notebookBackend
+ if notebookBackend and notebookBackend.lower() == '2d':
+ self.offscreen = True
+ if self.size == "auto":
+ self.size = (900, 700)
+ if (notebookBackend
+ and notebookBackend != "panel"
+ and notebookBackend.lower() != "2d"):
self.interactive = False
self.interactor = None
self.window = None
self.camera = None # let the backend choose
- if size == "auto":
+ if self.size == "auto":
self.size = (1000, 1000)
########################################################
return #################################################
@@ -580,13 +593,15 @@ def __init__(
if isinstance(shape, str):
if '|' in shape:
- if size == "auto": self.size = (800, 1200)
+ if self.size == "auto":
+ self.size = (800, 1200)
n = int(shape.split('|')[0])
m = int(shape.split('|')[1])
rangen = reversed(range(n))
rangem = reversed(range(m))
else:
- if size == "auto": self.size = (1200, 800)
+ if self.size == "auto":
+ self.size = (1200, 800)
m = int(shape.split('/')[0])
n = int(shape.split('/')[1])
rangen = range(n)
@@ -620,6 +635,7 @@ def __init__(
r.SetLightFollowCamera(settings.lightFollowsCamera)
if settings.useFXAA is not None:
r.SetUseFXAA(settings.useFXAA)
+ self.window.SetMultiSamples(settings.multiSamples)
if settings.useDepthPeeling:
r.SetUseDepthPeeling(True)
r.SetMaximumNumberOfPeels(settings.maxNumberOfPeels)
@@ -631,7 +647,7 @@ def __init__(
else:
- if size == "auto": # figure out a reasonable window size
+ if self.size == "auto": # figure out a reasonable window size
f = 1.5
xs = y / f * shape[1] # because y pip install itkwidgets # and if necessary:')
print('> conda install nodejs')
+ elif backend.lower() == '2d':
+ verbose=False
+
elif backend=='panel':
try:
import panel
@@ -317,8 +321,9 @@ def _init():
collectable_actors = []
for f in os.listdir(textures_path):
- textures.append(f.split(".")[0])
- textures.remove("earth")
+ tfn = f.split(".")[0]
+ if 'earth' in tfn: continue
+ textures.append(tfn)
textures = list(sorted(textures))
for f in os.listdir(fonts_path):
diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py
index 7191a675..5c2c4234 100644
--- a/vtkplotter/shapes.py
+++ b/vtkplotter/shapes.py
@@ -27,6 +27,8 @@
"Ribbon",
"Arrow",
"Arrows",
+ "Arrow2D",
+ "Arrows2D",
"FlatArrow",
"Polygon",
"Rectangle",
@@ -36,9 +38,11 @@
"Star",
"Sphere",
"Spheres",
+ "SphericGrid",
"Earth",
"Ellipsoid",
"Grid",
+ "CubicGrid",
"Plane",
"Box",
"Cube",
@@ -167,7 +171,7 @@ def __init__(self, plist, r=5, c="gold", alpha=1):
vgf.SetInputData(src.GetOutput())
vgf.Update()
pd = vgf.GetOutput()
- pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True))
+ pd.GetPoints().SetData(numpy_to_vtk(np.ascontiguousarray(plist), deep=True))
ucols = vtk.vtkUnsignedCharArray()
ucols.SetNumberOfComponents(4)
@@ -223,7 +227,7 @@ def __init__(self, plist, r=5, c="gold", alpha=1):
if n == 1: # passing just one point
pd.GetPoints().SetPoint(0, [0, 0, 0])
else:
- pd.GetPoints().SetData(numpy_to_vtk(plist, deep=True))
+ pd.GetPoints().SetData(numpy_to_vtk(np.ascontiguousarray(plist), deep=True))
Mesh.__init__(self, pd, c, alpha)
self.GetProperty().SetPointSize(r)
if n == 1:
@@ -238,6 +242,8 @@ class Glyph(Mesh):
At each vertex of a mesh, another mesh - a `'glyph'` - is shown with
various orientation options and coloring.
+ The input ``mesh`` can also be a simple list of 2D or 3D coordinates.
+
Color can be specified as a colormap which maps the size of the orientation
vectors in `orientationArray`.
@@ -261,6 +267,10 @@ def __init__(self, mesh, glyphObj, orientationArray=None,
cmap = c
c = None
+ if utils.isSequence(mesh):
+ # create a cloud of points
+ mesh = Points(mesh)
+
if tol:
mesh = mesh.clone().clean(tol)
poly = mesh.polydata()
@@ -458,7 +468,7 @@ def __init__(self, p0, p1=None, c="r", alpha=1, lw=1, dotted=False, res=None):
for i, p in enumerate(p0):
ppoints.InsertPoint(i, p[0], p[1], 0)
else:
- ppoints.SetData(numpy_to_vtk(p0, deep=True))
+ ppoints.SetData(numpy_to_vtk(np.ascontiguousarray(p0), deep=True))
lines = vtk.vtkCellArray() # Create the polyline.
lines.InsertNextCell(len(p0))
for i in range(len(p0)):
@@ -613,6 +623,9 @@ class Spline(Mesh):
|tutorial_spline| |tutorial.py|_
"""
def __init__(self, points, smooth=0.5, degree=2, s=2, res=None):
+
+ if isinstance(points, Mesh): points = points.points()
+
from scipy.interpolate import splprep, splev
if res is None:
res = len(points)*20
@@ -711,7 +724,7 @@ class Tube(Mesh):
def __init__(self, points, r=1, cap=True, c=None, alpha=1, res=12):
ppoints = vtk.vtkPoints() # Generate the polyline
- ppoints.SetData(numpy_to_vtk(points, deep=True))
+ ppoints.SetData(numpy_to_vtk(np.ascontiguousarray(points), deep=True))
lines = vtk.vtkCellArray()
lines.InsertNextCell(len(points))
for i in range(len(points)):
@@ -773,7 +786,7 @@ def __init__(self, line1, line2, c="m", alpha=1, res=(200,5)):
line2 = line2.points()
ppoints1 = vtk.vtkPoints() # Generate the polyline1
- ppoints1.SetData(numpy_to_vtk(line1, deep=True))
+ ppoints1.SetData(numpy_to_vtk(np.ascontiguousarray(line1), deep=True))
lines1 = vtk.vtkCellArray()
lines1.InsertNextCell(len(line1))
for i in range(len(line1)):
@@ -783,7 +796,7 @@ def __init__(self, line1, line2, c="m", alpha=1, res=(200,5)):
poly1.SetLines(lines1)
ppoints2 = vtk.vtkPoints() # Generate the polyline2
- ppoints2.SetData(numpy_to_vtk(line2, deep=True))
+ ppoints2.SetData(numpy_to_vtk(np.ascontiguousarray(line2), deep=True))
lines2 = vtk.vtkCellArray()
lines2.InsertNextCell(len(line2))
for i in range(len(line2)):
@@ -827,36 +840,6 @@ def __init__(self, line1, line2, c="m", alpha=1, res=(200,5)):
self.name = "Ribbon"
-def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1):
- """Build a 2D arrow in 3D space by joining two close lines.
-
- |flatarrow| |flatarrow.py|_
- """
- if isinstance(line1, Mesh): line1 = line1.points()
- if isinstance(line2, Mesh): line2 = line2.points()
-
- sm1, sm2 = np.array(line1[-1]), np.array(line2[-1])
-
- v = (sm1-sm2)/3*tipWidth
- p1 = sm1+v
- p2 = sm2-v
- pm1 = (sm1+sm2)/2
- pm2 = (np.array(line1[-2])+np.array(line2[-2]))/2
- pm12 = pm1-pm2
- tip = pm12/np.linalg.norm(pm12)*np.linalg.norm(v)*3*tipSize/tipWidth + pm1
-
- line1.append(p1)
- line1.append(tip)
- line2.append(p2)
- line2.append(tip)
- resm = max(100, len(line1))
-
- mesh = Ribbon(line1, line2, alpha=alpha, c=c, res=(resm, 1)).phong()
- settings.collectable_actors.pop()
- settings.collectable_actors.append(mesh)
- mesh.name = "FlatArrow"
- return mesh
-
class Arrow(Mesh):
"""
Build a 3D arrow from `startPoint` to `endPoint` of section size `s`,
@@ -954,6 +937,175 @@ def Arrows(startPoints, endPoints=None, s=None, scale=1, c="r", alpha=1, res=12)
return arrg
+class Arrow2D(Mesh):
+ """
+ Build a 2D arrow from `startPoint` to `endPoint`.
+
+ :param float shaftLength: fractional shaft length
+ :param float shaftWidth: fractional shaft width
+ :param float headLength: fractional head length
+ :param float headWidth: fractional head width
+ :param bool fill: if False only generate the outline
+ """
+ def __init__(self, startPoint, endPoint,
+ shaftLength=0.8,
+ shaftWidth=0.09,
+ headLength=None,
+ headWidth=0.2,
+ fill=True,
+ c="r",
+ alpha=1):
+
+ # in case user is passing meshs
+ if isinstance(startPoint, vtk.vtkActor): startPoint = startPoint.GetPosition()
+ if isinstance(endPoint, vtk.vtkActor): endPoint = endPoint.GetPosition()
+ if len(startPoint) == 2:
+ startPoint = [startPoint[0], startPoint[1], 0]
+ if len(endPoint) == 2:
+ endPoint = [endPoint[0], endPoint[1], 0]
+
+ headBase = 1 - headLength
+ if headWidth < shaftWidth:
+ headWidth = shaftWidth
+ if headLength is None or headBase > shaftLength:
+ headBase = shaftLength
+
+ verts = []
+ verts.append([0, -shaftWidth/2, 0])
+ verts.append([shaftLength,-shaftWidth/2, 0])
+ verts.append([headBase, -headWidth/2, 0])
+ verts.append([1,0,0])
+ verts.append([headBase, headWidth/2, 0])
+ verts.append([shaftLength, shaftWidth/2, 0])
+ verts.append([0, shaftWidth/2, 0])
+ if fill:
+ faces = ((0,1,3,5,6), (5,3,4), (1,2,3))
+ poly = utils.buildPolyData(verts, faces)
+ else:
+ lines = ((0,1,2,3,4,5,6,0))
+ poly = utils.buildPolyData(verts, [], lines=lines)
+
+ axis = np.array(endPoint) - np.array(startPoint)
+ length = np.linalg.norm(axis)
+ if length:
+ axis = axis / length
+ theta = np.arccos(axis[2])
+ phi = np.arctan2(axis[1], axis[0])
+ t = vtk.vtkTransform()
+ t.RotateZ(np.rad2deg(phi))
+ t.RotateY(np.rad2deg(theta))
+ t.RotateY(-90) # put it along Z
+ t.Scale(length, length, length)
+ tf = vtk.vtkTransformPolyDataFilter()
+ tf.SetInputData(poly)
+ tf.SetTransform(t)
+ tf.Update()
+
+ Mesh.__init__(self, tf.GetOutput(), c, alpha)
+ self.flat().lighting('ambient')
+ self.DragableOff()
+ self.PickableOff()
+ self.base = np.array(startPoint)
+ self.top = np.array(endPoint)
+ settings.collectable_actors.append(self)
+ self.name = "Arrow2D"
+
+def Arrows2D(startPoints, endPoints=None,
+ shaftLength=0.8,
+ shaftWidth=0.09,
+ headLength=None,
+ headWidth=0.2,
+ fill=True,
+ scale=1,
+ c="r", alpha=1):
+ """
+ Build 2D arrows between two lists of points `startPoints` and `endPoints`.
+ `startPoints` can be also passed in the form ``[[point1, point2], ...]``.
+
+ Color can be specified as a colormap which maps the size of the arrows.
+
+ :param float shaftLength: fractional shaft length
+ :param float shaftWidth: fractional shaft width
+ :param float headLength: fractional head length
+ :param float headWidth: fractional head width
+ :param bool fill: if False only generate the outline
+
+ :param float scale: apply a rescaling factor to the length
+ :param c: color or array of colors, can also be a color map name.
+ :param float alpha: set transparency
+
+ :Example:
+ .. code-block:: python
+
+ from vtkplotter import Grid, Arrows2D
+ g1 = Grid(sx=1, sy=1)
+ g2 = Grid(sx=1.2, sy=1.2).rotateZ(4)
+ arrs2d = Arrows2D(g1, g2, c='jet')
+ arrs2d.show(axes=1, bg='white')
+
+ |quiverPlot|
+ """
+ if isinstance(startPoints, Mesh): startPoints = startPoints.points()
+ if isinstance(endPoints, Mesh): endPoints = endPoints.points()
+ startPoints = np.array(startPoints)
+ if endPoints is None:
+ strt = startPoints[:,0]
+ endPoints = startPoints[:,1]
+ startPoints = strt
+ else:
+ endPoints = np.array(endPoints)
+
+ if headLength is None:
+ headLength = 1 - shaftLength
+
+ arr = Arrow2D((0,0,0), (1,0,0),
+ shaftLength, shaftWidth,
+ headLength, headWidth, fill)
+ pts = Points(startPoints, r=0.001, c=c, alpha=alpha).off()
+
+ orients = (endPoints - startPoints) * scale
+ if orients.shape[1] == 2: # make it 3d
+ orients = np.c_[np.array(orients), np.zeros(len(orients))]
+
+ arrg = Glyph(pts, arr.polydata(False),
+ orientationArray=orients, scaleByVectorSize=True,
+ c=c, alpha=alpha).flat().lighting('ambient')
+ settings.collectable_actors.append(arrg)
+ arrg.name = "Arrows2D"
+ return arrg
+
+
+def FlatArrow(line1, line2, c="m", alpha=1, tipSize=1, tipWidth=1):
+ """Build a 2D arrow in 3D space by joining two close lines.
+
+ |flatarrow| |flatarrow.py|_
+ """
+ if isinstance(line1, Mesh): line1 = line1.points()
+ if isinstance(line2, Mesh): line2 = line2.points()
+
+ sm1, sm2 = np.array(line1[-1]), np.array(line2[-1])
+
+ v = (sm1-sm2)/3*tipWidth
+ p1 = sm1+v
+ p2 = sm2-v
+ pm1 = (sm1+sm2)/2
+ pm2 = (np.array(line1[-2])+np.array(line2[-2]))/2
+ pm12 = pm1-pm2
+ tip = pm12/np.linalg.norm(pm12)*np.linalg.norm(v)*3*tipSize/tipWidth + pm1
+
+ line1.append(p1)
+ line1.append(tip)
+ line2.append(p2)
+ line2.append(tip)
+ resm = max(100, len(line1))
+
+ mesh = Ribbon(line1, line2, alpha=alpha, c=c, res=(resm, 1)).phong()
+ settings.collectable_actors.pop()
+ settings.collectable_actors.append(mesh)
+ mesh.name = "FlatArrow"
+ return mesh
+
+
class Polygon(Mesh):
"""
Build a polygon in the `xy` plane of `nsides` of radius `r`.
@@ -1206,12 +1358,35 @@ def __init__(self, centers, r=1, c="r", alpha=1, res=8):
self.name = "Spheres"
+def SphericGrid(pos=(0, 0, 0), r=1, c="t", alpha=1, res=12):
+ """Build a sphere made of quads.
+
+ |sphericgrid|
+ """
+ sg = CubicGrid(n=(res,res,res))
+
+ cgpts = sg.points()-(.5,.5,.5)
+
+ x, y, z = cgpts[:,0], cgpts[:,1], cgpts[:,2]
+ x = x*(1+x*x)/2
+ y = y*(1+y*y)/2
+ z = z*(1+z*z)/2
+ _, theta, phi = utils.cart2spher(x, y, z)
+
+ pts = utils.spher2cart(np.ones_like(phi)*r, theta, phi)
+
+ sg.points(pts).computeNormals().c(c).alpha(alpha)
+ sg.SetPosition(pos)
+ sg.name = "SphericGrid"
+ return sg
+
+
class Earth(Mesh):
"""Build a textured mesh representing the Earth.
|geodesic| |geodesic.py|_
"""
- def __init__(self, pos=(0, 0, 0), r=1):
+ def __init__(self, pos=(0, 0, 0), r=1, style=1):
tss = vtk.vtkTexturedSphereSource()
tss.SetRadius(r)
tss.SetThetaResolution(72)
@@ -1220,8 +1395,8 @@ def __init__(self, pos=(0, 0, 0), r=1):
Mesh.__init__(self, tss, c="w")
atext = vtk.vtkTexture()
- pnmReader = vtk.vtkPNMReader()
- fn = settings.textures_path + "earth.ppm"
+ pnmReader = vtk.vtkJPEGReader()
+ fn = settings.textures_path + "earth" + str(style) +".jpg"
pnmReader.SetFileName(fn)
atext.SetInputConnection(pnmReader.GetOutputPort())
atext.InterpolateOn()
@@ -1238,6 +1413,8 @@ class Ellipsoid(Mesh):
.. note:: `axis1` and `axis2` are only used to define sizes and one azimuth angle.
|projectsphere|
+
+ |pca| |pca.py|_
"""
def __init__(self, pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0, 3),
c="c", alpha=1, res=24):
@@ -1273,7 +1450,7 @@ def __init__(self, pos=(0, 0, 0), axis1=(1, 0, 0), axis2=(0, 2, 0), axis3=(0, 0,
self.GetProperty().BackfaceCullingOn()
self.SetPosition(pos)
- self.base = -np.array(axis1) / 2 + pos
+ self.Length = -np.array(axis1) / 2 + pos
self.top = np.array(axis1) / 2 + pos
settings.collectable_actors.append(self)
self.name = "Ellipsoid"
@@ -1328,7 +1505,6 @@ def __init__(self,
faces.append([i+j*n, i+1+j*n, i+1+j1n, i+j1n])
Mesh.__init__(self, [verts, faces], c, alpha)
- self.orientation(normal)
else:
ps = vtk.vtkPlaneSource()
@@ -1336,27 +1512,30 @@ def __init__(self,
ps.Update()
poly0 = ps.GetOutput()
t0 = vtk.vtkTransform()
+ # t0.Translate(pos)
t0.Scale(sx, sy, 1)
tf0 = vtk.vtkTransformPolyDataFilter()
tf0.SetInputData(poly0)
tf0.SetTransform(t0)
tf0.Update()
poly = tf0.GetOutput()
- axis = np.array(normal) / np.linalg.norm(normal)
- theta = np.arccos(axis[2])
- phi = np.arctan2(axis[1], axis[0])
- t = vtk.vtkTransform()
- t.PostMultiply()
- t.RotateY(np.rad2deg(theta))
- t.RotateZ(np.rad2deg(phi))
- tf = vtk.vtkTransformPolyDataFilter()
- tf.SetInputData(poly)
- tf.SetTransform(t)
- tf.Update()
- Mesh.__init__(self, tf.GetOutput(), c, alpha)
+ # axis = np.array(normal) / np.linalg.norm(normal)
+ # theta = np.arccos(axis[2])
+ # phi = np.arctan2(axis[1], axis[0])
+ # t = vtk.vtkTransform()
+ # t.PostMultiply()
+ # t.RotateY(np.rad2deg(theta))
+ # t.RotateZ(np.rad2deg(phi))
+ # tf = vtk.vtkTransformPolyDataFilter()
+ # tf.SetInputData(poly)
+ # tf.SetTransform(t)
+ # tf.Update()
+ Mesh.__init__(self, poly, c, alpha)
+ self.SetPosition(pos)
+
+ self.orientation(normal)
self.wireframe().lw(lw)
- self.SetPosition(pos)
settings.collectable_actors.append(self)
self.name = "Grid"
@@ -1440,6 +1619,30 @@ def Cube(pos=(0, 0, 0), side=1, c="g", alpha=1):
mesh.name = "Cube"
return mesh
+def CubicGrid(pos=(0, 0, 0), n=(10,10,10), spacing=(), c="lightgrey", alpha=0.1):
+ """Build a cubic Mesh made o `n` small cubes in the 3 axis directions.
+
+ :param list pos: position of the left bottom corner
+ :param int n: number of subdivisions
+ :parameter list spacing: size of the side of the cube in the 3 directions
+ """
+ from vtkplotter.volume import Volume
+ n = np.array(n).astype(int) + 2
+ nx, ny, nz = n
+ mx, my, mz = n - 1
+ data_matrix = np.zeros([nx, ny, nz], dtype=np.uint8)
+ data_matrix[0:mx, 0:my, 0:mz] = 1
+ data_matrix[mx:nx, my:ny, mz:nz] = 2
+ if len(spacing) == 0:
+ spacing = 1/(n-2)
+ vol = Volume(data_matrix, spacing=spacing)
+ mesh = vol.legosurface().clean().c(c).alpha(alpha)
+ mesh.SetPosition(pos)
+ mesh.base = np.array([0,0,0])
+ mesh.top = np.array([0,0,1])
+ mesh.name = "CubicGrid"
+ return mesh
+
class Spring(Mesh):
"""
diff --git a/vtkplotter/textures/earth.ppm b/vtkplotter/textures/earth.ppm
deleted file mode 100644
index 0a41c2ec..00000000
Binary files a/vtkplotter/textures/earth.ppm and /dev/null differ
diff --git a/vtkplotter/textures/earth1.jpg b/vtkplotter/textures/earth1.jpg
new file mode 100644
index 00000000..e8292497
Binary files /dev/null and b/vtkplotter/textures/earth1.jpg differ
diff --git a/vtkplotter/textures/earth2.jpg b/vtkplotter/textures/earth2.jpg
new file mode 100644
index 00000000..2cbb7e85
Binary files /dev/null and b/vtkplotter/textures/earth2.jpg differ
diff --git a/vtkplotter/textures/earth3.jpg b/vtkplotter/textures/earth3.jpg
new file mode 100644
index 00000000..1765a647
Binary files /dev/null and b/vtkplotter/textures/earth3.jpg differ
diff --git a/vtkplotter/textures/earth4.jpg b/vtkplotter/textures/earth4.jpg
new file mode 100644
index 00000000..4885d8e9
Binary files /dev/null and b/vtkplotter/textures/earth4.jpg differ
diff --git a/vtkplotter/textures/earth5.jpg b/vtkplotter/textures/earth5.jpg
new file mode 100644
index 00000000..cbcdfc66
Binary files /dev/null and b/vtkplotter/textures/earth5.jpg differ
diff --git a/vtkplotter/textures/earth6.jpg b/vtkplotter/textures/earth6.jpg
new file mode 100644
index 00000000..368af757
Binary files /dev/null and b/vtkplotter/textures/earth6.jpg differ
diff --git a/vtkplotter/textures/earth7.jpg b/vtkplotter/textures/earth7.jpg
new file mode 100644
index 00000000..24a325f2
Binary files /dev/null and b/vtkplotter/textures/earth7.jpg differ
diff --git a/vtkplotter/textures/sun.jpg b/vtkplotter/textures/sun.jpg
new file mode 100644
index 00000000..9d787d85
Binary files /dev/null and b/vtkplotter/textures/sun.jpg differ
diff --git a/vtkplotter/utils.py b/vtkplotter/utils.py
index 8850f12f..9192dd28 100644
--- a/vtkplotter/utils.py
+++ b/vtkplotter/utils.py
@@ -216,6 +216,8 @@ def buildPolyData(vertices, faces=None, lines=None, indexOffset=0, fast=True):
one by one. This is the fallback case when a mesh contains faces of
different number of vertices.
"""
+# if not len(vertices):
+# return None
if len(vertices[0]) < 3: # make sure it is 3d
vertices = np.c_[np.array(vertices), np.zeros(len(vertices))]
@@ -232,7 +234,7 @@ def buildPolyData(vertices, faces=None, lines=None, indexOffset=0, fast=True):
# Create a cell array to store the lines in and add the lines to it
linesarr = vtk.vtkCellArray()
- for i in range(1, len(lines)-1):
+ for i in range(0, len(lines)-1):
vline = vtk.vtkLine()
vline.GetPointIds().SetId(0,lines[i])
vline.GetPointIds().SetId(1,lines[i+1])
@@ -404,7 +406,7 @@ def distance(P0, P1, p):
def linInterpolate(x, rangeX, rangeY):
"""
Interpolate linearly variable x in rangeX onto rangeY.
- If x is a vector the linear weight is the distance to two the rangeX vectors.
+ If x is a 3D vector the linear weight is the distance to the two 3D rangeX vectors.
E.g. if x runs in rangeX=[x0,x1] and I want it to run in rangeY=[y0,y1] then
y = linInterpolate(x, rangeX, rangeY) will interpolate x onto rangeY.
@@ -635,8 +637,8 @@ def cart2spher(x, y, z):
"""Cartesian to Spherical coordinate conversion."""
hxy = np.hypot(x, y)
rho = np.hypot(hxy, z)
- if not rho:
- return np.array([0,0,0])
+ #if not rho:
+ # return np.array([0,0,0])
theta = np.arctan2(hxy, z)
phi = np.arctan2(y, x)
return rho, theta, phi
diff --git a/vtkplotter/version.py b/vtkplotter/version.py
index e9fd90c5..a6cce7c6 100644
--- a/vtkplotter/version.py
+++ b/vtkplotter/version.py
@@ -1 +1 @@
-_version='2020.0.1'
+_version='2020.0.2'
diff --git a/vtkplotter/volume.py b/vtkplotter/volume.py
index d6b37760..03e71465 100644
--- a/vtkplotter/volume.py
+++ b/vtkplotter/volume.py
@@ -189,11 +189,23 @@ def spacing(self, s=None):
"""Set/get the voxels size in the 3 dimensions."""
if s is not None:
self._imagedata.SetSpacing(s)
- self._mapper.Modified()
+ self._update(self._imagedata)
return self
else:
return np.array(self._imagedata.GetSpacing())
+ def center(self, center=None):
+ """Set/get the volume coordinates of its center.
+ Position is reset to (0,0,0)."""
+ if center is not None:
+ cn = self._imagedata.GetCenter()
+ self._imagedata.SetOrigin(-np.array(cn)/2)
+ self._update(self._imagedata)
+ self.pos(0,0,0)
+ return self
+ else:
+ return np.array(self._imagedata.GetCenter())
+
def permuteAxes(self, x, y ,z):
"""Reorder the axes of the Volume by specifying
the input axes which are supposed to become the new X, Y, and Z."""
diff --git a/vtkplotter/vtkio.py b/vtkplotter/vtkio.py
index a0de5c33..c8899c4d 100644
--- a/vtkplotter/vtkio.py
+++ b/vtkplotter/vtkio.py
@@ -41,9 +41,10 @@
def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True):
"""
- Load ``Mesh`` and ``Volume`` objects from file.
+ Load ``Mesh``, ``Volume`` and ``Picture`` objects from file.
The output will depend on the file extension. See examples below.
+ Unzip on the fly, if it ends with `.gz`.
:param c: color in RGB format, hex, symbol or name
:param alpha: transparency/opacity of the polygonal data.
@@ -97,6 +98,9 @@ def load(inputobj, c=None, alpha=1, threshold=False, spacing=(), unpack=True):
if os.path.isfile(fod): ### it's a file
+ if fod.endswith('.gz'):
+ fod = gunzip(fod)
+
a = _load_file(fod, c, alpha, threshold, spacing, unpack)
acts.append(a)
@@ -213,11 +217,12 @@ def _load_file(filename, c, alpha, threshold, spacing, unpack):
for i in range(mb.GetNumberOfBlocks()):
b = mb.GetBlock(i)
if isinstance(b, (vtk.vtkPolyData,
- vtk.vtkImageData,
vtk.vtkUnstructuredGrid,
vtk.vtkStructuredGrid,
vtk.vtkRectilinearGrid)):
- acts.append(b)
+ acts.append(Mesh(b, c=c, alpha=alpha))
+ if isinstance(b, vtk.vtkImageData):
+ acts.append(Volume(b))
return acts
else:
return mb
@@ -973,9 +978,9 @@ def write(objct, fileoutput, binary=True):
obj = objct
fr = fileoutput.lower()
- if ".vtk" in fr:
+ if fr.endswith(".vtk"):
writer = vtk.vtkPolyDataWriter()
- elif ".ply" in fr:
+ elif fr.endswith(".ply"):
writer = vtk.vtkPLYWriter()
pscal = obj.GetPointData().GetScalars()
if not pscal:
@@ -986,15 +991,17 @@ def write(objct, fileoutput, binary=True):
lut = objct.GetMapper().GetLookupTable()
if lut:
writer.SetLookupTable(lut)
- elif ".stl" in fr:
+ elif fr.endswith(".stl"):
writer = vtk.vtkSTLWriter()
- elif ".obj" in fr:
- writer = vtk.vtkOBJWriter()
- elif ".vtp" in fr:
+ elif fr.endswith(".vtp"):
writer = vtk.vtkXMLPolyDataWriter()
- elif ".vtm" in fr:
+ elif fr.endswith(".vtm"):
g = vtk.vtkMultiBlockDataGroupFilter()
for ob in objct:
+ if isinstance(ob, Mesh): # picks transformation
+ ob = ob.polydata(True)
+ elif isinstance(ob, (vtk.vtkActor, vtk.vtkVolume)):
+ ob = ob.GetMapper().GetInput()
g.AddInputData(ob)
g.Update()
mb = g.GetOutputDataObject(0)
@@ -1003,26 +1010,26 @@ def write(objct, fileoutput, binary=True):
wri.SetFileName(fileoutput)
wri.Write()
return mb
- elif ".xyz" in fr:
+ elif fr.endswith(".xyz"):
writer = vtk.vtkSimplePointsWriter()
- elif ".facet" in fr:
+ elif fr.endswith(".facet"):
writer = vtk.vtkFacetWriter()
- elif ".tif" in fr:
+ elif fr.endswith(".tif"):
writer = vtk.vtkTIFFWriter()
writer.SetFileDimensionality(len(obj.GetDimensions()))
- elif ".vti" in fr:
+ elif fr.endswith(".vti"):
writer = vtk.vtkXMLImageDataWriter()
- elif ".mhd" in fr:
+ elif fr.endswith(".mhd"):
writer = vtk.vtkMetaImageWriter()
- elif ".nii" in fr:
+ elif fr.endswith(".nii"):
writer = vtk.vtkNIFTIImageWriter()
- elif ".png" in fr:
+ elif fr.endswith(".png"):
writer = vtk.vtkPNGWriter()
- elif ".jpg" in fr:
+ elif fr.endswith(".jpg"):
writer = vtk.vtkJPEGWriter()
- elif ".bmp" in fr:
+ elif fr.endswith(".bmp"):
writer = vtk.vtkBMPWriter()
- elif ".npy" in fr:
+ elif fr.endswith(".npy"):
if utils.isSequence(objct):
objslist = objct
else:
@@ -1033,7 +1040,52 @@ def write(objct, fileoutput, binary=True):
np.save(fileoutput, dicts2save)
return dicts2save
- elif ".xml" in fr: # write tetrahedral dolfin xml
+ elif fr.endswith(".obj"):
+ outF = open(fileoutput, "w")
+ outF.write('# OBJ file format with ext .obj\n')
+ outF.write('# File Created by vtkplotter\n')
+ cobjct = objct.clone().clean()
+
+ for p in cobjct.points():
+ outF.write('v '+ str(p[0]) +" "+ str(p[1])+" "+ str(p[2])+'\n')
+
+ for vn in cobjct.normals(cells=False):
+ outF.write('vn '+str(vn[0])+" "+str(vn[1])+" "+str(vn[2])+'\n')
+
+ #pdata = cobjct.polydata().GetPointData().GetScalars()
+ #if pdata:
+ # ndata = vtk_to_numpy(pdata)
+ # for vd in ndata:
+ # outF.write('vp '+ str(vd) +'\n')
+
+ #ptxt = cobjct.polydata().GetPointData().GetTCoords() # not working
+ #if ptxt:
+ # ntxt = vtk_to_numpy(ptxt)
+ # print(len(cobjct.faces()), cobjct.points().shape, ntxt.shape)
+ # for vt in ntxt:
+ # outF.write('vt '+ str(vt[0]) +" "+ str(vt[1])+ ' 0\n')
+
+ for f in cobjct.faces():
+ fs = ''
+ for fi in f:
+ fs += " "+str(fi+1)
+ outF.write('f' + fs + '\n')
+
+ #ldata = cobjct.polydata().GetLines().GetData()
+ #print(cobjct.polydata().GetLines())
+ #if ldata:
+ # ndata = vtk_to_numpy(ldata)
+ # print(ndata)
+ # for l in ndata:
+ # ls = ''
+ # for li in l:
+ # ls += str(li+1)+" "
+ # outF.write('l '+ ls + '\n')
+
+ outF.close()
+ return objct
+
+ elif fr.endswith(".xml"): # write tetrahedral dolfin xml
vertices = objct.points().astype(str)
faces = np.array(objct.faces()).astype(str)
ncoords = vertices.shape[0]
@@ -1087,7 +1139,6 @@ def write(objct, fileoutput, binary=True):
writer.SetInputData(obj)
writer.SetFileName(fileoutput)
writer.Write()
- colors.printc("~save Saved file: " + fileoutput, c="g")
except Exception as e:
colors.printc("~noentry Error saving: " + fileoutput, "\n", e, c="r")
return objct
@@ -1123,13 +1174,19 @@ def exportWindow(fileoutput, binary=False, speed=None, html=True):
'''
fr = fileoutput.lower()
- if ".obj" in fr:
+ if fr.endswith(".obj"):
w = vtk.vtkOBJExporter()
w.SetInputData(settings.plotter_instance.window)
w.Update()
colors.printc("~save Saved file:", fileoutput, c="g")
- elif ".x3d" in fr:
+ elif fr.endswith(".obj"):
+ writer = vtk.vtkOBJWriter()
+ writer.SetInputData(obj)
+ writer.SetFileName(fileoutput)
+ writer.Write()
+
+ elif fr.endswith(".x3d"):
exporter = vtk.vtkX3DExporter()
exporter.SetBinary(binary)
exporter.FastestOff()
@@ -1156,7 +1213,7 @@ def exportWindow(fileoutput, binary=False, speed=None, html=True):
outF.close()
colors.printc("~save Saved files:", fileoutput,
fileoutput.replace('.x3d', '.html'), c="g")
- elif ".npy" in fr:
+ elif fr.endswith(".npy"):
sdict = dict()
vp = settings.plotter_instance
sdict['shape'] = vp.shape #todo
@@ -1274,13 +1331,17 @@ def importWindow(fileinput, mtlFile=None, texturePath=None):
##########################################################
-def screenshot(filename="screenshot.png", scale=None):
+def screenshot(filename="screenshot.png", scale=None, returnNumpy=False):
"""
Save a screenshot of the current rendering window.
+
+ :param int scale: set image magnification
+ :param bool returnNumpy: return a numpy array of the image
"""
if not settings.plotter_instance or not settings.plotter_instance.window:
colors.printc('~bomb screenshot(): Rendering window is not present, skip.', c=1)
return
+
if scale is None:
scale = settings.screeshotScale
@@ -1297,6 +1358,15 @@ def screenshot(filename="screenshot.png", scale=None):
w2if.ReadFrontBufferOff() # read from the back buffer
w2if.Update()
+ if returnNumpy:
+ w2ifout = w2if.GetOutput()
+ npdata = vtk_to_numpy(w2ifout.GetPointData().GetArray("ImageScalars"))
+ npdata = npdata[:,[0,1,2]]
+ ydim, xdim, _ = w2ifout.GetDimensions()
+ npdata = npdata.reshape([xdim, ydim, -1])
+ npdata = np.flip(npdata, axis=0)
+ return npdata
+
if filename.endswith('.png'):
writer = vtk.vtkPNGWriter()
writer.SetFileName(filename)