diff --git a/cxxfem/src/femPost.cpp b/cxxfem/src/femPost.cpp
index 1c4119ef30d1fed3832df50c139a937a69ba2940..be8d5e18d5c4a14200dc000fb50baf83a72e9dbd 100644
--- a/cxxfem/src/femPost.cpp
+++ b/cxxfem/src/femPost.cpp
@@ -24,11 +24,45 @@ Post::~Post()
         std::cout << "~Post()\n";
 }
 
-/// rebuild solution vectors for gmsh and add views
+/// rebuild solution vectors for gmsh and add views:
+///
+///  X
+///  Y
+///  Z
+///  displacement_vector
+///  force_vector
+///  stress_tensor
+///      stress_xx
+///      stress_yy
+///      stress_zz
+///      stress_xy
+///      stress_xz
+///      stress_yz
+///  strain_tensor
+///      strain_xx
+///      strain_yy
+///      strain_zz
+///      strain_xy
+///      strain_xz
+///      strain_yz
+///  smooth_stress_tensor
+///      smooth_stress_xx
+///      smooth_stress_yy
+///      smooth_stress_zz
+///      smooth_stress_xy
+///      smooth_stress_xz
+///      smooth_stress_yz
+///  smooth_strain_tensor
+///      smooth_strain_xx
+///      smooth_strain_yy
+///      smooth_strain_zz
+///      smooth_strain_xy
+///      smooth_strain_xz
+///      smooth_strain_yz
 
 void Post::build_views()
 {
-    std::cout << "creating views...\n";
+    std::cout << "creating views..." << std::endl;
     // nodal dofs
     add_dofs_view(solver.pbl, solver.dofs, solver.X, solver.dofPart, solver.dofIdx);
 
@@ -330,6 +364,19 @@ Post::probe(std::string const &name, std::string const &grpname)
     // get physical group from grpname
     Group *grp = solver.pbl.groups_by_name.at(grpname); // TODO: better handling of errors
 
+    std::vector<std::size_t> nodeTags;
+    std::vector<double> coord;
+    gmsh::model::mesh::getNodesForPhysicalGroup(grp->dim, grp->tag, nodeTags, coord);
+    // j'ai essayé de boucler sur les entités du groupe et faire un getNode de l'entité 
+    // mais ça ne me retourne rien (?)
+    // std::cout << "nodeTags.size()=" << nodeTags.size() << '\n';
+
+    // retourne les coordonnées des noeuds du groupe si demandé
+    if(name=="coords")
+    {   
+        return coord;
+    }
+
     // get the view
     auto it = views.find(name);
     if (it == views.end())
@@ -339,14 +386,6 @@ Post::probe(std::string const &name, std::string const &grpname)
     // probe values
     std::vector<double> all_values;
 
-    std::vector<std::size_t> nodeTags;
-    std::vector<double> coord;
-    gmsh::model::mesh::getNodesForPhysicalGroup(grp->dim, grp->tag, nodeTags, coord);
-    // j'ai essayé de boucler sur les entités du groupe et faire un getNode de l'entité 
-    // mais ça ne me retourne rien (?)
-
-    // std::cout << "nodeTags.size()=" << nodeTags.size() << '\n';
-
     for (size_t i = 0; i < nodeTags.size(); ++i)
     {
         std::vector<double> values;
@@ -365,6 +404,21 @@ Post::probe(std::string const &name, std::string const &grpname)
         // std::cout << "\tgot from gmsh: " << values << '\n';
         // std::cout << "\tdistance = " << distance << std::endl;
 
+        if(values.size() == 9) // tensor -> compute j2
+        {
+            // xx, xy, xz, yx, yy, yz, zx, zy, zz
+            //  0,  1,  2,  3,  4,  5,  6,  7,  8
+            double xx = values[0];
+            double yy = values[4];
+            double zz = values[8];
+            double xy = values[1];
+            double zx = values[6];
+            double yz = values[5];
+            double j2 = sqrt( ((xx-yy)*(xx-yy) + (yy-zz)*(yy-zz) + (zz-xx)*(zz-xx) )/2 + 3*(xy*xy+yz*yz+zx*zx) );
+            values.clear();
+            values.push_back(j2);
+        }
+
         all_values.insert(all_values.end(), values.begin(), values.end());
     }
 
diff --git a/fossils-win.spec b/fossils-win.spec
index 3862edbbb060c5584c048ce5339e8d21fc1b6303..4884ff97b07782ff844f7d467b5df2549bbae11b 100644
--- a/fossils-win.spec
+++ b/fossils-win.spec
@@ -2,7 +2,7 @@
 
 # BUILD DIST:
 #   load environment
-#   pyinstaller --noconfirm fossils.spec
+#   pyinstaller --noconfirm fossils-win.spec
 
 block_cipher = None
 
@@ -63,13 +63,29 @@ exe = EXE(
     entitlements_file=None,
     icon='gui/icons/fossils.ico',
 )
-coll = COLLECT(
-    exe,
-    a.binaries,
-    a.zipfiles,
-    a.datas,
-    strip=False,
-    upx=False, # works well but slower runtime
-    upx_exclude=[],
-    name='fossils',
-)
+
+# when using VBox + mesa drivers installed on c:\Python310\python.exe, 
+# mesa dlls are copied into the installer which makes
+# gmsh display crash at runtime.
+# => we manually remove the libraries related to openGL (they are not present when built outside a vbox)
+# see https://stackoverflow.com/questions/58097858/how-to-exclude-opengl32sw-dll-from-pyqt5-library-when-using-pyinstaller
+to_keep = []
+to_exclude = {'opengl32.dll', 'libglapi.dll', 'libgallium_wgl.dll'}
+for (dest, source, kind) in a.binaries:
+    if os.path.split(dest)[1].lower() in to_exclude:
+        print(f'removing {source}')
+        continue
+    to_keep.append((dest, source, kind))
+a.binaries = to_keep
+
+if 1:
+    coll = COLLECT(
+        exe,
+        a.binaries,
+        a.zipfiles,
+        a.datas,
+        strip=False,
+        upx=False, # works well but slower runtime
+        upx_exclude=[],
+        name='fossils',
+    )
diff --git a/fossils.iss b/fossils.iss
index 265ca0db683c1371d9dfceba3b4fb43725c2b4ab..d2ed60614b990a1f8348ed3c88c992b8356805da 100644
--- a/fossils.iss
+++ b/fossils.iss
@@ -2,7 +2,7 @@
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
 
 #define MyAppName "Fossils"
-#define MyAppVersion "1.3"
+#define MyAppVersion "1.4"
 #define MyAppPublisher "University of Liege"
 #define MyAppURL "https://gitlab.uliege.be/rboman/fossils"
 #define MyAppExeName "fossils.exe"
diff --git a/models/Clidastes/Clidastes_propython_50k.py b/models/Clidastes/Clidastes_propython_50k.py
index bfe6ccb0679d1d7b1b502bc35dfd68469a236a83..15b001ff350644da4c176beac54f3f2f362d2c64 100644
--- a/models/Clidastes/Clidastes_propython_50k.py
+++ b/models/Clidastes/Clidastes_propython_50k.py
@@ -7,7 +7,7 @@ def parms(d={}):
     p = {}
     import os
     path = os.path.join(os.path.dirname(__file__),'50k')
-    p['bone'] = f'{path}\Clidastes_50k.ply'
+    p['bone'] = f'{path}/Clidastes_50k.ply'
     p['fixations'] = [
         {
             'name': 'contact_pts',
diff --git a/models/bonemodel.py b/models/bonemodel.py
index a973806c9ad25e7474eb5602b7af8e5a7c314394..6193e0bfdd2e1c53e3fafbff30f5b8817cbc6174 100644
--- a/models/bonemodel.py
+++ b/models/bonemodel.py
@@ -318,6 +318,8 @@ def getMetafor(p={}):
                 valuesmanager.add(idx, IFNodalValueExtractor(target, field), fname)
             else:
                 raise Exception(f'Unknown variable: {var}')
+            # if the target is a single node, display the extractor [TSC-EXT]
+            # otherwise, the user should read the ascii file in the workspace.
             if target.getNumberOfMeshPoints() == 1:
                 metafor.getTestSuiteChecker().checkExtractor(idx)
             idx+=1
diff --git a/models/bonemodel2.py b/models/bonemodel2.py
index 417ae5240d77eb4446919856e178f52823e7cc08..25ade80a5748e54b97da8a4ac9757c166d4da3b8 100644
--- a/models/bonemodel2.py
+++ b/models/bonemodel2.py
@@ -64,6 +64,7 @@ def parms(d={}):
     ]
 
     p['loads'] = [] # additional loads
+    p['extractors'] = [] # additional extractors
 
     # material properties
     p['Young'] = 17000.      # [MPa] elastic modulus - bone: 17-20 GPa
@@ -135,6 +136,10 @@ def solve(p={}):
     for fix in p['fixations']:
         create_group(fix['nodes'], nods_no, nods_pos, groups, fix['name'])
 
+    # create groups of additional extractors
+    for extr in p['extractors']:
+        create_group(extr['nodes'], nods_no, nods_pos, groups, extr['name'])
+
     # --------------------------------------------------------------------------
     # print('== ADDING MEDIUM...')
     print('')
@@ -189,6 +194,7 @@ def solve(p={}):
 
     print('extracting results...')
     print(f'\tinternal energy = {solver.int_energy:.3f} N.mm')
+    # fixations (reaction forces)
     for fix in p['fixations']:
         grpname = fix['name']
         v = np.array(post.probe('force_vector', grpname))
@@ -198,6 +204,7 @@ def solve(p={}):
         print(f'\t{grpname}_Fy = {v[1]:.3f} N')
         print(f'\t{grpname}_Fz = {v[2]:.3f} N')
 
+    # muscle groups
     for name, group in mgroups.items():
         v=np.array( [0.0, 0.0, 0.0] )
         for tag in group.ntags:
@@ -206,6 +213,65 @@ def solve(p={}):
         print(f'\t{name}_Fy = {v[1]:.3f} N')
         print(f'\t{name}_Fz = {v[2]:.3f} N')
 
+    # additional extractors
+
+    var2field = { 
+        'dx': ('X', 'mm'), 
+        'dy': ('Y', 'mm'), 
+        'dz': ('Z', 'mm'),  
+        'sigxx': ('smooth_stress_xx', 'MPa'),  
+        'sigyy': ('smooth_stress_yy', 'MPa'), 
+        'sigzz': ('smooth_stress_zz', 'MPa'), 
+        'sigxy': ('smooth_stress_xy', 'MPa'), 
+        'sigxz': ('smooth_stress_xz', 'MPa'), 
+        'sigyz': ('smooth_stress_yz', 'MPa'), 
+        'epsxx': ('smooth_strain_xx', ''), 
+        'epsyy': ('smooth_strain_yy', ''), 
+        'epszz': ('smooth_strain_zz', ''), 
+        'epsxy': ('smooth_strain_xy', ''), 
+        'epsxz': ('smooth_strain_xz', ''), 
+        'epsyz': ('smooth_strain_yz', ''),
+        'sigvm': ('smooth_stress_tensor', 'MPa')  # post.probe computes sigvm                  
+    }
+
+    for extr in p['extractors']:
+        # if only one variable is given, convert it to a list
+        if isinstance(extr['variables'], str):
+            extr['variables'] = [extr['variables']]
+
+        for var in extr['variables']:
+            fname = f'{extr["name"]}_{var}.tsv'
+            grpname = extr['name']
+            v = []
+            units = ""
+            if var.lower() in var2field:
+                # extract values from gmsh views
+                field = var2field[var.lower()][0]
+                units = var2field[var.lower()][1]
+                v = np.array(post.probe(field, grpname))              
+            elif var.lower() in ['x0', 'y0', 'z0']:
+                # extract coordinates of the nodes
+                units = 'mm'
+                v = np.array(post.probe('coords', grpname))
+                v = np.reshape(v, (v.size//3, 3))
+                v = v[:, ['x0', 'y0', 'z0'].index(var.lower())] 
+            elif var.lower() in ['fx', 'fy', 'fz']: 
+                # extract forces
+                units = 'N'
+                v = np.array(post.probe('force_vector', grpname))
+                v = np.reshape(v, (v.size//3, 3))
+                v = v[:, ['fx', 'fy', 'fz'].index(var.lower())]
+            else:
+                print(f'unknown variable: "{var}"')
+
+            if len(v) > 0:
+                np.savetxt(fname, v, delimiter='\t', header=var.lower())
+                if len(v) == 1:
+                    print(f'\t{grpname}_{var} = {v[0]:.3f} {units}')
+                else:
+                    print(f'\t{grpname}_{var} = ({len(v)} values in "{fname}")')                
+
+
     end_time = time.perf_counter()
     elapsed_time = end_time - start_time
     print(f'Total Execution time: {elapsed_time:.1f}s')
diff --git a/models/dolicorhynchops/dolicorhynchops_10k.py b/models/dolicorhynchops/dolicorhynchops_10k.py
index 99e195cd8468bc6c925c8192e455e8ed43f569d5..4ef2f960f0853d48d63c62e0f2fd6f95def3fbbd 100644
--- a/models/dolicorhynchops/dolicorhynchops_10k.py
+++ b/models/dolicorhynchops/dolicorhynchops_10k.py
@@ -42,6 +42,23 @@ def parms(d={}):
             'direction': ['x', 'z']
         }
     ]
+    p['extractors'] = [
+        {
+            'name': 'tooth1',
+            'nodes': [-10.20362, -17.46838, -229.9061],
+            'variables': [ 'sigvm' ]
+        },
+        {
+            'name': 'tooth2',
+            'nodes': [-11.92466, 26.3042, -229.5354],
+            'variables': [ 'sigxx', 'sigyy', 'sigzz', 'sigvm' ]
+        },
+        {
+            'name': 'Lmuscle',
+            'nodes': f'{path}/Lmuscle.stl',
+            'variables': [ 'x0', 'y0', 'z0', 'sigvm' ]
+        }
+    ]
 
     # material properties
     p['density'] = 1.850e-9  # [T/mm³]