Repository: magpylib/magpylib (language: python)

Issue:
Introduce term `position` in docstrings instead of list/array of shape xxx
**observers**
- sensors
- positions
- Collection (sensor/mixed)

**sources**
- Magpylib source objects
- Collection (source/mixed)
Improve input checks and error behavior
- [ ] Complete input checks for all inputs !!! (How to add input checks at core level so they are not called twice when coming from level 1 ?)
- [ ] Properly format error messages
- [ ] Remove MagpylibXXXErrors, possibly replace with a single MagpylibError only, or better refer to standard error messages
- [ ] Define "good inputs" and "bad inputs" in a separate file for easy input review and easy testing
- [ ] include input checks for default_settings so that a user cannot make strange settings and default to them
- [ ] address `scalar` number input should not allow `bool`
- [ ] properly rework the utility - good/bad - error_msg - input_checks structure
4.0 Release tasks
* General:
  - [x] All versions at 4.0.0
  - [x] Spellcheck run
  - [x] All issues in [Milestone 4.0](https://github.com/magpylib/magpylib/milestone/1) are closed
- Code:
  - [x] All example codes (There was a lot of renaming)
  - [x] All doc strings are good (also examples - a lot of changes happened)
- Docs:
  - [x] All new features are documented
  - [x] Changelog is up to date
 


Discussion / hints:

Fix patch:
diff --git a/magpylib/__init__.py b/magpylib/__init__.py
index ccf3b72b6..a34d7629b 100644
--- a/magpylib/__init__.py
+++ b/magpylib/__init__.py
@@ -126,7 +126,7 @@
 
 # module level dunders
 __version__ = '4.0.0'
-__author__ =  'Michael Ortner & Alexandre Boissolet'
+__author__ =  'Michael Ortner & Alexandre Boisselet'
 __credits__ = 'The Magpylib community'
 __all__ = ['magnet', 'current', 'misc', 'getB', 'getH',
     'Sensor', 'Collection', 'show', 'defaults', '__version__',
diff --git a/magpylib/_src/defaults/defaults_classes.py b/magpylib/_src/defaults/defaults_classes.py
index 6526f4496..1a6ed6a43 100644
--- a/magpylib/_src/defaults/defaults_classes.py
+++ b/magpylib/_src/defaults/defaults_classes.py
@@ -8,33 +8,21 @@
 from magpylib._src.style import DisplayStyle
 
 
-# ON INTERFACE
 class DefaultConfig(MagicProperties):
     """Library default settings.
 
     Parameters
     ----------
-    checkinputs: bool, default=True
-        Check user input types, shapes at various stages and raise errors
-        when they are not within designated parameters.
-
-
     display: dict or Display
         `Display` class containing display settings. `('backend', 'animation', 'colorsequence' ...)`
     """
 
     def __init__(
         self,
-        checkinputs=None,
-        edgesize=None,
-        itercylinder=None,
         display=None,
         **kwargs,
     ):
         super().__init__(
-            checkinputs=checkinputs,
-            edgesize=edgesize,
-            itercylinder=itercylinder,
             display=display,
             **kwargs,
         )
@@ -45,50 +33,6 @@ def reset(self):
         self.update(get_defaults_dict(), _match_properties=False)
         return self
 
-    @property
-    def checkinputs(self):
-        """Check user input types, shapes at various stages and raise errors
-        when they are not within designated parameters."""
-        return self._checkinputs
-
-    @checkinputs.setter
-    def checkinputs(self, val):
-        assert val is None or isinstance(val, bool), (
-            f"the `checkinputs` property of {type(self).__name__} must be either `True` or `False`"
-            f" but received {repr(val)} instead"
-        )
-        self._checkinputs = val
-
-    @property
-    def edgesize(self):
-        """getB and getH return 0 on edge, formulas often show singularities there and undefined
-        forms. EDGESIZE defines how close to the edge 0 will be returned to avoid running into
-        numerical instabilities."""
-        return self._edgesize
-
-    @edgesize.setter
-    def edgesize(self, val):
-        assert val is None or isinstance(val, (int, float)) and val > 0, (
-            f"the `edgesize` property of {type(self).__name__} must be a strictly positive number"
-            f" but received {repr(val)} instead"
-        )
-        self._edgesize = val
-
-    @property
-    def itercylinder(self):
-        """Cylinder with diametral magnetization uses Simpsons iterative formula
-        to compute the integral. More iterations increase precision but slow
-        down the computation."""
-        return self._itercylinder
-
-    @itercylinder.setter
-    def itercylinder(self, val):
-        assert val is None or isinstance(val, int) and val > 0, (
-            f"the `itercylinder` property of {type(self).__name__} must be a strictly positive"
-            f" integer but received {repr(val)} instead"
-        )
-        self._itercylinder = val
-
     @property
     def display(self):
         """`Display` class containing display settings.
diff --git a/magpylib/_src/defaults/defaults_values.py b/magpylib/_src/defaults/defaults_values.py
index 15ec0a4a9..5bab152f7 100644
--- a/magpylib/_src/defaults/defaults_values.py
+++ b/magpylib/_src/defaults/defaults_values.py
@@ -1,7 +1,6 @@
 """Package level config defaults"""
 
 DEFAULTS = {
-    "checkinputs": True,
     "display": {
         "autosizefactor": 10,
         "animation": {
diff --git a/magpylib/_src/display/display.py b/magpylib/_src/display/display.py
index be9997660..f9a5bdf68 100644
--- a/magpylib/_src/display/display.py
+++ b/magpylib/_src/display/display.py
@@ -3,12 +3,16 @@
 import warnings
 from magpylib._src.utility import format_obj_input, test_path_format
 from magpylib._src.display.display_matplotlib import display_matplotlib
-from magpylib._src.input_checks import check_dimensions
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.exceptions import MagpylibBadUserInput
-from magpylib._src.defaults.defaults_utility import SUPPORTED_PLOTTING_BACKENDS
+from magpylib._src.input_checks import (
+    check_dimensions,
+    check_excitations,
+    check_format_input_backend,
+    check_input_zoom,
+    check_input_animation,
+    check_format_input_vector,
+    )
+
 
-# ON INTERFACE
 def show(
     *objects,
     zoom=0,
@@ -18,39 +22,41 @@ def show(
     canvas=None,
     **kwargs,
 ):
-    """
-    Display objects and paths graphically. Style input can be a style-dict or
+    """Display objects and paths graphically.
+
+    Global graphic styles can be set with kwargs as style-dictionary or using
     style-underscore_magic.
 
     Parameters
     ----------
-    objects: sources, collections or sensors
+    objects: Magpylib objects (sources, collections, sensors)
         Objects to be displayed.
 
-    zoom: float, default = 0
+    zoom: float, default=`0`
         Adjust plot zoom-level. When zoom=0 3D-figure boundaries are tight.
 
-    animation: bool, default=False
-        If True and at least one object has a path, animated paths are rendered.
-        If a positive float, the animation duration target is set to the given value.
-        This feature is only available for the `plotly` backend at the moment.
+    animation: bool or float, default=`False`
+        If `True` and at least one object has a path, the paths are rendered.
+        If input is a positive float, the animation time is set to the given value.
+        This feature is only available for the plotly backend.
 
-    markers: array_like, shape (N,3), default=None
-        Display position markers in the global CS. By default no marker is displayed.
+    markers: array_like, shape (n,3), default=`None`
+        Display position markers in the global coordinate system.
 
-    backend: string, default=None
-        One of `'matplotlib'` or `'plotly'`. If not set, parameter will default to
-        magpylib.defaults.display.backend which comes as 'matplotlib' with installation.
+    backend: string, default=`None`
+        Define plotting backend. Must be one of `'matplotlib'` or `'plotly'`. If not
+        set, parameter will default to `magpylib.defaults.display.backend` which is
+        `'matplotlib'` by installation default.
 
-    canvas: pyplot Axis or plotly Figure, default=None
-        Display graphical output in a given canvas:
-        - with matplotlib: pyplot axis (must be 3D).
-        - with plotly: plotly.graph_objects.Figure or plotly.graph_objects.FigureWidget
-        By default a new canvas is created and displayed.
+    canvas: matplotlib.pyplot `AxesSubplot` or plotly `Figure` object, default=`None`
+        Display graphical output on a given canvas:
+        - with matplotlib: `matplotlib.axes._subplots.AxesSubplot` with `projection=3d.
+        - with plotly: `plotly.graph_objects.Figure` or `plotly.graph_objects.FigureWidget`.
+        By default a new canvas is created and immediately displayed.
 
     Returns
     -------
-    None: NoneType
+    `None`: NoneType
 
     Examples
     --------
@@ -59,15 +65,15 @@ def show(
 
     >>> import magpylib as magpy
     >>> src = magpy.magnet.Sphere(magnetization=(0,0,1), diameter=1)
-    >>> src.move([(.1,0,0)]*50, increment=True)
-    >>> src.rotate_from_angax(angle=[10]*50, axis='z', anchor=0, start=0, increment=True)
+    >>> src.move([(0.1*x,0,0) for x in range(50)])
+    >>> src.rotate_from_angax(angle=range(0,400,10), axis='z', anchor=0, start=11)
     >>> ts = [-.4,0,.4]
     >>> sens = magpy.Sensor(position=(0,0,2), pixel=[(x,y,0) for x in ts for y in ts])
     >>> magpy.show(src, sens)
     >>> magpy.show(src, sens, backend='plotly')
     --> graphic output
 
-    Display figure on your own canvas (here Matplotlib 3D axis):
+    Display output on your own canvas (here a Matplotlib 3d-axes):
 
     >>> import matplotlib.pyplot as plt
     >>> import magpylib as magpy
@@ -82,11 +88,11 @@ def show(
     or as global style arguments in display.
 
     >>> import magpylib as magpy
-    >>> src1 = magpy.magnet.Sphere((1,1,1), 1)
-    >>> src2 = magpy.magnet.Sphere((1,1,1), 1, (1,0,0))
+    >>> src1 = magpy.magnet.Sphere((1,1,1), 1, [(0,0,0), (0,0,3)])
+    >>> src2 = magpy.magnet.Sphere((1,1,1), 1, [(1,0,0), (1,0,3)], style_path_show=False)
     >>> magpy.defaults.display.style.magnet.magnetization.size = 2
     >>> src1.style.magnetization.size = 1
-    >>> magpy.show(src1, src2, style_color='r', zoom=3)
+    >>> magpy.show(src1, src2, style_color='r')
     --> graphic output
     """
 
@@ -96,17 +102,27 @@ def show(
 
     # test if all source dimensions and excitations have been initialized
     check_dimensions(obj_list_flat)
+    check_excitations(obj_list_flat)
 
     # test if every individual obj_path is good
     test_path_format(obj_list_flat)
 
-    if backend is None:
-        backend = Config.display.backend
+    # input checks
+    backend = check_format_input_backend(backend)
+    check_input_zoom(zoom)
+    check_input_animation(animation)
+    check_format_input_vector(
+        markers,
+        dims=(2,),
+        shape_m1=3,
+        sig_name='markers',
+        sig_type='array_like of shape (n,3)',
+        allow_None=True)
 
     if backend == "matplotlib":
         if animation is not False:
-            msg = "The matplotlib backend does not support animation at the moment. "
-            msg += "Use plotly backend instead."
+            msg = "The matplotlib backend does not support animation at the moment.\n"
+            msg+= "Use `backend=plotly` instead."
             warnings.warn(msg)
             # animation = False
         display_matplotlib(
@@ -124,9 +140,3 @@ def show(
             animation=animation,
             **kwargs,
         )
-    else:
-        msg = (
-            f"The plotting backend must be one of {SUPPORTED_PLOTTING_BACKENDS},"
-            f" received {backend!r} instead"
-        )
-        raise MagpylibBadUserInput(msg)
diff --git a/magpylib/_src/display/plotly/plotly_base_traces.py b/magpylib/_src/display/plotly/plotly_base_traces.py
index 44289efc3..a85a0dc0a 100644
--- a/magpylib/_src/display/plotly/plotly_base_traces.py
+++ b/magpylib/_src/display/plotly/plotly_base_traces.py
@@ -6,9 +6,10 @@
 
 
 def base_validator(name, value, conditions):
-    """validates value based on dictionary of conditions"""
-    msg = f"""{name} must be one of `{tuple(conditions.keys())}`
-received value: {value!r}
+    """Validates value based on dictionary of conditions"""
+
+    msg = f"""Input {name} must be one of `{tuple(conditions.keys())},`
+received {value!r} instead.
 """
     assert value in conditions.keys(), msg
     return conditions[value]
@@ -16,8 +17,7 @@ def base_validator(name, value, conditions):
 validate_pivot = partial(base_validator, 'pivot')
 
 def make_BaseCuboid(dimension=(1.0, 1.0, 1.0), position=None, orientation=None) -> dict:
-    """
-    Provides the plotly mesh3d parameters for a cuboid in dictionary form, based on
+    """Provides the plotly mesh3d parameters for a cuboid in dictionary form, based on
     the given dimension. The zero position is in the barycenter of the vertices.
 
     Parameters
@@ -29,13 +29,13 @@ def make_BaseCuboid(dimension=(1.0, 1.0, 1.0), position=None, orientation=None)
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy `Rotation` object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
     -------
     BaseCuboid model3d plotly trace: dict
-        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys
+        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys.
     """
     dimension = np.array(dimension, dtype=float)
     trace = dict(
@@ -53,8 +53,7 @@ def make_BaseCuboid(dimension=(1.0, 1.0, 1.0), position=None, orientation=None)
 def make_BasePrism(
     base_vertices=6, diameter=1.0, height=1.0, position=None, orientation=None
 ) -> dict:
-    """
-    Provides the plotly mesh3d parameters for a prism in dictionary form, based on
+    """Provides the plotly mesh3d parameters for a prism in dictionary form, based on
     number of vertices of the base, diameter and height. The zero position is in the
     barycenter of the vertices.
 
@@ -73,13 +72,13 @@ def make_BasePrism(
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy Rotation object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
     -------
     BasePrism model3d plotly trace: dict
-        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys
+        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys.
     """
     N = base_vertices
     t = np.linspace(0, 2 * np.pi, N, endpoint=False)
@@ -120,8 +119,7 @@ def make_BasePrism(
 def make_BaseEllipsoid(
     dimension=(1.0, 1.0, 1.0), vert=15, position=None, orientation=None
 ) -> dict:
-    """
-    Provides the plotly mesh3d parameters for an ellipsoid in dictionary form, based
+    """Provides the plotly mesh3d parameters for an ellipsoid in dictionary form, based
     on number of vertices of the circumference, and the dimension. The zero position is in the
     barycenter of the vertices.
 
@@ -137,13 +135,13 @@ def make_BaseEllipsoid(
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy Rotation object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
     -------
     BaseEllipsoid model3d plotly trace: dict
-        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys
+        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys.
     """
     N = vert
     phi = np.linspace(0, 2 * np.pi, vert, endpoint=False)
@@ -179,8 +177,7 @@ def make_BaseEllipsoid(
 def make_BaseCylinderSegment(
     r1=1, r2=2, h=1, phi1=0, phi2=90, vert=50, position=None, orientation=None
 ) -> dict:
-    """
-    Provides the plotly mesh3d parameters for a cylinder segment in dictionary form, based on
+    """Provides the plotly mesh3d parameters for a cylinder segment in dictionary form, based on
     inner and outer diameters, height, and section angles in degrees. The zero position is at
     `z=0` at the center point of the arcs.
 
@@ -209,13 +206,13 @@ def make_BaseCylinderSegment(
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy Rotation object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
     -------
     BaseCylinderSegment model3d plotly trace: dict
-        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys
+        A dictionary with `type="mesh3d" and corresponding `i,j,k,x,y,z` keys.
     """
 
     N = max(5, int(vert * abs(phi1 - phi2) / 360))
@@ -271,8 +268,7 @@ def make_BaseCone(
     position=None,
     orientation=None,
 ) -> dict:
-    """
-    Provides the plotly mesh3d parameters for a cone in dictionary form, based on
+    """Provides the plotly mesh3d parameters for a cone in dictionary form, based on
     number of vertices of the base, diameter and height. The zero position is in the
     barycenter of the vertices.
 
@@ -295,7 +291,7 @@ def make_BaseCone(
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy Rotation object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
@@ -333,8 +329,7 @@ def make_BaseArrow(
     position=None,
     orientation=None,
 ) -> dict:
-    """
-    Provides the plotly mesh3d parameters for an arrow in dictionary form, based on
+    """Provides the plotly mesh3d parameters for an arrow in dictionary form, based on
     number of vertices of the base, diameter and height. The zero position is in the
     barycenter of the vertices.
 
@@ -357,7 +352,7 @@ def make_BaseArrow(
         Reference position of the vertices in the global CS. The zero position is
         in the barycenter of the vertices.
 
-    orientation: scipy Rotation object with length 1 or m, default=identity
+    orientation: scipy Rotation object with length 1 or m, default=`identity`
         Orientation of the vertices in the global CS.
 
     Returns
diff --git a/magpylib/_src/display/plotly/plotly_display.py b/magpylib/_src/display/plotly/plotly_display.py
index cce63b724..83faa6755 100644
--- a/magpylib/_src/display/plotly/plotly_display.py
+++ b/magpylib/_src/display/plotly/plotly_display.py
@@ -17,7 +17,6 @@
 
 import numpy as np
 from scipy.spatial.transform import Rotation as RotScipy
-from magpylib._src.exceptions import MagpylibBadUserInput
 from magpylib import _src
 from magpylib._src.defaults.defaults_classes import default_settings as Config
 from magpylib._src.display.plotly.plotly_sensor_mesh import get_sensor_mesh
@@ -1076,16 +1075,10 @@ def display_plotly(
         show_fig = True
         fig = go.Figure()
 
-    # Check animation parameters
-    if isinstance(animation, numbers.Number) and not isinstance(animation, bool) and animation > 0:
+    # set animation and animation_time
+    if isinstance(animation, numbers.Number) and not isinstance(animation, bool):
         kwargs["animation_time"] = animation
         animation = True
-    elif not isinstance(animation, bool):
-        msg = (
-            "The `animation` property must be either `True` or `False` or a positive number"
-            f" but received {animation!r} instead"
-        )
-        raise MagpylibBadUserInput(msg)
     if (
         not any(
             getattr(obj, "position", np.array([])).ndim > 1 for obj in flat_obj_list
diff --git a/magpylib/_src/display/plotly/plotly_utility.py b/magpylib/_src/display/plotly/plotly_utility.py
index 30f4043fc..2f706afcb 100644
--- a/magpylib/_src/display/plotly/plotly_utility.py
+++ b/magpylib/_src/display/plotly/plotly_utility.py
@@ -2,12 +2,11 @@
 import numpy as np
 
 def merge_mesh3d(*traces):
-    """
-    Merges a list of plotly mesh3d dictionaries. The `i,j,k` index parameters need to cumulate the
-    indices of each object in order to point to the right vertices in the concatenated vertices.
-    `x,y,z,i,j,k` are mandatory fields, the `intensity` and `facecolor` parameters also get
-    concatenated if they are present in all objects. All other parameter found in the dictionary
-    keys are taken from the first object, other keys from further objects are ignored.
+    """Merges a list of plotly mesh3d dictionaries. The `i,j,k` index parameters need to cumulate
+    the indices of each object in order to point to the right vertices in the concatenated
+    vertices. `x,y,z,i,j,k` are mandatory fields, the `intensity` and `facecolor` parameters also
+    get concatenated if they are present in all objects. All other parameter found in the
+    dictionary keys are taken from the first object, other keys from further objects are ignored.
     """
     merged_trace = {}
     L = np.array([0] + [len(b["x"]) for b in traces[:-1]]).cumsum()
@@ -26,10 +25,9 @@ def merge_mesh3d(*traces):
 
 
 def merge_scatter3d(*traces):
-    """
-    Merges a list of plotly scatter3d. `x,y,z` are mandatory fields and are concatenated with a
-    `None` vertex to prevent line connection between objects to be concatenated. Keys are taken from
-    the first object, other keys from further objects are ignored.
+    """Merges a list of plotly scatter3d. `x,y,z` are mandatory fields and are concatenated with a
+    `None` vertex to prevent line connection between objects to be concatenated. Keys are taken
+    from the first object, other keys from further objects are ignored.
     """
     merged_trace = {}
     for k in "xyz":
@@ -41,8 +39,7 @@ def merge_scatter3d(*traces):
 
 
 def merge_traces(*traces):
-    """
-    Merges a list of plotly 3d-traces. Supported trace types are `mesh3d` and `scatter3d`.
+    """Merges a list of plotly 3d-traces. Supported trace types are `mesh3d` and `scatter3d`.
     All traces have be of the same type when merging. Keys are taken from the first object, other
     keys from further objects are ignored.
     """
@@ -59,22 +56,20 @@ def merge_traces(*traces):
 
 
 def getIntensity(vertices, axis) -> np.ndarray:
-    """
-    Calculates the intensity values for vertices based on the distance of the vertices to the mean
-    vertices position in the provided axis direction. It can be used for plotting
+    """Calculates the intensity values for vertices based on the distance of the vertices to
+    the mean vertices position in the provided axis direction. It can be used for plotting
     fields on meshes. If `mag` See more infos here:https://plotly.com/python/3d-mesh/
 
     Parameters
     ----------
-    vertices : ndarray Nx3
-        the N vertices of the mesh object
-    axis : ndarray 3
-        direction vector
+    vertices : ndarray, shape (n,3)
+        The n vertices of the mesh object.
+    axis : ndarray, shape (3,)
+        Direction vector.
 
     Returns
     -------
-    ndarray N
-        returns 1D array of length N
+    Intensity values: ndarray, shape (n,)
     """
     p = np.array(vertices).T
     pos = np.mean(p, axis=1)
@@ -90,30 +85,29 @@ def getColorscale(
     color_middle="#DDDDDD",  # 'grey'
     color_south="#00B050",  # 'green'
 ) -> list:
-    """
-    Provides the colorscale for a plotly mesh3d trace.
-    The colorscale must be an array containing arrays mapping a normalized value to an rgb, rgba,
-    hex, hsl, hsv, or named color string. At minimum, a mapping for the lowest (0) and highest (1)
-    values are required. For example, `[[0, 'rgb(0,0,255)'], [1,'rgb(255,0,0)']]`.
-    In this case the colorscale is created depending on the north/middle/south poles colors. If the
-    middle color is `None` the colorscale will only have north and south pole colors.
+    """Provides the colorscale for a plotly mesh3d trace. The colorscale must be an array
+    containing arrays mapping a normalized value to an rgb, rgba, hex, hsl, hsv, or named
+    color string. At minimum, a mapping for the lowest (0) and highest (1) values is required.
+    For example, `[[0, 'rgb(0,0,255)'], [1,'rgb(255,0,0)']]`. In this case the colorscale
+    is created depending on the north/middle/south poles colors. If the middle color is
+    None, the colorscale will only have north and south pole colors.
 
     Parameters
     ----------
-    color_transition : float, optional
+    color_transition : float, default=0.1
         A value between 0 and 1. Sets the smoothness of the color transitions from adjacent colors
-        visualization., by default 0.1
-    color_north : str, optional
-        magnetic north pole color , by default None
-    color_middle : str, optional
-        middle between south and north pole color, by default None
-    color_south : str, optional
-        magnetic north pole color , by default None
+        visualization.
+    color_north : str, default=None
+        Magnetic north pole color.
+    color_middle : str, default=None
+        Color of area between south and north pole.
+    color_south : str, default=None
+        Magnetic north pole color.
 
     Returns
     -------
-    list
-        returns colorscale as list of tuples
+    colorscale: list
+        Colorscale as list of tuples.
     """
     if color_middle is False:
         colorscale = [
diff --git a/magpylib/_src/fields/field_BH_cuboid.py b/magpylib/_src/fields/field_BH_cuboid.py
index 1639b5ae3..93f41a749 100644
--- a/magpylib/_src/fields/field_BH_cuboid.py
+++ b/magpylib/_src/fields/field_BH_cuboid.py
@@ -6,17 +6,15 @@
 import numpy as np
 from magpylib._src.input_checks import check_field_input
 
-
-# ON INTERFACE
 def magnet_cuboid_field(
     magnetization: np.ndarray,
     dimension: np.ndarray,
     observer: np.ndarray,
     field='B') -> np.ndarray:
-    """
-    Computes the magnetic field of a homogeneously magnetized cuboid magnet
-    in Cartesian coordinates . The cuboid sides are parallel to the coordinate axes.
-    The geometric center of the cuboid lies in the origin.
+    """Magnetic field of a homogeneously magnetized cuboid.
+
+    The cuboid sides are parallel to the coordinate axes. The geometric center of the
+    cuboid lies in the origin.
 
     Parameters
     ----------
@@ -24,13 +22,14 @@ def magnet_cuboid_field(
         Homogeneous magnetization vector in units of [mT].
 
     dimension: ndarray, shape (n,3)
-        Cuboid side lengths in units of [mm]. Positive input expected.
+        Cuboid side lengths in units of [mm].
 
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_cylinder.py b/magpylib/_src/fields/field_BH_cylinder.py
index d5816504e..81b94d927 100644
--- a/magpylib/_src/fields/field_BH_cylinder.py
+++ b/magpylib/_src/fields/field_BH_cylinder.py
@@ -208,10 +208,10 @@ def magnet_cylinder_field(
     observer: np.ndarray,
     field='B',
     ) -> np.ndarray:
-    """
-    Computes the magnetic field of a homogeneously magnetized cylinder
-    in Cartesian coordinates. The cylinder axis coincides with the z-axis and
-    the geometric center of the cylinder lies in the origin.
+    """Magnetic field of a homogeneously magnetized cylinder.
+
+    The cylinder axis coincides with the z-axis and the geometric center of the
+    cylinder lies in the origin.
 
     Parameters
     ----------
@@ -224,8 +224,9 @@ def magnet_cylinder_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_cylinder_segment.py b/magpylib/_src/fields/field_BH_cylinder_segment.py
index b0f0831a7..4ef2a7b3a 100644
--- a/magpylib/_src/fields/field_BH_cylinder_segment.py
+++ b/magpylib/_src/fields/field_BH_cylinder_segment.py
@@ -1249,7 +1249,7 @@ def case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k)
     results[:,2,2] = Hz_zk_case235(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k)
     return results
 
-# ON INTERFACE
+
 def magnet_cylinder_segment_core(
     mag: np.ndarray,
     dim: np.ndarray,
@@ -1355,10 +1355,10 @@ def magnet_cylinder_segment_field(
     observer: np.ndarray,
     field='B',
     ) -> np.ndarray:
-    """
-    Computes the magnetic field of a homogeneously magnetized cylinder segment in
-    Cartesian coordinates. The full cylinder axis coincides with the z-axis of the coordinate
-    system. The geometric center of the full cylinder is in the origin.
+    """Magnetic field of a homogeneously magnetized cylinder segment.
+
+    The full cylinder axis coincides with the z-axis of the coordinate system. The geometric
+    center of the full cylinder lies in the origin.
 
     Parameters
     ----------
@@ -1372,8 +1372,9 @@ def magnet_cylinder_segment_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_dipole.py b/magpylib/_src/fields/field_BH_dipole.py
index 533af1396..1c37973b3 100644
--- a/magpylib/_src/fields/field_BH_dipole.py
+++ b/magpylib/_src/fields/field_BH_dipole.py
@@ -5,13 +5,15 @@
 import numpy as np
 from magpylib._src.input_checks import check_field_input
 
+
 def dipole_field(
     moment: np.ndarray,
     observer: np.ndarray,
     field='B'
     ) -> np.ndarray:
-    """
-    Computes the magnetic field of a magnetic dipole moment in Cartesian coordinates.
+    """Magnetic field of a dipole moment.
+
+    The dipole moment lies in the origin of the coordinate system.
 
     Parameters
     ----------
@@ -21,8 +23,9 @@ def dipole_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_line.py b/magpylib/_src/fields/field_BH_line.py
index 909b0187f..628239bed 100644
--- a/magpylib/_src/fields/field_BH_line.py
+++ b/magpylib/_src/fields/field_BH_line.py
@@ -66,10 +66,10 @@ def current_line_field(
     observer: np.ndarray,
     field='B'
     ) -> np.ndarray:
-    """
-    Computes the magnetic field of line current segments in Cartesian coordinates.
+    """Magnetic field of line current segments.
 
-    The field set to (0,0,0) on line.
+    The current flows from start to end positions. The field is set to (0,0,0) on a
+    line segment.
 
     Parameters
     ----------
@@ -85,8 +85,9 @@ def current_line_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_loop.py b/magpylib/_src/fields/field_BH_loop.py
index 90e063ca9..79b5ded53 100644
--- a/magpylib/_src/fields/field_BH_loop.py
+++ b/magpylib/_src/fields/field_BH_loop.py
@@ -17,8 +17,8 @@ def current_loop_field(
     observer: np.ndarray,
     field='B'
     ) -> np.ndarray:
-    """
-    Computes the magnetic field of a circular line-current loop in Cartesian coordinates.
+    """Magnetic field of a circular (line) current loop.
+
     The loop lies in the z=0 plane with the coordinate origin at its center.
 
     Parameters
@@ -32,8 +32,9 @@ def current_loop_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_BH_sphere.py b/magpylib/_src/fields/field_BH_sphere.py
index 570147d2b..9ee7cf0fa 100644
--- a/magpylib/_src/fields/field_BH_sphere.py
+++ b/magpylib/_src/fields/field_BH_sphere.py
@@ -13,9 +13,9 @@ def magnet_sphere_field(
     observer: np.ndarray,
     field='B'
     )->np.ndarray:
-    """
-    Computes the magnetic field of a homogeneously magnetized spherical magnet
-    in Cartesian coordinates.
+    """Magnetic field of a homogeneously magnetized sphere.
+
+    The center of the sphere lies in the origin of the coordinate system.
 
     Parameters
     ----------
@@ -28,8 +28,9 @@ def magnet_sphere_field(
     observer: ndarray, shape (n,3)
         Observer positions (x,y,z) in Cartesian coordinates in units of [mm].
 
-    field: str, default='B'
-        If 'B' return B-field in units of [mT], if 'H' return H-field in units of [kA/m].
+    field: str, default=`'B'`
+        If `field='B'` return B-field in units of [mT], if `field='H'` return H-field
+        in units of [kA/m].
 
     Returns
     -------
diff --git a/magpylib/_src/fields/field_wrap_BH_level2_dict.py b/magpylib/_src/fields/field_wrap_BH_level2_dict.py
index c04df62f5..064f3f1fe 100644
--- a/magpylib/_src/fields/field_wrap_BH_level2_dict.py
+++ b/magpylib/_src/fields/field_wrap_BH_level2_dict.py
@@ -7,7 +7,7 @@
 from magpylib._src.utility import LIBRARY_BH_DICT_SOURCE_STRINGS
 
 def getBH_dict_level2(**kwargs: dict) -> np.ndarray:
-    """ Direct access to vectorized computation
+    """ Direct interface access to vectorized computation
 
     Parameters
     ----------
@@ -41,8 +41,9 @@ def getBH_dict_level2(**kwargs: dict) -> np.ndarray:
     try:
         src_type = kwargs['source_type']
         if src_type not in LIBRARY_BH_DICT_SOURCE_STRINGS:
-            msg = f'sources input string must be one of {LIBRARY_BH_DICT_SOURCE_STRINGS}'
-            raise MagpylibBadUserInput(msg)
+            raise MagpylibBadUserInput(
+                f"Input parameter `sources` must be one of {LIBRARY_BH_DICT_SOURCE_STRINGS}"
+                " when using the direct interface.")
 
         poso = np.array(kwargs['observer'], dtype=float)
         tile_params['observer'] = (poso,2)    # <-- (input,tdim)
@@ -101,20 +102,24 @@ def getBH_dict_level2(**kwargs: dict) -> np.ndarray:
             tile_params['segment_end'] = (pos_end,2)
 
     except KeyError as kerr:
-        msg = f'Missing input keys: {str(kerr)}'
-        raise MagpylibBadUserInput(msg) from kerr
+        raise MagpylibBadUserInput(
+            f"Missing input keys: {str(kerr)}"
+        ) from kerr
     except TypeError as terr:
-        msg1='Bad user input type. When sources argument is a string,'
-        msg2=' all other inputs must be scalar or array_like.'
-        raise MagpylibBadUserInput(msg1+msg2) from terr
+        raise MagpylibBadUserInput(
+            "Bad user input type. When sources argument is a string,"
+            " all other inputs must be scalar or array_like."
+        ) from terr
 
     # auto tile 1D parameters ---------------------------------------
 
     # evaluation vector length
     ns = [len(val) for val,tdim in tile_params.values() if val.ndim == tdim]
     if len(set(ns)) > 1:
-        msg = f'Input array lengths must be similar. Instead received {str(set(ns))}'
-        raise MagpylibBadUserInput(msg)
+        raise MagpylibBadUserInput(
+        "Input array lengths must be 1 or of a similarlength.\n"
+        f"Instead received {str(set(ns))}"
+        )
     n = max(ns, default=1)
 
     # tile 1D inputs and replace original values in kwargs
@@ -136,209 +141,3 @@ def getBH_dict_level2(**kwargs: dict) -> np.ndarray:
     if squeeze:
         return np.squeeze(B)
     return B
-
-
-# def getB_dict(**kwargs):
-#     """
-#     B-Field computation in units of [mT] from a dictionary of input vectors of
-#     length N.
-
-#     This function avoids the object-oriented Magpylib interface and gives direct
-#     access to the field implementations. It is the fastest way to compute fields
-#     with Magpylib.
-
-#     "Static" inputs of shape (x,) will automatically be tiled to shape (N,x) to
-#     fit with other inputs.
-
-#     Required inputs depend on chosen source_type!
-
-#     Parameters
-#     ----------
-#     source_type: string
-#         Source type for computation. Must be either 'Cuboid', 'Cylinder', 'Cylinder_old',
-#           'Sphere',
-#         'Dipole', 'Loop' or 'Line'. Expected input parameters depend on source_type.
-
-#     position: array_like, shape (3,) or (N,3), default=(0,0,0)
-#         Source positions in units of [mm].
-
-#     orientation: scipy Rotation object, default=unit rotation
-#         Source rotations relative to the initial state (see object docstrings).
-
-#     observer: array_like, shape (3,) or (N,3)
-#         Observer positions in units of [mm].
-
-#     squeeze: bool, default=True
-#         If True, the output is squeezed, i.e. all axes of length 1 in the output are eliminated.
-
-#     magnetization: array_like, shape (3,) or (N,3)
-#         Only `source_type in ('Cuboid', 'Cylinder', 'Sphere')`! Magnetization vector (mu0*M) or
-#         remanence field of homogeneous magnet magnetization in units of [mT].
-
-#     moment:  array_like, shape (3,) or (N,3)
-#         Only `source_type = 'Moment'`! Magnetic dipole moment in units of [mT*mm^3]. For
-#         homogeneous magnets the relation is moment = magnetization*volume.
-
-#     current: array_like, shape (N,)
-#         Only `source_type in ('Line', 'Loop')`! Current flowing in loop in units of [A].
-
-#     dimension: array_like
-#         Only `source_type in ('Cuboid', 'Cylinder', 'CylinderSegment')`! Magnet dimension
-#         input in units of [mm]. Dimension format of sources similar as in object oriented
-#         interface.
-
-#     diameter: array_like, shape (N)
-#         Only `source_type in (Sphere, Loop)`! Diameter of source in units of [mm].
-
-#     segment_start: array_like, shape (N,3)
-#         Only `source_type = 'Line'`! Start positions of line current segments in units of [mm].
-
-#     segment_end: array_like, shape (N,3)
-#         Only `source_type = 'Line'`! End positions of line current segments in units of [mm].
-
-#     Returns
-#     -------
-#     B-field: ndarray, shape (N,3)
-#         B-field generated by sources at observer positions in units of [mT].
-
-#     Examples
-#     --------
-
-#     Three-fold evaluation of the dipole field. For each computation the moment is (100,100,100).
-
-#     >>> import magpylib as magpy
-#     >>> B = magpy.getB_dict(
-#     >>>     source_type='Dipole',
-#     >>>     position=[(1,2,3), (2,3,4), (3,4,5)],
-#     >>>     moment=(100,100,100),
-#     >>>     observer=[(1,1,1), (2,2,2), (3,3,3)])
-#     >>> print(B)
-#     [[-0.71176254  0.56941003  1.85058261]
-#      [-0.71176254  0.56941003  1.85058261]
-#      [-0.71176254  0.56941003  1.85058261]]
-
-#     Six-fold evaluation of a Cuboid magnet field with increasing size and magnetization
-#     of the magnet. Position and orientation are by default (0,0,0) and unit-orientation,
-#     respectively. The observer position is (1,2,3) for each evaluation.
-
-#     >>> import numpy as np
-#     >>> import magpylib as magpy
-#     >>> B = magpy.getB_dict(
-#     >>>     source_type='Cuboid',
-#     >>>     magnetization = [(0,0,m) for m in np.linspace(500,1000,6)],
-#     >>>     dimension = [(a,a,a) for a in np.linspace(1,2,6)],
-#     >>>     observer=(1,2,3))
-#     >>> print(B)
-#     [[ 0.48818967  0.97689261  0.70605984]
-#      [ 1.01203491  2.02636222  1.46575704]
-#      [ 1.87397714  3.756164    2.72063422]
-#      [ 3.19414311  6.41330652  4.65485356]
-#      [ 5.10909461 10.2855981   7.4881383 ]
-#      [ 7.76954697 15.70382556 11.48192812]]
-
-
-#     """
-#     return getBH_dict_level2(field='B', **kwargs)
-
-
-# # ON INTERFACE
-# def getH_dict(**kwargs):
-#     """
-#     H-Field computation in units of [kA/m] from a dictionary of input vectors of
-#     length N.
-
-#     This function avoids the object-oriented Magpylib interface and gives direct
-#     access to the field implementations. It is the fastest way to compute fields
-#     with Magpylib.
-
-#     "Static" inputs of shape (x,) will automatically be tiled to shape (N,x) to
-#     fit with other inputs.
-
-#     Required inputs depend on chosen source_type!
-
-#     Parameters
-#     ----------
-#     source_type: string
-#         Source type for computation. Must be either 'Cuboid', 'Cylinder','Cylinder_old',
-#           'Sphere',
-#         'Dipole', 'Loop' or 'Line'. Expected input parameters depend on source_type.
-
-#     position: array_like, shape (3,) or (N,3), default=(0,0,0)
-#         Source positions in units of [mm].
-
-#     orientation: scipy Rotation object, default=unit rotation
-#         Source rotations relative to the initial state (see object docstrings).
-
-#     observer: array_like, shape (3,) or (N,3)
-#         Observer positions in units of [mm].
-
-#     squeeze: bool, default=True
-#         If True, the output is squeezed, i.e. all axes of length 1 in the output are eliminated.
-
-#     magnetization: array_like, shape (3,) or (N,3)
-#         Only `source_type in ('Cuboid', 'Cylinder', 'Sphere')`! Magnetization vector (mu0*M) or
-#         remanence field of homogeneous magnet magnetization in units of [mT].
-
-#     moment:  array_like, shape (3,) or (N,3)
-#         Only `source_type = 'Moment'`! Magnetic dipole moment in units of [mT*mm^3]. For
-#         homogeneous magnets the relation is moment = magnetization*volume.
-
-#     current: array_like, shape (N,)
-#         Only `source_type in ('Line', 'Loop')`! Current flowing in loop in units of [A].
-
-#     dimension: array_like
-#         Only `source_type in ('Cuboid', 'Cylinder', 'CylinderSegment')`! Magnet dimension
-#         input in units of [mm]. Dimension format of sources similar as in object oriented
-#         interface.
-
-#     diameter: array_like, shape (N)
-#         Only `source_type in (Sphere, Loop)`! Diameter of source in units of [mm].
-
-#     segment_start: array_like, shape (N,3)
-#         Only `source_type = 'Line'`! Start positions of line current segments in units of [mm].
-
-#     segment_end: array_like, shape (N,3)
-#         Only `source_type = 'Line'`! End positions of line current segments in units of [mm].
-
-#     Returns
-#     -------
-#     H-field: ndarray, shape (N,3)
-#         H-field generated by sources at observer positions in units of [kA/m].
-
-#     Examples
-#     --------
-
-#     Three-fold evaluation of the dipole field. For each computation the moment is (100,100,100).
-
-#     >>> import magpylib as magpy
-#     >>> H = magpy.getH_dict(
-#     >>>     source_type='Dipole',
-#     >>>     position=[(1,2,3), (2,3,4), (3,4,5)],
-#     >>>     moment=(100,100,100),
-#     >>>     observer=[(1,1,1), (2,2,2), (3,3,3)])
-#     >>> print(H)
-#     [[-0.56640264  0.45312211  1.47264685]
-#      [-0.56640264  0.45312211  1.47264685]
-#      [-0.56640264  0.45312211  1.47264685]]
-
-#     Six-fold evaluation of a Cuboid magnet field with increasing size and magnetization
-#     of the magnet. Position and orientation are (0,0,0) and unit-orientation, respectively,
-#     by default. The observer position is (1,2,3) for each evaluation.
-
-#     >>> import numpy as np
-#     >>> import magpylib as magpy
-#     >>> H = magpy.getH_dict(
-#     >>>     source_type='Cuboid',
-#     >>>     magnetization = [(0,0,m) for m in np.linspace(500,1000,6)],
-#     >>>     dimension = [(a,a,a) for a in np.linspace(1,2,6)],
-#     >>>     observer=(1,2,3))
-#     >>> print(H)
-#     [[ 0.388489    0.77738644  0.56186457]
-#      [ 0.80535179  1.61252782  1.16641239]
-#      [ 1.49126363  2.98906034  2.16501192]
-#      [ 2.54181833  5.10354717  3.70421476]
-#      [ 4.06568831  8.1850189   5.95887113]
-#      [ 6.18280903 12.49670731  9.13702808]]
-
-#     """
-#     return getBH_dict_level2(field='H', **kwargs)
diff --git a/magpylib/_src/fields/field_wrap_BH_level3.py b/magpylib/_src/fields/field_wrap_BH_level3.py
index 7b9e620ba..d56459a7e 100644
--- a/magpylib/_src/fields/field_wrap_BH_level3.py
+++ b/magpylib/_src/fields/field_wrap_BH_level3.py
@@ -1,85 +1,90 @@
 from magpylib._src.fields.field_wrap_BH_level2 import getBH_level2
 
 
-# ON INTERFACE
 def getB(sources=None, observers=None, sumup=False, squeeze=True, **kwargs):
-    """
-    Compute B-field in [mT] for given sources and observers.
-
-    - Object-oriented (default):
-        ``sources`` are previously defined Magpylib objects or list thereof
+    """Compute B-field in [mT] for given sources and observers.
 
-    - Direct interface:
-        Field implementations can be directly accessed for faster computation. Note that ``sources``
-        parameter only accepts a single source and corresponding input parameters must be defined
-        via keyword arguments (see Other parameters).
+    Field implementations can be directly accessed (avoiding the object oriented
+    Magpylib interface) by providing a string input `sources=source_type`, array_like
+    positions as `observers` input, and all other necessary input parameters (see below)
+    as kwargs.
 
     Parameters
     ----------
-    sources: source object, Collection or 1D list thereof
-        Sources can be a single source object, a Collection or a 1D list of L source
-        objects and/or collections.
+    sources: source and collection objects or 1D list thereof
+        Sources that generate the magnetic field. Can be a single source (or collection)
+        or a 1D list of l source and/or collection objects.
 
-        In direct interface mode, must be either 'Cuboid', 'Cylinder',
-        'Cylinder_old', 'Sphere', 'Dipole', 'Loop' or 'Line'. and other parameters depending on
-        source type must be specified.
+        Direct interface: input must be one of (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`,
+        `'Sphere'`, `'Dipole'`, `'Loop'` or `'Line'`).
 
-    observers: array_like or Sensor or 1D list thereof
-        Observers can be array_like positions of shape (N1, N2, ..., 3) where the field
-        should be evaluated, can be a Sensor object with pixel shape (N1, N2, ..., 3) or
-        a 1D list of K Sensor objects with similar pixel shape. All positions are given
-        in units of [mm].
+    observers: array_like or (list of) `Sensor` objects
+            Can be array_like positions of shape (n1, n2, ..., 3) where the field
+            should be evaluated, a `Sensor` object with pixel shape (n1, n2, ..., 3) or a list
+            of such sensor objects (must all have similar pixel shapes). All positions
+            are given in units of [mm].
 
-        In direct interface mode, array_like, shape (3,) or (N,3)
-        Observer positions in units of [mm].
+        Direct interface: Input must be array_like with shape (3,) or (n,3) corresponding
+        positions to observer positions in units of [mm].
 
-    sumup: bool, default=False
-        If True, the fields of all sources are summed up.
+    sumup: bool, default=`False`
+        If `True`, the fields of all sources are summed up.
 
-    squeeze: bool, default=True
-        If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
+    squeeze: bool, default=`True`
+        If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
         a single sensor or only a single source) are eliminated.
 
-    Other Parameters
-    ----------------
-    position: array_like, shape (3,) or (N,3), default=(0,0,0)
-        Source positions in units of [mm].
+    Other Parameters (Direct interface)
+    -----------------------------------
+    position: array_like, shape (3,) or (n,3), default=`(0,0,0)`
+        Source position(s) in the global coordinates in units of [mm].
 
-    orientation: scipy Rotation object, default=unit rotation
-        Source rotations relative to the initial state (see object docstrings).
+    orientation: scipy `Rotation` object with length 1 or n, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation.
 
-    magnetization: array_like, shape (3,) or (N,3)
-        Only `source_type in ('Cuboid', 'Cylinder', 'Sphere')`! Magnetization vector (mu0*M) or
-        remanence field of homogeneous magnet magnetization in units of [mT].
+    magnetization: array_like, shape (3,) or (n,3)
+        Only source_type in (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`, `'Sphere'`)!
+        Magnetization vector(s) (mu0*M, remanence field) in units of [kA/m] given in
+        the local object coordinates (rotates with object).
 
-    moment:  array_like, shape (3,) or (N,3)
-        Only `source_type = 'Moment'`! Magnetic dipole moment in units of [mT*mm^3]. For
-        homogeneous magnets the relation is moment = magnetization*volume.
+    moment: array_like, shape (3) or (n,3), unit [mT*mm^3]
+        Only source_type == `'Dipole'`!
+        Magnetic dipole moment(s) in units of [mT*mm^3] given in the local object coordinates
+        (rotates with object). For homogeneous magnets the relation moment=magnetization*volume
+        holds.
 
-    current: array_like, shape (N,)
-        Only `source_type in ('Line', 'Loop')`! Current flowing in loop in units of [A].
+    current: array_like, shape (n,)
+        Only source_type == `'Loop'` or `'Line'`!
+        Electrical current in units of [A].
 
-    dimension: array_like
-        Only `source_type in ('Cuboid', 'Cylinder', 'CylinderSegment')`! Magnet dimension
-        input in units of [mm]. Dimension format of sources similar as in object oriented
-        interface.
+    dimension: array_like, shape (x,) or (n,x)
+        Only source_type in (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`)!
+        Magnet dimension input in units of [mm] and [deg]. Dimension format x of sources is similar
+        as in object oriented interface.
 
-    diameter: array_like, shape (N)
-        Only `source_type in (Sphere, Loop)`! Diameter of source in units of [mm].
+    diameter: array_like, shape (n,)
+        Only source_type == `'Sphere'` or `'Loop'`!
+        Diameter of source in units of [mm].
 
-    segment_start: array_like, shape (N,3)
-        Only `source_type = 'Line'`! Start positions of line current segments in units of [mm].
+    segment_start: array_like, shape (n,3)
+        Only source_type == `'Line'`!
+        Start positions of line current segments in units of [mm].
 
-    segment_end: array_like, shape (N,3)
-        Only `source_type = 'Line'`! End positions of line current segments in units of [mm].
+    segment_end: array_like, shape (n,3)
+        Only source_type == `'Line'`!
+        End positions of line current segments in units of [mm].
 
     Returns
     -------
-    B-field: ndarray, shape squeeze(L, M, K, N1, N2, ..., 3)
-        B-field of each source (L) at each path position (M) for each sensor (K) and each
-        sensor pixel position (N1,N2,...) in units of [mT]. Sensor pixel positions are
-        equivalent to simple observer positions. Paths of objects that are shorter than
-        M will be considered as static beyond their end.
+    B-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+        B-field at each path position (m) for each sensor (k) and each sensor pixel
+        position (n1, n2, ...) in units of [mT]. Sensor pixel positions are equivalent
+        to simple observer positions. Paths of objects that are shorter than m will be
+        considered as static beyond their end.
+
+    Direct interface: ndarray, shape (n,3)
+        B-field for every parameter set in units of [mT].
 
     Notes
     -----
@@ -87,134 +92,139 @@ def getB(sources=None, observers=None, sumup=False, squeeze=True, **kwargs):
     similar sources for optimal vectorization of the computation. For maximal performance
     call this function as little as possible and avoid using it in loops.
 
-    "Static" inputs of shape (x,) will automatically be tiled to shape (N,x) to
-    fit with other inputs.
-
     Examples
     --------
-    Compute the B-field of a spherical magnet at a sensor positioned at (1,2,3):
+    In this example we compute the B-field [mT] of a spherical magnet and a current loop
+    at the observer position (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> source = magpy.magnet.Sphere(magnetization=(1000,0,0), diameter=1)
-    >>> sensor = magpy.Sensor(position=(1,2,3))
-    >>> B = magpy.getB(source, sensor)
+    >>> src1 = magpy.current.Loop(current=100, diameter=2)
+    >>> src2 = magpy.magnet.Sphere(magnetization=(0,0,100), diameter=1)
+    >>> B = magpy.getB([src1, src2], (1,1,1))
     >>> print(B)
-    [-0.62497314  0.34089444  0.51134166]
+    [[6.23597388e+00 6.23597388e+00 2.66977810e+00]
+     [8.01875374e-01 8.01875374e-01 1.48029737e-16]]
 
-    Compute the B-field of a spherical magnet at five path positions as seen
-    by an observer at position (1,2,3):
+    We can also use sensor objects as observer inputs:
 
-    >>> import magpylib as magpy
-    >>> source = magpy.magnet.Sphere(magnetization=(1000,0,0), diameter=1)
-    >>> source.move([(x,0,0) for x in [1,2,3,4,5]])
-    >>> B = magpy.getB(source, (1,2,3))
+    >>> sens1 = magpy.Sensor(position=(1,1,1))
+    >>> sens2 = sens1.copy(position=(1,1,-1))
+    >>> B = magpy.getB([src1, src2], [sens1, sens2])
     >>> print(B)
-    [[-0.88894262  0.          0.        ]
-     [-0.62497314 -0.34089444 -0.51134166]
-     [-0.17483825 -0.41961181 -0.62941771]
-     [ 0.09177028 -0.33037301 -0.49555952]
-     [ 0.17480239 -0.22080302 -0.33120453]]
+    [[[ 6.23597388e+00  6.23597388e+00  2.66977810e+00]
+      [-6.23597388e+00 -6.23597388e+00  2.66977810e+00]]
 
-    Compute the B-field of two sources at two observer positions, with and without
-    sumup:
+     [[ 8.01875374e-01  8.01875374e-01  1.48029737e-16]
+      [-8.01875374e-01 -8.01875374e-01  1.48029737e-16]]]
 
-    >>> import magpylib as magpy
-    >>> src1 = magpy.current.Loop(current=15, diameter=2)
-    >>> src2 = magpy.misc.Dipole(moment=(100,100,100))
-    >>> obs_pos = [(1,1,1), (1,2,3)]
-    >>> B = magpy.getB([src1,src2], obs_pos)
+    Through the direct interface we can compute the same fields for the loop as:
+
+    >>> obs = [(1,1,1), (1,1,-1)]
+    >>> B = magpy.getB('Loop', obs, current=100, diameter=2)
     >>> print(B)
-    [[[0.93539608 0.93539608 0.40046672]
-      [0.05387784 0.10775569 0.0872515 ]]
-     [[3.06293831 3.06293831 3.06293831]
-      [0.04340403 0.23872216 0.43404028]]]
-    >>> B = magpy.getB([src1,src2], obs_pos, sumup=True)
+    [[ 6.23597388  6.23597388  2.6697781 ]
+     [-6.23597388 -6.23597388  2.6697781 ]]
+
+    But also for a set of four completely different instances:
+
+    >>> B = magpy.getB('Loop',
+    >>>     observers=((1,1,1), (1,1,-1), (1,2,3), (2,2,2)),
+    >>>     current=(11, 22, 33, 44),
+    >>>     diameter=(1, 2, 3, 4),
+    >>>     position=((0,0,0), (0,0,1), (0,0,2), (0,0,3)))
     >>> print(B)
-    [[3.99833439 3.99833439 3.46340502]
-     [0.09728187 0.34647784 0.52129178]]
+    [[ 0.17111325  0.17111325  0.01705189]
+     [-0.38852048 -0.38852048  0.49400758]
+     [ 1.14713551  2.29427102 -0.22065346]
+     [-2.48213467 -2.48213467 -0.79683487]]
     """
     return getBH_level2(sources, observers, sumup=sumup, squeeze=squeeze, field='B', **kwargs)
 
 
-# ON INTERFACE
 def getH(sources=None, observers=None, sumup=False, squeeze=True, **kwargs):
-    """
-    Compute H-field in [kA/m] for given sources and observers.
-
-    - Object-oriented (default):
-        ``sources`` are previously defined Magpylib objects or list thereof
+    """Compute H-field in [kA/m] for given sources and observers.
 
-    - Direct interface:
-        Field implementations can be directly accessed for faster computation. Note that ``sources``
-        parameter only accepts a single source and corresponding input parameters must be defined
-        via keyword arguments (see Other parameters).
+    Field implementations can be directly accessed (avoiding the object oriented
+    Magpylib interface) by providing a string input `sources=source_type`, array_like
+    positions as `observers` input, and all other necessary input parameters (see below)
+    as kwargs.
 
     Parameters
     ----------
-    sources: source object, Collection or 1D list thereof
-        Sources can be a single source object, a Collection or a 1D list of L source
-        objects and/or collections.
+    sources: source and collection objects or 1D list thereof
+        Sources that generate the magnetic field. Can be a single source (or collection)
+        or a 1D list of l source and/or collection objects.
 
-        In direct interface mode, must be either 'Cuboid', 'Cylinder',
-        'Cylinder_old', 'Sphere', 'Dipole', 'Loop' or 'Line'. and other parameters depending on
-        source type must be specified.
+        Direct interface: input must be one of (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`,
+        `'Sphere'`, `'Dipole'`, `'Loop'` or `'Line'`).
 
-    observers: array_like or Sensor or 1D list thereof
-        Observers can be array_like positions of shape (N1, N2, ..., 3) where the field
-        should be evaluated, can be a Sensor object with pixel shape (N1, N2, ..., 3) or
-        a 1D list of K Sensor objects with similar pixel shape. All positions are given
-        in units of [mm].
+    observers: array_like or (list of) `Sensor` objects
+            Can be array_like positions of shape (n1, n2, ..., 3) where the field
+            should be evaluated, a `Sensor` object with pixel shape (n1, n2, ..., 3) or a list
+            of such sensor objects (must all have similar pixel shapes). All positions
+            are given in units of [mm].
 
-        In direct interface mode, array_like, shape (3,) or (N,3)
-        Observer positions in units of [mm].
+         Direct interface: Input must be array_like with shape (3,) or (n,3) corresponding
+        positions to observer positions in units of [mm].
 
-    sumup: bool, default=False
-        If True, the fields of all sources are summed up.
+    sumup: bool, default=`False`
+        If `True`, the fields of all sources are summed up.
 
-    squeeze: bool, default=True
-        If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
+    squeeze: bool, default=`True`
+        If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
         a single sensor or only a single source) are eliminated.
 
-    Other Parameters
-    ----------------
-    position: array_like, shape (3,) or (N,3), default=(0,0,0)
-        Source positions in units of [mm].
+    Other Parameters (Direct interface)
+    -----------------------------------
+    position: array_like, shape (3,) or (n,3), default=`(0,0,0)`
+        Source position(s) in the global coordinates in units of [mm].
 
-    orientation: scipy Rotation object, default=unit rotation
-        Source rotations relative to the initial state (see object docstrings).
+    orientation: scipy `Rotation` object with length 1 or n, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation.
 
-    magnetization: array_like, shape (3,) or (N,3)
-        Only `source_type in ('Cuboid', 'Cylinder', 'Sphere')`! Magnetization vector (mu0*M) or
-        remanence field of homogeneous magnet magnetization in units of [mT].
+    magnetization: array_like, shape (3,) or (n,3)
+        Only source_type in (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`, `'Sphere'`)!
+        Magnetization vector(s) (mu0*M, remanence field) in units of [kA/m] given in
+        the local object coordinates (rotates with object).
 
-    moment:  array_like, shape (3,) or (N,3)
-        Only `source_type = 'Moment'`! Magnetic dipole moment in units of [mT*mm^3]. For
-        homogeneous magnets the relation is moment = magnetization*volume.
+    moment: array_like, shape (3) or (n,3), unit [mT*mm^3]
+        Only source_type == `'Dipole'`!
+        Magnetic dipole moment(s) in units of [mT*mm^3] given in the local object coordinates
+        (rotates with object). For homogeneous magnets the relation moment=magnetization*volume
+        holds.
 
-    current: array_like, shape (N,)
-        Only `source_type in ('Line', 'Loop')`! Current flowing in loop in units of [A].
+    current: array_like, shape (n,)
+        Only source_type == `'Loop'` or `'Line'`!
+        Electrical current in units of [A].
 
-    dimension: array_like
-        Only `source_type in ('Cuboid', 'Cylinder', 'CylinderSegment')`! Magnet dimension
-        input in units of [mm]. Dimension format of sources similar as in object oriented
-        interface.
+    dimension: array_like, shape (x,) or (n,x)
+        Only source_type in (`'Cuboid'`, `'Cylinder'`, `'CylinderSegment'`)!
+        Magnet dimension input in units of [mm] and [deg]. Dimension format x of sources is similar
+        as in object oriented interface.
 
-    diameter: array_like, shape (N)
-        Only `source_type in (Sphere, Loop)`! Diameter of source in units of [mm].
+    diameter: array_like, shape (n,)
+        Only source_type == `'Sphere'` or `'Loop'`!
+        Diameter of source in units of [mm].
 
-    segment_start: array_like, shape (N,3)
-        Only `source_type = 'Line'`! Start positions of line current segments in units of [mm].
+    segment_start: array_like, shape (n,3)
+        Only source_type == `'Line'`!
+        Start positions of line current segments in units of [mm].
 
-    segment_end: array_like, shape (N,3)
-        Only `source_type = 'Line'`! End positions of line current segments in units of [mm].
+    segment_end: array_like, shape (n,3)
+        Only source_type == `'Line'`!
+        End positions of line current segments in units of [mm].
 
     Returns
     -------
-    H-field: ndarray, shape squeeze(L, M, K, N1, N2, ..., 3)
-        H-field of each source (L) at each path position (M) for each sensor (K) and each
-        sensor pixel position (N1,N2,...) in units of [kA/m]. Sensor pixel positions are
-        equivalent to simple observer positions. Paths of objects that are shorter than
-        M will be considered as static beyond their end.
+    H-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+        H-field at each path position (m) for each sensor (k) and each sensor pixel
+        position (n1, n2, ...) in units of [kA/m]. Sensor pixel positions are equivalent
+        to simple observer positions. Paths of objects that are shorter than m will be
+        considered as static beyond their end.
+
+    Direct interface: ndarray, shape (n,3)
+        H-field for every parameter set in units of [kA/m].
 
     Notes
     -----
@@ -222,50 +232,50 @@ def getH(sources=None, observers=None, sumup=False, squeeze=True, **kwargs):
     similar sources for optimal vectorization of the computation. For maximal performance
     call this function as little as possible and avoid using it in loops.
 
-    "Static" inputs of shape (x,) will automatically be tiled to shape (N,x) to
-    fit with other inputs.
-
     Examples
     --------
-    Compute the H-field of a spherical magnet at a sensor positioned at (1,2,3):
+    In this example we compute the H-field [kA/m] of a spherical magnet and a current loop
+    at the observer position (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> source = magpy.magnet.Sphere(magnetization=(1000,0,0), diameter=1)
-    >>> sensor = magpy.Sensor(position=(1,2,3))
-    >>> H = magpy.getH(source, sensor)
+    >>> src1 = magpy.current.Loop(current=100, diameter=2)
+    >>> src2 = magpy.magnet.Sphere(magnetization=(0,0,100), diameter=1)
+    >>> H = magpy.getH([src1, src2], (1,1,1))
     >>> print(H)
-    [-0.49733782  0.27127518  0.40691277]
+    [[4.96243034e+00 4.96243034e+00 2.12454191e+00]
+     [6.38112147e-01 6.38112147e-01 1.17798322e-16]]
 
-    Compute the H-field of a spherical magnet at five path positions as seen
-    by an observer at position (1,2,3):
+    We can also use sensor objects as observer inputs:
 
-    >>> import magpylib as magpy
-    >>> source = magpy.magnet.Sphere(magnetization=(1000,0,0), diameter=1)
-    >>> source.move([(x,0,0) for x in [1,2,3,4,5]])
-    >>> H = magpy.getH(source, (1,2,3))
+    >>> sens1 = magpy.Sensor(position=(1,1,1))
+    >>> sens2 = sens1.copy(position=(1,1,-1))
+    >>> H = magpy.getH([src1, src2], [sens1, sens2])
     >>> print(H)
-    [[-0.70739806  0.          0.        ]
-     [-0.49733782 -0.27127518 -0.40691277]
-     [-0.13913186 -0.33391647 -0.5008747 ]
-     [ 0.07302847 -0.26290249 -0.39435373]
-     [ 0.13910332 -0.17570946 -0.26356419]]
+    [[[ 4.96243034e+00  4.96243034e+00  2.12454191e+00]
+      [-4.96243034e+00 -4.96243034e+00  2.12454191e+00]]
 
-    Compute the H-field of two sources at two observer positions, with and without
-    sumup:
+     [[ 6.38112147e-01  6.38112147e-01  1.17798322e-16]
+      [-6.38112147e-01 -6.38112147e-01  1.17798322e-16]]]
 
-    >>> import magpylib as magpy
-    >>> src1 = magpy.current.Loop(current=15, diameter=2)
-    >>> src2 = magpy.misc.Dipole(moment=(100,100,100))
-    >>> obs_pos = [(1,1,1), (1,2,3)]
-    >>> H = magpy.getH([src1,src2], obs_pos)
+    Through the direct interface we can compute the same fields for the loop as:
+
+    >>> obs = [(1,1,1), (1,1,-1)]
+    >>> H = magpy.getH('Loop', obs, current=100, diameter=2)
     >>> print(H)
-    [[[0.74436455 0.74436455 0.31868129]
-      [0.04287463 0.08574925 0.06943254]]
-     [[2.43740886 2.43740886 2.43740886]
-      [0.03453983 0.18996906 0.34539828]]]
-    >>> H = magpy.getH([src1,src2], obs_pos, sumup=True)
+    [[ 4.96243034  4.96243034  2.12454191]
+     [-4.96243034 -4.96243034  2.12454191]]
+
+    But also for a set of four completely different instances:
+
+    >>> H = magpy.getH('Loop',
+    >>>     observers=((1,1,1), (1,1,-1), (1,2,3), (2,2,2)),
+    >>>     current=(11, 22, 33, 44),
+    >>>     diameter=(1, 2, 3, 4),
+    >>>     position=((0,0,0), (0,0,1), (0,0,2), (0,0,3)))
     >>> print(H)
-    [[3.18177341 3.18177341 2.75609015]
-     [0.07741445 0.27571831 0.41483082]]
+    [[ 0.1361676   0.1361676   0.01356947]
+     [-0.30917477 -0.30917477  0.39311875]
+     [ 0.91286143  1.82572286 -0.17559045]
+     [-1.97522001 -1.97522001 -0.63410104]]
     """
     return getBH_level2(sources, observers, sumup=sumup, squeeze=squeeze, field='H', **kwargs)
diff --git a/magpylib/_src/input_checks.py b/magpylib/_src/input_checks.py
index 2fb7d4b1d..9366db65a 100644
--- a/magpylib/_src/input_checks.py
+++ b/magpylib/_src/input_checks.py
@@ -1,241 +1,411 @@
 """ input checks code"""
 
+import numbers
 import numpy as np
 from scipy.spatial.transform import Rotation
 from magpylib._src.exceptions import (
     MagpylibBadUserInput,
-    MagpylibBadInputShape,
     MagpylibMissingInput,
 )
+from magpylib._src.defaults.defaults_classes import default_settings
 
+#################################################################
+#################################################################
+# FUNDAMENTAL CHECKS
 
-def check_field_input(inp, origin):
+def is_array_like(inp, msg:str):
+    """ test if inp is array_like: type list, tuple or ndarray
+    inp: test object
+    msg: str, error msg
     """
-    check field input
+    if not isinstance(inp, (list, tuple, np.ndarray)):
+        raise MagpylibBadUserInput(msg)
+
+def make_float_array(inp, msg:str):
+    """transform inp to array with dtype=float, throw error with bad input
+    inp: test object
+    msg: str, error msg
     """
-    if isinstance(inp, str):
-        if inp == 'B':
-            return True
-        if inp == 'H':
-            return False
-    msg = f'{origin} input can only be "field=B" or "field=H".'
+    try:
+        inp_array = np.array(inp, dtype=float)
+    except Exception as err:
+        raise MagpylibBadUserInput(msg + f"{err}") from err
+    return inp_array
+
+
+def check_array_shape(inp: np.ndarray, dims:tuple, shape_m1:int, msg:str):
+    """check if inp shape is allowed
+    inp: test object
+    dims: list, list of allowed dims
+    shape_m1: shape of lowest level, if 'any' allow any shape
+    msg: str, error msg
+    """
+    if inp.ndim in dims:
+        if inp.shape[-1]==shape_m1:
+            return None
+        if shape_m1 == 'any':
+            return None
     raise MagpylibBadUserInput(msg)
 
 
-def check_dimensions(sources):
-    """check if all sources have dimension (or similar) initialized"""
-    # pylint: disable=protected-access
-    for s in sources:
-        obj_type = getattr(s, '_object_type', None)
-        if obj_type in ("Cuboid", "Cylinder"):
-            if np.isnan(s.dimension).any():
-                raise MagpylibMissingInput(f"{s} dimension must be initialized.")
-        elif obj_type in ("Sphere", "Loop"):
-            if s.diameter is None:
-                raise MagpylibMissingInput(f"{s} diameter must be initialized.")
-        elif obj_type == "Line":
-            if None in s.vertices[0]:
-                raise MagpylibMissingInput(f"{s} vertices must be initialized.")
-
-
-def check_excitations(sources):
-    """check if all sources have dimension (or similar) initialized"""
-    # pylint: disable=protected-access
-    for s in sources:
-        obj_type = getattr(s, '_object_type', None)
-        if obj_type in ("Cuboid", "Cylinder", "Sphere"):
-            if np.isnan(s.magnetization).any():
-                raise MagpylibMissingInput(f"{s} magnetization must be initialized.")
-        elif obj_type in ("Loop", "Line"):
-            if s.current is None:
-                raise MagpylibMissingInput(f"{s} current must be initialized.")
-        elif obj_type == "Dipole":
-            if np.isnan(s.moment).any():
-                raise MagpylibMissingInput(f"{s} moment must be initialized.")
-
-
-def check_path_format(inp, origin):
-    """test if input has the correct shape (3,) or (N,3)"""
-    msg = f"Bad {origin} input shape. Must be (3,) or (N,3)"
-    if not inp.shape[-1] == 3:
-        raise MagpylibBadInputShape(msg)
-    if not inp.ndim in (1, 2):
-        raise MagpylibBadInputShape(msg)
-
+def check_input_zoom(inp):
+    """check show zoom input"""
+    if not isinstance(inp, numbers.Number):
+        raise MagpylibBadUserInput(
+            "Input parameter `zoom` must be a number `zoom>=0`.\n"
+            f"Instead received {inp}."
+        )
+    if inp<0:
+        raise MagpylibBadUserInput(
+            "Input parameter `zoom` must be a number `zoom>=0`.\n"
+            f"Instead received {inp}."
+        )
 
-def check_anchor_type(anch):
-    """anchor input must be vector or None or 0"""
-    if not (isinstance(anch, (list, tuple, np.ndarray, type(None))) or anch == 0):
-        msg = "anchor input must be 0, None or a vector (list, tuple or ndarray)."
-        raise MagpylibBadUserInput(msg)
 
+def check_input_animation(inp):
+    """check show animation input"""
+    ERR_MSG = (
+        "Input parameter `animation` must be boolean or a positive number.\n"
+        f"Instead received {inp}."
+        )
+    if not isinstance(inp, numbers.Number):
+        raise MagpylibBadUserInput(ERR_MSG)
+    if inp<0:
+        raise MagpylibBadUserInput(ERR_MSG)
 
-def check_anchor_format(anch):
-    """must be shape (N,3), (3,) or 0"""
-    if not np.all(anch == np.array(0)):
-        msg = "Bad anchor input. Must be None, 0 or shape (3,) or (N,3)."
-        if not anch.shape[-1] == 3:
-            raise MagpylibBadInputShape(msg)
-        if not anch.ndim in (1, 2):
-            raise MagpylibBadInputShape(msg)
 
 
-def check_rot_type(inp):
-    """rotation input mut be scipy Rotation"""
-    if not isinstance(inp, (Rotation, type(None))):
-        msg = "orientation input must be None or scipy Rotation object."
-        raise MagpylibBadUserInput(msg)
+#################################################################
+#################################################################
+# SIMPLE CHECKS
 
-
-def check_start_type(start):
+def check_start_type(inp):
     """start input must be int or str"""
-    if not (isinstance(start, (int, np.int_)) or start == 'auto'):
-        msg = 'start input must be int or str ("auto")'
-        raise MagpylibBadUserInput(msg)
+    if not (isinstance(inp, (int, np.integer)) or (isinstance(inp, str) and inp == 'auto')):
+        raise MagpylibBadUserInput(
+            f"Input parameter `start` must be integer value or 'auto'.\n"
+            f"Instead received {repr(inp)}."
+        )
 
 
-def check_angle_type(angle):
-    """angle input must be scalar or vector"""
-    if not isinstance(angle, (int, float, list, tuple, np.ndarray, np.int_, np.float_)):
-        msg = (
-            "angle input must be scalar (int, float) or vector (list, tuple, ndarray)."
+def check_degree_type(inp):
+    """degrees input must be bool"""
+    if not isinstance(inp, bool):
+        raise MagpylibBadUserInput(
+            "Input parameter `degrees` must be boolean (`True` or `False`).\n"
+            f"Instead received {repr(inp)}."
         )
-        raise MagpylibBadUserInput(msg)
-
 
-def check_angle_format(angle):
-    """angle format must be scalar or of shape (N,)"""
-    if isinstance(angle, np.ndarray):
-        if angle.ndim not in (0,1):
-            msg = "Bad angle input shape. Must be scalar or 1D vector."
-            raise MagpylibBadInputShape(msg)
 
 
-def check_axis_type(ax):
-    """axis input must be vector or str"""
-    if not isinstance(ax, (list, tuple, np.ndarray, str)):
-        msg = "axis input must be a vector (list, tuple or ndarray) or a \
-            str ('x', 'y', 'z')."
-        raise MagpylibBadUserInput(msg)
+def check_field_input(inp, origin):
+    """check field input"""
+    if isinstance(inp, str):
+        if inp == 'B':
+            return True
+        if inp == 'H':
+            return False
+    raise MagpylibBadUserInput(
+        f"{origin} input can only be `field='B'` or `field='H'`.\n"
+        f"Instead received {repr(inp)}."
+    )
 
 
-def check_axis_format(axis):
-    """
-    - must be of shape (3,)
-    - must not be (0,0,0)
+def validate_field_lambda(val, bh):
+    """test if field function for custom source is valid
+    - needs to be a callable
+    - input and output shape must match
     """
-    msg = "Bad axis input shape. Must be shape (3,)."
-    if not axis.shape == (3,):
-        raise MagpylibBadInputShape(msg)
-    msg = "Bad axis input. Cannot be (0,0,0)."
-    if np.all(axis == 0):
-        raise MagpylibBadUserInput(msg)
-
-
-def check_degree_type(deg):
-    """degrees input must be bool"""
-    if not isinstance(deg, bool):
-        msg = "degrees input must be bool (True or False)."
-        raise MagpylibBadUserInput(msg)
-
+    if val is not None:
+        if not callable(val):
+            raise MagpylibBadUserInput(
+                f"Input parameter `field_{bh}_lambda` must be a callable."
+            )
+        out = val(np.array([4, 5, 6]))
+        out_shape = np.array(out).shape
+        case1 = out_shape!=(3,)
 
-def check_scalar_type(inp, origin):
-    """scalar input must be int or float or nan"""
-    if not (isinstance(inp, (int, float, np.int_, np.float_)) or inp is None):
-        msg = origin + " input must be scalar (int or float)."
-        raise MagpylibBadUserInput(msg)
+        out = val(np.array([[1, 2, 3], [4, 5, 6]]))
+        out_shape = np.array(out).shape
+        case2 = out_shape!=(2, 3)
+
+        if case1 | case2:
+            raise MagpylibBadUserInput(
+                f"Input parameter `field_{bh}_lambda` must be a callable function"
+                " and return a field ndarray of shape (3,) or (n,3) when its `observer`"
+                " input is of shape (3,) or (n,3) respectively.\n"
+                f"Instead received shape {out_shape}.")
+    return val
 
 
-# def check_scalar_init(inp, origin):
-#     """ check if scalar input was initialized (former None)
-#     """
-#     if inp is None:
-#         msg = origin + ' must be initialized.'
-#         raise MagpylibBadUserInput(msg)
+#################################################################
+#################################################################
+# CHECK - FORMAT
 
+def check_format_input_orientation(inp, init_format=False):
+    """checks orientation input returns in formatted form
+    - inp must be None or Rotation object
+    - transform None to unit rotation as quat (0,0,0,1)
+    if init_format: (for move method)
+        return inp and inpQ
+    else: (for init and setter)
+        return inpQ in shape (-1,4)
 
-def check_vector_type(inp, origin):
+    This function is used for setter and init only -> shape (1,4) and (4,) input
+    creates same behavior.
     """
-    - vector input must be list, tuple or ndarray
-    - return error msg with reference to origin
+    # check type
+    if not isinstance(inp, (Rotation, type(None))):
+        raise MagpylibBadUserInput(
+            f"Input parameter `orientation` must be `None` or scipy `Rotation` object.\n"
+            f"Instead received type {type(inp)}.")
+    # handle None input and compute inpQ
+    if inp is None:
+        inpQ = np.array((0,0,0,1))
+        inp=Rotation.from_quat(inpQ)
+    else:
+        inpQ = inp.as_quat()
+    # return
+    if init_format:
+        return np.reshape(inpQ, (-1,4))
+    return inp, inpQ
+
+
+def check_format_input_anchor(inp):
+    """ checks rotate anchor input and return in formatted form
+    - input must be array_like or None or 0
     """
-    if not isinstance(inp, (list, tuple, np.ndarray)):
-        msg = origin + " input must be vector type (list, tuple or ndarray)."
-        raise MagpylibBadUserInput(msg)
-
-
-def check_vector_format(inp, shape, origin):
+    if isinstance(inp, numbers.Number) and inp == 0:
+        return np.array((0.0, 0.0, 0.0))
+
+    return check_format_input_vector(inp,
+        dims=(1,2),
+        shape_m1=3,
+        sig_name='anchor',
+        sig_type='`None` or `0` or array_like (list, tuple, ndarray) with shape (3,)',
+        allow_None=True)
+
+
+def check_format_input_axis(inp):
+    """check rotate_from_angax axis input and return in formatted form
+    - input must be array_like or str
+    - if string 'x'->(1,0,0), 'y'->(0,1,0), 'z'->(0,0,1)
+    - convert inp to ndarray with dtype float
+    - inp shape must be (3,)
+    - axis must not be (0,0,0)
+    - return as ndarray shape (3,)
+    """
+    if isinstance(inp, str):
+        if inp == 'x':
+            return np.array((1,0,0))
+        if inp == 'y':
+            return np.array((0,1,0))
+        if inp == 'z':
+            return np.array((0,0,1))
+        raise MagpylibBadUserInput(
+            "Input parameter `axis` must be array_like shape (3,) or one of ['x', 'y', 'z'].\n"
+            f"Instead received string {inp}.\n")
+
+    inp = check_format_input_vector(inp,
+        dims=(1,),
+        shape_m1=3,
+        sig_name='axis',
+        sig_type="array_like (list, tuple, ndarray) with shape (3,) or one of ['x', 'y', 'z']")
+
+    if np.all(inp==0):
+        raise MagpylibBadUserInput(
+            "Input parameter `axis` must not be (0,0,0).\n")
+    return inp
+
+
+def check_format_input_angle(inp):
+    """check rotate_from_angax angle input and return in formatted form
+    - must be scalar (int/float) or array_like
+    - if scalar
+        - return float
+    - if array_like
+        - convert inp to ndarray with dtype float
+        - inp shape must be (n,)
+        - return as ndarray
     """
-    - check if vector input has correct format
-    - return error msg with reference to origin
+    if isinstance(inp, numbers.Number):
+        return float(inp)
+
+    return check_format_input_vector(inp,
+        dims=(1,),
+        shape_m1='any',
+        sig_name='angle',
+        sig_type='int, float or array_like (list, tuple, ndarray) with shape (n,)')
+
+
+def check_format_input_scalar(
+    inp,
+    sig_name,
+    sig_type,
+    allow_None=False,
+    forbid_negative=False):
+    """check sclar input and return in formatted form
+    - must be scalar or None (if allowed)
+    - must be float compatible
+    - tranform into float
     """
-    if not inp.shape == shape:
-        msg = f"Bad {origin} input shape. Must be shape {shape}."
-        raise MagpylibBadInputShape(msg)
-
-
-def check_input_cyl_sect(inp):
+    if allow_None:
+        if inp is None:
+            return None
+
+    ERR_MSG = (
+        f"Input parameter `{sig_name}` must be {sig_type}.\n"
+        f"Instead received {repr(inp)}.")
+
+    if not isinstance(inp, numbers.Number):
+        raise MagpylibBadUserInput(ERR_MSG)
+
+    inp = float(inp)
+
+    if forbid_negative:
+        if inp<0:
+            raise MagpylibBadUserInput(ERR_MSG)
+    return inp
+
+
+def check_format_input_vector(inp,
+    dims,
+    shape_m1,
+    sig_name,
+    sig_type,
+    reshape=False,
+    allow_None=False,
+    forbid_negative0=False,
+    squeeze=False):
+    """checks vector input and returns in formatted form
+    - inp must be array_like
+    - convert inp to ndarray with dtype float
+    - inp shape must be given by dims and shape_m1
+    - print error msg with signature arguments
+    - if reshape=True: returns shape (n,3) - required for position init and setter
+    - if allow_None: return None
     """
-    - check if cylinder dimension has correct format (5,)
+    if allow_None:
+        if inp is None:
+            return None
+
+    is_array_like(inp,
+        f"Input parameter `{sig_name}` must be {sig_type}.\n"
+        f"Instead received type {type(inp)}."
+    )
+    inp = make_float_array(inp,
+        f"Input parameter `{sig_name}` must contain only float compatible entries.\n"
+    )
+    check_array_shape(inp, dims=dims, shape_m1=shape_m1, msg=(
+        f"Input parameter `{sig_name}` must be {sig_type}.\n"
+        f"Instead received array_like with shape {inp.shape}.")
+    )
+    if reshape:
+        return np.reshape(inp, (-1,3))
+    if forbid_negative0:
+        if np.any(inp<=0):
+            raise MagpylibBadUserInput(
+                f"Input parameter `{sig_name}` cannot have values <= 0."
+            )
+    if squeeze:
+        return np.squeeze(inp)
+    return inp
+
+
+def check_format_input_vertices(inp):
+    """checks vertices input and returns in formatted form
+    - vector check with dim = (n,3) but n must be >=2
+    """
+    inp = check_format_input_vector(inp,
+        dims=(2,),
+        shape_m1=3,
+        sig_name='vertices',
+        sig_type='`None` or array_like (list, tuple, ndarray) with shape (n,3)',
+        allow_None=True)
+
+    if inp is not None:
+        if inp.shape[0]<2:
+            raise MagpylibBadUserInput(
+                "Input parameter `vertices` must have more than one vertex."
+            )
+    return inp
+
+
+def check_format_input_cylinder_segment(inp):
+    """checks vertices input and returns in formatted form
+    - vector check with dim = (5) or none
     - check if d1<d2, phi1<phi2
     - check if phi2-phi1 > 360
     - return error msg
     """
-    if not inp.shape == (5,):
-        msg = """Bad dimension input shape. Dimension must be (d1,d2,h,phi1,phi2)."""
-        raise MagpylibBadInputShape(msg)
-
-    d1, d2, _, phi1, phi2 = inp
-    if d1 >= d2:
-        msg = "d1 must be smaller than d2."
-        raise MagpylibBadUserInput(msg)
-
-    if phi1 >= phi2:
-        msg = "phi1 must be smaller than phi2."
-        raise MagpylibBadUserInput(msg)
+    inp = check_format_input_vector(inp,
+        dims=(1,),
+        shape_m1=5,
+        sig_name='CylinderSegment.dimension',
+        sig_type=(
+            'array_like of the form (r1, r2, h, phi1, phi2) with r1<r2,'
+            'phi1<phi2 and phi2-phi1<=360'),
+        allow_None=True)
+
+    if inp is None:
+        return None
+
+    r1, r2, h, phi1, phi2 = inp
+    case2 = r1>r2
+    case3 = phi1>phi2
+    case4 = (phi2 - phi1)>360
+    case5 = (r1<0) | (r2<=0) | (h<=0)
+    if case2 | case3 | case4 | case5:
+        raise MagpylibBadUserInput(
+            f"Input parameter `CylinderSegment.dimension` must be array_like of the form"
+            f" (r1, r2, h, phi1, phi2) with 0<=r1<r2, h>0, phi1<phi2 and phi2-phi1<=360,\n"
+            f"but received {inp} instead."
+        )
+    return inp
 
-    if (phi2 - phi1) > 360:
-        msg = "phi2-phi1 cannot be larger than 360°."
-        raise MagpylibBadUserInput(msg)
 
+def check_format_input_backend(inp):
+    """checks show-backend input and returns Non if bad input value"""
+    if inp is None:
+        inp = default_settings.display.backend
+    if inp in ('matplotlib', 'plotly'):
+        return inp
+    raise MagpylibBadUserInput(
+        "Input parameter `backend` must be one of `('matplotlib', 'plotly', None)`.\n"
+        f"Instead received {inp}.")
 
-def check_position_format(inp, origin):
-    """
-    - test if input has the correct shape (...,3)
-    - return error msg with reference to origin
-    """
-    msg = f"Bad {origin} input shape. Must be (...,3)."
-    if not inp.shape[-1] == 3:
-        raise MagpylibBadInputShape(msg)
 
+############################################################################################
+############################################################################################
+# SHOW AND GETB CHECKS
 
-def check_vertex_format(inp):
-    """
-    - test if input has the correct shape (N,3) with N >= 2
-    - return err msg
-    """
-    msg = "Bad vertex input shape. Must be (N,3) with N>1"
-    if not inp.shape[-1] == 3:
-        raise MagpylibBadInputShape(msg)
-    if not inp.ndim == 2:
-        raise MagpylibBadInputShape(msg)
-    if not inp.shape[0] > 1:
-        raise MagpylibBadInputShape(msg)
+def check_dimensions(sources):
+    """check if all sources have dimension (or similar) initialized"""
+    # pylint: disable=protected-access
+    for s in sources:
+        obj_type = getattr(s, '_object_type', None)
+        if obj_type in ('Cuboid', 'Cylinder', 'CylinderSegment'):
+            if s.dimension is None:
+                raise MagpylibMissingInput(f"Parameter `dimension` of {s} must be set.")
+        elif obj_type in ('Sphere', 'Loop'):
+            if s.diameter is None:
+                raise MagpylibMissingInput(f"Parameter `diameter` of {s} must be set.")
+        elif obj_type == 'Line':
+            if s.vertices is None:
+                raise MagpylibMissingInput(f"Parameter `vertices` of {s} must be set.")
 
 
-def validate_field_lambda(val, bh):
-    """
-    test if field function for custom source is valid
-    - needs to be a callable
-    - input and output schape must match
-    """
-    if val is not None:
-        assert callable(val), f"field_{bh}_lambda must be a callable"
-        out = val(np.array([[1, 2, 3], [4, 5, 6]]))
-        out_shape = np.array(out).shape
-        assert out_shape == (2, 3), (
-            f"field_{bh}_lambda input shape and output "
-            "shape must match and be of dimension (n,3)\n"
-            f"received shape={out_shape} instead"
-        )
-    return val
+def check_excitations(sources):
+    """check if all sources have exitation initialized"""
+    # pylint: disable=protected-access
+    for s in sources:
+        obj_type = getattr(s, '_object_type', None)
+        if obj_type in ('Cuboid', 'Cylinder', 'Sphere', 'CylinderSegment'):
+            if s.magnetization is None:
+                raise MagpylibMissingInput(f"Parameter `magnetization` of {s} must be set.")
+        elif obj_type in ('Loop', 'Line'):
+            if s.current is None:
+                raise MagpylibMissingInput(f"Parameter `current` of {s} must be set.")
+        elif obj_type == 'Dipole':
+            if s.moment is None:
+                raise MagpylibMissingInput(f"Parameter `moment` of {s} must be set.")
diff --git a/magpylib/_src/obj_classes/class_BaseDisplayRepr.py b/magpylib/_src/obj_classes/class_BaseDisplayRepr.py
index a5c07e483..7c4262560 100644
--- a/magpylib/_src/obj_classes/class_BaseDisplayRepr.py
+++ b/magpylib/_src/obj_classes/class_BaseDisplayRepr.py
@@ -1,25 +1,15 @@
-"""BaseGeo class code"""
+"""BaseGeo class code
+READY FOR V4
+"""
 
 from magpylib._src.display.display import show
 
-# ALL METHODS ON INTERFACE
 class BaseDisplayRepr:
-    """Provides the display(self) and self.repr methods for all objects
-
-    Properties
-    ----------
-
-    Methods
-    -------
-    - show(self, **kwargs)
-    - repr
-    """
+    """Provides the display(self) and self.repr methods for all objects"""
 
     show = show
     _object_type = None
 
-    # ------------------------------------------------------------------
-    # INTERFACE
     def __repr__(self) -> str:
         name = getattr(self, "name", None)
         if name is None and hasattr(self, "style"):
diff --git a/magpylib/_src/obj_classes/class_BaseExcitations.py b/magpylib/_src/obj_classes/class_BaseExcitations.py
index 6f710d03a..0fdaeb587 100644
--- a/magpylib/_src/obj_classes/class_BaseExcitations.py
+++ b/magpylib/_src/obj_classes/class_BaseExcitations.py
@@ -1,26 +1,15 @@
-"""BaseHomMag class code"""
+"""BaseHomMag class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
-from magpylib._src.defaults.defaults_classes import default_settings as Config
 from magpylib._src.input_checks import (
-    check_vector_type,
-    check_vector_format,
-    check_scalar_type,
+    check_format_input_scalar,
+    check_format_input_vector
 )
 
 
-# MAG PROPERTY ON INTERFACE
 class BaseHomMag:
-    """
-    provide magnetization attribute (homogeneous magnetization)
-
-    Properties
-    ----------
-    magnetization
-
-    Methods
-    -------
-    """
+    """provides the magnetization attribute  for homogeneously magnetized magnets """
 
     def __init__(self, magnetization):
         self.magnetization = magnetization
@@ -32,34 +21,18 @@ def magnetization(self):
 
     @magnetization.setter
     def magnetization(self, mag):
-        """Set magnetization vector, shape (3,), unit [mT]."""
-        # input type and init check
-        if Config.checkinputs:
-            check_vector_type(mag, "magnetization")
-            # check_vector_init(mag, 'magnetization')
-
-        # input type -> ndarray
-        mag = np.array(mag, dtype=float)
-
-        # input format check
-        if Config.checkinputs:
-            check_vector_format(mag, (3,), "magnetization")
+        """Set magnetization vector, array_like, shape (3,), unit [mT]."""
+        self._magnetization = check_format_input_vector(
+            mag,
+            dims=(1,),
+            shape_m1=3,
+            sig_name='magnetization',
+            sig_type='array_like (list, tuple, ndarray) with shape (3,)',
+            allow_None=True)
 
-        self._magnetization = mag
 
-
-# CURRENT PROPERTY ON INTERFACE
 class BaseCurrent:
-    """
-    provide scalar current attribute
-
-    Properties
-    ----------
-    current
-
-    Methods
-    -------
-    """
+    """provides scalar current attribute """
 
     def __init__(self, current):
         self.current = current
@@ -71,14 +44,10 @@ def current(self):
 
     @current.setter
     def current(self, current):
-        """Set Current value, unit [A]."""
+        """Set current value, scalar, unit [A]."""
         # input type and init check
-        if Config.checkinputs:
-            # check_scalar_init(current, 'current')
-            check_scalar_type(current, "current")
-
-        # secure type
-        if current is None:
-            self._current = None
-        else:
-            self._current = float(current)
+        self._current = check_format_input_scalar(
+            current,
+            sig_name='current',
+            sig_type='`None` or a number (int, float)',
+            allow_None=True)
diff --git a/magpylib/_src/obj_classes/class_BaseGeo.py b/magpylib/_src/obj_classes/class_BaseGeo.py
index 670cdc963..ec418f6f9 100644
--- a/magpylib/_src/obj_classes/class_BaseGeo.py
+++ b/magpylib/_src/obj_classes/class_BaseGeo.py
@@ -1,4 +1,6 @@
-"""BaseGeo class code"""
+"""BaseGeo class code
+DOCSTRING v4 READY
+"""
 
 # pylint: disable=cyclic-import
 # pylint: disable=too-many-instance-attributes
@@ -7,17 +9,15 @@
 import numpy as np
 from scipy.spatial.transform import Rotation as R
 from magpylib._src.obj_classes.class_BaseTransform import BaseTransform
-from magpylib._src.defaults.defaults_classes import default_settings as Config
 from magpylib._src.input_checks import (
-    check_vector_type,
-    check_path_format,
-    check_rot_type)
+    check_format_input_orientation,
+    check_format_input_vector,
+    )
 from magpylib._src.utility import add_iteration_suffix
 
 
 def pad_slice_path(path1, path2):
-    """
-    edge-pads or end-slices path 2 to fit path 1 format
+    """edge-pads or end-slices path 2 to fit path 1 format
     path1: shape (N,x)
     path2: shape (M,x)
     return: path2 with format (N,x)
@@ -29,38 +29,6 @@ def pad_slice_path(path1, path2):
         return path2[-delta_path:]
     return path2
 
-
-def position_input_check(pos):
-    """
-    checks input type and format end returns an ndarray of shape (N,3).
-    This function is used for setter and init only -> (1,3) and (3,) input
-    creates same behavior.
-    """
-    # check input type
-    if Config.checkinputs:
-        check_vector_type(pos, "position")
-    # path vector -> ndarray
-    pos_array = np.array(pos, dtype=float)
-    # check input format
-    if Config.checkinputs:
-        check_path_format(pos_array, "position")
-    # tile to format (N,3) and return
-    return pos_array.reshape(-1,3)
-
-def orientation_input_check(ori):
-    """
-    checks input type and format end returns an ndarray of shape (N,4).
-    This function is used for setter and init only -> (1,4) and (4,) input
-    creates same behavior.
-    """
-    # check input type
-    if Config.checkinputs:
-        check_rot_type(ori)
-    # None input generates unit rotation
-    ori_array = np.array([(0, 0, 0, 1)]) if ori is None else ori.as_quat()
-    # tile to format (N,4) and return
-    return ori_array.reshape(-1,4)
-
 class BaseGeo(BaseTransform):
     """Initializes position and orientation properties
     of an object in a global CS.
@@ -120,33 +88,40 @@ def _process_style_kwargs(style=None, **kwargs):
         return style
 
     def _init_position_orientation(self, position, orientation):
-        """
-        tile up position and orientation input at Class init and set attributes
-        _position and _orientation.
+        """tile up position and orientation input and set _position and
+        _orientation at class init. Because position and orientation inputs
+        come at the same time, tiling is slightly different then with setters.
         pos: position input
-        ori: orientation.as_quat() input
+        ori: orientation input
         """
 
         # format position and orientation inputs
-        pos = position_input_check(position)
-        ori = orientation_input_check(orientation)
+        pos = check_format_input_vector(
+            position,
+            dims=(1,2),
+            shape_m1=3,
+            sig_name='position',
+            sig_type='array_like (list, tuple, ndarray) with shape (3,) or (n,3)',
+            reshape=True)
+        oriQ = check_format_input_orientation(orientation, init_format=True)
 
         # padding logic: if one is longer than the other, edge-pad up the other
         len_pos = pos.shape[0]
-        len_ori = ori.shape[0]
+        len_ori = oriQ.shape[0]
 
         if len_pos>len_ori:
-            ori = np.pad(ori, ((0,len_pos-len_ori), (0,0)), 'edge')
+            oriQ = np.pad(oriQ, ((0,len_pos-len_ori), (0,0)), 'edge')
         elif len_pos<len_ori:
             pos = np.pad(pos, ((0,len_ori-len_pos), (0,0)), 'edge')
 
         # set attributes
         self._position = pos
-        self._orientation = R.from_quat(ori)
+        self._orientation = R.from_quat(oriQ)
 
     def _get_style_class(self):
         """returns style class based on object type. If class has no attribute `_object_type` or is
-        not found in `MAGPYLIB_FAMILIES` returns `BaseStyle` class."""
+        not found in `MAGPYLIB_FAMILIES` returns `BaseStyle` class.
+        """
         # pylint: disable=import-outside-toplevel
         from magpylib._src.style import get_style_class
 
@@ -174,7 +149,13 @@ def position(self, inp):
         old_pos = self._position
 
         # check and set new position
-        self._position = position_input_check(inp)
+        self._position = check_format_input_vector(
+            inp,
+            dims=(1,2),
+            shape_m1=3,
+            sig_name='position',
+            sig_type='array_like (list, tuple, ndarray) with shape (3,) or (n,3)',
+            reshape=True)
 
         # pad/slice and set orientation path to same length
         oriQ = self._orientation.as_quat()
@@ -208,7 +189,7 @@ def orientation(self, inp):
         old_oriQ = self._orientation.as_quat()
 
         # set _orientation attribute with ndim=2 format
-        oriQ = orientation_input_check(inp)
+        oriQ = check_format_input_orientation(inp, init_format=True)
         self._orientation = R.from_quat(oriQ)
 
         # pad/slice position path to same length
@@ -241,13 +222,15 @@ def _validate_style(self, val=None):
         if isinstance(val, dict):
             val = self.style_class(**val)
         if not isinstance(val, self.style_class):
-            raise ValueError(f"style must be of type {self.style_class}")
+            raise ValueError(
+                f"Input parameter `style` must be of type {self.style_class}.\n"
+                f"Instead received type {type(val)}"
+                )
         return val
 
     # dunders -------------------------------------------------------
     def __add__(self, obj):
-        """
-        Add up sources to a Collection object.
+        """Add up sources to a Collection object.
 
         Returns
         -------
@@ -258,8 +241,7 @@ def __add__(self, obj):
         return Collection(self, obj)
 
     def __radd__(self, other):
-        """
-        Add up sources to a Collection object. Allows to use `sum(objects)`
+        """Add up sources to a Collection object. Allows to use `sum(objects)`.
 
         Returns
         -------
@@ -271,25 +253,29 @@ def __radd__(self, other):
 
     # methods -------------------------------------------------------
     def reset_path(self):
-        """
-        Set object position to (0,0,0) and orientation = unit rotation.
+        """Set object position to (0,0,0) and orientation = unit rotation.
 
         Returns
         -------
-        self: Magpylib object
+        self: magpylib object
 
         Examples
         --------
-        Create an object with non-zero path
+        Demonstration of `reset_path` functionality:
 
         >>> import magpylib as magpy
         >>> obj = magpy.Sensor(position=(1,2,3))
+        >>> obj.rotate_from_angax(45, 'z')
         >>> print(obj.position)
+        >>> print(obj.orientation.as_euler('xyz', degrees=True))
         [1. 2. 3.]
+        [ 0.  0. 45.]
+
         >>> obj.reset_path()
         >>> print(obj.position)
+        >>> print(obj.orientation.as_euler('xyz', degrees=True))
+        [0. 0. 0.]
         [0. 0. 0.]
-
         """
         self.position = (0,0,0)
         self.orientation = None
@@ -301,24 +287,20 @@ def copy(self, **kwargs):
 
         Parameters
         ----------
-            kwargs: dict, optional
-                keyword arguments to be transmitted to the newly created object. Can be for example
-                `'position'`,`'orientation'`, `'style'` etc.
+        kwargs: dict
+            Keyword arguments (for example `position=(1,2,3)`) are applied to the copy.
 
         Examples
         --------
-        Create an object and copy to an another position
+        Create a `Sensor` object and copy to an another position:
 
         >>> import magpylib as magpy
-        >>> obj = magpy.Sensor(position=(1,2,3))
-        >>> print(obj.position)
-        [1. 2. 3.]
-        >>> obj2 = obj.copy(position=(2,6,10))
-        >>> print(obj.position)
-        [1. 2. 3.]
-        >>> print(obj2.position)
-        [2. 6. 10.]
-
+        >>> sens1 = magpy.Sensor()
+        >>> sens2 = sens1.copy(position=(2,6,10))
+        >>> print(f"Instance {sens1} with position {sens1.position}.")
+        >>> print(f"Instance {sens2} with position {sens2.position}.")
+        Instance Sensor(id=2756069642496) with position [0. 0. 0.].
+        Instance Sensor(id=2756171531744) with position [2. 6. 10.].
         """
         # pylint: disable=import-outside-toplevel
         from copy import deepcopy
diff --git a/magpylib/_src/obj_classes/class_BaseGetBH.py b/magpylib/_src/obj_classes/class_BaseGetBH.py
index edbf42bba..148970e0c 100644
--- a/magpylib/_src/obj_classes/class_BaseGetBH.py
+++ b/magpylib/_src/obj_classes/class_BaseGetBH.py
@@ -1,84 +1,112 @@
-"""BaseGetBHsimple class code"""
+"""BaseGetBHsimple class code
+DOCSTRINGS V4 READY
+"""
 
 from magpylib._src.fields.field_wrap_BH_level2 import getBH_level2
 from magpylib._src.utility import format_star_input
 
 
-# ALL METHODS ON INTERFACE
 class BaseGetBH:
-    """provides simple getB and getH methods
-
-    Properties
-    ----------
-
-    Methods
-    -------
-    - getB(self, *observers)
-    - getH(self, *observers)
+    """provides getB and getH methods for source objects
     """
 
-    # ------------------------------------------------------------------
-    # INTERFACE
     def getB(self, *observers, squeeze=True):
-        """
-        Compute B-field in units of [mT] for given observers.
+        """Compute the B-field in units of [mT] generated by the source.
 
         Parameters
         ----------
-
-        observers: array_like or Sensors
-            Observers can be array_like positions of shape (N1, N2, ..., 3) where the field
-            should be evaluated or Sensor objects with pixel shape (N1, N2, ..., 3). Pixel
-            shapes (or observer positions) of all inputs must be the same. All positions
+        observers: array_like or (list of) `Sensor` objects
+            Can be array_like positions of shape (n1, n2, ..., 3) where the field
+            should be evaluated, a `Sensor` object with pixel shape (n1, n2, ..., 3) or a list
+            of such sensor objects (must all have similar pixel shapes). All positions
             are given in units of [mm].
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
             only a single source) are eliminated.
 
         Returns
         -------
-        B-field: ndarray, shape squeeze(M, K, N1, N2, ..., 3)
-            B-field at each path position (M) for each sensor (K) and each sensor pixel
-            position (N1,N2,...) in units of [mT]. Sensor pixel positions are equivalent
-            to simple observer positions. Paths of objects that are shorter than M will be
+        B-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+            B-field at each path position (m) for each sensor (k) and each sensor pixel
+            position (n1,n2,...) in units of [mT]. Sensor pixel positions are equivalent
+            to simple observer positions. Paths of objects that are shorter than m will be
             considered as static beyond their end.
 
         Examples
         --------
+        Compute the B-field of a spherical magnet at three positions:
+
+        >>> import magpylib as magpy
+        >>> src = magpy.magnet.Sphere((0,0,1000), 1)
+        >>> B = src.getB(((0,0,0), (1,0,0), (2,0,0)))
+        >>> print(B)
+        >>> [[  0.           0.         666.66666667]
+        >>>  [  0.           0.         -41.66666667]
+        >>>  [  0.           0.          -5.20833333]]
+
+        Compute the B-field at two sensors, each one with two pixels
+
+        >>> sens1 = magpy.Sensor(position=(1,0,0), pixel=((0,0,.1), (0,0,-.1)))
+        >>> sens2 = sens1.copy(position=(2,0,0))
+        >>> B = src.getB(sens1, sens2)
+        >>> print(B)
+        >>> [[[ 12.19288783   0.         -39.83010025]
+        >>>   [-12.19288783   0.         -39.83010025]]
+        >>>  [[  0.77638847   0.          -5.15004352]
+        >>>   [ -0.77638847   0.          -5.15004352]]]
         """
         observers = format_star_input(observers)
         return getBH_level2(self, observers, sumup=False, squeeze=squeeze, field='B')
 
-    # ------------------------------------------------------------------
-    # INTERFACE
+
     def getH(self, *observers, squeeze=True):
-        """
-        Compute H-field in units of [kA/m] for given observers.
+        """Compute the H-field in units of [kA/m] generated by the source.
 
         Parameters
         ----------
-
-        observers: array_like or Sensors
-            Observers can be array_like positions of shape (N1, N2, ..., 3) where the field
-            should be evaluated or Sensor objects with pixel shape (N1, N2, ..., 3). Pixel
-            shapes (or observer positions) of all inputs must be the same. All positions
+        observers: array_like or (list of) `Sensor` objects
+            Can be array_like positions of shape (n1, n2, ..., 3) where the field
+            should be evaluated, a `Sensor` object with pixel shape (n1, n2, ..., 3) or a list
+            of such sensor objects (must all have similar pixel shapes). All positions
             are given in units of [mm].
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
             only a single source) are eliminated.
 
         Returns
         -------
-        H-field: ndarray, shape squeeze(M, K, N1, N2, ..., 3)
-            H-field at each path position (M) for each sensor (K) and each sensor pixel
-            position (N1,N2,...) in units of [kA/m]. Sensor pixel positions are equivalent
-            to simple observer positions. Paths of objects that are shorter than M will be
+        H-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+            H-field at each path position (m) for each sensor (k) and each sensor pixel
+            position (n1,n2,...) in units of [kA/m]. Sensor pixel positions are equivalent
+            to simple observer positions. Paths of objects that are shorter than m will be
             considered as static beyond their end.
 
         Examples
         --------
+        Compute the H-field of a spherical magnet at three positions:
+
+        >>> import magpylib as magpy
+
+        >>> src = magpy.magnet.Sphere((0,0,1000), 1)
+        >>> H = src.getH(((0,0,0), (1,0,0), (2,0,0)))
+        >>> print(H)
+        [[   0.            0.         -265.25823849]
+         [   0.            0.          -33.15727981]
+         [   0.            0.           -4.14465998]]
+
+        Compute the H-field at two sensors, each one with two pixels
+
+        >>> sens1 = magpy.Sensor(position=(1,0,0), pixel=((0,0,.1), (0,0,-.1)))
+        >>> sens2 = sens1.copy(position=(2,0,0))
+        >>> H = src.getH(sens1, sens2)
+        >>> print(H)
+        [[[  9.70279185   0.         -31.69578669]
+          [ -9.70279185   0.         -31.69578669]]
+
+         [[  0.61783031   0.          -4.09827441]
+          [ -0.61783031   0.          -4.09827441]]]
         """
         observers = format_star_input(observers)
         return getBH_level2(self, observers, sumup=False, squeeze=squeeze, field='H')
diff --git a/magpylib/_src/obj_classes/class_BaseTransform.py b/magpylib/_src/obj_classes/class_BaseTransform.py
index e00d25d8d..21743c50e 100644
--- a/magpylib/_src/obj_classes/class_BaseTransform.py
+++ b/magpylib/_src/obj_classes/class_BaseTransform.py
@@ -1,23 +1,20 @@
-"""BaseTransform class code"""
-# pylint: disable=too-many-instance-attributes
+"""BaseTransform class code
+DOCSTRING v4 READY
+"""
+
 # pylint: disable=protected-access
 
+import numbers
 import numpy as np
 from scipy.spatial.transform import Rotation as R
-from magpylib._src.exceptions import MagpylibBadUserInput
-from magpylib._src.defaults.defaults_classes import default_settings as Config
 from magpylib._src.input_checks import (
+    check_format_input_axis,
+    check_format_input_orientation,
+    check_format_input_anchor,
+    check_format_input_vector,
+    check_format_input_angle,
     check_start_type,
-    check_rot_type,
-    check_anchor_type,
-    check_anchor_format,
-    check_angle_type,
-    check_axis_type,
     check_degree_type,
-    check_angle_format,
-    check_axis_format,
-    check_vector_type,
-    check_path_format,
 )
 
 
@@ -43,11 +40,10 @@ def multi_anchor_behavior(anchor, inrotQ, rotation):
     return anchor, inrotQ, rotation
 
 
-def path_padding_param(scalar_input: bool, lenop: int, lenip: int, start):
-    """
-    compute path padding parameters
+def path_padding_param(scalar_input: bool, lenop: int, lenip: int, start: int):
+    """compute path padding parameters
 
-    Example: with start>0 input path exceeds old_path
+    Example: with start>0 and input path exceeds old_path
         old_path:            |abcdefg|
         input_path:              |xzyuvwrst|
         -> padded_old_path:  |abcdefggggggg|
@@ -70,7 +66,7 @@ def path_padding_param(scalar_input: bool, lenop: int, lenip: int, start):
     pad_behind = 0
 
     # start='auto': apply to all if scalar, append if vector
-    if start == "auto":
+    if start == 'auto':
         if scalar_input:
             start = 0
         else:
@@ -94,8 +90,7 @@ def path_padding_param(scalar_input: bool, lenop: int, lenip: int, start):
 
 
 def path_padding(inpath, start, target_object):
-    """
-    pad path of target_object and compute start- and end-index for apply_move()
+    """pad path of target_object and compute start- and end-index for apply_move()
     and apply_rotation() functions below so that ppath[start:end] = X... can be
     applied.
 
@@ -103,7 +98,7 @@ def path_padding(inpath, start, target_object):
     ----------
     inpath: user input as np.ndarray
     start: start index
-    target_object: magpylib object with position and orientation attributes
+    target_object: Magpylib object with position and orientation attributes
 
     Returns
     -------
@@ -125,8 +120,8 @@ def path_padding(inpath, start, target_object):
     # pad old path depending on input
     padding, start = path_padding_param(scalar_input, len(ppath), lenip, start)
     if padding:
-        ppath = np.pad(ppath, (padding, (0, 0)), "edge")
-        opath = np.pad(opath, (padding, (0, 0)), "edge")
+        ppath = np.pad(ppath, (padding, (0, 0)), 'edge')
+        opath = np.pad(opath, (padding, (0, 0)), 'edge')
 
     # set end-index
     end = len(ppath) if scalar_input else start + lenip
@@ -134,9 +129,8 @@ def path_padding(inpath, start, target_object):
     return ppath, opath, start, end, bool(padding)
 
 
-def apply_move(target_object, displacement, start="auto"):
-    """
-    Implementation of the move() functionality.
+def apply_move(target_object, displacement, start='auto'):
+    """Implementation of the move() functionality.
 
     Parameters
     ----------
@@ -145,7 +139,7 @@ def apply_move(target_object, displacement, start="auto"):
         If the input is scalar (shape (3,)) the operation is applied to the
         whole path. If the input is a vector (shape (n,3)), it is
         appended/merged with the existing path.
-    start: int, str, default='auto'
+    start: int, str, default=`'auto'`
         start=i applies an operation starting at the i'th path index.
         With start='auto' and scalar input the wole path is moved. With
         start='auto' and vector input the input is appended.
@@ -158,17 +152,14 @@ def apply_move(target_object, displacement, start="auto"):
     # pylint: disable=attribute-defined-outside-init
     # pylint: disable=too-many-branches
 
-    # check input types
-    if Config.checkinputs:
-        check_vector_type(displacement, "displacement")
-        check_start_type(start)
-
-    # displacement vector -> ndarray
-    inpath = np.array(displacement, dtype=float)
-
-    # check input format
-    if Config.checkinputs:
-        check_path_format(inpath, "displacement")
+    # check and format inputs
+    inpath = check_format_input_vector(
+            displacement,
+            dims=(1,2),
+            shape_m1=3,
+            sig_name='displacement',
+            sig_type='array_like (list, tuple, ndarray) with shape (3,) or (n,3)')
+    check_start_type(start)
 
     # pad target_object path and compute start and end-index for rotation application
     ppath, opath, start, end, padded = path_padding(inpath, start, target_object)
@@ -182,11 +173,8 @@ def apply_move(target_object, displacement, start="auto"):
     return target_object
 
 
-def apply_rotation(
-    target_object, rotation: R, anchor=None, start="auto", parent_path=None
-):
-    """
-    Implementation of the rotate() functionality.
+def apply_rotation(target_object, rotation: R, anchor=None, start='auto', parent_path=None):
+    """Implementation of the rotate() functionality.
 
     Parameters
     ----------
@@ -197,7 +185,7 @@ def apply_rotation(
         appended/merged with the existing path.
     anchor: array_like shape (3,)
         Rotation anchor
-    start: int, str, default='auto'
+    start: int, str, default=`'auto'`
         start=i applies an operation starting at the i'th path index.
         With start='auto' and scalar input the wole path is moved. With
         start='auto' and vector input the input is appended.
@@ -210,25 +198,13 @@ def apply_rotation(
     # pylint: disable=protected-access
     # pylint: disable=too-many-branches
 
-    # check input types
-    if Config.checkinputs:
-        check_rot_type(rotation)
-        check_anchor_type(anchor)
-        check_start_type(start)
-
-    # input -> quaternion ndarray
-    inrotQ = rotation.as_quat()
+    # check and format inputs
+    rotation, inrotQ = check_format_input_orientation(rotation)
+    anchor = check_format_input_anchor(anchor)
+    check_start_type(start)
 
     # when an anchor is given
     if anchor is not None:
-        # 0-anchor -> (0,0,0)
-        if np.isscalar(anchor) and anchor == 0:
-            anchor = np.array((0.0, 0.0, 0.0))
-        else:
-            anchor = np.array(anchor, dtype=float)
-        # check anchor input format
-        if Config.checkinputs:
-            check_anchor_format(anchor)
         # apply multi-anchor behavior
         anchor, inrotQ, rotation = multi_anchor_behavior(anchor, inrotQ, rotation)
 
@@ -270,51 +246,35 @@ def apply_rotation(
 
 
 class BaseTransform:
-    """
-    Inherit this class to provide rotation() and move() methods.
-
-    All rotate_from_XXX methods simply generate a scipy Rotation object and hand it
-    over to the main rotate() method. This then uses the apply_rotation function to
-    apply the rotations to all target objects.
-
-    - For Magpylib objects that inherit BaseRotate and BaseGeo (e.g. Cuboid()),
-      apply_rotation() is applied only to the object itself.
-    - Collections inherit only BaseRotate. In this case apply_rotation() is only
-      applied to the Collection children.
-    - Compounds are user-defined classes that inherit Collection but also inherit
-      BaseGeo. In this case apply_rotation() is applied to the object itself, but also
-      to its children.
-    """
-
-    def move(self, displacement, start="auto"):
-        """
-
-        Input Statement
-        If the input is a scalar the operation is applied to the whole path. If the
-        input is a vector, it is merged with the existing path.
-
-        Start Statement
-        start=i applies an operation starting at the i'th path index. start=None applies
-        an operation to the whole path.
-
-        Translates the object by the input displacement (can be a path).
-
-        This method uses vector addition to merge the input path given by displacement and the
-        existing old path of an object. It keeps the old orientation. If the input path extends
-        beyond the old path, the old path will be padded by its last entry before paths are
-        added up.
+    """Inherit this class to provide rotation() and move() methods."""
+
+    def move(self, displacement, start='auto'):
+        """Move object by the displacement input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        displacement: array_like, shape (3,) or (N,3)
-            Displacement vector shape=(3,) or path shape=(N,3) in units of [mm].
+        displacement: array_like, shape (3,) or (n,3)
+            Displacement vector in units of [mm].
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will start at the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
         Returns
         -------
@@ -323,87 +283,80 @@ def move(self, displacement, start="auto"):
         Examples
         --------
 
-        With the ``move`` method Magpylib objects can be repositioned in the global coordinate
-        system:
+        Move objects around with scalar input:
 
         >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor()
-        >>> print(sensor.position)
-        [0. 0. 0.]
-        >>> sensor.move((1,1,1))
-        >>> print(sensor.position)
+        >>> sens = magpy.Sensor(position=(1,1,1))
+        >>> print(sens.position)
         [1. 1. 1.]
+        >>> sens.move((1,1,1))
+        >>> print(sens.position)
+        [2. 2. 2.]
 
-        It is also a powerful tool for creating paths:
+        Create len>1 object paths with vector input:
 
-        >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor()
-        >>> sensor.move((1,1,1), start='append')
-        >>> print(sensor.position)
-        [[0. 0. 0.]
-         [1. 1. 1.]]
-        >>> sensor.move([(.1,.1,.1)]*2, start='append')
-        >>> print(sensor.position)
-        [[0.  0.  0. ]
-         [1.  1.  1. ]
-         [1.1 1.1 1.1]
-         [1.1 1.1 1.1]]
-
-        Complex paths can be generated with ease, by making use of the ``increment`` keyword
-        and superposition of subsequent paths:
+        >>> sens.move([(1,1,1),(2,2,2),(3,3,3)])
+        >>> print(sens.position)
+        [[2. 2. 2.]
+         [3. 3. 3.]
+         [4. 4. 4.]
+         [5. 5. 5.]]
 
-        >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor()
-        >>> sensor.move([(1,1,1)]*4, start='append', increment=True)
-        >>> print(sensor.position)
-        [[0. 0. 0.]
-         [1. 1. 1.]
-         [2. 2. 2.]
+        Apply operations starting with a designated path index:
+
+        >>> sens.move((0,0,2), start=2)
+        >>> print(sens.position)
+        [[2. 2. 2.]
          [3. 3. 3.]
-         [4. 4. 4.]]
-        >>> sensor.move([(.1,.1,.1)]*5, start=2)
-        >>> print(sensor.position)
-        [[0.  0.  0. ]
-         [1.  1.  1. ]
-         [2.1 2.1 2.1]
-         [3.1 3.1 3.1]
-         [4.1 4.1 4.1]
-         [4.1 4.1 4.1]
-         [4.1 4.1 4.1]]
+         [4. 4. 6.]
+         [5. 5. 7.]]
         """
 
         # Idea: An operation applied to a Collection is individually
         #    applied to its BaseGeo and to each child.
 
-        for child in getattr(self, "children", []):
+        for child in getattr(self, 'children', []):
             apply_move(child, displacement, start=start)
 
         apply_move(self, displacement, start=start)
 
         return self
 
-    def rotate(self, rotation: R, anchor=None, start="auto"):
-        """
-        Rotates object in the global coordinate system using a scipy Rotation object
-        as input.
+
+    def rotate(self, rotation: R, anchor=None, start='auto'):
+        """Rotate object about a given anchor.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        rotation: scipy Rotation object
-            Rotation to be applied to existing object orientation. The scipy Rotation
-            object can be of shape (3,) or (N,3).
+        rotation: `None` or scipy `Rotation` object
+            Rotation to be applied to the object. The scipy `Rotation` input can
+            be scalar or vector type (see terminology above). `None` input is interpreted
+            as unit rotation.
 
-        anchor: None, 0 or array_like, shape (3,), default=None
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
         Returns
         -------
@@ -411,24 +364,39 @@ def rotate(self, rotation: R, anchor=None, start="auto"):
 
         Examples
         --------
-        >>> from scipy.spatial.transform import Rotation as R
-        >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
 
-        print initial position and orientation
+        Rotate an object about the origin:
 
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
-        [1. 0. 0.]
-        [0. 0. 0.]
+        >>> from scipy.spatial.transform import Rotation as R
+        >>> import magpylib as magpy
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate(R.from_euler('z', 45, degrees=True), anchor=0)
+        >>> print(sens.position)
+        [0.70710678 0.70710678 0.        ]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [ 0.  0. 45.]
 
-        rotate and print resulting position and orientation
+        Rotate the object about itself:
 
-        >>> s.rotate(R.from_euler('z', 45, degrees=True), anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
+        >>> sens.rotate(R.from_euler('z', 45, degrees=True))
+        >>> print(sens.position)
         [0.70710678 0.70710678 0.        ]
-        [ 0.  0. 45.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [ 0.  0. 90.]
+
+        Create a rotation path by rotating in several steps about an anchor:
+
+        >>> sens.rotateR.from_euler('z', (15,30,45), degrees=True, anchor=(0,0,0))
+        >>> print(sens.position)
+        [[ 7.07106781e-01  7.07106781e-01  0.00000000e+00]
+         [ 5.00000000e-01  8.66025404e-01  0.00000000e+00]
+         [ 2.58819045e-01  9.65925826e-01  0.00000000e+00]
+         [ 0.0             1.0             0.0           ]]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [[  0.   0.  90.]
+         [  0.   0. 105.]
+         [  0.   0. 120.]
+         [  0.   0. 135.]]
         """
 
         # pylint: disable=no-member
@@ -437,7 +405,7 @@ def rotate(self, rotation: R, anchor=None, start="auto"):
         #    applied to its BaseGeo and to each child.
         #  -> this automatically generates the rotate-Compound behavior
 
-        for child in getattr(self, "children", []):
+        for child in getattr(self, 'children', []):
             apply_rotation(
                 child, rotation, anchor=anchor, start=start, parent_path=self._position
             )
@@ -446,35 +414,46 @@ def rotate(self, rotation: R, anchor=None, start="auto"):
 
         return self
 
-    def rotate_from_angax(self, angle, axis, anchor=None, start="auto", degrees=True):
-        """
-        Rotates object in the global coordinate system from angle-axis input.
+
+    def rotate_from_angax(self, angle, axis, anchor=None, start='auto', degrees=True):
+        """Rotates object using angle-axis input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        angle: int/float or array_like with shape (n,) unit [deg] (by default)
-            Angle of rotation, or a vector of n angles defining a rotation path in units
-            of [deg] (by default).
+        angle: int, float or array_like with shape (n,)
+            Angle(s) of rotation in units of [deg] (by default).
 
         axis: str or array_like, shape (3,)
             The direction of the axis of rotation. Input can be a vector of shape (3,)
             or a string 'x', 'y' or 'z' to denote respective directions.
 
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
-        degrees: bool, default=True
-            By default angle is given in units of [deg]. If degrees=False, angle is given
-            in units of [rad].
+        degrees: bool, default=`True`
+            Interpret input in units of [deg] or [rad].
 
         Returns
         -------
@@ -482,103 +461,96 @@ def rotate_from_angax(self, angle, axis, anchor=None, start="auto", degrees=True
 
         Examples
         --------
-        >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
+        Rotate an object about the origin:
 
-        print initial position and orientation
-
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
-        [1. 0. 0.]
-        [0. 0. 0.]
+        >>> import magpylib as magpy
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_angax(45, axis='z', anchor=0)
+        >>> print(sens.position)
+        [0.70710678 0.70710678 0.        ]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  45.]
 
-        rotate and print resulting position and orientation
+        Rotate the object about itself:
 
-        >>> s.rotate_from_angax(45, (0,0,1), anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
+        >>> sens.rotate_from_angax(45, axis=(0,0,1))
+        >>> print(sens.position)
         [0.70710678 0.70710678 0.        ]
-        [ 0.  0. 45.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  90.]
+
+        Create a rotation path by rotating in several steps about an anchor:
+
+        >>> sens.rotate_from_angax((15,30,45), axis='z', anchor=(0,0,0))
+        >>> print(sens.position)
+        [[ 7.07106781e-01  7.07106781e-01  0.00000000e+00]
+         [ 5.00000000e-01  8.66025404e-01  0.00000000e+00]
+         [ 2.58819045e-01  9.65925826e-01  0.00000000e+00]
+         [ 0.              1.              0.            ]]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [[  0.   0.  90.]
+         [  0.   0. 105.]
+         [  0.   0. 120.]
+         [  0.   0. 135.]]
         """
-
-        # check input types
-        if Config.checkinputs:
-            check_angle_type(angle)
-            check_axis_type(axis)
-            check_anchor_type(anchor)
-            check_start_type(start)
-            check_degree_type(degrees)
-
-        # generate axis from string
-        if isinstance(axis, str):
-            axis = (
-                (1, 0, 0)
-                if axis == "x"
-                else (0, 1, 0)
-                if axis == "y"
-                else (0, 0, 1)
-                if axis == "z"
-                else MagpylibBadUserInput(f'Bad axis string input "{axis}"')
-            )
-
-        # input is scalar or vector
-        is_scalar = isinstance(angle, (int, float))
-
-        # secure type - scalar angle will become a float
-        angle = np.array(angle, dtype=float)
-        axis = np.array(axis, dtype=float)
-
-        # format checks
-        if Config.checkinputs:
-            check_angle_format(angle)
-            check_axis_format(axis)
-            # anchor check in .rotate()
-
-        # Config.checkinputs format checks (after type secure)
-        # axis.shape != (3,)
-        # axis must not be (0,0,0)
+        # check/format inputs
+        angle = check_format_input_angle(angle)
+        axis = check_format_input_axis(axis)
+        check_start_type(start)
+        check_degree_type(degrees)
 
         # degree to rad
         if degrees:
             angle = angle / 180 * np.pi
 
-        # apply rotation
-        if is_scalar:
+        # create rotation vector from angle/axis input
+        if isinstance(angle, numbers.Number):
             angle = np.ones(3) * angle
         else:
             angle = np.tile(angle, (3, 1)).T
+        axis = axis / np.linalg.norm(axis) * angle
 
-        # generate rotation object from rotvec
-        axis = axis / np.linalg.norm(axis)
-        rot = R.from_rotvec(axis * angle)
-
+        # forwards rotation object to rotate method
+        rot = R.from_rotvec(axis)
         return self.rotate(rot, anchor, start)
 
-    def rotate_from_rotvec(self, rotvec, anchor=None, start="auto", degrees=False):
-        """
-        Rotates object in the global coordinate system from rotation vector input. (vector
-        direction is the rotation axis, vector length is the rotation angle in [rad])
+
+    def rotate_from_rotvec(self, rotvec, anchor=None, start='auto', degrees=True):
+        """Rotates object using rotation vector input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        rotvec : array_like, shape (N, 3) or (3,)
-            A single vector or a stack of vectors, where `rot_vec[i]` gives
-            the ith rotation vector.
+        rotvec : array_like, shape (n,3) or (3,)
+            Rotation input. Rotation vector direction is the rotation axis, vector length is
+            the rotation angle in units of [rad].
 
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
-        degrees : bool, default False
-            If True, then the given angles are assumed to be in degrees.
+        degrees: bool, default=`True`
+            Interpret input in units of [deg] or [rad].
 
         Returns
         -------
@@ -586,57 +558,83 @@ def rotate_from_rotvec(self, rotvec, anchor=None, start="auto", degrees=False):
 
         Examples
         --------
-        >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
-
-        print initial position and orientation
+        Rotate an object about the origin:
 
-        >>> print(s.position)
-        >>> print(s.orientation.as_rotvec())
-        [1. 0. 0.]
-        [0. 0. 0.]
+        >>> import magpylib as magpy
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_rotvec((0,0,45), anchor=0)
+        >>> print(sens.position)
+        [0.70710678 0.70710678 0.        ]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  45.]
 
-        rotate and print resulting position and orientation
+        Rotate the object about itself:
 
-        >>> s.rotate_from_rotvec((0,0,1), anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_rotvec())
-        [0.54030231 0.84147098 0.        ]
-        [0. 0. 1.]
+        >>> sens.rotate_from_rotvec((0,0,45))
+        >>> print(sens.position)
+        [0.70710678 0.70710678 0.        ]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  90.]
+
+        Create a rotation path by rotating in several steps about an anchor:
+
+        >>> sens.rotate_from_rotvec([(0,0,15), (0,0,30), (0,0,45)], anchor=(0,0,0))
+        >>> print(sens.position)
+        [[ 7.07106781e-01  7.07106781e-01  0.00000000e+00]
+         [ 5.00000000e-01  8.66025404e-01  0.00000000e+00]
+         [ 2.58819045e-01  9.65925826e-01  0.00000000e+00]
+         [ 0.              1.              0.            ]]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [[  0.   0.  90.]
+         [  0.   0. 105.]
+         [  0.   0. 120.]
+         [  0.   0. 135.]]
         """
         rot = R.from_rotvec(rotvec, degrees=degrees)
         return self.rotate(rot, anchor=anchor, start=start)
 
-    def rotate_from_euler(self, seq, angles, anchor=None, start="auto", degrees=False):
-        """
-        Rotates object in the global coordinate system from Euler angle input.
+
+    def rotate_from_euler(self, angle, seq, anchor=None, start='auto', degrees=True):
+        """Rotates object using Euler angle input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
+        angle: int, float or array_like with shape (n,)
+            Angle(s) of rotation in units of [deg] (by default).
+
         seq : string
             Specifies sequence of axes for rotations. Up to 3 characters
             belonging to the set {'X', 'Y', 'Z'} for intrinsic rotations, or
             {'x', 'y', 'z'} for extrinsic rotations. Extrinsic and intrinsic
             rotations cannot be mixed in one function call.
 
-        angles : float or array_like, shape (N,) or (N, [1 or 2 or 3])
-            Euler angles specified in radians (`degrees` is False) or degrees
-            (`degrees` is True).
-
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
-        degrees : bool, default False
-            If True, then the given angles are assumed to be in degrees.
+        degrees: bool, default=`True`
+            Interpret input in units of [deg] or [rad].
 
         Returns
         -------
@@ -644,49 +642,75 @@ def rotate_from_euler(self, seq, angles, anchor=None, start="auto", degrees=Fals
 
         Examples
         --------
-        >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
-
-        print initial position and orientation
+        Rotate an object about the origin:
 
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
-        [1. 0. 0.]
-        [0. 0. 0.]
+        >>> import magpylib as magpy
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_euler(45, 'z', anchor=0)
+        >>> print(sens.position)
+        [0.70710678 0.70710678 0.        ]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  45.]
 
-        rotate and print resulting position and orientation
+        Rotate the object about itself:
 
-        >>> s.rotate_from_euler('z', 45, anchor=0, degrees=True)
-        >>> print(s.position)
-        >>> print(s.orientation.as_euler('xyz', degrees=True))
+        >>> sens.rotate_from_euler(45, 'z')
+        >>> print(sens.position)
         [0.70710678 0.70710678 0.        ]
-        [ 0.  0. 45.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [0.  0.  90.]
+
+        Create a rotation path by rotating in several steps about an anchor:
+
+        >>> sens.rotate_from_euler((15,30,45), 'z', anchor=(0,0,0))
+        >>> print(sens.position)
+        [[ 7.07106781e-01  7.07106781e-01  0.00000000e+00]
+         [ 5.00000000e-01  8.66025404e-01  0.00000000e+00]
+         [ 2.58819045e-01  9.65925826e-01  0.00000000e+00]
+         [ 0.              1.              0.            ]]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [[  0.   0.  90.]
+         [  0.   0. 105.]
+         [  0.   0. 120.]
+         [  0.   0. 135.]]
         """
-        rot = R.from_euler(seq, angles, degrees=degrees)
+        rot = R.from_euler(seq, angle, degrees=degrees)
         return self.rotate(rot, anchor=anchor, start=start)
 
-    def rotate_from_matrix(self, matrix, anchor=None, start="auto"):
-        """
-        Rotates object in the global coordinate system from matrix input.
-        (see scipy rotation package matrix input)
+
+    def rotate_from_matrix(self, matrix, anchor=None, start='auto'):
+        """Rotates object using matrix input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
+
 
         Parameters
         ----------
-        matrix : array_like, shape (N, 3, 3) or (3, 3)
-            A single matrix or a stack of matrices, where `matrix[i]` is
-            the i-th matrix.
+        matrix : array_like, shape (n,3,3) or (3,3)
+            Rotation input. See scipy.spatial.transform.Rotation for details.
 
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
         Returns
         -------
@@ -694,52 +718,60 @@ def rotate_from_matrix(self, matrix, anchor=None, start="auto"):
 
         Examples
         --------
-        >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
-
-        print initial position and orientation
+        Rotate an object about the origin:
 
-        >>> print(s.position)
-        >>> print(s.orientation.as_matrix())
-        [1. 0. 0.]
-        [[1. 0. 0.]
-         [0. 1. 0.]
-         [0. 0. 1.]]
+        >>> import magpylib as magpy
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_matrix([(0,-1,0),(1,0,0),(0,0,1)], anchor=0)
+        >>> print(sens.position)
+        [0. 1. 0.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [ 0.  0. 90.]
 
-        rotate and print resulting position and orientation
+        Rotate the object about itself:
 
-        >>> s.rotate_from_matrix([(0,-1,0),(1,0,0),(0,0,1)], anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_matrix())
+        >>> sens.rotate_from_matrix([(0,-1,0),(1,0,0),(0,0,1)])
+        >>> print(sens.position)
         [0. 1. 0.]
-        [[ 0. -1.  0.]
-         [ 1.  0.  0.]
-         [ 0.  0.  1.]]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [  0.   0. 180.]
         """
         rot = R.from_matrix(matrix)
         return self.rotate(rot, anchor=anchor, start=start)
 
-    def rotate_from_mrp(self, mrp, anchor=None, start="auto"):
-        """
-        Rotates object in the global coordinate system from Modified Rodrigues Parameters input.
-        (see scipy rotation package Modified Rodrigues Parameters (MRPs))
+    def rotate_from_mrp(self, mrp, anchor=None, start='auto'):
+        """Rotates object using Modified Rodrigues Parameters (MRPs) input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        mrp : array_like, shape (N, 3) or (3,)
-            A single vector or a stack of vectors, where `mrp[i]` gives the ith set of MRPs.
+        mrp : array_like, shape (n,3) or (3,)
+            Rotation input. See scipy Rotation package for details on Modified Rodrigues
+            Parameters (MRPs).
 
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
         Returns
         -------
@@ -747,49 +779,60 @@ def rotate_from_mrp(self, mrp, anchor=None, start="auto"):
 
         Examples
         --------
+        Rotate an object about the origin:
+
         >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_mrp((0,0,1), anchor=0)
+        >>> print(sens.position)
+        [-1.  0.  0.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [  0.   0. 180.]
 
-        print initial position and orientation
+        Rotate the object about itself:
 
-        >>> print(s.position)
-        >>> print(s.orientation.as_mrp())
-        [1. 0. 0.]
+        >>> sens.rotate_from_matrix([(0,-1,0),(1,0,0),(0,0,1)])
+        >>> print(sens.position)
+        [-1.  1.  0.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
         [0. 0. 0.]
-
-        rotate and print resulting position and orientation
-
-        >>> s.rotate_from_mrp((0,0,1), anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_mrp())
-        [-1.  0.  0.]
-        [0. 0. 1.]
         """
         rot = R.from_mrp(mrp)
         return self.rotate(rot, anchor=anchor, start=start)
 
-    def rotate_from_quat(self, quat, anchor=None, start="auto"):
-        """
-        Rotates object in the global coordinate system from Quaternion input.
+
+    def rotate_from_quat(self, quat, anchor=None, start='auto'):
+        """Rotates object using quaternion input.
+
+        Terminology for move/rotate methods:
+        - 'path' refers to `position` and `orientation` of an object.
+        - When an input is just a single operation (e.g. one displacement vector or one angle)
+        we call it 'scalar input'. When it is an array_like of multiple scalars, we refer to
+        it as 'vector input'.
+
+        General move/rotate behavior:
+        - Scalar input is applied to the whole object path, starting with path index `start`.
+        - Vector input of length n applies the individual n operations to n object path
+        entries, starting with path index `start`.
+        - When an input extends beyond the object path, the object path will be padded by its
+        edge-entries before the operation is applied.
+        - By default (`start='auto'`) the index is set to `start=0` for scalar input [=move
+        whole object path], and to `start=len(object path)` for vector input [=append to
+        existing object path].
 
         Parameters
         ----------
-        quat : array_like, shape (N, 4) or (4,)
-            Each row is a (possibly non-unit norm) quaternion in scalar-last
-            (x, y, z, w) format. Each quaternion will be normalized to unit
-            norm.
+        quat : array_like, shape (n,4) or (4,)
+            Rotation input in quaternion form.
 
-        anchor: None or array_like, shape (3,), default=None, unit [mm]
+        anchor: `None`, `0` or array_like with shape (3,) or (n,3), default=`None`
             The axis of rotation passes through the anchor point given in units of [mm].
             By default (`anchor=None`) the object will rotate about its own center.
-            `anchor=0` rotates the object about the origin (0,0,0).
+            `anchor=0` rotates the object about the origin `(0,0,0)`.
 
-        start: int or str, default=-1
-            Choose at which index of the original object path, the input path will begin.
-            If `start=-1`, inp_path will overwrite the last old_path position.
-            If `start=0`, inp_path will start with the beginning of the old_path.
-            If `start=len(old_path)` or `start='append'`, inp_path will be attached to
-            the old_path.
+        start: int or str, default=`'auto'`
+            Starting index when applying operations. See 'General move/rotate behavior' above
+            for details.
 
         Returns
         -------
@@ -797,21 +840,23 @@ def rotate_from_quat(self, quat, anchor=None, start="auto"):
 
         Examples
         --------
+        Rotate an object about the origin:
+
         >>> import magpylib as magpy
-        >>> s = magpy.Sensor(position=(1,0,0))
-
-        print initial position and orientation
-        >>> print(s.position)
-        >>> print(s.orientation.as_quat())
-        [1. 0. 0.]
-        [0. 0. 0. 1.]
-
-        rotate and print resulting position and orientation
-        >>> s.rotate_from_quat((0,0,1,1), anchor=0)
-        >>> print(s.position)
-        >>> print(s.orientation.as_quat())
+        >>> sens = magpy.Sensor(position=(1,0,0))
+        >>> sens.rotate_from_quat((0,0,1,1), anchor=0)
+        >>> print(sens.position)
+        [0. 1. 0.]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [ 0.  0. 90.]
+
+        Rotate the object about itself:
+
+        >>> sens.rotate_from_quat((0,0,1,1))
+        >>> print(sens.position)
         [0. 1. 0.]
-        [0.         0.         0.70710678 0.70710678]
+        >>> print(sens.orientation.as_euler('xyz', degrees=True))
+        [  0.   0. 180.]
         """
         rot = R.from_quat(quat)
         return self.rotate(rot, anchor=anchor, start=start)
diff --git a/magpylib/_src/obj_classes/class_Collection.py b/magpylib/_src/obj_classes/class_Collection.py
index 2bd9199a5..b84a28e72 100644
--- a/magpylib/_src/obj_classes/class_Collection.py
+++ b/magpylib/_src/obj_classes/class_Collection.py
@@ -1,4 +1,7 @@
-"""Collection class code"""
+"""Collection class code
+DOCSTRING v4 READY
+"""
+
 from magpylib._src.utility import (
     format_obj_input,
     check_duplicates,
@@ -12,108 +15,16 @@
 from magpylib._src.defaults.defaults_utility import validate_style_keys
 from magpylib._src.exceptions import MagpylibBadUserInput
 
-
 class BaseCollection(BaseDisplayRepr):
-    """
-    Group multiple children in one Collection for common manipulation.
-
-    Operations applied to a Collection are sequentially applied to all children in the Collection.
-    Collections do not allow duplicate children (will be eliminated automatically).
-
-    Collections have the following dunders defined: __add__, __sub__, __iter__, __getitem__,
-    __repr__.
-
-    Depending on the input, Collection children can be sources, observers or both. A Collection
-    with only source children will become either a SourceCollection, one with only Sensors a
-    SensorCollection, and one with sources and sensors a MixedCollection
-    A SourceCollection functions like any SINGLE source. A SensorCollection functions like a
-    list of observer inputs. A MixedCollection will function as source or as observer.
-
-    Parameters
-    ----------
-    children: sources, sensors, collections or arbitrary lists thereof
-        Ordered list of children in the Collection.
-
-    Returns
-    -------
-    Collection object: Collection
-
-    Examples
-    --------
-
-    Create Collections for common manipulation. All children added to a Collection
-    are stored in the ``children`` attribute, and additionally in the ``sensors``
-    and ``sources`` attributes. These three return ordered sets (lists with
-    unique elements only)
-
-    >>> import magpylib as magpy
-    >>> sphere = magpy.magnet.Sphere((1,2,3),1)
-    >>> loop = magpy.current.Loop(1,1)
-    >>> dipole = magpy.misc.Dipole((1,2,3))
-    >>> col = magpy.Collection(sphere, loop, dipole)
-    >>> print(col.children)
-    [Sphere(id=1879891544384), Loop(id=1879891543040), Dipole(id=1879892157152)]
-
-    Cycle directly through the Collection ``children`` attribute
-
-    >>> for src in col:
-    >>>    print(src)
-    Sphere(id=1879891544384)
-    Loop(id=1879891543040)
-    Dipole(id=1879892157152)
-
-    and directly access children from the Collection
-
-    >>> print(col[1])
-    Loop(id=1879891543040)
-
-    Add and subtract children to form a Collection and to remove children from a Collection.
-
-    >>> col = sphere + loop
-    >>> print(col.children)
-    [Sphere(id=1879891544384), Loop(id=1879891543040)]
-    >>> col - sphere
-    >>> print(col.children)
-    [Loop(id=1879891543040)]
-
-    Consider three collections, a SourceCollection sCol a SensorCollection xCol and a
-    MixedCollection mCol, all made up from the same children.
-
-    >>> import numpy as np
-    >>> import magpylib as magpy
-
-    >>> s1=magpy.magnet.Sphere((1,2,3), 1)
-    >>> s2=magpy.magnet.Cylinder((1,2,3), (1,1), (3,0,0))
-    >>> s3=magpy.magnet.Cuboid((1,2,3), (1,1,1), (6,0,0))
-
-    >>> x1=magpy.Sensor((1,0,3))
-    >>> x2=magpy.Sensor((4,0,3))
-    >>> x3=magpy.Sensor((7,0,3))
-
-    >>> sCol = magpy.Collection(s1, s2, s3)
-    >>> xCol = magpy.Collection(x1, x2, x3)
-    >>> mCol = magpy.Collection(sCol, xCol)
-
-    All the following lines will all give the same output
-
-    >>> magpy.getB([s1,s2,s3], [x1,x2,x3], sumup=True)
-    >>> magpy.getB(sCol, xCol)
-    >>> magpy.getB(mCol, mCol)
-    >>> sCol.getB(xCol)
-    >>> xCol.getB(sCol)
-    >>> sCol.getB(mCol)
-    >>> xCol.getB(mCol)
-    >>> mCol.getB()
+    """ Collection base class without BaseGeo properties
     """
 
     def __init__(self, *children):
 
-        self._object_type = "Collection"
+        self._object_type = 'Collection'
 
-        # init inheritance
         BaseDisplayRepr.__init__(self)
 
-        # instance attributes
         self._children = []
         self._sources = []
         self._sensors = []
@@ -171,43 +82,41 @@ def __len__(self):
 
     def __repr__(self) -> str:
         # pylint: disable=protected-access
-        if not self._sources:
-            pref = "Sensor"
-        elif not self._sensors:
-            pref = "Source"
-        else:
-            pref = "Mixed"
         s = super().__repr__()
-        return f"{pref}{s}"
+        if self._children:
+            if not self._sources:
+                pref = 'Sensor'
+            elif not self._sensors:
+                pref = 'Source'
+            else:
+                pref = 'Mixed'
+            return f"{pref}{s}"
+        return s
 
     # methods -------------------------------------------------------
     def add(self, *children):
-        """
-        Add arbitrary Magpylib children or Collections.
+        """Add sources, sensors or collections.
 
         Parameters
         ----------
-        children: sources, Sensors, Collections or arbitrary lists thereof
-            Add arbitrary sequences of children and Collections to the Collection.
-            The new children will be added at the end of self.children. Duplicates
-            will be eliminated.
+        children: source, `Sensor` or `Collection` objects or arbitrary lists thereof
+            Add arbitrary sources, sensors or other collections to this collection.
+            Duplicate children will automatically be eliminated.
 
         Returns
         -------
-        self: Collection
+        self: `Collection` object
 
         Examples
         --------
-
-        Add children to a Collection:
+        In this example we add a sensor object to a collection:
 
         >>> import magpylib as magpy
-        >>> src = magpy.current.Loop(1,1)
         >>> col = magpy.Collection()
-        >>> col.add(src)
+        >>> sens = magpy.Sensor()
+        >>> col.add(sens)
         >>> print(col.children)
-        [Loop(id=2519738714432)]
-
+        [Sensor(id=2236606343584)]
         """
         # format input
         obj_list = format_obj_input(children)
@@ -231,32 +140,30 @@ def _update_src_and_sens(self):
         ]
 
     def remove(self, child):
-        """
-        Remove a specific child from the Collection.
+        """Remove a specific child from the collection.
 
         Parameters
         ----------
         child: child object
-            Remove the given child from the Collection.
+            Remove the given child from the collection.
 
         Returns
         -------
-        self: Collection
+        self: `Collection` object
 
         Examples
         --------
-        Remove a specific child from a Collection:
+        In this example we remove a child from a Collection:
 
         >>> import magpylib as magpy
-        >>> src1 = magpy.current.Loop(1,1)
-        >>> src2 = magpy.current.Loop(1,1)
-        >>> col = src1 + src2
-        >>> print(col.children)
-        [Loop(id=2405009623360), Loop(id=2405010235504)]
-        >>> col.remove(src1)
+        >>> sens = magpy.Sensor()
+        >>> col = magpy.Collection(sens)
         >>> print(col.children)
-        [Loop(id=2405010235504)]
+        [Sensor(id=2048351734560)]
 
+        >>> col.remove(sens)
+        >>> print(col.children)
+        []
         """
         self._children.remove(child)
         self._update_src_and_sens()
@@ -264,36 +171,32 @@ def remove(self, child):
 
 
     def set_children_styles(self, arg=None, **kwargs):
-        """
-        Set display style of all children in the Collection. Only matching properties
+        """Set display style of all children in the collection. Only matching properties
         will be applied. Input can be a style-dict or style-underscore_magic.
 
         Returns
         -------
-        self
+        self: `Collection` object
 
         Examples
         --------
-        Apply a style to all children inside a Collection using a style-dict or
-        style-underscore_magic.
+        In this example we start by creating a collection from three sphere magnets:
 
         >>> import magpylib as magpy
-        >>>
-        >>> # create collection
         >>> col = magpy.Collection()
         >>> for i in range(3):
-        >>>     col + magpy.magnet.Sphere((1,1,1), 1, (i,0,0))
-        >>>
-        >>> # separate object
-        >>> src = magpy.magnet.Sphere((1,1,1), 1, (3,0,0))
-        >>>
-        >>> # set collection style
-        >>> col.set_children_styles(color='g')
-        >>>
-        >>> # set collection style with style-dict
-        >>> style_dict = {'magnetization_size':0.5}
-        >>> col.set_children_styles(style_dict)
-        >>>
+        >>>     col = col + magpy.magnet.Sphere((0,0,1), 1, position=(i,0,0))
+
+        We apply styles using underscore magic for magnetization vector size and a style
+        dictionary for the color.
+
+        >>> col.set_children_styles(magnetization_size=0.5)
+        >>> col.set_children_styles({'color':'g'})
+
+        Finally we create a separate sphere magnet to demonstrate the default style and display
+        the collection and the separate magnet with Matplotlib:
+
+        >>> src = magpy.magnet.Sphere((0,0,1), 1, position=(3,0,0))
         >>> magpy.show(col, src)
         ---> graphic output
         """
@@ -323,8 +226,8 @@ def _validate_getBH_inputs(self, *children):
             sources, sensors = self, self
             if children:
                 raise MagpylibBadUserInput(
-                    "No inputs allowed for a Mixed Collection, "
-                    "since it already has Sensors and Sources"
+                    "Collections with sensors and sources do not allow `collection.getB()` inputs."
+                    "Consider using `magpy.getB()` instead."
                 )
         elif not sources:
             sources, sensors = children, self
@@ -332,58 +235,102 @@ def _validate_getBH_inputs(self, *children):
             sources, sensors = self, children
         return sources, sensors
 
-    def getB(self, *children, squeeze=True):
-        """
-        Compute B-field in [mT] for given sources and observers.
+
+    def getB(self, *sources_observers, squeeze=True):
+        """Compute B-field in [mT] for given sources and observer inputs.
 
         Parameters
         ----------
-        children: source or observer children
-            If parent is a SourceCollection, input can only be M observers.
-            If parent is a SensorCollection, input can only be L sources.
+        sources_observers: source or observer inputs
+            Input can only be observers if the collection contains only sources. In this case the
+            collection behaves like a single source.
+            Input can only be sources if the collection contains only sensors. In this case the
+            collection behaves like a list of all its sensors.
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
-            a single sensor or only a single source) are eliminated.
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
+            only a single source) are eliminated.
 
         Returns
         -------
-        B-field: ndarray, shape squeeze(L, M, N1, N2, ..., 3)
-            B-field of each source (L) at each path position (M) and each sensor pixel
-            position (N1,N2,...) in units of [mT]. Paths of children that are shorter than
-            M will be considered as static beyond their end.
+        B-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+            B-field at each path position (m) for each sensor (k) and each sensor pixel
+            position (n1,n2,...) in units of [mT]. Sensor pixel positions are equivalent
+            to simple observer positions. Paths of objects that are shorter than m will be
+            considered as static beyond their end.
 
         Examples
         --------
+        In this example we create a collection from two sources and two sensors:
+
+        >>> import magpylib as magpy
+        >>> src1 = magpy.magnet.Sphere((0,0,1000), 1)
+        >>> src2 = src1.copy()
+        >>> sens1 = magpy.Sensor(position=(0,0,1))
+        >>> sens2 = sens1.copy()
+        >>> col = src1 + src2 + sens1 + sens2
+
+        The following computations all give the same result:
+
+        >>> B = col.getB()
+        >>> B = magpy.getB(col, col)
+        >>> B = magpy.getB(col, [sens1, sens2])
+        >>> B = magpy.getB([src1, src2], col)
+        >>> B = magpy.getB([src1, src2], [sens1, sens2])
+        >>> print(B)
+        [[  0.           0.         166.66666667]
+         [  0.           0.         166.66666667]]
         """
 
-        sources, sensors = self._validate_getBH_inputs(*children)
+        sources, sensors = self._validate_getBH_inputs(*sources_observers)
 
         return getBH_level2(sources, sensors, sumup=False, squeeze=squeeze, field='B')
 
+
     def getH(self, *children, squeeze=True):
-        """
-        Compute H-field in [kA/m] for given sources and observers.
+        """Compute H-field in [kA/m] for given sources and observer inputs.
 
         Parameters
         ----------
-        children: source or observer children
-            If parent is a SourceCollection, input can only be M observers.
-            If parent is a SensorCollection, input can only be L sources.
+        sources_observers: source or observer inputs
+            Input can only be observers if the collection contains only sources. In this case the
+            collection behaves like a single source.
+            Input can only be sources if the collection contains sensors. In this case the
+            collection behaves like a list of all its sensors.
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
-            a single sensor or only a single source) are eliminated.
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g.
+            only a single source) are eliminated.
 
         Returns
         -------
-        H-field: ndarray, shape squeeze(L, M, N1, N2, ..., 3)
-            H-field of each source (L) at each path position (M) and each sensor pixel
-            position (N1,N2,...) in units of [kA/m]. Paths of children that are shorter than
-            M will be considered as static beyond their end.
+        H-field: ndarray, shape squeeze(m, k, n1, n2, ..., 3)
+            H-field at each path position (m) for each sensor (k) and each sensor pixel
+            position (n1,n2,...) in units of [kA/m]. Sensor pixel positions are equivalent
+            to simple observer positions. Paths of objects that are shorter than m will be
+            considered as static beyond their end.
 
         Examples
         --------
+        In this example we create a collection from two sources and two sensors:
+
+        >>> import magpylib as magpy
+        >>> src1 = magpy.magnet.Sphere((0,0,1000), 1)
+        >>> src2 = src1.copy()
+        >>> sens1 = magpy.Sensor(position=(0,0,1))
+        >>> sens2 = sens1.copy()
+        >>> col = src1 + src2 + sens1 + sens2
+
+        The following computations all give the same result:
+
+        >>> H = col.getH()
+        >>> H = magpy.getH(col, col)
+        >>> H = magpy.getH(col, [sens1, sens2])
+        >>> H = magpy.getH([src1, src2], col)
+        >>> H = magpy.getH([src1, src2], [sens1, sens2])
+        >>> print(H)
+        [[  0.           0.         66.31455962]
+         [  0.           0.         66.31455962]]
         """
 
         sources, sensors = self._validate_getBH_inputs(*children)
@@ -392,118 +339,91 @@ def getH(self, *children, squeeze=True):
 
 
 class Collection(BaseGeo, BaseCollection):
-    """
-    Group multiple children in one Collection for common manipulation.
-
-    Operations applied to a Collection are sequentially applied to all children in the Collection.
-    Collections do not allow duplicate children (will be eliminated automatically).
+    """ Group multiple children (sources and sensors) in one Collection for
+    common manipulation.
 
-    Collections have the following dunders defined: __add__, __sub__, __iter__, __getitem__,
-    __repr__.
+    Collections can be used as `sources` and `observers` input for magnetic field
+    computation. For magnetic field computation a collection that contains sources
+    functions like a single (compound) source. When the collection contains sensors
+    it functions like a list of all its sensors.
 
-    Depending on the input, Collection children can be sources, observers or both. A Collection
-    with only source children will become either a SourceCollection, one with only Sensors a
-    SensorCollection, and one with sources and sensors a MixedCollection
-    A SourceCollection functions like any SINGLE source. A SensorCollection functions like a
-    list of observer inputs. A MixedCollection will function as source or as observer.
+    Collections function like compound-objects. They have their own `position` and
+    `orientation` attributes. Move, rotate and setter operations acting on a
+    `Collection` object are individually applied to all child objects so that the
+    geometric compound sturcture is maintained. For example, `rotate()` with
+    `anchor=None` rotates all children about `collection.position`.
 
     Parameters
     ----------
     children: sources, sensors, collections or arbitrary lists thereof
-        Ordered list of children in the Collection.
+        Ordered list of all children.
+
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
+
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
+
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Collection object: Collection
+    collection: `Collection` object
 
     Examples
     --------
-
-    Create Collections for common manipulation. All children added to a Collection
-    are stored in the ``children`` attribute, and additionally in the ``sensors``
-    and ``sources`` attributes. These three return ordered sets (lists with
-    unique elements only)
+    Collections function as groups of multiple magpylib objects. In this example
+    we create a collection with two sources and move the whole collection:
 
     >>> import magpylib as magpy
-    >>> sphere = magpy.magnet.Sphere((1,2,3),1)
-    >>> loop = magpy.current.Loop(1,1)
-    >>> dipole = magpy.misc.Dipole((1,2,3))
-    >>> col = magpy.Collection(sphere, loop, dipole)
-    >>> print(col.children)
-    [Sphere(id=1879891544384), Loop(id=1879891543040), Dipole(id=1879892157152)]
-
-    Cycle directly through the Collection ``children`` attribute
-
-    >>> for src in col:
-    >>>    print(src)
-    Sphere(id=1879891544384)
-    Loop(id=1879891543040)
-    Dipole(id=1879892157152)
-
-    and directly access children from the Collection
-
-    >>> print(col[1])
-    Loop(id=1879891543040)
-
-    Add and subtract children to form a Collection and to remove children from a Collection.
-
-    >>> col = sphere + loop
+    >>> src1 = magpy.magnet.Sphere((1,2,3), 1, position=(2,0,0))
+    >>> src2 = magpy.current.Loop(1, 1, position=(-2,0,0))
+    >>> col = magpy.Collection(src1, src2)
+    >>> col.move(((0,0,2)))
+    >>> print(src1.position)
+    >>> print(src2.position)
+    >>> print(col.position)
+    [2. 0. 2.]
+    [-2.  0.  2.]
+    [0. 0. 2.]
+
+    We can still directly access individual objects by name and by index:
+
+    >>> src1.move((2,0,0))
+    >>> col[1].move((-2,0,0))
+    >>> print(src1.position)
+    >>> print(src2.position)
+    >>> print(col.position)
+    [4. 0. 2.]
+    [-4.  0.  2.]
+    [0. 0. 2.]
+
+    The field can be computed at position (0,0,0) as if the collection was a single source:
+
+    B = col.getB((0,0,0))
+    print(B)
+    [ 0.00126232 -0.00093169 -0.00034448]
+
+    We add a sensor at position (0,0,0) to the collection:
+
+    >>> sens = magpy.Sensor()
+    >>> col.add(sens)
     >>> print(col.children)
-    [Sphere(id=1879891544384), Loop(id=1879891543040)]
-    >>> col - sphere
-    >>> print(col.children)
-    [Loop(id=1879891543040)]
-
-    Manipulate all children in a Collection directly using ``move`` and ``rotate`` methods
-
-    >>> import magpylib as magpy
-    >>> sphere = magpy.magnet.Sphere((1,2,3),1)
-    >>> loop = magpy.current.Loop(1,1)
-    >>> col = sphere + loop
-    >>> col.move((1,1,1))
-    >>> print(sphere.position)
-    [1. 1. 1.]
-
-    and compute the total magnetic field generated by the Collection.
-
-    >>> B = col.getB((1,2,3))
-    >>> print(B)
-    [-0.00372678  0.01820438  0.03423079]
-
-    Consider three collections, a SourceCollection sCol a SensorCollection xCol and a
-    MixedCollection mCol, all made up from the same children.
-
-    >>> import numpy as np
-    >>> import magpylib as magpy
-
-    >>> s1=magpy.magnet.Sphere((1,2,3), 1)
-    >>> s2=magpy.magnet.Cylinder((1,2,3), (1,1), (3,0,0))
-    >>> s3=magpy.magnet.Cuboid((1,2,3), (1,1,1), (6,0,0))
-
-    >>> x1=magpy.Sensor((1,0,3))
-    >>> x2=magpy.Sensor((4,0,3))
-    >>> x3=magpy.Sensor((7,0,3))
-
-    >>> sCol = magpy.Collection(s1, s2, s3)
-    >>> xCol = magpy.Collection(x1, x2, x3)
-    >>> mCol = magpy.Collection(sCol, xCol)
+    [Sphere(id=2236606344304), Loop(id=2236606344256), Sensor(id=2236606343584)]
 
-    All the following lines will all give the same output
+    and can compute the field of the sources in the collection seen by the sensor with
+    a single command:
 
-    >>> magpy.getB([s1,s2,s3], [x1,x2,x3], sumup=True)
-    >>> magpy.getB(sCol, xCol)
-    >>> magpy.getB(mCol, mCol)
-    >>> sCol.getB(xCol)
-    >>> xCol.getB(sCol)
-    >>> sCol.getB(mCol)
-    >>> xCol.getB(mCol)
-    >>> mCol.getB()
+    B = col.getB()
+    print(B)
+    [ 0.00126232 -0.00093169 -0.00034448]
     """
 
-    def __init__(
-        self, *args, position=(0.0, 0.0, 0.0), orientation=None, style=None, **kwargs
-    ):
-        BaseGeo.__init__(
-            self, position=position, orientation=orientation, style=style, **kwargs
-        )
+    def __init__(self, *args, position=(0,0,0), orientation=None, style=None, **kwargs):
+        BaseGeo.__init__(self, position=position, orientation=orientation, style=style, **kwargs)
         BaseCollection.__init__(self, *args)
diff --git a/magpylib/_src/obj_classes/class_Sensor.py b/magpylib/_src/obj_classes/class_Sensor.py
index 30ad50415..b0590fc5a 100644
--- a/magpylib/_src/obj_classes/class_Sensor.py
+++ b/magpylib/_src/obj_classes/class_Sensor.py
@@ -1,102 +1,74 @@
-"""Sensor class code"""
-import numpy as np
+"""Sensor class code
+DOCSTRINGS V4 READY
+"""
+
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.utility import format_star_input
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_vector_type, check_position_format
+from magpylib._src.input_checks import check_format_input_vector
 from magpylib._src.fields.field_wrap_BH_level2 import getBH_level2
 
 
-# ON INTERFACE
 class Sensor(BaseGeo, BaseDisplayRepr):
-    """
-    Magnetic field sensor. Can be used as observer input for magnetic field
-    computation.
+    """ Magnetic field sensor.
+
+    Can be used as `observers` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` local object coordinates
+    coincide with the gobal coordinate system.
 
-    Local object coordinates: Sensor pixel (=sensing elements) are defined in the local
-    object coordinate system. Local (Sensor) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    A sensor is made up of pixel (sensing elements) where the magnetic field is evaluated.
 
     Parameters
     ----------
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    pixel: array_like, shape (3,) or (N1,N2,...,3), default=(0,0,0)
-        Sensor pixel positions (=sensing elements) in the local Sensor CS in
-        units of [mm]. The magnetic field is evaluated at Sensor pixels.
+    pixel: array_like, shape (3,) or (n1,n2,...,3), default=`(0,0,0)`
+        Sensor pixel (=sensing elements) positions in the local object coordinates
+        (rotate with object), in units of [mm].
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
+
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Sensor object: Sensor
+    observer: `Sensor` object
 
     Examples
     --------
-
-    By default a Sensor is initialized at position (0,0,0), with unit rotation
-    and pixel (0,0,0):
-
-    >>> import magpylib as magpy
-    >>> sensor = magpy.Sensor()
-    >>> print(sensor.position)
-    [0. 0. 0.]
-    >>> print(sensor.pixel)
-    [0. 0. 0.]
-    >>> print(sensor.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Sensors are observers for magnetic field computation. In this example we compute the
-    H-field as seen by the sensor in the center of a circular current loop:
+    `Sensor` objects are observers for magnetic field computation. In this example we compute the
+    B-field in units of [mT] as seen by the sensor in the center of a circular current loop:
 
     >>> import magpylib as magpy
-    >>> sensor = magpy.Sensor()
+    >>> sens = magpy.Sensor()
     >>> loop = magpy.current.Loop(current=1, diameter=1)
-    >>> H = sensor.getH(loop)
-    >>> print(H)
-    [0. 0. 1.]
+    >>> B = sens.getB(loop)
+    >>> print(B)
+    [0.         0.         1.25663706]
 
-    Field computation is performed at every pixel of a sensor. The above example is reproduced
-    for a 2x2-pixel sensor:
+    We rotate the sensor by 45 degrees and compute the field again:
 
-    >>> import magpylib as magpy
-    >>> sensor = magpy.Sensor(pixel=[[(0,0,0), (0,0,1)],[(0,0,2), (0,0,3)]])
-    >>> loop = magpy.current.Loop(current=1, diameter=1)
-    >>> H = sensor.getH(loop)
-    >>> print(H.shape)
-    (2, 2, 3)
-    >>> print(H)
-    [[[0.         0.         1.        ]
-      [0.         0.         0.08944272]]
-     [[0.         0.         0.0142668 ]
-      [0.         0.         0.00444322]]]
-
-    Compute the field of a sensor along a path. The path positions are chosen so that
-    they coincide with the pixel positions of the previous example:
+    >>> sens.rotate_from_rotvec((45,0,0))
+    >>> B = sens.getB(loop)
+    >>> print(B)
+    [0.         0.88857659 0.88857659]
 
-    >>> import magpylib as magpy
-    >>> loop = magpy.current.Loop(current=1, diameter=1)
-    >>> sensor = magpy.Sensor()
-    >>> sensor.move([(0,0,1)]*3, start=1, increment=True)
-    >>> print(sensor.position)
-    [[0. 0. 0.]
-     [0. 0. 1.]
-     [0. 0. 2.]
-     [0. 0. 3.]]
-    >>> H = sensor.getH(loop)
-    >>> print(H)
-    [[0.         0.         1.        ]
-     [0.         0.         0.08944272]
-     [0.         0.         0.0142668 ]
-     [0.         0.         0.00444322]]
+    Finally we set some sensor pixels and compute the field again:
 
+    >>> sens.pixel=((0,0,0), (.1,0,0), (.2,0,0))
+    >>> B = sens.getB(loop)
+    >>> print(B)
+    [[0.         0.88857659 0.88857659]
+        [0.         0.916274   0.916274  ]
+        [0.         1.01415383 1.01415383]]
     """
 
     def __init__(
@@ -110,7 +82,7 @@ def __init__(
 
         # instance attributes
         self.pixel = pixel
-        self._object_type = "Sensor"
+        self._object_type = 'Sensor'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -119,172 +91,128 @@ def __init__(
     # property getters and setters
     @property
     def pixel(self):
-        """Sensor pixel attribute getter and setter."""
+        """ Sensor pixel attribute getter and setter."""
         return self._pixel
 
     @pixel.setter
     def pixel(self, pix):
+        """ Set sensor pixel positions in the local sensor coordinates.
+        Must be an array_like, float compatible with shape (..., 3)
         """
-        Set Sensor pixel positions in Sensor CS, array_like, shape (...,3,)
-        """
-        # check input type
-        if Config.checkinputs:
-            check_vector_type(pix, "pixel_position")
-
-        # input type -> ndarray
-        pix = np.array(pix, dtype=float)
-
-        # check input format
-        if Config.checkinputs:
-            check_position_format(pix, "pixel_position")
+        self._pixel = check_format_input_vector(
+            pix,
+            dims=range(1,20),
+            shape_m1=3,
+            sig_name='pixel',
+            sig_type='array_like (list, tuple, ndarray) with shape (n1, n2, ..., 3)',
+            squeeze=True)
 
-        self._pixel = pix
 
-    # methods -------------------------------------------------------
     def getB(self, *sources, sumup=False, squeeze=True):
-        """
-        Compute B-field in [mT] for given sources as seen by the Sensor.
+        """Compute the B-field in units of [mT] as seen by the sensor.
 
         Parameters
         ----------
-        sources: source objects or Collections
-            Sources can be a mixture of L source objects or Collections.
+        sources: source and collection objects or 1D list thereof
+            Sources that generate the magnetic field. Can be a single source (or collection)
+            or a 1D list of l source and/or collection objects.
 
-        sumup: bool, default=False
-            If True, the fields of all sources are summed up.
+        sumup: bool, default=`False`
+            If `True`, the fields of all sources are summed up.
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
             a single sensor or only a single source) are eliminated.
 
         Returns
         -------
-        B-field: ndarray, shape squeeze(L, M, N1, N2, ..., 3)
-            B-field of each source (L) at each path position (M) and each sensor pixel
-            position (N1,N2,...) in units of [mT]. Paths of objects that are shorter than
-            M will be considered as static beyond their end.
+        B-field: ndarray, shape squeeze(l, m, n1, n2, ..., 3)
+            B-field of each source (l) at each path position (m) and each sensor pixel
+            position (n1,n2,...) in units of [mT]. Paths of objects that are shorter than
+            m will be considered as static beyond their end.
 
         Examples
         --------
-
         Sensors are observers for magnetic field computation. In this example we compute the
         B-field [mT] as seen by the sensor in the center of a circular current loop:
 
         >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor()
+        >>> sens = magpy.Sensor()
         >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> B = sensor.getB(loop)
+        >>> B = sens.getB(loop)
         >>> print(B)
         [0.         0.         1.25663706]
 
-        Field computation is performed at every pixel of a sensor. The above example is reproduced
-        for a 2x2-pixel sensor:
+        Then we rotate the sensor by 45 degrees and compute the field again:
 
-        >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor(pixel=[[(0,0,0), (0,0,1)],[(0,0,2), (0,0,3)]])
-        >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> B = sensor.getB(loop)
-        >>> print(B.shape)
-        (2, 2, 3)
+        >>> sens.rotate_from_rotvec((45,0,0))
+        >>> B = sens.getB(loop)
         >>> print(B)
-        [[[0.         0.         1.25663706]
-          [0.         0.         0.11239704]]
-         [[0.         0.         0.01792819]
-          [0.         0.         0.00558351]]]
+        [0.         0.88857659 0.88857659]
 
-        Compute the field of a sensor along a path. The path positions are chosen so that
-        they coincide with the pixel positions in the previous example.
+        Finally we set some sensor pixels and compute the field again:
 
-        >>> import magpylib as magpy
-        >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> sensor = magpy.Sensor()
-        >>> sensor.move([(0,0,1)]*3, start=1, increment=True)
-        >>> print(sensor.position)
-        [[0. 0. 0.]
-         [0. 0. 1.]
-         [0. 0. 2.]
-         [0. 0. 3.]]
-        >>> B = sensor.getB(loop)
+        >>> sens.pixel=((0,0,0), (.1,0,0), (.2,0,0))
+        >>> B = sens.getB(loop)
         >>> print(B)
-        [[0.         0.         1.25663706]
-         [0.         0.         0.11239704]
-         [0.         0.         0.01792819]
-         [0.         0.         0.00558351]]
-
+        [[0.         0.88857659 0.88857659]
+         [0.         0.916274   0.916274  ]
+         [0.         1.01415383 1.01415383]]
         """
         sources = format_star_input(sources)
         return getBH_level2(sources, self, sumup=sumup, squeeze=squeeze, field='B')
 
+
     def getH(self, *sources, sumup=False, squeeze=True):
-        """
-        Compute H-field in [kA/m] for given sources as seen by the Sensor.
+        """Compute the H-field in units of [kA/m] as seen by the sensor.
 
         Parameters
         ----------
-        sources: source objects or Collections
-            Sources can be a mixture of L source objects or Collections.
+        sources: source and collection objects or 1D list thereof
+            Sources that generate the magnetic field. Can be a single source (or collection)
+            or a 1D list of l source and/or collection objects.
 
-        sumup: bool, default=False
-            If True, the fields of all sources are summed up.
+        sumup: bool, default=`False`
+            If `True`, the fields of all sources are summed up.
 
-        squeeze: bool, default=True
-            If True, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
+        squeeze: bool, default=`True`
+            If `True`, the output is squeezed, i.e. all axes of length 1 in the output (e.g. only
             a single sensor or only a single source) are eliminated.
 
         Returns
         -------
-        H-field: ndarray, shape squeeze(L, M, N1, N2, ..., 3)
-            H-field of each source (L) at each path position (M) and each sensor pixel
-            position (N1,N2,...) in units of [kA/m]. Paths of objects that are shorter than
-            M will be considered as static beyond their end.
+        H-field: ndarray, shape squeeze(l, m, n1, n2, ..., 3)
+            H-field of each source (l) at each path position (m) and each sensor pixel
+            position (n1,n2,...) in units of [kA/m]. Paths of objects that are shorter than
+            m will be considered as static beyond their end.
 
         Examples
         --------
-
         Sensors are observers for magnetic field computation. In this example we compute the
-        H-field as seen by the sensor in the center of a circular current loop:
+        H-field [kA/m] as seen by the sensor in the center of a circular current loop:
 
         >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor()
+        >>> sens = magpy.Sensor()
         >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> H = sensor.getH(loop)
+        >>> H = sens.getH(loop)
         >>> print(H)
         [0. 0. 1.]
 
-        Field computation is performed at every pixel of a sensor. The above example is reproduced
-        for a 2x2-pixel sensor:
+        Then we rotate the sensor by 45 degrees and compute the field again:
 
-        >>> import magpylib as magpy
-        >>> sensor = magpy.Sensor(pixel=[[(0,0,0), (0,0,1)],[(0,0,2), (0,0,3)]])
-        >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> H = sensor.getH(loop)
-        >>> print(H.shape)
-        (2, 2, 3)
+        >>> sens.rotate_from_rotvec((45,0,0))
+        >>> H = sens.getH(loop)
         >>> print(H)
-        [[[0.         0.         1.        ]
-          [0.         0.         0.08944272]]
-         [[0.         0.         0.0142668 ]
-          [0.         0.         0.00444322]]]
+        [0.         0.70710678 0.70710678]
 
-        Compute the field of a sensor along a path. The path positions are chosen so that
-        they coincide with the pixel positions in the previous example.
+        Finally we set some sensor pixels and compute the field again:
 
-        >>> import magpylib as magpy
-        >>> loop = magpy.current.Loop(current=1, diameter=1)
-        >>> sensor = magpy.Sensor()
-        >>> sensor.move([(0,0,1)]*3, start=1, increment=True)
-        >>> print(sensor.position)
-        [[0. 0. 0.]
-         [0. 0. 1.]
-         [0. 0. 2.]
-         [0. 0. 3.]]
-        >>> H = sensor.getH(loop)
+        >>> sens.pixel=((0,0,0), (.1,0,0), (.2,0,0))
+        >>> H = sens.getH(loop)
         >>> print(H)
-        [[0.         0.         1.        ]
-         [0.         0.         0.08944272]
-         [0.         0.         0.0142668 ]
-         [0.         0.         0.00444322]]
-
+        [[0.         0.70710678 0.70710678]
+         [0.         0.72914768 0.72914768]
+         [0.         0.80703798 0.80703798]]
         """
         sources = format_star_input(sources)
         return getBH_level2(sources, self, sumup=sumup, squeeze=squeeze, field='H')
diff --git a/magpylib/_src/obj_classes/class_current_Line.py b/magpylib/_src/obj_classes/class_current_Line.py
index e8d8c1fa1..7f8d834d9 100644
--- a/magpylib/_src/obj_classes/class_current_Line.py
+++ b/magpylib/_src/obj_classes/class_current_Line.py
@@ -1,92 +1,89 @@
-"""Line current class code"""
+"""Line current class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseCurrent
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_vertex_format, check_vector_type
+from magpylib._src.input_checks import check_format_input_vertices
 
-# init for tool tips
-i0 = None
-pos1 = pos2 = (None, None, None)
-
-# ON INTERFACE
 class Line(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseCurrent):
-    """
-    Current flowing in straight lines from vertex to vertex.
+    """Current flowing in straight lines from vertex to vertex.
 
-    Local object coordinates: The Line current vertices are defined in the local object
-    coordinate system. Local (Line) and global CS coincide when position=(0,0,0)
-    and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    The vertex positions are defined in the local object coordinates (rotate with object).
+    When `position=(0,0,0)` and `orientation=None` global and local coordinates conincide.
 
     Parameters
     ----------
-    current: float
+    current: float, default=`None`
         Electrical current in units of [A].
 
-    vertices: array_like, shape (N,3)
+    vertices: array_like, shape (n,3), default=`None`
         The current flows along the vertices which are given in units of [mm] in the
-        local CS of the Line object.
+        local object coordinates (move/rotate with object). At least two vertices
+        must be given.
+
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Line object: Line
+    current source: `Line` object
 
     Examples
     --------
-    # By default a Line is initialized at position (0,0,0), with unit rotation:
+    `Line` objects are magnetic field sources. In this example we compute the H-field [kA/m]
+    of a square-shaped line-current with 1 [A] current at the observer position (1,1,1) given in
+    units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.current.Line(current=100, vertices=[(-1,0,0),(1,0,0)])
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Lines are magnetic field sources. Below we compute the H-field [kA/m] of the above Line at the
-    observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.current.Line(
+    >>>     current=1,
+    >>>     vertices=((1,0,0), (0,1,0), (-1,0,0), (0,-1,0), (1,0,0)))
+    >>> H = src.getH((1,1,1))
     >>> print(H)
-    [ 0.         -3.24873667  3.24873667]
-
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[ 0.         -3.24873667  3.24873667]
-     [ 0.         -0.78438229  0.78438229]
-     [ 0.         -0.34429579  0.34429579]]
-
-    The same result is obtained when the Line moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [[ 0.         -3.24873667  3.24873667]
-     [ 0.         -0.78438229  0.78438229]
-     [ 0.         -0.34429579  0.34429579]]
+    [0.03160639 0.03160639 0.00766876]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[-6.68990257e-18  3.50341393e-02 -3.50341393e-02]
+     [-5.94009823e-19  3.62181325e-03 -3.62181325e-03]
+     [-2.21112416e-19  1.03643004e-03 -1.03643004e-03]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). This time we use a `Sensor` object as observer.
+
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[-6.68990257e-18  3.50341393e-02 -3.50341393e-02]
+     [-5.94009823e-19  3.62181325e-03 -3.62181325e-03]
+     [-2.21112416e-19  1.03643004e-03 -1.03643004e-03]]
     """
 
     # pylint: disable=dangerous-default-value
 
     def __init__(
         self,
-        current=i0,
-        vertices=[pos1, pos2],
+        current=None,
+        vertices=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -95,7 +92,7 @@ def __init__(
 
         # instance attributes
         self.vertices = vertices
-        self._object_type = "Line"
+        self._object_type = 'Line'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -111,15 +108,4 @@ def vertices(self):
     @vertices.setter
     def vertices(self, vert):
         """Set Line vertices, array_like, [mm]."""
-        # input type check
-        if Config.checkinputs:
-            check_vector_type(vert, "vertices")
-
-        # input type -> ndarray
-        vert = np.array(vert)
-
-        # input format check
-        if Config.checkinputs:
-            check_vertex_format(vert)
-
-        self._vertices = vert
+        self._vertices = check_format_input_vertices(vert)
diff --git a/magpylib/_src/obj_classes/class_current_Loop.py b/magpylib/_src/obj_classes/class_current_Loop.py
index e3cee81da..5db16c2f6 100644
--- a/magpylib/_src/obj_classes/class_current_Loop.py
+++ b/magpylib/_src/obj_classes/class_current_Loop.py
@@ -1,88 +1,84 @@
-"""Loop current class code"""
+"""Loop current class code
+DOCSTRINGS V4 READY
+"""
 
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseCurrent
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_scalar_type
+from magpylib._src.input_checks import check_format_input_scalar
 
-# init for tool tips
-i0 = None
-d = None
-
-# ON INTERFACE
 class Loop(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseCurrent):
-    """
-    Loop current loop.
+    """Circular current loop.
+
+    Can be used as `sources` input for magnetic field computation.
 
-    Local object coordinates: The Loop current loop lies in the x-y plane of
-    the local object coordinate system, with its center in the origin. Local (Loop)
-    and global CS coincide when position=(0,0,0) and orientation=unit_rotation.
+    When `position=(0,0,0)` and `orientation=None` the current loop lies
+    in the x-y plane of the global coordinate system, with its center in
+    the origin.
 
     Parameters
     ----------
-    current: float
+    current: float, default=`None`
         Electrical current in units of [A].
 
-    diameter: float
+    diameter: float, default=`None`
         Diameter of the loop in units of [mm].
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
+
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Loop object: Loop
+    current source: `Loop` object
 
     Examples
     --------
-    By default a Loop is initialized at position (0,0,0), with unit rotation:
+    `Loop` objects are magnetic field sources. In this example we compute the H-field [kA/m]
+    of such a current loop with 100 [A] current and a diameter of 2 [mm] at the observer position
+    (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.current.Loop(current=100, diameter=2)
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Loops are magnetic field sources. Below we compute the H-field [kA/m] of the
-    above Loop at the observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.current.Loop(current=100, diameter=2)
+    >>> H = src.getH((1,1,1))
     >>> print(H)
     [4.96243034 4.96243034 2.12454191]
 
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[4.96243034 4.96243034 2.12454191]
-     [0.61894364 0.61894364 0.06167939]
-     [0.18075829 0.18075829 0.00789697]]
-
-    The same result is obtained when the Loop moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [[4.96243034 4.96243034 2.12454191]
-     [0.61894364 0.61894364 0.06167939]
-     [0.18075829 0.18075829 0.00789697]]
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[-1.44441884e-15  6.72068135e+00 -6.72068135e+00]
+     [-9.88027010e-17  5.89248328e-01 -5.89248328e-01]
+     [-3.55802727e-17  1.65201495e-01 -1.65201495e-01]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). This time we use a `Sensor` object as observer.
+
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[-1.44441884e-15  6.72068135e+00 -6.72068135e+00]
+     [-9.88027010e-17  5.89248328e-01 -5.89248328e-01]
+     [-3.55802727e-17  1.65201495e-01 -1.65201495e-01]]
     """
 
     def __init__(
         self,
-        current=i0,
-        diameter=d,
+        current=None,
+        diameter=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -91,7 +87,7 @@ def __init__(
 
         # instance attributes
         self.diameter = diameter
-        self._object_type = "Loop"
+        self._object_type = 'Loop'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -107,12 +103,9 @@ def diameter(self):
     @diameter.setter
     def diameter(self, dia):
         """Set Loop loop diameter, float, [mm]."""
-        # input type check
-        if Config.checkinputs:
-            check_scalar_type(dia, "diameter")
-
-        # secure type
-        if dia is None:
-            self._diameter = None
-        else:
-            self._diameter = float(dia)
+        self._diameter = check_format_input_scalar(
+            dia,
+            sig_name='diameter',
+            sig_type='`None` or a positive number (int, float)',
+            allow_None=True,
+            forbid_negative=True)
diff --git a/magpylib/_src/obj_classes/class_mag_Cuboid.py b/magpylib/_src/obj_classes/class_mag_Cuboid.py
index dbbad2cdc..ac153e681 100644
--- a/magpylib/_src/obj_classes/class_mag_Cuboid.py
+++ b/magpylib/_src/obj_classes/class_mag_Cuboid.py
@@ -1,91 +1,85 @@
-"""Magnet Cuboid class code"""
+"""Magnet Cuboid class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseHomMag
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_vector_format, check_vector_type
+from magpylib._src.input_checks import check_format_input_vector
 
-# init for tool tips
-a = b = c = None
-mx = my = mz = None
-
-# ON INTERFACE
 class Cuboid(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseHomMag):
-    """
-    Cuboid magnet with homogeneous magnetization.
+    """Cuboid magnet with homogeneous magnetization.
 
-    Local object coordinates: The geometric center of the Cuboid is located
-    in the origin of the local object coordinate system. Cuboid sides are
-    parallel to the local basis vectors. Local (Cuboid) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` the Cuboid sides are parallel
+    to the global coordinate basis vectors and the geometric center of the Cuboid
+    is located in the origin.
 
     Parameters
     ----------
-    magnetization: array_like, shape (3,)
+    magnetization: array_like, shape (3,), default=`None`
         Magnetization vector (mu0*M, remanence field) in units of [mT] given in
-        the local CS of the Cuboid object.
+        the local object coordinates (rotates with object).
+
+    dimension: array_like, shape (3,), default=`None`
+        Length of the cuboid sides [a,b,c] in units of [mm].
 
-    dimension: array_like, shape (3,)
-        Dimension/Size of the Cuboid with sides [a,b,c] in units of [mm].
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Cuboid object: Cuboid
+    magnet source: `Cuboid` object
 
     Examples
     --------
-    By default a Cuboid is initialized at position (0,0,0), with unit rotation:
+    `Cuboid` magnets are magnetic field sources. Below we compute the H-field [kA/m] of a
+    cubical magnet with magnetization (100,200,300) in units of [mT] and 1 [mm] sides
+    at the observer position (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.magnet.Cuboid(magnetization=(100,100,100), dimension=(1,1,1))
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Cuboids are magnetic field sources. Below we compute the H-field [kA/m] of the above Cuboid
-    at the observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.magnet.Cuboid(magnetization=(100,200,300), dimension=(1,1,1))
+    >>> H = src.getH((1,1,1))
     >>> print(H)
-    [2.4844679 2.4844679 2.4844679]
-
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[2.4844679  2.4844679  2.4844679 ]
-     [0.30499798 0.30499798 0.30499798]
-     [0.0902928  0.0902928  0.0902928 ]]
-
-    The same result is obtained when the Cuboid moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [[2.4844679  2.4844679  2.4844679 ]
-     [0.30499798 0.30499798 0.30499798]
-     [0.0902928  0.0902928  0.0902928 ]]
+    [6.21116976 4.9689358  3.72670185]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[4.30496934 6.9363475  0.50728577]
+     [0.54127889 0.86827283 0.05653357]
+     [0.1604214  0.25726266 0.01664045]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). Here we use a `Sensor` object as observer.
+
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[4.30496934 6.9363475  0.50728577]
+     [0.54127889 0.86827283 0.05653357]
+     [0.1604214  0.25726266 0.01664045]]
     """
 
     def __init__(
         self,
-        magnetization=(mx, my, mz),
-        dimension=(a, b, c),
+        magnetization=None,
+        dimension=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -94,7 +88,7 @@ def __init__(
 
         # instance attributes
         self.dimension = dimension
-        self._object_type = "Cuboid"
+        self._object_type = 'Cuboid'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -110,15 +104,11 @@ def dimension(self):
     @dimension.setter
     def dimension(self, dim):
         """Set Cuboid dimension (a,b,c), shape (3,), [mm]."""
-        # input type check
-        if Config.checkinputs:
-            check_vector_type(dim, "dimension")
-
-        # input type -> ndarray
-        dim = np.array(dim, dtype=float)
-
-        # input format check
-        if Config.checkinputs:
-            check_vector_format(dim, (3,), "dimension")
-
-        self._dimension = dim
+        self._dimension = check_format_input_vector(dim,
+            dims=(1,),
+            shape_m1=3,
+            sig_name='Cuboid.dimension',
+            sig_type='array_like (list, tuple, ndarray) of shape (3,) with positive values',
+            allow_None=True,
+            forbid_negative0=True,
+        )
diff --git a/magpylib/_src/obj_classes/class_mag_Cylinder.py b/magpylib/_src/obj_classes/class_mag_Cylinder.py
index 26a4f5575..fed43df26 100644
--- a/magpylib/_src/obj_classes/class_mag_Cylinder.py
+++ b/magpylib/_src/obj_classes/class_mag_Cylinder.py
@@ -1,91 +1,85 @@
-"""Magnet Cylinder class code"""
+"""Magnet Cylinder class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseHomMag
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_vector_type, check_vector_format
+from magpylib._src.input_checks import check_format_input_vector
 
-# init for tool tips
-d = h = None
-mx = my = mz = None
-
-# ON INTERFACE
 class Cylinder(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseHomMag):
-    """
-    Cylinder magnet with homogeneous magnetization.
+    """Cylinder magnet with homogeneous magnetization.
 
-    Local object coordinates: The geometric center of the Cylinder is located
-    in the origin of the local object coordinate system. The Cylinder axis conincides
-    with the local CS z-axis. Local (Cylinder) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` the geometric center of the
+    cylinder lies in the origin of the global coordinate system and
+    the cylinder axis conincides with the global z-axis.
 
     Parameters
     ----------
-    magnetization: array_like, shape (3,)
+    magnetization: array_like, shape (3,), default=`None`
         Magnetization vector (mu0*M, remanence field) in units of [mT] given in
-        the local CS of the Cylinder object.
+        the local object coordinates (rotates with object).
+
+    dimension: array_like, shape (2,), default=`None`
+        Dimension (d,h) denote diameter and height of the cylinder in units of [mm].
 
-    dimension: array_like, shape (2,)
-        Dimension/Size of the solid Cylinder with diameter/height (d,h) in units of [mm].
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Cylinder object: Cylinder
+    magnet source: `Cylinder`
 
     Examples
     --------
-    By default a Cylinder is initialized at position (0,0,0), with unit rotation:
+    `Cylinder` magnets are magnetic field sources. Below we compute the H-field [kA/m] of a
+    cylinder magnet with magnetization (100,200,300) in units of [mT] and 1 [mm] diameter and height
+    at the observer position (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.magnet.Cylinder(magnetization=(100,100,100), dimension=(1,1))
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Cylinders are magnetic field sources. Below we compute the H-field [kA/m] of the
-    above Cylinder at the observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.magnet.Cylinder(magnetization=(100,200,300), dimension=(1,1))
+    >>> H = src.getH((1,1,1))
     >>> print(H)
-    [1.95851744 1.95851744 1.8657571 ]
-
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[1.95851744 1.95851744 1.8657571 ]
-     [0.24025917 0.24025917 0.23767364]
-     [0.07101874 0.07101874 0.07068512]]
-
-    The same result is obtained when the Cylinder moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [[1.95851744 1.95851744 1.8657571 ]
-     [0.24025917 0.24025917 0.23767364]
-     [0.07101874 0.07101874 0.07068512]]
+    [4.84991343 3.88317816 2.73973202]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[3.31419501 5.26683023 0.37767015]
+     [0.42298405 0.67710536 0.04464932]
+     [0.12571523 0.20144503 0.01312389]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). Here we use a `Sensor` object as observer.
+
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[3.31419501 5.26683023 0.37767015]
+     [0.42298405 0.67710536 0.04464932]
+     [0.12571523 0.20144503 0.01312389]]
     """
 
     def __init__(
         self,
-        magnetization=(mx, my, mz),
-        dimension=(d, h),
+        magnetization=None,
+        dimension=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -94,7 +88,7 @@ def __init__(
 
         # instance attributes
         self.dimension = dimension
-        self._object_type = "Cylinder"
+        self._object_type = 'Cylinder'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -110,15 +104,11 @@ def dimension(self):
     @dimension.setter
     def dimension(self, dim):
         """Set Cylinder dimension (d,h) in units of [mm]."""
-        # input type check
-        if Config.checkinputs:
-            check_vector_type(dim, "dimension")
-
-        # input type -> ndarray
-        dim = np.array(dim, dtype=float)
-
-        # input format check
-        if Config.checkinputs:
-            check_vector_format(dim, (2,), "Cylinder")
-
-        self._dimension = dim
+        self._dimension = check_format_input_vector(dim,
+            dims=(1,),
+            shape_m1=2,
+            sig_name='Cylinder.dimension',
+            sig_type='array_like (list, tuple, ndarray) with shape (2,) with positive values',
+            allow_None=True,
+            forbid_negative0=True,
+        )
diff --git a/magpylib/_src/obj_classes/class_mag_CylinderSegment.py b/magpylib/_src/obj_classes/class_mag_CylinderSegment.py
index 764c142a2..bd43e2114 100644
--- a/magpylib/_src/obj_classes/class_mag_CylinderSegment.py
+++ b/magpylib/_src/obj_classes/class_mag_CylinderSegment.py
@@ -1,97 +1,90 @@
-"""Magnet Cylinder class code"""
+"""Magnet Cylinder class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseHomMag
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_input_cyl_sect, check_vector_type
+from magpylib._src.input_checks import check_format_input_cylinder_segment
 
-# init for tool tips
-d1 = d2 = h = phi1 = phi2 = None
-mx = my = mz = None
-
-# ON INTERFACE
 class CylinderSegment(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseHomMag):
-    """
-    Cylinder-Section/Tile (Ring-Section) magnet with homogeneous magnetization.
+    """Cylinder segment (ring-section) magnet with homogeneous magnetization.
 
-    Local object coordinates: The geometric center of the full Cylinder is located
-    in the origin of the local object coordinate system. The Cylinder axis conincides
-    with the local CS z-axis. Local (Cylinder) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` the geometric center of the
+    cylinder lies in the origin of the global coordinate system and
+    the cylinder axis conincides with the global z-axis. Section angle 0
+    corresponds to an x-z plane section of the cylinder.
 
     Parameters
     ----------
-    magnetization: array_like, shape (3,)
+    magnetization: array_like, shape (3,), default=`None`
         Magnetization vector (mu0*M, remanence field) in units of [mT] given in
-        the local CS of the Cylinder object.
+        the local object coordinates (rotates with object).
+
+    dimension: array_like, shape (5,), default=`None`
+        Dimension/Size of the cylinder segment of the form (r1, r2, h, phi1, phi2)
+        where r1<r2 denote inner and outer radii in units of [mm], phi1<phi2 denote
+        the cylinder section angles in units of [deg] and h is the cylinder height
+        in units of [mm].
 
-    dimension: array_like, shape (5,)
-        Dimension/Size of a Cylinder-Section (r1,r2,h,phi1,phi2) where r1 < r2 denote inner
-        and outer radius in units of [mm], phi1 < phi2 denote the Cylinder section angles
-        in units of [deg] and h the Cylinder height in units of [mm].
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    CylinderSegment object: CylinderSegment
+    magnet source: `CylinderSegment` object
 
     Examples
     --------
-
-    Initialize a CylinderSegment with inner diameter d1=1, outer diameter d2=2,
-    height h=2 spanning over 90 deg in the positive quadrant. By default a CylinderSegment
-    is initialized at position (0,0,0), which corresponds to the center of the full
-    Cylinder, with unit rotation:
+    `CylinderSegment` magnets are magnetic field sources. In this example we compute the
+    H-field [kA/m] of such a cylinder segment magnet with magnetization (100,200,300)
+    in units of [mT], inner radius 1 [mm], outer radius 2 [mm], height 1 [mm], and
+    section anges 0 and 45 [deg] at the observer position (2,2,2) in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.magnet.CylinderSegment(magnetization=(100,100,100), dimension=(0.5,1,2,0,90))
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    CylinderSegments are magnetic field sources. Below we compute the H-field [kA/m] of the
-    above CylinderSegment at the observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [9.68649054 9.68649054 7.98245579]
-
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[9.68649054 9.68649054 7.98245579]
-     [0.55368221 0.55368221 0.68274112]
-     [0.13864524 0.13864524 0.16646942]]
-
-    The same result is obtained when the Cylinder moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.magnet.CylinderSegment(magnetization=(100,200,300), dimension=(1,2,1,0,45))
+    >>> H = src.getH((2,2,2))
     >>> print(H)
-    [[9.68649054 9.68649054 7.98245579]
-     [0.55368221 0.55368221 0.68274112]
-     [0.13864524 0.13864524 0.16646942]]
+    [0.80784692 1.93422813 2.74116805]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[-32.82849635  30.15882073 -16.32885658]
+     [  0.62876075   3.97579164   0.73297829]
+     [  0.25439493   0.74331628   0.11682542]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). Here we use a `Sensor` object as observer.
+
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[-32.82849635  30.15882073 -16.32885658]
+     [  0.62876075   3.97579164   0.73297829]
+     [  0.25439493   0.74331628   0.11682542]]
     """
 
     def __init__(
         self,
-        magnetization=(mx, my, mz),
-        dimension=(d1, d2, h, phi1, phi2),
+        magnetization=None,
+        dimension=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -100,7 +93,7 @@ def __init__(
 
         # instance attributes
         self.dimension = dimension
-        self._object_type = "CylinderSegment"
+        self._object_type = 'CylinderSegment'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -116,15 +109,4 @@ def dimension(self):
     @dimension.setter
     def dimension(self, dim):
         """Set Cylinder dimension (r1,r2,h,phi1,phi2), shape (5,), [mm, deg]."""
-        # input type check
-        if Config.checkinputs:
-            check_vector_type(dim, "dimension")
-
-        # input type -> ndarray
-        dim = np.array(dim, dtype=float)
-
-        # input format check
-        if Config.checkinputs:
-            check_input_cyl_sect(dim)
-
-        self._dimension = dim
+        self._dimension = check_format_input_cylinder_segment(dim)
diff --git a/magpylib/_src/obj_classes/class_mag_Sphere.py b/magpylib/_src/obj_classes/class_mag_Sphere.py
index d7459f845..b5d29640a 100644
--- a/magpylib/_src/obj_classes/class_mag_Sphere.py
+++ b/magpylib/_src/obj_classes/class_mag_Sphere.py
@@ -1,88 +1,85 @@
-"""Magnet Sphere class code"""
+"""Magnet Sphere class code
+DOCSTRINGS V4 READY
+"""
 
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
 from magpylib._src.obj_classes.class_BaseExcitations import BaseHomMag
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_scalar_type
+from magpylib._src.input_checks import check_format_input_scalar
 
-# init for tool tips
-mx = my = mz = d = None
-
-# ON INTERFACE
 class Sphere(BaseGeo, BaseDisplayRepr, BaseGetBH, BaseHomMag):
-    """
-    Spherical magnet with homogeneous magnetization.
+    """Spherical magnet with homogeneous magnetization.
 
-    Local object coordinates: The Sphere center is located in the origin of
-    the local object coordinate system. Local (Sphere) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` the sphere center is located
+    in the origin of the global coordinate system.
 
     Parameters
     ----------
-    magnetization: array_like, shape (3,)
+    magnetization: array_like, shape (3,), default=`None`
         Magnetization vector (mu0*M, remanence field) in units of [mT] given in
-        the local CS of the Sphere object.
+        the local object coordinates (rotates with object).
+
+    diameter: float, default=`None`
+        Diameter of the sphere in units of [mm].
 
-    diameter: float
-        Diameter of the Sphere in units of [mm].
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
+
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
 
     Returns
     -------
-    Sphere object: Sphere
+    magnet source: `Sphere` object
 
     Examples
     --------
-    By default a Sphere is initialized at position (0,0,0), with unit rotation:
+    `Sphere` objects are magnetic field sources. In this example we compute the H-field [kA/m]
+    of a spherical magnet with magnetization (100,200,300) in units of [mT] and diameter
+    of 1 [mm] at the observer position (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> magnet = magpy.magnet.Sphere(magnetization=(100,100,100), diameter=1)
-    >>> print(magnet.position)
-    [0. 0. 0.]
-    >>> print(magnet.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Spheres are magnetic field sources. Below we compute the H-field [kA/m] of the
-    above Sphere at the observer position (1,1,1),
-
-    >>> H = magnet.getH((1,1,1))
-    >>> print(H)
-    [1.27622429 1.27622429 1.27622429]
-
-    or at a set of observer positions:
-
-    >>> H = magnet.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[1.27622429 1.27622429 1.27622429]
-     [0.15952804 0.15952804 0.15952804]
-     [0.04726757 0.04726757 0.04726757]]
-
-    The same result is obtained when the Sphere object moves along a path,
-    away from the observer:
-
-    >>> magnet.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = magnet.getH((1,1,1))
+    >>> src = magpy.magnet.Sphere(magnetization=(100,200,300), diameter=1)
+    >>> H = src.getH((1,1,1))
     >>> print(H)
-    [[1.27622429 1.27622429 1.27622429]
-     [0.15952804 0.15952804 0.15952804]
-     [0.04726757 0.04726757 0.04726757]]
+    [3.19056074 2.55244859 1.91433644]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[2.26804606 3.63693295 0.23486386]
+     [0.28350576 0.45461662 0.02935798]
+     [0.08400171 0.13470122 0.00869866]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). This time we use a `Sensor` object as observer.
+
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[2.26804606 3.63693295 0.23486386]
+     [0.28350576 0.45461662 0.02935798]
+     [0.08400171 0.13470122 0.00869866]]
     """
 
     def __init__(
         self,
-        magnetization=(mx, my, mz),
-        diameter=d,
+        magnetization=None,
+        diameter=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -107,12 +104,9 @@ def diameter(self):
     @diameter.setter
     def diameter(self, dia):
         """Set Sphere diameter, float, [mm]."""
-        # input type check
-        if Config.checkinputs:
-            check_scalar_type(dia, "Sphere diameter")
-
-        # secure type
-        if dia is None:
-            self._diameter = None
-        else:
-            self._diameter = float(dia)
+        self._diameter = check_format_input_scalar(
+            dia,
+            sig_name='diameter',
+            sig_type='`None` or a positive number (int, float)',
+            allow_None=True,
+            forbid_negative=True,)
diff --git a/magpylib/_src/obj_classes/class_misc_Custom.py b/magpylib/_src/obj_classes/class_misc_Custom.py
index 5fe6df10c..139835f81 100644
--- a/magpylib/_src/obj_classes/class_misc_Custom.py
+++ b/magpylib/_src/obj_classes/class_misc_Custom.py
@@ -1,4 +1,6 @@
-"""Custom class code"""
+"""Custom class code
+DOCSTRINGS V4 READY
+"""
 
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
@@ -8,60 +10,76 @@
 
 # ON INTERFACE
 class CustomSource(BaseGeo, BaseDisplayRepr, BaseGetBH):
-    """
-    Custom Source class
+    """User-defined custom source.
+
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` local object coordinates
+    coincide with the gobal coordinate system.
 
     Parameters
     ----------
-    field_B_lambda: function
-        field function for B-field, should accept (n,3) position array and return
-        B-field array of same shape in the global coordinate system.
+    field_B_lambda: callable, default=`None`
+        Field function for the B-field. must accept position input with format (n,3) and
+        return the B-field with similar shape.
 
-    field_H_lambda: function
-        field function for H-field, should accept (n,3) position array and return
-        H-field array of same shape in the global coordinate system.
+    field_H_lambda: callable, default=`None`
+        Field function for the H-field. must accept position input with format (n,3) and
+        return the H-field with similar shape.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
+
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    CustomSource object: CustomSource
+    source: `CustomSource` object
 
     Examples
     --------
-    By default the CustomSource is initialized at position (0,0,0), with unit rotation:
+    With version 4.0.0 `CustomSource` objects enable users to define their own source
+    objects, and to embedd them in the Magpylib object oriented interface. In this example
+    we create a source that generates a constant field and evaluate the field at observer
+    position (1,1,1) given in [mm]:
 
-    >>> import magpylib as magpy
     >>> import numpy as np
-
-    Define a external B-field function which returns constant vector in x-direction
-
-    >>> def constant_Bfield(position=((0,0,0))):
-    ...    return np.array([[1,0,0]]*len(position))
-
-    Construct a ``CustomSource`` from the field function
-
-    >>> external_field = magpy.misc.CustomSource(field_B_lambda=constant_Bfield)
-    >>> B = external_field.getB([[1,2,3],[4,5,6]])
+    >>> import magpylib as magpy
+    >>>
+    >>> bfield = lambda observer: np.array([(100,0,0)]*len(observer))
+    >>> hfield = lambda observer: np.array([(80,0,0)]*len(observer))
+    >>> src = magpy.misc.CustomSource(field_B_lambda=bfield, field_H_lambda=hfield)
+    >>> H = src.getH((1,1,1))
+    >>> print(H)
+    [80.  0.  0.]
+
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'z')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
     >>> print(B)
-    [[1. 0. 0.]
-     [1. 0. 0.]]
+    [[70.71067812 70.71067812  0.        ]
+     [70.71067812 70.71067812  0.        ]
+     [70.71067812 70.71067812  0.        ]]
 
-    The custom source can be rotated as any other source object in the library.
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). This time we use a `Sensor` object as observer.
 
-    >>> external_field.rotate_from_angax(90, 'z')
-    >>> B = external_field.getB([[1,2,3],[4,5,6]])
-    >>> print(B) # Notice the output field is now pointing in y-direction
-    [[0. 1. 0.]
-     [0. 1. 0.]]
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[70.71067812 70.71067812  0.        ]
+     [70.71067812 70.71067812  0.        ]
+     [70.71067812 70.71067812  0.        ]]
     """
 
     def __init__(
@@ -84,8 +102,10 @@ def __init__(
 
     @property
     def field_B_lambda(self):
-        """field function for B-field, should accept (n,3) position array and return
-        B-field array of same shape in the global coordinate system."""
+        """Field function for B-field, should accept array_like positions of shape (n,3)
+        in units of [mm] and return a B-field array of same shape in the global
+        coordinate system in units of [mT].
+        """
         return self._field_B_lambda
 
     @field_B_lambda.setter
@@ -94,8 +114,10 @@ def field_B_lambda(self, val):
 
     @property
     def field_H_lambda(self):
-        """field function for H-field, should accept (n,3) position array and return
-        H-field array of same shape in the global coordinate system."""
+        """Field function for H-field, should accept array_like positions of shape (n,3)
+        in units of [mm] and return a H-field array of same shape in the global
+        coordinate system in units of [kA/m].
+        """
         return self._field_H_lambda
 
     @field_H_lambda.setter
diff --git a/magpylib/_src/obj_classes/class_misc_Dipole.py b/magpylib/_src/obj_classes/class_misc_Dipole.py
index ecf7028c0..53fd78898 100644
--- a/magpylib/_src/obj_classes/class_misc_Dipole.py
+++ b/magpylib/_src/obj_classes/class_misc_Dipole.py
@@ -1,84 +1,80 @@
-"""Dipole class code"""
+"""Dipole class code
+DOCSTRINGS V4 READY
+"""
 
-import numpy as np
 from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
 from magpylib._src.obj_classes.class_BaseDisplayRepr import BaseDisplayRepr
 from magpylib._src.obj_classes.class_BaseGetBH import BaseGetBH
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_vector_format, check_vector_type
+from magpylib._src.input_checks import check_format_input_vector
 
-# init for tool tips
-mx = my = mz = None
-
-# ON INTERFACE
 class Dipole(BaseGeo, BaseDisplayRepr, BaseGetBH):
-    """
-    Magnetic dipole moment.
+    """Magnetic dipole moment.
 
-    Local object coordinates: The Dipole is located in the origin of
-    the local object coordinate system. Local (Dipole) and global CS coincide when
-    position=(0,0,0) and orientation=unit_rotation.
+    Can be used as `sources` input for magnetic field computation.
+
+    When `position=(0,0,0)` and `orientation=None` the dipole is located in the origin of
+    global coordinate system.
 
     Parameters
     ----------
-    moment: array_like, shape (3,), unit [mT*mm^3]
-        Magnetic dipole moment in units of [mT*mm^3] given in the local CS.
-        For homogeneous magnets there is a relation moment=magnetization*volume.
+    moment: array_like, shape (3,), unit [mT*mm^3], default=`None`
+        Magnetic dipole moment in units of [mT*mm^3] given in the local object coordinates
+        (rotates with object). For homogeneous magnets the relation moment=magnetization*volume
+        holds.
+
+    position: array_like, shape (3,) or (m,3), default=`(0,0,0)`
+        Object position(s) in the global coordinates in units of [mm]. For m>1, the
+        `position` and `orientation` attributes together represent an object path.
 
-    position: array_like, shape (3,) or (M,3), default=(0,0,0)
-        Object position (local CS origin) in the global CS in units of [mm].
-        For M>1, the position represents a path. The position and orientation
-        parameters must always be of the same length.
+    orientation: scipy `Rotation` object with length 1 or m, default=`None`
+        Object orientation(s) in the global coordinates. `None` corresponds to
+        a unit-rotation. For m>1, the `position` and `orientation` attributes
+        together represent an object path.
 
-    orientation: scipy Rotation object with length 1 or M, default=unit rotation
-        Object orientation (local CS orientation) in the global CS. For M>1
-        orientation represents different values along a path. The position and
-        orientation parameters must always be of the same length.
+    style: dict
+        Object style inputs must be in dictionary form, e.g. `{'color':'red'}` or
+        using style-underscore_magic, e.g. `style_color='red'`.
 
     Returns
     -------
-    Dipole object: Dipole
+    source: `Dipole` object
 
     Examples
     --------
-    By default a Dipole is initialized at position (0,0,0), with unit rotation:
+    `Dipole` objects are magnetic field sources. In this example we compute the H-field [kA/m]
+    of such a magnetic dipole with a moment of (100,100,100) [mT*mm^2] at an observer position
+    (1,1,1) given in units of [mm]:
 
     >>> import magpylib as magpy
-    >>> dipole = magpy.misc.Dipole(moment=(100,100,100))
-    >>> print(dipole.position)
-    [0. 0. 0.]
-    >>> print(dipole.orientation.as_quat())
-    [0. 0. 0. 1.]
-
-    Dipoles are magnetic field sources. Below we compute the H-field [kA/m] of the above Dipole at
-    an observer position (1,1,1),
-
-    >>> H = dipole.getH((1,1,1))
+    >>> src = magpy.misc.Dipole(moment=(100,100,100))
+    >>> H = src.getH((1,1,1))
     >>> print(H)
     [2.43740886 2.43740886 2.43740886]
 
-    or at a set of observer positions:
-
-    >>> H = dipole.getH([(1,1,1), (2,2,2), (3,3,3)])
-    >>> print(H)
-    [[2.43740886 2.43740886 2.43740886]
-     [0.30467611 0.30467611 0.30467611]
-     [0.0902744  0.0902744  0.0902744 ]]
-
-    The same result is obtained when the Dipole object moves along a path,
-    away from the observer:
-
-    >>> dipole.move([(-1,-1,-1), (-2,-2,-2)], start=1)
-    >>> H = dipole.getH((1,1,1))
-    >>> print(H)
-    [[2.43740886 2.43740886 2.43740886]
-     [0.30467611 0.30467611 0.30467611]
-     [0.0902744  0.0902744  0.0902744 ]]
+    We rotate the source object, and compute the B-field, this time at a set of observer positions:
+
+    >>> src.rotate_from_angax(45, 'x')
+    >>> B = src.getB([(1,1,1), (2,2,2), (3,3,3)])
+    >>> print(B)
+    [[2.16582445 3.6972936  1.53146915]
+     [0.27072806 0.4621617  0.19143364]
+     [0.08021572 0.1369368  0.05672108]]
+
+    The same result is obtained when the rotated source moves along a path away from an
+    observer at position (1,1,1). This time we use a `Sensor` object as observer.
+
+    >>> src.move([(-1,-1,-1), (-2,-2,-2)])
+    >>> sens = magpy.Sensor(position=(1,1,1))
+    >>> B = src.getB(sens)
+    >>> print(B)
+    [[2.16582445 3.6972936  1.53146915]
+     [0.27072806 0.4621617  0.19143364]
+     [0.08021572 0.1369368  0.05672108]]
     """
 
     def __init__(
         self,
-        moment=(mx, my, mz),
+        moment=None,
         position=(0, 0, 0),
         orientation=None,
         style=None,
@@ -86,7 +82,7 @@ def __init__(
     ):
         # instance attributes
         self.moment = moment
-        self._object_type = "Dipole"
+        self._object_type = 'Dipole'
 
         # init inheritance
         BaseGeo.__init__(self, position, orientation, style=style, **kwargs)
@@ -101,15 +97,9 @@ def moment(self):
     @moment.setter
     def moment(self, mom):
         """Set dipole moment vector, shape (3,), unit [mT*mm^3]."""
-        # input type check
-        if Config.checkinputs:
-            check_vector_type(mom, "moment")
-
-        # secure type
-        mom = np.array(mom, dtype=float)
-
-        # input format check
-        if Config.checkinputs:
-            check_vector_format(mom, (3,), "moment")
-
-        self._moment = mom
+        self._moment = check_format_input_vector(mom,
+            dims=(1,),
+            shape_m1=3,
+            sig_name='moment',
+            sig_type='array_like (list, tuple, ndarray) with shape (3,)',
+            allow_None=True)
diff --git a/magpylib/_src/style.py b/magpylib/_src/style.py
index e80177438..44eb7732a 100644
--- a/magpylib/_src/style.py
+++ b/magpylib/_src/style.py
@@ -1,4 +1,4 @@
-"""Collection of classes for display styling"""
+"""Collection of classes for display styling."""
 # pylint: disable=C0302
 import numpy as np
 
@@ -16,8 +16,9 @@
 
 
 def get_style_class(obj):
-    """returns style class based on object type. If class has no attribute `_object_type` or is
-    not found in `MAGPYLIB_FAMILIES` returns `BaseStyle` class."""
+    """Returns style instance based on object type. If object has no attribute `_object_type` or is
+    not found in `MAGPYLIB_FAMILIES` returns `BaseStyle` instance.
+    """
     obj_type = getattr(obj, "_object_type", None)
     style_fam = MAGPYLIB_FAMILIES.get(obj_type, None)
     if isinstance(style_fam, (list, tuple)):
@@ -26,8 +27,7 @@ def get_style_class(obj):
 
 
 def get_style(obj, default_settings, **kwargs):
-    """
-    returns default style based on increasing priority:
+    """Returns default style based on increasing priority:
     - style from defaults
     - style from object
     - style from kwargs arguments
@@ -71,29 +71,28 @@ def get_style(obj, default_settings, **kwargs):
 
 
 class BaseStyle(MagicProperties):
-    """
-    Base class for display styling options of `BaseGeo` objects
+    """Base class for display styling options of `BaseGeo` objects.
 
     Parameters
     ----------
     label: str, default=None
-        label of the class instance, can be any string.
+        Label of the class instance, e.g. to be displayed in the legend.
 
-    description: dict or Description, default=None
-        object description properties
+    description: dict or `Description` object, default=None
+        Object description properties.
 
     color: str, default=None
-        a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
 
     opacity: float, default=None
         object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
 
-    path: dict or Path, default=None
-        an instance of `Path` or dictionary of equivalent key/value pairs, defining the object
+    path: dict or `Path` object, default=None
+        An instance of `Path` or dictionary of equivalent key/value pairs, defining the object
         path marker and path line properties.
 
-    model3d: list of Trace3d objects, default=None
-        a list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+    model3d: list of `Trace3d` objects, default=None
+        A list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
@@ -121,7 +120,7 @@ def __init__(
 
     @property
     def label(self):
-        """label of the class instance, can be any string"""
+        """Label of the class instance, e.g. to be displayed in the legend."""
         return self._label
 
     @label.setter
@@ -130,7 +129,7 @@ def label(self, val):
 
     @property
     def description(self):
-        """Description class with 'text' and 'show' properties"""
+        """Description with 'text' and 'show' properties."""
         return self._description
 
     @description.setter
@@ -141,7 +140,7 @@ def description(self, val):
 
     @property
     def color(self):
-        """a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`"""
+        """A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`."""
         return self._color
 
     @color.setter
@@ -150,21 +149,21 @@ def color(self, val):
 
     @property
     def opacity(self):
-        """object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent"""
+        """Object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent."""
         return self._opacity
 
     @opacity.setter
     def opacity(self, val):
         assert val is None or (isinstance(val, (float, int)) and 0 <= val <= 1), (
-            "opacity must be a value betwen 0 and 1\n"
-            f"but received {repr(val)} instead"
+            "The `opacity` property must be a value betwen 0 and 1,\n"
+            f"but received {repr(val)} instead."
         )
         self._opacity = val
 
     @property
     def path(self):
-        """an instance of `Path` or dictionary of equivalent key/value pairs, defining the
-        object path marker and path line properties"""
+        """An instance of `Path` or dictionary of equivalent key/value pairs, defining the
+        object path marker and path line properties."""
         return self._path
 
     @path.setter
@@ -173,7 +172,7 @@ def path(self, val):
 
     @property
     def model3d(self):
-        """3d object representation properties"""
+        """3d object representation properties."""
         return self._model3d
 
     @model3d.setter
@@ -182,16 +181,15 @@ def model3d(self, val):
 
 
 class Description(MagicProperties):
-    """
-    Defines properties for a description object
+    """Defines properties for a description object.
 
     Parameters
     ----------
     text: str, default=None
-        Object description text
+        Object description text.
 
     show: bool, default=None
-        if `True`, adds legend entry suffix based on value
+        If True, adds legend entry suffix based on value.
     """
 
     def __init__(self, text=None, show=None, **kwargs):
@@ -199,42 +197,41 @@ def __init__(self, text=None, show=None, **kwargs):
 
     @property
     def text(self):
-        """description text"""
+        """Description text."""
         return self._text
 
     @text.setter
     def text(self, val):
         assert val is None or isinstance(val, str), (
-            f"the `show` property of {type(self).__name__} must be a string\n"
-            f"but received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be a string,\n"
+            f"but received {repr(val)} instead."
         )
         self._text = val
 
     @property
     def show(self):
-        """if `True`, adds legend entry suffix based on value"""
+        """If True, adds legend entry suffix based on value."""
         return self._show
 
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `show` property of {type(self).__name__} must be either `True` or `False`\n"
-            f"but received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be either True or False,\n"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
 
 class Model3d(MagicProperties):
-    """
-    Defines properties for the 3d model representation of the magpylib objects
+    """Defines properties for the 3d model representation of magpylib objects.
 
     Parameters
     ----------
     showdefault: bool, default=True
-        Shows/hides default 3D-model
+        Shows/hides default 3d-model.
 
     data: dict or list of dicts, default=None
-        a trace or list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+        A trace or list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
@@ -245,21 +242,21 @@ def __init__(self, showdefault=True, data=None, **kwargs):
 
     @property
     def showdefault(self):
-        """shows/hides main model3d object representation"""
+        """If True, show default model3d object representation, else hide it."""
         return self._showdefault
 
     @showdefault.setter
     def showdefault(self, val):
         assert isinstance(val, bool), (
-            f"the `showdefault` property of {type(self).__name__} must be "
-            f"one of `[True, False]`"
-            f" but received {repr(val)} instead"
+            f"The `showdefault` property of {type(self).__name__} must be "
+            f"one of `[True, False]`,\n"
+            f"but received {repr(val)} instead."
         )
         self._showdefault = val
 
     @property
     def data(self):
-        """data 3d object representation (trace or list of traces)"""
+        """Data of 3d object representation (trace or list of traces)."""
         return self._data
 
     @data.setter
@@ -277,34 +274,28 @@ def _validate_data(self, val):
             m3.append(v)
         return m3
 
-    def add_trace(
-        self, trace, show=True, backend="matplotlib", coordsargs=None, scale=1,
-    ):
-        """creates an additional user-defined 3d model object which is positioned relatively
+    def add_trace(self, trace, show=True, backend="matplotlib", coordsargs=None, scale=1):
+        """Creates an additional user-defined 3d model object which is positioned relatively
         to the main object to be displayed and moved automatically with it. This feature also allows
-        the user to replace the original 3d representation of the object
+        the user to replace the original 3d representation of the object.
 
         Properties
         ----------
         show : bool, default=None
-            shows/hides model3d object based on provided trace:
+            Shows/hides model3d object based on provided trace.
 
         trace: dict or callable, default=None
-            dictionary containing the `x,y,z,i,j,k` keys/values pairs for a model3d object
+            Dictionary containing the `x,y,z,i,j,k` keys/values pairs for a model3d object.
 
-        backend:
-            plotting backend corresponding to the trace.
-            Can be one of `['matplotlib', 'plotly']`
+        backend: str, default='matplotlib'
+            Plotting backend corresponding to the trace. Can be one of `['matplotlib', 'plotly']`.
 
-        coordsargs: dict
-            tells magpylib the name of the coordinate arrays to be moved or rotated.
-            by default: `{"x": "x", "y": "y", "z": "z"}`
-            if False, object is not rotated
+        coordsargs: dict, default=None
+            Tells magpylib the name of the coordinate arrays to be moved or rotated,
+            by default: `{"x": "x", "y": "y", "z": "z"}`, if False, object is not rotated.
 
         scale: float, default=1
-            scaling factor by which the trace vertices coordinates should be multiplied by. Be aware
-            that if the object is not centered at the global CS origin, its position will
-            also be scaled.
+            Scaling factor by which the trace vertices coordinates are multiplied.
         """
 
         new_trace = Trace3d(
@@ -315,32 +306,27 @@ def add_trace(
 
 
 class Trace3d(MagicProperties):
-    """
-    Defines properties for an additional user-defined 3d model object which is positioned relatively
-    to the main object to be displayed and moved automatically with it. This feature also allows
-    the user to replace the original 3d representation of the object
+    """Defines properties for an additional user-defined 3d model object which is positioned
+    relatively to the main object to be displayed and moved automatically with it. This feature
+    also allows the user to replace the original 3d representation of the object.
 
     Parameters
     ----------
     show : bool, default=None
-        shows/hides model3d object based on provided trace:
+        Shows/hides model3d object based on provided trace.
 
     trace: dict or callable, default=None
-        dictionary containing the `x,y,z,i,j,k` keys/values pairs for a model3d object
+        Dictionary containing the `x,y,z,i,j,k` keys/values pairs for a model3d object.
 
     scale: float, default=1
-        scaling factor by which the trace vertices coordinates should be multiplied by. Be aware
-        that if the object is not centered at the global CS origin, its position will
-        also be scaled.
-
-    backend:
-        plotting backend corresponding to the trace.
-        Can be one of `['matplotlib', 'plotly']`
-
-    coordsargs: dict
-        tells magpylib the name of the coordinate arrays to be moved or rotated.
-        by default: `{"x": "x", "y": "y", "z": "z"}`
-        if False, object is not rotated
+        Scaling factor by which the trace vertices coordinates are multiplied.
+
+    backend: str, default='matplotlib'
+        Plotting backend corresponding to the trace. Can be one of `['matplotlib', 'plotly']`.
+
+    coordsargs: dict, default=None
+        Tells magpylib the name of the coordinate arrays to be moved or rotated,
+            by default: `{"x": "x", "y": "y", "z": "z"}`, if False, object is not rotated.
     """
 
     def __init__(
@@ -363,42 +349,36 @@ def __init__(
 
     @property
     def show(self):
-        """
-        shows/hides model3d object based on provided trace
-        """
+        """If True, show default model3d object representation, else hide it."""
         return self._show
 
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `show` property of {type(self).__name__} must be "
-            f"one of `[True, False]`"
-            f" but received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be "
+            f"one of `[True, False]`,\n"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
     @property
     def scale(self):
-        """
-        scaling factor by which the trace vertices coordinates should be multiplied by. Be aware
-        that if the object is not centered at the global CS origin, its position will
-        also be scaled.
-        """
+        """Scaling factor by which the trace vertices coordinates are multiplied."""
         return self._scale
 
     @scale.setter
     def scale(self, val):
         assert isinstance(val, (int, float)) and val > 0, (
-            f"the `scale` property of {type(self).__name__} must be a strictly positive number"
-            f" but received {repr(val)} instead"
+            f"The `scale` property of {type(self).__name__} must be a strictly positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._scale = val
 
     @property
     def coordsargs(self):
-        """tells magpylib the name of the coordinate arrays to be moved or rotated.
-        by default: `{"x": "x", "y": "y", "z": "z"}`
-        if False, object is not rotated"""
+        """Tells magpylib the name of the coordinate arrays to be moved or rotated,
+        by default: `{"x": "x", "y": "y", "z": "z"}`, if False, object is not rotated.
+        """
         return self._coordsargs
 
     @coordsargs.setter
@@ -406,23 +386,23 @@ def coordsargs(self, val):
         if val is None:
             val = {"x": "x", "y": "y", "z": "z"}
         assert isinstance(val, dict) and all(key in val for key in "xyz"), (
-            f"the `coordsargs` property of {type(self).__name__} must be "
-            f"a dictionary with `'x', 'y', 'z'` keys"
-            f" but received {repr(val)} instead"
+            f"The `coordsargs` property of {type(self).__name__} must be "
+            f"a dictionary with `'x', 'y', 'z'` keys,\n"
+            f"but received {repr(val)} instead."
         )
         self._coordsargs = val
 
     @property
     def trace(self):
-        """dictionary keys/values pairs for a model3d object"""
+        """Dictionary containing the `x,y,z,i,j,k` keys/values pairs for a model3d object."""
         return self._trace
 
     @trace.setter
     def trace(self, val):
         if val is not None:
             assert isinstance(val, dict) or callable(val), (
-                "trace must be a dictionary or a callable returning a dictionary"
-                f" but received {type(val).__name__} instead"
+                "The `trace` input must be a dictionary or a callable returning a dictionary,\n"
+                f"but received {type(val).__name__} instead."
             )
             test_val = val
             if callable(val):
@@ -432,36 +412,35 @@ def trace(self, val):
 
     @property
     def backend(self):
-        """plotting backend corresponding to the trace.
-        Can be one of `['matplotlib', 'plotly']`"""
+        """Plotting backend corresponding to the trace. Can be one of `['matplotlib', 'plotly']`.
+        """
         return self._backend
 
     @backend.setter
     def backend(self, val):
         assert val is None or val in SUPPORTED_PLOTTING_BACKENDS, (
-            f"the `backend` property of {type(self).__name__} must be one of"
-            f"{SUPPORTED_PLOTTING_BACKENDS}"
-            f" but received {repr(val)} instead"
+            f"The `backend` property of {type(self).__name__} must be one of"
+            f"{SUPPORTED_PLOTTING_BACKENDS},\n"
+            f"but received {repr(val)} instead."
         )
         self._backend = val
 
 
 class Magnetization(MagicProperties):
-    """
-    Defines magnetization styling properties
+    """Defines magnetization styling properties.
 
     Parameters
     ----------
     show : bool, default=None
-        if `True` shows magnetization direction based on active plotting backend
+        If True show magnetization direction.
 
     size: float, default=None
-        arrow size of the magnetization direction (for the matplotlib backend)
-        only applies if `show=True`
+        Arrow size of the magnetization direction (for the matplotlib backend)
+        only applies if `show=True`.
 
-    color: dict, MagnetizationColor, default=None
-        color properties showing the magnetization direction (for the plotly backend)
-        only applies if `show=True`
+    color: dict or MagnetizationColor object, default=None
+        Color properties showing the magnetization direction (for the plotly backend).
+        Only applies if `show=True`.
     """
 
     def __init__(self, show=None, size=None, color=None, **kwargs):
@@ -469,34 +448,35 @@ def __init__(self, show=None, size=None, color=None, **kwargs):
 
     @property
     def show(self):
-        """if `True` shows magnetization direction based on active plotting backend"""
+        """If True, show magnetization direction."""
         return self._show
 
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            "show must be either `True` or `False` \n"
-            f" but received {repr(val)} instead"
+            "The `show` input must be either True or False,\n"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
     @property
     def size(self):
-        """positive float for relative arrow size to magnet size"""
+        """Positive float for ratio of arrow size to magnet size."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `size` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def color(self):
-        """color properties showing the magnetization direction (for the plotly backend)
-        only applies if `show=True`"""
+        """Color properties showing the magnetization direction (for the plotly backend).
+        Applies only if `show=True`.
+        """
         return self._color
 
     @color.setter
@@ -505,34 +485,30 @@ def color(self, val):
 
 
 class MagnetizationColor(MagicProperties):
-    """
-    Defines the magnetization direction color styling properties.
-
-    Note: This feature is only relevant for the plotly backend.
+    """Defines the magnetization direction color styling properties. (Only relevant for
+    the plotly backend)
 
     Parameters
     ----------
     north: str, default=None
-        defines the color of the magnetic north pole
+        Defines the color of the magnetic north pole.
 
     south: str, default=None
-        defines the color of the magnetic south pole
+        Defines the color of the magnetic south pole.
 
     middle: str, default=None
-        defines the color between the magnetic poles
+        Defines the color between the magnetic poles.
 
     transition: float, default=None
-        sets the transition smoothness between poles colors.
-        - `transition=0`: discrete transition
-        - `transition=1`: smoothest transition
-        - can be any value in-between 0 and 1"
+        Sets the transition smoothness between poles colors. Can be any value
+        in-between 0 (discrete) and 1(smooth).
 
     mode: str, default=None
-        sets the coloring mode for the magnetization.
-        - `'bicolor'`: only north and south poles are shown, middle color is hidden.
-        - `'tricolor'`: both pole colors and middle color are shown.
-        - `'tricycle'`: both pole colors are shown and middle color is replaced by a color cycling
-            through the color sequence.
+        Sets the coloring mode for the magnetization.
+        - `'bicolor'`: Only north and south pole colors are shown.
+        - `'tricolor'`: Both pole colors and middle color are shown.
+        - `'tricycle'`: Both pole colors are shown and middle color is replaced by a color cycling
+            through the default color sequence.
     """
 
     _allowed_modes = ("bicolor", "tricolor", "tricycle")
@@ -551,7 +527,7 @@ def __init__(
 
     @property
     def north(self):
-        """the color of the magnetic north pole"""
+        """Color of the magnetic north pole."""
         return self._north
 
     @north.setter
@@ -560,7 +536,7 @@ def north(self, val):
 
     @property
     def south(self):
-        """the color of the magnetic south pole"""
+        """Color of the magnetic south pole."""
         return self._south
 
     @south.setter
@@ -569,7 +545,7 @@ def south(self, val):
 
     @property
     def middle(self):
-        """the color between the magnetic poles"""
+        """Color between the magnetic poles."""
         return self._middle
 
     @middle.setter
@@ -578,10 +554,9 @@ def middle(self, val):
 
     @property
     def transition(self):
-        """sets the transition smoothness between poles colors.
-        `transition=0`: discrete transition
-        `transition=1`: smoothest transition
-        can be any value in-between 0 and 1"""
+        """Sets the transition smoothness between poles colors. Can be any value
+        in-between 0 (discrete) and 1(smooth).
+        """
         return self._transition
 
     @transition.setter
@@ -593,40 +568,38 @@ def transition(self, val):
 
     @property
     def mode(self):
-        """sets the coloring mode for the magnetization.
-        - `'bicolor'`: only north and south poles are shown, middle color is hidden.
-        - `'tricolor'`: both pole colors and middle color are shown.
-        - `'tricycle'`: both pole colors are shown and middle color is replaced by a color cycling
-            through the color sequence.
+        """Sets the coloring mode for the magnetization.
+        - `'bicolor'`: Only north and south pole colors are shown.
+        - `'tricolor'`: Both pole colors and middle color are shown.
+        - `'tricycle'`: Both pole colors are shown and middle color is replaced by a color cycling
+            through the default color sequence.
         """
         return self._mode
 
     @mode.setter
     def mode(self, val):
         assert val is None or val in self._allowed_modes, (
-            f"the `mode` property of {type(self).__name__} must be one of"
-            f"{list(self._allowed_modes)}"
-            f" but received {repr(val)} instead"
+            f"The `mode` property of {type(self).__name__} must be one of"
+            f"{list(self._allowed_modes)},\n"
+            f"but received {repr(val)} instead."
         )
         self._mode = val
 
 
 class MagnetProperties:
-    """
-    Defines the specific styling properties of objects of the `magnet` family
+    """Defines styling properties of homogeneous magnet classes.
 
     Parameters
     ----------
-    magnetization: dict or Magnetization, default=None
-        Magnetization styling with `'show'`, `'size'`, `'color'` properties
-        or a dictionary with equivalent key/value pairs
+    magnetization: dict or `Magnetization` object, default=None
+        `Magnetization` instance with `'show'`, `'size'`, `'color'` properties
+        or a dictionary with equivalent key/value pairs.
     """
 
     @property
     def magnetization(self):
-        """
-        Magnetization styling with `'show'`, `'size'`, `'color'` properties
-        or a dictionary with equivalent key/value pairs
+        """`Magnetization` instance with `'show'`, `'size'`, `'color'` properties
+        or a dictionary with equivalent key/value pairs.
         """
         return self._magnetization
 
@@ -638,13 +611,11 @@ def magnetization(self, val):
 
 
 class Magnet(MagicProperties, MagnetProperties):
-    """
-    Defines the specific styling properties of objects of the `magnet` family
+    """Defines styling properties of homogeneous magnet classes.
 
     Parameters
     ----------
     magnetization: dict or Magnetization, default=None
-
     """
 
     def __init__(self, magnetization=None, **kwargs):
@@ -652,35 +623,35 @@ def __init__(self, magnetization=None, **kwargs):
 
 
 class MagnetStyle(BaseStyle, MagnetProperties):
-    """Defines the styling properties of objects of the `magnet` family with base properties
+    """Defines styling properties of homogeneous magnet classes.
 
     Parameters
     ----------
     label: str, default=None
-        label of the class instance, can be any string.
+        Label of the class instance, e.g. to be displayed in the legend.
 
-    description: dict or Description, default=None
-        object description properties
+    description: dict or `Description` object, default=None
+        Object description properties.
 
     color: str, default=None
-        a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
 
     opacity: float, default=None
-        object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
+        Object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
 
-    path: dict or Path, default=None
-        an instance of `Path` or dictionary of equivalent key/value pairs, defining the object
+    path: dict or `Path` object, default=None
+        An instance of `Path` or dictionary of equivalent key/value pairs, defining the object
         path marker and path line properties.
 
-    model3d: list of Trace3d objects, default=None
-        a list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+    model3d: list of `Trace3d` objects, default=None
+        A list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
 
     magnetization: dict or Magnetization, default=None
         Magnetization styling with `'show'`, `'size'`, `'color'` properties
-        or a dictionary with equivalent key/value pairs
+        or a dictionary with equivalent key/value pairs.
     """
 
     def __init__(self, **kwargs):
@@ -688,21 +659,21 @@ def __init__(self, **kwargs):
 
 
 class ArrowCS(MagicProperties):
-    """Triple coordinate system arrow properties
+    """Defines triple coordinate system arrow properties.
 
     Parameters
     ----------
-    x: dict or ArrowSingle, default=None
-        x-direction `Arrowsingle` class or dict with equivalent key/value pairs
-        (e.g. `color`, `show`)
+    x: dict or `ArrowSingle` object, default=None
+        x-direction `Arrowsingle` object or dict with equivalent key/value pairs
+        (e.g. `color`, `show`).
 
-    y: dict or ArrowSingle, default=None
-        y-direction `Arrowsingle` class or dict with equivalent key/value pairs
-        (e.g. `color`, `show`)
+    y: dict or `ArrowSingle` object, default=None
+        y-direction `Arrowsingle` object or dict with equivalent key/value pairs
+        (e.g. `color`, `show`).
 
-    z: dict or ArrowSingle, default=None
-        z-direction `Arrowsingle` class or dict with equivalent key/value pairs
-        (e.g. `color`, `show`)
+    z: dict or `ArrowSingle` object, default=None
+        z-direction `Arrowsingle` object or dict with equivalent key/value pairs
+        (e.g. `color`, `show`).
     """
 
     def __init__(self, x=None, y=None, z=None):
@@ -710,7 +681,9 @@ def __init__(self, x=None, y=None, z=None):
 
     @property
     def x(self):
-        """`Arrowsingle` class or dict with equivalent key/value pairs (e.g. `color`, `show`)"""
+        """
+        `ArrowSingle` object or dict with equivalent key/value pairs (e.g. `color`, `show`).
+        """
         return self._x
 
     @x.setter
@@ -719,7 +692,9 @@ def x(self, val):
 
     @property
     def y(self):
-        """`Arrowsingle` class or dict with equivalent key/value pairs (e.g. `color`, `show`)"""
+        """
+        `ArrowSingle` object or dict with equivalent key/value pairs (e.g. `color`, `show`).
+        """
         return self._y
 
     @y.setter
@@ -728,7 +703,9 @@ def y(self, val):
 
     @property
     def z(self):
-        """`Arrowsingle` class or dict with equivalent key/value pairs (e.g. `color`, `show`)"""
+        """
+        `ArrowSingle` object or dict with equivalent key/value pairs (e.g. `color`, `show`).
+        """
         return self._z
 
     @z.setter
@@ -737,35 +714,35 @@ def z(self, val):
 
 
 class ArrowSingle(MagicProperties):
-    """Single coordinate system arrow properties
+    """Single coordinate system arrow properties.
 
     Parameters
     ----------
     show: bool, default=True
-        show/hide arrow
+        Show/hide arrow.
 
     color: color, default=None
-        valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        Valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
     """
     def __init__(self, show=True, color=None):
         super().__init__(show=show, color=color)
 
     @property
     def show(self):
-        """show/hide arrow"""
+        """Show/hide arrow."""
         return self._show
 
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `show` property of {type(self).__name__} must be either `True` or `False`"
-            f" but received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be either True or False,\n"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
     @property
     def color(self):
-        """a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`"""
+        """A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`."""
         return self._color
 
     @color.setter
@@ -774,37 +751,36 @@ def color(self, val):
 
 
 class SensorProperties:
-    """
-    Defines the specific styling properties of objects of the `sensor` family
+    """Defines the specific styling properties of the Sensor class.
 
     Parameters
     ----------
     size: float, default=None
-        positive float for relative sensor to canvas size
+        Positive float for ratio of sensor to canvas size.
 
     pixel: dict, Pixel, default=None
-        `Pixel` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `Pixel` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
 
     arrows: dict, ArrowCS, default=None
-        `ArrowCS` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `ArrowCS` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
     """
 
     @property
     def size(self):
-        """positive float for relative sensor to canvas size"""
+        """Positive float for ratio of sensor to canvas size."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `size` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def pixel(self):
-        """`Pixel` class or dict with equivalent key/value pairs (e.g. `color`, `size`)"""
+        """`Pixel` object or dict with equivalent key/value pairs (e.g. `color`, `size`)."""
         return self._pixel
 
     @pixel.setter
@@ -813,7 +789,7 @@ def pixel(self, val):
 
     @property
     def arrows(self):
-        """`ArrowCS` class or dict with equivalent key/value pairs (e.g. `color`, `size`)"""
+        """`ArrowCS` object or dict with equivalent key/value pairs (e.g. `color`, `size`)."""
         return self._arrows
 
     @arrows.setter
@@ -822,19 +798,18 @@ def arrows(self, val):
 
 
 class Sensor(MagicProperties, SensorProperties):
-    """
-    Defines the specific styling properties of objects of the `sensor` family
+    """Defines styling properties of the Sensor class.
 
     Parameters
     ----------
     size: float, default=None
-        positive float for relative sensor to canvas size
+        Positive float for ratio of sensor to canvas size.
 
     pixel: dict, Pixel, default=None
-        `Pixel` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `Pixel` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
 
     arrows: dict, ArrowCS, default=None
-        `ArrowCS` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `ArrowCS` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
     """
 
     def __init__(self, size=None, pixel=None, arrows=None, **kwargs):
@@ -842,40 +817,40 @@ def __init__(self, size=None, pixel=None, arrows=None, **kwargs):
 
 
 class SensorStyle(BaseStyle, SensorProperties):
-    """Defines the styling properties of objects of the `sensor` family with base properties
+    """Defines the styling properties of the Sensor class.
 
     Parameters
     ----------
     label: str, default=None
-        label of the class instance, can be any string.
+        Label of the class instance, e.g. to be displayed in the legend.
 
-    description: dict or Description, default=None
-        object description properties
+    description: dict or `Description` object, default=None
+        Object description properties.
 
     color: str, default=None
-        a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
 
     opacity: float, default=None
-        object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
+        Object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
 
-    path: dict or Path, default=None
-        an instance of `Path` or dictionary of equivalent key/value pairs, defining the object
+    path: dict or `Path` object, default=None
+        An instance of `Path` or dictionary of equivalent key/value pairs, defining the object
         path marker and path line properties.
 
-    model3d: list of Trace3d objects, default=None
-        a list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+    model3d: list of `Trace3d` objects, default=None
+        A list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
 
     size: float, default=None
-        positive float for relative sensor to canvas size
+        Positive float for ratio of sensor size to canvas size.
 
     pixel: dict, Pixel, default=None
-        `Pixel` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `Pixel` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
 
     arrows: dict, ArrowCS, default=None
-        `ArrowCS` class or dict with equivalent key/value pairs (e.g. `color`, `size`)
+        `ArrowCS` object or dict with equivalent key/value pairs (e.g. `color`, `size`).
     """
 
     def __init__(self, **kwargs):
@@ -883,23 +858,21 @@ def __init__(self, **kwargs):
 
 
 class Pixel(MagicProperties):
-    """
-    Defines the styling properties of sensor pixels
+    """Defines the styling properties of sensor pixels.
 
     Parameters
     ----------
-    size: float, default=None
-        defines the relative pixel size:
-        - matplotlib backend: pixel size is the marker size
-        - plotly backend:  relative size to the distance of nearest neighboring pixel
+    size: float, default=1
+        Positive float for relative pixel size.
+        - matplotlib backend: Pixel size is the marker size.
+        - plotly backend: Relative distance to nearest neighbor pixel.
 
     color: str, default=None
-        defines the pixel color@property
+        Defines the pixel color@property.
 
     symbol: str, default=None
-        pixel symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`
-        Only applies for matplotlib.
-
+        Pixel symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`.
+        Only applies for matplotlib plotting backend.
     """
 
     def __init__(self, size=1, color=None, symbol=None, **kwargs):
@@ -907,22 +880,22 @@ def __init__(self, size=1, color=None, symbol=None, **kwargs):
 
     @property
     def size(self):
-        """positive float, the relative pixel size:
-        - matplotlib backend: pixel size is the marker size
-        - plotly backend:  relative size to the distance of nearest neighboring pixel"""
+        """Positive float for relative pixel size.
+        - matplotlib backend: Pixel size is the marker size.
+        - plotly backend: Relative distance to nearest neighbor pixel."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
             f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def color(self):
-        """the pixel color"""
+        """Pixel color."""
         return self._color
 
     @color.setter
@@ -931,32 +904,31 @@ def color(self, val):
 
     @property
     def symbol(self):
-        """pixel symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`"""
+        """Pixel symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`."""
         return self._symbol
 
     @symbol.setter
     def symbol(self, val):
         assert val is None or val in SYMBOLS_MATPLOTLIB_TO_PLOTLY, (
-            f"the `symbol` property of {type(self).__name__} must be one of"
-            f"{list(SYMBOLS_MATPLOTLIB_TO_PLOTLY.keys())}"
-            f" but received {repr(val)} instead"
+            f"The `symbol` property of {type(self).__name__} must be one of"
+            f"{list(SYMBOLS_MATPLOTLIB_TO_PLOTLY.keys())},\n"
+            f"but received {repr(val)} instead."
         )
         self._symbol = val
 
 
 class CurrentProperties:
-    """
-    Defines the specific styling properties of objects of the `current` family
+    """Defines styling properties of line current classes.
 
     Parameters
     ----------
-    arrow: dict or Arrow, default=None
-        Arrow class or dict with `'show'`, `'size'` properties/keys
+    arrow: dict or `Arrow` object, default=None
+        `Arrow` object or dict with `'show'`, `'size'` properties/keys.
     """
 
     @property
     def arrow(self):
-        """Arrow class or dict with `'show'`, `'size'` properties/keys"""
+        """`Arrow` object or dict with `'show'`, `'size'` properties/keys."""
         return self._arrow
 
     @arrow.setter
@@ -965,13 +937,12 @@ def arrow(self, val):
 
 
 class Current(MagicProperties, CurrentProperties):
-    """
-    Defines the specific styling properties of objects of the `current` family
+    """Defines the specific styling properties of line current classes.
 
     Parameters
     ----------
-    arrow: dict or Arrow, default=None
-        Arrow class or dict with 'show', 'size' properties/keys
+    arrow: dict or `Arrow`object, default=None
+        `Arrow` object or dict with 'show', 'size' properties/keys.
     """
 
     def __init__(self, arrow=None, **kwargs):
@@ -979,34 +950,34 @@ def __init__(self, arrow=None, **kwargs):
 
 
 class CurrentStyle(BaseStyle, CurrentProperties):
-    """Defines the styling properties of objects of the `current` family and base properties
+    """Defines styling properties of line current classes.
 
     Parameters
     ----------
     label: str, default=None
-        label of the class instance, can be any string.
+        Label of the class instance, e.g. to be displayed in the legend.
 
-    description: dict or Description, default=None
-        object description properties
+    description: dict or `Description` object, default=None
+        Object description properties.
 
     color: str, default=None
-        a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
 
     opacity: float, default=None
-        object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
+        Object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
 
-    path: dict or Path, default=None
-        an instance of `Path` or dictionary of equivalent key/value pairs, defining the object
+    path: dict or `Path` object, default=None
+        An instance of `Path` or dictionary of equivalent key/value pairs, defining the object
         path marker and path line properties.
 
-    model3d: list of Trace3d objects, default=None
-        a list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+    model3d: list of `Trace3d` objects, default=None
+        A list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
 
-    arrow: dict or Arrow, default=None
-        Arrow class or dict with `'show'`, `'size'` properties/keys
+    arrow: dict or `Arrow` object, default=None
+        `Arrow` object or dict with `'show'`, `'size'` properties/keys.
     """
 
     def __init__(self, **kwargs):
@@ -1014,19 +985,18 @@ def __init__(self, **kwargs):
 
 
 class Arrow(MagicProperties):
-    """
-    Defines the styling properties of current arrows
+    """Defines styling properties of current arrows.
 
     Parameters
     ----------
     show: bool, default=None
-        if `True` current direction is shown with an arrow
+        If True current direction is shown with an arrow.
 
-    size: float
-        positive number defining the size of the arrows
+    size: float, default=None
+        Positive number defining the size of the arrows.
 
     width: float, default=None
-        positive number that defines the arrow line width
+        Positive number that defines the arrow line width.
     """
 
     def __init__(self, show=None, size=None, **kwargs):
@@ -1034,57 +1004,55 @@ def __init__(self, show=None, size=None, **kwargs):
 
     @property
     def show(self):
-        """show/hide arrow showing current direction"""
+        """Show/hide arrow showing current direction."""
         return self._show
 
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `show` property of {type(self).__name__} must be either `True` or `False`"
-            f" but received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be either True or False,"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
     @property
     def size(self):
-        """positive number defining the size of the arrows"""
+        """Positive number defining the size of the arrows."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `size` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def width(self):
-        """positive number that defines the arrow line width"""
+        """Positive number that defines the arrow line width."""
         return self._width
 
     @width.setter
     def width(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `width` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `width` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._width = val
 
 
 class Marker(MagicProperties):
-    """
-    Defines the styling properties of plot markers
+    """Defines styling properties of plot markers.
 
     Parameters
     ----------
     size: float, default=None
-        marker size
+        Marker size.
     color: str, default=None
-        marker color
+        Marker color.
     symbol: str, default=None
-        marker symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`
-
+        Marker symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`.
     """
 
     def __init__(self, size=None, color=None, symbol=None, **kwargs):
@@ -1092,20 +1060,20 @@ def __init__(self, size=None, color=None, symbol=None, **kwargs):
 
     @property
     def size(self):
-        """marker size"""
+        """Marker size."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `size` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def color(self):
-        """marker color"""
+        """Marker color."""
         return self._color
 
     @color.setter
@@ -1114,28 +1082,27 @@ def color(self, val):
 
     @property
     def symbol(self):
-        """marker symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`"""
+        """Marker symbol. Can be one of `['.', 'o', '+', 'D', 'd', 's', 'x']`."""
         return self._symbol
 
     @symbol.setter
     def symbol(self, val):
         assert val is None or val in SYMBOLS_MATPLOTLIB_TO_PLOTLY, (
-            f"the `symbol` property of {type(self).__name__} must be one of"
-            f"{list(SYMBOLS_MATPLOTLIB_TO_PLOTLY.keys())}"
-            f" but received {repr(val)} instead"
+            f"The `symbol` property of {type(self).__name__} must be one of"
+            f"{list(SYMBOLS_MATPLOTLIB_TO_PLOTLY.keys())},\n"
+            f"but received {repr(val)} instead."
         )
         self._symbol = val
 
 
 class Markers(BaseStyle):
-    """
-    Defines the styling properties of the markers trace
+    """Defines styling properties of the markers trace.
 
     Parameters
     ----------
-    marker: dict, Markers, default=None
-        Markers class with 'color', 'symbol', 'size' properties, or dictionary with equivalent
-        key/value pairs
+    marker: dict or `Markers` object, default=None
+        `Markers` object with 'color', 'symbol', 'size' properties, or dictionary with equivalent
+        key/value pairs.
     """
 
     def __init__(self, marker=None, **kwargs):
@@ -1143,7 +1110,7 @@ def __init__(self, marker=None, **kwargs):
 
     @property
     def marker(self):
-        """Markers class with 'color', 'symbol', 'size' properties"""
+        """`Markers` object with 'color', 'symbol', 'size' properties."""
         return self._marker
 
     @marker.setter
@@ -1152,62 +1119,62 @@ def marker(self, val):
 
 
 class DipoleProperties:
-    """
-    Defines the specific styling properties of the objects of the `dipole` family
+    """Defines styling properties of dipoles.
 
     Parameters
     ----------
-    size: float, default=None
-        positive float for relative dipole to size to canvas size
+    size: float
+        Positive value for ratio of dipole size to canvas size.
 
-    pivot: str, default=None
-        the part of the arrow that is anchored to the X, Y grid.
-        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`
+    pivot: str
+        The part of the arrow that is anchored to the X, Y grid.
+        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`.
     """
 
     _allowed_pivots = ("tail", "middle", "tip")
 
     @property
     def size(self):
-        """positive float for relative dipole to size to canvas size"""
+        """Positive value for ratio of dipole size to canvas size."""
         return self._size
 
     @size.setter
     def size(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `size` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `size` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._size = val
 
     @property
     def pivot(self):
         """The part of the arrow that is anchored to the X, Y grid.
-        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`"""
+        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`.
+        """
         return self._pivot
 
     @pivot.setter
     def pivot(self, val):
         assert val is None or val in (self._allowed_pivots), (
-            f"the `pivot` property of {type(self).__name__} must be one of "
-            f"{self._allowed_pivots}\n"
-            f" but received {repr(val)} instead"
+            f"The `pivot` property of {type(self).__name__} must be one of "
+            f"{self._allowed_pivots},\n"
+            f"but received {repr(val)} instead."
         )
         self._pivot = val
 
 
 class Dipole(MagicProperties, DipoleProperties):
     """
-    Defines the specific styling properties of the objects of the `dipole` family
+    Defines styling properties of dipoles.
 
     Parameters
     ----------
     size: float, default=None
-        positive float for relative dipole to size to canvas size
+        Positive float for ratio of dipole size to canvas size.
 
     pivot: str, default=None
-        the part of the arrow that is anchored to the X, Y grid.
-        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`
+        The part of the arrow that is anchored to the X, Y grid.
+        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`.
     """
 
     def __init__(self, size=None, pivot=None, **kwargs):
@@ -1215,38 +1182,38 @@ def __init__(self, size=None, pivot=None, **kwargs):
 
 
 class DipoleStyle(BaseStyle, DipoleProperties):
-    """Defines the styling properties of the objects of the `dipole` family and base properties
+    """Defines the styling properties of dipole objects.
 
     Parameters
     ----------
     label: str, default=None
-        label of the class instance, can be any string.
+        Label of the class instance, e.g. to be displayed in the legend.
 
-    description: dict or Description, default=None
-        object description properties
+    description: dict or `Description` object, default=None
+        Object description properties.
 
     color: str, default=None
-        a valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`
+        A valid css color. Can also be one of `['r', 'g', 'b', 'y', 'm', 'c', 'k', 'w']`.
 
     opacity: float, default=None
-        object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
+        Object opacity between 0 and 1, where 1 is fully opaque and 0 is fully transparent.
 
-    path: dict or Path, default=None
-        an instance of `Path` or dictionary of equivalent key/value pairs, defining the object
+    path: dict or `Path` object, default=None
+        An instance of `Path` or dictionary of equivalent key/value pairs, defining the object
         path marker and path line properties.
 
-    model3d: list of Trace3d objects, default=None
-        a list of traces where each is an instance of `Trace3d` or dictionary of equivalent
+    model3d: list of `Trace3d` objects, default=None
+        A list of traces where each is an instance of `Trace3d` or dictionary of equivalent
         key/value pairs. Defines properties for an additional user-defined model3d object which is
         positioned relatively to the main object to be displayed and moved automatically with it.
         This feature also allows the user to replace the original 3d representation of the object.
 
     size: float, default=None
-        positive float for relative dipole to size to canvas size
+        Positive float for ratio of dipole size to canvas size.
 
     pivot: str, default=None
-        the part of the arrow that is anchored to the X, Y grid.
-        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`
+        The part of the arrow that is anchored to the X, Y grid.
+        The arrow rotates about this point. Can be one of `['tail', 'middle', 'tip']`.
     """
 
     def __init__(self, **kwargs):
@@ -1254,32 +1221,31 @@ def __init__(self, **kwargs):
 
 
 class Path(MagicProperties):
-    """
-    Defines the styling properties of an object's path
+    """Defines styling properties of an object's path.
 
     Parameters
     ----------
-    marker: dict, Markers, default=None
-        Markers class with 'color', 'symbol', 'size' properties, or dictionary with equivalent
-        key/value pairs
+    marker: dict or `Markers` object, default=None
+        `Markers` object with 'color', 'symbol', 'size' properties, or dictionary with equivalent
+        key/value pairs.
 
-    line: dict, Line, default=None
-        Line class with 'color', 'symbol', 'size' properties, or dictionary with equivalent
-        key/value pairs
+    line: dict or `Line` object, default=None
+        `Line` object with 'color', 'symbol', 'size' properties, or dictionary with equivalent
+        key/value pairs.
 
     show: bool, default=None
-        show/hide path
-        - False: shows object(s) at final path position and hides paths lines and markers.
-        - True: shows object(s) shows object paths depending on `line`, `marker` and `frames`
+        Show/hide path.
+        - False: Shows object(s) at final path position and hides paths lines and markers.
+        - True: Shows object(s) shows object paths depending on `line`, `marker` and `frames`
         parameters.
 
-    frames: int or 1D-array_like
-        show copies of the 3D-model along the given path indices.
-        - integer i: displays the object(s) at every i'th path position.
-        - array_like shape (n,) of integers: describes certain path indices.
+    frames: int or array_like, shape (n,), default=None
+        Show copies of the 3D-model along the given path indices.
+        - integer i: Displays the object(s) at every i'th path position.
+        - array_like, shape (n,), dtype=int: Displays object(s) at given path indices.
 
     numbering: bool, default=False
-        show/hide numbering on path positions. Only applies if show=True.
+        Show/hide numbering on path positions.
     """
 
     def __init__(
@@ -1296,7 +1262,7 @@ def __init__(
 
     @property
     def marker(self):
-        """Markers class with 'color', 'symbol', 'size' properties"""
+        """`Markers` object with 'color', 'symbol', 'size' properties."""
         return self._marker
 
     @marker.setter
@@ -1305,7 +1271,7 @@ def marker(self, val):
 
     @property
     def line(self):
-        """Line class with 'color', 'type', 'width' properties"""
+        """`Line` object with 'color', 'type', 'width' properties."""
         return self._line
 
     @line.setter
@@ -1314,9 +1280,9 @@ def line(self, val):
 
     @property
     def show(self):
-        """show/hide path
-        - False: shows object(s) at final path position and hides paths lines and markers.
-        - True: shows object(s) shows object paths depending on `line`, `marker` and `frames`
+        """Show/hide path.
+        - False: Shows object(s) at final path position and hides paths lines and markers.
+        - True: Shows object(s) shows object paths depending on `line`, `marker` and `frames`
         parameters.
         """
         return self._show
@@ -1324,17 +1290,16 @@ def show(self):
     @show.setter
     def show(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `show` property of {type(self).__name__} must be either "
-            "`True` or `False`"
-            f"\nbut received {repr(val)} instead"
+            f"The `show` property of {type(self).__name__} must be either True or False,\n"
+            f"but received {repr(val)} instead."
         )
         self._show = val
 
     @property
     def frames(self):
-        """show copies of the 3D-model along the given path indices.
-        - integer i: displays the object(s) at every i'th path position.
-        - array_like shape (n,) of integers: describes certain path indices.
+        """Show copies of the 3D-model along the given path indices.
+        - integer i: Displays the object(s) at every i'th path position.
+        - array_like shape (n,) of integers: Displays object(s) at given path indices.
         """
         return self._frames
 
@@ -1347,29 +1312,28 @@ def frames(self, val):
                 is_valid_path = False
         elif not (val is None or np.issubdtype(type(val), int)):
             is_valid_path = False
-        assert is_valid_path, f"""the `frames` property of {type(self).__name__} must be either:
-- integer i: displays the object(s) at every i'th path position.
-- array_like shape (n,) of integers: describes certain path indices.
+        assert is_valid_path, f"""The `frames` property of {type(self).__name__} must be either:
+- integer i: Displays the object(s) at every i'th path position.
+- array_like, shape (n,), dtype=int: Displays object(s) at given path indices.
 but received {repr(val)} instead"""
         self._frames = val
 
     @property
     def numbering(self):
-        """show/hide numbering on path positions. Only applies if show=True."""
+        """Show/hide numbering on path positions. Only applies if show=True."""
         return self._numbering
 
     @numbering.setter
     def numbering(self, val):
         assert val is None or isinstance(val, bool), (
-            f"the `numbering` property of {type(self).__name__} must be one of (True, False),"
-            f" but received {repr(val)} instead"
+            f"The `numbering` property of {type(self).__name__} must be one of (True, False),\n"
+            f"but received {repr(val)} instead."
         )
         self._numbering = val
 
 
 class Line(MagicProperties):
-    """
-    Defines Line styling properties
+    """Defines line styling properties.
 
     Parameters
     ----------
@@ -1379,11 +1343,10 @@ class Line(MagicProperties):
         'loosely dotted', 'loosely dashdotted']`
 
     color: str, default=None
-        line color
+        Line color.
 
     width: float, default=None
-        positive number that defines the line width
-
+        Positive number that defines the line width.
     """
 
     def __init__(self, style=None, color=None, width=None, **kwargs):
@@ -1391,21 +1354,21 @@ def __init__(self, style=None, color=None, width=None, **kwargs):
 
     @property
     def style(self):
-        """line style"""
+        """Line style."""
         return self._style
 
     @style.setter
     def style(self, val):
         assert val is None or val in LINESTYLES_MATPLOTLIB_TO_PLOTLY, (
-            f"the `style` property of {type(self).__name__} must be one of"
-            f"{list(LINESTYLES_MATPLOTLIB_TO_PLOTLY.keys())}"
-            f" but received {repr(val)} instead"
+            f"The `style` property of {type(self).__name__} must be one of "
+            f"{list(LINESTYLES_MATPLOTLIB_TO_PLOTLY.keys())},\n"
+            f"but received {repr(val)} instead."
         )
         self._style = val
 
     @property
     def color(self):
-        """line color"""
+        """Line color."""
         return self._color
 
     @color.setter
@@ -1414,42 +1377,41 @@ def color(self, val):
 
     @property
     def width(self):
-        """positive number that defines the line width"""
+        """Positive number that defines the line width."""
         return self._width
 
     @width.setter
     def width(self, val):
         assert val is None or isinstance(val, (int, float)) and val >= 0, (
-            f"the `width` property of {type(self).__name__} must be a positive number"
-            f" but received {repr(val)} instead"
+            f"The `width` property of {type(self).__name__} must be a positive number,\n"
+            f"but received {repr(val)} instead."
         )
         self._width = val
 
 
 class DisplayStyle(MagicProperties):
-    """
-    Base class containing styling properties for all object families. The properties of the
-    sub-classes get set to hard coded defaults at class instantiation
+    """Base class containing styling properties for all object families. The properties of the
+    sub-classes are set to hard coded defaults at class instantiation.
 
     Parameters
     ----------
-    base: dict, Base, default=None
-        base properties common to all families
+    base: dict or `Base` object, default=None
+        Base properties common to all families.
 
-    magnet: dict, Magnet, default=None
-        magnet properties
+    magnet: dict or `Magnet` object, default=None
+        Magnet properties.
 
-    current: dict, Current, default=None
-        current properties
+    current: dict or `Current` object, default=None
+        Current properties.
 
-    dipole: dict, Dipole, default=None
-        dipole properties
+    dipole: dict or `Dipole` object, default=None
+        Dipole properties.
 
-    sensor: dict, Sensor, default=None
-        sensor properties
+    sensor: dict or `Sensor` object, default=None
+        Sensor properties.
 
-    markers: dict, Markers, default=None
-        markers properties
+    markers: dict or `Markers` object, default=None
+        Markers properties.
     """
 
     def __init__(
@@ -1474,13 +1436,13 @@ def __init__(
         # self.reset()
 
     def reset(self):
-        """Resets all nested properties to their hard coded default values"""
+        """Resets all nested properties to their hard coded default values."""
         self.update(get_defaults_dict("display.style"), _match_properties=False)
         return self
 
     @property
     def base(self):
-        """base properties common to all families"""
+        """Base properties common to all families."""
         return self._base
 
     @base.setter
@@ -1489,7 +1451,7 @@ def base(self, val):
 
     @property
     def magnet(self):
-        """Magnet class"""
+        """Magnet class."""
         return self._magnet
 
     @magnet.setter
@@ -1498,7 +1460,7 @@ def magnet(self, val):
 
     @property
     def current(self):
-        """Current class"""
+        """Current class."""
         return self._current
 
     @current.setter
@@ -1507,7 +1469,7 @@ def current(self, val):
 
     @property
     def dipole(self):
-        """Dipole class"""
+        """Dipole class."""
         return self._dipole
 
     @dipole.setter
@@ -1516,7 +1478,7 @@ def dipole(self, val):
 
     @property
     def sensor(self):
-        """Sensor"""
+        """Sensor class."""
         return self._sensor
 
     @sensor.setter
@@ -1525,7 +1487,7 @@ def sensor(self, val):
 
     @property
     def markers(self):
-        """Markers class"""
+        """Markers class."""
         return self._markers
 
     @markers.setter
diff --git a/magpylib/_src/utility.py b/magpylib/_src/utility.py
index 05f635a52..6f18e5b7e 100644
--- a/magpylib/_src/utility.py
+++ b/magpylib/_src/utility.py
@@ -1,13 +1,11 @@
 """ some utility functions"""
+import numbers
 from math import log10
 from typing import Sequence
 import numpy as np
-
-# from scipy.spatial.transform import Rotation as R
 from magpylib._src.exceptions import MagpylibBadUserInput
 from magpylib import _src
-from magpylib._src.defaults.defaults_classes import default_settings as Config
-from magpylib._src.input_checks import check_position_format
+from magpylib._src.input_checks import check_format_input_vector
 
 LIBRARY_SOURCES = (
     "Cuboid",
@@ -171,7 +169,7 @@ def format_obs_inputs(observers) -> list:
         observers = (observers,)
     elif len(observers) == 0:
         raise MagpylibBadUserInput(wrong_obj_msg(allow="observers"))
-    elif np.isscalar(observers[0]):
+    elif isinstance(observers[0], numbers.Number):
         observers = (observers,)
 
     sensors = []
@@ -183,8 +181,12 @@ def format_obs_inputs(observers) -> list:
 
         # case 2: ndarray of positions
         elif isinstance(obs, (list, tuple, np.ndarray)):
-            if Config.checkinputs:
-                check_position_format(np.array(obs), "observer position")
+            check_format_input_vector(
+                obs,
+                dims=range(1,20),
+                shape_m1=3,
+                sig_name='observer position',
+                sig_type='array_like (list, tuple, ndarray) with shape (n1, n2, ..., 3)')
             sensors.append(Sensor(pixel=obs))
         elif getattr(obs, "_object_type", "") == "Collection":
             if not obs.sensors:
@@ -277,7 +279,7 @@ def filter_objects(obj_list, allow="sources+sensors", warn=True):
         if obj._object_type in allowed_list:
             new_list += [obj]
         else:
-            if Config.checkinputs and warn:
+            if warn:
                 print(f"Warning, cannot add {obj.__repr__()} to Collection.")
     return new_list
 
diff --git a/setup.py b/setup.py
index 96ffc4e57..de1b4ef77 100644
--- a/setup.py
+++ b/setup.py
@@ -64,7 +64,7 @@ def run(self):
     packages=setuptools.find_packages(),
     zip_safe=False,  ## Gives the environment files so we can access docs,
     ## enables tooltips but may decrease performance
-    install_requires=["numpy>=1.20", "scipy>=1.7", "matplotlib>=3.3",],
+    install_requires=["numpy>=1.20", "scipy>=1.7", "matplotlib>=3.3"],
     # kaleido, jupyterlab are needed for testing with display(renderer='json', backend='plotly')
     extras_require={
         "testing": [


Test patch:
diff --git a/tests/test_CustomSource.py b/tests/test_CustomSource.py
index 16e63f8a5..d927b1f80 100644
--- a/tests/test_CustomSource.py
+++ b/tests/test_CustomSource.py
@@ -1,61 +1,79 @@
 import pytest
 import numpy as np
 import magpylib as magpy
-from magpylib._src.exceptions import MagpylibInternalError
+from magpylib._src.exceptions import MagpylibBadUserInput, MagpylibInternalError
 
 # pylint: disable=assignment-from-no-return
 # pylint: disable=unused-argument
 
-def constant_Bfield(position=((0, 0, 0))):
+def constant_Bfield(position=(0,0,0)):
     """ constant field"""
+    position = np.array(position)
+    if position.ndim==1:
+        return np.array([1,2,3])
     return np.array([[1, 2, 3]] * len(position))
 
-def constant_Hfield(position=((0, 0, 0))):
+def constant_Hfield(position=(0, 0, 0)):
     """ constant field - no idea why we need this """
-    return np.array([[4, 5, 6]] * len(position))
+    position = np.array(position)
+    if position.ndim==1:
+        return np.array([1, 2, 3])
+    return np.array([[1, 2, 3]] * len(position))
 
 def bad_Bfield_func(position):
     """ another constant function without docstring"""
     return np.array([[1, 2, 3]])
 
+
 def test_CustomSource_basicB():
     """Basic custom source class test"""
     external_field = magpy.misc.CustomSource(field_B_lambda=constant_Bfield)
 
+    B = external_field.getB((1, 2, 3))
+    Btest = np.array((1, 2, 3))
+    np.testing.assert_allclose(B, Btest)
+
     B = external_field.getB([[1, 2, 3], [4, 5, 6]])
     Btest = np.array([[1, 2, 3]] * 2)
-    assert np.allclose(B, Btest)
+    np.testing.assert_allclose(B, Btest)
 
     external_field.rotate_from_angax(45, "z")
     B = external_field.getB([[1, 2, 3], [4, 5, 6]])
     Btest = np.array([[-0.70710678, 2.12132034, 3.0]] * 2)
-    assert np.allclose(B, Btest)
+    np.testing.assert_allclose(B, Btest)
 
 
 def test_CustomSource_basicH():
     """Basic custom source class test"""
     external_field = magpy.misc.CustomSource(field_H_lambda=constant_Hfield)
 
+    H = external_field.getH((1, 2, 3))
+    Htest = np.array((1, 2, 3))
+    np.testing.assert_allclose(H, Htest)
+
     H = external_field.getH([[1, 2, 3], [4, 5, 6]])
-    Htest = np.array([[4, 5, 6]] * 2)
-    assert np.allclose(H, Htest)
+    Htest = np.array([[1, 2, 3]] * 2)
+    np.testing.assert_allclose(H, Htest)
 
-    external_field.rotate_from_angax(35, "x")
+    external_field.rotate_from_angax(45, "z")
     H = external_field.getH([[1, 2, 3], [4, 5, 6]])
-    Htest = np.array([[4.0, 0.6543016, 7.78279445]] * 2)
-    assert np.allclose(H, Htest)
+    Htest = np.array([[-0.70710678, 2.12132034, 3.0]] * 2)
+    np.testing.assert_allclose(H, Htest)
+
 
 def test_CustomSource_bad_inputs():
-    with pytest.raises(AssertionError):
+    """missing docstring"""
+    with pytest.raises(MagpylibBadUserInput):
         magpy.misc.CustomSource(field_H_lambda='not a callable')
 
-    with pytest.raises(AssertionError):
+    with pytest.raises(MagpylibBadUserInput):
         magpy.misc.CustomSource(field_H_lambda=bad_Bfield_func)
 
     src = magpy.misc.CustomSource()
     with pytest.raises(MagpylibInternalError):
         src.getB([0,0,0])
 
+
 def test_repr():
     """test __repr__"""
     dip = magpy.misc.CustomSource()
diff --git a/tests/test_defaults.py b/tests/test_defaults.py
index 3e91aa18a..10408dae3 100644
--- a/tests/test_defaults.py
+++ b/tests/test_defaults.py
@@ -8,7 +8,6 @@
 )
 
 bad_inputs = {
-    "checkinputs": (-1,),  # bool
     "display_autosizefactor": (0,),  # float>0
     "display_animation_maxfps": (0,),  # int>0
     "display_animation_fps": (0,),  # int>0
@@ -93,7 +92,6 @@ def test_defaults_bad_inputs(key, value, expected_errortype):
 # This is just for check. dict keys should not be tuples in general, but the test will iterate
 # over the values for each key
 good_inputs = {
-    "checkinputs": (True, False),  # bool
     "display_autosizefactor": (1,),  # float>0
     "display_animation_maxfps": (10,),  # int>0
     "display_animation_fps": (10,),  # int>0
@@ -210,9 +208,9 @@ def test_bad_default_classes():
     with pytest.raises(ValueError):
         magpy.defaults.display.style = "wrong input"
 
-def test_resetting_defaults():
-    """test setting and resetting the config"""
-    magpy.defaults.checkinputs = False
-    assert magpy.defaults.checkinputs is False, "setting config failed"
-    magpy.defaults.reset()
-    assert magpy.defaults.checkinputs is True, "resetting config failed"
+# def test_resetting_defaults():
+#     """test setting and resetting the config"""
+#     magpy.defaults.checkinputs = False
+#     assert magpy.defaults.checkinputs is False, "setting config failed"
+#     magpy.defaults.reset()
+#     assert magpy.defaults.checkinputs is True, "resetting config failed"
diff --git a/tests/test_display_matplotlib.py b/tests/test_display_matplotlib.py
index cbd5ab0cd..ad5b66466 100644
--- a/tests/test_display_matplotlib.py
+++ b/tests/test_display_matplotlib.py
@@ -7,21 +7,22 @@
 
 # pylint: disable=assignment-from-no-return
 
+magpy.defaults.reset()
 
 def test_Cylinder_display():
     """testing display"""
     ax = plt.subplot(projection="3d")
     src = Cylinder((1, 2, 3), (1, 2))
-    x = src.show(canvas=ax, style_path_frames=15)
+    x = src.show(canvas=ax, style_path_frames=15, backend='matplotlib')
     assert x is None, "path should revert to True"
     src.move(np.linspace((.4,.4,.4), (12,12,12), 30), start=-1)
-    x = src.show(canvas=ax, style_path_show=False, show_direction=True)
+    x = src.show(canvas=ax, style_path_show=False, show_direction=True, backend='matplotlib')
     assert x is None, "display test fail"
 
-    x = src.show(canvas=ax, style_path_frames=[], show_direction=True)
+    x = src.show(canvas=ax, style_path_frames=[], show_direction=True, backend='matplotlib')
     assert x is None, "ind>path_len, should display last position"
 
-    x = src.show(canvas=ax, style_path_frames=[1, 5, 6], show_direction=True)
+    x = src.show(canvas=ax, style_path_frames=[1, 5, 6], show_direction=True, backend='matplotlib')
     assert x is None, "should display 1,5,6 position"
 
 
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 4b9633542..c204b34ba 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -289,11 +289,11 @@ def test_except_getBH_lev2(self):
     def test_except_bad_input_shape_basegeo(self):
         """ BaseGeo bad input shapes
         """
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_basegeo_pos)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_cuboid_dim)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_cuboid_mag)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_cyl_dim)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_cyl_mag)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_sphere_mag)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_sensor_pix_pos)
-        self.assertRaises(MagpylibBadInputShape, bad_input_shape_dipole_mom)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_basegeo_pos)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_cuboid_dim)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_cuboid_mag)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_cyl_dim)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_cyl_mag)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_sphere_mag)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_sensor_pix_pos)
+        self.assertRaises(MagpylibBadUserInput, bad_input_shape_dipole_mom)
diff --git a/tests/test_input_checks.py b/tests/test_input_checks.py
index 0843ff330..8fc70ea63 100644
--- a/tests/test_input_checks.py
+++ b/tests/test_input_checks.py
@@ -1,411 +1,616 @@
-import unittest
 import numpy as np
 from scipy.spatial.transform import Rotation as R
-from magpylib._src.obj_classes.class_BaseGeo import BaseGeo
-from magpylib._src.exceptions import (MagpylibBadUserInput,
-    MagpylibBadInputShape, MagpylibMissingInput)
+from magpylib._src.exceptions import MagpylibBadUserInput, MagpylibMissingInput
 import magpylib as magpy
 
-a3 = np.array([1,2,3])
-a2 = np.array([1,2])
-a234 = np.array([[[1,2]]*3]*4)
-r0 = R.from_quat((0,0,0,1))
-
-# Check error throwing when dimension or excitation is not initialized
-def init_dim_display1():
-    """throw dimension init error from display"""
-    src1 = magpy.current.Loop(current=1)
-    src2 = magpy.magnet.Sphere(magnetization=(1,2,3))
-    magpy.show(src1, src2)
-
-def init_dim_display2():
-    """throw dimension init error from display"""
-    src1 = magpy.current.Loop(current=1, diameter=2)
-    src2 = magpy.magnet.Cuboid(magnetization=(1,2,3))
-    magpy.show(src1, src2)
-
-def init_dim_display3():
-    """throw dimension init error from display"""
-    src1 = magpy.current.Loop(current=1, diameter=2)
-    src2 = magpy.current.Line(current=1)
-    magpy.show(src1, src2)
-
-def init_dim_getBH1():
-    """throw dimension init error from getB"""
-    src1 = magpy.current.Loop(current=1)
-    src2 = magpy.magnet.Sphere(magnetization=(1,2,3))
-    magpy.getB([src1,src2], (1,2,3))
-
-def init_excite_display1():
-    """throw excitation init error from display"""
-    src1 = magpy.current.Loop(diameter=1)
-    src2 = magpy.magnet.Sphere(diameter=2)
-    magpy.show(src1, src2, style_magnetization_show=True, style_arrow_show=True)
-
-def init_excite_getBH1():
-    """throw excitation init error from getB"""
-    src1 = magpy.current.Loop(diameter=1, current=1)
-    src2 = magpy.magnet.Sphere(diameter=2)
-    magpy.getB([src1, src2], (1,2,3))
-
-def init_excite_getBH2():
-    """throw excitation init error from getB"""
-    src1 = magpy.current.Loop(current=1, diameter=2)
-    src2 = magpy.magnet.Cuboid(dimension=(1,2,3))
-    magpy.getB([src1, src2], (1,2,3))
-
-def init_excite_getBH3():
-    """throw excitation init error from getB"""
-    src1 = magpy.current.Loop(current=1, diameter=2)
-    src2 = magpy.current.Line(vertices=[(1,2,3),(2,3,4)])
-    magpy.getB([src1, src2], (1,2,3))
-
-def init_excite_getBH4():
-    """throw excitation init error from getB"""
-    src1 = magpy.current.Loop(current=1, diameter=2)
-    src2 = magpy.misc.Dipole()
-    magpy.getB([src1, src2], (1,2,3))
-
-# BAD BASEGEO INPUTS --------------------------------------
-def badInput_BaseGeo1():
-    """ bad pos input type"""
-    BaseGeo(123, r0)
-
-def badInput_BaseGeo2():
-    """ bad pos input format1"""
-    BaseGeo(a2, r0)
-
-def badInput_BaseGeo3():
-    """ bad pos input format2"""
-    BaseGeo(a234, r0)
-
-def badInput_BaseGeo4():
-    """ bad rot input type"""
-    BaseGeo(a3, 123)
-
-
-# BAD BASEGEO .MOVE INPUT--------------------------------------------------
-def badInput_move1():
-    """ bad displacement input type"""
-    bg = BaseGeo(a3, r0)
-    bg.move(123)
-
-def badInput_move2():
-    """ bad start input type"""
-    bg = BaseGeo(a3, r0)
-    bg.move([1,2,3], start=1.1)
-
-def badInput_move3():
-    """ bad displacement input format1"""
-    bg = BaseGeo(a3, r0)
-    bg.move([1,2])
-
-def badInput_move4():
-    """ bad displacement input format2"""
-    bg = BaseGeo(a3, r0)
-    bg.move([[[1,2,3]]*3]*4)
-
-
-# BAD BASEGEO .ROTATE INPUT------------------------------------------------
-def badInput_rotate1():
-    """ bad rot input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate(123)
-
-def badInput_rotate2():
-    """ bad anchor input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate(r0, anchor=123)
-
-def badInput_rotate3():
-    """ bad start input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate(r0, start=1.23)
-
-def badInput_rotate5():
-    """ bad anchor input format"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate(r0, anchor=(1,2,3,4))
-
-
-# BAD BASEGEO .rotate_from_angax INPUT-----------------------------------------
-def badInput_rotate_from_angax1():
-    """ bad ang input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax('123', (1,2,3))
-
-def badInput_rotate_from_angax2():
-    """ bad axis input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, 1)
-
-def badInput_rotate_from_angax3():
-    """ bad anchor input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, (1,2,3), anchor=1)
-
-def badInput_rotate_from_angax4():
-    """ bad start input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, (1,2,3), start=1.1)
-
-def badInput_rotate_from_angax6():
-    """ bad degrees input type"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, (1,2,3), degrees=None)
-
-def badInput_rotate_from_angax7():
-    """ bad angle input format"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax([[1,2,3]]*2, (1,2,3))
-
-def badInput_rotate_from_angax8():
-    """ bad axis input format"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax([1,2,3], [1,2,3,4])
-
-def badInput_rotate_from_angax9():
-    """ bad axis input format"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, [0,0,0])
-
-def badInput_rotate_from_angax10():
-    """ bad axis input format"""
-    bg = BaseGeo(a3, r0)
-    bg.rotate_from_angax(123, [1,2,3], anchor=[[(1,2,3)]*2]*2)
-
-
-# EXCITATIONS -----------------------------------------
-
-def badMag_input1():
-    """bad magnetization input type"""
-    magpy.magnet.Cuboid(magnetization='woot', dimension=a3)
-
-def badMag_input2():
-    """bad magnetization input format"""
-    magpy.magnet.Cuboid(magnetization=(1,2,3,4), dimension=a3)
-
-def badCurrent_input1():
-    """bad current input type"""
-    magpy.current.Loop(current='1', diameter=1)
-
-# DIMENSIONS --------------------------------------------
-
-def bad_dim_input1():
-    """cuboid dim type"""
-    magpy.magnet.Cuboid(magnetization=a3, dimension=1)
-
-def bad_dim_input3():
-    """cuboid dim format"""
-    magpy.magnet.Cuboid(magnetization=a3, dimension=(1,2))
-
-def bad_dim_input4():
-    """cylinder dim type"""
-    magpy.magnet.Cylinder(magnetization=a3, dimension=1)
-def bad_dim_input6():
-    """cylinder bad dim shape"""
-    magpy.magnet.Cylinder(magnetization=a3, dimension=(1,2,0,45))
-
-def bad_dim_input7():
-    """Sphere dim type"""
-    magpy.magnet.Sphere(magnetization=a3, diameter=(1,1))
-
-def bad_dim_input9():
-    """Loop dim type"""
-    magpy.current.Loop(current=1, diameter=(1,1))
-
-def bad_dim_input10():
-    """cylinder segment bad dim shape"""
-    magpy.magnet.CylinderSegment(magnetization=a3, dimension=(1,2,0,45))
-def bad_dim_input11():
-    """cylinder segment bad dim d1>d2"""
-    magpy.magnet.CylinderSegment(magnetization=a3, dimension=(3,2,2,0,45))
-def bad_dim_input12():
-    """cylinder segment bad dim phi1>phi2"""
-    magpy.magnet.CylinderSegment(magnetization=a3, dimension=(1,2,2,100,45))
-def bad_dim_input13():
-    """cylinder segment bad dim phi2-phi1>360"""
-    magpy.magnet.CylinderSegment(magnetization=a3, dimension=(1,2,2,0,1145))
-
-
-# MISC SOURCE ------------------------------------------------------------
-
-def bad_misc_input1():
-    """Sensor pixel type"""
-    magpy.Sensor(pixel=1)
-def bad_misc_input2():
-    """Sensor pixel format"""
-    magpy.Sensor(pixel=[[1,2]]*3)
-
-def bad_misc_input3():
-    """Line vertex type"""
-    magpy.current.Line(1, '1')
-def bad_misc_input4():
-    """Line vertex format 1"""
-    magpy.current.Line(1, [(1,2,3)])
-def bad_misc_input5():
-    """Line vertex format 2"""
-    magpy.current.Line(1, [[(1,2,3)]*2]*2)
-def bad_misc_input6():
-    """Line vertex format 3"""
-    magpy.current.Line(1, [(1,2)]*2)
-
-
-# OBSERVER ------------------------------------------------------------
-
-src = magpy.current.Loop(current=1, diameter=1)
-sens = magpy.Sensor()
-def bad_observer_input1():
-    """getBH observer format"""
-    magpy.getB(src, a234)
-def bad_observer_input2():
-    """getBH observer format"""
-    magpy.getB(src, [sens, [(1,2),(1,2)]])
-def bad_observer_input3():
-    """getBH observer format"""
-    magpy.getB(src, [(1,2),(1,2)])
-def bad_observer_input4():
-    """bad observer type"""
-    magpy.getB(src, 123)
-
-
-# test bad level0 inputs
-def bad_level0_field_input_0():
-    """sphere"""
-    mag = np.array([(100,200,300)])
-    dim1 = np.array((2,))
-    obs = np.array([(1,2,3)])
-    magpy.core.magnet_sphere_field(mag, dim1, obs, field='x')
-
-def bad_level0_field_input_1():
-    """cylinder"""
-    mag = np.array([(100,200,300)])
-    dim2 = np.array([(2,2)])
-    obs = np.array([(1,2,3)])
-    magpy.core.magnet_cylinder_field(mag, dim2, obs, field='y')
-
-def bad_level0_field_input_2():
-    """cylinder segment"""
-    mag = np.array([(100,200,300)])
-    dim5 = np.array([(0,1,2,0,360)])
-    obs = np.array([(1,2,3)])
-    magpy.core.magnet_cylinder_segment_field(mag, dim5, obs, field=123)
-
-def bad_level0_field_input_3():
-    """dipole"""
-    mag = np.array([(100,200,300)])
-    obs = np.array([(1,2,3)])
-    magpy.core.dipole_field(mag, obs, field=mag)
-
-def bad_level0_field_input_4():
-    """loop"""
-    cur = np.array((20,))
-    dim1 = np.array((2,))
-    obs = np.array([(1,2,3)])
-    magpy.core.current_loop_field(cur, dim1, obs, field=None)
-
-def bad_level0_field_input_5():
-    """line"""
-    cur = np.array((20,))
-    start = np.array([(1,1,1)])
-    end = np.array([(3,3,3)])
-    obs = np.array([(1,2,3)])
-    magpy.core.current_line_field(cur, start, end, obs, field=(1,2,3))
-
-def bad_level0_field_input_6():
-    """cuboid"""
-    mag = np.array([(100,200,300)])
-    dim3 = np.array([(2,2,2)])
-    obs = np.array([(1,2,3)])
-    magpy.core.magnet_cuboid_field(mag, dim3, obs, field='F')
-
-class TestExceptions(unittest.TestCase):
-    """ test class for exception testing """
-    def test_level0_field_inputs(self):
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_0)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_1)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_2)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_3)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_4)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_5)
-        self.assertRaises(MagpylibBadUserInput, bad_level0_field_input_6)
-
-    def test_init(self):
-        """ missing inputs when calling display and getB"""
-        self.assertRaises(MagpylibMissingInput, init_dim_display1)
-        self.assertRaises(MagpylibMissingInput, init_dim_display2)
-        self.assertRaises(MagpylibMissingInput, init_dim_display3)
-        self.assertRaises(MagpylibMissingInput, init_dim_getBH1)
-        self.assertRaises(MagpylibMissingInput, init_excite_display1)
-        self.assertRaises(MagpylibMissingInput, init_excite_getBH1)
-        self.assertRaises(MagpylibMissingInput, init_excite_getBH2)
-        self.assertRaises(MagpylibMissingInput, init_excite_getBH3)
-        self.assertRaises(MagpylibMissingInput, init_excite_getBH4)
-
-    def test_BaseGeo(self):
-        """ bad BG inputs"""
-        self.assertRaises(MagpylibBadUserInput, badInput_BaseGeo1)
-        self.assertRaises(MagpylibBadInputShape, badInput_BaseGeo2)
-        self.assertRaises(MagpylibBadInputShape, badInput_BaseGeo3)
-        self.assertRaises(MagpylibBadUserInput, badInput_BaseGeo4)
-
-    def test_move(self):
-        """ bad .move inputs"""
-        self.assertRaises(MagpylibBadUserInput, badInput_move1)
-        self.assertRaises(MagpylibBadUserInput, badInput_move2)
-        self.assertRaises(MagpylibBadInputShape, badInput_move3)
-        self.assertRaises(MagpylibBadInputShape, badInput_move4)
-
-    def test_rotate(self):
-        """ bad .rotate inputs"""
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate1)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate2)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate3)
-        self.assertRaises(MagpylibBadInputShape, badInput_rotate5)
-
-    def test_rotate_from_angax(self):
-        """ bad .rotate_from_angax inputs"""
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax1)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax2)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax3)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax4)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax6)
-        self.assertRaises(MagpylibBadInputShape, badInput_rotate_from_angax7)
-        self.assertRaises(MagpylibBadInputShape, badInput_rotate_from_angax8)
-        self.assertRaises(MagpylibBadUserInput, badInput_rotate_from_angax9)
-        self.assertRaises(MagpylibBadInputShape, badInput_rotate_from_angax10)
-
-    def test_magnetization_input(self):
-        """ bad magnetization inputs"""
-        self.assertRaises(MagpylibBadUserInput, badMag_input1)
-        self.assertRaises(MagpylibBadInputShape, badMag_input2)
-        self.assertRaises(MagpylibBadUserInput, badCurrent_input1)
-
-    def test_dim_inputs(self):
-        """ bad dimension inputs"""
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input1)
-        self.assertRaises(MagpylibBadInputShape, bad_dim_input3)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input4)
-        self.assertRaises(MagpylibBadInputShape, bad_dim_input6)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input7)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input9)
-        self.assertRaises(MagpylibBadInputShape, bad_dim_input10)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input11)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input12)
-        self.assertRaises(MagpylibBadUserInput, bad_dim_input13)
-
-    def test_misc_source_inputs(self):
-        """ bad misc source inputs"""
-        self.assertRaises(MagpylibBadUserInput, bad_misc_input1)
-        self.assertRaises(MagpylibBadInputShape, bad_misc_input2)
-        self.assertRaises(MagpylibBadUserInput, bad_misc_input3)
-        self.assertRaises(MagpylibBadInputShape, bad_misc_input4)
-        self.assertRaises(MagpylibBadInputShape, bad_misc_input5)
-        self.assertRaises(MagpylibBadInputShape, bad_misc_input6)
-
-    def test_observer_inputs(self):
-        """ bad observer inputs"""
-        self.assertRaises(MagpylibBadInputShape, bad_observer_input1)
-        self.assertRaises(MagpylibBadInputShape, bad_observer_input2)
-        self.assertRaises(MagpylibBadInputShape, bad_observer_input3)
-        self.assertRaises(MagpylibBadUserInput, bad_observer_input4)
+###########################################################
+###########################################################
+# OBJECT INPUTS
+
+
+def test_input_objects_position_good():
+    """good positions"""
+    goods = [
+        (1,2,3),
+        (0,0,0),
+        ((1,2,3),(2,3,4)),
+        [(2,3,4)],
+        [2,3,4],
+        [[2,3,4],[3,4,5]],
+        [(2,3,4),(3,4,5)],
+        np.array((1,2,3)),
+        np.array(((1,2,3),(2,3,4))),
+        ]
+    for good in goods:
+        sens = magpy.Sensor(position=good)
+        np.testing.assert_allclose(sens.position, np.squeeze(np.array(good)))
+
+
+def test_input_objects_position_bad():
+    """bad positions"""
+    bads = [
+        (1,2),
+        (1,2,3,4),
+        [(1,2,3,4)]*2,
+        (((1,2,3), (1,2,3)), ((1,2,3), (1,2,3))),
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.Sensor, bad)
+
+
+def test_input_objects_pixel_good():
+    """good pixel"""
+    goods = [
+        (1,-2,3),
+        (0,0,0),
+        ((1,2,3),(2,3,4)),
+        (((1,2,3),(2,-3,4)), ((1,2,3),(2,3,4))),
+        [(2,3,4)],
+        [2,3,4],
+        [[-2,3,4],[3,4,5]],
+        [[[2,3,4],[3,4,5]]]*4,
+        [(2,3,4),(3,4,5)],
+        np.array((1,2,-3)),
+        np.array(((1,-2,3),(2,3,4))),
+        ]
+    for good in goods:
+        sens = magpy.Sensor(pixel=good)
+        np.testing.assert_allclose(sens.pixel, np.squeeze(np.array(good)))
+
+
+def test_input_objects_pixel_bad():
+    """bad pixel"""
+    bads = [
+        (1,2),
+        (1,2,3,4),
+        [(1,2,3,4)]*2,
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.Sensor, (0,0,0), bad)
+
+
+def test_input_objects_orientation_good():
+    """good orientations (from rotvec)"""
+    goods = [
+        None,
+        (.1,.2,.3),
+        (0,0,0),
+        [(.1,.2,.3)],
+        [(.1,.2,.3)]*5,
+        ]
+    for good in goods:
+        if good is None:
+            sens = magpy.Sensor(orientation=None)
+            np.testing.assert_allclose(sens.orientation.as_rotvec(), (0,0,0))
+        else:
+            sens = magpy.Sensor(orientation=R.from_rotvec(good))
+            np.testing.assert_allclose(sens.orientation.as_rotvec(), np.squeeze(np.array(good)))
+
+
+def test_input_objects_orientation_bad():
+    """bad orienations"""
+    bads = [
+        (1,2),
+        (1,2,3,4),
+        [(1,2,3,4)]*2,
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.Sensor, (0,0,0), (0,0,0), bad)
+
+
+def test_input_objects_current_good():
+    """good currents"""
+    goods = [
+        None,
+        0,
+        1,
+        1.2,
+        np.array([1,2,3])[1],
+        -1,
+        -1.123,
+        True,
+        ]
+    for good in goods:
+        src = magpy.current.Loop(good)
+        if good is None:
+            assert src.current is None
+        else:
+            np.testing.assert_allclose(src.current, good)
+
+
+def test_input_objects_current_bad():
+    """bad current"""
+    bads = [
+        (1,2),
+        [(1,2,3,4)]*2,
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.current.Loop, bad)
+
+
+def test_input_objects_diameter_good():
+    """good diameter"""
+    goods = [
+        None,
+        0,
+        1,
+        1.2,
+        np.array([1,2,3])[1],
+        True,
+        ]
+    for good in goods:
+        src = magpy.current.Loop(1, good)
+        if good is None:
+            assert src.diameter is None
+        else:
+            np.testing.assert_allclose(src.diameter, good)
+
+
+def test_input_objects_diameter_bad():
+    """bad diameter"""
+    bads = [
+        (1,2),
+        [(1,2,3,4)]*2,
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        -1,
+        -1.123,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.current.Loop, 1, bad)
+
+
+def test_input_objects_vertices_good():
+    """good vertices"""
+    goods = [
+        None,
+        ((0,0,0),(0,0,0)),
+        ((1,2,3),(2,3,4)),
+        [(2,3,4), (-1,-2,-3)]*2,
+        [[2,3,4],[3,4,5]],
+        np.array(((1,2,3),(2,3,4))),
+        ]
+    for good in goods:
+        src = magpy.current.Line(1, good)
+        if good is None:
+            assert src.vertices is None
+        else:
+            np.testing.assert_allclose(src.vertices, good)
+
+
+def test_input_objects_vertices_bad():
+    """bad vertices"""
+    bads = [
+        (1,2),
+        [(1,2,3,4)]*2,
+        [(1,2,3)],
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        0,
+        -1.123,
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.current.Line, 1, bad)
+
+
+def test_input_objects_magnetization_moment_good():
+    """good magnetization and moment"""
+    goods = [
+        None,
+        (1,2,3),
+        (0,0,0),
+        [-1,-2,-3],
+        np.array((1,2,3)),
+        ]
+    for good in goods:
+        src = magpy.magnet.Cuboid(good)
+        src2 = magpy.misc.Dipole(good)
+        if good is None:
+            assert src.magnetization is None
+            assert src2.moment is None
+        else:
+            np.testing.assert_allclose(src.magnetization, good)
+            np.testing.assert_allclose(src2.moment, good)
+
+
+def test_input_objects_magnetization_moment_bad():
+    """bad magnetization and moment"""
+    bads = [
+        (1,2),
+        [1,2,3,4],
+        [(1,2,3)]*2,
+        np.array([(1,2,3)]*2),
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        0,
+        -1.123,
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.magnet.Cuboid, bad)
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.misc.Dipole, bad)
+
+
+def test_input_objects_dimension_cuboid_good():
+    """good cuboid dimension"""
+    goods = [
+        None,
+        (1,2,3),
+        [11,22,33],
+        np.array((1,2,3)),
+        ]
+    for good in goods:
+        src = magpy.magnet.Cuboid((1,1,1), good)
+        if good is None:
+            assert src.dimension is None
+        else:
+            np.testing.assert_allclose(src.dimension, good)
+
+
+def test_input_objects_dimension_cuboid_bad():
+    """bad cuboid dimension"""
+    bads = [
+        [-1,2,3],
+        (0,1,2),
+        (1,2),
+        [1,2,3,4],
+        [(1,2,3)]*2,
+        np.array([(1,2,3)]*2),
+        'x',
+        ['x','y','z'],
+        dict(woot=15),
+        0,
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.magnet.Cuboid, (1,1,1), bad)
+
+
+def test_input_objects_dimension_cylinder_good():
+    """good cylinder dimension"""
+    goods = [
+        None,
+        (1,2),
+        [11,22],
+        np.array((1,2)),
+        ]
+    for good in goods:
+        src = magpy.magnet.Cylinder((1,1,1), good)
+        if good is None:
+            assert src.dimension is None
+        else:
+            np.testing.assert_allclose(src.dimension, good)
+
+
+def test_input_objects_dimension_cylinder_bad():
+    """bad cylinder dimension"""
+    bads = [
+        [-1,2],
+        (0,1),
+        (1,),
+        [1,2,3],
+        [(1,2)]*2,
+        np.array([(2,3)]*2),
+        'x',
+        ['x','y'],
+        dict(woot=15),
+        0,
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.magnet.Cylinder, (1,1,1), bad)
+
+
+def test_input_objects_dimension_cylinderSegment_good():
+    """good cylinder segment dimension"""
+    goods = [
+        None,
+        (0,2,3,0,50),
+        (1,2,3,40,50),
+        [11,22,33,44,360],
+        [11,22,33,-44,55],
+        np.array((1,2,3,4,5)),
+        [11,22,33,-44,-33],
+        (0,2,3,-10,0),
+        ]
+    for good in goods:
+        src = magpy.magnet.CylinderSegment((1,1,1), good)
+        if good is None:
+            assert src.dimension is None
+        else:
+            np.testing.assert_allclose(src.dimension, good)
+
+
+def test_input_objects_dimension_cylinderSegment_bad():
+    """bad cylinder segment dimension"""
+    bads = [
+        (1,2,3,4),
+        (1,2,3,4,5,6),
+        (0,0,3,4,5),
+        (2,1,3,4,5),
+        (-1,2,3,4,5),
+        (1,2,0,4,5),
+        (1,2,-1,4,5),
+        (1,2,3,5,4),
+        [(1,2,3,4,5)]*2,
+        np.array([(1,2,3,4,5)]*2),
+        'x',
+        ['x','y','z',1,2],
+        dict(woot=15),
+        0,
+        True
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.magnet.CylinderSegment, (1,1,1), bad)
+
+
+def test_input_objects_fiedBHlambda_good():
+    """good custom fiedBHlambda"""
+    def f(x):
+        """3 in 3 out"""
+        return x
+    src = magpy.misc.CustomSource(field_B_lambda=f, field_H_lambda=f)
+
+    np.testing.assert_allclose(src.getB((1,2,3)), (1,2,3))
+    np.testing.assert_allclose(src.getH((1,2,3)), (1,2,3))
+
+
+def test_input_objects_fiedBHlambda_bad():
+    """bad custom fiedBlambda"""
+    def f(x):
+        """bad fieldBH lambda"""
+        return 1
+    np.testing.assert_raises(MagpylibBadUserInput, magpy.misc.CustomSource, f)
+    np.testing.assert_raises(MagpylibBadUserInput, magpy.misc.CustomSource, field_H_lambda=f)
+
+
+
+###########################################################
+###########################################################
+# DISPLAY
+
+def test_input_show_zoom_bad():
+    """bad show zoom inputs"""
+    x = magpy.Sensor()
+    bads = [
+        (1,2,3),
+        -1,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.show, x, zoom=bad)
+
+
+def test_input_show_animation_bad():
+    """bad show animation inputs"""
+    x = magpy.Sensor()
+    bads = [
+        (1,2,3),
+        -1,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.show, x, animation=bad)
+
+
+def test_input_show_backend_bad():
+    """bad show backend inputs"""
+    x = magpy.Sensor()
+    bads = [
+        (1,2,3),
+        -1,
+        'x',
+        True,
+        ]
+    for bad in bads:
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.show, x, backend=bad)
+
+
+
+def test_input_show_missing_parameters1():
+    """missing inputs"""
+    s = magpy.magnet.Cuboid()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Cylinder()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.CylinderSegment()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Sphere()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Loop()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Line()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.misc.Dipole()
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+
+
+def test_input_show_missing_parameters2():
+    """missing inputs"""
+    s = magpy.magnet.Cuboid(dimension=(1,2,3))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Cylinder(dimension=(1,2))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.CylinderSegment(dimension=(1,2,3,4,5))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Sphere(diameter=1)
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Loop(diameter=1)
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Line(vertices=[(1,2,3)]*2)
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+
+
+def test_input_show_missing_parameters3():
+    """missing inputs"""
+    s = magpy.magnet.Cuboid(magnetization=(1,2,3))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Cylinder(magnetization=(1,2,3))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.CylinderSegment(magnetization=(1,2,3))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.magnet.Sphere(magnetization=(1,2,3))
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Loop(current=1)
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+    s = magpy.current.Line(current=1)
+    np.testing.assert_raises(MagpylibMissingInput, magpy.show, s)
+
+###########################################################
+###########################################################
+# MOVE ROTATE
+
+def test_input_move_start_good():
+    """good start inputs"""
+    goods = [
+        'auto',
+        0,
+        1,
+        15,
+        -2,
+        -250,
+        np.array((1,2,3))[0],
+        ]
+    for good in goods:
+        x = magpy.Sensor(position=[(0,0,i) for i in range(10)])
+        x.move((1,0,0), start=good)
+        assert isinstance(x.position, np.ndarray)
+
+
+def test_input_move_start_bad():
+    """bad start inputs"""
+    bads = [
+        1.1,
+        1.,
+        'x',
+        None,
+        [11],
+        (1,),
+        np.array([(1,2,3,4,5)]*2),
+        dict(woot=15),
+        ]
+    for bad in bads:
+        x = magpy.Sensor(position=[(0,0,i) for i in range(10)])
+        np.testing.assert_raises(MagpylibBadUserInput, x.move, (1,1,1), start=bad)
+
+
+def test_input_rotate_degrees_good():
+    """good degrees inputs"""
+    goods = [
+        True,
+        False,
+        ]
+    for good in goods:
+        x = magpy.Sensor()
+        x.rotate_from_angax(10, 'z', degrees=good)
+        assert isinstance(x.position, np.ndarray)
+
+
+def test_input_rotate_degrees_bad():
+    """bad degrees inputs"""
+    bads = [
+        1,
+        0,
+        1.1,
+        1.,
+        'x',
+        None,
+        [True],
+        (1,),
+        np.array([(1,2,3,4,5)]*2),
+        dict(woot=15),
+        ]
+    for bad in bads:
+        x = magpy.Sensor()
+        np.testing.assert_raises(MagpylibBadUserInput, x.rotate_from_angax, 10, 'z', degrees=bad)
+
+
+def test_input_rotate_axis_good():
+    """good rotate axis inputs"""
+    goods = [
+        (1,2,3),
+        (0,0,1),
+        [0,0,1],
+        np.array([0,0,1]),
+        'x',
+        'y',
+        'z',
+        ]
+    for good in goods:
+        x = magpy.Sensor()
+        x.rotate_from_angax(10, good)
+        assert isinstance(x.position, np.ndarray)
+
+
+def test_input_rotate_axis_bad():
+    """bad rotate axis inputs"""
+    bads = [
+        (0,0,0),
+        (1,2),
+        (1,2,3,4),
+        1.1,
+        1,
+        'xx',
+        None,
+        True,
+        np.array([(1,2,3,4,5)]*2),
+        dict(woot=15),
+        ]
+    for bad in bads:
+        x = magpy.Sensor()
+        np.testing.assert_raises(MagpylibBadUserInput, x.rotate_from_angax, 10, bad)
+
+
+###########################################################
+###########################################################
+# GET BH
+
+
+def test_input_getBH_field_good():
+    """good getBH field inputs"""
+    goods = [
+        'B',
+        'H',
+        ]
+    for good in goods:
+        moms = np.array([[1,2,3]])
+        obs = np.array([[1,2,3]])
+        B = magpy.core.dipole_field(moms, obs, field=good)
+        assert isinstance(B, np.ndarray)
+
+
+def test_input_getBH_field_bad():
+    """bad getBH field inputs"""
+    bads = [
+        1,
+        0,
+        1.1,
+        1.,
+        'x',
+        None,
+        [True],
+        (1,),
+        np.array([(1,2,3,4,5)]*2),
+        dict(woot=15),
+        ]
+    for bad in bads:
+        moms = np.array([[1,2,3]])
+        obs = np.array([[1,2,3]])
+        np.testing.assert_raises(MagpylibBadUserInput, magpy.core.dipole_field, moms, obs, field=bad)
diff --git a/tests/test_misc.py b/tests/test_misc.py
new file mode 100644
index 000000000..17e268a03
--- /dev/null
+++ b/tests/test_misc.py
@@ -0,0 +1,12 @@
+import magpylib as magpy
+
+def test_bare_init():
+    """test if magpylib object can be initilized without attributes"""
+    magpy.current.Loop()
+    magpy.current.Line()
+    magpy.magnet.Cuboid()
+    magpy.magnet.Cylinder()
+    magpy.magnet.CylinderSegment()
+    magpy.magnet.Sphere()
+    magpy.misc.Dipole()
+    magpy.misc.CustomSource()
diff --git a/tests/test_obj_BaseGeo.py b/tests/test_obj_BaseGeo.py
index 1b33c1fc8..701e2b90f 100644
--- a/tests/test_obj_BaseGeo.py
+++ b/tests/test_obj_BaseGeo.py
@@ -299,7 +299,7 @@ def test_scipy_from_methods():
         cube0.orientation.as_rotvec(), from_angax.orientation.as_rotvec()
     ), "from_rotvec failed on orientation"
 
-    from_euler = cube().rotate_from_euler("z", angs_deg, anchor=anchor, degrees=True)
+    from_euler = cube().rotate_from_euler(angs_deg, "z", anchor=anchor, degrees=True)
     assert np.allclose(
         cube0.position, from_euler.position
     ), "from_euler failed on position"
diff --git a/tests/test_obj_Collection.py b/tests/test_obj_Collection.py
index 4cadc23ff..8c2935774 100644
--- a/tests/test_obj_Collection.py
+++ b/tests/test_obj_Collection.py
@@ -307,3 +307,17 @@ def test_set_children_styles():
     ), """failed updating styles to children"""
     with pytest.raises(ValueError):
         col.set_children_styles(bad_input="somevalue")
+
+
+def test_reprs():
+    """test repr strings"""
+    s1 = magpy.magnet.Sphere((1,2,3), 5)
+    x1 = magpy.Sensor()
+    c = magpy.Collection()
+    assert repr(c)[:10]=='Collection'
+    c = magpy.Collection(s1)
+    assert repr(c)[:10]=='SourceColl'
+    c = magpy.Collection(x1)
+    assert repr(c)[:10]=='SensorColl'
+    c = magpy.Collection(s1,x1)
+    assert repr(c)[:10]=='MixedColle'
diff --git a/tests/test_obj_Collection_v4motion.py b/tests/test_obj_Collection_v4motion.py
index 78de712b5..a670b3e16 100644
--- a/tests/test_obj_Collection_v4motion.py
+++ b/tests/test_obj_Collection_v4motion.py
@@ -303,7 +303,7 @@ def test_compound_motion_01():
     validate_pos_orient(s1, [(1, 1, 2), (1, 1, 3)], [(0, 0, 0)] * 2)
     validate_pos_orient(s2, [(-1, -1, 0), (-1, -1, 1)], [(0, 0, 0)] * 2)
     validate_pos_orient(col, [(0, 0, 1), (0, 0, 2)], [(0, 0, 0)] * 2)
-    col.rotate_from_rotvec((0, 0, np.pi / 2), anchor=0)
+    col.rotate_from_rotvec((0, 0, np.pi / 2), anchor=0, degrees=False)
     validate_pos_orient(s1, [(-1, 1, 2), (-1, 1, 3)], [(0, 0, np.pi / 2)] * 2)
     validate_pos_orient(s2, [(1, -1, 0), (1, -1, 1)], [(0, 0, np.pi / 2)] * 2)
     validate_pos_orient(col, [(0, 0, 1), (0, 0, 2)], [(0, 0, np.pi / 2)] * 2)
@@ -314,7 +314,7 @@ def test_compound_motion_02():
     s1 = magpy.magnet.Cuboid((1, 0, 0), (1, 1, 1), (1, 0, 1))
     s2 = magpy.magnet.Cuboid((1, 0, 0), (1, 1, 1), (-1, 0, -1))
     col = magpy.Collection(s1, s2, position=(3, 0, 3))
-    col.rotate_from_rotvec((0, 0, np.pi / 2), anchor=[(1, 0, 0), (2, 0, 0)])
+    col.rotate_from_rotvec((0, 0, np.pi / 2), anchor=[(1, 0, 0), (2, 0, 0)], degrees=False)
     validate_pos_orient(
         s1,
         [(1, 0, 1), (1, 0, 1), (2, -1, 1)],
@@ -346,6 +346,7 @@ def test_compound_motion_03():
         [(0, 0, np.pi / 2), (0, 0, 3 * np.pi / 2)],
         anchor=[(1, 0, 0), (2, 0, 0)],
         start=0,
+        degrees=False,
     )
     validate_pos_orient(
         s1, [(1, 2, 0), (2, 1, 0)], [(0, 0, np.pi / 2), (0, 0, -np.pi / 2)]