Update 2: workflow to create perfectly cubical voxels
In update 1, I discovered that although MaxCellMeasure
will allow you to control the resolution of the base mesh, ToElementMesh
makes some internal choices to refine the mesh. Unfortunately, this refinement makes it virtually impossible to guarantee that the voxels are perfect cubes. Therefore, I created a workflow that builds the base mesh by hand to create a cubic voxel.
Helper functions
For simplicity, I wrapped the workflow into a function called voxelize
as shown below:
(*Import required FEM package*)
Needs["NDSolve`FEM`"];
(*Tensor product mesh from:https://wolfram.com/xid/0rs5ccudm-eqv31q*)
pointsToMesh[data_] :=
MeshRegion[Transpose[{data}],
Line@Table[{i, i + 1}, {i, Length[data] - 1}]];
Clear[voxelize]
voxelize[fil_, nelm_ : 10] :=
Module[{stl, rb, extents, mcm, paddedextents, rp, crd,
inc, em, vol, rmfv, reg, regmarkerfn, mean, regmarkers, mesh,
subset, torusinc, gc, full, cut, unique, rules, torusem},
(*Import required STL file*)
stl = Import[fil];
(*Center region*)
stl = TransformedRegion[stl,
TranslationTransform[-Mean@Transpose@RegionBounds[stl]]];
(*Get bounding box of the shape*)
rb = RegionBounds[stl];
extents = Flatten@Differences[rb, {0, 1}];
(*Calculate maximum cell measure*)
mcm = Min@Differences[rb, {0, 1}]/nelm;
(*Pad the extents so we can make the voxels perfect cubes*)
paddedextents = mcm Round[#/mcm] & /@ extents;
rp = RegionProduct @@ ((pointsToMesh@
Subdivide[#1, #2, #3]) & @@@ ({-#/2, #/2, Round[#/mcm]} & /@
paddedextents));
crd = MeshCoordinates[rp];
(*grab hexa element incidents RegionProduct mesh*)
inc = Delete[0] /@ MeshCells[rp, 3];
(*Get bounding cuboid*)
em = ToElementMesh["Coordinates" -> crd,
"MeshElements" -> {HexahedronElement[inc]}];
(*Create volume mesh of torus*)
vol = MeshRegion@ToElementMesh[stl];
(*Get region member function of torus*)
rmfv = RegionMember[vol];
(*Association for Clearer Region Assignment*)
reg = <|"main" -> 1, "incl" -> 2|>;
regmarkerfn = If[rmfv[#], reg["incl"], reg["main"]] &;
(*Get mean coordinate of each hexa for region marker assignment*)
mean = Mean /@ GetElementCoordinates[em["Coordinates"], #] & /@
ElementIncidents[em["MeshElements"]] // First;
regmarkers = regmarkerfn /@ mean;
(*Create marked region element mesh*)
mesh =
ToElementMesh["Coordinates" -> em["Coordinates"],
"MeshElements" -> {HexahedronElement[inc, regmarkers]}];
(*Find hexa subset that belongs to torus*)
subset =
Flatten@Position [
ElementMarkers[
First@mesh["MeshElements"]], _?(# == reg["incl"] &), 1];
torusinc = First[ ElementIncidents[mesh["MeshElements"]]][[subset]];
(*ToElementMesh does not like extra coordinates*)
(*Therefore,the element incidents need to be renumbered*)
unique = Union@Flatten@torusinc;
rules = AssociationThread[# -> Range@Length@#] &@unique;
torusem =
ToElementMesh["Coordinates" -> crd[[unique]],
"MeshElements" -> {HexahedronElement[torusinc /. rules]}];
(*GraphicsComplex does not care about the extra coordinates*)
gc = GraphicsComplex[mesh["Coordinates"], {Hexahedron[torusinc]}];
full = Graphics3D[gc, Boxed -> False];
cut = Graphics3D[gc, Boxed -> False,
PlotRange -> (mesh["Bounds"] /. {x_, y_} -> {0, y})];
<|"stl" -> stl, "nelm" -> nelm, "mesh" -> mesh, "gc" -> gc,
"full" -> full, "cut" -> cut, "mean" -> mean,
"crd" -> torusem["Coordinates"], "inc" -> torusinc,
"torusmesh" -> torusem|>
]
stlfile = "torus.stl";
Background mesh
With voxelize
, it is easy to demonstrate that your input resolution is your output resolution. For example, if I want 4 elements across the minimum dimension, I simply execute:
vox = voxelize[stlfile, 4];
Show[vox["stl"], vox["mesh"]["Wireframe"]]

As you can see, there are 4 elements across the vertical/minimum dimension.
Resolution study
It is easy to set up a resolution study like so:
Grid[Transpose@Table[With[{v = voxelize[stlfile, #]},
{StringTemplate["Full extracted torus (nelm=``)"][v["nelm"]],
v["full"], StringTemplate["Cutaway (nelm=``)"][v["nelm"]],
v["cut"]}
] &[i], {i, 2, 10, 3}], Frame -> All]

Accessing the torus mesh info
In response to @F.Mark's comment, I added additional keys to the output of the voxelize
module function to access the torus voxelized mesh. For example:
vox = voxelize[stlfile, 2];
vox["torusmesh"]["Wireframe"]
Short[vox["crd"], 5]
Short[vox["inc"], 5]

More complex objects
Here's an example to show that it can handle more complex STL objects.
vox = voxelize["http://exampledata.wolfram.com/gear.1", 10];
Show[{Graphics3D[{Opacity[1], vox["gc"]}, Boxed -> False]}]

Update 1: workflow to create volumetric region
The following uses ToElementMesh
to create a bounding hexa mesh around the torus. Then it uses a region member function to identify the hexahedra that lie within the torus to create a separate region. These hexahedra can then be extracted by the region marker.
(*Import required FEM package*)
Needs["NDSolve`FEM`"];
(*Import required STL file*)
stl = Import["torus.stl"]
(*Get bounding box of the shape*)
rb = RegionBounds[stl];
(*Desired number of elements across minimum dimension*)
nelm = 10;
(*Calculate maximum cell measure*)
mcm = Min@Differences[rb, {0, 1}]/nelm;
(*Get bounding cuboid*)
em = ToElementMesh[Cuboid[Delete[0]@Transpose@rb],
MaxCellMeasure -> {"Length" -> mcm}, "MeshOrder" -> 1];
inc = ElementIncidents[em["MeshElements"]][[1]];
(*Create volume mesh of torus*)
vol = MeshRegion@ToElementMesh[stl];
(*Get region member function of torus*)
rmfv = RegionMember[vol];
(*Association for Clearer Region Assignment*)
reg = <|"main" -> 1, "incl" -> 2|>;
regmarkerfn = If[rmfv[#], reg["incl"], reg["main"]] &;
(*Get mean coordinate of each hexa for region marker assignment*)
mean = Mean /@ GetElementCoordinates[em["Coordinates"], #] & /@
ElementIncidents[em["MeshElements"]] // First;
regmarkers = regmarkerfn /@ mean;
(*Create and view element mesh*)
Print["Converted Hexa Element Mesh Cutaway Drawing"]
mesh = ToElementMesh["Coordinates" -> em["Coordinates"],
"MeshElements" -> {HexahedronElement[inc, regmarkers]}];
mesh[
"Wireframe"["MeshElement" -> "MeshElements",
"MeshElementStyle" -> (Directive[Opacity[0.5], FaceForm[#](*,
EdgeForm[]*)] & /@ {Orange, Blue}),
PlotRange -> (rb /. {x_, y_} -> {0, y})]]

To extract the torus is a GraphicsComplex, you could do the following:
subset = Flatten@
Position [
ElementMarkers[
First@mesh["MeshElements"]], _?(# == reg["incl"] &), 1];
torusinc = First[ ElementIncidents[mesh["MeshElements"]]][[subset]];
gc = GraphicsComplex[mesh["Coordinates"], {Hexahedron[torusinc]}];
Print["Full extracted torus"]
Graphics3D[gc, Boxed -> False]
Print["Cutaway to show that it is a volume mesh"]
Graphics3D[gc, Boxed -> False,
PlotRange -> (rb /. {x_, y_} -> {0, y})]

Previous answer
Note: I may delete this answer since, after closer inspection, I found out that it is only returning a boundary mesh and not a volume mesh.
You could use RegionImage
and ImageMesh
(note that Windows 10 gave me a virus warning on the rar file, so I made my own torus.stl):
stl = Import["torus.stl"]
ImageMesh@RegionImage[stl, RasterSize -> 100]
