From 714d5b28d29f8dc28af599b851f002d7811366f5 Mon Sep 17 00:00:00 2001 From: jean-pierre charras Date: Mon, 6 Jan 2014 21:14:24 +0100 Subject: [PATCH] Better vrml export, from Cirilo Bernardo. --- pcbnew/CMakeLists.txt | 1 + pcbnew/export_vrml.cpp | 1542 +++++++++++++++++++++------------------- pcbnew/vrml_board.cpp | 1501 ++++++++++++++++++++++++++++++++++++++ pcbnew/vrml_board.h | 379 ++++++++++ 4 files changed, 2682 insertions(+), 741 deletions(-) create mode 100644 pcbnew/vrml_board.cpp create mode 100644 pcbnew/vrml_board.h diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 1816bf42c9..4822ac28ba 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -177,6 +177,7 @@ set( PCBNEW_CLASS_SRCS export_gencad.cpp export_idf.cpp export_vrml.cpp + vrml_board.cpp files.cpp gen_drill_report_files.cpp gen_modules_placefile.cpp diff --git a/pcbnew/export_vrml.cpp b/pcbnew/export_vrml.cpp index 580fe40a0c..866712f8b9 100644 --- a/pcbnew/export_vrml.cpp +++ b/pcbnew/export_vrml.cpp @@ -2,6 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2009-2013 Lorenzo Mercantonio + * Copyright (C) 2014 Cirilo Bernado * Copyright (C) 2013 Jean-Pierre Charras jp.charras at wanadoo.fr * Copyright (C) 2004-2013 KiCad Developers, see change_log.txt for contributors. * @@ -22,6 +23,44 @@ * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ + +/* + * NOTE: + * 1. 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 + * + * 2. How can we suppress fiducials such as those in the corners of the pic-programmer demo? + * + * 3. Export Graphics to Layer objects (see 3d_draw.cpp for clues) to ensure that custom + * tracks/fills/logos are rendered. + * + * 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 #include #include @@ -36,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -44,183 +84,210 @@ #include #include - -// 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 /* helper function: * some characters cannot be used in names, * this function change them to "_" */ -static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal ); +static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal ); -// I use this a lot... -static const double PI2 = M_PI / 2; - -struct POINT_3D +struct VRML_COLOR { - double x, y, z; -}; + float diffuse_red; + float diffuse_grn; + float diffuse_blu; -struct POINT_2D -{ - POINT_2D( double _x = 0, double _y = 0 ) : x( _x ), y( _y ) - { } - double x, y; -}; + float spec_red; + float spec_grn; + float spec_blu; -// 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 ) + float emit_red; + float emit_grn; + float emit_blu; + + float ambient; + float transp; + float shiny; + + VRML_COLOR() { - 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 TRIANGLEBAG; + // default green + diffuse_red = 0.13; + diffuse_grn = 0.81; + diffuse_blu = 0.22; + spec_red = 0.13; + spec_grn = 0.81; + spec_blu = 0.22; + emit_red = 0.0; + emit_grn = 0.0; + emit_blu = 0.0; -// A flat triangle fan -struct FLAT_FAN -{ - POINT_2D c; - std::vector 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 inner; - std::vector outer; - void add_inner( double x, double y ) - { - inner.push_back( POINT_2D( x, y ) ); + ambient = 1.0; + transp = 0; + shiny = 0.2; } - void add_outer( double x, double y ) + VRML_COLOR( float dr, float dg, float db, + float sr, float sg, float sb, + float er, float eg, float eb, + float am, float tr, float sh ) { - outer.push_back( POINT_2D( x, y ) ); - } + diffuse_red = dr; + diffuse_grn = dg; + diffuse_blu = db; + spec_red = sr; + spec_grn = sg; + spec_blu = sb; + emit_red = er; + emit_grn = eg; + emit_blu = eb; - void bag( LAYER_NUM layer, bool close = true ); + ambient = am; + transp = tr; + shiny = sh; + } }; -// A vertical quad loop -struct VLoop +enum VRML_COLOR_INDEX { - std::vector 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 ); + VRML_COLOR_PCB = 0, + VRML_COLOR_TRACK, + VRML_COLOR_SILK, + VRML_COLOR_TIN, + VRML_COLOR_LAST }; -// 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 ) +class MODEL_VRML { - double z = layer_z[layer]; +private: - layer_triangles[layer].push_back( TRIANGLE( x1, y1, z, x2, y2, z, x3, y3, z ) ); + double layer_z[NB_LAYERS]; + VRML_COLOR colors[VRML_COLOR_LAST]; + +public: + + VRML_LAYER holes; + VRML_LAYER board; + VRML_LAYER top_copper; + VRML_LAYER bot_copper; + VRML_LAYER top_silk; + VRML_LAYER bot_silk; + VRML_LAYER top_tin; + VRML_LAYER bot_tin; + + 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; + + // pcb green + colors[ VRML_COLOR_PCB ] = VRML_COLOR( .07, .3, .12, .07, .3, .12, + 0, 0, 0, 1, 0, 0.2 ); + // track green + colors[ VRML_COLOR_TRACK ] = VRML_COLOR( .08, .5, .1, .08, .5, .1, + 0, 0, 0, 1, 0, 0.2 ); + // silkscreen white + colors[ VRML_COLOR_SILK ] = VRML_COLOR( .9, .9, .9, .9, .9, .9, + 0, 0, 0, 1, 0, 0.2 ); + // pad silver + colors[ VRML_COLOR_TIN ] = VRML_COLOR( .749, .756, .761, .749, .756, .761, + 0, 0, 0, 0.8, 0, 0.8 ); + } + + VRML_COLOR& GetColor( VRML_COLOR_INDEX aIndex ) + { + return colors[aIndex]; + } + + void SetOffset( double aXoff, double aYoff ) + { + tx = aXoff; + ty = aYoff; + } + + double GetLayerZ( LAYER_NUM aLayer ) + { + if( aLayer >= NB_LAYERS ) + return 0; + + return layer_z[ aLayer ]; + } + + void SetLayerZ( LAYER_NUM aLayer, double aValue ) + { + layer_z[aLayer] = aValue; + } + + void SetMaxDev( double dev ) + { + holes.SetMaxDev( dev ); + board.SetMaxDev( dev ); + top_copper.SetMaxDev( dev ); + bot_copper.SetMaxDev( dev ); + top_silk.SetMaxDev( dev ); + bot_silk.SetMaxDev( dev ); + top_tin.SetMaxDev( dev ); + bot_tin.SetMaxDev( dev ); + } +}; + + +// static var. for dealing with text +namespace VRMLEXPORT +{ + static MODEL_VRML* model_vrml; + bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer ); } -void FLAT_FAN::bag( LAYER_NUM layer, bool close ) //{{{ +// 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 ) { - unsigned i; + switch( layer ) + { + case FIRST_COPPER_LAYER: + *vlayer = &aModel.bot_copper; + break; - 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 ); + case LAST_COPPER_LAYER: + *vlayer = &aModel.top_copper; + break; - if( close ) - bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[0].x, pts[0].y ); + 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 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 write_triangle_bag( FILE* output_file, VRML_COLOR& color, + 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,21 +300,18 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ " Shape {\n", " appearance Appearance {\n", " material Material {\n", - 0, // Material marker - " ambientIntensity 0.8\n", - " transparency 0.2\n", - " shininess 0.2\n", + 0, // Material marker " }\n", " }\n", " geometry IndexedFaceSet {\n", " 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 +319,7 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ " }\n", " ]\n", "}\n", - 0 // End marker + 0 // End marker }; int marker_found = 0, lineno = 0; @@ -270,59 +334,49 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ switch( marker_found ) { - case 1: // Material marker + 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 ); + " diffuseColor %g %g %g\n", + color.diffuse_red, + color.diffuse_grn, + color.diffuse_blu ); 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 ); + " specularColor %g %g %g\n", + color.spec_red, + color.spec_grn, + color.spec_blu ); 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 ); + " emissiveColor %g %g %g\n", + color.emit_red, + color.emit_grn, + color.emit_blu ); + fprintf( output_file, + " ambientIntensity %g\n", color.ambient ); + fprintf( output_file, + " transparency %g\n", color.transp ); + fprintf( output_file, + " shininess %g\n", color.shiny ); 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 ); + + fprintf( output_file, "\n" ); + 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 ); + + fprintf( output_file, "\n" ); + break; default: break; @@ -334,344 +388,211 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ } -static void compute_layer_Zs( BOARD* pcb ) //{{{ +static void write_layers( MODEL_VRML& aModel, FILE* output_file, BOARD* aPcb ) { - int copper_layers = pcb->GetCopperLayerCount( ); + // VRML_LAYER board; + aModel.board.Tesselate( &aModel.holes ); + double brdz = aModel.board_thickness / 2.0 - 40000 * aModel.scale; + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_PCB ), + &aModel.board, false, false, brdz, -brdz ); + + // VRML_LAYER top_copper; + aModel.top_copper.Tesselate( &aModel.holes ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ), + &aModel.top_copper, true, true, + aModel.GetLayerZ( LAST_COPPER_LAYER ), 0 ); + + // VRML_LAYER top_tin; + aModel.top_tin.Tesselate( &aModel.holes ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ), + &aModel.top_tin, true, true, + aModel.GetLayerZ( LAST_COPPER_LAYER ), 0 ); + + // VRML_LAYER bot_copper; + aModel.bot_copper.Tesselate( &aModel.holes ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ), + &aModel.bot_copper, true, false, + aModel.GetLayerZ( FIRST_COPPER_LAYER ), 0 ); + + // VRML_LAYER bot_tin; + aModel.bot_tin.Tesselate( &aModel.holes ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ), + &aModel.bot_tin, true, false, + aModel.GetLayerZ( FIRST_COPPER_LAYER ), 0 ); + + // VRML_LAYER top_silk; + aModel.top_silk.Tesselate( &aModel.holes ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), + &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, aModel.GetColor( VRML_COLOR_SILK ), + &aModel.bot_silk, true, false, + aModel.GetLayerZ( SILKSCREEN_N_BACK ), 0 ); +} + + +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; + double epsilon_z = Millimeter2iu( 0.02 ) * aModel.scale; + 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( LAYER_NUM layer, double startx, double starty, //{{{ - double endx, double endy, double width, int divisions ) +static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer, + double startx, double starty, + double endx, double endy, double width ) { - double r = width / 2; - double angle = atan2( endy - starty, endx - startx ); - double alpha; - FLAT_FAN fan; + VRML_LAYER* vlayer; - // Output the 'bone' as a triangle fan, this is the fan centre - fan.c.x = (startx + endx) / 2; - fan.c.y = (starty + endy) / 2; + if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) ) + return; - // The 'end' side cap - for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions ) - fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) ); + starty = -starty; + endy = -endy; - alpha = angle + PI2; - fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) ); + 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; - // 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 ); + vlayer->AddSlot( cx, cy, length, width, angle, 1, false ); } -static void export_vrml_circle( LAYER_NUM layer, double startx, double starty, //{{{ - double endx, double endy, double width ) +static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer, + double startx, double starty, + double endx, double endy, double width ) { - double hole, radius; - FLAT_RING ring; + 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; + hole = radius - width; - for( double alpha = 0; alpha < M_PI * 2; alpha += INC_ANGLE ) + vlayer->AddCircle( startx, starty, radius, 1, false ); + + if( hole > 0.0001 ) { - ring.add_inner( startx + hole * cos( alpha ), starty + hole * sin( alpha ) ); - ring.add_outer( startx + radius * cos( alpha ), starty + radius * sin( alpha ) ); + vlayer->AddCircle( startx, starty, hole, 1, true ); } - - 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 ) +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 ) { - double capx, capy; // Cap center - VLoop loop; - int divisions = SEGM_COUNT_PER_360 / 2; + VRML_LAYER* vlayer; - loop.z_top = layer_z[top_layer]; - loop.z_bottom = layer_z[bottom_layer]; - double angle = DECIDEG2RAD( orient ); + if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) ) + return; - if( dy > dx ) - { - EXCHG( dx, dy ); - angle += PI2; - } + centery = -centery; + arc_starty = -arc_starty; - // 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; + arc_angle *= -M_PI / 180; - 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 ); + vlayer->AddArc( centerx, centery, arc_startx, arc_starty, + width, arc_angle, 1, false ); } -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 ) //{{{ +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 ) + return; + + switch( drawseg->GetShape() ) { - 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; + 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; - // 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; + case S_CIRCLE: + export_vrml_circle( aModel, layer, x, y, xf, yf, w ); + 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; - } + 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 +601,9 @@ static void export_vrml_pcbtext( TEXTE_PCB* text ) 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 +613,11 @@ static void export_vrml_pcbtext( TEXTE_PCB* text ) { 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 +626,34 @@ static void export_vrml_pcbtext( TEXTE_PCB* text ) 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 ) + continue; + 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 +663,382 @@ static void export_vrml_drawings( BOARD* pcb ) //{{{ } -static void export_round_padstack( BOARD* pcb, double x, double y, double r, //{{{ - LAYER_NUM bottom_layer, LAYER_NUM top_layer ) +// board edges and cutouts +static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) { - int copper_layers = pcb->GetCopperLayerCount( ); + CPOLYGONS_LIST bufferPcbOutlines; // stores the board main outlines + CPOLYGONS_LIST allLayerHoles; // Contains through holes, calculated only once - for( LAYER_NUM layer = bottom_layer; layer < copper_layers; ++layer ) + allLayerHoles.reserve( 20000 ); + + // Build a polygon from edge cut items + wxString msg; + + if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, + allLayerHoles, &msg ) ) { - // 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; + msg << wxT( "\n\n" ) << + _( "Unable to calculate the board outlines;\n" + "fall back to using the board boundary box." ); + wxMessageBox( msg ); + } - if( layer <= top_layer ) - export_vrml_circle( layer, x, y, x + r / 2, y, r ); + 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 ) + { + msg << wxT( "\n\n" ) << + _( "VRML Export Failed:\nCould not add outline to contours." ); + wxMessageBox( msg ); + + return; + } + + 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(); + + i = 0; + while( i < nvert ) + { + seg = aModel.holes.NewContour(); + + if( seg < 0 ) + { + msg << wxT( "\n\n" ) << + _( "VRML Export Failed:\nCould not add holes to contours." ); + wxMessageBox( msg ); + + return; + } + + while( i < nvert ) + { + aModel.holes.AddVertex( seg, allLayerHoles[i].x * scale + dx, + -(allLayerHoles[i].y * scale + dy) ); + + if( allLayerHoles[i].end_contour ) + break; + + ++i; + } + + aModel.holes.EnsureWinding( seg, true ); + ++i; } } -static void export_vrml_via( BOARD* pcb, SEGVIA* via ) //{{{ +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.AddCircle( x, -y, hole, 1, true ); + 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.AddCircle( x, -y, hole, 1, true ); + 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 ); - // Export the via padstack - export_round_padstack( pcb, x, y, r, bottom_layer, top_layer ); + // do not render a buried via + if( top_layer != LAST_COPPER_LAYER && bottom_layer != FIRST_COPPER_LAYER ) + return; - // Drill a hole - export_vrml_hole( via_triangles[via->GetShape()], top_layer, bottom_layer, x, y, hole ); + // Export the via padstack + 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 ) +static void export_vrml_zones( MODEL_VRML& aModel, BOARD* aPcb ) { - // 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 ); + double scale = aModel.scale; + double dx = aModel.tx; + double dy = aModel.ty; - if( ( zone->m_FilledPolysList.size() == 0 ) - ||( zone->GetMinThickness() <= 1 ) ) + double x, y; + + for( int ii = 0; ii < aPcb->GetAreaCount(); ii++ ) + { + ZONE_CONTAINER* zone = aPcb->GetArea( ii ); + + VRML_LAYER* vl; + + if( !VRMLEXPORT::GetLayer( aModel, zone->GetLayer(), &vl ) ) continue; - int width = zone->GetMinThickness(); - - if( width > 0 ) + if( !zone->IsFilled() ) { - int imax = zone->m_FilledPolysList.size() - 1; - LAYER_NUM layer = zone->GetLayer(); - CPolyPt* firstcorner = &zone->m_FilledPolysList[0]; - CPolyPt* begincorner = firstcorner; + zone->SetFillMode( 0 ); // use filled polygons + zone->BuildFilledSolidAreasPolygons( aPcb ); + } + const CPOLYGONS_LIST& poly = zone->GetFilledPolysList(); - // I'm not really positive about what he's doing here... - for( int ic = 1; ic <= imax; ic++ ) + int nvert = poly.GetCornersCount(); + int i = 0; + + while( i < nvert ) + { + int seg = vl->NewContour(); + bool first = true; + + if( seg < 0 ) + break; + + while( i < nvert ) { - CPolyPt* endcorner = &zone->m_FilledPolysList[ic]; + x = poly.GetX(i) * scale + dx; + y = -(poly.GetY(i) * scale + dy); + vl->AddVertex( seg, x, y ); - export_vrml_line( layer, begincorner->x, begincorner->y, - endcorner->x, endcorner->y, width, 1 ); + if( poly.IsEndContour(i) ) + break; - 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; + ++i; } + + // KiCad ensures that the first polygon is the outline + // and all others are holes + vl->EnsureWinding( seg, first ? false : true ); + + if( first ) + first = false; + + ++i; } } } -*/ -static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{ + +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 + NEGATE( size.x ); // Text is mirrored + + VRMLEXPORT::model_vrml->s_text_layer = module->GetLayer(); + VRMLEXPORT::model_vrml->s_text_width = module->GetThickness(); - s_text_layer = module->GetLayer(); - 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 ); + export_vrml_circle( aModel, layer, x, y, xf, yf, w ); break; default: - export_vrml_line( layer, x, y, xf, yf, w, 1 ); + export_vrml_line( aModel, layer, x, y, xf, yf, w ); break; } } -static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{ +static void export_vrml_padshape( MODEL_VRML& aModel, VRML_LAYER* aLayer, + VRML_LAYER* aTinLayer, 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; + // 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, true ); + aTinLayer->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, true ); + aTinLayer->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[2], -coord[3] ); + aLayer->AddVertex( lines, coord[6], -coord[7] ); + aLayer->AddVertex( lines, coord[4], -coord[5] ); + aLayer->AddVertex( lines, coord[0], -coord[1] ); + aLayer->EnsureWinding( lines, true ); + + lines = aTinLayer->NewContour(); + + if( lines < 0 ) + return; + + aTinLayer->AddVertex( lines, coord[0], -coord[1] ); + aTinLayer->AddVertex( lines, coord[4], -coord[5] ); + aTinLayer->AddVertex( lines, coord[6], -coord[7] ); + aTinLayer->AddVertex( lines, coord[2], -coord[3] ); + aTinLayer->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 +1046,27 @@ static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{ 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, true ); } 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.AddCircle( hole_x, -hole_y, hole_drill, 1, true ); } } // The pad proper, on the selected layers - LAYER_MSK layer_mask = aPad->GetLayerMask(); - int copper_layers = pcb->GetCopperLayerCount( ); + LAYER_MSK layer_mask = aPad->GetLayerMask(); - // 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 ) + if( layer_mask & LAYER_BACK ) { - // 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; + export_vrml_padshape( aModel, &aModel.bot_copper, &aModel.bot_tin, aPad ); + } - 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: - ; - } - } + if( layer_mask & LAYER_FRONT ) + { + export_vrml_padshape( aModel, &aModel.top_copper, &aModel.top_tin, aPad ); } } @@ -1021,27 +1100,32 @@ static void compose_quat( double q1[4], double q2[4], double qr[4] ) { 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() ); + if( aModule->Reference().IsVisible() ) + export_vrml_text_module( &aModule->Reference() ); + + if( aModule->Value().IsVisible() ) + 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 +1134,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, break; case PCB_MODULE_EDGE_T: - export_vrml_edge_module( dynamic_cast( item ) ); + export_vrml_edge_module( aModel, dynamic_cast( item ) ); break; default: @@ -1059,8 +1143,8 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, } // 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 +1156,12 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, 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 +1182,9 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, * 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 ) { @@ -1138,21 +1222,21 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, 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 +1247,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, // 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 +1255,25 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, 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 ) +bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, + double aMMtoWRMLunit, bool aExport3DFiles, + const wxString& a3D_Subdir ) { - if( !triangles.empty() ) - { - write_triangle_bag( output_file, color, triangles, boardIU2WRML ); - triangles.clear( ); - } -} + wxString msg; + FILE* output_file; + BOARD* pcb = GetBoard(); + MODEL_VRML model3d; -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(); + VRMLEXPORT::model_vrml = &model3d; output_file = wxFopen( aFullFileName, wxT( "wt" ) ); @@ -1217,39 +1293,36 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, " 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; + // Set the mechanical deviation limit (in this case 0.02mm) + // XXX - NOTE: the value should be set via the GUI + model3d.SetMaxDev( 20000 * model3d.scale ); + 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 ); + compute_layer_Zs( model3d, pcb ); - // Drawing and text on the board, and edges which are special - export_vrml_drawings( 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); -*/ + export_vrml_zones( model3d, pcb); /* scaling factor to convert 3D models to board units (decimils) * Usually we use Wings3D to create thems. @@ -1261,25 +1334,12 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, // 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 ); - /* 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 ); + // write out the board and all layers + write_layers( model3d, output_file, pcb ); // Close the outer 'transform' node fputs( "]\n}\n", output_file ); @@ -1296,7 +1356,7 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, * 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( "_" ) ); diff --git a/pcbnew/vrml_board.cpp b/pcbnew/vrml_board.cpp new file mode 100644 index 0000000000..4ef750278b --- /dev/null +++ b/pcbnew/vrml_board.cpp @@ -0,0 +1,1501 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#ifndef CALLBACK +#define CALLBACK +#endif + +#define GLCALLBACK(x) (( void (CALLBACK*)() )&(x)) + +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; +} + + +static void CALLBACK vrml_tess_begin( GLenum cmd, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glStart( cmd ); +} + + +static void CALLBACK vrml_tess_end( void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glEnd(); +} + + +static void CALLBACK vrml_tess_vertex( void* vertex_data, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->glPushVertex( (VERTEX_3D*) vertex_data ); +} + + +static void CALLBACK vrml_tess_err( GLenum errorID, void* user_data ) +{ + VRML_LAYER* lp = (VRML_LAYER*) user_data; + + lp->Fault = true; + lp->SetGLError( errorID ); +} + + +static void CALLBACK 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, GLCALLBACK( vrml_tess_begin ) ); + + gluTessCallback( tess, GLU_TESS_VERTEX_DATA, GLCALLBACK( vrml_tess_vertex ) ); + + gluTessCallback( tess, GLU_TESS_END_DATA, GLCALLBACK( vrml_tess_end ) ); + + gluTessCallback( tess, GLU_TESS_ERROR_DATA, GLCALLBACK( vrml_tess_err ) ); + + gluTessCallback( tess, GLU_TESS_COMBINE_DATA, GLCALLBACK( 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(); + } + + while( !areas.empty() ) + areas.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 ) + { + error = "SetMaxDev(): specified value is < 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* contour = new std::list; + + if( !contour ) + return -1; + + contours.push_back( contour ); + areas.push_back( 0.0 ); + + 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 ) + { + error = "AddVertex(): no more vertices may be added (Tesselate was previously executed)"; + return false; + } + + if( aContour < 0 || (unsigned int) aContour >= contours.size() ) + { + error = "AddVertex(): aContour is not within a valid range"; + return false; + } + + VERTEX_3D* vertex = new VERTEX_3D; + + if( !vertex ) + { + error = "AddVertex(): a new vertex could not be allocated"; + return false; + } + + vertex->x = x; + vertex->y = y; + vertex->i = idx++; + vertex->o = -1; + + VERTEX_3D* v2 = NULL; + + if( contours[aContour]->size() > 0 ) + v2 = vertices[ contours[aContour]->back() ]; + + vertices.push_back( vertex ); + contours[aContour]->push_back( vertex->i ); + + if( v2 ) + areas[aContour] += ( x - v2->x ) * ( y + v2->y ); + + 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() ) + { + error = "EnsureWinding(): aContour is outside the valid range"; + return false; + } + + std::list* cp = contours[aContour]; + + if( cp->size() < 3 ) + { + error = "EnsureWinding(): there are fewer than 3 vertices"; + return false; + } + + double dir = areas[aContour]; + + VERTEX_3D* vp0 = vertices[ cp->back() ]; + VERTEX_3D* vp1 = vertices[ cp->front() ]; + + dir += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); + + // if dir is positive, winding is CW + if( ( hole && dir < 0 ) || ( !hole && dir > 0 ) ) + { + cp->reverse(); + areas[aContour] = -areas[aContour]; + } + + 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 ) + { + error = "AddCircle(): failed to add a contour"; + 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; + + bool fail = false; + + if( hole ) + { + for( double angle = 0; angle < M_PI * 2; angle += da ) + fail |= !AddVertex( pad, x + rad * cos( angle ), y - rad * sin( angle ) ); + } + else + { + for( double angle = 0; angle < M_PI * 2; angle += da ) + fail |= !AddVertex( pad, x + rad * cos( angle ), y + rad * sin( angle ) ); + } + + return !fail; +} + + +// 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 ) + { + error = "AddCircle(): failed to add a contour"; + return false; + } + + da = M_PI / csides; + bool fail = false; + + if( hole ) + { + for( ang = angle + M_PI2, i = 0; i < csides; ang -= da, ++i ) + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + + ang = angle - M_PI2; + fail |= !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 ) + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + + ang = angle + M_PI2; + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + } + else + { + for( ang = angle - M_PI2, i = 0; i < csides; ang += da, ++i ) + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + + ang = angle + M_PI2; + fail |= !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 ) + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + + ang = angle - M_PI2; + fail |= !AddVertex( pad, capx + width * cos( ang ), capy + width * sin( ang ) ); + } + + return !fail; +} + + +// 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 ) + { + error = "AddArc(): angle is too small: abs( 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 * 1.01 ) ) + { + error = "AddArc(): width/2 exceeds radius*1.01"; + 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 ) + { + error = "AddArc(): could not create a contour"; + 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 the contours in preparation for a 3D output; +// returns true if all was fine, false otherwise +bool VRML_LAYER::Tesselate( VRML_LAYER* holes ) +{ + if( !tess ) + { + error = "Tesselate(): GLU tesselator was not initialized"; + return false; + } + + pholes = holes; + Fault = false; + + if( contours.size() < 1 || vertices.size() < 3 ) + { + error = "Tesselate(): not enough vertices"; + return false; + } + + // finish the winding calculation on all vertices prior to setting 'fix' + if( !fix ) + { + for( unsigned int i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + VERTEX_3D* vp0 = vertices[ contours[i]->back() ]; + VERTEX_3D* vp1 = vertices[ contours[i]->front() ]; + areas[i] += ( vp1->x - vp0->x ) * ( vp1->y + vp0->y ); + } + } + + // 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( false ); + + // close the polygon + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + // push the (solid) outline to the tesselator + if( !pushOutline( holes ) ) + return false; + + // add the holes contained by this object + pushVertices( true ); + + // import external holes (if any) + if( hidx && ( holes->Import( idx, tess ) < 0 ) ) + { + std::ostringstream ostr; + ostr << "Tesselate():FAILED: " << holes->GetError(); + error = ostr.str(); + return NULL; + } + + if( Fault ) + return false; + + // erase the previous outline data and vertex order + // but preserve the extra vertices + for( int i = outline.size(); i > 0; --i ) + { + delete outline.back(); + outline.pop_back(); + } + + for( unsigned int i = ordmap.size(); i > 0; --i ) + ordmap.pop_back(); + + // go through the vertex lists and reset ephemeral parameters + for( unsigned int i = 0; i < vertices.size(); ++i ) + { + vertices[i]->o = -1; + } + + for( unsigned int i = 0; i < extra_verts.size(); ++i ) + { + extra_verts[i]->o = -1; + } + + ord = 0; + + // close the polygon; we now have all the data necessary for the tesselation + gluTessEndPolygon( tess ); + + // request a tesselated surface + gluTessProperty( tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE ); + + if( !pushOutline( holes ) ) + return false; + + gluTessEndPolygon( tess ); + + if( Fault ) + return false; + + return true; +} + + +bool VRML_LAYER::pushOutline( VRML_LAYER* holes ) +{ + // traverse the outline list to push all used vertices + if( outline.size() < 1 ) + { + error = "pushOutline() failed: no vertices to push"; + return false; + } + + gluTessBeginPolygon( tess, this ); + + std::list*>::const_iterator obeg = outline.begin(); + std::list*>::const_iterator oend = outline.end(); + + int pi; + std::list::const_iterator begin; + std::list::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; + + if( pi < 0 || (unsigned int) pi > ordmap.size() ) + { + error = "pushOutline():BUG: *outline.begin() is not a valid index to ordmap"; + return false; + } + + // retrieve the actual index + pi = ordmap[pi]; + + vp = getVertexByIndex( pi, holes ); + + if( !vp ) + { + error = "pushOutline():: BUG: ordmap[n] is not a valid index to vertices[]"; + return false; + } + + pt[0] = vp->x; + pt[1] = vp->y; + pt[2] = 0.0; + gluTessVertex( tess, pt, vp ); + ++begin; + } + + gluTessEndContour( tess ); + ++obeg; + } + + 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 ) + { + error = "WriteVertices(): invalid file pointer"; + return false; + } + + if( ordmap.size() < 3 ) + { + error = "WriteVertices(): not enough vertices"; + 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 ) + { + error = "Write3DVertices(): NULL file pointer"; + return false; + } + + if( ordmap.size() < 3 ) + { + error = "Write3DVertices(): insufficient vertices"; + return false; + } + + if( top <= bottom ) + { + error = "Write3DVertices(): 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() ) + { + error = "WriteIndices(): no triplets (triangular facets) to write"; + return false; + } + + // go through the triplet list and write out the indices based on order + std::list::const_iterator tbeg = triplets.begin(); + std::list::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() ) + { + error = "Write3DIndices(): no triplets (triangular facets) to write"; + return false; + } + + if( outline.empty() ) + { + error = "WriteIndices(): no outline available"; + return false; + } + + // go through the triplet list and write out the indices based on order + std::list::const_iterator tbeg = triplets.begin(); + std::list::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*>::const_iterator obeg = outline.begin(); + std::list*>::const_iterator oend = outline.end(); + std::list* cp; + std::list::const_iterator cbeg; + std::list::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 ) + { + error = "AddExtraVertex(): could not allocate a new 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* loop = new std::list; + + 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: + break; + } + + while( !vlist.empty() ) + vlist.pop_back(); + + glcmd = 0; +} + + +// set the error message +void VRML_LAYER::SetGLError( GLenum errorID ) +{ + error = ""; + error = (const char*)gluGetString( errorID ); + + if( error.empty() ) + { + std::ostringstream ostr; + ostr << "Unknown OpenGL error: " << errorID; + error = ostr.str(); + } +} + + +// 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( bool holes ) +{ + // push the internally held vertices + unsigned int i; + + std::list::const_iterator begin; + std::list::const_iterator end; + GLdouble pt[3]; + VERTEX_3D* vp; + + for( i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + if( ( holes && areas[i] <= 0.0 ) || ( !holes && areas[i] > 0.0 ) ) + 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_LAYER* holes ) +{ + if( index < 0 || (unsigned int) index >= ( idx + hidx + extra_verts.size() ) ) + { + error = "getVertexByIndex():BUG: invalid index"; + 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 ) + { + error = "getVertexByIndex():BUG: invalid index"; + return NULL; + } + + VERTEX_3D* vp = holes->GetVertexByIndex( index ); + + if( !vp ) + { + std::ostringstream ostr; + ostr << "getVertexByIndex():FAILED: " << holes->GetError(); + error = ostr.str(); + return NULL; + } + + return vp; +} + + +// retrieve the total number of vertices +int VRML_LAYER::GetSize( void ) +{ + return vertices.size(); +} + + +// 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_LAYER::Import( int start, GLUtesselator* tess ) +{ + if( start < 0 ) + { + error = "Import(): invalid index ( start < 0 )"; + return -1; + } + + if( !tess ) + { + error = "Import(): NULL tesselator pointer"; + return -1; + } + + unsigned int i, j; + + // renumber from 'start' + for( i = 0, j = vertices.size(); i < j; ++i ) + { + vertices[i]->i = start++; + vertices[i]->o = -1; + } + + // push each contour to the tesselator + VERTEX_3D* vp; + GLdouble pt[3]; + + std::list::const_iterator cbeg; + std::list::const_iterator cend; + + for( i = 0; i < contours.size(); ++i ) + { + if( contours[i]->size() < 3 ) + continue; + + cbeg = contours[i]->begin(); + cend = contours[i]->end(); + + gluTessBeginContour( tess ); + + while( cbeg != cend ) + { + vp = vertices[ *cbeg++ ]; + pt[0] = vp->x; + pt[1] = vp->y; + pt[2] = 0.0; + gluTessVertex( tess, pt, vp ); + } + + gluTessEndContour( tess ); + } + + return start; +} + + +// return the vertex identified by index +VERTEX_3D* VRML_LAYER::GetVertexByIndex( int index ) +{ + int i0 = vertices[0]->i; + + if( index < i0 || index >= ( i0 + (int) vertices.size() ) ) + { + error = "GetVertexByIndex(): invalid index"; + return NULL; + } + + return vertices[index - i0]; +} + + +// return the error string +const std::string& VRML_LAYER::GetError( void ) +{ + return error; +} diff --git a/pcbnew/vrml_board.h b/pcbnew/vrml_board.h new file mode 100644 index 0000000000..aa7e935abe --- /dev/null +++ b/pcbnew/vrml_board.h @@ -0,0 +1,379 @@ +/* + * 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 +# else +# include +# endif +#else +# include +#endif + +#include +#include +#include +#include + +#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_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 vertices; // vertices of all contours + std::vector*> contours; // lists of vertices for each contour + std::vector< double > areas; // area of the contours (positive if winding is CCW) + std::list triplets; // output facet triplet list (triplet of ORDER values) + std::list*> outline; // indices for outline outputs (index by ORDER values) + std::vector ordmap; // mapping of ORDER to INDEX + + std::string error; // error message + + 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 extra_verts; // extra vertices added for outlines and facets + std::vector vlist; // vertex list for the GL command in progress + VRML_LAYER* pholes; // pointer to another layer object used for tesselation; + // this object is normally expected to hold only holes + + 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 foreign VRML_LAYER object + VERTEX_3D* getVertexByIndex( int index, VRML_LAYER* 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( bool holes ); // push the internal vertices + bool pushOutline( VRML_LAYER* holes ); // push the outline 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 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 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 + * creates a list of outline vertices as well as 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_LAYER* 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 ); + + /** + * Function SetGLError + * sets the error message according to the specified OpenGL error + */ + void SetGLError( GLenum error_id ); + + /** + * 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 ); + + /** + * 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 ); + + /* + * Function GetError + * Returns the error message related to the last failed operation + */ + const std::string& GetError( void ); +}; + +#endif // VRML_BOARD_H