showcase

Parametric Constructive Solid Geometry

Define your models explicitly with an expressive and ubiquitous programming language.

Download Version 2.0

Note: this package is intended to be used with Linux & GCC.


Very Basic Examples

An intersection of two spheres.

showcase


#include "model.h"
model(example_2)
{
	intersection();
		sphere(10);
		translate(-5, 0, 0);
		sphere(10);
		translate(5, 0, 0);
	end();

	save_stl("/tmp/example_2_mesh.stl", .25, 5);    
}
	

A sphere cut by two planes, with 32 cylinders cut out of the side.

showcase


#include "model.h"

model(example_3)
{
	difference();
	
		sphere(10);
	
		plane(0, 1, 0, 5);
		plane(0, -1, 0, 5);

		for( double i = 0.0; i < 1.0; i += 1.0 / 32.0 )
		{
		cone(0.5, 0.5, 6);
		translate( sin( i * PI2 ) * 10 ,0 , cos( i * PI2 ) * 10 );
		}
		
	end();

	save_stl("/tmp/example_3_mesh.stl", .25, 5);
}
	

Getting Started

  1. Make sure the following dependencies are installed:
  2. Edit example.c or create a new file with your model code in the project root directory.
  3. Compile and run with the command make run my_model_name where my_model_name is the text inside "model()" in your file.
  4. Provided the model did not contain errors, the output file will be generated and can be examined by MeshLab or other tools.

Model Description Language and API

The programming language used to define the model is C. Running make creates an executable "./c-csg", and it's this executable that produces the output files. If you are using model files from untrusted sources it is highly recommended that you review the code before compilation.

There are a myriad of C tutorials explaining how to use for loops and so on, so if you are new to C you might find it useful to reference some of them. However, there is no need to fully understand the language to be proficient at modeling with C-CSG.

Defining a model is accomplished by using "compound objects" in combination with simple objects, often nested. For example:

union(); //this is the beginning of a compound object definition.
    sphere(10); //this is the first object contained within the compound object, a sphere with a radius of 10
    translate(0,10,0); //this translation moves the sphere 10 units on the y axis
    box(); //this object is also contained within the compound "union" object, it is a 1x1x1 box centered on the origin
    intersection(); //this is the beginning of another compound object contained within the original "union" compound object
        sphere(1);
        translate(-2,0,0); //this moves the object -2 units on the x axis
        sphere(1);
    end(); //the intersection object definition is closed
    sphere(2); //this object belongs to the original union compound object
    translate(20,0,0); //this translation is applied to the sphere
end(); //this closes the union compound object, and any objects that might follow do not become part of it.
rotate(0,PI,0); //this transformation rotates the compound object around the y axis.

All compound objects must eventually be closed by a trailing end(), objects that are specified before the end() are contained within it. Transformations can be applied to compound objects before the containing objects are added, or after being closed with end() or both.

The default world space has the y-axis up and all trigonometry and rotation functions use radians.

All *.c files in the project directory will be compiled automatically when make is run, and included in the resulting executable, so editing the makefile is not necessary.


Basic Compound Objects

intersection()

The contained objects define a solid only where they all overlap.

union()

The contained objects are grouped together as one.

difference()

The first contained object is cut by every following object.


Simple Objects

sphere(r)

r is the radius.
sphere

sphere(10);

box()

Defaults to 1x1x1 centered on the origin. Use the scale function to size.
box

box();

plane(x,y,z, d)

x,y,z defines the normal, and d the distance from the origin. Generally it should only be used in a difference to cut other objects because it is infinite.

plane(0,1,0, -0.5);

cone(r1, r2, d)

r1 and r2 define the radius of each end, and d defines the distance between the end points. The first radius is created at the origin and the second radius is created at y=d.
cone

cone(5,10,20);

sweep(point_list_len, point_list, r)

Uses a 2d polygon to define a circular object where point_list is an array of doubles of the form {x1,y1,x2,y2,x3,y3,...} and point_list_length is the element count, eg. {3,6,8,9} = 4. r is the inner radius.
sweep

double k[]={0.9,0.0, 0.9,4.5, 1.2,4.5, 1.0,1.4, 1.2,0.0};
sweep(len(k), k, 8.0);

lathe(point_list_length, point_list, distance, twists)

Uses a 2d polygon with y coordinates between 0 and 1 to define a twisting shape, eg. screw threads. Distance is the height of the shape, twists is the number of rotations that the polygon will sweep through before reaching the specified distance. Negative values for twist can be used to make counter-clockwise threads.
lathe

double m[]={3.0,1.0, 4.0,1.0, 5.0,0.5, 4.0,0.0, 3.0,0.0};
lathe(len(m),m,10,8);

extrude(point_list_length, point_list, depth)

Uses a 2d polygon and a depth value to create an extruded object.
extrude

double k[]={0.9,0, 0.9,4.5, 1.2,4.5, 4.6,1.4, 1.2,0};
extrude(len(k), k, 1.0);

extrude_quad(point_list_length, point_list, depth)

Uses a 2d polygon made from straight lines and quadratic beziers to create an extruded object. The point_list is formatted like so: x,y,control_x,control_y,x,y,control_x,control_y,x,y... The final point must equal the first, and straight lines should have their control points set to the value of QUAD_LINE. Note: the bezier curves will be clipped to the internal polygon consisting of all the points if the control point intrudes too far in upon another part of the curve.
bezier

double q[]={0.9,0.0, -2,2.2, 0.9,4.5, QUAD_LINE,QUAD_LINE, 1.2,4.5, 3.0,2.0, 2.6,1.4, QUAD_LINE,QUAD_LINE, 1.2,0.0, QUAD_LINE,QUAD_LINE, 0.9,0.0};
extrude_quad(len(q),q,4);

text(string, font, d)

Creates a group of extrude_quad objects using the specified utf8 encoded string and a path to a ttf font. Because the dimensions are variable depending on the font it is often helpful to use the dimensions function to scale it appropriately.
text

text("Hello World!","/usr/share/fonts/truetype/droid/DroidSerif-Bold.ttf",4.5);

lathe_quad(point_list_length, point_list, distance)

Uses a 2d polygon made from straight lines and quadratic beziers to create an object of the same nature as the lathe object.
lathe_q

double q[]={3.0,1.0, QUAD_LINE,QUAD_LINE, 4.0,1.0, 5.0,0.5, 4.0,0.0, QUAD_LINE,QUAD_LINE, 3.0,0.0, QUAD_LINE,QUAD_LINE, 3.0,1.0};
lathe_quad(len(q),q,8,3);

sweep_quad(point_list_length, point_list, r)

Uses a 2d polygon made from straight lines and quadratic beziers to create an object of the same nature as the sweep object.
sweep_q

double q[]={0.9,0.0, -2,2.2, 0.9,4.5, QUAD_LINE,QUAD_LINE, 1.2,4.5, 3.0,2.0, 2.6,1.4, QUAD_LINE,QUAD_LINE, 1.2,0, QUAD_LINE, QUAD_LINE, 0.9,0};
sweep_quad(len(q),q,8);

Special Compound Objects

interior(noise_function(x,y,z))

Defines a non-solid interior for the enclosed objects.
interior

#include "model.h"

int my_interior_noise(double *local)
{

    // use three octaves 
    double v0,v1,v2;

    v0 = open_simplex_noise3(osctx,  local[0] /2.0, local[1]/2.0 ,  local[2]/2.0);
    v1 = open_simplex_noise3(osctx,  local[0] / 4.0, local[1] / 4.0,  local[2] / 4.0);
    v2 = open_simplex_noise3(osctx,  local[0] / 8.0, local[1] / 8.0,  local[2] / 8.0);

    double v = v0+v1+v2;

    if(v>=-1.0 && v<=0.4) //arbitrary range is solid
        return 1;

    return 0;   
}


model(noise_test_1)
{
    interior(my_interior_noise);
        sphere(5);
        sphere(5);
        translate(5,0,0);
    end();

    save_stl("/tmp/mesh.stl", .25/6, 5);
}

exterior(noise_function(x,y,z), padding)

Moves the surface of enclosed objects according to the user defined noise function, the padding value is used to expand the bounding box and should be at least equal to the maximum deviation that the noise function applies to the object.
exterior

#include "model.h"

void my_exterior_noise(double *local)
{   
    // use three octaves 
    double v0,v1,v2;
    v0 = open_simplex_noise3(osctx,  local[0] /2.0, local[1]/2.0 ,  local[2]/2.0);
    v1 = open_simplex_noise3(osctx,  local[0] / 4.0, local[1] / 4.0,  local[2] / 4.0);
    v2 = open_simplex_noise3(osctx,  local[0] / 8.0, local[1] / 8.0,  local[2] / 8.0);

    double v = v0+v1+v2;

    if(v>=-1.0 && v<=0.4) //if the noise is in this range we move the point passed to us
    {
        local[0]+=v;
        local[1]+=v;
        local[2]+=v;
    }
}


model(noise_test_2)
{

    exterior(my_exterior_noise, 1.0);
        sphere(5);
        sphere(5);
        translate(5,0,0);
    end();

    save_stl("/tmp/mesh.stl", .25/6, 5);
}

Transformation Functions

scale(x,y,z)

Scale current object.


translate(x,y,z)

Move current object.


identity()

Reset transforms for current object. Can be used to remove transformations from a previously copied object.

paste(o);
identity(); //scaling, and rotation of copied object are now clear

rotate(x,y,z)

Rotate current object by x, y, and z in that order.


mirror_x()

Mirror current object. The same thing can be accomplished using scale(x,y,z) with negative values .


mirror_y()

Mirror current object.


mirror_z()

Mirror current object.


rotate_arbitrary(a, x,y,z)

Rotate an object by angle a along an arbitrary axis defined by x,y,z.


Other Functions

flip_world_axis(x,y,z)

If an integer 1 is set for x, y, or z the axis will be inverted.


world_z_up()

Changes from a y-up world to a z-up world. Useful to set just prior to exporting an STL file.


end()

This function is required to follow a compound object to indicate the end of the enclosed objects.


dimensions(*cx, *cy, *cz)

Changes the values in the variables pointed to by cx,cy,cz to the width, height, and depth of the bounding box, generally useful for resizing text and aligning objects without prior knowledge of their general size.

double plate_cx = 60.0;
double plate_cz = 25.0;
double text_border = 5.0;
double text_cx,text_cy,text_cz;

difference();

    box();
    translate(0,-0.5,0);
    scale(plate_cx,4,plate_cz); 

    text("C-CSG","/usr/share/fonts/truetype/droid/DroidSerif-Bold.ttf",4.5);

    //center text on plate
    dimensions(&text_cx,&text_cy,&text_cz);         
    translate(-text_cx/2.0,-4,-text_cz/2.0); //center text on zero  
    scale((plate_cx-(text_border*2.0))/text_cx,1,(plate_cz-(text_border*2.0))/text_cz);  //scale to correct size to fill plate minus the border padding

end();

cut()

Removes the current object and children from the model and returns it.

intersection();
    sphere(10);
    translate(-5,0,0);
    sphere(10);
    translate(5,0,0);
    sphere(10);
    translate(0,0,-5);
    sphere(10);
    translate(0,0,5);
end();
OBJECT my_var = cut(); //removes current object (the intersection and contents) from the model and stores it in the variable "my_var".

dup()

Duplicates the current object, with its existing transform.


    sphere(10);
    translate(5,0,0);
    dup();    
    translate(-10,0,0); //two spheres, one is at x=5 and one is at x=-5

paste(OBJECT)

Puts a copy of the stored OBJECT into the model.

copypaste

intersection();
    sphere(10);
    translate(-5,0,0);
    sphere(10);
    translate(5,0,0);
    sphere(10);
    translate(0,0,-5);
    sphere(10);
    translate(0,0,5);
end();

OBJECT my_var = cut();

difference();
    sphere(10);
    paste(my_var);
    translate(0,5,0);
    paste(my_var);
    translate(0,-5,0);
end();

save_stl(filename,resolution,threads)

Exports the model as a triangle mesh using the specified number of CPU threads and the specified resolution (0.3 is a good starting point if you're working in mm for 3d printing).