diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h
index fd2badc482..fe715084fd 100644
--- a/include/wxPcbStruct.h
+++ b/include/wxPcbStruct.h
@@ -479,6 +479,25 @@ public:
void ExportToGenCAD( wxCommandEvent& event );
+ /**
+ * Function OnExportVRML
+ * will export the current BOARD to a VRML file.
+ */
+ void OnExportVRML( wxCommandEvent& event );
+
+ /**
+ * Function ExportVRML_File
+ * Creates the file(s) exporting current BOARD to a VRML file.
+ * @param aFullFileName = the full filename of the file to create
+ * @param aScale = the general scaling factor. 1.0 to export in inches
+ * @param aExport3DFiles = true to copy 3D shapes in the subir a3D_Subdir
+ * @param a3D_Subdir = sub directory where 3D sahpes files are copied
+ * used only when aExport3DFiles == true
+ * @return true if Ok.
+ */
+ bool ExportVRML_File( const wxString & aFullFileName, double aScale,
+ bool aExport3DFiles, const wxString & a3D_Subdir );
+
/**
* Function ExporttoSPECCTRA
* will export the current BOARD to a specctra dsn file. See
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 130bc99fb0..4c99c51fae 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -51,6 +51,7 @@ set(PCBNEW_SRCS
dialog_edit_module_text.cpp
dialog_edit_module_text_base.cpp
dialog_exchange_modules_base.cpp
+ dialog_export_3Dfiles_base.cpp
dialog_freeroute_exchange.cpp
dialog_freeroute_exchange_base.cpp
# dialog_gendrill.cpp
@@ -99,6 +100,7 @@ set(PCBNEW_SRCS
edtxtmod.cpp
event_handlers_tracks_vias_sizes.cpp
export_gencad.cpp
+ export_vrml.cpp
files.cpp
find.cpp
gen_drill_report_files.cpp
diff --git a/pcbnew/dialog_export_3Dfiles_base.cpp b/pcbnew/dialog_export_3Dfiles_base.cpp
new file mode 100644
index 0000000000..0887f95c10
--- /dev/null
+++ b/pcbnew/dialog_export_3Dfiles_base.cpp
@@ -0,0 +1,80 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Apr 16 2008)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "dialog_export_3Dfiles_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+DIALOG_EXPORT_3DFILE_BASE::DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+
+ wxBoxSizer* bSizer1;
+ bSizer1 = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* bUpperSizer;
+ bUpperSizer = new wxBoxSizer( wxVERTICAL );
+
+ bUpperSizer->SetMinSize( wxSize( 450,-1 ) );
+ m_staticText1 = new wxStaticText( this, wxID_ANY, _("Wrml main file filename:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText1->Wrap( -1 );
+ bUpperSizer->Add( m_staticText1, 0, wxTOP|wxRIGHT|wxLEFT, 5 );
+
+ m_filePicker = new wxFilePickerCtrl( this, wxID_ANY, wxEmptyString, _("Save VRML Board File"), wxT("*.wrl"), wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE|wxFLP_SAVE );
+ bUpperSizer->Add( m_filePicker, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 );
+
+ m_staticText3 = new wxStaticText( this, wxID_ANY, _("Wrml 3D footprints shapes subdir:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText3->Wrap( -1 );
+ bUpperSizer->Add( m_staticText3, 0, wxTOP|wxRIGHT|wxLEFT, 5 );
+
+ m_SubdirNameCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+ bUpperSizer->Add( m_SubdirNameCtrl, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+
+ bSizer1->Add( bUpperSizer, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bLowerSizer;
+ bLowerSizer = new wxBoxSizer( wxHORIZONTAL );
+
+ wxString m_rbSelectUnitsChoices[] = { _("Inch"), _("mm"), _("Meter") };
+ int m_rbSelectUnitsNChoices = sizeof( m_rbSelectUnitsChoices ) / sizeof( wxString );
+ m_rbSelectUnits = new wxRadioBox( this, wxID_ANY, _("Units:"), wxDefaultPosition, wxDefaultSize, m_rbSelectUnitsNChoices, m_rbSelectUnitsChoices, 1, wxRA_SPECIFY_COLS );
+ m_rbSelectUnits->SetSelection( 0 );
+ bLowerSizer->Add( m_rbSelectUnits, 1, wxALL|wxEXPAND, 5 );
+
+ wxString m_rb3DFilesOptionChoices[] = { _("Copy 3D Shapes Files in Subdir"), _("Use Absolute Path in Wrml File ") };
+ int m_rb3DFilesOptionNChoices = sizeof( m_rb3DFilesOptionChoices ) / sizeof( wxString );
+ m_rb3DFilesOption = new wxRadioBox( this, wxID_ANY, _("3D Shapes Files Option:"), wxDefaultPosition, wxDefaultSize, m_rb3DFilesOptionNChoices, m_rb3DFilesOptionChoices, 1, wxRA_SPECIFY_COLS );
+ m_rb3DFilesOption->SetSelection( 0 );
+ bLowerSizer->Add( m_rb3DFilesOption, 1, wxALL|wxEXPAND, 5 );
+
+ bSizer1->Add( bLowerSizer, 1, wxEXPAND, 5 );
+
+ m_staticline1 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer1->Add( m_staticline1, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 );
+
+ m_sdbSizer1 = new wxStdDialogButtonSizer();
+ m_sdbSizer1OK = new wxButton( this, wxID_OK );
+ m_sdbSizer1->AddButton( m_sdbSizer1OK );
+ m_sdbSizer1Cancel = new wxButton( this, wxID_CANCEL );
+ m_sdbSizer1->AddButton( m_sdbSizer1Cancel );
+ m_sdbSizer1->Realize();
+ bSizer1->Add( m_sdbSizer1, 0, wxEXPAND|wxALL, 5 );
+
+ this->SetSizer( bSizer1 );
+ this->Layout();
+
+ // Connect Events
+ m_sdbSizer1Cancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_3DFILE_BASE::OnCancelClick ), NULL, this );
+ m_sdbSizer1OK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_3DFILE_BASE::OnOkClick ), NULL, this );
+}
+
+DIALOG_EXPORT_3DFILE_BASE::~DIALOG_EXPORT_3DFILE_BASE()
+{
+ // Disconnect Events
+ m_sdbSizer1Cancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_3DFILE_BASE::OnCancelClick ), NULL, this );
+ m_sdbSizer1OK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_3DFILE_BASE::OnOkClick ), NULL, this );
+}
diff --git a/pcbnew/dialog_export_3Dfiles_base.fbp b/pcbnew/dialog_export_3Dfiles_base.fbp
new file mode 100644
index 0000000000..ab7b0a744d
--- /dev/null
+++ b/pcbnew/dialog_export_3Dfiles_base.fbp
@@ -0,0 +1,496 @@
+
+
+
+
+
diff --git a/pcbnew/dialog_export_3Dfiles_base.h b/pcbnew/dialog_export_3Dfiles_base.h
new file mode 100644
index 0000000000..25b6f7d90a
--- /dev/null
+++ b/pcbnew/dialog_export_3Dfiles_base.h
@@ -0,0 +1,59 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Apr 16 2008)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __dialog_export_3Dfiles_base__
+#define __dialog_export_3Dfiles_base__
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+///////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class DIALOG_EXPORT_3DFILE_BASE
+///////////////////////////////////////////////////////////////////////////////
+class DIALOG_EXPORT_3DFILE_BASE : public wxDialog
+{
+ private:
+
+ protected:
+ wxStaticText* m_staticText1;
+ wxFilePickerCtrl* m_filePicker;
+ wxStaticText* m_staticText3;
+ wxTextCtrl* m_SubdirNameCtrl;
+ wxRadioBox* m_rbSelectUnits;
+ wxRadioBox* m_rb3DFilesOption;
+ wxStaticLine* m_staticline1;
+ wxStdDialogButtonSizer* m_sdbSizer1;
+ wxButton* m_sdbSizer1OK;
+ wxButton* m_sdbSizer1Cancel;
+
+ // Virtual event handlers, overide them in your derived class
+ virtual void OnCancelClick( wxCommandEvent& event ){ event.Skip(); }
+ virtual void OnOkClick( wxCommandEvent& event ){ event.Skip(); }
+
+
+ public:
+ DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 370,252 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
+ ~DIALOG_EXPORT_3DFILE_BASE();
+
+};
+
+#endif //__dialog_export_3Dfiles_base__
diff --git a/pcbnew/export_vrml.cpp b/pcbnew/export_vrml.cpp
new file mode 100644
index 0000000000..365a2baf1b
--- /dev/null
+++ b/pcbnew/export_vrml.cpp
@@ -0,0 +1,1244 @@
+#include "fctsys.h"
+#include "common.h"
+#include "confirm.h"
+#include "kicad_string.h"
+#include "gestfich.h"
+#include "pcbnew.h"
+#include "wxPcbStruct.h"
+#include "drawtxt.h"
+#include "trigo.h"
+#include "appl_wxstruct.h"
+#include "3d_struct.h"
+
+#include
+#include
+
+
+/* I use this a lot... */
+static const double PI2 = M_PI / 2;
+
+/* Absolutely not optimized triangle bag :D */
+struct VRMLPt
+{
+ double x, y, z;
+};
+struct FlatPt
+{
+ FlatPt( double _x = 0, double _y = 0 ) : x( _x ), y( _y ) { }
+ double x, y;
+};
+struct Triangle
+{
+ Triangle( double x1, double y1, double z1,
+ double x2, double y2, double z2,
+ double x3, double y3, double z3 )
+ {
+ p1.x = x1; p1.y = y1; p1.z = z1;
+ p2.x = x2; p2.y = y2; p2.z = z2;
+ p3.x = x3; p3.y = y3; p3.z = z3;
+ }
+ Triangle() { }
+ VRMLPt p1, p2, p3;
+};
+typedef std::vector TriangleBag;
+
+/* A flat triangle fan */
+struct FlatFan
+{
+ FlatPt c;
+ std::vector pts;
+ void add( double x, double y )
+ {
+ pts.push_back( FlatPt( x, y ) );
+ }
+ void bag( int layer, bool close = true );
+};
+
+/* A flat quad ring */
+struct FlatRing
+{
+ std::vector inner;
+ std::vector outer;
+ void add_inner( double x, double y )
+ {
+ inner.push_back( FlatPt( x, y ) );
+ }
+
+ void add_outer( double x, double y )
+ {
+ outer.push_back( FlatPt( x, y ) );
+ }
+
+ void bag( int layer, bool close = true );
+};
+
+/* A vertical quad loop */
+struct VLoop
+{
+ std::vector pts;
+ double z_top, z_bottom;
+ void add( double x, double y )
+ {
+ pts.push_back( FlatPt( x, y ) );
+ }
+
+ void bag( TriangleBag& triangles, bool close = true );
+};
+
+/* The bags for all the layers */
+static TriangleBag layer_triangles[LAYER_COUNT];
+static TriangleBag via_triangles[4];
+static double layer_z[LAYER_COUNT];
+
+static void bag_flat_triangle( int layer, /*{{{*/
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3 )
+{
+ double z = layer_z[layer];
+
+ layer_triangles[layer].push_back( Triangle( x1, y1, z,
+ x2, y2, z, x3, y3, z ) );
+}
+
+
+void FlatFan::bag( int layer, bool close ) /*{{{*/
+{
+ unsigned i;
+
+ for( i = 0; i < pts.size() - 1; i++ )
+ bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y,
+ pts[i + 1].x, pts[i + 1].y );
+
+ if( close )
+ bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y,
+ pts[0].x, pts[0].y );
+}
+
+
+static void bag_flat_quad( int layer, /*{{{*/
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3,
+ double x4, double y4 )
+{
+ bag_flat_triangle( layer, x1, y1, x3, y3, x2, y2 );
+ bag_flat_triangle( layer, x2, y2, x3, y3, x4, y4 );
+}
+
+
+void FlatRing::bag( int layer, bool close ) /*{{{*/
+{
+ unsigned i;
+
+ for( i = 0; i < inner.size() - 1; i++ )
+ bag_flat_quad( layer,
+ inner[i].x, inner[i].y,
+ outer[i].x, outer[i].y,
+ inner[i + 1].x, inner[i + 1].y,
+ outer[i + 1].x, outer[i + 1].y );
+
+ if( close )
+ bag_flat_quad( layer,
+ inner[i].x, inner[i].y,
+ outer[i].x, outer[i].y,
+ inner[0].x, inner[0].y,
+ outer[0].x, outer[0].y );
+}
+
+
+static void bag_vquad( TriangleBag& triangles, /*{{{*/
+ double x1, double y1, double x2, double y2,
+ double z1, double z2 )
+{
+ triangles.push_back( Triangle(
+ x1, y1, z1,
+ x2, y2, z1,
+ x2, y2, z2 ) );
+ triangles.push_back( Triangle(
+ x1, y1, z1,
+ x2, y2, z2,
+ x1, y1, z2 ) );
+}
+
+
+void VLoop::bag( TriangleBag& triangles, bool close ) /*{{{*/
+{
+ unsigned i;
+
+ for( i = 0; i < pts.size() - 1; i++ )
+ bag_vquad( triangles, pts[i].x, pts[i].y,
+ pts[i + 1].x, pts[i + 1].y,
+ z_top, z_bottom );
+
+ if( close )
+ bag_vquad( triangles, pts[i].x, pts[i].y,
+ pts[0].x, pts[0].y,
+ z_top, z_bottom );
+}
+
+
+static void write_triangle_bag( FILE* output_file, int color_index, /*{{{*/
+ const TriangleBag& triangles )
+{
+ /* A lot of nodes are not required, but blender sometimes chokes
+ * without them */
+ static const char* shape_boiler[] =
+ {
+ "Transform {\n",
+ " translation 0.0 0.0 0.0\n",
+ " rotation 1.0 0.0 0.0 0.0\n",
+ " scale 1.0 1.0 1.0\n",
+ " children [\n",
+ " Group {\n",
+ " children [\n",
+ " Shape {\n",
+ " appearance Appearance {\n",
+ " material Material {\n",
+ 0, /* Material marker */
+ " ambientIntensity 0.8\n",
+ " transparency 0.2\n",
+ " shininess 0.2\n",
+ " }\n",
+ " }\n",
+ " geometry IndexedFaceSet {\n",
+ " solid TRUE\n",
+ " coord Coordinate {\n",
+ " point [\n",
+ 0, /* Coordinates marker */
+ " ]\n",
+ " }\n",
+ " coordIndex [\n",
+ 0, /* Index marker */
+ " ]\n",
+ " }\n",
+ " }\n",
+ " ]\n",
+ " }\n",
+ " ]\n",
+ "}\n",
+ 0 /* End marker */
+ };
+ int marker_found = 0, lineno = 0;
+
+ while( marker_found < 4 )
+ {
+ if( shape_boiler[lineno] )
+ fputs( shape_boiler[lineno], output_file );
+ else
+ {
+ marker_found++;
+ switch( marker_found )
+ {
+ case 1: /* Material marker */
+ fprintf( output_file,
+ " diffuseColor %g %g %g\n",
+ (double) ColorRefs[color_index].m_Red / 255.0,
+ (double) ColorRefs[color_index].m_Green / 255.0,
+ (double) ColorRefs[color_index].m_Blue / 255.0 );
+ fprintf( output_file,
+ " specularColor %g %g %g\n",
+ (double) ColorRefs[color_index].m_Red / 255.0,
+ (double) ColorRefs[color_index].m_Green / 255.0,
+ (double) ColorRefs[color_index].m_Blue / 255.0 );
+ fprintf( output_file,
+ " emissiveColor %g %g %g\n",
+ (double) ColorRefs[color_index].m_Red / 255.0,
+ (double) ColorRefs[color_index].m_Green / 255.0,
+ (double) ColorRefs[color_index].m_Blue / 255.0 );
+ break;
+
+ case 2:
+ {
+ /* Coordinates marker */
+ for( TriangleBag::const_iterator i = triangles.begin();
+ i != triangles.end();
+ i++ )
+ {
+ fprintf( output_file, "%g %g %g\n",
+ i->p1.x, -i->p1.y, i->p1.z );
+ fprintf( output_file, "%g %g %g\n",
+ i->p2.x, -i->p2.y, i->p2.z );
+ fprintf( output_file, "%g %g %g\n",
+ i->p3.x, -i->p3.y, i->p3.z );
+ }
+ }
+ break;
+
+ case 3:
+ {
+ /* Index marker */
+ /* OK, that's sick ... */
+ int j = 0;
+ for( TriangleBag::const_iterator i = triangles.begin();
+ i != triangles.end();
+ i++ )
+ {
+ fprintf( output_file, "%d %d %d -1\n",
+ j, j + 1, j + 2 );
+ j += 3;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ lineno++;
+ }
+}
+
+
+static void compute_layer_Zs( BOARD* pcb ) /*{{{*/
+{
+ int copper_layers = pcb->GetCopperLayerCount( );
+
+ // We call it 'layer' thickness, but it's the whole board thickness!
+ double board_thickness = pcb->GetBoardDesignSettings()->m_BoardThickness;
+ double half_thickness = board_thickness / 2;
+
+ /* Compute each layer's Z value, more or less like the 3d view */
+ for( int i = 0; i <= LAYER_N_FRONT; i++ )
+ {
+ if( i < copper_layers )
+ layer_z[i] = board_thickness * i / (copper_layers - 1) - half_thickness;
+ else
+ layer_z[i] = half_thickness; /* The component layer... */
+ }
+
+ /* To avoid rounding interference, we apply an epsilon to each
+ * successive layer */
+ const double epsilon_z = 10; /* That's 1 mils, about 1/50 mm */
+ layer_z[SOLDERPASTE_N_BACK] = -half_thickness - epsilon_z * 4;
+ layer_z[ADHESIVE_N_BACK] = -half_thickness - epsilon_z * 3;
+ layer_z[SILKSCREEN_N_BACK] = -half_thickness - epsilon_z * 2;
+ layer_z[SOLDERMASK_N_BACK] = -half_thickness - epsilon_z;
+ layer_z[SOLDERMASK_N_FRONT] = half_thickness + epsilon_z;
+ layer_z[SILKSCREEN_N_FRONT] = half_thickness + epsilon_z * 2;
+ layer_z[ADHESIVE_N_FRONT] = half_thickness + epsilon_z * 3;
+ layer_z[SOLDERPASTE_N_FRONT] = half_thickness + epsilon_z * 4;
+ layer_z[DRAW_N] = half_thickness + epsilon_z * 5;
+ layer_z[COMMENT_N] = half_thickness + epsilon_z * 6;
+ layer_z[ECO1_N] = half_thickness + epsilon_z * 7;
+ layer_z[ECO2_N] = half_thickness + epsilon_z * 8;
+ layer_z[EDGE_N] = 0;
+}
+
+
+static void export_vrml_line( int 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;
+ FlatFan 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( int layer, double startx, double starty, /*{{{*/
+ double endx, double endy, double width, int divisions )
+{
+ double hole, rayon;
+ FlatRing ring;
+
+ rayon = hypot( startx - endx, starty - endy ) + ( width / 2);
+ hole = rayon - width;
+
+ for( double alpha = 0; alpha < M_PI * 2; alpha += M_PI * 2 / divisions )
+ {
+ ring.add_inner( startx + hole * cos( alpha ), starty + hole * sin( alpha ) );
+ ring.add_outer( startx + rayon * cos( alpha ), starty + rayon * sin( alpha ) );
+ }
+
+ ring.bag( layer );
+}
+
+
+static void export_vrml_slot( TriangleBag& triangles, /*{{{*/
+ int top_layer, int bottom_layer, double xc, double yc,
+ double dx, double dy, int orient, int divisions )
+{
+ double capx, capy; /* Cap center */
+ VLoop loop;
+
+ loop.z_top = layer_z[top_layer];
+ loop.z_bottom = layer_z[bottom_layer];
+ double angle = orient / 1800.0 * M_PI;
+ 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,
+ int divisions )
+{
+ VLoop loop;
+
+ loop.z_top = layer_z[top_layer];
+ loop.z_bottom = layer_z[bottom_layer];
+ for( double alpha = 0; alpha < M_PI * 2; alpha += M_PI * 2 / divisions )
+ loop.add( xc + cos( alpha ) * hole, yc + sin( alpha ) * hole );
+
+ loop.bag( triangles );
+}
+
+
+static void export_vrml_varc( TriangleBag& triangles, /*{{{*/
+ int top_layer, int bottom_layer, double startx, double starty,
+ double endx, double endy, int divisions )
+{
+ VLoop loop;
+
+ loop.z_top = layer_z[top_layer];
+ loop.z_bottom = layer_z[bottom_layer];
+ double angle = atan2( endx - startx, endy - starty );
+ double rayon = hypot( startx - endx, starty - endy );
+ for( double alpha = angle; alpha < angle + PI2; alpha += PI2 / divisions )
+ {
+ loop.add( startx + cos( alpha ) * rayon, starty + sin( alpha ) * rayon );
+ }
+
+ loop.bag( triangles );
+}
+
+
+static void export_vrml_oval_pad( int layer, /*{{{*/
+ double xc, double yc,
+ double dx, double dy, int orient, int divisions )
+{
+ double capx, capy; /* Cap center */
+ FlatFan fan;
+
+ fan.c.x = xc;
+ fan.c.y = yc;
+ double angle = orient / 1800.0 * M_PI;
+ if( dy > dx )
+ {
+ EXCHG( dx, dy );
+ angle += PI2;
+ }
+
+ /* The exchange above means that cutter radius is alvays dy/2 */
+ double r = dy / 2;
+ double alpha;
+ /* The first side cap */
+ capx = xc + cos( angle ) * dx / 2;
+ capy = yc + sin( angle ) * dx / 2;
+ for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
+ fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
+
+ alpha = angle + PI2;
+ fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
+ /* The other side cap */
+ capx = xc - cos( angle ) * dx / 2;
+ capy = yc - sin( angle ) * dx / 2;
+ for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
+ fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
+
+ alpha = angle + 3 * PI2;
+ fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
+ fan.bag( layer );
+}
+
+
+static void export_vrml_arc( int layer, double startx, double starty, /*{{{*/
+ double endx, double endy, double width, int divisions )
+{
+ FlatRing ring;
+ double hole, rayon;
+ double angle = atan2( endx - startx, endy - starty );
+
+ rayon = hypot( startx - endx, starty - endy ) + ( width / 2);
+ hole = rayon - width;
+
+ for( double alpha = angle; alpha < angle + PI2; alpha += PI2 / divisions )
+ {
+ ring.add_inner( startx + cos( alpha ) * hole, starty + sin( alpha ) * hole );
+ ring.add_outer( startx + cos( alpha ) * rayon, starty + sin( alpha ) * rayon );
+ }
+
+ ring.bag( layer, false );
+}
+
+
+static void export_vrml_drawsegment( DRAWSEGMENT* drawseg ) /*{{{*/
+{
+ int layer = drawseg->GetLayer();
+ double w = drawseg->m_Width;
+ double x = drawseg->m_Start.x;
+ double y = drawseg->m_Start.y;
+ double xf = drawseg->m_End.x;
+ double yf = drawseg->m_End.y;
+
+ /* Items on the edge layer are high, not thick */
+ if( layer == EDGE_N )
+ {
+ switch( drawseg->m_Shape )
+ {
+ /* 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, 4 );
+ 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,
+ hypot( xf - x, yf - y ) / 2, 12 );
+ 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->m_Shape )
+ {
+ case S_ARC:
+ export_vrml_arc( layer, x, y, xf, yf, w, 3 );
+ break;
+
+ case S_CIRCLE:
+ export_vrml_circle( layer, x, y, xf, yf, w, 12 );
+ break;
+
+ default:
+ export_vrml_line( layer, x, y, xf, yf, w, 1 );
+ break;
+ }
+ }
+}
+
+
+/* C++ doesn't have closures and neither continuation forms... this is
+ * for coupling the vrml_text_callback with the common parameters */
+
+static int s_text_layer;
+static int s_text_width;
+static void vrml_text_callback( int x0, int y0, int xf, int yf )
+{
+ export_vrml_line( s_text_layer, x0, y0, xf, yf, s_text_width, 1 );
+}
+
+
+static void export_vrml_pcbtext( TEXTE_PCB* text ) /*{{{*/
+/*************************************************************/
+{
+ /* Coupling by globals! Ewwww... */
+ s_text_layer = text->GetLayer();
+ s_text_width = text->m_Width;
+ wxSize size = text->m_Size;
+ if( text->m_Mirror )
+ NEGATE( size.x );
+ if( text->m_MultilineAllowed )
+ {
+ wxPoint pos = text->m_Pos;
+ wxArrayString* list = wxStringSplit( text->m_Text, '\n' );
+ wxPoint offset;
+
+ offset.y = text->GetInterline();
+
+ RotatePoint( &offset, text->m_Orient );
+ for( unsigned i = 0; iCount(); i++ )
+ {
+ wxString txt = list->Item( i );
+ DrawGraphicText( NULL, NULL, pos, (EDA_Colors) 0,
+ txt, text->m_Orient, size,
+ text->m_HJustify, text->m_VJustify,
+ text->m_Width, text->m_Italic,
+ true,
+ vrml_text_callback );
+ pos += offset;
+ }
+
+ delete (list);
+ }
+ else
+ {
+ DrawGraphicText( NULL, NULL, text->m_Pos, (EDA_Colors) 0,
+ text->m_Text, text->m_Orient, size,
+ text->m_HJustify, text->m_VJustify,
+ text->m_Width, text->m_Italic,
+ true,
+ vrml_text_callback );
+ }
+}
+
+
+static void export_vrml_drawings( BOARD* pcb ) /*{{{*/
+{
+ /* draw graphic items */
+ for( EDA_BaseStruct* drawing = pcb->m_Drawings;
+ drawing != 0;
+ drawing = drawing->Next() )
+ {
+ switch( drawing->Type() )
+ {
+ case TYPE_DRAWSEGMENT:
+ export_vrml_drawsegment( (DRAWSEGMENT*) drawing );
+ break;
+
+ case TYPE_TEXTE:
+ export_vrml_pcbtext( (TEXTE_PCB*) drawing );
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+static void export_round_padstack( BOARD* pcb, double x, double y, double r, /*{{{*/
+ int bottom_layer, int top_layer, int divisions )
+{
+ int copper_layers = pcb->GetCopperLayerCount( );
+
+ for( int layer = bottom_layer; layer < copper_layers; layer++ )
+ {
+ /* The last layer is always the component one, unless it's single face */
+ if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
+ layer = LAST_COPPER_LAYER;
+ if( layer <= top_layer )
+ export_vrml_circle( layer, x, y, x + r / 2, y, r, divisions );
+ }
+}
+
+
+static void export_vrml_via( BOARD* pcb, SEGVIA* via ) /*{{{*/
+{
+ double x, y, r, hole;
+ int top_layer, bottom_layer;
+
+ r = via->m_Width / 2;
+ hole = via->GetDrillValue() / 2;
+ x = via->m_Start.x;
+ y = via->m_Start.y;
+ via->ReturnLayerPair( &top_layer, &bottom_layer );
+
+ /* Export the via padstack */
+ export_round_padstack( pcb, x, y, r, bottom_layer, top_layer, 8 );
+
+ /* Drill a rough hole */
+ export_vrml_hole( via_triangles[via->m_Shape], top_layer, bottom_layer, x, y, hole, 8 );
+}
+
+
+static void export_vrml_tracks( BOARD* pcb ) /*{{{*/
+{
+ for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() )
+ {
+ if( track->Type() == TYPE_VIA )
+ export_vrml_via( pcb, (SEGVIA*) track );
+ else
+ export_vrml_line( track->GetLayer(), track->m_Start.x, track->m_Start.y,
+ track->m_End.x, track->m_End.y, track->m_Width, 4 );
+ }
+}
+
+
+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() == TYPE_ZONE )
+ export_vrml_line( segzone->GetLayer(), segzone->m_Start.x, segzone->m_Start.y,
+ segzone->m_End.x, segzone->m_End.y, segzone->m_Width, 1 );
+ }
+
+ /* Export zone outlines */
+ for( int i = 0; i < pcb->GetAreaCount(); i++ )
+ {
+ ZONE_CONTAINER* zone = pcb->GetArea( i );
+ if( ( zone->m_FilledPolysList.size() == 0 )
+ ||( zone->m_ZoneMinThickness <= 1 ) )
+ continue;
+ int width = zone->m_ZoneMinThickness;
+ if( width > 0 )
+ {
+ int imax = zone->m_FilledPolysList.size() - 1;
+ int layer = zone->GetLayer();
+ CPolyPt* firstcorner = &zone->m_FilledPolysList[0];
+ CPolyPt* begincorner = firstcorner;
+ /* I'm not really positive about what he's doing here... */
+ for( int ic = 1; ic <= imax; ic++ )
+ {
+ CPolyPt* endcorner = &zone->m_FilledPolysList[ic];
+ if( begincorner->utility == 0 ) // Draw only basic outlines, not extra segments
+ export_vrml_line( layer, begincorner->x, begincorner->y,
+ endcorner->x, endcorner->y, width, 1 );
+ if( (endcorner->end_contour) || (ic == imax) ) // the last corner of a filled area is found: draw it
+ {
+ if( endcorner->utility == 0 ) // Draw only basic outlines, not extra segments
+ export_vrml_line( layer, endcorner->x, endcorner->y,
+ firstcorner->x, firstcorner->y, width, 1 );
+ ic++;
+ /* A new contour? */
+ if( ic < imax - 1 )
+ begincorner = firstcorner = &zone->m_FilledPolysList[ic];
+ }
+ else
+ begincorner = endcorner;
+ }
+ }
+ }
+}
+
+
+static void export_vrml_text_module( TEXTE_MODULE* module ) /*{{{*/
+{
+ if( !module->m_NoShow )
+ {
+ wxSize size = module->m_Size;
+
+ if( module->m_Mirror )
+ NEGATE( size.x ); // Text is mirrored
+
+ s_text_layer = module->GetLayer();
+ s_text_width = module->m_Width;
+ DrawGraphicText( NULL, NULL, module->m_Pos, (EDA_Colors) 0,
+ module->m_Text, module->GetDrawRotation(), size,
+ module->m_HJustify, module->m_VJustify,
+ module->m_Width, module->m_Italic,
+ true,
+ vrml_text_callback );
+ }
+}
+
+
+static void export_vrml_edge_module( EDGE_MODULE* module ) /*{{{*/
+{
+ int layer = module->GetLayer();
+ double x = module->m_Start.x;
+ double y = module->m_Start.y;
+ double xf = module->m_End.x;
+ double yf = module->m_End.y;
+ double w = module->m_Width;
+
+ switch( module->m_Shape )
+ {
+ case S_ARC:
+ export_vrml_arc( layer, x, y, xf, yf, w, 3 );
+ break;
+
+ case S_CIRCLE:
+ export_vrml_circle( layer, x, y, xf, yf, w, 12 );
+ break;
+
+ default:
+ export_vrml_line( layer, x, y, xf, yf, w, 1 );
+ break;
+ }
+}
+
+
+static void export_vrml_pad( BOARD* pcb, D_PAD* pad ) /*{{{*/
+{
+ double hole_drill_w = (double) pad->m_Drill.x / 2;
+ double hole_drill_h = (double) pad->m_Drill.y / 2;
+ double hole_drill = MIN( hole_drill_w, hole_drill_h );
+ double hole_x = pad->m_Pos.x;
+ double hole_y = pad->m_Pos.y;
+
+ /* Export the hole on the edge layer */
+ if( hole_drill > 0 )
+ {
+ if( pad->m_DrillShape == 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, pad->m_Orient, 6 );
+ }
+ else
+ {
+ // Drill a round hole
+ export_vrml_hole( layer_triangles[EDGE_N],
+ FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
+ hole_x, hole_y, hole_drill, 12 );
+ }
+ }
+
+ /* The pad proper, on the selected layers */
+ unsigned long layer_mask = pad->m_Masque_Layer;
+ int copper_layers = pcb->GetCopperLayerCount( );
+ /* The (maybe offseted) pad position */
+ wxPoint pad_pos = pad->ReturnShapePos();
+ double pad_x = pad_pos.x;
+ double pad_y = pad_pos.y;
+ wxSize pad_delta = pad->m_DeltaSize;
+ double pad_dx = pad_delta.x / 2;
+ double pad_dy = pad_delta.y / 2;
+ double pad_w = pad->m_Size.x / 2;
+ double pad_h = pad->m_Size.y / 2;
+ for( int layer = FIRST_COPPER_LAYER; layer < copper_layers; layer++ )
+ {
+ /* The last layer is always the component one, unless it's single face */
+ if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
+ layer = LAST_COPPER_LAYER;
+
+ if( layer_mask & (1 << layer) )
+ {
+ /* OK, the pad is on this layer, export it */
+ switch( pad->m_PadShape & 0x7F ) /* What is the masking for? */
+ {
+ case PAD_CIRCLE:
+ export_vrml_circle( layer, pad_x, pad_y,
+ pad_x + pad_w / 2, pad_y, pad_w, 12 );
+ break;
+
+ case PAD_OVAL:
+ export_vrml_oval_pad( layer,
+ pad_x, pad_y,
+ pad_w * 2, pad_h * 2, pad->m_Orient, 4 );
+ break;
+
+ case PAD_RECT:
+ /* Just to be sure :D */
+ pad_dx = 0;
+ pad_dy = 0;
+
+ case PAD_TRAPEZOID:
+ {
+ int coord[8] =
+ {
+ -pad_w - pad_dy, +pad_h + pad_dx,
+ -pad_w + pad_dy, -pad_h - pad_dx,
+ +pad_w - pad_dy, +pad_h - pad_dx,
+ +pad_w + pad_dy, -pad_h + pad_dx,
+ };
+
+ for( int i = 0; i < 4; i++ )
+ {
+ RotatePoint( &coord[i * 2], &coord[i * 2 + 1], pad->m_Orient );
+ coord[i * 2] += pad_x;
+ coord[i * 2 + 1] += pad_y;
+ }
+
+ bag_flat_quad( layer, coord[0], coord[1],
+ coord[2], coord[3],
+ coord[4], coord[5],
+ coord[6], coord[7] );
+ }
+ break;
+ }
+ }
+ }
+}
+
+
+/* From axis/rot to quaternion */
+static void build_quat( double x, double y, double z, double a, double q[4] )
+{
+ double sina = sin( a / 2 );
+
+ q[0] = x * sina;
+ q[1] = y * sina;
+ q[2] = z * sina;
+ q[3] = cos( a / 2 );
+}
+
+
+/* From quaternion to axis/rot */
+static void from_quat( double q[4], double rot[4] )
+{
+ rot[3] = acos( q[3] ) * 2;
+ for( int i = 0; i < 3; i++ )
+ {
+ rot[i] = q[i] / sin( rot[3] / 2 );
+ }
+}
+
+
+/* Quaternion composition */
+static void compose_quat( double q1[4], double q2[4], double qr[4] )
+{
+ double tmp[4];
+
+ tmp[0] = q2[3] *q1[0] + q2[0] *q1[3] + q2[1] *q1[2] - q2[2] *q1[1];
+ tmp[1] = q2[3] *q1[1] + q2[1] *q1[3] + q2[2] *q1[0] - q2[0] *q1[2];
+ tmp[2] = q2[3] *q1[2] + q2[2] *q1[3] + q2[0] *q1[1] - q2[1] *q1[0];
+ tmp[3] = q2[3] *q1[3] - q2[0] *q1[0] - q2[1] *q1[1] - q2[2] *q1[2];
+ qr[0] = tmp[0]; qr[1] = tmp[1];
+ qr[2] = tmp[2]; qr[3] = tmp[3];
+}
+
+
+static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
+ FILE* aOutputFile, double aScalingFactor,
+ bool aExport3DFiles, const wxString & a3D_Subdir )
+{
+ /* Reference and value */
+ export_vrml_text_module( aModule->m_Reference );
+ export_vrml_text_module( aModule->m_Value );
+ /* Export module edges */
+ for( EDA_BaseStruct* item = aModule->m_Drawings;
+ item != NULL;
+ item = item->Next() )
+ {
+ switch( item->Type() )
+ {
+ case TYPE_TEXTE_MODULE:
+ export_vrml_text_module( dynamic_cast(item) );
+ break;
+
+ case TYPE_EDGE_MODULE:
+ export_vrml_edge_module( dynamic_cast(item) );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Export pads */
+ for( D_PAD* pad = aModule->m_Pads;
+ pad != 0;
+ pad = pad->Next() )
+ export_vrml_pad( aPcb, pad );
+
+ bool isFlipped = aModule->GetLayer() == LAYER_N_BACK;
+ /* Export the object VRML model(s) */
+ for( S3D_MASTER* vrmlm = aModule->m_3D_Drawings;
+ vrmlm != 0; vrmlm = vrmlm->Next() )
+ {
+ wxString fname = vrmlm->m_Shape3DName;
+ if( fname.IsEmpty() )
+ continue;
+
+ if( ! wxFileName::FileExists( fname ) )
+ {
+ wxFileName fn = fname;
+ fname = wxGetApp().FindLibraryPath( fn );
+ if( fname.IsEmpty() ) // keep "short" name if full filemane not found
+ fname = vrmlm->m_Shape3DName;
+ }
+
+ fname.Replace(wxT("\\"), wxT("/" ) );
+ wxString source_fname = fname;
+ if( aExport3DFiles )
+ {
+ fname.Replace(wxT("/"), wxT("_" ) );
+ fname.Replace(wxT(":_"), wxT("_" ) );
+ fname = a3D_Subdir + wxT("/") + fname;
+ if( !wxFileExists( fname ) )
+ wxCopyFile( source_fname, fname );
+ }
+
+ /* Calculate 3D shape rotation:
+ * this is the rotation parameters, with an additional 180 deg rotation
+ * for footprints that are flipped
+ * When flipped, axis rotation is the horizontal axis (X axis)
+ */
+ int rotx = vrmlm->m_MatRotation.x;
+ if ( isFlipped )
+ rotx += 1800;
+
+ /* Do some quaternion munching */
+ double q1[4], q2[4], rot[4];
+ build_quat( 1, 0, 0, rotx / 1800.0 * M_PI, q1 );
+ build_quat( 0, 1, 0, vrmlm->m_MatRotation.y / 1800.0 * M_PI, q2 );
+ compose_quat( q1, q2, q1 );
+ build_quat( 0, 0, 1, vrmlm->m_MatRotation.z / 1800.0 * M_PI, q2 );
+ compose_quat( q1, q2, q1 );
+ build_quat( 0, 0, 1, aModule->m_Orient / 1800.0 * M_PI, q2 );
+ compose_quat( q1, q2, q1 );
+ from_quat( q1, rot );
+
+ fprintf( aOutputFile, "Transform {\n" );
+ /* A null rotation would fail the acos! */
+ if( rot[3] != 0.0 )
+ {
+ fprintf( aOutputFile, " rotation %g %g %g %g\n",
+ rot[0], rot[1], rot[2], rot[3] );
+ }
+ fprintf( aOutputFile, " scale %g %g %g\n",
+ vrmlm->m_MatScale.x * aScalingFactor,
+ vrmlm->m_MatScale.y * aScalingFactor,
+ vrmlm->m_MatScale.z * aScalingFactor );
+ fprintf( aOutputFile, " translation %g %g %g\n",
+ vrmlm->m_MatPosition.x + aModule->m_Pos.x,
+ -vrmlm->m_MatPosition.y - aModule->m_Pos.y,
+ vrmlm->m_MatPosition.z + layer_z[aModule->GetLayer()] );
+ fprintf( aOutputFile,
+ " children [\n Inline {\n url [ \"%s\" ]\n } ]\n",
+ CONV_TO_UTF8( fname ) );
+ fprintf( aOutputFile, " }\n" );
+
+ }
+}
+
+
+static void write_and_empty_triangle_bag( FILE* output_file,
+ TriangleBag& triangles, int color )
+{
+ if( !triangles.empty() )
+ {
+ write_triangle_bag( output_file, color, triangles );
+ triangles.clear( );
+ }
+}
+
+
+/* the dialog to create VRML files, derived from DIALOG_EXPORT_3DFILE_BASE,
+ * created by wxFormBuilder
+ */
+#include "dialog_export_3Dfiles_base.h" // the wxFormBuilder header file
+
+class DIALOG_EXPORT_3DFILE : public DIALOG_EXPORT_3DFILE_BASE
+{
+ private:
+ static int m_UnitsOpt; // to remember last option
+ static int m_3DFilesOpt; // to remember last option
+ virtual void OnCancelClick( wxCommandEvent& event ){ EndModal( wxID_CANCEL ); }
+ virtual void OnOkClick( wxCommandEvent& event ){ EndModal( wxID_OK ); }
+
+ public:
+ DIALOG_EXPORT_3DFILE( wxWindow* parent ) :
+ DIALOG_EXPORT_3DFILE_BASE( parent )
+ {
+ SetFocus();
+ m_rbSelectUnits->SetSelection(m_UnitsOpt);
+ m_rb3DFilesOption->SetSelection(m_3DFilesOpt);
+ GetSizer()->SetSizeHints( this );
+ Centre();
+ }
+ ~DIALOG_EXPORT_3DFILE()
+ {
+ m_UnitsOpt = GetUnits( );
+ m_3DFilesOpt = Get3DFilesOption( );
+ };
+
+ void SetSubdir( const wxString & aDir )
+ {
+ m_SubdirNameCtrl->SetValue( aDir);
+ }
+
+ wxString GetSubdir( )
+ {
+ return m_SubdirNameCtrl->GetValue( );
+ }
+
+ wxFilePickerCtrl* FilePicker()
+ {
+ return m_filePicker;
+ }
+
+ int GetUnits( )
+ {
+ return m_UnitsOpt = m_rbSelectUnits->GetSelection();
+ }
+
+ int Get3DFilesOption( )
+ {
+ return m_3DFilesOpt = m_rb3DFilesOption->GetSelection();
+ }
+};
+int DIALOG_EXPORT_3DFILE::m_UnitsOpt = 0; // to remember last option
+int DIALOG_EXPORT_3DFILE::m_3DFilesOpt = 1; // to remember last option
+
+/**
+ * Function OnExportVRML
+ * will export the current BOARD to a VRML file.
+ */
+void WinEDA_PcbFrame::OnExportVRML( wxCommandEvent& event )
+{
+ wxFileName fn;
+ static wxString subDirFor3Dshapes = wxT("shapes3D");
+ double scaleList[3] = { 1.0, 25.4, 25.4/1000 };
+
+ // Build default file name
+ wxString ext = wxT( "wrl" );
+ fn = GetScreen()->m_FileName;
+ fn.SetExt( ext );
+
+ DIALOG_EXPORT_3DFILE dlg( this );
+ dlg.FilePicker()->SetPath( fn.GetFullPath() );
+ dlg.SetSubdir( subDirFor3Dshapes );
+ if( dlg.ShowModal() != wxID_OK )
+ return;
+
+ double scale = scaleList[dlg.GetUnits( )]; // final scale export
+ bool export3DFiles = dlg.Get3DFilesOption( ) == 0;
+
+wxBusyCursor dummy;
+
+ wxString fullFilename = dlg.FilePicker()->GetPath();
+ subDirFor3Dshapes = dlg.GetSubdir();
+ if( ! wxDirExists( subDirFor3Dshapes ) )
+ wxMkdir( subDirFor3Dshapes );
+ if( ! ExportVRML_File( fullFilename, scale, export3DFiles, subDirFor3Dshapes ) )
+ {
+ wxString msg = _( "Unable to create " ) + fullFilename;
+ DisplayError( this, msg );
+ return;
+ }
+}
+
+/**
+ * Function ExportVRML_File
+ * Creates the file(s) exporting current BOARD to a VRML file.
+ * @param aFullFileName = the full filename of the file to create
+ * @param aScale = the general scaling factor. 1.0 to export in inch
+ * @param aExport3DFiles = true to copy 3D shapes in the subdir a3D_Subdir
+ * @param a3D_Subdir = sub directory where 3D shapes files are copied
+ * used only when aExport3DFiles == true
+ * @return true if Ok.
+ */
+/* When copying 3D shapes files, the new filename is build from
+ * the full path name, changing the separators by underscore.
+ * this is needed because files with the same shortname can exist in different directories
+ */
+bool WinEDA_PcbFrame::ExportVRML_File( const wxString & aFullFileName,
+ double aScale, bool aExport3DFiles,
+ const wxString & a3D_Subdir )
+{
+ wxString msg;
+ FILE* output_file;
+ BOARD* pcb = GetBoard();
+
+ output_file = wxFopen( aFullFileName, wxT( "wt" ) );
+ if( output_file == NULL )
+ return false;
+
+ // Switch the locale to standard C (needed to print floating point numbers like 1.3)
+ SetLocaleTo_C_standard();
+
+ /* Begin with the usual VRML boilerplate */
+ fprintf( output_file, "#VRML V2.0 utf8\n"
+ "WorldInfo {\n"
+ " title \"%s - Generated by PCBNEW\"\n"
+ "}\n", CONV_TO_UTF8( aFullFileName ) );
+
+ /* The would be in decimils 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...
+ */
+
+ /* scaling factor to convert internal units (decimils) to inches
+ */
+ double board_scaling_factor = 0.0001;
+ /* auxiliary scale to export to a different scale.
+ */
+ double general_scaling_factor = board_scaling_factor * aScale;
+ fprintf(output_file, "Transform {\n");
+ fprintf(output_file, " scale %g %g %g\n",
+ general_scaling_factor, general_scaling_factor, general_scaling_factor );
+
+ /* Define the translation to have the board centre to the 2D axis origin
+ * more easy for rotations...
+ */
+ pcb->ComputeBoundaryBox();
+ double dx = board_scaling_factor * pcb->m_BoundaryBox.Centre().x;
+ double dy = board_scaling_factor * pcb->m_BoundaryBox.Centre().y;
+ fprintf(output_file, " translation %g %g 0.0\n", -dx, dy );
+
+ fprintf(output_file, " children [\n" );
+
+ /* scaling factor to convert 3D models to board units (decimils)
+ * Usually we use Wings3D to create thems.
+ * One can consider the 3D units is 0.1 inch
+ * So the scaling factor from 0.1 inch to board units
+ * is 0.1 / board_scaling_factor
+ */
+ double wrml_3D_models_scaling_factor = 0.1 / board_scaling_factor;
+ /* Preliminary computation: the z value for each layer */
+ compute_layer_Zs( pcb );
+
+ /* Drawing and text on the board, and edges which are special */
+ export_vrml_drawings( pcb );
+
+ /* Export vias and trackage */
+ export_vrml_tracks( pcb );
+
+ /* Export zone fills */
+/* TODO export_vrml_zones(pcb);
+*/
+
+ /* Export footprints */
+ for( MODULE* module = pcb->m_Modules;
+ module != 0;
+ module = module->Next() )
+ export_vrml_module( pcb, module, output_file,
+ wrml_3D_models_scaling_factor,
+ aExport3DFiles, a3D_Subdir );
+
+ /* Output the bagged triangles for each layer
+ * Each layer will be a separate shape */
+ for( int layer = 0; layer < LAYER_COUNT; layer++ )
+ write_and_empty_triangle_bag( output_file,
+ layer_triangles[layer],
+ pcb->GetLayerColor(layer) );
+
+ /* 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 ) );
+
+ /* Close the outer 'transform' node */
+ fputs( "]\n}\n", output_file );
+
+ // End of work
+ fclose( output_file );
+ SetLocaleTo_Default(); // revert to the current locale
+
+ return true;
+}
diff --git a/pcbnew/menubar_pcbframe.cpp b/pcbnew/menubar_pcbframe.cpp
index b0ab9ad9bb..7ca1f1f82f 100644
--- a/pcbnew/menubar_pcbframe.cpp
+++ b/pcbnew/menubar_pcbframe.cpp
@@ -178,6 +178,13 @@ void WinEDA_PcbFrame::ReCreateMenuBar()
_( "Create a report of all modules on the current board" ) );
item->SetBitmap( tools_xpm );
submenuexport->Append( item );
+
+ item = new wxMenuItem( submenuexport, ID_GEN_EXPORT_FILE_VRML,
+ _( "&VRML" ),
+ _( "Export a VRML board representation" ) );
+ item->SetBitmap( show_3d_xpm );
+ submenuexport->Append( item );
+
ADD_MENUITEM_WITH_HELP_AND_SUBMENU( filesMenu, submenuexport,
ID_GEN_EXPORT_FILE, _( "&Export" ),
_( "Export board" ), export_xpm );
diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp
index 496c731c7b..efbf9c80ef 100644
--- a/pcbnew/pcbframe.cpp
+++ b/pcbnew/pcbframe.cpp
@@ -94,6 +94,7 @@ BEGIN_EVENT_TABLE( WinEDA_PcbFrame, WinEDA_BasePcbFrame )
EVT_MENU( ID_GEN_EXPORT_FILE_GENCADFORMAT, WinEDA_PcbFrame::ExportToGenCAD )
EVT_MENU( ID_GEN_EXPORT_FILE_MODULE_REPORT,
WinEDA_PcbFrame::GenModuleReport )
+ EVT_MENU( ID_GEN_EXPORT_FILE_VRML, WinEDA_PcbFrame::OnExportVRML )
EVT_MENU( ID_GEN_IMPORT_SPECCTRA_SESSION,
WinEDA_PcbFrame::ImportSpecctraSession )
diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h
index 7b6bfab5ec..70bc11c302 100644
--- a/pcbnew/pcbnew_id.h
+++ b/pcbnew/pcbnew_id.h
@@ -210,6 +210,7 @@ enum pcbnew_ids
ID_MENU_LIST_NETS,
ID_MENU_PCB_CLEAN,
ID_MENU_PCB_SWAP_LAYERS,
+ ID_GEN_EXPORT_FILE_VRML,
ID_TOOLBARH_PCB_AUTOPLACE,
ID_TOOLBARH_PCB_AUTOROUTE,