diff --git a/doc/texinfo/opt_print.texi b/doc/texinfo/opt_print.texi
index 05552e2932b17871fe142554ccc4c5a6bee0f550..599885489e503aaae95c09925fd937d535a7d1b4 100644
--- a/doc/texinfo/opt_print.texi
+++ b/doc/texinfo/opt_print.texi
@@ -231,6 +231,16 @@ Save vertices in CAD X3D output (0: no, 1: yes)@*
 Default value: @code{0}@*
 Saved in: @code{General.OptionsFileName}
 
+@item Print.X3dVolumes
+Save surfaces in CAD X3D output (0: no, 1: create separate x3d file for each volume in model)@*
+Default value: @code{0}@*
+Saved in: @code{General.OptionsFileName}
+
+@item Print.X3dColorize
+Save surfaces in CAD X3D output (0: no, 1: apply color to faces)@*
+Default value: @code{0}@*
+Saved in: @code{General.OptionsFileName}
+
 @item Print.Width
 Width of printed image; use (possibly scaled) current width if < 0)@*
 Default value: @code{-1}@*
diff --git a/examples/api/x3d_export.py b/examples/api/x3d_export.py
new file mode 100644
index 0000000000000000000000000000000000000000..28cda17d00ca13503aa4dc629c74f63731b0ad08
--- /dev/null
+++ b/examples/api/x3d_export.py
@@ -0,0 +1,30 @@
+import gmsh
+import argparse
+import os
+
+# python gmsh_breakdown.py --split 1 will create separate x3d files for each volume
+parser = argparse.ArgumentParser(description='x3d print options')
+parser.add_argument('--surface_mode',default=2,type=int)
+parser.add_argument('--split',default=0,type=int)
+parser.add_argument('--colorize', default=1,type=int)
+args = parser.parse_args()
+
+x3dsurface = args.surface_mode
+x3dvolume = args.split
+x3dcolorize = args.colorize
+
+gmsh.initialize()
+gmsh.open('as1-tu-203.stp') # change to any input stp in directory
+
+path = os.path.join(os.curdir,"x3d_output")
+if not os.path.exists(path):
+    os.makedirs(path)
+
+gmsh.option.setNumber('Print.X3dSurfaces',x3dsurface)
+gmsh.option.setNumber('Print.X3dVolumes',x3dvolume)
+gmsh.option.setNumber('Print.X3dColorize',x3dcolorize)
+
+outfile = os.path.join(path, 'out.x3d')
+gmsh.write(outfile)
+gmsh.clear()
+gmsh.finalize()
\ No newline at end of file
diff --git a/src/common/Context.h b/src/common/Context.h
index d94cbdb14838edcf906ba805c9acc68865e7aae7..35a96ee043c745465d89d5f6496e6d0a23bf9288 100644
--- a/src/common/Context.h
+++ b/src/common/Context.h
@@ -344,7 +344,7 @@ public:
     std::string parameterCommand;
     int x3dCompatibility, x3dRemoveInnerBorders;
     double x3dPrecision, x3dTransparency;
-    int x3dSurfaces, x3dEdges, x3dVertices;
+    int x3dSurfaces, x3dEdges, x3dVertices, x3dVolumes, x3dColorize;
   } print;
   // color options
   struct {
diff --git a/src/common/CreateFile.cpp b/src/common/CreateFile.cpp
index 912aee1eb60defd21aa14cc0bf0ef1ecdeecb729..229d4312d4e1d17e0d17340c9cbee75915db9d0b 100644
--- a/src/common/CreateFile.cpp
+++ b/src/common/CreateFile.cpp
@@ -350,7 +350,9 @@ void CreateOutputFile(const std::string &fileName, int format,
        CTX::instance()->mesh.scalingFactor,
        CTX::instance()->print.x3dSurfaces,
        CTX::instance()->print.x3dEdges,
-       CTX::instance()->print.x3dVertices);
+       CTX::instance()->print.x3dVertices,
+       CTX::instance()->print.x3dVolumes,
+       CTX::instance()->print.x3dColorize);
     break;
 
   case FORMAT_VRML:
diff --git a/src/common/DefaultOptions.h b/src/common/DefaultOptions.h
index 30b443e7e9ec2eefaaaeaa363ad93d57f21b6248..cbc47ac49400656701a05d34579ee74747d47cfa 100644
--- a/src/common/DefaultOptions.h
+++ b/src/common/DefaultOptions.h
@@ -2079,6 +2079,10 @@ StringXNumber PrintOptions_Number[] = {
     "physical edge)"},
   { F|O, "X3dVertices" , opt_print_x3d_vertices, 0. ,
     "Save vertices in CAD X3D output (0: no, 1: yes)"},
+  { F|O, "X3dVolumes" , opt_print_x3d_volumes, 0. ,
+    "Save separate volumes in CAD X3D output (0: no, 1: yes)"},
+  { F|O, "X3dColorize" , opt_print_x3d_colorize, 0. ,
+    "Apply colors to faces (0: no, 1: yes)"},
 
   { F|O, "Width" , opt_print_width , -1. ,
     "Width of printed image; use (possibly scaled) current width if < 0)" },
diff --git a/src/common/Options.cpp b/src/common/Options.cpp
index f5942107f85774037e73b049c8bb24cc06ae7bbd..67f61dbb9b5864c63c0fc3ad8f50c0af9dbfde43 100644
--- a/src/common/Options.cpp
+++ b/src/common/Options.cpp
@@ -9378,6 +9378,18 @@ double opt_print_x3d_vertices(OPT_ARGS_NUM)
   return CTX::instance()->print.x3dVertices;
 }
 
+double opt_print_x3d_volumes(OPT_ARGS_NUM)
+{
+  if(action & GMSH_SET) CTX::instance()->print.x3dVolumes = (int)val;
+  return CTX::instance()->print.x3dVolumes;
+}
+
+double opt_print_x3d_colorize(OPT_ARGS_NUM)
+{
+  if(action & GMSH_SET) CTX::instance()->print.x3dColorize = (int)val;
+  return CTX::instance()->print.x3dColorize;
+}
+
 // Color option routines
 
 #if defined(HAVE_FLTK)
diff --git a/src/common/Options.h b/src/common/Options.h
index b27184042ff46d1b9c2097cbfe24e10d1cb381b1..577d6ef48b49325355ba94c705ae873f87d9d89f 100644
--- a/src/common/Options.h
+++ b/src/common/Options.h
@@ -817,6 +817,8 @@ double opt_print_x3d_precision(OPT_ARGS_NUM);
 double opt_print_x3d_surfaces(OPT_ARGS_NUM);
 double opt_print_x3d_edges(OPT_ARGS_NUM);
 double opt_print_x3d_vertices(OPT_ARGS_NUM);
+double opt_print_x3d_volumes(OPT_ARGS_NUM);
+double opt_print_x3d_colorize(OPT_ARGS_NUM);
 
 // COLORS
 
diff --git a/src/geo/GModel.h b/src/geo/GModel.h
index 1681893b2ab4556909774c19f6a541468af93d13..2ae35eb382cbb439a635e989f3d90d4e284dd4c1 100644
--- a/src/geo/GModel.h
+++ b/src/geo/GModel.h
@@ -79,6 +79,9 @@ private:
   int _writePartitionedMSH4(const std::string &baseName, double version,
                             bool binary, bool saveAll, bool saveParametric,
                             double scalingFactor);
+  int _writeX3dFile(FILE* fp, bool saveAll,
+                     double scalingFactor, int x3dsurfaces, int x3dedges,
+                     int x3dvertices, int x3dcolorize, std::vector<GFace *>& customFaces);
 
 protected:
   // the name of the model
@@ -769,7 +772,7 @@ public:
   // X3D (only output from OCCT's triangulation)
   int writeX3D(const std::string &name, bool saveAll = false,
                double scalingFactor = 1.0, int x3dsurfaces = 1,
-               int x3dedges = 0, int x3dvertices = 0);
+               int x3dedges = 0, int x3dvertices = 0, int x3dvolumes = 0, int x3dcolorize = 0);
 
   // PLY(2) format (ascii text format)
   int readPLY(const std::string &name);
diff --git a/src/geo/GModelIO_X3D.cpp b/src/geo/GModelIO_X3D.cpp
index 396d5f47781de59d70c3d5b79054142a27c68199..d518a08fc73923255496f54fd005f54de4e6ae66 100644
--- a/src/geo/GModelIO_X3D.cpp
+++ b/src/geo/GModelIO_X3D.cpp
@@ -19,15 +19,99 @@
 #include "StringUtils.h"
 #include "Context.h"
 
+static void writeX3dHeader(FILE *fp, std::vector<std::string> &metadata)
+{
+  // X3D Header
+  fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+  fprintf(fp, "<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 3.3//EN\" "
+              "\"http://www.web3d.org/specifications/x3d-3.3.dtd\">\n");
+  fprintf(fp, "<X3D profile='Interchange' version='3.3'  "
+              "xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' >\n");
+  fprintf(fp, "  <head>\n");
+  fprintf(fp, "    <meta name='creator' content='gmsh'/> \n");
+  for(auto it = metadata.begin(); it != metadata.end(); ++it){
+    fprintf(fp, "    <meta name='metadata_%s' content='%s'/> \n",
+            std::to_string(std::distance(metadata.begin(), it)).c_str(),
+            (*it).c_str());
+  }
+  fprintf(fp, "  </head>\n");
+  fprintf(fp, "  <Scene>\n");
+}
+
+static void writeX3dTrailer(FILE *fp){
+  fprintf(fp, "  </Scene>\n");
+  fprintf(fp, "</X3D>\n");
+}
+
+static void writeHTMLHeader(FILE *fp){
+  fprintf(fp, "<!DOCTYPE HTML\n>");
+  fprintf(fp, "<html lang=\"en\"> \n");
+  fprintf(fp, "<head>\n");
+  fprintf(fp, "  <title> gmsh x3d render</title>\n");
+  fprintf(fp, "  <meta charset=\"utf-8\">\n");
+  fprintf(fp, "  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://x3dom.org/release/x3dom.css\">\n");
+  fprintf(fp, "  <script src=\"https://x3dom.org/release/x3dom.js\"></script>\n");
+  fprintf(fp, "  <style>\n");
+  fprintf(fp, "    body {\n");
+  fprintf(fp, "      background: #ced7de;\n");
+  fprintf(fp, "      margin: 0px;\n");
+  fprintf(fp, "      overflow: hidden;\n");
+  fprintf(fp, "    }\n");
+  fprintf(fp, "    a {\n");
+  fprintf(fp, "      color: #f7941e;\n");
+  fprintf(fp, "      text-decoration: none;\n");
+  fprintf(fp, "    }\n");
+  fprintf(fp, "    a:hover {\n");
+  fprintf(fp, "      color: #ffffff;\n");
+  fprintf(fp, "    }\n");
+  fprintf(fp, "  </style>\n");
+  fprintf(fp, "</head>\n");
+}
+
+static void writeHTMLBody(FILE *fp, std::vector<std::string> &x3dfiles){
+  fprintf(fp, "<body>\n");
+  fprintf(fp, "  <x3d id=\"gmsh-scene\" style=\"width: 100%%; height: 100%%;border: none\" >\n");
+  fprintf(fp, "    <Scene>\n");
+  fprintf(fp, "      <transform scale=\"1,1,1\">\n");
+  fprintf(fp, "      <transform id=\"plane_axis\" rotation=\"1 0 0 -1.57079632679\">\n"); 
+  fprintf(fp, "        <inline url=\"https://rawcdn.githack.com/x3dom/component-editor/master/static/x3d/plane.x3d\" mapDEFToID=\"true\" namespaceName=\"plane\"></inline>\n");
+  fprintf(fp, "        <inline url=\"https://rawcdn.githack.com/x3dom/component-editor/master/static/x3d/axesSmall.x3d\" mapDEFToID=\"true\" namespaceName=\"axesSmall\"></inline>\n");
+  fprintf(fp, "      </transform>\n");
+  fprintf(fp, "      <inline url=\"https://rawcdn.githack.com/x3dom/component-editor/master/static/x3d/axes.x3d\" mapDEFToID=\"true\" namespaceName=\"axes\"></inline>\n");
+  fprintf(fp, "      </transform>\n");
+  fprintf(fp, "      <transform id=\"components\" rotation=\"1 0 0 -1.57079632679\">\n");
+  for(auto it = x3dfiles.begin(); it != x3dfiles.end(); ++it){
+    //drop path on x3d since it will be in the same folder as html
+    std::vector<std::string> split = SplitFileName(*it);
+    std::string x3dname = split[1] + split[2];
+    fprintf(fp, "        <inline onload=\"fit()\" mapDEFToID=\"true\" url=%s></inline>\n", x3dname.c_str());
+  }
+  fprintf(fp, "      </transform>\n");
+  fprintf(fp, "    </Scene>\n");
+  fprintf(fp, "  </x3d>\n");
+  fprintf(fp, "  <script>\n");
+  fprintf(fp, "  function fit()\n");
+  fprintf(fp, "  {\n");
+  fprintf(fp, "    var x3dElem = document.getElementById('gmsh-scene');\n");
+  fprintf(fp, "    x3dElem.runtime.fitAll();\n");
+  fprintf(fp, "  }\n");
+  fprintf(fp, "  </script>\n");
+  fprintf(fp, "</body>\n");
+  fprintf(fp, "</html>\n");
+}
+
 static void writeX3dFaces(FILE *fp, std::vector<GFace *> &faces,
                           bool useIndexedSet, double scalingFactor,
-                          const std::string &name)
+                          const std::string &name,
+                          bool useColor,
+                          std::vector<unsigned int> &colors)
 {
   bool useGeoSTL = false;
   unsigned int nfacets = 0;
   for(auto it = faces.begin(); it != faces.end(); ++it) {
     nfacets += (*it)->triangles.size() + 2 * (*it)->quadrangles.size();
   }
+
   if(!nfacets) { // use CAD STL if there is no mesh
     useGeoSTL = true;
     for(auto it = faces.begin(); it != faces.end(); ++it) {
@@ -36,10 +120,30 @@ static void writeX3dFaces(FILE *fp, std::vector<GFace *> &faces,
     }
   }
 
-  fprintf(fp, "    <Shape DEF=\"%s\">\n", name.c_str());
-  fprintf(fp,
-          "     <Appearance><Material DEF=\"mat%s\"></Material></Appearance>\n",
-          name.c_str());
+  if(!useColor){
+    fprintf(fp, "    <Shape DEF=\"%s\">\n", name.c_str());
+    fprintf(fp,
+            "     <Appearance><Material DEF=\"mat%s\"></Material></Appearance>\n",
+            name.c_str());
+  }
+  else{
+    if(colors.size()==1){
+      unsigned int cvalue = colors[0];
+      float r = static_cast<float>(CTX::instance()->unpackRed(cvalue)) / 255.0;
+      float g = static_cast<float>(CTX::instance()->unpackGreen(cvalue)) / 255.0;
+      float b = static_cast<float>(CTX::instance()->unpackBlue(cvalue)) / 255.0;
+      fprintf(fp, "    <Shape DEF=\"%s\">\n", name.c_str());
+      fprintf(fp,
+              "     <Appearance><Material DEF=\"mat%s\" diffuseColor=\"%s %s %s\" shininess=\"0.9\" specularColor=\"0.2 0.2 0.2\" transparency=\"0\"></Material></Appearance>\n",
+              name.c_str(),
+              std::to_string(r).c_str(),
+              std::to_string(g).c_str(),
+              std::to_string(b).c_str());
+    }
+    else{
+      Msg::Error("Error in x3d coloring");
+    }
+  }
 
   if(useGeoSTL) {
     if(useIndexedSet) {
@@ -184,66 +288,63 @@ static void writeX3dEdges(FILE *fp, std::vector<GEdge *> &edges,
   }
 }
 
-int GModel::writeX3D(const std::string &name, bool saveAll,
+int GModel::_writeX3dFile(FILE* fp, bool saveAll,
                      double scalingFactor, int x3dsurfaces, int x3dedges,
-                     int x3dvertices)
+                     int x3dvertices, int x3dcolorize, std::vector<GFace *>& customFaces)
 {
-  FILE *fp;
+  if(noPhysicalGroups()) saveAll = true;
 
-  if(x3dsurfaces == 0 && x3dedges == 0 && x3dvertices == 0) {
-    Msg::Info("No surfaces, edges or vertices to write into '%s'",
-              name.c_str());
-    return 0;
+  std::vector<GFace *> modelFaces;
+  if(customFaces.size() > 0)
+  {
+    for(auto it = customFaces.begin(); it != customFaces.end(); ++it){
+      modelFaces.push_back(*it);
+    }
   }
-
-  fp = Fopen(name.c_str(), "w");
-  if(!fp) {
-    Msg::Warning("Unable to open file '%s'", name.c_str());
-    return 0;
+  else{
+    for(auto it = firstFace(); it != lastFace(); ++it){
+      modelFaces.push_back(*it);
+    }
   }
 
-  // X3D Header
-  fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-  fprintf(fp, "<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 3.3//EN\" "
-              "\"http://www.web3d.org/specifications/x3d-3.3.dtd\">\n");
-  fprintf(fp, "<X3D profile='Interchange' version='3.3'  "
-              "xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' >\n");
-  fprintf(fp, "  <head>\n");
-  fprintf(fp, "    <meta name='creator' content='gmsh'/> \n");
-  fprintf(fp, "  </head>\n");
-  fprintf(fp, "  <Scene>\n");
-
-  if(noPhysicalGroups()) saveAll = true;
-
   if(x3dsurfaces != 0) {
     fprintf(fp, "   <Group DEF=\"faces\">\n");
     if(x3dsurfaces == 1) {
       // all surfaces in a single x3d object
       std::vector<GFace *> faces;
-      for(auto it = firstFace(); it != lastFace(); ++it) {
+      std::vector<unsigned int> colors;
+      //for(auto it = first_face; it != last_face; ++it) {
+      for(auto it = modelFaces.begin(); it != modelFaces.end(); ++it) {
         if(saveAll || (*it)->physicals.size()) { faces.push_back(*it); }
       }
       std::string name = "face";
-      writeX3dFaces(fp, faces, false, scalingFactor, name);
+      writeX3dFaces(fp, faces, false, scalingFactor, name, false, colors);
     }
     else if(x3dsurfaces == 2) {
-      // one x3d object for each physical surface
-      for(auto it = firstFace(); it != lastFace(); ++it) {
+      // one x3d object for each geometrical surface
+      //for(auto it = first_face; it != last_face; ++it) {
+      for(auto it = modelFaces.begin(); it != modelFaces.end(); ++it) {
         if(saveAll || (*it)->physicals.size()) {
           std::vector<GFace *> faces(1, *it);
+          std::vector<unsigned int> colors;
           std::string name = getElementaryName(2, (*it)->tag());
+          // get color info
+          unsigned int cvalue = (*it)->getColor();
+          colors.push_back(cvalue);
           if(name.empty()) {
             std::ostringstream s;
             s << "face" << (*it)->tag();
             name = s.str();
           }
-          writeX3dFaces(fp, faces, true, scalingFactor, name);
+          bool useColor = x3dcolorize == 1;
+          writeX3dFaces(fp, faces, true, scalingFactor, name, useColor, colors);
         }
       }
     }
     else if(x3dsurfaces == 3) {
       // one x3d object per physical surface
       std::map<int, std::vector<GEntity *> > phys;
+      std::vector<unsigned int> colors;
       getPhysicalGroups(2, phys);
       for(auto it = phys.begin(); it != phys.end(); it++) {
         std::vector<GFace *> faces;
@@ -256,7 +357,7 @@ int GModel::writeX3D(const std::string &name, bool saveAll,
           s << "physicalsurface" << it->first;
           name = s.str();
         }
-        writeX3dFaces(fp, faces, false, scalingFactor, name);
+        writeX3dFaces(fp, faces, false, scalingFactor, name, false, colors);
       }
     }
 
@@ -329,10 +430,88 @@ int GModel::writeX3D(const std::string &name, bool saveAll,
     }
     fprintf(fp, "   </Group>\n");
   }
+  return 1;
+}
 
-  fprintf(fp, "  </Scene>\n");
-  fprintf(fp, "</X3D>\n");
+static std::string TagFileName(std::string const & name, int tag)
+{
+  std::vector<std::string> split = SplitFileName(name); //<path>/<name>.<ext>
+  std::string new_name = split[1] + "_" + std::to_string(tag);
+  return split[0] + new_name + split[2];
+}
 
+static std::string HtmlFileName(std::string const & name)
+{
+  std::vector<std::string> split = SplitFileName(name); //<path>/<name>.<ext>
+  return split[0] + "index.html";
+}
+
+int GModel::writeX3D(const std::string &name, bool saveAll,
+                     double scalingFactor, int x3dsurfaces, int x3dedges,
+                     int x3dvertices, int x3dvolumes, int x3dcolorize)
+{
+  FILE *fp;
+  std::vector<std::string> metadata;
+  std::vector<std::string> x3dfiles;
+  if(x3dsurfaces == 0 && x3dedges == 0 && x3dvertices == 0) {
+    Msg::Info("no surfaces, edges or vertices to write into '%s'",
+              name.c_str());
+    return 0;
+  }
+
+  std::vector<GFace *> faces;
+  if(x3dvolumes == 1) {
+    Msg::Info("separating volumes into separate files");
+    std::vector<GEntity *> volumes;
+    getEntities(volumes, 3);
+    
+    // if volumes present in model, else continue to single file mode
+    if(volumes.size() > 0){
+      for(auto it = volumes.begin(); it != volumes.end(); ++it){
+        faces = (*it)->bindingsGetFaces();
+        std::string _vol_name = getElementaryName((*it)->dim(), (*it)->tag());
+        metadata.push_back(_vol_name);
+        std::string _filename = TagFileName(name, (*it)->tag());
+        x3dfiles.push_back(_filename);
+        fp = Fopen(_filename.c_str(), "w");
+        if(!fp) {
+          Msg::Warning("Unable to open file '%s'", name.c_str());
+          return 0;
+        }
+        writeX3dHeader(fp,metadata);
+        _writeX3dFile(fp, saveAll, scalingFactor, x3dsurfaces, x3dedges, x3dvertices, x3dcolorize, faces);
+        writeX3dTrailer(fp);
+        fclose(fp);
+        metadata.clear();
+      }
+      //collate x3d files to html
+      std::string _htmlname = HtmlFileName(name);
+      fp = Fopen(_htmlname.c_str(), "w");
+      writeHTMLHeader(fp);
+      writeHTMLBody(fp, x3dfiles);
+      fclose(fp);
+      return 1;
+    }
+  }
+
+  Msg::Info("writing single file");
+  x3dfiles.push_back(name);
+  fp = Fopen(name.c_str(), "w");
+  if(!fp) {
+    Msg::Warning("Unable to open file '%s'", name.c_str());
+    return 0;
+  }
+  writeX3dHeader(fp,metadata);
+  // main write function
+  _writeX3dFile(fp, saveAll, scalingFactor, x3dsurfaces, x3dedges, x3dvertices, x3dcolorize, faces);
+  writeX3dTrailer(fp);
   fclose(fp);
+
+  std::string _htmlname = HtmlFileName(name);
+  fp = Fopen(_htmlname.c_str(), "w");
+  writeHTMLHeader(fp);
+  writeHTMLBody(fp, x3dfiles);
+  fclose(fp);
+
   return 1;
 }