summaryrefslogtreecommitdiff
path: root/pcbnew/exporters/gen_modules_placefile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pcbnew/exporters/gen_modules_placefile.cpp')
-rw-r--r--pcbnew/exporters/gen_modules_placefile.cpp690
1 files changed, 690 insertions, 0 deletions
diff --git a/pcbnew/exporters/gen_modules_placefile.cpp b/pcbnew/exporters/gen_modules_placefile.cpp
new file mode 100644
index 0000000..202ddb1
--- /dev/null
+++ b/pcbnew/exporters/gen_modules_placefile.cpp
@@ -0,0 +1,690 @@
+/**
+ * @file gen_modules_placefile.cpp
+ */
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * 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
+ */
+
+/*
+ * 1 - create ascii files for automatic placement of smd components
+ * 2 - create a module report (pos and module descr) (ascii file)
+ */
+
+#include <fctsys.h>
+#include <confirm.h>
+#include <kicad_string.h>
+#include <gestfich.h>
+#include <wxPcbStruct.h>
+#include <pgm_base.h>
+#include <build_version.h>
+#include <macros.h>
+#include <reporter.h>
+
+#include <class_board.h>
+#include <class_module.h>
+
+#include <pcbnew.h>
+#include <wildcards_and_files_ext.h>
+#include <kiface_i.h>
+#include <wx_html_report_panel.h>
+
+
+#include <dialog_gen_module_position_file_base.h>
+/*
+ * The format of the kicad place file is:
+ * ### Module positions - created on 04/12/2012 15:24:24 ###
+ * ### Printed by Pcbnew version pcbnew (2012-11-30 BZR 3828)-testing
+ * ## Unit = inches, Angle = deg.
+ * or
+ * ## Unit = mm, Angle = deg.
+ * ## Side : top
+ * or
+ * ## Side : bottom
+ * or
+ * ## Side : all
+ * # Ref Val Package PosX PosY Rot Side
+ * C123 0,1uF/50V SM0603 1.6024 -2.6280 180.0 Front
+ * C124 0,1uF/50V SM0603 1.6063 -2.7579 180.0 Front
+ * C125 0,1uF/50V SM0603 1.6010 -2.8310 180.0 Front
+ * ## End
+ */
+
+#define PLACEFILE_UNITS_KEY wxT( "PlaceFileUnits" )
+#define PLACEFILE_OPT_KEY wxT( "PlaceFileOpts" )
+
+
+#define PCB_BACK_SIDE 0
+#define PCB_FRONT_SIDE 1
+#define PCB_BOTH_SIDES 2
+
+class LIST_MOD // An helper class used to build a list of useful footprints.
+{
+public:
+ MODULE* m_Module; // Link to the actual footprint
+ wxString m_Reference; // Its schematic reference
+ wxString m_Value; // Its schematic value
+ LAYER_NUM m_Layer; // its side (B_Cu, or F_Cu)
+};
+
+
+/**
+ * The dialog to create footprint position files,
+ * and choose options (one or 2 files, units and force all SMD footprints in list)
+ */
+class DIALOG_GEN_MODULE_POSITION : public DIALOG_GEN_MODULE_POSITION_BASE
+{
+public:
+ DIALOG_GEN_MODULE_POSITION( PCB_EDIT_FRAME * aParent ):
+ DIALOG_GEN_MODULE_POSITION_BASE( aParent ),
+ m_parent( aParent ),
+ m_plotOpts( aParent->GetPlotSettings() )
+ {
+ m_reporter = &m_messagesPanel->Reporter();
+ initDialog();
+
+ GetSizer()->SetSizeHints(this);
+ Centre();
+ }
+
+private:
+ PCB_EDIT_FRAME* m_parent;
+ PCB_PLOT_PARAMS m_plotOpts;
+ wxConfigBase* m_config;
+ REPORTER* m_reporter;
+
+ static int m_unitsOpt;
+ static int m_fileOpt;
+
+ void initDialog();
+ void OnOutputDirectoryBrowseClicked( wxCommandEvent& event );
+ void OnOKButton( wxCommandEvent& event );
+
+ bool CreateFiles();
+
+ // accessors to options:
+ wxString GetOutputDirectory()
+ {
+ return m_outputDirectoryName->GetValue();
+ }
+
+ bool UnitsMM()
+ {
+ return m_radioBoxUnits->GetSelection() == 1;
+ }
+
+ bool OneFileOnly()
+ {
+ return m_radioBoxFilesCount->GetSelection() == 1;
+ }
+
+ bool ForceAllSmd()
+ {
+ return m_radioBoxForceSmd->GetSelection() == 1;
+ }
+};
+
+
+// Static members to remember choices
+int DIALOG_GEN_MODULE_POSITION::m_unitsOpt = 0;
+int DIALOG_GEN_MODULE_POSITION::m_fileOpt = 0;
+
+// Use standard board side name. do not translate them,
+// they are keywords in place file
+const wxString frontSideName = wxT( "top" );
+const wxString backSideName = wxT( "bottom" );
+
+void DIALOG_GEN_MODULE_POSITION::initDialog()
+{
+ m_config = Kiface().KifaceSettings();
+ m_config->Read( PLACEFILE_UNITS_KEY, &m_unitsOpt, 1 );
+ m_config->Read( PLACEFILE_OPT_KEY, &m_fileOpt, 0 );
+
+ // Output directory
+ m_outputDirectoryName->SetValue( m_plotOpts.GetOutputDirectory() );
+ m_radioBoxUnits->SetSelection( m_unitsOpt );
+ m_radioBoxFilesCount->SetSelection( m_fileOpt );
+
+ m_sdbSizerButtonsOK->SetDefault();
+}
+
+void DIALOG_GEN_MODULE_POSITION::OnOutputDirectoryBrowseClicked( wxCommandEvent& event )
+{
+ // Build the absolute path of current output plot directory
+ // to preselect it when opening the dialog.
+ wxString path = Prj().AbsolutePath( m_outputDirectoryName->GetValue() );
+
+ wxDirDialog dirDialog( this, _( "Select Output Directory" ), path );
+
+ if( dirDialog.ShowModal() == wxID_CANCEL )
+ return;
+
+ wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
+
+ wxMessageDialog dialog( this, _( "Use a relative path? "),
+ _( "Plot Output Directory" ),
+ wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
+
+ if( dialog.ShowModal() == wxID_YES )
+ {
+ wxString boardFilePath = ( (wxFileName) m_parent->GetBoard()->GetFileName()).GetPath();
+
+ if( !dirName.MakeRelativeTo( boardFilePath ) )
+ wxMessageBox( _( "Cannot make path relative (target volume different from board file volume)!" ),
+ _( "Plot Output Directory" ), wxOK | wxICON_ERROR );
+ }
+
+ m_outputDirectoryName->SetValue( dirName.GetFullPath() );
+}
+
+void DIALOG_GEN_MODULE_POSITION::OnOKButton( wxCommandEvent& event )
+{
+ m_unitsOpt = m_radioBoxUnits->GetSelection();
+ m_fileOpt = m_radioBoxFilesCount->GetSelection();
+
+ m_config->Write( PLACEFILE_UNITS_KEY, m_unitsOpt );
+ m_config->Write( PLACEFILE_OPT_KEY, m_fileOpt );
+
+ // Set output directory and replace backslashes with forward ones
+ // (Keep unix convention in cfg files)
+ wxString dirStr;
+ dirStr = m_outputDirectoryName->GetValue();
+ dirStr.Replace( wxT( "\\" ), wxT( "/" ) );
+
+ m_plotOpts.SetOutputDirectory( dirStr );
+
+ m_parent->SetPlotSettings( m_plotOpts );
+
+ CreateFiles();
+}
+
+
+bool DIALOG_GEN_MODULE_POSITION::CreateFiles()
+{
+ BOARD * brd = m_parent->GetBoard();
+ wxFileName fn;
+ wxString msg;
+ bool singleFile = OneFileOnly();
+ int fullcount = 0;
+
+ // Count the footprints to place, do not yet create a file
+ int fpcount = m_parent->DoGenFootprintsPositionFile( wxEmptyString, UnitsMM(),
+ ForceAllSmd(), PCB_BOTH_SIDES );
+ if( fpcount == 0)
+ {
+ wxMessageBox( _( "No footprint for automated placement." ) );
+ return false;
+ }
+
+ // Create output directory if it does not exist (also transform it in
+ // absolute form). Bail if it fails
+ wxFileName outputDir = wxFileName::DirName( m_plotOpts.GetOutputDirectory() );
+ wxString boardFilename = m_parent->GetBoard()->GetFileName();
+
+ m_reporter = &m_messagesPanel->Reporter();
+
+ if( !EnsureFileDirectoryExists( &outputDir, boardFilename, m_reporter ) )
+ {
+ msg.Printf( _( "Could not write plot files to folder \"%s\"." ),
+ GetChars( outputDir.GetPath() ) );
+ DisplayError( this, msg );
+ return false;
+ }
+
+ fn = m_parent->GetBoard()->GetFileName();
+ fn.SetPath( outputDir.GetPath() );
+
+ // Create the the Front or Top side placement file,
+ // or the single file
+ int side = PCB_FRONT_SIDE;
+
+ if( singleFile )
+ {
+ side = PCB_BOTH_SIDES;
+ fn.SetName( fn.GetName() + wxT( "-" ) + wxT("all") );
+ }
+ else
+ fn.SetName( fn.GetName() + wxT( "-" ) + frontSideName );
+
+ fn.SetExt( FootprintPlaceFileExtension );
+
+ fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
+ ForceAllSmd(), side );
+ if( fpcount < 0 )
+ {
+ msg.Printf( _( "Unable to create '%s'." ), GetChars( fn.GetFullPath() ) );
+ wxMessageBox( msg );
+ m_reporter->Report( msg, REPORTER::RPT_ERROR );
+ return false;
+ }
+
+ if( singleFile )
+ msg.Printf( _( "Place file: '%s'." ), GetChars( fn.GetFullPath() ) );
+ else
+ msg.Printf( _( "Front side (top side) place file: '%s'." ),
+ GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ msg.Printf( _( "Component count: %d." ), fpcount );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ if( singleFile )
+ {
+ m_reporter->Report( _( "Component Placement File generation OK." ), REPORTER::RPT_ACTION );
+ return true;
+ }
+
+ // Create the Back or Bottom side placement file
+ fullcount = fpcount;
+ side = PCB_BACK_SIDE;
+ fn = brd->GetFileName();
+ fn.SetPath( outputDir.GetPath() );
+ fn.SetName( fn.GetName() + wxT( "-" ) + backSideName );
+ fn.SetExt( wxT( "pos" ) );
+
+ fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
+ ForceAllSmd(), side );
+
+ if( fpcount < 0 )
+ {
+ msg.Printf( _( "Unable to create file '%s'." ), GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_ERROR );
+ wxMessageBox( msg );
+ return false;
+ }
+
+ // Display results
+ if( !singleFile )
+ {
+ msg.Printf( _( "Back side (bottom side) place file: '%s'." ), GetChars( fn.GetFullPath() ) );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+
+ msg.Printf( _( "Component count: %d." ), fpcount );
+
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+ }
+
+ if( !singleFile )
+ {
+ fullcount += fpcount;
+ msg.Printf( _( "Full component count: %d\n" ), fullcount );
+ m_reporter->Report( msg, REPORTER::RPT_INFO );
+ }
+
+ m_reporter->Report( _( "Component Placement File generation OK." ), REPORTER::RPT_ACTION );
+
+ return true;
+}
+
+// Defined values to write coordinates using inches or mm:
+static const double conv_unit_inch = 0.001 / IU_PER_MILS ; // units = INCHES
+static const char unit_text_inch[] = "## Unit = inches, Angle = deg.\n";
+
+static const double conv_unit_mm = 1.0 / IU_PER_MM; // units = mm
+static const char unit_text_mm[] = "## Unit = mm, Angle = deg.\n";
+
+static wxPoint File_Place_Offset; // Offset coordinates for generated file.
+
+
+// Sort function use by GenereModulesPosition()
+// sort is made by side (layer) top layer first
+// then by reference increasing order
+static bool sortFPlist( const LIST_MOD& ref, const LIST_MOD& tst )
+{
+ if( ref.m_Layer == tst.m_Layer )
+ return StrNumCmp( ref.m_Reference, tst.m_Reference, 16 ) < 0;
+
+ return ref.m_Layer > tst.m_Layer;
+}
+
+
+/**
+ * Helper function HasNonSMDPins
+ * returns true if the given module has any non smd pins, such as through hole
+ * and therefore cannot be placed automatically.
+ */
+static bool HasNonSMDPins( MODULE* aModule )
+{
+ D_PAD* pad;
+
+ for( pad = aModule->Pads(); pad; pad = pad->Next() )
+ {
+ if( pad->GetAttribute() != PAD_ATTRIB_SMD )
+ return true;
+ }
+
+ return false;
+}
+
+void PCB_EDIT_FRAME::GenFootprintsPositionFile( wxCommandEvent& event )
+{
+ DIALOG_GEN_MODULE_POSITION dlg( this );
+ dlg.ShowModal();
+}
+
+/*
+ * Creates a footprint position file
+ * aSide = 0 -> Back (bottom) side)
+ * aSide = 1 -> Front (top) side)
+ * aSide = 2 -> both sides
+ * if aFullFileName is empty, the file is not created, only the
+ * count of footprints to place is returned
+ */
+int PCB_EDIT_FRAME::DoGenFootprintsPositionFile( const wxString& aFullFileName,
+ bool aUnitsMM,
+ bool aForceSmdItems, int aSide )
+{
+ MODULE* footprint;
+
+ // Minimal text lenghts:
+ int lenRefText = 8;
+ int lenValText = 8;
+ int lenPkgText = 16;
+
+ File_Place_Offset = GetAuxOrigin();
+
+ // Calculating the number of useful footprints (CMS attribute, not VIRTUAL)
+ int footprintCount = 0;
+
+ // Select units:
+ double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
+ const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
+
+ // Build and sort the list of footprints alphabetically
+ std::vector<LIST_MOD> list;
+ list.reserve( footprintCount );
+
+ for( footprint = GetBoard()->m_Modules; footprint; footprint = footprint->Next() )
+ {
+ if( aSide != PCB_BOTH_SIDES )
+ {
+ if( footprint->GetLayer() == B_Cu && aSide == PCB_FRONT_SIDE)
+ continue;
+ if( footprint->GetLayer() == F_Cu && aSide == PCB_BACK_SIDE)
+ continue;
+ }
+
+ if( footprint->GetAttributes() & MOD_VIRTUAL )
+ {
+ DBG( printf( "skipping footprint %s because it's virtual\n",
+ TO_UTF8( footprint->GetReference() ) );)
+ continue;
+ }
+
+ if( ( footprint->GetAttributes() & MOD_CMS ) == 0 )
+ {
+ if( aForceSmdItems ) // true to fix a bunch of mis-labeled footprints:
+ {
+ if( !HasNonSMDPins( footprint ) )
+ {
+ // all footprint's pins are SMD, mark the part for pick and place
+ footprint->SetAttributes( footprint->GetAttributes() | MOD_CMS );
+ OnModify();
+ }
+ else
+ {
+ DBG(printf( "skipping %s because its attribute is not CMS and it has non SMD pins\n",
+ TO_UTF8(footprint->GetReference()) ) );
+ continue;
+ }
+ }
+ else
+ continue;
+ }
+
+ footprintCount++;
+
+ LIST_MOD item;
+ item.m_Module = footprint;
+ item.m_Reference = footprint->GetReference();
+ item.m_Value = footprint->GetValue();
+ item.m_Layer = footprint->GetLayer();
+ list.push_back( item );
+
+ lenRefText = std::max( lenRefText, int(item.m_Reference.length()) );
+ lenValText = std::max( lenValText, int(item.m_Value.length()) );
+ lenPkgText = std::max( lenPkgText, int(item.m_Module->GetFPID().GetFootprintName().length()) );
+ }
+
+ if( aFullFileName.IsEmpty() )
+ return footprintCount;
+
+ FILE * file = wxFopen( aFullFileName, wxT( "wt" ) );
+ if( file == NULL )
+ return -1;
+
+ if( list.size() > 1 )
+ sort( list.begin(), list.end(), sortFPlist );
+
+ // Switch the locale to standard C (needed to print floating point numbers)
+ LOCALE_IO toggle;
+
+ // Write file header
+ fprintf( file, "### Module positions - created on %s ###\n", TO_UTF8( DateAndTime() ) );
+
+ wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
+ fprintf( file, "### Printed by Pcbnew version %s\n", TO_UTF8( Title ) );
+
+ fputs( unit_text, file );
+
+ fputs( "## Side : ", file );
+
+ if( aSide == PCB_BACK_SIDE )
+ fputs( TO_UTF8( backSideName ), file );
+ else if( aSide == PCB_FRONT_SIDE )
+ fputs( TO_UTF8( frontSideName ), file );
+ else
+ fputs( "All", file );
+
+ fputs( "\n", file );
+
+ fprintf(file, "%-*s %-*s %-*s %9.9s %9.9s %8.8s %s\n",
+ int(lenRefText), "# Ref",
+ int(lenValText), "Val",
+ int(lenPkgText), "Package",
+ "PosX", "PosY", "Rot", "Side" );
+
+ for( int ii = 0; ii < footprintCount; ii++ )
+ {
+ wxPoint footprint_pos;
+ footprint_pos = list[ii].m_Module->GetPosition();
+ footprint_pos -= File_Place_Offset;
+
+ LAYER_NUM layer = list[ii].m_Module->GetLayer();
+ wxASSERT( layer==F_Cu || layer==B_Cu );
+
+ const wxString& ref = list[ii].m_Reference;
+ const wxString& val = list[ii].m_Value;
+ const wxString& pkg = list[ii].m_Module->GetFPID().GetFootprintName();
+
+ fprintf(file, "%-*s %-*s %-*s %9.4f %9.4f %8.4f %s\n",
+ lenRefText, TO_UTF8( ref ),
+ lenValText, TO_UTF8( val ),
+ lenPkgText, TO_UTF8( pkg ),
+ footprint_pos.x * conv_unit,
+ // Keep the coordinates in the first quadrant,
+ // (i.e. change y sign
+ -footprint_pos.y * conv_unit,
+ list[ii].m_Module->GetOrientation() / 10.0,
+ (layer == F_Cu ) ? TO_UTF8( frontSideName ) : TO_UTF8( backSideName ));
+ }
+
+ // Write EOF
+ fputs( "## End\n", file );
+
+ fclose( file );
+ return footprintCount;
+}
+
+
+void PCB_EDIT_FRAME::GenFootprintsReport( wxCommandEvent& event )
+{
+ wxFileName fn;
+
+ wxString boardFilePath = ( (wxFileName) GetBoard()->GetFileName()).GetPath();
+ wxDirDialog dirDialog( this, _( "Select Output Directory" ), boardFilePath );
+
+ if( dirDialog.ShowModal() == wxID_CANCEL )
+ return;
+
+ fn = GetBoard()->GetFileName();
+ fn.SetPath( dirDialog.GetPath() );
+ fn.SetExt( wxT( "rpt" ) );
+
+ bool unitMM = g_UserUnit != INCHES;
+ bool success = DoGenFootprintsReport( fn.GetFullPath(), unitMM );
+
+ wxString msg;
+ if( success )
+ {
+ msg.Printf( _( "Footprint report file created:\n'%s'" ),
+ GetChars( fn.GetFullPath() ) );
+ wxMessageBox( msg, _( "Footprint Report" ), wxICON_INFORMATION );
+ }
+
+ else
+ {
+ msg.Printf( _( "Unable to create '%s'" ), GetChars( fn.GetFullPath() ) );
+ DisplayError( this, msg );
+ }
+}
+
+/* Print a module report.
+ */
+bool PCB_EDIT_FRAME::DoGenFootprintsReport( const wxString& aFullFilename, bool aUnitsMM )
+{
+ wxString msg;
+ FILE* rptfile;
+ wxPoint module_pos;
+
+ File_Place_Offset = wxPoint( 0, 0 );
+
+ rptfile = wxFopen( aFullFilename, wxT( "wt" ) );
+
+ if( rptfile == NULL )
+ return false;
+
+ // Select units:
+ double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
+ const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
+
+ LOCALE_IO toggle;
+
+ // Generate header file comments.)
+ fprintf( rptfile, "## Footprint report - date %s\n", TO_UTF8( DateAndTime() ) );
+
+ wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
+ fprintf( rptfile, "## Created by Pcbnew version %s\n", TO_UTF8( Title ) );
+ fputs( unit_text, rptfile );
+
+ fputs( "\n$BeginDESCRIPTION\n", rptfile );
+
+ EDA_RECT bbbox = GetBoard()->ComputeBoundingBox();
+
+ fputs( "\n$BOARD\n", rptfile );
+
+ fprintf( rptfile, "upper_left_corner %9.6f %9.6f\n",
+ bbbox.GetX() * conv_unit,
+ bbbox.GetY() * conv_unit );
+
+ fprintf( rptfile, "lower_right_corner %9.6f %9.6f\n",
+ bbbox.GetRight() * conv_unit,
+ bbbox.GetBottom() * conv_unit );
+
+ fputs( "$EndBOARD\n\n", rptfile );
+
+ for( MODULE* Module = GetBoard()->m_Modules; Module; Module = Module->Next() )
+ {
+ fprintf( rptfile, "$MODULE %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
+
+ fprintf( rptfile, "reference %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
+ fprintf( rptfile, "value %s\n", EscapedUTF8( Module->GetValue() ).c_str() );
+ fprintf( rptfile, "footprint %s\n",
+ EscapedUTF8( FROM_UTF8( Module->GetFPID().Format().c_str() ) ).c_str() );
+
+ msg = wxT( "attribut" );
+
+ if( Module->GetAttributes() & MOD_VIRTUAL )
+ msg += wxT( " virtual" );
+
+ if( Module->GetAttributes() & MOD_CMS )
+ msg += wxT( " smd" );
+
+ if( ( Module->GetAttributes() & (MOD_VIRTUAL | MOD_CMS) ) == 0 )
+ msg += wxT( " none" );
+
+ msg += wxT( "\n" );
+ fputs( TO_UTF8( msg ), rptfile );
+
+ module_pos = Module->GetPosition();
+ module_pos.x -= File_Place_Offset.x;
+ module_pos.y -= File_Place_Offset.y;
+
+ fprintf( rptfile, "position %9.6f %9.6f orientation %.2f\n",
+ module_pos.x * conv_unit,
+ module_pos.y * conv_unit,
+ Module->GetOrientation() / 10.0 );
+
+ if( Module->GetLayer() == F_Cu )
+ fputs( "layer front\n", rptfile );
+ else if( Module->GetLayer() == B_Cu )
+ fputs( "layer back\n", rptfile );
+ else
+ fputs( "layer other\n", rptfile );
+
+ for( D_PAD* pad = Module->Pads(); pad != NULL; pad = pad->Next() )
+ {
+ fprintf( rptfile, "$PAD \"%s\"\n", TO_UTF8( pad->GetPadName() ) );
+ int layer = 0;
+
+ if( pad->GetLayerSet()[B_Cu] )
+ layer = 1;
+
+ if( pad->GetLayerSet()[F_Cu] )
+ layer |= 2;
+
+ static const char* layer_name[4] = { "nocopper", "back", "front", "both" };
+ fprintf( rptfile, "Shape %s Layer %s\n", TO_UTF8( pad->ShowPadShape() ), layer_name[layer] );
+
+ fprintf( rptfile, "position %9.6f %9.6f size %9.6f %9.6f orientation %.2f\n",
+ pad->GetPos0().x * conv_unit, pad->GetPos0().y * conv_unit,
+ pad->GetSize().x * conv_unit, pad->GetSize().y * conv_unit,
+ (pad->GetOrientation() - Module->GetOrientation()) / 10.0 );
+
+ fprintf( rptfile, "drill %9.6f\n", pad->GetDrillSize().x * conv_unit );
+
+ fprintf( rptfile, "shape_offset %9.6f %9.6f\n",
+ pad->GetOffset().x * conv_unit,
+ pad->GetOffset().y * conv_unit );
+
+ fprintf( rptfile, "$EndPAD\n" );
+ }
+
+ fprintf( rptfile, "$EndMODULE %s\n\n", TO_UTF8 (Module->GetReference() ) );
+ }
+
+ // Generate EOF.
+ fputs( "$EndDESCRIPTION\n", rptfile );
+ fclose( rptfile );
+
+ return true;
+}
+