← Back to team overview

kicad-developers team mailing list archive

patch - improvement to VRML export

 

Hi folks,

 Attached is a patch to improve the VRML export; this was generated against Rev 4589. I apologize in advance for the size of the patch -- 2 new files + major rewrite of export_vrml.cpp, total of ~4K lines in the patch, many of which are deletions from export_vrml.cpp.

features:

1. there is an extruded PCB; this will be useful for those people who translate VRML--STL--STEP

2. arcs and circles are rendered according to mechanical tolerance criteria; this is useful for people who currently use the VRML model to derive a mechanical model

Testing: I rendered the demo 'video' board.  There are a few visual glitches in *some* rendered items (a few pads ) but nothing serious. The extruded board is good.  I also rendered the 'pic_programmer' demo; once again there were some visual glitches with the rendering of *some* resistor silk outlines but no show stoppers.

Proposed future work (which I will do if people want the features):

1. make changes to the exporter's GUI to control the rendering of tracks and vias; after all people who want to check mechanical fit don't want to see such features on the board

2. make changes to the GUI to add mechanical feature controls, primarily for the rendering of arcs and circles. However, this will not apply to the board outline since the routines use the outline provided by specctra_export.

3. improve eyecandy with plated holes, shiny pads, and more realistic coloring for the PCB in general

4. improve eyecandy with filled regions

5. provide an option to include all module VRML data within the exported file

I hope to provide a VRML exporter which can create great eyecandy for publicity material and which also provides models which are of value for people who use VRML to create mechanical models.

- Cirilo
=== modified file 'pcbnew/CMakeLists.txt'
--- pcbnew/CMakeLists.txt	2013-12-30 00:36:57 +0000
+++ pcbnew/CMakeLists.txt	2013-12-31 03:07:07 +0000
@@ -174,6 +174,7 @@
     export_d356.cpp
     export_gencad.cpp
     export_vrml.cpp
+    vrml_board.cpp
     files.cpp
     gen_drill_report_files.cpp
     gen_modules_placefile.cpp

=== modified file 'pcbnew/export_vrml.cpp'
--- pcbnew/export_vrml.cpp	2013-05-05 07:17:48 +0000
+++ pcbnew/export_vrml.cpp	2013-12-31 01:42:38 +0000
@@ -22,6 +22,42 @@
  * or you may write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
+
+/*
+ * NOTE:
+ * 1. pads can be put on separate layers so that they can be
+ *      rendered in silver. The trick is to put a pad-sized hole
+ *      in the appropriate copper layer to cut back the track.
+ * 2. for improved looks, create a DRILL layer for PTH drills.
+ *      To render the improved board, render the vertical outline only
+ *      for the board (no added drill holes), then render the
+ *      outline only for PTH, and finally render the top and bottom
+ *      of the board. NOTE: if we don't want extra eye-candy then
+ *      we must maintain the current board export.
+ *      Additional bits needed for improved eyecandy:
+ *      + CalcOutline: calculates only the outline of a VRML_LAYER or
+ *          a VERTICAL_HOLES
+ *      + WriteVerticalIndices: writes the indices of only the vertical
+ *          facets of a VRML_LAYER or a VRML_HOLES.
+ *      + WriteVerticalVertices: writes only the outline vertices to
+ *          form vertical walls; applies to VRML_LAYER and VRML_HOLES
+ *
+ * For mechanical correctness, we should use the following settings with arcs:
+ * 1. max. deviation:  the number of edges should be determined by the max.
+ *      mechanical deviation and the minimum number of edges shall be 6.
+ * 2. for very large features we may introduce too many edges in a circle;
+ *      to control this, we should specify a MAX number of edges or a threshold
+ *      radius and a deviation for larger features
+ *
+ * For example, many mechanical fits are to within +/-0.05mm, so specifying
+ *      a max. deviation of 0.02mm will yield a hole near the max. material
+ *      condition. Calculating sides for a 10mm radius hole will yield about
+ *      312 points; such large holes (and arcs) will typically have a specified
+ *      tolerance of +/-0.2mm in which case we can set the MAX edges to 32
+ *      provided none of the important holes requires > 32 edges.
+ *
+ */
+
 #include <fctsys.h>
 #include <kicad_string.h>
 #include <wxPcbStruct.h>
@@ -44,183 +80,113 @@
 
 #include <vector>
 #include <cmath>
-
-// Number of segments to approximate a circle per segments:
-#define SEGM_COUNT_PER_360 32
-// basic angle to approximate a circle per segments
-static const double INC_ANGLE = M_PI*2 / SEGM_COUNT_PER_360;
+#include <vrml_board.h>
 
 /* helper function:
  * some characters cannot be used in names,
  * this function change them to "_"
  */
-static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal );
-
-// I use this a lot...
-static const double PI2 = M_PI / 2;
-
-struct POINT_3D
-{
-    double x, y, z;
-};
-
-struct POINT_2D
-{
-    POINT_2D( double _x = 0, double _y = 0 ) : x( _x ), y( _y )
-    { }
-    double x, y;
-};
-
-// Absolutely not optimized triangle bag :D
-struct TRIANGLE
-{
-    TRIANGLE( double x1, double y1, double z1,
-              double x2, double y2, double z2,
-              double x3, double y3, double z3 )
-    {
-        p1.x = x1; p1.y = y1; p1.z = z1;
-        p2.x = x2; p2.y = y2; p2.z = z2;
-        p3.x = x3; p3.y = y3; p3.z = z3;
-    }
-    TRIANGLE() { }
-    POINT_3D p1, p2, p3;
-};
-typedef std::vector<TRIANGLE> TRIANGLEBAG;
-
-// A flat triangle fan
-struct FLAT_FAN
-{
-    POINT_2D              c;
-    std::vector<POINT_2D> pts;
-    void                add( double x, double y )
-    {
-        pts.push_back( POINT_2D( x, y ) );
-    }
-    void bag( LAYER_NUM layer, bool close = true );
-};
-
-// A flat quad ring
-struct FLAT_RING
-{
-    std::vector<POINT_2D> inner;
-    std::vector<POINT_2D> outer;
-    void                add_inner( double x, double y )
-    {
-        inner.push_back( POINT_2D( x, y ) );
-    }
-
-    void add_outer( double x, double y )
-    {
-        outer.push_back( POINT_2D( x, y ) );
-    }
-
-    void bag( LAYER_NUM layer, bool close = true );
-};
-
-// A vertical quad loop
-struct VLoop
-{
-    std::vector<POINT_2D> pts;
-    double              z_top, z_bottom;
-    void                add( double x, double y )
-    {
-        pts.push_back( POINT_2D( x, y ) );
-    }
-
-    void bag( TRIANGLEBAG& triangles, bool close = true );
-};
-
-// The bags for all the layers
-static TRIANGLEBAG layer_triangles[NB_LAYERS];
-static TRIANGLEBAG via_triangles[4];
-static double      layer_z[NB_LAYERS];
-
-static void bag_flat_triangle( LAYER_NUM layer, //{{{
-                               double x1, double y1,
-                               double x2, double y2,
-                               double x3, double y3 )
-{
-    double z = layer_z[layer];
-
-    layer_triangles[layer].push_back( TRIANGLE( x1, y1, z, x2, y2, z, x3, y3, z ) );
-}
-
-
-void FLAT_FAN::bag( LAYER_NUM layer, bool close ) //{{{
-{
-    unsigned i;
-
-    for( i = 0; i < pts.size() - 1; i++ )
-        bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y );
-
-    if( close )
-        bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[0].x, pts[0].y );
-}
-
-
-static void bag_flat_quad( LAYER_NUM layer, //{{{
-                           double x1, double y1,
-                           double x2, double y2,
-                           double x3, double y3,
-                           double x4, double y4 )
-{
-    bag_flat_triangle( layer, x1, y1, x3, y3, x2, y2 );
-    bag_flat_triangle( layer, x2, y2, x3, y3, x4, y4 );
-}
-
-
-void FLAT_RING::bag( LAYER_NUM layer, bool close ) //{{{
-{
-    unsigned i;
-
-    for( i = 0; i < inner.size() - 1; i++ )
-        bag_flat_quad( layer,
-                       inner[i].x, inner[i].y,
-                       outer[i].x, outer[i].y,
-                       inner[i + 1].x, inner[i + 1].y,
-                       outer[i + 1].x, outer[i + 1].y );
-
-    if( close )
-        bag_flat_quad( layer,
-                       inner[i].x, inner[i].y,
-                       outer[i].x, outer[i].y,
-                       inner[0].x, inner[0].y,
-                       outer[0].x, outer[0].y );
-}
-
-
-static void bag_vquad( TRIANGLEBAG& triangles, //{{{
-                       double x1, double y1, double x2, double y2,
-                       double z1, double z2 )
-{
-    triangles.push_back( TRIANGLE( x1, y1, z1,
-                                   x2, y2, z1,
-                                   x2, y2, z2 ) );
-    triangles.push_back( TRIANGLE( x1, y1, z1,
-                                   x2, y2, z2,
-                                   x1, y1, z2 ) );
-}
-
-
-void VLoop::bag( TRIANGLEBAG& triangles, bool close ) //{{{
-{
-    unsigned i;
-
-    for( i = 0; i < pts.size() - 1; i++ )
-        bag_vquad( triangles, pts[i].x, pts[i].y,
-                   pts[i + 1].x, pts[i + 1].y,
-                   z_top, z_bottom );
-
-    if( close )
-        bag_vquad( triangles, pts[i].x, pts[i].y,
-                   pts[0].x, pts[0].y,
-                   z_top, z_bottom );
-}
-
-
-static void write_triangle_bag( FILE* output_file, int color_index, //{{{
-                                const TRIANGLEBAG& triangles,
-                                double boardIU2WRML )
+static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal );
+
+
+class MODEL_VRML
+{
+private:
+
+    double layer_z[NB_LAYERS];
+
+public:
+    VRML_HOLES  holes;
+    VRML_LAYER  board;
+    VRML_LAYER  top_copper;
+    VRML_LAYER  bot_copper;
+    VRML_LAYER  top_silk;
+    VRML_LAYER  bot_silk;
+
+    double scale;           // board internal units to output scaling
+
+    double  tx;             // global translation along X
+    double  ty;             // global translation along Y
+
+    double board_thickness; // depth of the PCB
+
+    LAYER_NUM s_text_layer;
+    int s_text_width;
+
+    MODEL_VRML()
+    {
+        for( int i = 0; i < NB_LAYERS; ++i )
+            layer_z[i] = 0;
+
+        // this default only makes sense if the output is in mm
+        board_thickness = 1.6;
+    }
+
+    void SetOffset( double aXoff, double aYoff )
+    {
+        tx  = aXoff;
+        ty  = aYoff;
+    }
+
+    double GetLayerZ( LAYER_NUM aLayer )
+    {
+        if( aLayer >= NB_LAYERS )
+        {
+            fprintf( stderr, "BLAH!\n" );
+            return 0;
+        }
+
+        return layer_z[ aLayer ];
+    }
+
+    void SetLayerZ( LAYER_NUM aLayer, double aValue )
+    {
+        layer_z[aLayer] = aValue;
+    }
+};
+
+
+// static var. for dealing with text
+namespace VRMLEXPORT
+{
+    static MODEL_VRML* model_vrml;
+    bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer );
+}
+
+
+// select the VRML layer object to draw on; return true if
+// a layer has been selected.
+bool VRMLEXPORT::GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer )
+{
+    switch( layer )
+    {
+    case FIRST_COPPER_LAYER:
+        *vlayer = &aModel.bot_copper;
+        break;
+
+    case LAST_COPPER_LAYER:
+        *vlayer = &aModel.top_copper;
+        break;
+
+    case SILKSCREEN_N_BACK:
+        *vlayer = &aModel.bot_silk;
+        break;
+
+    case SILKSCREEN_N_FRONT:
+        *vlayer = &aModel.top_silk;
+        break;
+
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+
+static void write_triangle_bag( FILE* output_file, int color_index,
+        VRML_LAYER* layer, bool plane, bool top,
+        double top_z, double bottom_z )
 {
     /* A lot of nodes are not required, but blender sometimes chokes
      * without them */
@@ -233,9 +199,9 @@
         "        Shape {\n",
         "          appearance Appearance {\n",
         "            material Material {\n",
-        0,                                          // Material marker
-        "              ambientIntensity 0.8\n",
-        "              transparency 0.2\n",
+        0,                                      // Material marker
+        "              ambientIntensity 1\n",
+        "              transparency 0\n",
         "              shininess 0.2\n",
         "            }\n",
         "          }\n",
@@ -243,11 +209,11 @@
         "            solid TRUE\n",
         "            coord Coordinate {\n",
         "              point [\n",
-        0,                                          // Coordinates marker
+        0,                                      // Coordinates marker
         "              ]\n",
         "            }\n",
         "            coordIndex [\n",
-        0,                                          // Index marker
+        0,                                      // Index marker
         "            ]\n",
         "          }\n",
         "        }\n",
@@ -255,7 +221,7 @@
         "    }\n",
         "  ]\n",
         "}\n",
-        0 // End marker
+        0    // End marker
     };
 
     int marker_found = 0, lineno = 0;
@@ -270,59 +236,41 @@
 
             switch( marker_found )
             {
-            case 1: // Material marker
-                fprintf( output_file,
-                         "              diffuseColor %g %g %g\n",
-                         (double) g_ColorRefs[color_index].m_Red / 255.0,
-                         (double) g_ColorRefs[color_index].m_Green / 255.0,
-                         (double) g_ColorRefs[color_index].m_Blue / 255.0 );
-                fprintf( output_file,
-                         "              specularColor %g %g %g\n",
-                         (double) g_ColorRefs[color_index].m_Red / 255.0,
-                         (double) g_ColorRefs[color_index].m_Green / 255.0,
-                         (double) g_ColorRefs[color_index].m_Blue / 255.0 );
-                fprintf( output_file,
-                         "              emissiveColor %g %g %g\n",
-                         (double) g_ColorRefs[color_index].m_Red / 255.0,
-                         (double) g_ColorRefs[color_index].m_Green / 255.0,
-                         (double) g_ColorRefs[color_index].m_Blue / 255.0 );
+            case 1:    // Material marker
+                fprintf( output_file,
+                        "              diffuseColor %g %g %g\n",
+                        (double) g_ColorRefs[color_index].m_Red / 255.0,
+                        (double) g_ColorRefs[color_index].m_Green / 255.0,
+                        (double) g_ColorRefs[color_index].m_Blue / 255.0 );
+                fprintf( output_file,
+                        "              specularColor %g %g %g\n",
+                        (double) g_ColorRefs[color_index].m_Red / 255.0,
+                        (double) g_ColorRefs[color_index].m_Green / 255.0,
+                        (double) g_ColorRefs[color_index].m_Blue / 255.0 );
+                fprintf( output_file,
+                        "              emissiveColor %g %g %g\n",
+                        (double) g_ColorRefs[color_index].m_Red / 255.0,
+                        (double) g_ColorRefs[color_index].m_Green / 255.0,
+                        (double) g_ColorRefs[color_index].m_Blue / 255.0 );
                 break;
 
             case 2:
-            {
-                // Coordinates marker
-                for( TRIANGLEBAG::const_iterator i = triangles.begin();
-                     i != triangles.end();
-                     i++ )
-                {
-                    fprintf( output_file, "%.8g %.8g %.8g\n",
-                             i->p1.x * boardIU2WRML, -i->p1.y * boardIU2WRML,
-                             i->p1.z * boardIU2WRML );
-                    fprintf( output_file, "%.8g %.8g %.8g\n",
-                             i->p2.x * boardIU2WRML, -i->p2.y * boardIU2WRML,
-                             i->p2.z * boardIU2WRML );
-                    fprintf( output_file, "%.8g %.8g %.8g\n",
-                             i->p3.x * boardIU2WRML, -i->p3.y * boardIU2WRML,
-                             i->p3.z * boardIU2WRML );
-                }
-            }
-            break;
+
+                if( plane )
+                    layer->WriteVertices( top_z, output_file );
+                else
+                    layer->Write3DVertices( top_z, bottom_z, output_file );
+
+                break;
 
             case 3:
-            {
-                // Index marker
-                // OK, that's sick ...
-                int j = 0;
-
-                for( TRIANGLEBAG::const_iterator i = triangles.begin();
-                     i != triangles.end();
-                     i++ )
-                {
-                    fprintf( output_file, "%d %d %d -1\n", j, j + 1, j + 2 );
-                    j += 3;
-                }
-            }
-            break;
+
+                if( plane )
+                    layer->WriteIndices( top, output_file );
+                else
+                    layer->Write3DIndices( output_file );
+
+                break;
 
             default:
                 break;
@@ -334,344 +282,210 @@
 }
 
 
-static void compute_layer_Zs( BOARD* pcb ) //{{{
-{
-    int    copper_layers = pcb->GetCopperLayerCount( );
+static void write_layers( MODEL_VRML& aModel, FILE* output_file, BOARD* aPcb )
+{
+    // notes:
+    // 1. extract color index for a layer via:
+    // int color_index = pcb->GetLayerColor( layer )
+    // 2. forget about the KiCad layer colors since they were chosen for
+    // the graphical design environment and not for eyecandy output
+
+    // VRML_LAYER board;
+    aModel.board.Tesselate3D( &aModel.holes );
+    write_triangle_bag( output_file, aPcb->GetLayerColor( EDGE_N ),
+            &aModel.board, false, false,
+            aModel.board_thickness / 2.04,
+            -aModel.board_thickness / 2.04 );
+
+    // VRML_LAYER top_copper;
+    aModel.top_copper.Tesselate( &aModel.holes );
+    write_triangle_bag( output_file, aPcb->GetLayerColor( LAST_COPPER_LAYER ),
+            &aModel.top_copper, true, true,
+            aModel.GetLayerZ( LAST_COPPER_LAYER ), 0 );
+
+    // VRML_LAYER bot_copper;
+    aModel.bot_copper.Tesselate( &aModel.holes );
+    write_triangle_bag( output_file, aPcb->GetLayerColor( FIRST_COPPER_LAYER ),
+            &aModel.bot_copper, true, false,
+            aModel.GetLayerZ( FIRST_COPPER_LAYER ), 0 );
+
+    // VRML_LAYER top_silk;
+    aModel.top_silk.Tesselate( &aModel.holes );
+    write_triangle_bag( output_file, aPcb->GetLayerColor( SILKSCREEN_N_FRONT ),
+            &aModel.top_silk, true, true,
+            aModel.GetLayerZ( SILKSCREEN_N_FRONT ), 0 );
+
+    // VRML_LAYER bot_silk;
+    aModel.bot_silk.Tesselate( &aModel.holes );
+    write_triangle_bag( output_file, aPcb->GetLayerColor( SILKSCREEN_N_BACK ),
+            &aModel.bot_silk, true, false,
+            aModel.GetLayerZ( SILKSCREEN_N_BACK ), 0 );
+
+    // (not yet implemented; for pretty silver pads)
+    // VRML_LAYER top_silver;
+    // VRML_LAYER bot_silver;
+}
+
+
+static void compute_layer_Zs( MODEL_VRML& aModel, BOARD* pcb )
+{
+    int copper_layers = pcb->GetCopperLayerCount();
 
     // We call it 'layer' thickness, but it's the whole board thickness!
-    double board_thickness = pcb->GetDesignSettings().GetBoardThickness();
-    double half_thickness  = board_thickness / 2;
+    aModel.board_thickness = pcb->GetDesignSettings().GetBoardThickness() * aModel.scale;
+    double half_thickness = aModel.board_thickness / 2;
 
     // Compute each layer's Z value, more or less like the 3d view
     for( LAYER_NUM i = FIRST_LAYER; i <= LAYER_N_FRONT; ++i )
     {
         if( i < copper_layers )
-            layer_z[i] = board_thickness * i / (copper_layers - 1) - half_thickness;
+            aModel.SetLayerZ( i, aModel.board_thickness * i / (copper_layers - 1) - half_thickness );
         else
-            layer_z[i] = half_thickness; // The component layer...
+            aModel.SetLayerZ( i, half_thickness );  // component layer
     }
 
     /* To avoid rounding interference, we apply an epsilon to each
      * successive layer */
-    const double epsilon_z = 0.02 * IU_PER_MM; // That's 1/50 mm
-    layer_z[SOLDERPASTE_N_BACK]  = -half_thickness - epsilon_z * 4;
-    layer_z[ADHESIVE_N_BACK]     = -half_thickness - epsilon_z * 3;
-    layer_z[SILKSCREEN_N_BACK]   = -half_thickness - epsilon_z * 2;
-    layer_z[SOLDERMASK_N_BACK]   = -half_thickness - epsilon_z;
-    layer_z[SOLDERMASK_N_FRONT]  = half_thickness + epsilon_z;
-    layer_z[SILKSCREEN_N_FRONT]  = half_thickness + epsilon_z * 2;
-    layer_z[ADHESIVE_N_FRONT]    = half_thickness + epsilon_z * 3;
-    layer_z[SOLDERPASTE_N_FRONT] = half_thickness + epsilon_z * 4;
-    layer_z[DRAW_N]    = half_thickness + epsilon_z * 5;
-    layer_z[COMMENT_N] = half_thickness + epsilon_z * 6;
-    layer_z[ECO1_N]    = half_thickness + epsilon_z * 7;
-    layer_z[ECO2_N]    = half_thickness + epsilon_z * 8;
-    layer_z[EDGE_N]    = 0;
-}
-
-
-static void export_vrml_line( LAYER_NUM layer, double startx, double starty, //{{{
-                              double endx, double endy, double width, int divisions )
-{
-    double  r     = width / 2;
-    double  angle = atan2( endy - starty, endx - startx );
-    double  alpha;
-    FLAT_FAN fan;
-
-    // Output the 'bone' as a triangle fan, this is the fan centre
-    fan.c.x = (startx + endx) / 2;
-    fan.c.y = (starty + endy) / 2;
-
-    // The 'end' side cap
-    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
-        fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );
-
-    alpha = angle + PI2;
-    fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );
-
-    // The 'start' side cap
-    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
-        fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );
-
-    alpha = angle + 3 * PI2;
-    fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );
-    // Export the fan
-    fan.bag( layer );
-}
-
-
-static void export_vrml_circle( LAYER_NUM layer, double startx, double starty, //{{{
-                                double endx, double endy, double width )
-{
-    double   hole, radius;
-    FLAT_RING ring;
+    double epsilon_z = 0.02;    // That's 1/50 mm
+    aModel.SetLayerZ( SOLDERPASTE_N_BACK, -half_thickness - epsilon_z * 4 );
+    aModel.SetLayerZ( ADHESIVE_N_BACK, -half_thickness - epsilon_z * 3 );
+    aModel.SetLayerZ( SILKSCREEN_N_BACK, -half_thickness - epsilon_z * 2 );
+    aModel.SetLayerZ( SOLDERMASK_N_BACK, -half_thickness - epsilon_z );
+    aModel.SetLayerZ( SOLDERMASK_N_FRONT, half_thickness + epsilon_z );
+    aModel.SetLayerZ( SILKSCREEN_N_FRONT, half_thickness + epsilon_z * 2 );
+    aModel.SetLayerZ( ADHESIVE_N_FRONT, half_thickness + epsilon_z * 3 );
+    aModel.SetLayerZ( SOLDERPASTE_N_FRONT, half_thickness + epsilon_z * 4 );
+    aModel.SetLayerZ( DRAW_N, half_thickness + epsilon_z * 5 );
+    aModel.SetLayerZ( COMMENT_N, half_thickness + epsilon_z * 6 );
+    aModel.SetLayerZ( ECO1_N, half_thickness + epsilon_z * 7 );
+    aModel.SetLayerZ( ECO2_N, half_thickness + epsilon_z * 8 );
+    aModel.SetLayerZ( EDGE_N, 0 );
+}
+
+
+static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer,
+        double startx, double starty,
+        double endx, double endy, double width )
+{
+    VRML_LAYER* vlayer;
+
+    if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
+        return;
+
+    starty = -starty;
+    endy = -endy;
+
+    double  angle   = atan2( endy - starty, endx - startx );
+    double  length  = Distance( startx, starty, endx, endy ) + width;
+    double  cx  = ( startx + endx ) / 2.0;
+    double  cy  = ( starty + endy ) / 2.0;
+
+    vlayer->AddSlot( cx, cy, length, width, angle, 1, false );
+}
+
+
+static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer,
+        double startx, double starty,
+        double endx, double endy, double width )
+{
+    VRML_LAYER* vlayer;
+
+    if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
+        return;
+
+    starty = -starty;
+    endy = -endy;
+
+    double hole, radius;
 
     radius = Distance( startx, starty, endx, endy ) + ( width / 2);
-    hole  = radius - width;
-
-    for( double alpha = 0; alpha < M_PI * 2; alpha += INC_ANGLE )
-    {
-        ring.add_inner( startx + hole * cos( alpha ), starty + hole * sin( alpha ) );
-        ring.add_outer( startx + radius * cos( alpha ), starty + radius * sin( alpha ) );
-    }
-
-    ring.bag( layer );
-}
-
-
-static void export_vrml_slot( TRIANGLEBAG& triangles, //{{{
-                              LAYER_NUM top_layer, LAYER_NUM bottom_layer, double xc, double yc,
-                              double dx, double dy, double orient )
-{
-    double capx, capy; // Cap center
-    VLoop  loop;
-    int divisions = SEGM_COUNT_PER_360 / 2;
-
-    loop.z_top    = layer_z[top_layer];
-    loop.z_bottom = layer_z[bottom_layer];
-    double angle  = DECIDEG2RAD( orient );
-
-    if( dy > dx )
-    {
-        EXCHG( dx, dy );
-        angle += PI2;
-    }
-
-    // The exchange above means that cutter radius is alvays dy/2
-    double r = dy / 2;
-    double alpha;
-    // The first side cap
-    capx = xc + cos( angle ) * dx / 2;
-    capy = yc + sin( angle ) * dx / 2;
-
-    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
-        loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-
-    alpha = angle + PI2;
-    loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-
-    // The other side cap
-    capx = xc - cos( angle ) * dx / 2;
-    capy = yc - sin( angle ) * dx / 2;
-
-    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
-        loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-
-    alpha = angle + 3 * PI2;
-    loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-    loop.bag( triangles );
-}
-
-
-static void export_vrml_hole( TRIANGLEBAG& triangles,
-                              int top_layer, int bottom_layer,
-                              double xc, double yc, double hole )
-{
-    VLoop loop;
-
-    loop.z_top    = layer_z[top_layer];
-    loop.z_bottom = layer_z[bottom_layer];
-
-    for( double alpha = 0; alpha < M_PI * 2; alpha += INC_ANGLE )
-        loop.add( xc + cos( alpha ) * hole, yc + sin( alpha ) * hole );
-
-    loop.bag( triangles );
-}
-
-
-static void export_vrml_oval_pad( LAYER_NUM layer, double xc, double yc,
-                                  double dx, double dy, double orient )
-{
-    double  capx, capy; // Cap center
-    FLAT_FAN fan;
-
-    fan.c.x = xc;
-    fan.c.y = yc;
-    double angle = DECIDEG2RAD( orient );
-    int divisions = SEGM_COUNT_PER_360 / 2;
-
-    if( dy > dx )
-    {
-        EXCHG( dx, dy );
-        angle += PI2;
-    }
-
-    // The exchange above means that cutter radius is alvays dy/2
-    double r = dy / 2;
-    double alpha;
-
-    // The first side cap
-    capx = xc + cos( angle ) * dx / 2;
-    capy = yc + sin( angle ) * dx / 2;
-
-    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
-        fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-
-    alpha = angle + PI2;
-    fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-    // The other side cap
-    capx = xc - cos( angle ) * dx / 2;
-    capy = yc - sin( angle ) * dx / 2;
-
-    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
-        fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-
-    alpha = angle + 3 * PI2;
-    fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
-    fan.bag( layer );
-}
-
-
-static void export_vrml_arc( LAYER_NUM layer, double centerx, double centery,
-                             double arc_startx, double arc_starty,
-                             double width, double arc_angle )
-{
-    FLAT_RING ring;
-    double   start_angle = atan2( arc_starty - centery, arc_startx - centerx );
-
-    int count = KiROUND( arc_angle / 360.0 * SEGM_COUNT_PER_360 );
-
-    if( count < 0 )
-        count = -count;
-
-    if( count == 0 )
-        count = 1;
-
-    double divisions = arc_angle*M_PI/180.0 / count;
-
-    double outer_radius = Distance( arc_startx, arc_starty, centerx, centery ) 
-                          + ( width / 2);
-    double inner_radius  = outer_radius - width;
-
-    double alpha = 0;
-    for( int ii = 0; ii <= count; alpha += divisions, ii++ )
-    {
-        double angle_rot = start_angle + alpha;
-        ring.add_inner( centerx + cos( angle_rot ) * inner_radius,
-                        centery + sin( angle_rot ) * inner_radius );
-        ring.add_outer( centerx + cos( angle_rot ) * outer_radius,
-                        centery + sin( angle_rot ) * outer_radius );
-    }
-
-    ring.bag( layer, false );
-}
-
-
-static void export_vrml_varc( TRIANGLEBAG& triangles,
-                              LAYER_NUM top_layer, LAYER_NUM bottom_layer,
-                              double centerx, double centery,
-                              double arc_startx, double arc_starty,
-                              double arc_angle )
-{
-    VLoop loop;
-
-    loop.z_top    = layer_z[top_layer];
-    loop.z_bottom = layer_z[bottom_layer];
-
-    double start_angle = atan2( arc_starty - centery, arc_startx - centerx );
-    double radius = Distance( arc_startx, arc_starty, centerx, centery );
-
-    int count = KiROUND( arc_angle / 360.0 * SEGM_COUNT_PER_360 );
-
-    if( count < 0 )
-        count = -count;
-
-    if( count == 0 )
-        count = 1;
-
-    double divisions = arc_angle*M_PI/180.0 / count;
-
-    double alpha = 0;
-
-    for( int ii = 0; ii <= count; alpha += divisions, ii++ )
-    {
-        double angle_rot = start_angle + alpha;
-        loop.add( centerx + cos( angle_rot ) * radius, centery + sin( angle_rot ) * radius );
-    }
-
-    loop.bag( triangles );
-}
-
-
-static void export_vrml_drawsegment( DRAWSEGMENT* drawseg ) //{{{
+    hole = radius - width;
+
+    vlayer->AddCircle( startx, starty, radius, 1, false );
+
+    if( hole > 0.0001 )
+    {
+        vlayer->AddCircle( startx, starty, hole, 1, true );
+    }
+}
+
+
+static void export_vrml_arc( MODEL_VRML& aModel, LAYER_NUM layer,
+        double centerx, double centery,
+        double arc_startx, double arc_starty,
+        double width, double arc_angle )
+{
+    VRML_LAYER* vlayer;
+
+    if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
+        return;
+
+    centery = -centery;
+    arc_starty = -arc_starty;
+
+    arc_angle *= -M_PI / 180;
+
+    vlayer->AddArc( centerx, centery, arc_startx, arc_starty,
+            width, arc_angle, 1, false );
+}
+
+
+static void export_vrml_drawsegment( MODEL_VRML& aModel, DRAWSEGMENT* drawseg )
 {
     LAYER_NUM layer = drawseg->GetLayer();
-    double w     = drawseg->GetWidth();
-    double x     = drawseg->GetStart().x;
-    double y     = drawseg->GetStart().y;
-    double xf    = drawseg->GetEnd().x;
-    double yf    = drawseg->GetEnd().y;
+    double  w   = drawseg->GetWidth() * aModel.scale;
+    double  x   = drawseg->GetStart().x * aModel.scale + aModel.tx;
+    double  y   = drawseg->GetStart().y * aModel.scale + aModel.ty;
+    double  xf  = drawseg->GetEnd().x * aModel.scale + aModel.tx;
+    double  yf  = drawseg->GetEnd().y * aModel.scale + aModel.ty;
 
-    // Items on the edge layer are high, not thick
+    // Items on the edge layer are handled elsewhere; just return
     if( layer == EDGE_N )
-    {
-        switch( drawseg->GetShape() )
-        {
-        // There is a special 'varc' primitive for this
-        case S_ARC:
-            export_vrml_varc( layer_triangles[layer],
-                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
-                              x, y, xf, yf, drawseg->GetAngle()/10 );
-            break;
-
-        // Circles on edge are usually important holes
-        case S_CIRCLE:
-            export_vrml_hole( layer_triangles[layer],
-                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER, x, y,
-                              Distance( xf, yf, x, y ) / 2 );
-            break;
-
-        default:
-        {
-            // Simply a quad
-            double z_top    = layer_z[FIRST_COPPER_LAYER];
-            double z_bottom = layer_z[LAST_COPPER_LAYER];
-            bag_vquad( layer_triangles[layer], x, y, xf, yf, z_top, z_bottom );
-            break;
-        }
-        }
-    }
-    else
-    {
-        switch( drawseg->GetShape() )
-        {
-        case S_ARC:
-            export_vrml_arc( layer,
-                             (double) drawseg->GetCenter().x,
-                             (double) drawseg->GetCenter().y,
-                             (double) drawseg->GetArcStart().x,
-                             (double) drawseg->GetArcStart().y,
-                             w, drawseg->GetAngle()/10 );
-            break;
-
-        case S_CIRCLE:
-            export_vrml_circle( layer, x, y, xf, yf, w );
-            break;
-
-        default:
-            export_vrml_line( layer, x, y, xf, yf, w, 1 );
-            break;
-        }
+        return;
+
+    switch( drawseg->GetShape() )
+    {
+    case S_ARC:
+        export_vrml_arc( aModel, layer,
+                (double) drawseg->GetCenter().x,
+                (double) drawseg->GetCenter().y,
+                (double) drawseg->GetArcStart().x,
+                (double) drawseg->GetArcStart().y,
+                w, drawseg->GetAngle() / 10 );
+        break;
+
+    case S_CIRCLE:
+        export_vrml_circle( aModel, layer, x, y, xf, yf, w );
+        break;
+
+    default:
+        export_vrml_line( aModel, layer, x, y, xf, yf, w );
+        break;
     }
 }
 
 
 /* C++ doesn't have closures and neither continuation forms... this is
  * for coupling the vrml_text_callback with the common parameters */
-
-static LAYER_NUM s_text_layer;
-static int s_text_width;
 static void vrml_text_callback( int x0, int y0, int xf, int yf )
 {
-    export_vrml_line( s_text_layer, x0, y0, xf, yf, s_text_width, 1 );
+    LAYER_NUM s_text_layer = VRMLEXPORT::model_vrml->s_text_layer;
+    int s_text_width = VRMLEXPORT::model_vrml->s_text_width;
+    double  scale = VRMLEXPORT::model_vrml->scale;
+    double  tx  = VRMLEXPORT::model_vrml->tx;
+    double  ty  = VRMLEXPORT::model_vrml->ty;
+
+    export_vrml_line( *VRMLEXPORT::model_vrml, s_text_layer,
+            x0 * scale + tx, y0 * scale + ty,
+            xf * scale + tx, yf * scale + ty,
+            s_text_width * scale );
 }
 
 
-static void export_vrml_pcbtext( TEXTE_PCB* text )
+static void export_vrml_pcbtext( MODEL_VRML& aModel, TEXTE_PCB* text )
 {
-    // Coupling by globals! Ewwww...
-    s_text_layer = text->GetLayer();
-    s_text_width = text->GetThickness();
+    VRMLEXPORT::model_vrml->s_text_layer    = text->GetLayer();
+    VRMLEXPORT::model_vrml->s_text_width    = text->GetThickness();
 
     wxSize size = text->GetSize();
 
@@ -680,9 +494,9 @@
 
     if( text->IsMultilineAllowed() )
     {
-        wxPoint        pos  = text->GetTextPosition();
+        wxPoint pos = text->GetTextPosition();
         wxArrayString* list = wxStringSplit( text->GetText(), '\n' );
-        wxPoint        offset;
+        wxPoint offset;
 
         offset.y = text->GetInterline();
 
@@ -692,11 +506,11 @@
         {
             wxString txt = list->Item( i );
             DrawGraphicText( NULL, NULL, pos, BLACK,
-                             txt, text->GetOrientation(), size,
-                             text->GetHorizJustify(), text->GetVertJustify(),
-                             text->GetThickness(), text->IsItalic(),
-                             true,
-                             vrml_text_callback );
+                    txt, text->GetOrientation(), size,
+                    text->GetHorizJustify(), text->GetVertJustify(),
+                    text->GetThickness(), text->IsItalic(),
+                    true,
+                    vrml_text_callback );
             pos += offset;
         }
 
@@ -705,28 +519,34 @@
     else
     {
         DrawGraphicText( NULL, NULL, text->GetTextPosition(), BLACK,
-                         text->GetText(), text->GetOrientation(), size,
-                         text->GetHorizJustify(), text->GetVertJustify(),
-                         text->GetThickness(), text->IsItalic(),
-                         true,
-                         vrml_text_callback );
+                text->GetText(), text->GetOrientation(), size,
+                text->GetHorizJustify(), text->GetVertJustify(),
+                text->GetThickness(), text->IsItalic(),
+                true,
+                vrml_text_callback );
     }
 }
 
 
-static void export_vrml_drawings( BOARD* pcb ) //{{{
+static void export_vrml_drawings( MODEL_VRML& aModel, BOARD* pcb )    // {{{
 {
     // draw graphic items
-    for( EDA_ITEM* drawing = pcb->m_Drawings;  drawing != 0;  drawing = drawing->Next() )
+    for( EDA_ITEM* drawing = pcb->m_Drawings; drawing != 0; drawing = drawing->Next() )
     {
+        LAYER_NUM layer = ( (DRAWSEGMENT*) drawing )->GetLayer();
+
+        if( layer != FIRST_COPPER_LAYER && layer != LAST_COPPER_LAYER
+            && layer != SILKSCREEN_N_BACK && layer != SILKSCREEN_N_FRONT )
+            return;
+
         switch( drawing->Type() )
         {
         case PCB_LINE_T:
-            export_vrml_drawsegment( (DRAWSEGMENT*) drawing );
+            export_vrml_drawsegment( aModel, (DRAWSEGMENT*) drawing );
             break;
 
         case PCB_TEXT_T:
-            export_vrml_pcbtext( (TEXTE_PCB*) drawing );
+            export_vrml_pcbtext( aModel, (TEXTE_PCB*) drawing );
             break;
 
         default:
@@ -736,167 +556,351 @@
 }
 
 
-static void export_round_padstack( BOARD* pcb, double x, double y, double r, //{{{
-                                   LAYER_NUM bottom_layer, LAYER_NUM top_layer )
-{
-    int copper_layers = pcb->GetCopperLayerCount( );
-
-    for( LAYER_NUM layer = bottom_layer; layer < copper_layers; ++layer )
-    {
-        // The last layer is always the component one, unless it's single face
-        if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
-            layer = LAST_COPPER_LAYER;
-
-        if( layer <= top_layer )
-            export_vrml_circle( layer, x, y, x + r / 2, y, r );
-    }
-}
-
-
-static void export_vrml_via( BOARD* pcb, SEGVIA* via ) //{{{
+// board edges and cutouts
+static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb )
+{
+    CPOLYGONS_LIST  bufferPcbOutlines;          // stores the board main outlines
+    CPOLYGONS_LIST  allLayerHoles;              // Contains through holes, calculated only once
+
+    allLayerHoles.reserve( 20000 );
+
+    // Build a polygon from edge cut items
+    wxString msg;
+
+    if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines,
+                allLayerHoles, &msg ) )
+    {
+        msg << wxT( "\n\n" ) <<
+        _( "Unable to calculate the board outlines.\n"
+           "Therefore use the board boundary box." );
+        wxMessageBox( msg );
+    }
+
+    double  scale = aModel.scale;
+    double  dx  = aModel.tx;
+    double  dy  = aModel.ty;
+
+    int i = 0;
+    int seg;
+
+    // deal with the solid outlines
+    int nvert = bufferPcbOutlines.GetCornersCount();
+
+    while( i < nvert )
+    {
+        seg = aModel.board.NewContour();
+
+        if( seg < 0 )
+            return;     // xxx: we should throw some sort of error
+
+        while( i < nvert )
+        {
+            aModel.board.AddVertex( seg, bufferPcbOutlines[i].x * scale + dx,
+                    -(bufferPcbOutlines[i].y * scale + dy) );
+
+            if( bufferPcbOutlines[i].end_contour )
+                break;
+
+            ++i;
+        }
+
+        aModel.board.EnsureWinding( seg, false );
+        ++i;
+    }
+
+    // deal with the holes
+    nvert = allLayerHoles.GetCornersCount();
+
+    while( i < nvert )
+    {
+        seg = aModel.holes.NewContour();
+
+        if( seg < 0 )
+            return;     // xxx: we should throw some sort of error
+
+        while( i < nvert )
+        {
+            aModel.holes.AddVertex( seg, bufferPcbOutlines[i].x * scale + dx,
+                    -(bufferPcbOutlines[i].y * scale + dy) );
+
+            if( bufferPcbOutlines[i].end_contour )
+                break;
+
+            ++i;
+        }
+
+        aModel.holes.EnsureWinding( seg );
+        ++i;
+    }
+}
+
+
+static void export_round_padstack( MODEL_VRML& aModel, BOARD* pcb,
+        double x, double y, double r,
+        LAYER_NUM bottom_layer, LAYER_NUM top_layer,
+        double hole )
+{
+    LAYER_NUM layer = top_layer;
+    bool thru = true;
+
+    // if not a thru hole do not put a hole in the board
+    if( top_layer != LAST_COPPER_LAYER || bottom_layer != FIRST_COPPER_LAYER )
+        thru = false;
+
+    while( 1 )
+    {
+        if( layer == FIRST_COPPER_LAYER )
+        {
+            aModel.bot_copper.AddCircle( x, -y, r, 1 );
+
+            if( hole > 0 )
+            {
+                if( thru )
+                    aModel.holes.AddHole( x, -y, hole, 1 );
+                else
+                    aModel.bot_copper.AddCircle( x, -y, hole, 1, true );
+            }
+        }
+        else if( layer == LAST_COPPER_LAYER )
+        {
+            aModel.top_copper.AddCircle( x, -y, r, 1 );
+
+            if( hole > 0 )
+            {
+                if( thru )
+                    aModel.holes.AddHole( x, -y, hole, 1 );
+                else
+                    aModel.top_copper.AddCircle( x, -y, hole, 1, true );
+            }
+        }
+
+        if( layer == bottom_layer )
+            break;
+
+        layer = bottom_layer;
+    }
+}
+
+
+static void export_vrml_via( MODEL_VRML& aModel, BOARD* pcb, SEGVIA* via )
 {
     double x, y, r, hole;
     LAYER_NUM top_layer, bottom_layer;
 
-    r    = via->GetWidth() / 2;
-    hole = via->GetDrillValue() / 2;
-    x    = via->GetStart().x;
-    y    = via->GetStart().y;
+    hole = via->GetDrillValue() * aModel.scale / 2.0;
+    r   = via->GetWidth() * aModel.scale / 2.0;
+    x   = via->GetStart().x * aModel.scale + aModel.tx;
+    y   = via->GetStart().y * aModel.scale + aModel.ty;
     via->ReturnLayerPair( &top_layer, &bottom_layer );
 
+    // do not render a buried via
+    if( top_layer != LAST_COPPER_LAYER && bottom_layer != FIRST_COPPER_LAYER )
+        return;
+
     // Export the via padstack
-    export_round_padstack( pcb, x, y, r, bottom_layer, top_layer );
-
-    // Drill a hole
-    export_vrml_hole( via_triangles[via->GetShape()], top_layer, bottom_layer, x, y, hole );
+    export_round_padstack( aModel, pcb, x, y, r, bottom_layer, top_layer, hole );
 }
 
 
-static void export_vrml_tracks( BOARD* pcb ) //{{{
+static void export_vrml_tracks( MODEL_VRML& aModel, BOARD* pcb )
 {
     for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() )
     {
         if( track->Type() == PCB_VIA_T )
-            export_vrml_via( pcb, (SEGVIA*) track );
-        else
-            export_vrml_line( track->GetLayer(), track->GetStart().x, track->GetStart().y,
-                              track->GetEnd().x, track->GetEnd().y, track->GetWidth(), 4 );
+        {
+            export_vrml_via( aModel, pcb, (SEGVIA*) track );
+        }
+        else if( track->GetLayer() == FIRST_COPPER_LAYER
+                 || track->GetLayer() == LAST_COPPER_LAYER )
+            export_vrml_line( aModel, track->GetLayer(),
+                    track->GetStart().x * aModel.scale + aModel.tx,
+                    track->GetStart().y * aModel.scale + aModel.ty,
+                    track->GetEnd().x * aModel.scale + aModel.tx,
+                    track->GetEnd().y * aModel.scale + aModel.ty,
+                    track->GetWidth() * aModel.scale );
     }
 }
 
 
 /* not used? @todo complete
-static void export_vrml_zones( BOARD* pcb )
-{
-    // Export fill segments
-    for( SEGZONE* segzone = pcb->m_Zone;
-        segzone != 0;
-        segzone = segzone->Next() )
-    {
-        // Fill tracks are exported with low subdivisions
-        if( segzone->Type() == PCB_ZONE_T )
-            export_vrml_line( segzone->GetLayer(), segzone->m_Start.x, segzone->m_Start.y,
-                              segzone->m_End.x, segzone->m_End.y, segzone->m_Width, 1 );
-    }
-
-    // Export zone outlines
-    for( int i = 0; i < pcb->GetAreaCount(); i++ )
-    {
-        ZONE_CONTAINER* zone = pcb->GetArea( i );
-
-        if( ( zone->m_FilledPolysList.size() == 0 )
-           ||( zone->GetMinThickness() <= 1 ) )
-            continue;
-
-        int width = zone->GetMinThickness();
-
-        if( width > 0 )
-        {
-            int      imax  = zone->m_FilledPolysList.size() - 1;
-            LAYER_NUM layer = zone->GetLayer();
-            CPolyPt* firstcorner = &zone->m_FilledPolysList[0];
-            CPolyPt* begincorner = firstcorner;
-
-            // I'm not really positive about what he's doing here...
-            for( int ic = 1; ic <= imax; ic++ )
-            {
-                CPolyPt* endcorner = &zone->m_FilledPolysList[ic];
-
-                export_vrml_line( layer, begincorner->x, begincorner->y,
-                                  endcorner->x, endcorner->y, width, 1 );
-
-                if( (endcorner->end_contour) || (ic == imax) )  // the last corner of a filled area is found: draw it
-                {
-                    export_vrml_line( layer, endcorner->x, endcorner->y,
-                                      firstcorner->x, firstcorner->y, width, 1 );
-                    ic++;
-
-                    // A new contour?
-                    if( ic < imax - 1 )
-                        begincorner = firstcorner = &zone->m_FilledPolysList[ic];
-                }
-                else
-                    begincorner = endcorner;
-            }
-        }
-    }
-}
-*/
-
-static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{
+ *  static void export_vrml_zones( BOARD* pcb )
+ *  {
+ *   // Export fill segments
+ *   for( SEGZONE* segzone = pcb->m_Zone;
+ *       segzone != 0;
+ *       segzone = segzone->Next() )
+ *   {
+ *       // Fill tracks are exported with low subdivisions
+ *       if( segzone->Type() == PCB_ZONE_T )
+ *           export_vrml_line( segzone->GetLayer(), segzone->m_Start.x, segzone->m_Start.y,
+ *                             segzone->m_End.x, segzone->m_End.y, segzone->m_Width, 1 );
+ *   }
+ *
+ *   // Export zone outlines
+ *   for( int i = 0; i < pcb->GetAreaCount(); i++ )
+ *   {
+ *       ZONE_CONTAINER* zone = pcb->GetArea( i );
+ *
+ *       if( ( zone->m_FilledPolysList.size() == 0 )
+ * ||( zone->GetMinThickness() <= 1 ) )
+ *           continue;
+ *
+ *       int width = zone->GetMinThickness();
+ *
+ *       if( width > 0 )
+ *       {
+ *           int      imax  = zone->m_FilledPolysList.size() - 1;
+ *           LAYER_NUM layer = zone->GetLayer();
+ *           CPolyPt* firstcorner = &zone->m_FilledPolysList[0];
+ *           CPolyPt* begincorner = firstcorner;
+ *
+ *           // I'm not really positive about what he's doing here...
+ *           for( int ic = 1; ic <= imax; ic++ )
+ *           {
+ *               CPolyPt* endcorner = &zone->m_FilledPolysList[ic];
+ *
+ *               export_vrml_line( layer, begincorner->x, begincorner->y,
+ *                                 endcorner->x, endcorner->y, width, 1 );
+ *
+ *               if( (endcorner->end_contour) || (ic == imax) )  // the last corner of a filled area is found: draw it
+ *               {
+ *                   export_vrml_line( layer, endcorner->x, endcorner->y,
+ *                                     firstcorner->x, firstcorner->y, width, 1 );
+ *                   ic++;
+ *
+ *                   // A new contour?
+ *                   if( ic < imax - 1 )
+ *                       begincorner = firstcorner = &zone->m_FilledPolysList[ic];
+ *               }
+ *               else
+ *                   begincorner = endcorner;
+ *           }
+ *       }
+ *   }
+ *  }
+ */
+
+static void export_vrml_text_module( TEXTE_MODULE* module )
 {
     if( module->IsVisible() )
     {
         wxSize size = module->GetSize();
 
         if( module->IsMirrored() )
-            NEGATE( size.x ); // Text is mirrored
-
-        s_text_layer = module->GetLayer();
-        s_text_width = module->GetThickness();
+            NEGATE( size.x );  // Text is mirrored
+
+        VRMLEXPORT::model_vrml->s_text_layer    = module->GetLayer();
+        VRMLEXPORT::model_vrml->s_text_width    = module->GetThickness();
+
         DrawGraphicText( NULL, NULL, module->GetTextPosition(), BLACK,
-                         module->GetText(), module->GetDrawRotation(), size,
-                         module->GetHorizJustify(), module->GetVertJustify(),
-                         module->GetThickness(), module->IsItalic(),
-                         true,
-                         vrml_text_callback );
+                module->GetText(), module->GetDrawRotation(), size,
+                module->GetHorizJustify(), module->GetVertJustify(),
+                module->GetThickness(), module->IsItalic(),
+                true,
+                vrml_text_callback );
     }
 }
 
 
-static void export_vrml_edge_module( EDGE_MODULE* aOutline ) //{{{
+static void export_vrml_edge_module( MODEL_VRML& aModel, EDGE_MODULE* aOutline )
 {
     LAYER_NUM layer = aOutline->GetLayer();
-    double x     = aOutline->GetStart().x;
-    double y     = aOutline->GetStart().y;
-    double xf    = aOutline->GetEnd().x;
-    double yf    = aOutline->GetEnd().y;
-    double w     = aOutline->GetWidth();
+    double  x   = aOutline->GetStart().x * aModel.scale + aModel.tx;
+    double  y   = aOutline->GetStart().y * aModel.scale + aModel.ty;
+    double  xf  = aOutline->GetEnd().x * aModel.scale + aModel.tx;
+    double  yf  = aOutline->GetEnd().y * aModel.scale + aModel.ty;
+    double  w   = aOutline->GetWidth() * aModel.scale;
 
     switch( aOutline->GetShape() )
     {
     case S_ARC:
-        export_vrml_arc( layer, x, y, xf, yf, w, aOutline->GetAngle()/10 );
+        export_vrml_arc( aModel, layer, x, y, xf, yf, w, aOutline->GetAngle() / 10 );
         break;
 
     case S_CIRCLE:
-        export_vrml_circle( layer, x, y, xf, yf, w );
-        break;
-
-    default:
-        export_vrml_line( layer, x, y, xf, yf, w, 1 );
-        break;
-    }
-}
-
-
-static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{
-{
-    double hole_drill_w = (double) aPad->GetDrillSize().x / 2;
-    double hole_drill_h = (double) aPad->GetDrillSize().y / 2;
-    double hole_drill   = std::min( hole_drill_w, hole_drill_h );
-    double hole_x = aPad->GetPosition().x;
-    double hole_y = aPad->GetPosition().y;
+        export_vrml_circle( aModel, layer, x, y, xf, yf, w );
+        break;
+
+    default:
+        export_vrml_line( aModel, layer, x, y, xf, yf, w );
+        break;
+    }
+}
+
+
+static void export_vrml_padshape( MODEL_VRML& aModel, VRML_LAYER* aLayer, D_PAD* aPad )
+{
+    // The (maybe offset) pad position
+    wxPoint pad_pos = aPad->ReturnShapePos();
+    double  pad_x   = pad_pos.x * aModel.scale + aModel.tx;
+    double  pad_y   = pad_pos.y * aModel.scale + aModel.ty;
+    wxSize  pad_delta = aPad->GetDelta();
+
+    double  pad_dx  = pad_delta.x * aModel.scale / 2.0;
+    double  pad_dy  = pad_delta.y * aModel.scale / 2.0;
+
+    double  pad_w   = aPad->GetSize().x * aModel.scale / 2.0;
+    double  pad_h   = aPad->GetSize().y * aModel.scale / 2.0;
+
+    switch( aPad->GetShape() )
+    {
+    case PAD_CIRCLE:
+        aLayer->AddCircle( pad_x, -pad_y, pad_w, 1, false );
+        break;
+
+    case PAD_OVAL:
+        aLayer->AddSlot( pad_x, -pad_y, pad_w * 2.0, pad_h * 2.0,
+                DECIDEG2RAD( aPad->GetOrientation() ), 1, false );
+        break;
+
+    case PAD_RECT:
+        // Just to be sure :D
+        pad_dx  = 0;
+        pad_dy  = 0;
+
+    case PAD_TRAPEZOID:
+        {
+            double coord[8] =
+            {
+                -pad_w + pad_dy, -pad_h - pad_dx,
+                -pad_w - pad_dy, pad_h + pad_dx,
+                +pad_w - pad_dy, -pad_h + pad_dx,
+                +pad_w + pad_dy, pad_h - pad_dx
+            };
+
+            for( int i = 0; i < 4; i++ )
+            {
+                RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
+                coord[i * 2] += pad_x;
+                coord[i * 2 + 1] += pad_y;
+            }
+
+            int lines = aLayer->NewContour();
+
+            if( lines < 0 )
+                return;
+
+            aLayer->AddVertex( lines, coord[0], -coord[1] );
+            aLayer->AddVertex( lines, coord[4], -coord[5] );
+            aLayer->AddVertex( lines, coord[6], -coord[7] );
+            aLayer->AddVertex( lines, coord[2], -coord[3] );
+            aLayer->EnsureWinding( lines, false );
+        }
+        break;
+
+    default:
+        ;
+    }
+}
+
+
+static void export_vrml_pad( MODEL_VRML& aModel, BOARD* pcb, D_PAD* aPad )
+{
+    double  hole_drill_w    = (double) aPad->GetDrillSize().x * aModel.scale / 2.0;
+    double  hole_drill_h    = (double) aPad->GetDrillSize().y * aModel.scale / 2.0;
+    double  hole_drill = std::min( hole_drill_w, hole_drill_h );
+    double  hole_x  = aPad->GetPosition().x * aModel.scale + aModel.tx;
+    double  hole_y  = aPad->GetPosition().y * aModel.scale + aModel.ty;
 
     // Export the hole on the edge layer
     if( hole_drill > 0 )
@@ -904,90 +908,27 @@
         if( aPad->GetDrillShape() == PAD_OVAL )
         {
             // Oblong hole (slot)
-            export_vrml_slot( layer_triangles[EDGE_N],
-                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
-                              hole_x, hole_y, hole_drill_w, hole_drill_h,
-                              aPad->GetOrientation() );
+            aModel.holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0, hole_drill_h * 2.0,
+                    DECIDEG2RAD( aPad->GetOrientation() ), 1 );
         }
         else
         {
             // Drill a round hole
-            export_vrml_hole( layer_triangles[EDGE_N],
-                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
-                              hole_x, hole_y, hole_drill );
+            aModel.holes.AddHole( hole_x, -hole_y, hole_drill, 1 );
         }
     }
 
     // The pad proper, on the selected layers
-    LAYER_MSK   layer_mask    = aPad->GetLayerMask();
-    int         copper_layers = pcb->GetCopperLayerCount( );
-
-    // The (maybe offseted) pad position
-    wxPoint     pad_pos   = aPad->ReturnShapePos();
-    double      pad_x     = pad_pos.x;
-    double      pad_y     = pad_pos.y;
-    wxSize      pad_delta = aPad->GetDelta();
-
-    double      pad_dx    = pad_delta.x / 2;
-    double      pad_dy    = pad_delta.y / 2;
-
-    double      pad_w     = aPad->GetSize().x / 2;
-    double      pad_h     = aPad->GetSize().y / 2;
-
-    for( LAYER_NUM layer = FIRST_COPPER_LAYER; layer < copper_layers; ++layer )
-    {
-        // The last layer is always the component one, unless it's single face
-        if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
-            layer = LAST_COPPER_LAYER;
-
-        if( layer_mask & GetLayerMask( layer ) )
-        {
-            // OK, the pad is on this layer, export it
-            switch( aPad->GetShape() )
-            {
-            case PAD_CIRCLE:
-                export_vrml_circle( layer, pad_x, pad_y,
-                                    pad_x + pad_w / 2, pad_y, pad_w );
-                break;
-
-            case PAD_OVAL:
-                export_vrml_oval_pad( layer, pad_x, pad_y,
-                                      pad_w * 2, pad_h * 2, aPad->GetOrientation() );
-                break;
-
-            case PAD_RECT:
-                // Just to be sure :D
-                pad_dx = 0;
-                pad_dy = 0;
-
-            case PAD_TRAPEZOID:
-            {
-                int coord[8] =
-                {
-                    KiROUND( -pad_w - pad_dy ), KiROUND( +pad_h + pad_dx ),
-                    KiROUND( -pad_w + pad_dy ), KiROUND( -pad_h - pad_dx ),
-                    KiROUND( +pad_w - pad_dy ), KiROUND( +pad_h - pad_dx ),
-                    KiROUND( +pad_w + pad_dy ), KiROUND( -pad_h + pad_dx ),
-                };
-
-                for( int i = 0; i < 4; i++ )
-                {
-                    RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
-                    coord[i * 2]     += KiROUND( pad_x );
-                    coord[i * 2 + 1] += KiROUND( pad_y );
-                }
-
-                bag_flat_quad( layer, coord[0], coord[1],
-                               coord[2], coord[3],
-                               coord[4], coord[5],
-                               coord[6], coord[7] );
-            }
-            break;
-
-            default:
-                ;
-            }
-        }
+    LAYER_MSK layer_mask = aPad->GetLayerMask();
+
+    if( layer_mask & LAYER_BACK )
+    {
+        export_vrml_padshape( aModel, &aModel.bot_copper, aPad );
+    }
+
+    if( layer_mask & LAYER_FRONT )
+    {
+        export_vrml_padshape( aModel, &aModel.top_copper, aPad );
     }
 }
 
@@ -997,10 +938,10 @@
 {
     double sina = sin( a / 2 );
 
-    q[0] = x * sina;
-    q[1] = y * sina;
-    q[2] = z * sina;
-    q[3] = cos( a / 2 );
+    q[0]    = x * sina;
+    q[1]    = y * sina;
+    q[2]    = z * sina;
+    q[3]    = cos( a / 2 );
 }
 
 
@@ -1021,27 +962,26 @@
 {
     double tmp[4];
 
-    tmp[0] = q2[3] *q1[0] + q2[0] *q1[3] + q2[1] *q1[2] - q2[2] *q1[1];
-    tmp[1] = q2[3] *q1[1] + q2[1] *q1[3] + q2[2] *q1[0] - q2[0] *q1[2];
-    tmp[2] = q2[3] *q1[2] + q2[2] *q1[3] + q2[0] *q1[1] - q2[1] *q1[0];
-    tmp[3] = q2[3] *q1[3] - q2[0] *q1[0] - q2[1] *q1[1] - q2[2] *q1[2];
-    qr[0]  = tmp[0]; qr[1] = tmp[1];
-    qr[2]  = tmp[2]; qr[3] = tmp[3];
+    tmp[0]  = q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1];
+    tmp[1]  = q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2];
+    tmp[2]  = q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0];
+    tmp[3]  = q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2];
+    qr[0]   = tmp[0]; qr[1] = tmp[1];
+    qr[2]   = tmp[2]; qr[3] = tmp[3];
 }
 
 
-static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
-                                FILE* aOutputFile,
-                                double aVRMLModelsToBiu,
-                                bool aExport3DFiles, const wxString & a3D_Subdir,
-                                double boardIU2WRML )
+static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule,
+        FILE* aOutputFile,
+        double aVRMLModelsToBiu,
+        bool aExport3DFiles, const wxString& a3D_Subdir )
 {
     // Reference and value
     export_vrml_text_module( &aModule->Reference() );
     export_vrml_text_module( &aModule->Value() );
 
     // Export module edges
-    for( EDA_ITEM* item = aModule->GraphicalItems();  item != NULL;  item = item->Next() )
+    for( EDA_ITEM* item = aModule->GraphicalItems(); item != NULL; item = item->Next() )
     {
         switch( item->Type() )
         {
@@ -1050,7 +990,7 @@
             break;
 
         case PCB_MODULE_EDGE_T:
-            export_vrml_edge_module( dynamic_cast<EDGE_MODULE*>( item ) );
+            export_vrml_edge_module( aModel, dynamic_cast<EDGE_MODULE*>( item ) );
             break;
 
         default:
@@ -1059,8 +999,8 @@
     }
 
     // Export pads
-    for( D_PAD* pad = aModule->Pads();  pad; pad = pad->Next() )
-        export_vrml_pad( aPcb, pad );
+    for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
+        export_vrml_pad( aModel, aPcb, pad );
 
     bool isFlipped = aModule->GetLayer() == LAYER_N_BACK;
 
@@ -1072,12 +1012,12 @@
         if( fname.IsEmpty() )
             continue;
 
-        if( ! wxFileName::FileExists( fname ) )
+        if( !wxFileName::FileExists( fname ) )
         {
             wxFileName fn = fname;
             fname = wxGetApp().FindLibraryPath( fn );
 
-            if( fname.IsEmpty() )   // keep "short" name if full filemane not found
+            if( fname.IsEmpty() ) // keep "short" name if full filemane not found
                 fname = vrmlm->m_Shape3DName;
         }
 
@@ -1098,9 +1038,9 @@
          * for footprints that are flipped
          * When flipped, axis rotation is the horizontal axis (X axis)
          */
-        double rotx = - vrmlm->m_MatRotation.x;
-        double roty = - vrmlm->m_MatRotation.y;
-        double rotz = - vrmlm->m_MatRotation.z;
+        double  rotx    = -vrmlm->m_MatRotation.x;
+        double  roty    = -vrmlm->m_MatRotation.y;
+        double  rotz    = -vrmlm->m_MatRotation.z;
 
         if( isFlipped )
         {
@@ -1133,26 +1073,26 @@
 
         // adjust 3D shape local offset position
         // they are given in inch, so they are converted in board IU.
-        double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0;
-        double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0;
-        double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;
+        double  offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0;
+        double  offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0;
+        double  offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;
 
         if( isFlipped )
-            NEGATE(offsetz);
-        else    // In normal mode, Y axis is reversed in Pcbnew.
-            NEGATE(offsety);
+            NEGATE( offsetz );
+        else // In normal mode, Y axis is reversed in Pcbnew.
+            NEGATE( offsety );
 
         RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );
 
         fprintf( aOutputFile, "  translation %g %g %g\n",
-                 (offsetx + aModule->GetPosition().x) * boardIU2WRML,
-               - (offsety + aModule->GetPosition().y) * boardIU2WRML,   // Y axis is reversed in Pcbnew
-                 (offsetz + layer_z[aModule->GetLayer()]) * boardIU2WRML);
+                (offsetx + aModule->GetPosition().x) * aModel.scale + aModel.tx,
+                -(offsety + aModule->GetPosition().y) * aModel.scale - aModel.ty,
+                (offsetz * aModel.scale ) + aModel.GetLayerZ( aModule->GetLayer() ) );
 
         fprintf( aOutputFile, "  scale %g %g %g\n",
-                 vrmlm->m_MatScale.x * aVRMLModelsToBiu,
-                 vrmlm->m_MatScale.y * aVRMLModelsToBiu,
-                 vrmlm->m_MatScale.z * aVRMLModelsToBiu );
+                vrmlm->m_MatScale.x * aVRMLModelsToBiu,
+                vrmlm->m_MatScale.y * aVRMLModelsToBiu,
+                vrmlm->m_MatScale.z * aVRMLModelsToBiu );
 
         if( fname.EndsWith( wxT( "x3d" ) ) )
         {
@@ -1163,7 +1103,7 @@
                 // embed x3d model in vrml format
                 parser->Load( fname );
                 fprintf( aOutputFile,
-                         "  children [\n %s ]\n", TO_UTF8( parser->VRML_representation() ) );
+                        "  children [\n %s ]\n", TO_UTF8( parser->VRML_representation() ) );
                 fprintf( aOutputFile, "  }\n" );
                 delete parser;
             }
@@ -1171,33 +1111,25 @@
         else
         {
             fprintf( aOutputFile,
-                     "  children [\n    Inline {\n      url \"%s\"\n    } ]\n",
-                     TO_UTF8( fname ) );
+                    "  children [\n    Inline {\n      url \"%s\"\n    } ]\n",
+                    TO_UTF8( fname ) );
             fprintf( aOutputFile, "  }\n" );
         }
-
-    }
-}
-
-
-static void write_and_empty_triangle_bag( FILE* output_file, TRIANGLEBAG& triangles,
-                                          int color, double boardIU2WRML )
-{
-    if( !triangles.empty() )
-    {
-        write_triangle_bag( output_file, color, triangles, boardIU2WRML );
-        triangles.clear( );
-    }
-}
-
-
-bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName,
-                                      double aMMtoWRMLunit, bool aExport3DFiles,
-                                      const wxString & a3D_Subdir )
-{
-    wxString   msg;
-    FILE*      output_file;
-    BOARD*     pcb = GetBoard();
+    }
+}
+
+
+bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName,
+        double aMMtoWRMLunit, bool aExport3DFiles,
+        const wxString& a3D_Subdir )
+{
+    wxString msg;
+    FILE* output_file;
+    BOARD* pcb = GetBoard();
+
+    MODEL_VRML model3d;
+
+    VRMLEXPORT::model_vrml = &model3d;
 
     output_file = wxFopen( aFullFileName, wxT( "wt" ) );
 
@@ -1217,39 +1149,33 @@
                           "  title \"%s - Generated by Pcbnew\"\n"
                           "}\n", TO_UTF8( name ) );
 
-    /* The would be in BIU and not in meters, as the standard wants.
-     * It is trivial to embed everything in a transform node to
-     * fix it. For example here we build the world in inches...
-    */
-
     // Global VRML scale to export to a different scale.
-    // (aMMtoWRMLScale = 1.0 to export in mm)
-    double boardIU2WRML = aMMtoWRMLunit / MM_PER_IU;
+    model3d.scale = aMMtoWRMLunit / MM_PER_IU;
     fprintf( output_file, "Transform {\n" );
 
-    /* Define the translation to have the board centre to the 2D axis origin
-     * more easy for rotations...
-     */
+    // compute the offset to center the board on (0, 0, 0)
+    // XXX - NOTE: we should allow the user a GUI option to specify the offset
     EDA_RECT bbbox = pcb->ComputeBoundingBox();
 
-    double dx = boardIU2WRML * bbbox.Centre().x;
-    double dy = boardIU2WRML * bbbox.Centre().y;
+    model3d.SetOffset( -model3d.scale * bbbox.Centre().x, -model3d.scale * bbbox.Centre().y );
 
-    fprintf( output_file, "  translation %g %g 0.0\n", -dx, dy );
     fprintf( output_file, "  children [\n" );
 
     // Preliminary computation: the z value for each layer
-    compute_layer_Zs( pcb );
-
-    // Drawing and text on the board, and edges which are special
-    export_vrml_drawings( pcb );
+    compute_layer_Zs( model3d, pcb );
+
+    // board edges and cutouts
+    export_vrml_board( model3d, pcb );
+
+    // Drawing and text on the board
+    export_vrml_drawings( model3d, pcb );
 
     // Export vias and trackage
-    export_vrml_tracks( pcb );
+    export_vrml_tracks( model3d, pcb );
 
     // Export zone fills
 /* TODO    export_vrml_zones(pcb);
-*/
+ */
 
     /* scaling factor to convert 3D models to board units (decimils)
      * Usually we use Wings3D to create thems.
@@ -1261,25 +1187,27 @@
 
     // Export footprints
     for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() )
-        export_vrml_module( pcb, module, output_file,
-                            wrml_3D_models_scaling_factor,
-                            aExport3DFiles, a3D_Subdir,
-                            boardIU2WRML );
+        export_vrml_module( model3d, pcb, module, output_file,
+                wrml_3D_models_scaling_factor,
+                aExport3DFiles, a3D_Subdir );
+
+    // write out the board and all layers
+    write_layers( model3d, output_file, pcb );
 
     /* Output the bagged triangles for each layer
      * Each layer will be a separate shape */
-    for( LAYER_NUM layer = FIRST_LAYER; layer < NB_LAYERS; ++layer )
-        write_and_empty_triangle_bag( output_file,
-                                      layer_triangles[layer],
-                                      pcb->GetLayerColor(layer),
-                                      boardIU2WRML );
-
-    // Same thing for the via layers
-    for( int i = 0; i < 4; i++ )
-        write_and_empty_triangle_bag( output_file,
-                                      via_triangles[i],
-                                      pcb->GetVisibleElementColor( VIAS_VISIBLE + i ),
-                                      boardIU2WRML );
+    /* DEPRECATED:  TO BE DELETED
+     *  for( LAYER_NUM layer = FIRST_LAYER; layer < NB_LAYERS; ++layer )
+     *   write_and_empty_triangle_bag( output_file,
+     *                                 model3d.GetLayerBag( layer ),
+     *                                 pcb->GetLayerColor(layer) );
+     *
+     *  // Same thing for the via layers
+     *  for( int i = 0; i < 4; i++ )
+     *   write_and_empty_triangle_bag( output_file,
+     *                                 model3d.GetViaBag( i ),
+     *                                 pcb->GetVisibleElementColor( VIAS_VISIBLE + i ) );
+     */
 
     // Close the outer 'transform' node
     fputs( "]\n}\n", output_file );
@@ -1296,7 +1224,7 @@
  * some characters cannot be used in filenames,
  * this function change them to "_"
  */
-static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal )
+static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal )
 {
     if( aDirSepIsIllegal )
         aFileName.Replace( wxT( "/" ), wxT( "_" ) );

=== added file 'pcbnew/vrml_board.cpp'
--- pcbnew/vrml_board.cpp	1970-01-01 00:00:00 +0000
+++ pcbnew/vrml_board.cpp	2013-12-31 01:49:31 +0000
@@ -0,0 +1,1633 @@
+/*
+ * file: vrml_board.cpp
+ *
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013  Cirilo Bernardo
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/*
+ * NOTES ON OUTPUT PRECISION:
+ *
+ * If we use %.6f then we have no need for special unit dependent formatting:
+ *
+ *  inch: .0254 microns
+ *  mm:   0.001 microns
+ *  m:    1 micron
+ *
+ */
+
+
+#include <sstream>
+#include <string>
+#include <iomanip>
+#include <cmath>
+#include <vrml_board.h>
+
+void FormatDoublet( double x, double y, int precision, std::string& strx, std::string& stry )
+{
+    std::ostringstream ostr;
+
+    ostr << std::fixed << std::setprecision( precision );
+
+    ostr << x;
+    strx = ostr.str();
+
+    ostr.str( "" );
+    ostr << y;
+    stry = ostr.str();
+
+    while( *strx.rbegin() == '0' )
+        strx.erase( strx.size() - 1 );
+
+    while( *stry.rbegin() == '0' )
+        stry.erase( stry.size() - 1 );
+}
+
+
+void FormatSinglet( double x, int precision, std::string& strx )
+{
+    std::ostringstream ostr;
+
+    ostr << std::fixed << std::setprecision( precision );
+
+    ostr << x;
+    strx = ostr.str();
+
+    while( *strx.rbegin() == '0' )
+        strx.erase( strx.size() - 1 );
+}
+
+
+int CalcNSides( double rad, double dev )
+{
+    if( dev <= 0 || rad <= 0 )
+        return 6;
+
+    int csides;
+    double n = dev / rad;
+
+    // note: in the following, the first comparison and csides is chosen to
+    // yield a maximum of 360 segments; in practice we probably want a smaller limit.
+    if( n < 0.0001523048 )
+        csides = 360;
+    else if( n >= 0.5 ) // 0.5 yields an angle >= 60 deg. (6 or fewer sides)
+        csides = 6;
+    else
+        csides = M_PI * 2.0 / acos( 1.0 - n ) + 1;
+
+    if( csides < 6 )
+        csides = 6;
+
+    return csides;
+}
+
+
+void vrml_tess_begin( GLenum cmd, void* user_data )
+{
+    VRML_LAYER* lp = (VRML_LAYER*) user_data;
+
+    lp->glStart( cmd );
+}
+
+
+void vrml_tess_end( void* user_data )
+{
+    VRML_LAYER* lp = (VRML_LAYER*) user_data;
+
+    lp->glEnd();
+}
+
+
+void vrml_tess_vertex( void* vertex_data, void* user_data )
+{
+    VRML_LAYER* lp = (VRML_LAYER*) user_data;
+
+    lp->glPushVertex( (VERTEX_3D*) vertex_data );
+}
+
+
+void vrml_tess_err( GLenum errno, void* user_data )
+{
+    VRML_LAYER* lp = (VRML_LAYER*) user_data;
+
+    lp->Fault = true;
+    // XXX: respond to the error - perhaps set the error type via a call to the VRML_LAYER
+}
+
+
+void vrml_tess_combine( GLdouble coords[3], void* vertex_data[4],
+        GLfloat weight[4], void** outData, void* user_data )
+{
+    VRML_LAYER* lp = (VRML_LAYER*) user_data;
+
+    *outData = lp->AddExtraVertex( coords[0], coords[1] );
+}
+
+
+VRML_LAYER::VRML_LAYER()
+{
+    fix = false;
+    Fault = false;
+    idx = 0;
+    ord = 0;
+    glcmd   = 0;
+    pholes  = NULL;
+    maxdev  = 0.02;
+
+    tess = gluNewTess();
+
+    if( !tess )
+        return;
+
+    // set up the tesselator callbacks
+    gluTessCallback( tess, GLU_TESS_BEGIN_DATA, ( GLvoid (*)() ) & vrml_tess_begin );
+
+    gluTessCallback( tess, GLU_TESS_VERTEX_DATA, ( GLvoid (*)() ) & vrml_tess_vertex );
+
+    gluTessCallback( tess, GLU_TESS_END_DATA, ( GLvoid (*)() ) & vrml_tess_end );
+
+    gluTessCallback( tess, GLU_TESS_ERROR_DATA, ( GLvoid (*)() ) & vrml_tess_err );
+
+    gluTessCallback( tess, GLU_TESS_COMBINE_DATA, ( GLvoid (*)() ) & vrml_tess_combine );
+
+    gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
+
+    gluTessNormal( tess, 0, 0, 1 );
+}
+
+
+VRML_LAYER::~VRML_LAYER()
+{
+    Clear();
+
+    if( tess )
+    {
+        gluDeleteTess( tess );
+        tess = NULL;
+    }
+}
+
+
+// clear all data
+void VRML_LAYER::Clear( void )
+{
+    int i;
+
+    fix = false;
+    idx = 0;
+
+    for( i = contours.size(); i > 0; --i )
+    {
+        delete contours.back();
+        contours.pop_back();
+    }
+
+    for( i = vertices.size(); i > 0; --i )
+    {
+        delete vertices.back();
+        vertices.pop_back();
+    }
+
+    clearTmp();
+}
+
+
+// set the max. deviation of an arc segment
+bool VRML_LAYER::SetMaxDev( double max )
+{
+    // assure max. dev > 2 microns regardless of the
+    // prevailing units ( inch, mm, m, 0.1 inch )
+    if( max < 0.000002 )
+        return false;
+
+    maxdev = max;
+
+    return true;
+}
+
+
+// clear ephemeral data in between invocations of the tesselation routine
+void VRML_LAYER::clearTmp( void )
+{
+    unsigned int i;
+
+    Fault   = false;
+    hidx    = 0;
+    eidx    = 0;
+    ord = 0;
+    glcmd = 0;
+
+    while( !triplets.empty() )
+        triplets.pop_back();
+
+    for( i = outline.size(); i > 0; --i )
+    {
+        delete outline.back();
+        outline.pop_back();
+    }
+
+    for( i = ordmap.size(); i > 0; --i )
+        ordmap.pop_back();
+
+    for( i = extra_verts.size(); i > 0; --i )
+    {
+        delete extra_verts.back();
+        extra_verts.pop_back();
+    }
+
+    // note: unlike outline and extra_verts,
+    // vlist is not responsible for memory management
+    for( i = vlist.size(); i > 0; --i )
+        vlist.pop_back();
+
+    // go through the vertex list and reset ephemeral parameters
+    for( i = 0; i < vertices.size(); ++i )
+    {
+        vertices[i]->o = -1;
+    }
+}
+
+
+// create a new contour to be populated; returns an index
+// into the contour list or -1 if there are problems
+int VRML_LAYER::NewContour( void )
+{
+    if( fix )
+        return -1;
+
+    std::list<int>* contour = new std::list<int>;
+
+    if( !contour )
+        return -1;
+
+    contours.push_back( contour );
+
+    return contours.size() - 1;
+}
+
+
+// adds a vertex to the existing list and places its index in
+// an existing contour; returns true if OK,
+// false otherwise (indexed contour does not exist)
+bool VRML_LAYER::AddVertex( int aContour, double x, double y )
+{
+    if( fix )
+        return false;
+
+    if( aContour < 0 || (unsigned int) aContour >= contours.size() )
+        return false;
+
+    VERTEX_3D* vertex = new VERTEX_3D;
+
+    if( !vertex )
+        return false;
+
+    vertex->x   = x;
+    vertex->y   = y;
+    vertex->i   = idx++;
+    vertex->o   = -1;
+
+    vertices.push_back( vertex );
+    contours[aContour]->push_back( vertex->i );
+
+    return true;
+}
+
+
+// ensure the winding of a contour with respect to the normal (0, 0, 1);
+// set 'hole' to true to ensure a hole (clockwise winding)
+bool VRML_LAYER::EnsureWinding( int aContour, bool hole )
+{
+    if( aContour < 0 || (unsigned int) aContour > contours.size() )
+        return false;
+
+    std::list<int>* cp = contours[aContour];
+
+    if( cp->size() < 3 )
+        return false;
+
+    double dir = 0;
+
+    std::list<int>::const_iterator  cbeg    = cp->begin();
+    std::list<int>::const_iterator  cend    = cp->end();
+
+    double  firstX, firstY;
+    double  lastX, lastY;
+    double  curX, curY;
+
+    firstX  = vertices[ *cbeg ]->x;
+    firstY  = vertices[ *cbeg ]->y;
+    lastX   = firstX;
+    lastY   = firstY;
+    ++cbeg;
+
+    while( cbeg != cend )
+    {
+        curX    = vertices[ *cbeg ]->x;
+        curY    = vertices[ *cbeg ]->y;
+        dir     += ( curX - lastX ) * ( curY + lastY );
+        lastX   = curX;
+        lastY   = curY;
+        ++cbeg;
+    }
+
+    dir += ( firstX - lastX ) * ( firstY + lastY );
+
+    // if dir is positive, winding is CW
+    if( ( hole && dir < 0 ) || ( !hole && dir > 0 ) )
+        cp->reverse();
+
+    return true;
+}
+
+
+// adds a circle the existing list; if 'hole' is true the contour is
+// a hole. Returns true if OK.
+bool VRML_LAYER::AddCircle( double x, double y, double rad, int csides, bool hole )
+{
+    int pad = NewContour();
+
+    if( pad < 0 )
+        return false;
+
+    if( csides < 6 )
+        csides = CalcNSides( rad, maxdev );
+
+    // even numbers give prettier results
+    if( csides & 1 )
+        csides += 1;
+
+    double da = M_PI * 2.0 / csides;
+
+    if( hole )
+    {
+        for( double angle = 0; angle < M_PI * 2; angle += da )
+            AddVertex( pad, x + rad * cos( angle ), y - rad * sin( angle ) );
+    }
+    else
+    {
+        for( double angle = 0; angle < M_PI * 2; angle += da )
+            AddVertex( pad, x + rad * cos( angle ), y + rad * sin( angle ) );
+    }
+
+    return true;
+}
+
+
+// adds a slotted pad with orientation given by angle; if 'hole' is true the
+// contour is a hole. Returns true if OK.
+bool VRML_LAYER::AddSlot( double cx, double cy, double length, double width,
+        double angle, int csides, bool hole )
+{
+    if( width > length )
+    {
+        angle += M_PI2;
+        std::swap( length, width );
+    }
+
+    width   /= 2.0;
+    length  = length / 2.0 - width;
+
+    if( csides < 6 )
+        csides = CalcNSides( width, maxdev );
+
+    if( csides & 1 )
+        csides += 1;
+
+    csides /= 2;
+
+    double capx, capy;
+
+    capx    = cx + cos( angle ) * length;
+    capy    = cy + sin( angle ) * length;
+
+    double ang, da;
+    int i;
+    int pad = NewContour();
+
+    if( pad < 0 )
+        return false;
+
+    da = M_PI / csides;
+
+    if( hole )
+    {
+        for( ang = angle + M_PI2, i = 0; i < csides; ang -= da, ++i )
+            AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        ang = angle - M_PI2;
+        AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        capx    = cx - cos( angle ) * length;
+        capy    = cy - sin( angle ) * length;
+
+        for( ang = angle - M_PI2, i = 0; i < csides; ang -= da, ++i )
+            AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        ang = angle + M_PI2;
+        AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+    }
+    else
+    {
+        for( ang = angle - M_PI2, i = 0; i < csides; ang += da, ++i )
+            AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        ang = angle + M_PI2;
+        AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        capx    = cx - cos( angle ) * length;
+        capy    = cy - sin( angle ) * length;
+
+        for( ang = angle + M_PI2, i = 0; i < csides; ang += da, ++i )
+            AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+        ang = angle - M_PI2;
+        AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) );
+    }
+
+    return true;
+}
+
+
+// adds an arc with the given center, start point, pen width, and angle.
+bool VRML_LAYER::AddArc( double cx, double cy, double startx, double starty,
+        double width, double angle, int csides, bool hole )
+{
+    // we don't accept small angles; in fact, 1 degree ( 0.01745 ) is already
+    // way too small but we must set a limit somewhere
+    if( angle < 0.01745 && angle > -0.01745 )
+        return false;
+
+    double rad = sqrt( (startx - cx) * (startx - cx) + (starty - cy) * (starty - cy) );
+
+    width /= 2.0;    // this is the radius of the caps
+
+    // we will not accept an arc with an inner radius close to zero so we
+    // set a limit here. the end result will vary somewhat depending on
+    // the output units
+    if( width >= ( rad + 0.0001 ) )
+        return false;
+
+    // calculate the radii of the outer and inner arcs
+    double  orad    = rad + width;
+    double  irad    = rad - width;
+
+    int osides  = csides * angle / ( M_PI * 2.0 );
+    int isides  = csides * angle / ( M_PI * 2.0 );
+
+    if( osides < 0 )
+        osides = -osides;
+
+    if( osides < 3 )
+    {
+        osides = CalcNSides( orad, maxdev ) * angle / ( M_PI * 2.0 );
+
+        if( osides < 0 )
+            osides = -osides;
+
+        if( osides < 3 )
+            osides = 3;
+    }
+
+    if( isides < 0 )
+        isides = -isides;
+
+    if( isides < 3 )
+    {
+        isides = CalcNSides( irad, maxdev ) * angle / ( M_PI * 2.0 );
+
+        if( isides < 0 )
+            isides = -isides;
+
+        if( isides < 3 )
+            isides = 3;
+    }
+
+    if( csides < 6 )
+        csides = CalcNSides( width, maxdev );
+
+    if( csides & 1 )
+        csides += 1;
+
+    csides /= 2;
+
+    double  stAngle     = atan2( starty - cy, startx - cx );
+    double  endAngle    = stAngle + angle;
+
+    // calculate ends of inner and outer arc
+    double  oendx   = cx + orad* cos( endAngle );
+    double  oendy   = cy + orad* sin( endAngle );
+    double  ostx    = cx + orad* cos( stAngle );
+    double  osty    = cy + orad* sin( stAngle );
+
+    double  iendx   = cx + irad* cos( endAngle );
+    double  iendy   = cy + irad* sin( endAngle );
+    double  istx    = cx + irad* cos( stAngle );
+    double  isty    = cy + irad* sin( stAngle );
+
+    if( ( angle < 0 && !hole ) || ( angle > 0 && hole ) )
+    {
+        angle = -angle;
+        std::swap( stAngle, endAngle );
+        std::swap( oendx, ostx );
+        std::swap( oendy, osty );
+        std::swap( iendx, istx );
+        std::swap( iendy, isty );
+    }
+
+    int arc = NewContour();
+
+    if( arc < 0 )
+        return false;
+
+    // trace the outer arc:
+    int i;
+    double  ang;
+    double  da = angle / osides;
+
+    for( ang = stAngle, i = 0; i < osides; ang += da, ++i )
+        AddVertex( arc, cx + orad * cos( ang ), cy + orad * sin( ang ) );
+
+    // trace the first cap
+    double  capx    = ( iendx + oendx ) / 2.0;
+    double  capy    = ( iendy + oendy ) / 2.0;
+
+    if( hole )
+        da = -M_PI / csides;
+    else
+        da = M_PI / csides;
+
+    for( ang = endAngle + da, i = 2; i < csides; ang += da, ++i )
+        AddVertex( arc, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    // trace the inner arc:
+    da = -angle / isides;
+
+    for( ang = endAngle, i = 0; i < isides; ang += da, ++i )
+        AddVertex( arc, cx + irad * cos( ang ), cy + irad * sin( ang ) );
+
+    // trace the final cap
+    capx    = ( istx + ostx ) / 2.0;
+    capy    = ( isty + osty ) / 2.0;
+
+    if( hole )
+        da = -M_PI / csides;
+    else
+        da = M_PI / csides;
+
+    for( ang = stAngle + M_PI + da, i = 2; i < csides; ang += da, ++i )
+        AddVertex( arc, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    return true;
+}
+
+
+// tesselates planar features; returns true if all was fine,
+// false otherwise ( error list can be retrieved via XXX )
+bool VRML_LAYER::Tesselate( VRML_HOLES* holes )
+{
+    if( !tess )
+        return false;
+
+    pholes  = holes;
+    Fault   = false;
+
+    if( contours.size() < 1 || vertices.size() < 3 )
+        return false;
+
+    // prevent the addition of any further contours and contour vertices
+    fix = true;
+
+    // clear temporary internals which may have been used in a previous run
+    clearTmp();
+
+    // ensure that we are tesselating the surface
+    gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE );
+
+    // adjust internal indices for extra points and holes
+    if( holes )
+        hidx = holes->GetSize();
+    else
+        hidx = 0;
+
+    eidx = idx + hidx;
+
+    // open the polygon
+    gluTessBeginPolygon( tess, this );
+
+    pushVertices();
+
+    // import holes (if any)
+    if( hidx )
+    {
+        // this may seem like a waste of resources but it improves the eyecandy
+        // with no growth in the VRML file. The repetitive removal of holes
+        // ensures that pads which have up to 3 overlying tracks will be
+        // cleaned up
+        holes->Import( idx, tess );
+        holes->Import( idx, tess );
+        holes->Import( idx, tess );
+    }
+
+    // close the polygon
+    gluTessEndPolygon( tess );
+
+    if( Fault )
+        return false;
+
+    return true;
+}
+
+
+// tesselates the contours in preparation for a 3D output;
+// returns true if all was fine, false otherwise
+// ( error list can be retrieved via XXX )
+bool VRML_LAYER::Tesselate3D( VRML_HOLES* holes )
+{
+    if( !tess )
+        return false;
+
+    pholes  = holes;
+    Fault   = false;
+
+    if( contours.size() < 1 || vertices.size() < 3 )
+        return false;
+
+    // prevent the addition of any further contours and contour vertices
+    fix = true;
+
+    // clear temporary internals which may have been used in a previous run
+    clearTmp();
+
+    // request an outline
+    gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE );
+
+    // adjust internal indices for extra points and holes
+    if( holes )
+        hidx = holes->GetSize();
+    else
+        hidx = 0;
+
+    eidx = idx + hidx;
+
+    // open the polygon
+    gluTessBeginPolygon( tess, this );
+
+    pushVertices();
+
+    // import holes (if any)
+    if( hidx )
+        holes->Import( idx, tess );
+
+    // close the polygon
+    gluTessEndPolygon( tess );
+
+    if( Fault )
+        return false;
+
+    // request a tesselated surface
+    gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE );
+
+    // traverse the outline list to push all used vertices
+    if( outline.size() < 1 )
+        return false;
+
+    gluTessBeginPolygon( tess, this );
+
+    std::list<std::list<int>*>::const_iterator  obeg    = outline.begin();
+    std::list<std::list<int>*>::const_iterator  oend    = outline.end();
+
+    int pi;
+    std::list<int>::const_iterator  begin;
+    std::list<int>::const_iterator  end;
+    GLdouble pt[3];
+    VERTEX_3D* vp;
+
+    while( obeg != oend )
+    {
+        if( (*obeg)->size() < 3 )
+        {
+            ++obeg;
+            continue;
+        }
+
+        gluTessBeginContour( tess );
+
+        begin = (*obeg)->begin();
+        end = (*obeg)->end();
+
+        while( begin != end )
+        {
+            pi = *begin;
+
+            // xxx - this is a fault and must be reported
+            if( pi < 0 || (unsigned int) pi > ordmap.size() )
+                return false;
+
+            // retrieve the actual index
+            pi = ordmap[pi];
+
+            vp = getVertexByIndex( pi, holes );
+
+            // xxx - this is a fault and must be reported
+            if( !vp )
+                return false;
+
+            pt[0]   = vp->x;
+            pt[1]   = vp->y;
+            pt[2]   = 0.0;
+            gluTessVertex( tess, pt, vp );
+            ++begin;
+        }
+
+        gluTessEndContour( tess );
+        ++obeg;
+    }
+
+    gluTessEndPolygon( tess );
+
+    if( Fault )
+        return false;
+
+    return true;
+}
+
+
+// writes out the vertex list;
+// 'z' is the Z coordinate of every point
+bool VRML_LAYER::WriteVertices( double z, FILE* fp )
+{
+    if( !fp )
+        return false;
+
+    if( ordmap.size() < 3 )
+        return false;
+
+    int i, j;
+
+    VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes );
+
+    if( !vp )
+        return false;
+
+    std::string strx, stry, strz;
+    FormatDoublet( vp->x, vp->y, 6, strx, stry );
+    FormatSinglet( z, 6, strz );
+
+    fprintf( fp, "%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+
+    for( i = 1, j = ordmap.size(); i < j; ++i )
+    {
+        vp = getVertexByIndex( ordmap[i], pholes );
+
+        if( !vp )
+            return false;
+
+        FormatDoublet( vp->x, vp->y, 6, strx, stry );
+
+        if( i & 1 )
+            fprintf( fp, ", %s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+        else
+            fprintf( fp, ",\n%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+    }
+
+    return true;
+}
+
+
+// writes out the vertex list for a 3D feature; top and bottom are the
+// Z values for the top and bottom; top must be > bottom
+bool VRML_LAYER::Write3DVertices( double top, double bottom, FILE* fp )
+{
+    if( !fp )
+        return false;
+
+    if( ordmap.size() < 3 )
+        return false;
+
+    if( top <= bottom )
+        return false;
+
+    int i, j;
+
+    VERTEX_3D* vp = getVertexByIndex( ordmap[0], pholes );
+
+    if( !vp )
+        return false;
+
+    std::string strx, stry, strz;
+    FormatDoublet( vp->x, vp->y, 6, strx, stry );
+    FormatSinglet( top, 6, strz );
+
+    fprintf( fp, "%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+
+    for( i = 1, j = ordmap.size(); i < j; ++i )
+    {
+        vp = getVertexByIndex( ordmap[i], pholes );
+
+        if( !vp )
+            return false;
+
+        FormatDoublet( vp->x, vp->y, 6, strx, stry );
+
+        if( i & 1 )
+            fprintf( fp, ", %s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+        else
+            fprintf( fp, ",\n%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+    }
+
+    // repeat for the bottom layer
+    vp = getVertexByIndex( ordmap[0], pholes );
+    FormatDoublet( vp->x, vp->y, 6, strx, stry );
+    FormatSinglet( bottom, 6, strz );
+
+    bool endl;
+
+    if( i & 1 )
+    {
+        fprintf( fp, ", %s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+        endl = false;
+    }
+    else
+    {
+        fprintf( fp, ",\n%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+        endl = true;
+    }
+
+    for( i = 1, j = ordmap.size(); i < j; ++i )
+    {
+        vp = getVertexByIndex( ordmap[i], pholes );
+        FormatDoublet( vp->x, vp->y, 6, strx, stry );
+
+        if( endl )
+        {
+            fprintf( fp, ", %s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+            endl = false;
+        }
+        else
+        {
+            fprintf( fp, ",\n%s %s %s", strx.c_str(), stry.c_str(), strz.c_str() );
+            endl = true;
+        }
+    }
+
+    return true;
+}
+
+
+// writes out the index list;
+// 'top' indicates the vertex ordering and should be
+// true for a polygon visible from above the PCB
+bool VRML_LAYER::WriteIndices( bool top, FILE* fp )
+{
+    if( triplets.empty() )
+        return false;
+
+    // go through the triplet list and write out the indices based on order
+    std::list<TRIPLET_3D>::const_iterator   tbeg    = triplets.begin();
+    std::list<TRIPLET_3D>::const_iterator   tend    = triplets.end();
+
+    int i = 1;
+
+    if( top )
+        fprintf( fp, "%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+    else
+        fprintf( fp, "%d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
+
+    ++tbeg;
+
+    while( tbeg != tend )
+    {
+        if( (i++ & 7) == 4 )
+        {
+            i = 1;
+
+            if( top )
+                fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+            else
+                fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
+        }
+        else
+        {
+            if( top )
+                fprintf( fp, ", %d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+            else
+                fprintf( fp, ", %d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
+        }
+
+        ++tbeg;
+    }
+
+    return true;
+}
+
+
+// writes out the index list for a 3D feature
+bool VRML_LAYER::Write3DIndices( FILE* fp )
+{
+    if( triplets.empty() )
+        return false;
+
+    if( outline.empty() )
+        return false;
+
+    // go through the triplet list and write out the indices based on order
+    std::list<TRIPLET_3D>::const_iterator   tbeg    = triplets.begin();
+    std::list<TRIPLET_3D>::const_iterator   tend    = triplets.end();
+
+    int i = 1;
+    int idx2 = ordmap.size();    // index to the bottom vertices
+
+    // print out the top vertices
+    fprintf( fp, "%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+    ++tbeg;
+
+    while( tbeg != tend )
+    {
+        if( (i++ & 7) == 4 )
+        {
+            i = 1;
+            fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+        }
+        else
+        {
+            fprintf( fp, ", %d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
+        }
+
+        ++tbeg;
+    }
+
+    // print out the bottom vertices
+    tbeg = triplets.begin();
+
+    while( tbeg != tend )
+    {
+        if( (i++ & 7) == 4 )
+        {
+            i = 1;
+            fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i2 + idx2, tbeg->i1 + idx2, tbeg->i3 + idx2 );
+        }
+        else
+        {
+            fprintf( fp, ", %d, %d, %d, -1", tbeg->i2 + idx2, tbeg->i1 + idx2, tbeg->i3 + idx2 );
+        }
+
+        ++tbeg;
+    }
+
+    int firstPoint;
+    int lastPoint;
+    int curPoint;
+
+    std::list<std::list<int>*>::const_iterator  obeg    = outline.begin();
+    std::list<std::list<int>*>::const_iterator  oend    = outline.end();
+    std::list<int>* cp;
+    std::list<int>::const_iterator  cbeg;
+    std::list<int>::const_iterator  cend;
+
+    while( obeg != oend )
+    {
+        cp = *obeg;
+
+        if( cp->size() < 3 )
+        {
+            ++obeg;
+            continue;
+        }
+
+        cbeg    = cp->begin();
+        cend    = cp->end();
+
+        firstPoint  = *(cbeg++);
+        lastPoint   = firstPoint;
+
+        while( cbeg != cend )
+        {
+            curPoint = *(cbeg++);
+            fprintf( fp, ",\n %d, %d, %d, -1, %d, %d, %d, -1",
+                    curPoint, lastPoint, curPoint + idx2,
+                    curPoint + idx2, lastPoint, lastPoint + idx2 );
+            lastPoint = curPoint;
+        }
+
+        fprintf( fp, ",\n %d, %d, %d, -1, %d, %d, %d, -1",
+                firstPoint, lastPoint, firstPoint + idx2,
+                firstPoint + idx2, lastPoint, lastPoint + idx2 );
+
+        ++obeg;
+    }
+
+    return true;
+}
+
+
+// add a triangular facet (triplet) to the ouptut index list
+bool VRML_LAYER::addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 )
+{
+    double  dx0 = p1->x - p0->x;
+    double  dx1 = p2->x - p0->x;
+
+    double  dy0 = p1->y - p0->y;
+    double  dy1 = p2->y - p0->y;
+
+    // this number is chosen because we shall only write 6 decimal places
+    // on the VRML output
+    double err = 0.000001;
+
+    // test if the triangles are degenerate (parallel sides)
+
+    if( dx0 < err && dx0 > -err && dx1 < err && dx1 > -err )
+        return false;
+
+    if( dy0 < err && dy0 > -err && dy1 < err && dy1 > -err )
+        return false;
+
+    double  sl0 = dy0 / dx0;
+    double  sl1 = dy1 / dx1;
+
+    double dsl = sl1 - sl0;
+
+    if( dsl < err && dsl > -err )
+        return false;
+
+    triplets.push_back( TRIPLET_3D( p0->o, p1->o, p2->o ) );
+
+    return true;
+}
+
+
+// add an extra vertex (to be called only by the COMBINE callback)
+VERTEX_3D* VRML_LAYER::AddExtraVertex( double x, double y )
+{
+    VERTEX_3D* vertex = new VERTEX_3D;
+
+    if( !vertex )
+        return NULL;
+
+    if( eidx == 0 )
+        eidx = idx + hidx;
+
+    vertex->x   = x;
+    vertex->y   = y;
+    vertex->i   = eidx++;
+    vertex->o   = -1;
+
+    extra_verts.push_back( vertex );
+
+    return vertex;
+}
+
+
+// start a GL command list
+void VRML_LAYER::glStart( GLenum cmd )
+{
+    glcmd = cmd;
+
+    while( !vlist.empty() )
+        vlist.pop_back();
+}
+
+
+// process a vertex
+void VRML_LAYER::glPushVertex( VERTEX_3D* vertex )
+{
+    if( vertex->o < 0 )
+    {
+        vertex->o = ord++;
+        ordmap.push_back( vertex->i );
+    }
+
+    vlist.push_back( vertex );
+}
+
+
+// end a GL command list
+void VRML_LAYER::glEnd( void )
+{
+    switch( glcmd )
+    {
+    case GL_LINE_LOOP:
+        {
+            // add the loop to the list of outlines
+            std::list<int>* loop = new std::list<int>;
+
+            if( !loop )
+                break;
+
+            for( unsigned int i = 0; i < vlist.size(); ++i )
+            {
+                loop->push_back( vlist[i]->o );
+            }
+
+            outline.push_back( loop );
+        }
+        break;
+
+    case GL_TRIANGLE_FAN:
+        processFan();
+        break;
+
+    case GL_TRIANGLE_STRIP:
+        processStrip();
+        break;
+
+    case GL_TRIANGLES:
+        processTri();
+        break;
+
+    default:
+        fprintf( stderr, "XXX: bad command\n" );
+        break;
+    }
+
+    while( !vlist.empty() )
+        vlist.pop_back();
+
+    glcmd = 0;
+}
+
+
+// process a GL_TRIANGLE_FAN list
+void VRML_LAYER::processFan( void )
+{
+    if( vlist.size() < 3 )
+        return;
+
+    VERTEX_3D* p0 = vlist[0];
+
+    int i;
+    int end = vlist.size();
+
+    for( i = 2; i < end; ++i )
+    {
+        addTriplet( p0, vlist[i - 1], vlist[i] );
+    }
+}
+
+
+// process a GL_TRIANGLE_STRIP list
+void VRML_LAYER::processStrip( void )
+{
+    // note: (source: http://www.opengl.org/wiki/Primitive)
+    // GL_TRIANGLE_STRIP​: Every group of 3 adjacent vertices forms a triangle.
+    // The face direction of the strip is determined by the winding of the
+    // first triangle. Each successive triangle will have its effective face
+    // order reverse, so the system compensates for that by testing it in the
+    // opposite way. A vertex stream of n length will generate n-2 triangles.
+
+    if( vlist.size() < 3 )
+        return;
+
+    int i;
+    int end = vlist.size();
+    bool flip = false;
+
+    for( i = 2; i < end; ++i )
+    {
+        if( flip )
+        {
+            addTriplet( vlist[i - 1], vlist[i - 2], vlist[i] );
+            flip = false;
+        }
+        else
+        {
+            addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] );
+            flip = true;
+        }
+    }
+}
+
+
+// process a GL_TRIANGLES list
+void VRML_LAYER::processTri( void )
+{
+    // notes:
+    // 1. each successive group of 3 vertices is a triangle
+    // 2. as per OpenGL specification, any incomplete triangles are to be ignored
+
+    if( vlist.size() < 3 )
+        return;
+
+    int i;
+    int end = vlist.size();
+
+    for( i = 2; i < end; i += 3 )
+        addTriplet( vlist[i - 2], vlist[i - 1], vlist[i] );
+}
+
+
+// push the internally held vertices
+void VRML_LAYER::pushVertices( void )
+{
+    // push the internally held vertices
+    unsigned int i;
+
+    std::list<int>::const_iterator  begin;
+    std::list<int>::const_iterator  end;
+    GLdouble pt[3];
+    VERTEX_3D* vp;
+
+    for( i = 0; i < contours.size(); ++i )
+    {
+        if( contours[i]->size() < 3 )
+            continue;
+
+        gluTessBeginContour( tess );
+
+        begin = contours[i]->begin();
+        end = contours[i]->end();
+
+        while( begin != end )
+        {
+            vp = vertices[ *begin ];
+            pt[0]   = vp->x;
+            pt[1]   = vp->y;
+            pt[2]   = 0.0;
+            gluTessVertex( tess, pt, vp );
+            ++begin;
+        }
+
+        gluTessEndContour( tess );
+    }
+}
+
+
+VERTEX_3D* VRML_LAYER::getVertexByIndex( int index, VRML_HOLES* holes )
+{
+    if( index < 0 || (unsigned int) index >= ( idx + hidx + extra_verts.size() ) )
+        return NULL;
+
+    if( index < idx )
+    {
+        // vertex is in the vertices[] list
+        return vertices[ index ];
+    }
+    else if( index >= idx + hidx )
+    {
+        // vertex is in the extra_verts[] list
+        return extra_verts[index - idx - hidx];
+    }
+
+    // vertex is in the holes object
+    if( !holes )
+        return NULL;
+
+    return holes->GetVertexByIndex( index );
+}
+
+
+VRML_HOLES::VRML_HOLES()
+{
+    indexed = false;
+    // a max. deviation of 0.02 units;
+    // this is appropriate for mm but not for others
+    maxdev = 0.02;
+}
+
+
+VRML_HOLES::~VRML_HOLES()
+{
+    Clear();
+}
+
+
+// clear all data
+void VRML_HOLES::Clear( void )
+{
+    indexed = false;
+
+    std::vector<VERTEX_3D*>* item;
+
+    for( int i = contours.size(); i > 0; --i )
+    {
+        item = contours.back();
+
+        for( int j = item->size(); j > 0; --j )
+        {
+            delete item->back();
+            item->pop_back();
+        }
+
+        contours.pop_back();
+    }
+
+    // note: the index is not responsible for
+    // memory management of the vertices
+    while( !index.empty() )
+        index.pop_back();
+}
+
+
+// set the max. deviation of an arc segment
+bool VRML_HOLES::SetMaxDev( double max )
+{
+    // assure max. dev > 2 microns regardless of the
+    // prevailing units ( inch, mm, m, 0.1 inch )
+    if( max < 0.000002 )
+        return false;
+
+    maxdev = max;
+
+    return true;
+}
+
+
+// retrieve the total number of vertices
+int VRML_HOLES::GetSize( void )
+{
+    if( !indexed && !buildIndex() )
+        return 0;
+
+    return index.size();
+}
+
+
+// create a new contour to be populated; returns an index
+// to the contour vector or -1 if there are problems
+int VRML_HOLES::NewContour( void )
+{
+    indexed = false;
+
+    std::vector<VERTEX_3D*>* contour = new std::vector<VERTEX_3D*>;
+
+    if( !contour )
+        return -1;
+
+    contours.push_back( contour );
+
+    return contours.size() - 1;
+}
+
+
+// adds a vertex to the existing list and places its index in
+// an existing contour; returns true if OK,
+// false otherwise (indexed contour does not exist)
+bool VRML_HOLES::AddVertex( int aContour, double x, double y )
+{
+    indexed = false;
+
+    if( aContour < 0 || (unsigned int) aContour >= contours.size() )
+        return false;
+
+    VERTEX_3D* vertex = new VERTEX_3D;
+
+    if( !vertex )
+        return false;
+
+    vertex->x   = x;
+    vertex->y   = y;
+    vertex->i   = -1;
+    vertex->o   = -1;
+
+    contours[aContour]->push_back( vertex );
+
+    return true;
+}
+
+
+// adds a circular hole
+bool VRML_HOLES::AddHole( double x, double y, double rad, int csides )
+{
+    indexed = false;
+
+    int hole = NewContour();
+
+    if( hole < 0 )
+        return false;
+
+    if( csides < 6 )
+        csides = CalcNSides( rad, maxdev );
+
+    // even numbers give prettier results
+    if( csides & 1 )
+        csides += 1;
+
+    double da = M_PI * 2.0 / csides;
+
+    for( double angle = 0; angle < M_PI * 2; angle += da )
+        AddVertex( hole, x + rad * cos( angle ), y - rad * sin( angle ) );
+
+    return true;
+}
+
+
+// adds a slotted hole with orientation given by angle
+bool VRML_HOLES::AddSlot( double cx, double cy, double length, double width,
+        double angle, int csides )
+{
+    indexed = false;
+
+    if( width > length )
+    {
+        angle += M_PI2;
+        std::swap( length, width );
+    }
+
+    width   /= 2.0;
+    length  = length / 2.0 - width;
+
+    if( csides < 6 )
+        csides = CalcNSides( width, maxdev );
+
+    csides /= 2;
+
+    if( csides & 1 )
+        csides += 1;
+
+    double capx, capy;
+
+    capx    = cx + cos( angle ) * length;
+    capy    = cy + sin( angle ) * length;
+
+    double ang, da;
+    int i;
+    int hole = NewContour();
+
+    if( hole < 0 )
+        return false;
+
+    da = M_PI / csides;
+
+    for( ang = angle + M_PI2, i = 0; i < csides; ang -= da, ++i )
+        AddVertex( hole, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    ang = angle - M_PI2;
+    AddVertex( hole, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    capx    = cx - cos( angle ) * length;
+    capy    = cy - sin( angle ) * length;
+
+    for( ang = angle - M_PI2, i = 0; i < csides; ang -= da, ++i )
+        AddVertex( hole, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    ang = angle + M_PI2;
+    AddVertex( hole, capx + width * cos( ang ), capy + width * sin( ang ) );
+
+    return true;
+}
+
+
+// ensure the winding of a hole contour with respect to the normal (0, 0, 1)
+bool VRML_HOLES::EnsureWinding( int aContour )
+{
+    if( aContour < 0 || (unsigned int) aContour > contours.size() )
+        return false;
+
+    std::vector<VERTEX_3D*>* cp = contours[aContour];
+
+    if( cp->size() < 3 )
+        return false;
+
+    int i   = 0;
+    int j   = cp->size();
+
+    double  dir = 0;
+    double  firstX, firstY;
+    double  lastX, lastY;
+    double  curX, curY;
+
+    firstX  = (*cp)[i]->x;
+    firstY  = (*cp)[i++]->y;
+    lastX   = firstX;
+    lastY   = firstY;
+
+    while( i < j )
+    {
+        curX    = (*cp)[i]->x;
+        curY    = (*cp)[i++]->y;
+        dir     += ( curX - lastX ) * ( curY + lastY );
+        lastX   = curX;
+        lastY   = curY;
+    }
+
+    dir += ( firstX - lastX ) * ( firstY + lastY );
+
+    // if dir is negative, winding is CCW
+    if( dir < 0 )
+    {
+        i   = 0;
+        j   -= 1;
+        VERTEX_3D* vp;
+
+        while( i < j )
+        {
+            vp = (*cp)[i];
+            (*cp)[i++]  = (*cp)[j];
+            (*cp)[j--]  = vp;
+        }
+    }
+
+    return true;
+}
+
+
+// Inserts all contours into the given tesselator; this results in the
+// renumbering of all vertices from 'start'. Returns the end number.
+// Take care when using this call since tesselators cannot work on
+// the internal data concurrently
+int VRML_HOLES::Import( int start, GLUtesselator* tess )
+{
+    if( start < 0 )
+        return -1;
+
+    if( !tess )
+        return -1;
+
+    if( !indexed && !buildIndex() )
+        return -1;
+
+    unsigned int i, j;
+
+    // renumber from 'start'
+    for( i = 0, j = index.size(); i < j; ++i )
+    {
+        index[i]->i = start++;
+        index[i]->o = -1;
+    }
+
+    // push each hole contour to the tesselator
+    std::vector<VERTEX_3D*>* cp;
+    GLdouble pt[3];
+
+    for( i = 0; i < contours.size(); ++i )
+    {
+        if( contours[i]->size() < 3 )
+            continue;
+
+        cp = contours[i];
+
+        gluTessBeginContour( tess );
+
+        for( j = 0; j < cp->size(); ++j )
+        {
+            pt[0]   = (*cp)[j]->x;
+            pt[1]   = (*cp)[j]->y;
+            pt[2]   = 0;
+            gluTessVertex( tess, pt, (*cp)[j] );
+        }
+
+        gluTessEndContour( tess );
+    }
+
+    return start;
+}
+
+
+// return the vertex identified by index
+VERTEX_3D* VRML_HOLES::GetVertexByIndex( int ptindex )
+{
+    if( !indexed && !buildIndex() )
+        return NULL;
+
+    int i0 = index[0]->i;
+
+    if( ptindex < i0 || ptindex >= ( i0 + (int) index.size() ) )
+        return NULL;
+
+    return index[ptindex - i0];
+}
+
+
+bool VRML_HOLES::buildIndex( void )
+{
+    indexed = false;
+
+    while( !index.empty() )
+        index.pop_back();
+
+    unsigned int i, j;
+
+    for( i = 0; i < contours.size(); ++i )
+    {
+        if( contours[i]->size() < 3 )
+            continue;
+
+        std::vector<VERTEX_3D*>* cp = contours[i];
+
+        for( j = 0; j < cp->size(); ++j )
+            index.push_back( (*cp)[j] );
+    }
+
+    if( index.size() > 3 )
+        indexed = true;
+
+    return indexed;
+}

=== added file 'pcbnew/vrml_board.h'
--- pcbnew/vrml_board.h	1970-01-01 00:00:00 +0000
+++ pcbnew/vrml_board.h	2013-12-31 03:04:50 +0000
@@ -0,0 +1,481 @@
+/*
+ * file: vrml_board.h
+ *
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2013  Cirilo Bernardo
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/**
+ *  @file vrml_board.h
+ */
+
+/*
+ *  Classes and structures to support the tesselation of a
+ *  PCB for VRML output.
+ */
+
+#ifndef VRML_BOARD_H
+#define VRML_BOARD_H
+
+#ifdef __WXMAC__
+#  ifdef __DARWIN__
+#    include <OpenGL/glu.h>
+#  else
+#    include <glu.h>
+#  endif
+#else
+#  include <GL/glu.h>
+#endif
+
+#include <cstdio>
+#include <vector>
+#include <list>
+#include <utility>
+
+#ifndef M_PI2
+#define M_PI2 ( M_PI / 2.0 )
+#endif
+
+#ifndef M_PI4
+#define M_PI4 ( M_PI / 4.0 )
+#endif
+
+struct GLUtesselator;
+
+struct VERTEX_3D
+{
+    double  x;
+    double  y;
+    int i;          // vertex index
+    int o;          // vertex order
+};
+
+struct TRIPLET_3D
+{
+    int i1, i2, i3;
+
+    TRIPLET_3D( int p1, int p2, int p3 )
+    {
+        i1  = p1;
+        i2  = p2;
+        i3  = p3;
+    }
+};
+
+
+/**
+ * @class VRML_HOLES
+ * stores the contours of cutouts and thru holes
+ */
+class VRML_HOLES
+{
+private:
+    bool indexed;                                   ///< true if an index has been built
+    std::vector<std::vector<VERTEX_3D*>*> contours; ///< vectors of vertices for each contour
+    std::vector<VERTEX_3D*> index;                  ///< index to all current vertices
+
+    ///< max. deviation used in calculating number of segments in an arc
+    double maxdev;
+
+    // build an index to all existing vertices; return
+    // true if indexing succeeded; otherwise false
+    bool buildIndex( void );
+
+public:
+    VRML_HOLES();
+
+    virtual ~VRML_HOLES();
+
+    /**
+     * Function Clear
+     * erases all data.
+     */
+    void Clear( void );
+
+    /**
+     * Function GetSize
+     * returns the total number of vertices indexed
+     */
+    int GetSize( void );
+
+    /**
+     * Function SetMaxDev
+     * sets the maximum deviation from a circle; this parameter is
+     * used for the automatic calculation of segments within a
+     * circle or an arc.
+     *
+     * @param max is the maximum deviation from a perfect circle or arc;
+     * minimum value is 0.000002 units
+     *
+     * @return bool: true if the value was accepted
+     */
+    bool SetMaxDev( double max );
+
+    /**
+     * Function NewContour
+     * creates a new list of vertices and returns an index to the list
+     *
+     * @return int: index to the list or -1 if the operation failed
+     */
+    int NewContour( void );
+
+    /**
+     * Function AddVertex
+     * adds a point to the requested contour
+     *
+     * @param aContour is an index previously returned by a call to NewContour()
+     * @param x is the X coordinate of the vertex
+     * @param y is the Y coordinate of the vertex
+     *
+     * @return bool: true if the vertex was added
+     */
+    bool AddVertex( int aContour, double x, double y );
+
+    /**
+     * Function AddHole
+     * adds a circular hole to the list of contours.
+     *
+     * @param x is the X coordinate of the hole center
+     * @param y is the Y coordinate of the hole center
+     * @param rad is the radius of the hole
+     * @param csides is the number of sides (segments) in a circle;
+     *      use a value of 1 to automatically calculate a suitable number.
+     *
+     * @return bool: true if the new contour was successfully created
+     */
+    bool AddHole( double x, double y, double rad, int csides );
+
+    /**
+     * Function AddSlot
+     * adds a slotted hole to the list of contours
+     *
+     * @param cx is the X coordinate of the hole center
+     * @param cy is the Y coordinate of the hole center
+     * @param length is the length of the slot along the major axis
+     * @param width is the width of the slot along the minor axis
+     * @param angle (radians) is the orientation of the slot
+     * @param csides is the number of sides to a circle; use 1 to
+     *  take advantage of automatic calculations.
+     *
+     * @return bool: true if the slot was successfully created
+     */
+    bool AddSlot( double cx, double cy, double length, double width,
+            double angle, int csides );
+
+    /**
+     * Function EnsureWinding
+     * checks the winding of a contour and ensures that it is a hole
+     *
+     * @param aContour is an index to a contour as returned by NewContour()
+     *
+     * @return bool: true if the operation suceeded
+     */
+    bool EnsureWinding( int aContour );
+
+    /**
+     * Function Import
+     * inserts all contours into the given tesselator; this results in the
+     * renumbering of all vertices from @param start.
+     * Take care when using this call since tesselators cannot work on
+     * the internal data concurrently.
+     *
+     * @param start is the starting number for vertex indices
+     * @param tess is a pointer to a GLU Tesselator object
+     *
+     * @return int: the number of vertices exported
+     */
+    int Import( int start, GLUtesselator* tess );
+
+    // return the vertex identified by index
+    /**
+     * Function GetVertexByIndex
+     * returns a pointer to the requested vertex or
+     * NULL if no such vertex exists.
+     *
+     * @param ptindex is a vertex index
+     *
+     * @return VERTEX_3D*: the requested vertex or NULL
+     */
+    VERTEX_3D* GetVertexByIndex( int ptindex );
+};
+
+
+class VRML_LAYER
+{
+private:
+    bool    fix;                            // when true, no more vertices may be added by the user
+    int     idx;                            // vertex index (number of contained vertices)
+    int     ord;                            // vertex order (number of ordered vertices)
+    std::vector<VERTEX_3D*> vertices;       // vertices of all contours
+    std::vector<std::list<int>*> contours;  // lists of vertices for each contour
+    std::list<TRIPLET_3D> triplets;         // output facet triplet list (triplet of ORDER values)
+    std::list<std::list<int>*> outline;     // indices for outline outputs (index by ORDER values)
+    std::vector<int> ordmap;                // mapping of ORDER to INDEX
+
+    double maxdev;                          // max. deviation from circle when calculating N sides
+
+    int hidx;                               // number of vertices in the holes
+    int eidx;                               // index for extra vertices
+    std::vector<VERTEX_3D*> extra_verts;    // extra vertices added for outlines and facets
+    std::vector<VERTEX_3D*> vlist;          // vertex list for the GL command in progress
+    VRML_HOLES* pholes;                     // pointer to hole object used for tesselation
+
+    GLUtesselator* tess;                    // local instance of the GLU tesselator
+
+    GLenum glcmd;                           // current GL command type ( fan, triangle, tri-strip, loop )
+
+    void clearTmp( void );                  // clear ephemeral data used by the tesselation routine
+
+    // add a triangular facet (triplet) to the output index list
+    bool addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 );
+
+    // retrieve a vertex given its index; the vertex may be contained in the
+    // vertices vector, extra_verts vector, or VRML_HOLES object
+    VERTEX_3D* getVertexByIndex( int index, VRML_HOLES* holes );
+
+    void    processFan( void );                 // process a GL_TRIANGLE_FAN list
+    void    processStrip( void );               // process a GL_TRIANGLE_STRIP list
+    void    processTri( void );                 // process a GL_TRIANGLES list
+
+    void pushVertices( void );                  // push the internal vertices
+
+public:
+    /// set to true when a fault is encountered during tesselation
+    bool Fault;
+
+    VRML_LAYER();
+    virtual ~VRML_LAYER();
+
+    /**
+     * Function Clear
+     * erases all data.
+     */
+    void Clear( void );
+
+    /**
+     * Function SetMaxDev
+     * sets the maximum deviation from a circle; this parameter is
+     * used for the automatic calculation of segments within a
+     * circle or an arc.
+     *
+     * @param max is the maximum deviation from a perfect circle or arc;
+     * minimum value is 0.000002 units
+     *
+     * @return bool: true if the value was accepted
+     */
+    bool SetMaxDev( double max );
+
+    /**
+     * Function NewContour
+     * creates a new list of vertices and returns an index to the list
+     *
+     * @return int: index to the list or -1 if the operation failed
+     */
+    int NewContour( void );
+
+    /**
+     * Function AddVertex
+     * adds a point to the requested contour
+     *
+     * @param aContour is an index previously returned by a call to NewContour()
+     * @param x is the X coordinate of the vertex
+     * @param y is the Y coordinate of the vertex
+     *
+     * @return bool: true if the vertex was added
+     */
+    bool AddVertex( int aContour, double x, double y );
+
+    /**
+     * Function EnsureWinding
+     * checks the winding of a contour and ensures that it is a hole or
+     * a solid depending on the value of @param hole
+     *
+     * @param aContour is an index to a contour as returned by NewContour()
+     * @param hole determines if the contour must be a hole
+     *
+     * @return bool: true if the operation suceeded
+     */
+    bool EnsureWinding( int aContour, bool hole );
+
+    /**
+     * Function AddCircle
+     * creates a circular contour and adds it to the internal list
+     *
+     * @param x is the X coordinate of the hole center
+     * @param y is the Y coordinate of the hole center
+     * @param rad is the radius of the hole
+     * @param csides is the number of sides (segments) in a circle;
+     *      use a value of 1 to automatically calculate a suitable number.
+     * @param hole determines if the contour to be created is a cutout
+     *
+     * @return bool: true if the new contour was successfully created
+     */
+    bool AddCircle( double x, double y, double rad, int csides, bool hole = false );
+
+    /**
+     * Function AddSlot
+     * creates and adds a slot feature to the list of contours
+     *
+     * @param cx is the X coordinate of the slot
+     * @param cy is the Y coordinate of the slot
+     * @param length is the length of the slot along the major axis
+     * @param width is the width of the slot along the minor axis
+     * @param angle (radians) is the orientation of the slot
+     * @param csides is the number of sides to a circle; use 1 to
+     *  take advantage of automatic calculations.
+     * @param hole determines whether the slot is a hole or a solid
+     *
+     * @return bool: true if the slot was successfully created
+     */
+    bool AddSlot( double cx, double cy, double length, double width,
+            double angle, int csides, bool hole = false );
+
+    /**
+     * Function AddArc
+     * creates an arc and adds it to the internal list of contours
+     *
+     * @param cx is the X coordinate of the arc's center
+     * @param cy is the Y coordinate of the arc's center
+     * @param startx is the X coordinate of the starting point
+     * @param starty is the Y coordinate of the starting point
+     * @param width is the width of the arc
+     * @param angle is the included angle
+     * @param csides is the number of segments in a circle; use 1
+     *  to take advantage of automatic calculations of this number
+     * @param hole determined whether the arc is to be a hole or a solid
+     *
+     * @return bool: true if the feature was successfully created
+     */
+    bool AddArc( double cx, double cy, double startx, double starty,
+            double width, double angle, int csides, bool hole = false );
+
+
+    /**
+     * Function Tesselate
+     * calculates the vertex sets required to render the surface
+     *
+     * @param holes is a pointer to cutouts to be imposed on the
+     * surface.
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool Tesselate( VRML_HOLES* holes );
+
+    /**
+     * Function Tesselate3D
+     * creates a list of outline vertices as well as the
+     * vertex sets required to render the surface. The outline
+     * vertices are used by Tesselate3D() to create an extruded
+     * solid (that is, the PCB)
+     *
+     * @param holes  is a pointer to cutouts to be imposed on the
+     * surface.
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool Tesselate3D( VRML_HOLES* holes );
+
+    /**
+     * Function WriteVertices
+     * writes out the list of vertices required to render a
+     * planar surface.
+     *
+     * @param z is the Z coordinate of the plane
+     * @param fp is the file to write to
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool WriteVertices( double z, FILE* fp );
+
+    /**
+     * Function Write3DVertices
+     * writes out the list of vertices required to render an extruded solid
+     *
+     * @param top is the Z coordinate of the top plane
+     * @param bottom is the Z coordinate of the bottom plane
+     * @param fp is the file to write to
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool Write3DVertices( double top, double bottom, FILE* fp );
+
+    /**
+     * Function WriteIndices
+     * writes out the vertex sets required to render a planar
+     * surface.
+     *
+     * @param top is true if the surface is to be visible from above;
+     * if false the surface will be visible from below.
+     * @param fp is the file to write to
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool WriteIndices( bool top, FILE* fp );
+
+    /**
+     * Function Write3DIndices
+     * writes out the vertex sets required to render an extruded solid
+     *
+     * @param fp is the file to write to
+     *
+     * @return bool: true if the operation succeeded
+     */
+    bool Write3DIndices( FILE* fp );
+
+    /**
+     * Function AddExtraVertex
+     * adds an extra vertex as required by the GLU tesselator
+     *
+     * @return VERTEX_3D*: is the new vertex or NULL if a vertex
+     * could not be created.
+     */
+    VERTEX_3D* AddExtraVertex( double x, double y );
+
+    /**
+     * Function glStart
+     * is invoked by the GLU tesselator callback to notify this object
+     * of the type of GL command which is applicable to the upcoming
+     * vertex list.
+     *
+     * @param cmd is the GL command
+     */
+    void glStart( GLenum cmd );
+
+    /**
+     * Function glPushVertex
+     * is invoked by the GLU tesselator callback; the supplied vertex is
+     * added to the internal list of vertices awaiting processing upon
+     * execution of glEnd()
+     *
+     * @param vertex is a vertex forming part of the GL command as previously
+     * set by glStart
+     */
+    void glPushVertex( VERTEX_3D* vertex );
+
+    /**
+     * Function glEnd
+     * is invoked by the GLU tesselator callback to notify this object
+     * that the vertex list is complete and ready for processing
+     */
+    void glEnd( void );
+};
+
+#endif    // VRML_BOARD_H