diff --git a/navipy/sensors/blendtraj.py b/navipy/sensors/blendtraj.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8cb6c9c67fdb8afb35c2a0edd93352422d2bc29
--- /dev/null
+++ b/navipy/sensors/blendtraj.py
@@ -0,0 +1,100 @@
+"""
+"""
+import os
+import sys
+import argparse
+import inspect
+import pandas as pd
+
+
+def parser_blendtraj():
+    """ 
+    Parse argument for function blendtraj
+    """
+    # Create command line options
+    parser = argparse.ArgumentParser()
+    arghelp = 'Path to the environment (.blend) in which your agent lives'
+    defaultworld = pkg_resources.resource_filename(
+        'navipy', 'resources/twocylinders_world.blend')
+    parser.add_argument('--blender-world',
+                        type=str,
+                        default=defaultworld,
+                        help=arghelp)
+    arghelp = 'Command to run blender\n'
+    arghelp += 'If not provided, the script will try to find the command'
+    arghelp += " by using: shutil.which('blender')"
+    parser.add_argument('--blender-command',
+                        type=str,
+                        default=None,
+                        help=arghelp)
+    arghelp = 'To display some stuff \n'
+    arghelp += ' * -v print command \n'
+    arghelp += ' * -vv print also script'
+    parser.add_argument('-v', '--verbose',
+                        action='count',
+                        default=0,
+                        help=arghelp)
+    defaultfile = pkg_resources.resource_filename(
+        'navipy', 'resources/configs/BlenderRender.yaml')
+    arghelp = 'BlenderRender configuration files \n'
+    arghelp += 'by default: {}'.format(defaultfile)
+    parser.add_argument('--config',
+                        type=str,
+                        default=defaultfile,
+                        help=arghelp)
+    arghelp = 'Trajectory of the agent\n'
+    arghelp += 'pandas dataframe saved in hdf'
+    parser.add_argument('--trajectory',
+                        type=str,
+                        default=defaultfile,
+                        help=arghelp)
+
+
+
+def render_trajectory(traj_filename, output_path, imformat='.jpg'):
+    # Get extension to check that file is hdf
+    _, ext = os.path.splitext(traj_filename)
+    if ext in ['h5', 'hdf']:
+        trajdf = pd.read_hdf(traj_filename)
+    else:
+        raise IOError(
+            'File {} can not be read as a trajectory'.format(traj_filename))
+    
+
+def main():
+    """
+    Render a trajectory in a blender environment
+    """
+    # encoding for temporary file
+    encoding = 'utf-8'
+
+    # Fetch arguments
+    args = parser_blendtraj().parse_args()
+
+    # Create tempfile with testing code and then call blendnavipy
+    header = '# Generated by {}\n'.format(sys.argv[0])
+    with tempfile.NamedTemporaryFile() as tfile:
+        # Start of file
+        tfile.write(header.encode(encoding))
+        tfile.write('import unittest \n'.encode(encoding))
+        for line in inspect.getsourcelines(run)[0]:
+            tfile.write(line.encode(encoding))
+        tfile.write('\n\n'.encode(encoding))
+        tfile.write('try:\n'.encode(encoding))
+        tfile.write('     run("{}")\n'.format(args.start_dir).encode(encoding))
+        tfile.write('     sys.exit(0)\n'.encode(encoding))
+        tfile.write('except Exception:\n'.encode(encoding))
+        tfile.write('     sys.exit(1)\n'.encode(encoding))
+        tfile.seek(0)
+
+        command = 'blendnavipy --blender-world {} --python-script {}'
+        command = command.format(args.blender_world, tfile.name)
+        if args.blender_command is not None:
+            command += ' --blender-command {}'.format(args.blender_command)
+        for _ in range(args.verbose):
+            command += ' -v'
+        os.system(command)
+    
+if __name__ == "__main__":
+    # execute only if run as a script
+    main()
diff --git a/navipy/sensors/renderer.py b/navipy/sensors/renderer.py
index c480dc159554ba15804974a33b8d1f254663fe55..36f9ab4627815a608bccccba0ce53bf3997d6e83 100644
--- a/navipy/sensors/renderer.py
+++ b/navipy/sensors/renderer.py
@@ -16,9 +16,97 @@ import yaml  # Used to load config files
 import pkg_resources
 from navipy.maths.homogeneous_transformations import compose_matrix
 import navipy.maths.constants as constants
+from navipy.tools.trajectory import Trajectory
 
 
-class BlenderRender():
+class AbstractRender():
+    """
+    List method to render on a grid or along a trajectory
+    """
+
+    def __init__(self):
+        pass
+
+    def render_trajectory(trajectory, outputfile, imformat='.jpg'):
+        pass
+
+    def render_ongrid(x, y, z, alpha_0=[0], alpha_1=[0], alpha_2=[0],
+                      q_0=None, q_1=None, q_2=None, q_3=None,
+                      rotconv='rzyx'):
+        if rotconv == 'quaternion':
+            if (q_0 is None) or \
+               (q_1 is None) or \
+               (q_2 is None) or \
+               (q_3 is None):
+                msg = 'With rotconv {}, q_0,q_1,q_2,q_3 can not be None'
+                msg = msg.format(rotconv)
+                raise ValueError(msg)
+        if not (isinstance(x, np.ndarray) or isinstance(x, list)):
+            raise TypeError('x must be list or np.array')
+        if not (isinstance(y, np.ndarray) or isinstance(y, list)):
+            raise TypeError('y must be list or np.array')
+        if not (isinstance(z, np.ndarray) or isinstance(z, list)):
+            raise TypeError('z must be list or np.array')
+
+        if rotconv == 'quaternion':
+            if not (isinstance(q_0, np.ndarray) or
+                    isinstance(q_0, list)):
+                raise TypeError('q_0 must be list or np.array')
+            if not (isinstance(q_1, np.ndarray) or
+                    isinstance(q_1, list)):
+                raise TypeError('q_1 must be list or np.array')
+            if not (isinstance(q_2, np.ndarray) or
+                    isinstance(q_2, list)):
+                raise TypeError('q_3 must be list or np.array')
+            if not (isinstance(q_3, np.ndarray) or
+                    isinstance(q_3, list)):
+                raise TypeError('q_3 must be list or np.array')
+            [mx, my, mz, mq0, mq1, mq2, mq3] = np.meshgrid(x,
+                                                           y,
+                                                           z,
+                                                           q_0,
+                                                           q_1,
+                                                           q_2,
+                                                           q_3)
+
+            mx = mx.flatten()
+            grid_point = Trajectory(rotconv, indeces=range(0, mx.shape[0]))
+            grid_point.x = mx
+            grid_point.y = my.flatten()
+            grid_point.z = mz.flatten()
+            grid_point.q0 = mq0.flatten()
+            grid_point.q1 = mq1.flatten()
+            grid_point.q2 = mq2.flatten()
+            grid_point.q3 = mq3.flatten()
+
+        else:
+            if not (isinstance(alpha_0, np.ndarray) or
+                    isinstance(alpha_0, list)):
+                raise TypeError('alpha_0 must be list or np.array')
+            if not (isinstance(alpha_1, np.ndarray) or
+                    isinstance(alpha_1, list)):
+                raise TypeError('alpha_1 must be list or np.array')
+            if not (isinstance(alpha_2, np.ndarray) or
+                    isinstance(alpha_2, list)):
+                raise TypeError('alpha_2 must be list or np.array')
+            [mx, my, mz, ma0, ma1, ma2] = np.meshgrid(x,
+                                                      y,
+                                                      z,
+                                                      alpha_0,
+                                                      alpha_1,
+                                                      alpha_2)
+
+            mx = mx.flatten()
+            grid_point = Trajectory(rotconv, indeces=range(0, mx.shape[0]))
+            grid_point.x = mx
+            grid_point.y = my.flatten()
+            grid_point.z = mz.flatten()
+            grid_point.alpha_0 = ma0.flatten()
+            grid_point.alpha_1 = ma1.flatten()
+            grid_point.alpha_2 = ma2.flatten()
+
+
+class BlenderRender(AbstractRender):
     """
     BlenderRender is a small class binding python with blender.
     With BlenderRender one can move the bee to a position, and render what
@@ -142,7 +230,7 @@ class BlenderRender():
             self.camera_gaussian_width = blendconfig['gaussian_width']
         else:
             raise KeyError(
-                    'Yaml config file should contain gaussian_width')
+                'Yaml config file should contain gaussian_width')
         # Load cycle for rendering
         if 'samples' in blendconfig.keys():
             self.camera_samples = blendconfig['samples']
diff --git a/navipy/tools/__init__.py b/navipy/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/navipy/tools/trajectory.py b/navipy/tools/trajectory.py
new file mode 100644
index 0000000000000000000000000000000000000000..05f383ab09d687860ce11a4f479de9380a8c756a
--- /dev/null
+++ b/navipy/tools/trajectory.py
@@ -0,0 +1,141 @@
+"""
+  Trajectory in navipy
+"""
+import pandas as pd
+import navipy.maths.constants as mconst
+
+
+class Trajectory(pd.DataFrame):
+    def __init__(self, rotconv, indeces):
+        if rotconv == 'quaternion':
+            index = pd.MultiIndex.from_tuples(
+                [('location', 'x'), ('location', 'y'),
+                 ('location', 'z'), (rotconv, 'q_0'),
+                    (rotconv, 'q_1'), (rotconv, 'q_2'),
+                 (rotconv, 'q_3')])
+        elif rotconv in mconst._AXES2TUPLE.keys():
+            index = pd.MultiIndex.from_tuples(
+                [('location', 'x'), ('location', 'y'),
+                 ('location', 'z'), (rotconv, 'alpha_0'),
+                    (rotconv, 'alpha_1'), (rotconv, 'alpha_2')])
+        else:
+            msg = 'convention for rotation {} is not suppored\n'
+            msg += msg.format(rotconv)
+            msg += 'the following convention are supported\n:'
+            for rconv in mconst._AXES2TUPLE.keys():
+                msg += '{}\n'.format(rconv)
+            msg += 'quaternion\n'
+            raise KeyError(msg)
+        super().__init__(index=indeces, columns=index)
+        self.__rotconv = rotconv
+
+    @property
+    def x(self):
+        return self.loc[:, ('location', 'x')]
+
+    @x.setter
+    def x(self, x):
+        self.loc[:, ('location', 'x')] = x
+
+    @property
+    def y(self):
+        return self.loc[:, ('location', 'y')]
+
+    @y.setter
+    def y(self, y):
+        self.loc[:, ('location', 'y')] = y
+
+    @property
+    def z(self):
+        return self.loc[:, ('location', 'z')]
+
+    @z.setter
+    def z(self, z):
+        self.loc[:, ('location', 'z')] = z
+
+    def __get_alpha_i(self, alphai):
+        if self.__rotconf != 'quaternion':
+            return self.loc[:, (self.__rotconf, 'alpha_{}'.format(alphai))]
+        else:
+            msg = 'alpha_{0:} does not exist for quaternion (try q_{0:})'
+            raise ValueError(msg.format(alphai))
+
+    def __set_alpha_i(self, alphai, val):
+        if self.__rotconf != 'quaternion':
+            self.loc[:, (self.__rotconf, 'alpha_{}'.format(alphai))] = val
+        else:
+            msg = 'alpha_{0:} does not exist for quaternion (try q_{0:})'
+            raise ValueError(msg.format(alphai))
+
+    @property
+    def alpha_0(self):
+        self.__get_alpha_i(0)
+
+    @alpha_0.setter
+    def alpha_0(self, alpha_0):
+        self.__set_alpha_i(0, alpha_0)
+
+    @property
+    def alpha_1(self):
+        self.__get_alpha_i(1)
+
+    @alpha_1.setter
+    def alpha_1(self, alpha_1):
+        self.__set_alpha_i(1, alpha_1)
+
+    @property
+    def alpha_2(self):
+        self.__get_alpha_i(2)
+
+    @alpha_2.setter
+    def alpha_2(self, alpha_2):
+        self.__set_alpha_i(2, alpha_2)
+
+    def __get_q_i(self, qi):
+        if self.__rotconf == 'quaternion':
+            return self.loc[:, (self.__rotconf, 'q_{}'.format(qi))]
+        else:
+            msg = 'q_{0:} does not exist for none quaternion (try alpha_{0:})'
+            raise ValueError(msg.format(qi))
+
+    def __set_q_i(self, qi, val):
+        if self.__rotconf != 'quaternion':
+            self.loc[:, (self.__rotconf, 'q_{}'.format(qi))] = val
+        else:
+            msg = 'q_{0:} does not exist for none quaternion (try alpha_{0:})'
+            raise ValueError(msg.format(qi))
+
+    @property
+    def q_0(self):
+        self.__get_q_i(0)
+
+    @q_0.setter
+    def q_0(self, q_0):
+        self.__set_q_i(0, q_0)
+
+    @property
+    def q_1(self):
+        self.__get_q_i(1)
+
+    @q_1.setter
+    def q_1(self, q_1):
+        self.__set_q_i(1, q_1)
+
+    @property
+    def q_2(self):
+        self.__get_q_i(2)
+
+    @q_2.setter
+    def q_2(self, q_2):
+        self.__set_q_i(2, q_2)
+
+    @property
+    def q_3(self):
+        self.__get_q_i(3)
+
+    @q_3.setter
+    def q_3(self, q_3):
+        self.__set_q_i(3, q_3)
+
+    def lollipops(self):
+        raise NameError('Not implemented')