CindyPrint
CindyPrint is a plug-in that enables the user to print three-dimensional objects from Cindy3D and CindyGL. For this, triangle meshes of the objects can be exported.
For Cindy3D, there are the options to print either the whole scene as the union of all objects (constructive solid geometry, CSG) or print tubes constructed around a path in space. For the former, the JavaScript library csg.js is used (https://github.com/jscad/csg.js/).
For CindyGL, iso surfaces of scalar functions can be created and printed using the Marching Cubes or SnapMC algorithms.
![]() |
![]() |
![]() |
|---|---|---|
| Barth sextic | Torus | Discrete asymptotic line catenoid |
As a first step, in order to use the plugin, at the beginning of the HTML file, the following code needs to be added (possibly with different file paths).
<script type="text/javascript" src="../../build/js/CindyPrint.js"></script>
Examples on how to use CindyPrint can be found in the folder examples/cindyprint/.
GUI Options
When exporting objects for printing, it is often desirable that the user is able to have control over different types of settings. In the following section, an overview over the different settings the user can configure is given.
In Cindy3D, spheres, cylinders and triangles are the objects a user can add to a scene. For the export, the user can specify the following information.
- What types of objects to export.
- The number of subdivisons of spheres and cylinders (i.e., sphere quality and cylinder quality).
- Whether the radius of spheres and cylinders should be scaled compared to what is specified in Cindy3D. This can be useful e.g. for models where the primary object is a triangular surface. When the surface is extruded, it may also be necessary to boost the radius of spheres and cylinders, as they might otherwise be too small.
- What scaling factor to use for the model. This factor controls how large the printed object will be. Please note that, to get the desired model size, it may be necessary to import the object into the printer's slicer software to see how large the object will be in real-world coordinates. After that, the user needs to either scale the object up in the printer software or go back to CindyPrint and export the object at a different size. Unfortunately, different printer software may interpret the dimensionless coordinates in .stl and .obj files differently, so it is hard to get a good standard scaling value.
- Whether to extrude triangle surfaces (i.e., whether to convert open surfaces without volume to hull volumes).
- The radius of these extrusion surfaces.
- Whether the extrusion surfaces should have smooth, circular edges.
When exporting tube meshes enclosing curves, different settings are necessary.
- The number of subdivisons of the cylindrical tube segements (i.e., the cylinder quality).
- The number of line subdivisions, i.e., how many points of the curve are used for discretization.
- The radius of the tube to export is specified in JavaScript. However, the user may scale the radius in the interface.
- Again, like in the user interface explained above, the model scale.
Finally, in CindyGL, the user can export triangle mesh approximations of implicit surfaces of scalar functions. The following settings can be specified.
- Again, like in the user interface for Cindy3D, the model scale, whether to extrude triangle surfaces and whether to use smooth extrusion edges can be specified. Here, triangle iso surfaces are the approximations of the implicit surface to export.
- Whether to clip the geometry to a sphere. In the ray tracer for implicit surfaces, only points are shown that lie within a radius of 2.2. If this box is checked, the object will be clipped to this sphere. Note, however, that for meshes with a really high resolution, csg.js may be too numerically unstable to produce good clipping results and creates meshes with holes.
- The iso offset. Normally, the implicit surface for
f(x,y,z) = cwithc = 0is exported. The user may, however, specify a different iso offsetc ∈ ℝ^+_0. This is useful for implicit surfaces like the Barth Sextic that would result in infinitely thin structures (which can of course not be printed) ifc = 0is chosen. - The grid resolution. This is the discretization resolution of the implicit surface. A larger value creates qualitatively better results, but is considerably slower (runtime and space complexity of
O(n³)). - What algorithm to use for implicit surface reconstruction. Recommended to leave at the default value. SnapMC removes very thin triangles from the mesh and thus improves the numerical stability, e.g., of csg.js compared to the standard marching cubes algorithm.
API Documentation
Saving a triangle mesh of a Cindy3D scene: savecsgmesh(‹meshfilename›)
Description: Computes the union of all objects in a certain Cindy3D instance to create an output mesh. The union is computed as a triangle mesh using constructive solid geometry (CSG).
meshfilenameis the name the triangle mesh file is given for downloading. At the moment, both binary.stlfiles and.objfiles are supported as output formats.
Modifiers:
| Modifier | Parameter | Effect |
|---|---|---|
instancename |
‹string› |
The name of the Cindy3D instance to use. |
Example #1:
savecsgmesh("mesh.stl");
Example #1:
savecsgmesh("mesh.obj", instancename->"Cindy3DSecondInstance");
Saving a triangle mesh of a tube: savetubemesh(‹meshfilename›, ‹tubepoints›, ‹tuberadius›, ‹tubeclosed›)
Description: Generates the triangle mesh of a tube represented by a list of path line points and a radius and saves it in a file.
meshfilenameis the name the triangle mesh file is given for downloading. At the moment, both binary.stlfiles and.objfiles are supported as output formats.tubepointsare the tube line points, i.e., a list of points in ℝ³.tuberadiusis the radius of the tube.tubeclosedis a boolean value specifying whether the tube is closed or not (i.e., whether the curve is periodic in the start and end point). If it is not closed, caps are added at the ends.
Example (f: [0°, 360°] -> ℝ³):
// n is the number of line points used for discretization.
n = 4000;
radius = 1;
f(w) := (sin(4*w), sin(5*w), sin(6*w));
tubePoints = apply(1..n, f((#-1)/n*360°));
savetubemesh("tube.stl", tubePoints, radius, true);
Adding the print UI: addcindy3dprintui(‹meshfilename›)
Description:
Adds a printing user interface to the bottom of the website. This includes both changing print settings (like the model scale) and a print preview canvas using Cindy3D. It is expected that savecsgmesh will be used for generating print models (and not savetubemesh).
meshfilenameis the filename to use for saving the mesh when the user presses the Export mesh button.
Example:
<script id='init' type='text/x-cindyscript'>
use("Cindy3D");
use("Cindy3DPrint");
addcindy3dprintui("mesh.stl");
// ...
</script>
Adding the print UI: addcindy3dprintuitubes(‹meshfilename›, ‹computetubepointsfunction, ‹numtubepointsstring›, ‹radiusstring›, ‹tubeclosed›)
Description:
Adds a printing user interface to the bottom of the website. This includes both changing print settings (like the model scale) and a print preview canvas using Cindy3D. It is expected that savetubemesh will be used for generating print models (and not savecsgmesh}).
meshfilenameis the filename to use for saving the mesh when the user presses the Export mesh button.computetubepointsfunctionis a function that expects the number of tube points to generate as an argument, and returns the tube points.numtubepointsstringis a string encoding the number of tube points to generate.radiusstringis a string encoding the radius of the tube.tubeclosedis a boolean value specifying whether the tube is closed or not (i.e., whether the curve is periodic in the start and end point). If it is not closed, caps are added at the ends.
NOTE: numtubepointsstring and radiusstring are passed as strings and not as variable values, as this way, arbitrary computations for the number of points and the radius can be used that are variable and dependent on the program state. These arguments will be passed to savetubemesh.
Example:
<script id='init' type='text/x-cindyscript'>
use("Cindy3D");
use("Cindy3DPrint");
radius = 1;
n = 4000;
f(w) := (sin(4*w), sin(5*w), sin(6*w));
computeTubePoints(numTubePointsPrint) := (
apply(1..numTubePointsPrint, f((#-1)/numTubePointsPrint*360°))
);
addcindy3dprintuitubes("tube.stl", computeTubePoints, "n", "radius", true);
// ...
</script>
Drawing the print preview: drawprintpreview()
Description: Renders the content of the print preview canvas using the last generated triangle mesh.
NOTE: This should be called after the rendering code of the main Cindy3D instance!
Example (Cindy3D):
<script id='csmove' type='text/x-cindyscript'>
begin3d();
// ...
end3d();
drawprintpreview();
</script>
Example (CindyGL):
<script id='csdraw' type='text/x-cindyscript'>
colorplot(...);
drawprintpreview();
</script>
Updating the print preview: updatepreviewcdy3d()
Description:
The function updates the print preview added by addcindy3dprintui by generating a new mesh using the objects stored in the specified Cindy3D instance (for more details see savecsgmesh).
NOTE: A button with the caption Update preview is automatically added to the UI when calling one of the addcindy3dprintui* functions. Thus, this function doesn't need to be called by the programmer of a website.
Modifiers:
| Modifier | Parameter | Effect |
|---|---|---|
instancename |
‹string› |
The name of the Cindy3D instance to use. |
Example #1:
updatepreviewcdy3d();
Example #2:
updatepreviewcdy3d("Cindy3DSecondInstance");
Updating the print preview: updatepreviewtubes()
Description:
The function updates the print preview added by addcindy3dprintuitubes by generating a new tube mesh using the data set by addcindy3dprintuitubes.
NOTE: A button with the caption Update preview is automatically added to the UI when calling one of the addcindy3dprintui* functions. Thus, this function doesn't need to be called by the programmer of a website.
Example:
updatepreviewtubes();
Saving a triangle mesh of an iso surface: saveisomeshtofile(‹meshfilename›, ‹F›, ‹dF›, ‹isovalue›, ‹origin›, ‹dx›, ‹nx›)
Description: This function creates an approximation of the iso surface of a scalar field and saves it to a file as a triangle mesh. For this, it evaluates the function we want to compute the implicit surface of at vertices forming a Cartesian grid in 3D.
meshfilenameis the filename to use for saving/downloading the triangle mesh.Fis a three-dimensional scalar field functionF: U -> ℝ, U ⊆ ℝ^3.dFis the gradient function of the scalar function above, i.e.,dF: U -> ℝ³, dF = ∇F.isovalueis the iso value of the iso surface to construct (usually zero for the implicit surface forF(x,y,z) = 0, generallyF(x,y,z) = isovalue).originis the origin of the Cartesian grid to use for creating the iso mesh, i.e., a vector in ℝ³ representing the position of the bottom left corner of the grid.dxis the cell width in x, y, and z direction (Cartesian grid: dx = dy = dz).nxis the number of vertices in x, y and z direction (nx = ny = nz).
NOTE: The number of vertices in each dimension is larger by one than the number of cells.
Example:
// Some scalar field function.
fun(x, y, z) := (x^2 + y ^2 + z^2 - 1);
// F takes vec3 instead of 3 variables.
F(p) := (fun(p.x, p.y, p.z));
// Use central difference to approximate dF.
dF(p) := (
(F(p + [eps, 0, 0]) - F(p - [eps, 0, 0])),
(F(p + [0, eps, 0]) - F(p - [0, eps, 0])),
(F(p + [0, 0, eps]) - F(p - [0, 0, eps]))
) / (2 * eps);
saveisomeshtofile("model.stl", F, dF, 0, [-5,-5,-5], 0.01, 1001);
Saving a triangle mesh of an iso surface: saveisomeshtofile(‹meshfilename›, ‹F›, ‹dF›, ‹isovalue›, ‹radius›)
Description: A second version of the command assuming a Cartesian grid centered at (0,0,0) and standard values for the other arguments. The function creates an approximation of the iso surface of a scalar field and saves it to a file as a triangle mesh. For this, it evaluates the function we want to compute the implicit surface of at vertices forming a Cartesian grid in 3D.
meshfilenameis the filename to use for saving/downloading the triangle mesh.Fis a three-dimensional scalar field functionF: U -> ℝ, U ⊆ ℝ^3.dFis the gradient function of the scalar function above, i.e.,dF: U -> ℝ³, dF = ∇F.isovalueis the iso value of the iso surface to construct (usually zero for the implicit surface forF(x,y,z) = 0, generallyF(x,y,z) = isovalue).radiusis the extent of the Cartesian grid in x, y and z direction.
Example:
// Changed when the user is zooming in or out.
zoom = 1;
// Some scalar field function.
fun(x, y, z) := (x^2 + y ^2 + z^2 - 1);
// F takes vec3 instead of 3 variables.
F(p) := (fun(p.x, p.y, p.z));
// Use central difference to approximate dF.
dF(p) := (
(F(p + [eps, 0, 0]) - F(p - [eps, 0, 0])),
(F(p + [0, eps, 0]) - F(p - [0, eps, 0])),
(F(p + [0, 0, eps]) - F(p - [0, 0, eps]))
) / (2 * eps);
saveisomeshtofile("mesh.stl", F, dF, 0, 1/zoom);
Adding the print UI: addcindyglprintui(‹meshfilename›, ‹updatepreviewcdyglarguments›)
Description:
Adds a printing user interface to the bottom of the website. This includes both changing print settings (like the model scale) and a print preview canvas using Cindy3D. It is expected that saveisomeshtofile will be used for generating print models.
meshFilenameis the filename to use for saving the mesh when the user presses the Export mesh button.updatepreviewcdyglArgumentsare the arguments to use forupdatepreviewcdyglandsaveisomeshtofilewhen the user presses the Export mesh or Update preview buttons encoded as a string. This needs to be a string, as this way, the arguments can also contain a variable expression like1/zoomthat can't be precomputed.
Example:
<script id='init' type='text/x-cindyscript'>
use("CindyGL");
use("Cindy3D");
use("CindyGLPrint");
addcindyglprintui("isomesh.stl", "F, dF, 0, 1/zoom");
// ...
</script>
Updating the print preview: updatepreviewcdygl(‹F›, ‹dF›, ‹isovalue›, ‹origin›, ‹dx›, ‹nx›)
Description:
The function updates the print preview added by addcindyglprintui.
Fis a three-dimensional scalar field functionF: U -> ℝ, U ⊆ ℝ^3.dFis the gradient function of the scalar function above, i.e.,dF: U -> ℝ³, dF = ∇F.isovalueis the iso value of the iso surface to construct (usually zero for the implicit surface forF(x,y,z) = 0, generallyF(x,y,z) = isovalue).originis the origin of the Cartesian grid to use for creating the iso mesh, i.e., a vector in ℝ³ representing the position of the bottom left corner of the grid.dxis the cell width in x, y, and z direction (Cartesian grid: dx = dy = dz).nxis the number of vertices in x, y and z direction (nx = ny = nz).
NOTE: A button with the caption Update preview is automatically added to the UI when calling addcindyglprintui. Thus, this function doesn't need to be called by the programmer of a website.
Example:
updatepreviewcdygl(F, dF, 0, [-5,-5,-5], 0.01, 1001);
Updating the print preview: updatepreviewcdygl(‹F›, ‹dF›, ‹isovalue›, ‹radius›)
Description:
The function updates the print preview added by addcindyglprintui.
Fis a three-dimensional scalar field functionF: U -> ℝ, U ⊆ ℝ^3.dFis the gradient function of the scalar function above, i.e.,dF: U -> ℝ³, dF = ∇F.isovalueis the iso value of the iso surface to construct (usually zero for the implicit surface forF(x,y,z) = 0, generallyF(x,y,z) = isovalue).radiusis the extent of the Cartesian grid in x, y and z direction.
NOTE: A button with the caption Update preview is automatically added to the UI when calling addcindyglprintui. Thus, this function doesn't need to be called by the programmer of a website.
Example:
updatepreviewcdygl(F, dF, 0, 1/zoom);



