AngelScript CSG

Download AngelScript CSG version V2.0-02, with IDE included
Windows 64bit here.
Linux (K)ubuntu 15.10 64bit here.

Constructive Solid geometry (CSG) is a form of 3d modelling where you assemble simple 3d primitives into a hierarchy to make interesting 3d objects.  This technique is much used e.g. in 3d printing.

 

The key to CSG modelling is using 3d primitives like cylinder, sphere, cube … and for each of them apply 3d transformations like translate, rotate and scale. Finally, the transformed pieces are assembled into a CSG tree using 3d operations like union, difference and intersection. The result is a (hopefully) interesting 3d object. Wikipedia has a nice example:

A CSG assembly (from Wikipedia)

OpenSCAD

A popular open source program for CSG modelling with focus on 3d Printing is OpenSCAD. It is different from other CAD software in that it does not provide a complicated graphical user interface for modelling, but instead provides its own language for describing models. Below is a very simple example of OpenSCAD code for defining a simple “lollipop”, i.e. an object consisting of a stick to hold it and the head with the sweet:

OpenSCAD code below

// CSG modelling with OpenSCAD.  

// Carsten Arnholm, 26. December 2015

 

module lollipop(height, radius, head_radius )

{

   // use cylinder and sphere primitives and

   // assemble as a union.

   union() {

      cylinder(height,r=radius);

      translate([0,0,height])

      sphere(head_radius);

   }

   // The union is implicitly returned

}

// model parameters

height       = 200;

radius       = 5;

head_radius  = 20;

 

// assemble and rotate

rotate([0,30,0])

lollipop(height, radius, head_radius );

 

// export needs to be done from the OpenSCAD GUI
//
File → Export ...

 

The resulting model becomes:

This is all very nice. There are a couple of things to observe, however. One is that The OpenSCAD language is static, i.e. you cannot change the values of the “variables” in the program, they are just declarations of named values. Another is that the models created cannot be assigned to variables and reused via them. The only means of abstraction available are functions (called “modules”) that implicitly return the model created within them. Also, the language is unique to OpenSCAD, and there seems to be significant resources put into developing this language in the OpenSCAD project.

Possible changes

This author is of the opinion that the excellent idea of OpenSCAD as a language oriented CSG modeller would benefit from a couple of (admittedly major) changes:

  • Use an existing scripting language instead of a self made language. This would enable focusing on the modelling features rather than the language syntax. For example, the somewhat  incomprehensible “list comprehensions is an example of obscure language syntax.

  • Use a more complete geometry/topology kernel such as for example OpenCascade instead of the facet-based and very resource-demanding CGAL. This could also simplify the language syntax by doing away with the “convexity” parameters that are currently seen in OpenSCAD, they are really irrelevant from a modelling point of view.

These are clearly radical suggestions that will probably never be implemented in OpenSCAD. This does not, however, stop one from experiementing with such ideas. The focus of this post is the language (#2 in the list is left for some other time). Could we use an existing scripting language to implement similar functionality as is currently in OpenSCAD?

AngelScript

I have been playing with the idea of using AngelScript as a language interpreter for CSG modelling. AngelScript has some very nice features for end users and is designed for integration into a C++ application. The language provides pretty much everything in the current OpenSCAD language, plus much more

  • It is not just a declarative language, variables can be modified

  • The script writer can, if needed write functions and even classes.

  • The language is strongly typed

  • and much more.


As an experiment a CSG interpreter prototype using AngelScript has been implemented. As with any real prototype, the idea is not to make a final application, just explore the possibilities. This is throw-away work, with the sole intention of learning. Also, since this prototype does not address #2 in the list, the prototype simply exports to the OpenSCAD .csg file format for visualisation (The OpenSCAD .csg file format is not the same as the OpenSCAD language, but it is still dependent on it).

So let us try the “lollipop” example in the prototype AngelScript CSG interpreter:

AngelScript CSG code below

// CSG modelling with AngelScript
//
Carsten Arnholm, 25. December 2015

// a global constant + a function to convert from degrees to radians

const double pi = 4.0*atan(1.0);        

double rad(double deg) { return pi*deg/180.0; }

 

csg_solid@ lollipop(double height, double radius, double head_radius )

{

   // use cylinder and sphere primitives and

   // assemble as a union. We choose to keep variables here.

   csg_cylinder@ stick = csg_cylinder(height,radius);

   csg_solid@ head     = csg_sphere(head_radius);

   return csg_union( array<csg_solid@> =

                    { stick, translate(0,0,stick.height())*head }

                   );

}

 

void main()

{

    // model parameters

    double height       = 200;

    double radius       = 5;

    double head_radius  = 20;       

        

    // assemble and rotate

    csg_solid@ model = rotate_y(rad(30)) *

                       lollipop(height,radius,head_radius);

 

    // export to OpenSCAD-style .csg for display

    model.write_csg("lollipop.csg");

}

 

The generated CSG file “lollipop.csg” :

Generated OpenSCAD-style CSG file (generated from AngelScript CSG)

group() {

   multmatrix([[0.866025396499,0,0.500000012618,0],[0,1,0,0],[-0.500000012618,0,0.866025396499,0],[0,0,0,1]]) {

      union() {

         multmatrix([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]) {

            cylinder( r1=5, r2=5, h=200 );

         }

         multmatrix([[1,0,0,0],[0,1,0,0],[0,0,1,200],[0,0,0,1]]) {

            sphere( r=20 );

         }

      }

   }

}

 

The generated CSG file  displayed in OpenSCAD:

As can be seen, the result is the same in both cases, and it makes it possible to evaluate the syntax similarities and differences. For an example as small as this, the AngelScript code does not appear to offer much benefit, but the code illustrates some important differences.

Basic construction and type safety of 3d primitives

First, observe that parts can be referred to in variables, like this

csg_cylinder@ stick = csg_cylinder(height,radius);

 double h = stick.height();   // Ok

The “stick” is a variable of type csg_cylinder, which allows you to do things like extracting data from it in code later, such as reading the cylinder height. The following code is also allowed:

csg_solid@ stick_solid = csg_cylinder(height,radius);

Because every csg_cylinder is also a csg_solid, it can be received as such. You are however, no longer allowed to call the height() function in this case:

       double h = stick_solid.height();    // error, slick_solid is not a csg_cylinder


… unless you typecast the stick_solid variable back to a csg_cylinder type:

csg_cylinder@ stick = cast<csg_cylinder@>(stick_solid);
     
double h = stick.height();   // Ok


The use of the character @ behind the types means that the variable is a handle. All of the CSG objects in the prototype are handle types and is used with the @ character.

3d transformations
Transformations are handled with a syntax familiar to OpenSCAD-users, except an explicit  multiplication operator is used  to indicate the coordinate system transformation from the object being transformed to the coordinate system of the result:

      csg_solid@ head = translate(0,0,stick.height()) * csg_sphere(head_radius);

 

Transformations can also be stored in variables:

tmatrix@ transform = rotate_y(pi/2) * translate(1,0,0);

 

Angelscript CSG vs OpenSCAD

Thefollowing is a summary of the CSG related commands

Angelscript CSG

OpenSCAD

3d primitives

csg_solid   (abstract type)

N/A

csg_cone(double h, double r);

cylinder(h,r1,r2);

csg_cube(double size);

cube(size);

csg_cuboid(double dx, double dy, double dz);

cube([width,depth,height]);

csg_cylinder(double h, double r);

cylinder(h,r);

csg_sphere(double r);

sphere(r):

3d transformations

tmatrix   (abstract type)

multimatrix([ …. ]);

rotate_x(double rx);

rotate([rx,0,0]);

rotate_y(double ry);

rotate([0,ry,0]);

rotate_z(double rz);

rotate([0,0,rz]);

scale(double sx, double sy, double sz);

scale([x,y,z]);

translate(double dx, double dy, double dz);

translate([dx,dy,dz]);

Boolean operations

csg_difference(csg_solid@ incl, csg_solid@  excl);
(array<csg_solid@>@ also accepted)

difference() { ... }

csg_intersection(csg_solid@ incl, csg_solid@  excl);
(array<csg_solid@>@ also accepted)

intersection() { ... }

csg_union(array<csg_solid@>@+ arr);

union() { ... }

 

Math functions

Angelscript

http://www.angelcode.com/angelscript/sdk/docs/manual/doc_addon_math.html

OpenSCAD

https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Mathematical_Functions

 

Other language features

Angelscript

http://www.angelcode.com/angelscript/sdk/docs/manual/doc_script.html

OpenSCAD

https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language

 

Conclusion

For non-trivial models, it looks like using a language like AngelScript is indeed interesting. The Angelscript CSG prototype shows it is possible to express CSG models that have similar characteristics as OpenSCAD, but using an existing object oriented scripting language. It also shows that using objects will allow more flexible and powerful expressions, since the object properties may be retrieved from the objects directly (like in the example with the cylinder height).

 

To implement such features in AngelScript is reasonably straight forward. All you have to do is write C++ classes for the different concepts and expose constructors and member functions to those classes in AngelScript by means of the AngelScript API. The current prototype implementation is not 100% stable, for some cases there are issues with using the built-in array type (see for example its use in csg_union). However, such issues can always be overcome with a bit of effort.

 

All in all, the prototype appears successful in that it demontrated how a language like AngelScript can be used for CSG modelling.

 

Octave plug-in calling MSVC

This post is about creating plugins to the Windows version of GNU Octave 4.0.0 (Octave) using existing components created with Microsoft Visual Studio 2013 (MSVC). It takes too long to explain why this is sometimes useful, just assume that it is. A typical scenario could be that you have some kind of database accessible from MSVC code and you want to expose the data in Octave.

The figure below illustrates a possible setup. To implement a plugin in Octave, you write a piece of C++ code and compile/link it into a special kind of shared library referred to as an 'oct-file'. Such code can call other components, for example a DLL (Dynamic Link Library) created with MSVC. This way, the oct-file functions as the glue between the Octave application and some other software component.


T
his sounds simple enough, but in practice there are a couple of things to handle to make it work:

First, Octave and its oct-files are compiled using the MinGW GNU g++ C++ compiler (It is not practical to recompile Octave using MSVC) and GNU g++ code is not binary compatible with MSVC code. Therefore, we cannot statically link the MSVC dll with the oct-file and we cannot pass C++ objects in the calls between them, because name mangling schemes and calling conventions are incompatible between the compilers.

Second, Octave and its oct-files are compiled as 32bit. Even if we find a way around the first problem, it will not work if the MSVC component is compiled as 64bit, it has to be 32bit as Octave.

Now the whole thing sounds a lot more complicated, but the problem description also provides the clues to the solution

  • Use a C interface in the calls from the oct-file (GNU g++) to the MSVC code

  • Load the MSVC dll dynamically instead of linking statically

  • Compile the MSVC code as 32 bit

MSVC C++ code

The first bullet above means we must provide global functions declared as extern ”C” in the MSVC C++ code (linked as DLL, and exported), here is a sample header declaration:

#ifndef MSVC_COMP_H
#define MSVC_COMP_H

#ifdef MSVC_COMP_IMPLEMENTATION

  #define MSVC_COMP_PUBLIC __declspec(dllexport)

#else

  #define MSVC_COMP_PUBLIC __declspec(dllimport)

#endif

 

extern "C" {

   // msvc_get_data returns a pointer to internal data, must not be deleted outside

   MSVC_COMP_PUBLIC double* msvc_get_data(const char* file_path, const char* data_id, long* nsamp);

}

 

#endif // MSVC_COMP_H

Note that the function takes and returns parameters as if it was an old style C-function, no C++ objects or pointers are allowed, du to the compiler differences. The extern ”C” statement removes any name mangling in the compiled name, it makes it possible to look up from other code (see below).

Notice also that in this case, the function returns a pointer to some numerical data. Such data may be dynamically allocated by the MSVC compiler, and cannot be deleted in the GNU code (in this case the pointer ponts to a global variable internally in the MSVC code and it will be cleaned up in the next call).

The above is just the header file declaration, the implementation of the msvc_get_data function can use all C++ constructs, objects, pointers etc.  The msvc_get_data function is thus just an adapter.

OCT-file code

The second bullet in our list, and how the OCT-file interfaces the MSVC code is best illustrated using an example (note that most error checking has been omitted for clarity).

File oct_get_data.cpp:

#include <octave/oct.h>

#include <fstream>

#include <windows.h>

#include <dMatrix.h>

 

// function pointer to the MSVC fuunction

typedef double* (*msvc_gdfunc)(const char* file_path, const char* data_id, long* nsamp);

 

// Octave function declaration

DEFUN_DLD (oct_get_data, args, nargout, "oct_get_data String")

{

   int nargin = args.length ();

   if(nargin < 2) {

      octave_stdout << "oct_get_data called with "  

                    << nargin << " input and "

                    << nargout << " output arguments.\n";

      octave_stdout << "Usage:  oct_get_data(file_path,data_id); \n";

   }

 

   // get the arguments from the Octave call

   int iarg=0;

   std::string file_path   = args(iarg++).char_matrix_value().row_as_string(0);

   std::string data_id     = args(iarg++).char_matrix_value().row_as_string(0);

 

   // load the MSVC DLL dynamically using Windows API

   HMODULE hdll = LoadLibrary("msvc_comp.dll");

   if(hdll != NULL) {

      // get the function pointer

      msvc_gdfunc msvc_get_data = (msvc_gdfunc)GetProcAddress(hdll,"msvc_get_data");

      if(msvc_get_data) {                 

         // call the function in the MSVC dll

         long nsamp = 0;

         if(double* values = msvc_get_data(file_path.c_str(),data_id.c_str(),&nsamp)) {

            // assign output data, it returns data column major order, 2 columns

            Matrix result(nsamp,2);

            for(int j=0;j<2;j++) {

               for(int i=0; i<nsamp; i++) {

                  result(i,j) = *values++;

               }

            }

            return octave_value(result);

         }

      }

   }

   // empty return value

   return octave_value_list();

}

The code above declares a function pointer type for our MSVC function. It then uses the Windows API to load the DLL dynamically + look up the pointer to the function. If found, it calls the MSVC function and constructs a suitable matrix object to return to the Octave application.

Obviously much more rigorous error checking is in order.

Compiling the OCT-file

As previously mentioned, the oct-file must be compiled using the GNU C++ compiler to be compatible with Octave. For this purpose we use the mkoctfile utility. It can be done within Octave, or via a Wiindows batch script as shown below.

File cppoct.bat:

@echo off

REM Script to compile C++ into Octave oct-files

REM

REM configure Octave oct compiler

set OCT_VER=4.0.0

set OCT_HOME=C:\Octave\Octave-%OCT_VER%

set OCT_BIN=%OCT_HOME%\bin

set OCT_INC=%OCT_HOME%\include\octave-%OCT_VER%

set OCT_LIB=%OCT_HOME%\lib\octave\%OCT_VER%

REM

REM set Octave bin dir first in path so g++ can be found

set PATH=%OCT_BIN%;%PATH%

REM

REM turn on echo so we can see what is going on as we compile

@echo on

%OCT_BIN%\mkoctfile.exe -I%OCT_INC% -I%OCT_INC%\octave  -L%OCT_LIB%  %1

@echo off

REM tidy up intermediate files

del *.o

Running this script for the oct_get_data.cpp file generates the oct_get_data.oct file.

Using the plugin in Octave

Once the oct-file and the msvc dll exist, it is recommended to store them in a common folder in the file system. In Octave, you then need to specify that folder using 'addpath'.

addpath("C:\\somefolder");
data = oct_get_data("myfile.dat","whatever");
plot( data(:,1), data(:,2));

Assuming the data returned was a matrix of X,Y data, the result could look something like below

 

 

If you find this useful, please add a comment below :-)