Commit 714d5b28 authored by jean-pierre charras's avatar jean-pierre charras

Better vrml export, from Cirilo Bernardo.

parent 54bbba6c
...@@ -177,6 +177,7 @@ set( PCBNEW_CLASS_SRCS ...@@ -177,6 +177,7 @@ set( PCBNEW_CLASS_SRCS
export_gencad.cpp export_gencad.cpp
export_idf.cpp export_idf.cpp
export_vrml.cpp export_vrml.cpp
vrml_board.cpp
files.cpp files.cpp
gen_drill_report_files.cpp gen_drill_report_files.cpp
gen_modules_placefile.cpp gen_modules_placefile.cpp
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2009-2013 Lorenzo Mercantonio * Copyright (C) 2009-2013 Lorenzo Mercantonio
* Copyright (C) 2014 Cirilo Bernado
* Copyright (C) 2013 Jean-Pierre Charras jp.charras at wanadoo.fr * Copyright (C) 2013 Jean-Pierre Charras jp.charras at wanadoo.fr
* Copyright (C) 2004-2013 KiCad Developers, see change_log.txt for contributors. * Copyright (C) 2004-2013 KiCad Developers, see change_log.txt for contributors.
* *
...@@ -22,6 +23,44 @@ ...@@ -22,6 +23,44 @@
* or you may write to the Free Software Foundation, Inc., * or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 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 <fctsys.h> #include <fctsys.h>
#include <kicad_string.h> #include <kicad_string.h>
#include <wxPcbStruct.h> #include <wxPcbStruct.h>
...@@ -36,6 +75,7 @@ ...@@ -36,6 +75,7 @@
#include <class_board.h> #include <class_board.h>
#include <class_module.h> #include <class_module.h>
#include <class_track.h> #include <class_track.h>
#include <class_zone.h>
#include <class_edge_mod.h> #include <class_edge_mod.h>
#include <class_pcb_text.h> #include <class_pcb_text.h>
#include <convert_from_iu.h> #include <convert_from_iu.h>
...@@ -44,183 +84,210 @@ ...@@ -44,183 +84,210 @@
#include <vector> #include <vector>
#include <cmath> #include <cmath>
#include <vrml_board.h>
// 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;
/* helper function: /* helper function:
* some characters cannot be used in names, * some characters cannot be used in names,
* this function change them to "_" * 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 float spec_red;
{ float spec_grn;
POINT_2D( double _x = 0, double _y = 0 ) : x( _x ), y( _y ) float spec_blu;
{ }
double x, y;
};
// Absolutely not optimized triangle bag :D float emit_red;
struct TRIANGLE float emit_grn;
{ float emit_blu;
TRIANGLE( double x1, double y1, double z1,
double x2, double y2, double z2,
double x3, double y3, double z3 )
{
p1.x = x1; p1.y = y1; p1.z = z1;
p2.x = x2; p2.y = y2; p2.z = z2;
p3.x = x3; p3.y = y3; p3.z = z3;
}
TRIANGLE() { }
POINT_3D p1, p2, p3;
};
typedef std::vector<TRIANGLE> TRIANGLEBAG;
// A flat triangle fan float ambient;
struct FLAT_FAN float transp;
{ float shiny;
POINT_2D c;
std::vector<POINT_2D> pts;
void add( double x, double y )
{
pts.push_back( POINT_2D( x, y ) );
}
void bag( LAYER_NUM layer, bool close = true );
};
// A flat quad ring VRML_COLOR()
struct FLAT_RING
{
std::vector<POINT_2D> inner;
std::vector<POINT_2D> outer;
void add_inner( double x, double y )
{ {
inner.push_back( POINT_2D( x, y ) ); // 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;
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;
ambient = am;
transp = tr;
shiny = sh;
} }
void bag( LAYER_NUM layer, bool close = true );
}; };
// A vertical quad loop enum VRML_COLOR_INDEX
struct VLoop
{ {
std::vector<POINT_2D> pts; VRML_COLOR_PCB = 0,
double z_top, z_bottom; VRML_COLOR_TRACK,
void add( double x, double y ) VRML_COLOR_SILK,
{ VRML_COLOR_TIN,
pts.push_back( POINT_2D( x, y ) ); VRML_COLOR_LAST
}
void bag( TRIANGLEBAG& triangles, bool close = true );
}; };
// The bags for all the layers
static TRIANGLEBAG layer_triangles[NB_LAYERS];
static TRIANGLEBAG via_triangles[4];
static double layer_z[NB_LAYERS];
static void bag_flat_triangle( LAYER_NUM layer, //{{{ class MODEL_VRML
double x1, double y1,
double x2, double y2,
double x3, double y3 )
{ {
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:
void FLAT_FAN::bag( LAYER_NUM layer, bool close ) //{{{ VRML_LAYER holes;
{ VRML_LAYER board;
unsigned i; VRML_LAYER top_copper;
VRML_LAYER bot_copper;
VRML_LAYER top_silk;
VRML_LAYER bot_silk;
VRML_LAYER top_tin;
VRML_LAYER bot_tin;
for( i = 0; i < pts.size() - 1; i++ ) double scale; // board internal units to output scaling
bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y );
if( close ) double tx; // global translation along X
bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[0].x, pts[0].y ); double ty; // global translation along Y
}
double board_thickness; // depth of the PCB
static void bag_flat_quad( LAYER_NUM layer, //{{{ LAYER_NUM s_text_layer;
double x1, double y1, int s_text_width;
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 );
}
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 );
}
void FLAT_RING::bag( LAYER_NUM layer, bool close ) //{{{ VRML_COLOR& GetColor( VRML_COLOR_INDEX aIndex )
{ {
unsigned i; return colors[aIndex];
}
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 );
}
void SetOffset( double aXoff, double aYoff )
{
tx = aXoff;
ty = aYoff;
}
static void bag_vquad( TRIANGLEBAG& triangles, //{{{ double GetLayerZ( LAYER_NUM aLayer )
double x1, double y1, double x2, double y2, {
double z1, double z2 ) 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
{ {
triangles.push_back( TRIANGLE( x1, y1, z1, static MODEL_VRML* model_vrml;
x2, y2, z1, bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer );
x2, y2, z2 ) );
triangles.push_back( TRIANGLE( x1, y1, z1,
x2, y2, z2,
x1, y1, z2 ) );
} }
void VLoop::bag( TRIANGLEBAG& triangles, 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;
case LAST_COPPER_LAYER:
*vlayer = &aModel.top_copper;
break;
case SILKSCREEN_N_BACK:
*vlayer = &aModel.bot_silk;
break;
case SILKSCREEN_N_FRONT:
*vlayer = &aModel.top_silk;
break;
for( i = 0; i < pts.size() - 1; i++ ) default:
bag_vquad( triangles, pts[i].x, pts[i].y, return false;
pts[i + 1].x, pts[i + 1].y, }
z_top, z_bottom );
if( close ) return true;
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, //{{{ static void write_triangle_bag( FILE* output_file, VRML_COLOR& color,
const TRIANGLEBAG& triangles, VRML_LAYER* layer, bool plane, bool top,
double boardIU2WRML ) double top_z, double bottom_z )
{ {
/* A lot of nodes are not required, but blender sometimes chokes /* A lot of nodes are not required, but blender sometimes chokes
* without them */ * without them */
...@@ -234,9 +301,6 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ ...@@ -234,9 +301,6 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{
" appearance Appearance {\n", " appearance Appearance {\n",
" material Material {\n", " material Material {\n",
0, // Material marker 0, // Material marker
" ambientIntensity 0.8\n",
" transparency 0.2\n",
" shininess 0.2\n",
" }\n", " }\n",
" }\n", " }\n",
" geometry IndexedFaceSet {\n", " geometry IndexedFaceSet {\n",
...@@ -273,55 +337,45 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ ...@@ -273,55 +337,45 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{
case 1: // Material marker case 1: // Material marker
fprintf( output_file, fprintf( output_file,
" diffuseColor %g %g %g\n", " diffuseColor %g %g %g\n",
(double) g_ColorRefs[color_index].m_Red / 255.0, color.diffuse_red,
(double) g_ColorRefs[color_index].m_Green / 255.0, color.diffuse_grn,
(double) g_ColorRefs[color_index].m_Blue / 255.0 ); color.diffuse_blu );
fprintf( output_file, fprintf( output_file,
" specularColor %g %g %g\n", " specularColor %g %g %g\n",
(double) g_ColorRefs[color_index].m_Red / 255.0, color.spec_red,
(double) g_ColorRefs[color_index].m_Green / 255.0, color.spec_grn,
(double) g_ColorRefs[color_index].m_Blue / 255.0 ); color.spec_blu );
fprintf( output_file, fprintf( output_file,
" emissiveColor %g %g %g\n", " emissiveColor %g %g %g\n",
(double) g_ColorRefs[color_index].m_Red / 255.0, color.emit_red,
(double) g_ColorRefs[color_index].m_Green / 255.0, color.emit_grn,
(double) g_ColorRefs[color_index].m_Blue / 255.0 ); 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; break;
case 2: case 2:
{
// Coordinates marker if( plane )
for( TRIANGLEBAG::const_iterator i = triangles.begin(); layer->WriteVertices( top_z, output_file );
i != triangles.end(); else
i++ ) layer->Write3DVertices( top_z, bottom_z, output_file );
{
fprintf( output_file, "%.8g %.8g %.8g\n", fprintf( output_file, "\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; break;
case 3: case 3:
{
// Index marker
// OK, that's sick ...
int j = 0;
for( TRIANGLEBAG::const_iterator i = triangles.begin(); if( plane )
i != triangles.end(); layer->WriteIndices( top, output_file );
i++ ) else
{ layer->Write3DIndices( output_file );
fprintf( output_file, "%d %d %d -1\n", j, j + 1, j + 2 );
j += 3; fprintf( output_file, "\n" );
}
}
break; break;
default: default:
...@@ -334,344 +388,211 @@ static void write_triangle_bag( FILE* output_file, int color_index, //{{{ ...@@ -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! // We call it 'layer' thickness, but it's the whole board thickness!
double board_thickness = pcb->GetDesignSettings().GetBoardThickness(); aModel.board_thickness = pcb->GetDesignSettings().GetBoardThickness() * aModel.scale;
double half_thickness = board_thickness / 2; double half_thickness = aModel.board_thickness / 2;
// Compute each layer's Z value, more or less like the 3d view // Compute each layer's Z value, more or less like the 3d view
for( LAYER_NUM i = FIRST_LAYER; i <= LAYER_N_FRONT; ++i ) for( LAYER_NUM i = FIRST_LAYER; i <= LAYER_N_FRONT; ++i )
{ {
if( i < copper_layers ) 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 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 /* To avoid rounding interference, we apply an epsilon to each
* successive layer */ * successive layer */
const double epsilon_z = 0.02 * IU_PER_MM; // That's 1/50 mm double epsilon_z = Millimeter2iu( 0.02 ) * aModel.scale;
layer_z[SOLDERPASTE_N_BACK] = -half_thickness - epsilon_z * 4; aModel.SetLayerZ( SOLDERPASTE_N_BACK, -half_thickness - epsilon_z * 4 );
layer_z[ADHESIVE_N_BACK] = -half_thickness - epsilon_z * 3; aModel.SetLayerZ( ADHESIVE_N_BACK, -half_thickness - epsilon_z * 3 );
layer_z[SILKSCREEN_N_BACK] = -half_thickness - epsilon_z * 2; aModel.SetLayerZ( SILKSCREEN_N_BACK, -half_thickness - epsilon_z * 2 );
layer_z[SOLDERMASK_N_BACK] = -half_thickness - epsilon_z; aModel.SetLayerZ( SOLDERMASK_N_BACK, -half_thickness - epsilon_z );
layer_z[SOLDERMASK_N_FRONT] = half_thickness + epsilon_z; aModel.SetLayerZ( SOLDERMASK_N_FRONT, half_thickness + epsilon_z );
layer_z[SILKSCREEN_N_FRONT] = half_thickness + epsilon_z * 2; aModel.SetLayerZ( SILKSCREEN_N_FRONT, half_thickness + epsilon_z * 2 );
layer_z[ADHESIVE_N_FRONT] = half_thickness + epsilon_z * 3; aModel.SetLayerZ( ADHESIVE_N_FRONT, half_thickness + epsilon_z * 3 );
layer_z[SOLDERPASTE_N_FRONT] = half_thickness + epsilon_z * 4; aModel.SetLayerZ( SOLDERPASTE_N_FRONT, half_thickness + epsilon_z * 4 );
layer_z[DRAW_N] = half_thickness + epsilon_z * 5; aModel.SetLayerZ( DRAW_N, half_thickness + epsilon_z * 5 );
layer_z[COMMENT_N] = half_thickness + epsilon_z * 6; aModel.SetLayerZ( COMMENT_N, half_thickness + epsilon_z * 6 );
layer_z[ECO1_N] = half_thickness + epsilon_z * 7; aModel.SetLayerZ( ECO1_N, half_thickness + epsilon_z * 7 );
layer_z[ECO2_N] = half_thickness + epsilon_z * 8; aModel.SetLayerZ( ECO2_N, half_thickness + epsilon_z * 8 );
layer_z[EDGE_N] = 0; 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 )
{
double r = width / 2;
double angle = atan2( endy - starty, endx - startx );
double alpha;
FLAT_FAN fan;
// Output the 'bone' as a triangle fan, this is the fan centre
fan.c.x = (startx + endx) / 2;
fan.c.y = (starty + endy) / 2;
// The 'end' side cap
for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );
alpha = angle + PI2;
fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );
// The 'start' side cap
for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );
alpha = angle + 3 * PI2;
fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );
// Export the fan
fan.bag( layer );
} }
static void export_vrml_circle( LAYER_NUM layer, double startx, double starty, //{{{ static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer,
double startx, double starty,
double endx, double endy, double width ) double endx, double endy, double width )
{ {
double hole, radius; VRML_LAYER* vlayer;
FLAT_RING ring;
radius = Distance( startx, starty, endx, endy ) + ( width / 2);
hole = radius - width;
for( double alpha = 0; alpha < M_PI * 2; alpha += INC_ANGLE )
{
ring.add_inner( startx + hole * cos( alpha ), starty + hole * sin( alpha ) );
ring.add_outer( startx + radius * cos( alpha ), starty + radius * sin( alpha ) );
}
ring.bag( layer );
}
static void export_vrml_slot( TRIANGLEBAG& triangles, //{{{
LAYER_NUM top_layer, LAYER_NUM bottom_layer, double xc, double yc,
double dx, double dy, double orient )
{
double capx, capy; // Cap center
VLoop loop;
int divisions = SEGM_COUNT_PER_360 / 2;
loop.z_top = layer_z[top_layer];
loop.z_bottom = layer_z[bottom_layer];
double angle = DECIDEG2RAD( orient );
if( dy > dx )
{
EXCHG( dx, dy );
angle += PI2;
}
// The exchange above means that cutter radius is alvays dy/2
double r = dy / 2;
double alpha;
// The first side cap
capx = xc + cos( angle ) * dx / 2;
capy = yc + sin( angle ) * dx / 2;
for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
alpha = angle + PI2;
loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
// The other side cap
capx = xc - cos( angle ) * dx / 2;
capy = yc - sin( angle ) * dx / 2;
for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
alpha = angle + 3 * PI2;
loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
loop.bag( triangles );
}
static void export_vrml_hole( TRIANGLEBAG& triangles,
int top_layer, int bottom_layer,
double xc, double yc, double hole )
{
VLoop loop;
loop.z_top = layer_z[top_layer]; if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
loop.z_bottom = layer_z[bottom_layer]; return;
for( double alpha = 0; alpha < M_PI * 2; alpha += INC_ANGLE ) starty = -starty;
loop.add( xc + cos( alpha ) * hole, yc + sin( alpha ) * hole ); endy = -endy;
loop.bag( triangles ); 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;
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; vlayer->AddSlot( cx, cy, length, width, angle, 1, false );
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, static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer,
double arc_startx, double arc_starty, double startx, double starty,
double width, double arc_angle ) double endx, double endy, double width )
{ {
FLAT_RING ring; VRML_LAYER* vlayer;
double start_angle = atan2( arc_starty - centery, arc_startx - centerx );
int count = KiROUND( arc_angle / 360.0 * SEGM_COUNT_PER_360 ); if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
return;
if( count < 0 ) starty = -starty;
count = -count; endy = -endy;
if( count == 0 ) double hole, radius;
count = 1;
double divisions = arc_angle*M_PI/180.0 / count; radius = Distance( startx, starty, endx, endy ) + ( width / 2);
hole = radius - width;
double outer_radius = Distance( arc_startx, arc_starty, centerx, centery ) vlayer->AddCircle( startx, starty, radius, 1, false );
+ ( width / 2);
double inner_radius = outer_radius - width;
double alpha = 0; if( hole > 0.0001 )
for( int ii = 0; ii <= count; alpha += divisions, ii++ )
{ {
double angle_rot = start_angle + alpha; vlayer->AddCircle( startx, starty, hole, 1, true );
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, static void export_vrml_arc( MODEL_VRML& aModel, LAYER_NUM layer,
LAYER_NUM top_layer, LAYER_NUM bottom_layer,
double centerx, double centery, double centerx, double centery,
double arc_startx, double arc_starty, double arc_startx, double arc_starty,
double arc_angle ) double width, double arc_angle )
{ {
VLoop loop; VRML_LAYER* vlayer;
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 ) if( !VRMLEXPORT::GetLayer( aModel, layer, &vlayer ) )
count = -count; return;
if( count == 0 ) centery = -centery;
count = 1; arc_starty = -arc_starty;
double divisions = arc_angle*M_PI/180.0 / count; arc_angle *= -M_PI / 180;
double alpha = 0; vlayer->AddArc( centerx, centery, arc_startx, arc_starty,
width, arc_angle, 1, false );
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(); LAYER_NUM layer = drawseg->GetLayer();
double w = drawseg->GetWidth(); double w = drawseg->GetWidth() * aModel.scale;
double x = drawseg->GetStart().x; double x = drawseg->GetStart().x * aModel.scale + aModel.tx;
double y = drawseg->GetStart().y; double y = drawseg->GetStart().y * aModel.scale + aModel.ty;
double xf = drawseg->GetEnd().x; double xf = drawseg->GetEnd().x * aModel.scale + aModel.tx;
double yf = drawseg->GetEnd().y; 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 ) if( layer == EDGE_N )
{ return;
switch( drawseg->GetShape() )
{
// There is a special 'varc' primitive for this
case S_ARC:
export_vrml_varc( layer_triangles[layer],
FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
x, y, xf, yf, drawseg->GetAngle()/10 );
break;
// Circles on edge are usually important holes
case S_CIRCLE:
export_vrml_hole( layer_triangles[layer],
FIRST_COPPER_LAYER, LAST_COPPER_LAYER, x, y,
Distance( xf, yf, x, y ) / 2 );
break;
default:
{
// Simply a quad
double z_top = layer_z[FIRST_COPPER_LAYER];
double z_bottom = layer_z[LAST_COPPER_LAYER];
bag_vquad( layer_triangles[layer], x, y, xf, yf, z_top, z_bottom );
break;
}
}
}
else
{
switch( drawseg->GetShape() ) switch( drawseg->GetShape() )
{ {
case S_ARC: case S_ARC:
export_vrml_arc( layer, export_vrml_arc( aModel, layer,
(double) drawseg->GetCenter().x, (double) drawseg->GetCenter().x,
(double) drawseg->GetCenter().y, (double) drawseg->GetCenter().y,
(double) drawseg->GetArcStart().x, (double) drawseg->GetArcStart().x,
(double) drawseg->GetArcStart().y, (double) drawseg->GetArcStart().y,
w, drawseg->GetAngle()/10 ); w, drawseg->GetAngle() / 10 );
break; break;
case S_CIRCLE: case S_CIRCLE:
export_vrml_circle( layer, x, y, xf, yf, w ); export_vrml_circle( aModel, layer, x, y, xf, yf, w );
break; break;
default: default:
export_vrml_line( layer, x, y, xf, yf, w, 1 ); export_vrml_line( aModel, layer, x, y, xf, yf, w );
break; break;
} }
}
} }
/* C++ doesn't have closures and neither continuation forms... this is /* C++ doesn't have closures and neither continuation forms... this is
* for coupling the vrml_text_callback with the common parameters */ * 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 ) 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... VRMLEXPORT::model_vrml->s_text_layer = text->GetLayer();
s_text_layer = text->GetLayer(); VRMLEXPORT::model_vrml->s_text_width = text->GetThickness();
s_text_width = text->GetThickness();
wxSize size = text->GetSize(); wxSize size = text->GetSize();
...@@ -714,19 +635,25 @@ static void export_vrml_pcbtext( TEXTE_PCB* text ) ...@@ -714,19 +635,25 @@ static void export_vrml_pcbtext( TEXTE_PCB* text )
} }
static void export_vrml_drawings( BOARD* pcb ) //{{{ static void export_vrml_drawings( MODEL_VRML& aModel, BOARD* pcb )
{ {
// draw graphic items // 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() ) switch( drawing->Type() )
{ {
case PCB_LINE_T: case PCB_LINE_T:
export_vrml_drawsegment( (DRAWSEGMENT*) drawing ); export_vrml_drawsegment( aModel, (DRAWSEGMENT*) drawing );
break; break;
case PCB_TEXT_T: case PCB_TEXT_T:
export_vrml_pcbtext( (TEXTE_PCB*) drawing ); export_vrml_pcbtext( aModel, (TEXTE_PCB*) drawing );
break; break;
default: default:
...@@ -736,114 +663,247 @@ static void export_vrml_drawings( BOARD* pcb ) //{{{ ...@@ -736,114 +663,247 @@ static void export_vrml_drawings( BOARD* pcb ) //{{{
} }
static void export_round_padstack( BOARD* pcb, double x, double y, double r, //{{{ // board edges and cutouts
LAYER_NUM bottom_layer, LAYER_NUM top_layer ) static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb )
{
CPOLYGONS_LIST bufferPcbOutlines; // stores the board main outlines
CPOLYGONS_LIST allLayerHoles; // Contains through holes, calculated only once
allLayerHoles.reserve( 20000 );
// Build a polygon from edge cut items
wxString msg;
if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines,
allLayerHoles, &msg ) )
{
msg << wxT( "\n\n" ) <<
_( "Unable to calculate the board outlines;\n"
"fall back to using the board boundary box." );
wxMessageBox( msg );
}
double scale = aModel.scale;
double dx = aModel.tx;
double dy = aModel.ty;
int i = 0;
int seg;
// deal with the solid outlines
int nvert = bufferPcbOutlines.GetCornersCount();
while( i < nvert )
{
seg = aModel.board.NewContour();
if( seg < 0 )
{
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_round_padstack( MODEL_VRML& aModel, BOARD* pcb,
double x, double y, double r,
LAYER_NUM bottom_layer, LAYER_NUM top_layer,
double hole )
{ {
int copper_layers = pcb->GetCopperLayerCount( ); 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 );
for( LAYER_NUM layer = bottom_layer; layer < copper_layers; ++layer ) if( hole > 0 )
{ {
// The last layer is always the component one, unless it's single face if( thru )
if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) ) aModel.holes.AddCircle( x, -y, hole, 1, true );
layer = LAST_COPPER_LAYER; else
aModel.top_copper.AddCircle( x, -y, hole, 1, true );
}
}
if( layer <= top_layer ) if( layer == bottom_layer )
export_vrml_circle( layer, x, y, x + r / 2, y, r ); break;
layer = bottom_layer;
} }
} }
static void export_vrml_via( BOARD* pcb, SEGVIA* via ) //{{{ static void export_vrml_via( MODEL_VRML& aModel, BOARD* pcb, SEGVIA* via )
{ {
double x, y, r, hole; double x, y, r, hole;
LAYER_NUM top_layer, bottom_layer; LAYER_NUM top_layer, bottom_layer;
r = via->GetWidth() / 2; hole = via->GetDrillValue() * aModel.scale / 2.0;
hole = via->GetDrillValue() / 2; r = via->GetWidth() * aModel.scale / 2.0;
x = via->GetStart().x; x = via->GetStart().x * aModel.scale + aModel.tx;
y = via->GetStart().y; y = via->GetStart().y * aModel.scale + aModel.ty;
via->ReturnLayerPair( &top_layer, &bottom_layer ); via->ReturnLayerPair( &top_layer, &bottom_layer );
// Export the via padstack // do not render a buried via
export_round_padstack( pcb, x, y, r, bottom_layer, top_layer ); if( top_layer != LAST_COPPER_LAYER && bottom_layer != FIRST_COPPER_LAYER )
return;
// Drill a hole // Export the via padstack
export_vrml_hole( via_triangles[via->GetShape()], top_layer, bottom_layer, x, y, hole ); export_round_padstack( aModel, pcb, x, y, r, bottom_layer, top_layer, hole );
} }
static void export_vrml_tracks( BOARD* pcb ) //{{{ static void export_vrml_tracks( MODEL_VRML& aModel, BOARD* pcb )
{ {
for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() ) for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() )
{ {
if( track->Type() == PCB_VIA_T ) if( track->Type() == PCB_VIA_T )
export_vrml_via( pcb, (SEGVIA*) track ); {
else export_vrml_via( aModel, pcb, (SEGVIA*) track );
export_vrml_line( track->GetLayer(), track->GetStart().x, track->GetStart().y, }
track->GetEnd().x, track->GetEnd().y, track->GetWidth(), 4 ); 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( MODEL_VRML& aModel, BOARD* aPcb )
static void export_vrml_zones( BOARD* pcb )
{ {
// Export fill segments
for( SEGZONE* segzone = pcb->m_Zone;
segzone != 0;
segzone = segzone->Next() )
{
// Fill tracks are exported with low subdivisions
if( segzone->Type() == PCB_ZONE_T )
export_vrml_line( segzone->GetLayer(), segzone->m_Start.x, segzone->m_Start.y,
segzone->m_End.x, segzone->m_End.y, segzone->m_Width, 1 );
}
// Export zone outlines double scale = aModel.scale;
for( int i = 0; i < pcb->GetAreaCount(); i++ ) double dx = aModel.tx;
double dy = aModel.ty;
double x, y;
for( int ii = 0; ii < aPcb->GetAreaCount(); ii++ )
{ {
ZONE_CONTAINER* zone = pcb->GetArea( i ); ZONE_CONTAINER* zone = aPcb->GetArea( ii );
if( ( zone->m_FilledPolysList.size() == 0 ) VRML_LAYER* vl;
||( zone->GetMinThickness() <= 1 ) )
continue;
int width = zone->GetMinThickness(); if( !VRMLEXPORT::GetLayer( aModel, zone->GetLayer(), &vl ) )
continue;
if( width > 0 ) if( !zone->IsFilled() )
{ {
int imax = zone->m_FilledPolysList.size() - 1; zone->SetFillMode( 0 ); // use filled polygons
LAYER_NUM layer = zone->GetLayer(); zone->BuildFilledSolidAreasPolygons( aPcb );
CPolyPt* firstcorner = &zone->m_FilledPolysList[0]; }
CPolyPt* begincorner = firstcorner; const CPOLYGONS_LIST& poly = zone->GetFilledPolysList();
// I'm not really positive about what he's doing here... int nvert = poly.GetCornersCount();
for( int ic = 1; ic <= imax; ic++ ) int i = 0;
while( i < nvert )
{ {
CPolyPt* endcorner = &zone->m_FilledPolysList[ic]; int seg = vl->NewContour();
bool first = true;
export_vrml_line( layer, begincorner->x, begincorner->y, if( seg < 0 )
endcorner->x, endcorner->y, width, 1 ); break;
if( (endcorner->end_contour) || (ic == imax) ) // the last corner of a filled area is found: draw it while( i < nvert )
{ {
export_vrml_line( layer, endcorner->x, endcorner->y, x = poly.GetX(i) * scale + dx;
firstcorner->x, firstcorner->y, width, 1 ); y = -(poly.GetY(i) * scale + dy);
ic++; vl->AddVertex( seg, x, y );
// A new contour? if( poly.IsEndContour(i) )
if( ic < imax - 1 ) break;
begincorner = firstcorner = &zone->m_FilledPolysList[ic];
} ++i;
else
begincorner = endcorner;
} }
// 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() ) if( module->IsVisible() )
{ {
...@@ -852,8 +912,9 @@ static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{ ...@@ -852,8 +912,9 @@ static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{
if( module->IsMirrored() ) if( module->IsMirrored() )
NEGATE( size.x ); // Text is mirrored NEGATE( size.x ); // Text is mirrored
s_text_layer = module->GetLayer(); VRMLEXPORT::model_vrml->s_text_layer = module->GetLayer();
s_text_width = module->GetThickness(); VRMLEXPORT::model_vrml->s_text_width = module->GetThickness();
DrawGraphicText( NULL, NULL, module->GetTextPosition(), BLACK, DrawGraphicText( NULL, NULL, module->GetTextPosition(), BLACK,
module->GetText(), module->GetDrawRotation(), size, module->GetText(), module->GetDrawRotation(), size,
module->GetHorizJustify(), module->GetVertJustify(), module->GetHorizJustify(), module->GetVertJustify(),
...@@ -864,95 +925,59 @@ static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{ ...@@ -864,95 +925,59 @@ static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{
} }
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(); LAYER_NUM layer = aOutline->GetLayer();
double x = aOutline->GetStart().x; double x = aOutline->GetStart().x * aModel.scale + aModel.tx;
double y = aOutline->GetStart().y; double y = aOutline->GetStart().y * aModel.scale + aModel.ty;
double xf = aOutline->GetEnd().x; double xf = aOutline->GetEnd().x * aModel.scale + aModel.tx;
double yf = aOutline->GetEnd().y; double yf = aOutline->GetEnd().y * aModel.scale + aModel.ty;
double w = aOutline->GetWidth(); double w = aOutline->GetWidth() * aModel.scale;
switch( aOutline->GetShape() ) switch( aOutline->GetShape() )
{ {
case S_ARC: 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; break;
case S_CIRCLE: case S_CIRCLE:
export_vrml_circle( layer, x, y, xf, yf, w ); export_vrml_circle( aModel, layer, x, y, xf, yf, w );
break; break;
default: default:
export_vrml_line( layer, x, y, xf, yf, w, 1 ); export_vrml_line( aModel, layer, x, y, xf, yf, w );
break; 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; // The (maybe offset) pad position
double hole_drill_h = (double) aPad->GetDrillSize().y / 2;
double hole_drill = std::min( hole_drill_w, hole_drill_h );
double hole_x = aPad->GetPosition().x;
double hole_y = aPad->GetPosition().y;
// Export the hole on the edge layer
if( hole_drill > 0 )
{
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() );
}
else
{
// Drill a round hole
export_vrml_hole( layer_triangles[EDGE_N],
FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
hole_x, hole_y, hole_drill );
}
}
// The pad proper, on the selected layers
LAYER_MSK layer_mask = aPad->GetLayerMask();
int copper_layers = pcb->GetCopperLayerCount( );
// The (maybe offseted) pad position
wxPoint pad_pos = aPad->ReturnShapePos(); wxPoint pad_pos = aPad->ReturnShapePos();
double pad_x = pad_pos.x; double pad_x = pad_pos.x * aModel.scale + aModel.tx;
double pad_y = pad_pos.y; double pad_y = pad_pos.y * aModel.scale + aModel.ty;
wxSize pad_delta = aPad->GetDelta(); wxSize pad_delta = aPad->GetDelta();
double pad_dx = pad_delta.x / 2; double pad_dx = pad_delta.x * aModel.scale / 2.0;
double pad_dy = pad_delta.y / 2; double pad_dy = pad_delta.y * aModel.scale / 2.0;
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 ) double pad_w = aPad->GetSize().x * aModel.scale / 2.0;
{ double pad_h = aPad->GetSize().y * aModel.scale / 2.0;
// The last layer is always the component one, unless it's single face
if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
layer = LAST_COPPER_LAYER;
if( layer_mask & GetLayerMask( layer ) )
{
// OK, the pad is on this layer, export it
switch( aPad->GetShape() ) switch( aPad->GetShape() )
{ {
case PAD_CIRCLE: case PAD_CIRCLE:
export_vrml_circle( layer, pad_x, pad_y, aLayer->AddCircle( pad_x, -pad_y, pad_w, 1, true );
pad_x + pad_w / 2, pad_y, pad_w ); aTinLayer->AddCircle( pad_x, -pad_y, pad_w, 1, false );
break; break;
case PAD_OVAL: case PAD_OVAL:
export_vrml_oval_pad( layer, pad_x, pad_y, aLayer->AddSlot( pad_x, -pad_y, pad_w * 2.0, pad_h * 2.0,
pad_w * 2, pad_h * 2, aPad->GetOrientation() ); 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; break;
case PAD_RECT: case PAD_RECT:
...@@ -962,32 +987,86 @@ static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{ ...@@ -962,32 +987,86 @@ static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{
case PAD_TRAPEZOID: case PAD_TRAPEZOID:
{ {
int coord[8] = double coord[8] =
{ {
KiROUND( -pad_w - pad_dy ), KiROUND( +pad_h + pad_dx ), -pad_w + pad_dy, -pad_h - pad_dx,
KiROUND( -pad_w + pad_dy ), KiROUND( -pad_h - pad_dx ), -pad_w - pad_dy, pad_h + pad_dx,
KiROUND( +pad_w - pad_dy ), KiROUND( +pad_h - pad_dx ), +pad_w - pad_dy, -pad_h + pad_dx,
KiROUND( +pad_w + pad_dy ), KiROUND( -pad_h + pad_dx ), +pad_w + pad_dy, pad_h - pad_dx
}; };
for( int i = 0; i < 4; i++ ) for( int i = 0; i < 4; i++ )
{ {
RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() ); RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
coord[i * 2] += KiROUND( pad_x ); coord[i * 2] += pad_x;
coord[i * 2 + 1] += KiROUND( pad_y ); coord[i * 2 + 1] += pad_y;
} }
bag_flat_quad( layer, coord[0], coord[1], int lines = aLayer->NewContour();
coord[2], coord[3],
coord[4], coord[5], if( lines < 0 )
coord[6], coord[7] ); 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; break;
default: 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 )
{
if( aPad->GetDrillShape() == PAD_OVAL )
{
// Oblong hole (slot)
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
aModel.holes.AddCircle( hole_x, -hole_y, hole_drill, 1, true );
}
}
// The pad proper, on the selected layers
LAYER_MSK layer_mask = aPad->GetLayerMask();
if( layer_mask & LAYER_BACK )
{
export_vrml_padshape( aModel, &aModel.bot_copper, &aModel.bot_tin, aPad );
} }
if( layer_mask & LAYER_FRONT )
{
export_vrml_padshape( aModel, &aModel.top_copper, &aModel.top_tin, aPad );
} }
} }
...@@ -1021,23 +1100,28 @@ static void compose_quat( double q1[4], double q2[4], double qr[4] ) ...@@ -1021,23 +1100,28 @@ static void compose_quat( double q1[4], double q2[4], double qr[4] )
{ {
double tmp[4]; double tmp[4];
tmp[0] = q2[3] *q1[0] + q2[0] *q1[3] + q2[1] *q1[2] - q2[2] *q1[1]; 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[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[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]; 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]; 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, static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule,
FILE* aOutputFile, FILE* aOutputFile,
double aVRMLModelsToBiu, double aVRMLModelsToBiu,
bool aExport3DFiles, const wxString & a3D_Subdir, bool aExport3DFiles, const wxString& a3D_Subdir )
double boardIU2WRML )
{ {
// Reference and value // Reference and value
if( aModule->Reference().IsVisible() )
export_vrml_text_module( &aModule->Reference() ); export_vrml_text_module( &aModule->Reference() );
if( aModule->Value().IsVisible() )
export_vrml_text_module( &aModule->Value() ); export_vrml_text_module( &aModule->Value() );
// Export module edges // Export module edges
...@@ -1050,7 +1134,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1050,7 +1134,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
break; break;
case PCB_MODULE_EDGE_T: case PCB_MODULE_EDGE_T:
export_vrml_edge_module( dynamic_cast<EDGE_MODULE*>( item ) ); export_vrml_edge_module( aModel, dynamic_cast<EDGE_MODULE*>( item ) );
break; break;
default: default:
...@@ -1060,7 +1144,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1060,7 +1144,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
// Export pads // Export pads
for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() ) for( D_PAD* pad = aModule->Pads(); pad; pad = pad->Next() )
export_vrml_pad( aPcb, pad ); export_vrml_pad( aModel, aPcb, pad );
bool isFlipped = aModule->GetLayer() == LAYER_N_BACK; bool isFlipped = aModule->GetLayer() == LAYER_N_BACK;
...@@ -1072,7 +1156,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1072,7 +1156,7 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
if( fname.IsEmpty() ) if( fname.IsEmpty() )
continue; continue;
if( ! wxFileName::FileExists( fname ) ) if( !wxFileName::FileExists( fname ) )
{ {
wxFileName fn = fname; wxFileName fn = fname;
fname = wxGetApp().FindLibraryPath( fn ); fname = wxGetApp().FindLibraryPath( fn );
...@@ -1098,9 +1182,9 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1098,9 +1182,9 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
* for footprints that are flipped * for footprints that are flipped
* When flipped, axis rotation is the horizontal axis (X axis) * When flipped, axis rotation is the horizontal axis (X axis)
*/ */
double rotx = - vrmlm->m_MatRotation.x; double rotx = -vrmlm->m_MatRotation.x;
double roty = - vrmlm->m_MatRotation.y; double roty = -vrmlm->m_MatRotation.y;
double rotz = - vrmlm->m_MatRotation.z; double rotz = -vrmlm->m_MatRotation.z;
if( isFlipped ) if( isFlipped )
{ {
...@@ -1138,16 +1222,16 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1138,16 +1222,16 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0; double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0;
if( isFlipped ) if( isFlipped )
NEGATE(offsetz); NEGATE( offsetz );
else // In normal mode, Y axis is reversed in Pcbnew. else // In normal mode, Y axis is reversed in Pcbnew.
NEGATE(offsety); NEGATE( offsety );
RotatePoint( &offsetx, &offsety, aModule->GetOrientation() ); RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );
fprintf( aOutputFile, " translation %g %g %g\n", fprintf( aOutputFile, " translation %g %g %g\n",
(offsetx + aModule->GetPosition().x) * boardIU2WRML, (offsetx + aModule->GetPosition().x) * aModel.scale + aModel.tx,
- (offsety + aModule->GetPosition().y) * boardIU2WRML, // Y axis is reversed in Pcbnew -(offsety + aModule->GetPosition().y) * aModel.scale - aModel.ty,
(offsetz + layer_z[aModule->GetLayer()]) * boardIU2WRML); (offsetz * aModel.scale ) + aModel.GetLayerZ( aModule->GetLayer() ) );
fprintf( aOutputFile, " scale %g %g %g\n", fprintf( aOutputFile, " scale %g %g %g\n",
vrmlm->m_MatScale.x * aVRMLModelsToBiu, vrmlm->m_MatScale.x * aVRMLModelsToBiu,
...@@ -1175,30 +1259,22 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule, ...@@ -1175,30 +1259,22 @@ static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
TO_UTF8( fname ) ); TO_UTF8( fname ) );
fprintf( aOutputFile, " }\n" ); fprintf( aOutputFile, " }\n" );
} }
}
}
static void write_and_empty_triangle_bag( FILE* output_file, TRIANGLEBAG& triangles,
int color, double boardIU2WRML )
{
if( !triangles.empty() )
{
write_triangle_bag( output_file, color, triangles, boardIU2WRML );
triangles.clear( );
} }
} }
bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName,
double aMMtoWRMLunit, bool aExport3DFiles, double aMMtoWRMLunit, bool aExport3DFiles,
const wxString & a3D_Subdir ) const wxString& a3D_Subdir )
{ {
wxString msg; wxString msg;
FILE* output_file; FILE* output_file;
BOARD* pcb = GetBoard(); BOARD* pcb = GetBoard();
MODEL_VRML model3d;
VRMLEXPORT::model_vrml = &model3d;
output_file = wxFopen( aFullFileName, wxT( "wt" ) ); output_file = wxFopen( aFullFileName, wxT( "wt" ) );
if( output_file == NULL ) if( output_file == NULL )
...@@ -1217,39 +1293,36 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, ...@@ -1217,39 +1293,36 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName,
" title \"%s - Generated by Pcbnew\"\n" " title \"%s - Generated by Pcbnew\"\n"
"}\n", TO_UTF8( name ) ); "}\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. // Global VRML scale to export to a different scale.
// (aMMtoWRMLScale = 1.0 to export in mm) model3d.scale = aMMtoWRMLunit / MM_PER_IU;
double boardIU2WRML = 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" ); fprintf( output_file, "Transform {\n" );
/* Define the translation to have the board centre to the 2D axis origin // compute the offset to center the board on (0, 0, 0)
* more easy for rotations... // XXX - NOTE: we should allow the user a GUI option to specify the offset
*/
EDA_RECT bbbox = pcb->ComputeBoundingBox(); EDA_RECT bbbox = pcb->ComputeBoundingBox();
double dx = boardIU2WRML * bbbox.Centre().x; model3d.SetOffset( -model3d.scale * bbbox.Centre().x, -model3d.scale * bbbox.Centre().y );
double dy = boardIU2WRML * bbbox.Centre().y;
fprintf( output_file, " translation %g %g 0.0\n", -dx, dy );
fprintf( output_file, " children [\n" ); fprintf( output_file, " children [\n" );
// Preliminary computation: the z value for each layer // Preliminary computation: the z value for each layer
compute_layer_Zs( pcb ); compute_layer_Zs( model3d, pcb );
// board edges and cutouts
export_vrml_board( model3d, pcb );
// Drawing and text on the board, and edges which are special // Drawing and text on the board
export_vrml_drawings( pcb ); export_vrml_drawings( model3d, pcb );
// Export vias and trackage // Export vias and trackage
export_vrml_tracks( pcb ); export_vrml_tracks( model3d, pcb );
// Export zone fills // Export zone fills
/* TODO export_vrml_zones(pcb); export_vrml_zones( model3d, pcb);
*/
/* scaling factor to convert 3D models to board units (decimils) /* scaling factor to convert 3D models to board units (decimils)
* Usually we use Wings3D to create thems. * Usually we use Wings3D to create thems.
...@@ -1261,25 +1334,12 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, ...@@ -1261,25 +1334,12 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName,
// Export footprints // Export footprints
for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() ) for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() )
export_vrml_module( pcb, module, output_file, export_vrml_module( model3d, pcb, module, output_file,
wrml_3D_models_scaling_factor, wrml_3D_models_scaling_factor,
aExport3DFiles, a3D_Subdir, aExport3DFiles, a3D_Subdir );
boardIU2WRML );
// write out the board and all layers
/* Output the bagged triangles for each layer write_layers( model3d, output_file, pcb );
* 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 );
// Close the outer 'transform' node // Close the outer 'transform' node
fputs( "]\n}\n", output_file ); fputs( "]\n}\n", output_file );
...@@ -1296,7 +1356,7 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName, ...@@ -1296,7 +1356,7 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName,
* some characters cannot be used in filenames, * some characters cannot be used in filenames,
* this function change them to "_" * this function change them to "_"
*/ */
static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal ) static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal )
{ {
if( aDirSepIsIllegal ) if( aDirSepIsIllegal )
aFileName.Replace( wxT( "/" ), wxT( "_" ) ); aFileName.Replace( wxT( "/" ), wxT( "_" ) );
......
/*
* file: vrml_board.cpp
*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 Cirilo Bernardo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/*
* NOTES ON OUTPUT PRECISION:
*
* If we use %.6f then we have no need for special unit dependent formatting:
*
* inch: .0254 microns
* mm: 0.001 microns
* m: 1 micron
*
*/
#include <sstream>
#include <string>
#include <iomanip>
#include <cmath>
#include <fctsys.h>
#include <vrml_board.h>
#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<int>* contour = new std::list<int>;
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<int>* 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<std::list<int>*>::const_iterator obeg = outline.begin();
std::list<std::list<int>*>::const_iterator oend = outline.end();
int pi;
std::list<int>::const_iterator begin;
std::list<int>::const_iterator end;
GLdouble pt[3];
VERTEX_3D* vp;
while( obeg != oend )
{
if( (*obeg)->size() < 3 )
{
++obeg;
continue;
}
gluTessBeginContour( tess );
begin = (*obeg)->begin();
end = (*obeg)->end();
while( begin != end )
{
pi = *begin;
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<TRIPLET_3D>::const_iterator tbeg = triplets.begin();
std::list<TRIPLET_3D>::const_iterator tend = triplets.end();
int i = 1;
if( top )
fprintf( fp, "%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
else
fprintf( fp, "%d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
++tbeg;
while( tbeg != tend )
{
if( (i++ & 7) == 4 )
{
i = 1;
if( top )
fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
else
fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
}
else
{
if( top )
fprintf( fp, ", %d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
else
fprintf( fp, ", %d, %d, %d, -1", tbeg->i2, tbeg->i1, tbeg->i3 );
}
++tbeg;
}
return true;
}
// writes out the index list for a 3D feature
bool VRML_LAYER::Write3DIndices( FILE* fp )
{
if( triplets.empty() )
{
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<TRIPLET_3D>::const_iterator tbeg = triplets.begin();
std::list<TRIPLET_3D>::const_iterator tend = triplets.end();
int i = 1;
int idx2 = ordmap.size(); // index to the bottom vertices
// print out the top vertices
fprintf( fp, "%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
++tbeg;
while( tbeg != tend )
{
if( (i++ & 7) == 4 )
{
i = 1;
fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
}
else
{
fprintf( fp, ", %d, %d, %d, -1", tbeg->i1, tbeg->i2, tbeg->i3 );
}
++tbeg;
}
// print out the bottom vertices
tbeg = triplets.begin();
while( tbeg != tend )
{
if( (i++ & 7) == 4 )
{
i = 1;
fprintf( fp, ",\n%d, %d, %d, -1", tbeg->i2 + idx2, tbeg->i1 + idx2, tbeg->i3 + idx2 );
}
else
{
fprintf( fp, ", %d, %d, %d, -1", tbeg->i2 + idx2, tbeg->i1 + idx2, tbeg->i3 + idx2 );
}
++tbeg;
}
int firstPoint;
int lastPoint;
int curPoint;
std::list<std::list<int>*>::const_iterator obeg = outline.begin();
std::list<std::list<int>*>::const_iterator oend = outline.end();
std::list<int>* cp;
std::list<int>::const_iterator cbeg;
std::list<int>::const_iterator cend;
while( obeg != oend )
{
cp = *obeg;
if( cp->size() < 3 )
{
++obeg;
continue;
}
cbeg = cp->begin();
cend = cp->end();
firstPoint = *(cbeg++);
lastPoint = firstPoint;
while( cbeg != cend )
{
curPoint = *(cbeg++);
fprintf( fp, ",\n %d, %d, %d, -1, %d, %d, %d, -1",
curPoint, lastPoint, curPoint + idx2,
curPoint + idx2, lastPoint, lastPoint + idx2 );
lastPoint = curPoint;
}
fprintf( fp, ",\n %d, %d, %d, -1, %d, %d, %d, -1",
firstPoint, lastPoint, firstPoint + idx2,
firstPoint + idx2, lastPoint, lastPoint + idx2 );
++obeg;
}
return true;
}
// add a triangular facet (triplet) to the ouptut index list
bool VRML_LAYER::addTriplet( VERTEX_3D* p0, VERTEX_3D* p1, VERTEX_3D* p2 )
{
double dx0 = p1->x - p0->x;
double dx1 = p2->x - p0->x;
double dy0 = p1->y - p0->y;
double dy1 = p2->y - p0->y;
// this number is chosen because we shall only write 6 decimal places
// on the VRML output
double err = 0.000001;
// test if the triangles are degenerate (parallel sides)
if( dx0 < err && dx0 > -err && dx1 < err && dx1 > -err )
return false;
if( dy0 < err && dy0 > -err && dy1 < err && dy1 > -err )
return false;
double sl0 = dy0 / dx0;
double sl1 = dy1 / dx1;
double dsl = sl1 - sl0;
if( dsl < err && dsl > -err )
return false;
triplets.push_back( TRIPLET_3D( p0->o, p1->o, p2->o ) );
return true;
}
// add an extra vertex (to be called only by the COMBINE callback)
VERTEX_3D* VRML_LAYER::AddExtraVertex( double x, double y )
{
VERTEX_3D* vertex = new VERTEX_3D;
if( !vertex )
{
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<int>* loop = new std::list<int>;
if( !loop )
break;
for( unsigned int i = 0; i < vlist.size(); ++i )
{
loop->push_back( vlist[i]->o );
}
outline.push_back( loop );
}
break;
case GL_TRIANGLE_FAN:
processFan();
break;
case GL_TRIANGLE_STRIP:
processStrip();
break;
case GL_TRIANGLES:
processTri();
break;
default:
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<int>::const_iterator begin;
std::list<int>::const_iterator end;
GLdouble pt[3];
VERTEX_3D* vp;
for( i = 0; i < contours.size(); ++i )
{
if( contours[i]->size() < 3 )
continue;
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<int>::const_iterator cbeg;
std::list<int>::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;
}
/*
* file: vrml_board.h
*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 Cirilo Bernardo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file vrml_board.h
*/
/*
* Classes and structures to support the tesselation of a
* PCB for VRML output.
*/
#ifndef VRML_BOARD_H
#define VRML_BOARD_H
#ifdef __WXMAC__
# ifdef __DARWIN__
# include <OpenGL/glu.h>
# else
# include <glu.h>
# endif
#else
# include <GL/glu.h>
#endif
#include <cstdio>
#include <vector>
#include <list>
#include <utility>
#ifndef M_PI2
#define M_PI2 ( M_PI / 2.0 )
#endif
#ifndef M_PI4
#define M_PI4 ( M_PI / 4.0 )
#endif
struct GLUtesselator;
struct VERTEX_3D
{
double x;
double y;
int i; // vertex index
int o; // vertex order
};
struct TRIPLET_3D
{
int i1, i2, i3;
TRIPLET_3D( int p1, int p2, int p3 )
{
i1 = p1;
i2 = p2;
i3 = p3;
}
};
class VRML_LAYER
{
private:
bool fix; // when true, no more vertices may be added by the user
int idx; // vertex index (number of contained vertices)
int ord; // vertex order (number of ordered vertices)
std::vector<VERTEX_3D*> vertices; // vertices of all contours
std::vector<std::list<int>*> contours; // lists of vertices for each contour
std::vector< double > areas; // area of the contours (positive if winding is CCW)
std::list<TRIPLET_3D> triplets; // output facet triplet list (triplet of ORDER values)
std::list<std::list<int>*> outline; // indices for outline outputs (index by ORDER values)
std::vector<int> ordmap; // mapping of ORDER to INDEX
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<VERTEX_3D*> extra_verts; // extra vertices added for outlines and facets
std::vector<VERTEX_3D*> vlist; // vertex list for the GL command in progress
VRML_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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment