diff --git a/ogstools/msh2vtu/__init__.py b/ogstools/msh2vtu/__init__.py
index aaeb297945220c98a4ebf19cbe55b8b231c3b430..ac2a24d6e224523cd716e46e8f5d53183f802288 100644
--- a/ogstools/msh2vtu/__init__.py
+++ b/ogstools/msh2vtu/__init__.py
@@ -150,19 +150,49 @@ def find_connected_domain_cells(
 
 # TODO: rename
 def msh2vtu(
-    input_filename: Path,
+    filename: Path,
     output_path: Path = Path(),
     output_prefix: str = "",
     dim: Union[int, list[int]] = 0,
     delz: bool = False,
     swapxy: bool = False,
-    rdcd: bool = True,
-    ogs: bool = True,
+    reindex: bool = False,
+    keep_ids: bool = False,
     ascii: bool = False,
     log_level: Union[int, str] = "DEBUG",
 ):
+    """
+    Convert a gmsh mesh (.msh) to an unstructured grid file (.vtu).
+
+    Prepares a Gmsh-mesh for use in OGS by extracting domain-,
+    boundary- and physical group-submeshes, and saves them in
+    vtu-format. Note that all mesh entities should belong to
+    physical groups.
+
+    :param filename:    Gmsh mesh file (.msh) as input data
+    :param output_path: Path of output files, defaults to current working dir
+    :param output_prefix: Output files prefix, defaults to basename of inputfile
+    :param dim: Spatial dimension (1, 2 or 3), trying automatic detection,
+                if not given. If multiple dimensions are provided, all elements
+                of these dimensions are embedded in the resulting domain mesh.
+    :param delz:    Delete z-coordinate, for 2D-meshes with z=0.
+                    Note that vtu-format requires 3D points.
+    :param swapxy:  Swap x and y coordinate
+    :param reindex: Renumber physical group / region / Material IDs to be
+                    renumbered beginning with zero.
+    :param keep_ids:    By default, rename 'gmsh:physical' to 'MaterialIDs'
+                        and change type of corresponding cell data to INT32.
+                        If True, this is skipped.
+    :param ascii:   Save output files (.vtu) in ascii format.
+    :param log_level:   Level of log output. Possible values:
+                        <https://docs.python.org/3/library/logging.html#levels>
+
+    :returns:           A MeshSeries object
+    """
     logging.getLogger().setLevel(log_level)
-    input_filename = Path(input_filename)
+    if isinstance(dim, list):
+        assert len(dim) < 3, "Specify at most 3 dim values."
+    filename = Path(filename)
     # some variable declarations
     ph_index = 0  # to access physical group id in field data
     geo_index = 1  # to access geometrical dimension in field data
@@ -178,55 +208,39 @@ def msh2vtu(
         dim0: {"vertex"},
         dim1: {"line", "line3", "line4"},
         dim2: {
-            "triangle",
-            "triangle6",
-            "triangle9",
-            "triangle10",
-            "quad",
-            "quad8",
-            "quad9",
+            *["triangle", "triangle6", "triangle9", "triangle10"],
+            *["quad", "quad8", "quad9"],
         },
         dim3: {
-            "tetra",
-            "tetra10",
-            "pyramid",
-            "pyramid13",
-            "pyramid15",
-            "pyramid14",
-            "wedge",  # outdated, keep for backward compatibility
-            "wedge15",
-            "wedge18",
-            "hexahedron",
-            "hexahedron20",
-            "hexahedron27",
-            "prism",
-            "prism15",
-            "prism18",
+            *["tetra", "tetra10"],
+            *["pyramid", "pyramid13", "pyramid15", "pyramid14"],
+            *["wedge", "wedge15", "wedge18"],
+            *["hexahedron", "hexahedron20", "hexahedron27"],
+            *["prism", "prism15", "prism18"],
         },
     }
     gmsh_physical_cell_data_key = "gmsh:physical"
     ogs_domain_cell_data_key = "MaterialIDs"
     ogs_boundary_cell_data_key = "bulk_elem_ids"
+    ogs = not keep_ids
 
     # check if input file exists and is in gmsh-format
-    if not input_filename.is_file():
+    if not filename.is_file():
         logging.warning("No input file (mesh) found.")
         # raise FileNotFoundError
         return 1
 
-    if input_filename.suffix != ".msh":
+    if filename.suffix != ".msh":
         logging.warning(
             "Warning, input file seems not to be in gmsh-format (*.msh)"
         )
 
     # if no parameter given, use same basename as input file
-    output_basename = (
-        input_filename.stem if output_prefix == "" else output_prefix
-    )
+    output_basename = filename.stem if output_prefix == "" else output_prefix
     logging.info("Output: %s", output_basename)
 
     # read in mesh (be aware of shallow copies, i.e. by reference)
-    mesh: meshio.Mesh = meshio.read(str(input_filename))
+    mesh: meshio.Mesh = meshio.read(str(filename))
     points, point_data = mesh.points, mesh.point_data
     cells_dict, cell_data, cell_data_dict = (
         mesh.cells_dict,
@@ -323,24 +337,12 @@ def msh2vtu(
                     pg_key = "PhysicalGroup_" + str(pg_id)
                     field_data[pg_key] = np.array([pg_id, pg_dim])
 
-        # if user wants physical group numbering of domains beginning with zero
-        id_offset = 0  # initial value, zero will not change anything
-        if rdcd:  # prepare renumber-domain-cell-data (rdcd)
-            # find minimum physical_id of domains (dim)
-            id_list_domains = []
-            for (
-                dataset
-            ) in field_data.values():  # go through all physical groups
-                if (
-                    dataset[geo_index] == domain_dim
-                ):  # only for domains, ignore lower dimensional entities
-                    id_list_domains.append(
-                        dataset[ph_index]
-                    )  # append physical id
-            if len(id_list_domains):  # if there are some domains..
-                id_offset = min(
-                    id_list_domains
-                )  # ..then find minimal physical id
+        id_list_domains = [
+            physical_group[ph_index]
+            for physical_group in field_data.values()
+            if physical_group[geo_index] == domain_dim
+        ]
+        id_offset = min(id_list_domains, default=0) if reindex else 0
 
     else:
         logging.info("No physical groups found.")
@@ -354,9 +356,8 @@ def msh2vtu(
     ############################################################################
     all_points = np.copy(points)  # copy all, superfluous get deleted later
     if ogs:
-        original_point_numbers = np.arange(
-            number_of_original_points
-        )  # to associate domain points later
+        # to associate domain points later
+        original_point_numbers = np.arange(number_of_original_points)
         all_point_data = {}
         all_point_data[ogs_domain_point_data_key] = np.uint64(
             original_point_numbers
@@ -555,9 +556,8 @@ def msh2vtu(
                     gmsh_physical_cell_data_key
                 ][boundary_cell_type]
             else:
-                number_of_boundary_cells = len(
-                    boundary_cells_values
-                )  # cells of specific type
+                # cells of specific type
+                number_of_boundary_cells = len(boundary_cells_values)
                 boundary_cell_data_values = np.zeros(
                     (number_of_boundary_cells), dtype=int
                 )
@@ -630,9 +630,8 @@ def msh2vtu(
                 # use gmsh, as the requirements from OGS
                 subdomain_cell_data_key = gmsh_physical_cell_data_key
         else:
-            subdomain_cell_data_key = (
-                gmsh_physical_cell_data_key  # same for all dimensions
-            )
+            # same for all dimensions
+            subdomain_cell_data_key = gmsh_physical_cell_data_key
         subdomain_cell_data[subdomain_cell_data_key] = []  # list
         # flag to indicate invalid bulk_element_ids, then no cell data will be
         # written
diff --git a/ogstools/msh2vtu/_cli.py b/ogstools/msh2vtu/_cli.py
index 495b1a0b361a45c281b37c773d8e1b09c87a87bf..5721a8edd05a61fa8aa5360e0f3bc83fee3215cf 100644
--- a/ogstools/msh2vtu/_cli.py
+++ b/ogstools/msh2vtu/_cli.py
@@ -6,107 +6,43 @@ from ogstools.msh2vtu import msh2vtu
 
 def argparser():
     # parsing command line arguments
+    def get_help(arg: str):
+        assert msh2vtu.__doc__ is not None
+        return msh2vtu.__doc__.split(arg + ":")[1].split(":param")[0].strip()
+
     parser = argparse.ArgumentParser(
-        description=(
-            "Prepares a Gmsh-mesh for use in OGS by extracting domain-,"
-            " boundary- and physical group-submeshes, and saves them in"
-            " vtu-format. Note that all mesh entities should belong to"
-            " physical groups."
-        ),
-    )
-    parser.add_argument("filename", help="Gmsh mesh file (*.msh) as input data")
-    parser.add_argument(
-        "-g",
-        "--ogs",
-        action="store_true",
-        help=(
-            'rename "gmsh:physical" to "MaterialIDs" for domains and change '
-            "type of corresponding cell data to INT32"
-        ),
-    )
-    parser.add_argument(
-        "-r",
-        "--rdcd",
-        action="store_true",
-        help=(
-            "renumber domain cell data, physical IDs (cell data) of domains "
-            "get numbered beginning with zero"
-        ),
-    )
-    parser.add_argument(
-        "-a",
-        "--ascii",
-        action="store_true",
-        help="save output files (*.vtu) in ascii format",
-    )
-    parser.add_argument(
-        "-d",
-        "--dim",
-        type=int,
-        nargs="*",
-        default=0,
-        help=(
-            "spatial dimension (1, 2 or 3), trying automatic detection, "
-            "if not given. If multiple dimensions are provided, all elements of"
-            "these dimensions are embedded in the resulting domain mesh."
-        ),
-    )
-    parser.add_argument(
-        "-o",
-        "--output_path",
-        default="",
-        help=("path of output files; if not given, then it defaults to cwd"),
-    )
-    parser.add_argument(
-        "-p",
-        "--prefix",
-        default="",
-        help=(
-            "basename of output files; if not given, then it defaults to"
-            " basename of inputfile"
-        ),
-    )
-    parser.add_argument(
-        "-z",
-        "--delz",
-        action="store_true",
-        help=(
-            "deleting z-coordinate, for 2D-meshes with z=0, note that"
-            " vtu-format requires 3D points"
-        ),
-    )
-    parser.add_argument(
-        "-s",
-        "--swapxy",
-        action="store_true",
-        help="swap x and y coordinate",
-    )
-    parser.add_argument(
-        "-v",
-        "--version",
-        action="version",
-        version=f"msh2vtu (part of ogstools {__version__}, Dominik Kern)",
-    )
+        description=msh2vtu.__doc__.split(":param")[0].strip()
+    )
+    add_arg = parser.add_argument
+    add_arg("filename", help=get_help("filename"))
+    add_arg("-o", "--output_path", default="", help=get_help("output_path"))
+    add_arg("-p", "--prefix", default="", help=get_help("prefix"))
+    add_arg("-d", "--dim", type=int, nargs="*", default=0, help=get_help("dim"))
+    add_arg("-z", "--delz", action="store_true", help=get_help("delz"))
+    add_arg("-s", "--swapxy", action="store_true", help=get_help("swapxy"))
+    add_arg("-r", "--reindex", action="store_true", help=get_help("reindex"))
+    add_arg("-k", "--keep_ids", action="store_true", help=get_help("keep_ids"))
+    add_arg("-a", "--ascii", action="store_true", help=get_help("ascii"))
+    add_arg("-l", "--log_level", default="DEBUG", help=get_help("log_level"))
+    version = f"msh2vtu (part of ogstools {__version__}, Dominik Kern)"
+    add_arg("-v", "--version", action="version", version=version)
 
     return parser
 
 
-def cli():
+def cli() -> int:
     """command line use"""
     args = argparser().parse_args()
 
-    ErrorCode = msh2vtu(
-        input_filename=args.filename,
+    return msh2vtu(
+        filename=args.filename,
         output_path=args.output_path,
         output_prefix=args.prefix,
         dim=args.dim,
         delz=args.delz,
         swapxy=args.swapxy,
-        rdcd=args.rdcd,
-        ogs=args.ogs,
+        reindex=args.reindex,
+        keep_ids=args.keep_ids,
         ascii=args.ascii,
+        log_level=args.log_level,
     )
-    if ErrorCode == 0:
-        print("msh2vtu successfully finished")
-    else:
-        print("msh2vtu stopped with errors")
diff --git a/tests/test_studies_convergence.py b/tests/test_studies_convergence.py
index d45fe5968e28694795e20121b24312e132092001..c0cae8d69a62d2f0935b2bfbef326b8e49aa36fa 100644
--- a/tests/test_studies_convergence.py
+++ b/tests/test_studies_convergence.py
@@ -31,9 +31,7 @@ class ConvergenceTest(unittest.TestCase):
                 structured_grid=True,
                 out_name=msh_path,
             )
-            msh2vtu(
-                input_filename=msh_path, output_path=temp_dir, log_level="ERROR"
-            )
+            msh2vtu(filename=msh_path, output_path=temp_dir, log_level="ERROR")
             model = ogs.OGS(
                 PROJECT_FILE=temp_dir / "default.prj",
                 INPUT_FILE=convergence.examples.steady_state_diffusion_prj,