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 @@ + + + + + + C++ + 1 + UTF-8 + connect + dialog_export_3Dfiles_base + 1000 + none + 1 + dialog_export_3Dfiles + + . + + 1 + 1 + 0 + + + + + 1 + + + + 0 + wxID_ANY + + + DIALOG_EXPORT_3DFILE_BASE + + 370,252 + wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -1,-1 + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND + 0 + + 450,-1 + bUpperSizer + wxVERTICAL + none + + 5 + wxTOP|wxRIGHT|wxLEFT + 0 + + + + 1 + + + 0 + wxID_ANY + Wrml main file filename: + + + m_staticText1 + protected + + + + + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND + 0 + + + + 1 + + + 0 + wxID_ANY + + Save VRML Board File + -1,-1 + m_filePicker + protected + + + wxFLP_DEFAULT_STYLE|wxFLP_SAVE + + + + *.wrl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxTOP|wxRIGHT|wxLEFT + 0 + + + + 1 + + + 0 + wxID_ANY + Wrml 3D footprints shapes subdir: + + + m_staticText3 + protected + + + + + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + 0 + + + + 1 + + + 0 + wxID_ANY + + 0 + + m_SubdirNameCtrl + protected + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 1 + + + bLowerSizer + wxHORIZONTAL + none + + 5 + wxALL|wxEXPAND + 1 + + + "Inch" "mm" "Meter" + + 1 + + + 0 + wxID_ANY + Units: + 1 + + + m_rbSelectUnits + protected + + 0 + + wxRA_SPECIFY_COLS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + + "Copy 3D Shapes Files in Subdir" "Use Absolute Path in Wrml File " + + 1 + + + 0 + wxID_ANY + 3D Shapes Files Option: + 1 + + + m_rb3DFilesOption + protected + + 0 + + wxRA_SPECIFY_COLS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxRIGHT|wxLEFT + 0 + + + + 1 + + + 0 + wxID_ANY + + + m_staticline1 + protected + + + wxLI_HORIZONTAL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND|wxALL + 0 + + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + + m_sdbSizer1 + protected + + OnCancelClick + + + + OnOkClick + + + + + + + + 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,