1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015-2020 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  */
23 
24 /*
25  *  1 - create ascii/csv files for automatic placement of smd components
26  *  2 - create a footprint report (pos and footprint descr) (ascii file)
27  */
28 
29 #include <string_utils.h>
30 #include <macros.h>
31 #include <locale_io.h>
32 #include <board_design_settings.h>
33 #include <build_version.h>
34 #include <export_footprints_placefile.h>
35 #include <pad.h>
36 
37 #include <wx/dirdlg.h>
38 
39 class LIST_MOD      // An helper class used to build a list of useful footprints.
40 {
41 public:
42     FOOTPRINT*    m_Footprint;      // Link to the actual footprint
43     wxString      m_Reference;      // Its schematic reference
44     wxString      m_Value;          // Its schematic value
45     LAYER_NUM     m_Layer;          // its side (B_Cu, or F_Cu)
46 };
47 
48 
49 // Defined values to write coordinates using inches or mm:
50 static const double conv_unit_inch = 0.001 / IU_PER_MILS ;      // units = INCHES
51 static const char unit_text_inch[] = "## Unit = inches, Angle = deg.\n";
52 
53 static const double conv_unit_mm = 1.0 / IU_PER_MM;    // units = mm
54 static const char unit_text_mm[] = "## Unit = mm, Angle = deg.\n";
55 
56 // Sort function use by GenerefootprintsPosition()
57 // sort is made by side (layer) top layer first
58 // then by reference increasing order
sortFPlist(const LIST_MOD & ref,const LIST_MOD & tst)59 static bool sortFPlist( const LIST_MOD& ref, const LIST_MOD& tst )
60 {
61     if( ref.m_Layer == tst.m_Layer )
62         return StrNumCmp( ref.m_Reference, tst.m_Reference ) < 0;
63 
64     return ref.m_Layer > tst.m_Layer;
65 }
66 
67 
68 enum SELECT_SIDE
69 {
70     PCB_NO_SIDE,
71     PCB_BACK_SIDE,
72     PCB_FRONT_SIDE,
73     PCB_BOTH_SIDES
74 };
75 
PLACE_FILE_EXPORTER(BOARD * aBoard,bool aUnitsMM,bool aOnlySMD,bool aExcludeAllTH,bool aTopSide,bool aBottomSide,bool aFormatCSV,bool aUseAuxOrigin)76 PLACE_FILE_EXPORTER::PLACE_FILE_EXPORTER( BOARD* aBoard, bool aUnitsMM, bool aOnlySMD,
77                                           bool aExcludeAllTH, bool aTopSide, bool aBottomSide,
78                                           bool aFormatCSV, bool aUseAuxOrigin )
79 {
80     m_board        = aBoard;
81     m_unitsMM      = aUnitsMM;
82     m_onlySMD      = aOnlySMD;
83     m_excludeAllTH = aExcludeAllTH;
84     m_fpCount      = 0;
85 
86     if( aTopSide && aBottomSide )
87         m_side = PCB_BOTH_SIDES;
88     else if( aTopSide )
89         m_side = PCB_FRONT_SIDE;
90     else if( aBottomSide )
91         m_side = PCB_BACK_SIDE;
92     else
93         m_side = PCB_NO_SIDE;
94 
95     m_formatCSV = aFormatCSV;
96 
97     if( aUseAuxOrigin )
98         m_place_Offset = m_board->GetDesignSettings().GetAuxOrigin();
99     else
100         m_place_Offset = wxPoint( 0, 0 );
101 }
102 
103 
GenPositionData()104 std::string PLACE_FILE_EXPORTER::GenPositionData()
105 {
106     std::string buffer;
107     char line[1024];        // A line to print intermediate data
108 
109     // Minimal text lengths:
110     m_fpCount = 0;
111     int lenRefText = 8;
112     int lenValText = 8;
113     int lenPkgText = 16;
114 
115     // Calculating the number of useful footprints (CMS attribute, not VIRTUAL)
116     m_fpCount = 0;
117 
118     // Select units:
119     double conv_unit = m_unitsMM ? conv_unit_mm : conv_unit_inch;
120     const char *unit_text = m_unitsMM ? unit_text_mm : unit_text_inch;
121 
122     // Build and sort the list of footprints alphabetically
123     std::vector<LIST_MOD> list;
124 
125     for( FOOTPRINT* footprint : m_board->Footprints() )
126     {
127         if( m_side != PCB_BOTH_SIDES )
128         {
129             if( footprint->GetLayer() == B_Cu && m_side != PCB_BACK_SIDE )
130                 continue;
131             if( footprint->GetLayer() == F_Cu && m_side != PCB_FRONT_SIDE )
132                 continue;
133         }
134 
135         if( footprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES )
136             continue;
137 
138         if( m_onlySMD && !( footprint->GetAttributes() & FP_SMD ) )
139             continue;
140 
141         if( m_excludeAllTH && footprint->HasThroughHolePads() )
142             continue;
143 
144         m_fpCount++;
145 
146         LIST_MOD item;
147         item.m_Footprint    = footprint;
148         item.m_Reference = footprint->Reference().GetShownText();
149         item.m_Value     = footprint->Value().GetShownText();
150         item.m_Layer     = footprint->GetLayer();
151         list.push_back( item );
152 
153         lenRefText = std::max( lenRefText, (int) item.m_Reference.length() );
154         lenValText = std::max( lenValText, (int) item.m_Value.length() );
155         lenPkgText = std::max( lenPkgText, (int) item.m_Footprint->GetFPID().GetLibItemName().length() );
156     }
157 
158     if( list.size() > 1 )
159         sort( list.begin(), list.end(), sortFPlist );
160 
161     // Switch the locale to standard C (needed to print floating point numbers)
162     LOCALE_IO   toggle;
163 
164     if( m_formatCSV )
165     {
166         wxChar csv_sep = ',';
167 
168         // Set first line:;
169         sprintf( line, "Ref%cVal%cPackage%cPosX%cPosY%cRot%cSide\n",
170                  csv_sep, csv_sep, csv_sep, csv_sep, csv_sep, csv_sep );
171 
172         buffer += line;
173 
174         for( int ii = 0; ii < m_fpCount; ii++ )
175         {
176             wxPoint  footprint_pos;
177             footprint_pos  = list[ii].m_Footprint->GetPosition();
178             footprint_pos -= m_place_Offset;
179 
180             LAYER_NUM layer = list[ii].m_Footprint->GetLayer();
181             wxASSERT( layer == F_Cu || layer == B_Cu );
182 
183             if( layer == B_Cu )
184                 footprint_pos.x = - footprint_pos.x;
185 
186             wxString tmp = "\"" + list[ii].m_Reference;
187             tmp << "\"" << csv_sep;
188             tmp << "\"" << list[ii].m_Value;
189             tmp << "\"" << csv_sep;
190             tmp << "\"" << list[ii].m_Footprint->GetFPID().GetLibItemName().wx_str();
191             tmp << "\"" << csv_sep;
192 
193             tmp << wxString::Format( "%f%c%f%c%f",
194                                     footprint_pos.x * conv_unit, csv_sep,
195                                     // Keep the Y axis oriented from bottom to top,
196                                     // ( change y coordinate sign )
197                                     -footprint_pos.y * conv_unit, csv_sep,
198                                      list[ii].m_Footprint->GetOrientation() / 10.0 );
199             tmp << csv_sep;
200 
201             tmp << ( (layer == F_Cu ) ? PLACE_FILE_EXPORTER::GetFrontSideName()
202                                       : PLACE_FILE_EXPORTER::GetBackSideName() );
203             tmp << '\n';
204 
205             buffer += TO_UTF8( tmp );
206         }
207     }
208     else
209     {
210         // Write file header
211         sprintf( line, "### Module positions - created on %s ###\n", TO_UTF8( DateAndTime() ) );
212 
213         buffer += line;
214 
215         wxString Title = GetBuildVersion();
216         sprintf( line, "### Printed by Pcbnew version %s\n", TO_UTF8( Title ) );
217         buffer += line;
218 
219         buffer +=  unit_text;
220         buffer += "## Side : ";
221 
222         if( m_side == PCB_BACK_SIDE )
223             buffer += GetBackSideName().c_str();
224         else if( m_side == PCB_FRONT_SIDE )
225             buffer += GetFrontSideName().c_str();
226         else if( m_side == PCB_BOTH_SIDES )
227             buffer += "All";
228         else
229             buffer += "---";
230 
231         buffer += "\n";
232 
233         sprintf(line, "%-*s  %-*s  %-*s  %9.9s  %9.9s  %8.8s  %s\n",
234                 int(lenRefText), "# Ref",
235                 int(lenValText), "Val",
236                 int(lenPkgText), "Package",
237                 "PosX", "PosY", "Rot", "Side" );
238         buffer += line;
239 
240         for( int ii = 0; ii < m_fpCount; ii++ )
241         {
242             wxPoint  footprint_pos;
243             footprint_pos  = list[ii].m_Footprint->GetPosition();
244             footprint_pos -= m_place_Offset;
245 
246             LAYER_NUM layer = list[ii].m_Footprint->GetLayer();
247             wxASSERT( layer == F_Cu || layer == B_Cu );
248 
249             if( layer == B_Cu )
250                 footprint_pos.x = - footprint_pos.x;
251 
252             wxString ref = list[ii].m_Reference;
253             wxString val = list[ii].m_Value;
254             wxString pkg = list[ii].m_Footprint->GetFPID().GetLibItemName();
255             ref.Replace( wxT( " " ), wxT( "_" ) );
256             val.Replace( wxT( " " ), wxT( "_" ) );
257             pkg.Replace( wxT( " " ), wxT( "_" ) );
258             sprintf(line, "%-*s  %-*s  %-*s  %9.4f  %9.4f  %8.4f  %s\n",
259                     lenRefText, TO_UTF8( ref ),
260                     lenValText, TO_UTF8( val ),
261                     lenPkgText, TO_UTF8( pkg ),
262                     footprint_pos.x * conv_unit,
263                     // Keep the coordinates in the first quadrant,
264                     // (i.e. change y sign
265                     -footprint_pos.y * conv_unit,
266                     list[ii].m_Footprint->GetOrientation() / 10.0,
267                     (layer == F_Cu ) ? GetFrontSideName().c_str() : GetBackSideName().c_str() );
268             buffer += line;
269         }
270 
271         // Write EOF
272         buffer += "## End\n";
273     }
274 
275     return buffer;
276 }
277 
278 
GenReportData()279 std::string PLACE_FILE_EXPORTER::GenReportData()
280 {
281     std::string buffer;
282 
283     m_place_Offset = wxPoint( 0, 0 );
284 
285     // Select units:
286     double conv_unit = m_unitsMM ? conv_unit_mm : conv_unit_inch;
287     const char *unit_text = m_unitsMM ? unit_text_mm : unit_text_inch;
288 
289     LOCALE_IO   toggle;
290 
291     // Generate header file comments.)
292     char line[1024];
293     sprintf( line, "## Footprint report - date %s\n", TO_UTF8( DateAndTime() ) );
294     buffer += line;
295 
296     wxString Title = GetBuildVersion();
297     sprintf( line, "## Created by Pcbnew version %s\n", TO_UTF8( Title ) );
298     buffer += line;
299 
300     buffer += unit_text;
301 
302     buffer += "\n$BeginDESCRIPTION\n";
303 
304     EDA_RECT bbbox = m_board->ComputeBoundingBox();
305 
306     buffer += "\n$BOARD\n";
307 
308     sprintf( line, "upper_left_corner %9.6f %9.6f\n",
309              bbbox.GetX() * conv_unit, bbbox.GetY() * conv_unit );
310     buffer += line;
311 
312     sprintf( line, "lower_right_corner %9.6f %9.6f\n",
313              bbbox.GetRight()  * conv_unit, bbbox.GetBottom() * conv_unit );
314     buffer += line;
315 
316     buffer += "$EndBOARD\n\n";
317 
318     std::vector<FOOTPRINT*> sortedFootprints;
319 
320     for( FOOTPRINT* footprint : m_board->Footprints() )
321         sortedFootprints.push_back( footprint );
322 
323     std::sort( sortedFootprints.begin(), sortedFootprints.end(),
324                []( FOOTPRINT* a, FOOTPRINT* b ) -> bool
325                {
326                    return StrNumCmp( a->GetReference(), b->GetReference(), true ) < 0;
327                });
328 
329     for( FOOTPRINT* footprint : sortedFootprints )
330     {
331         wxString ref = footprint->Reference().GetShownText();
332 
333         sprintf( line, "$MODULE %s\n", TO_UTF8( ref ) );
334         buffer += line;
335 
336         sprintf( line, "reference %s\n", TO_UTF8( ref ) );
337         sprintf( line, "value %s\n", EscapedUTF8( footprint->Value().GetShownText() ).c_str() );
338         sprintf( line, "footprint %s\n", footprint->GetFPID().Format().c_str() );
339         buffer += line;
340 
341         buffer += "attribut";
342 
343         if(( footprint->GetAttributes() & ( FP_THROUGH_HOLE | FP_SMD ) ) == 0 )
344             buffer += " virtual";
345 
346         if( footprint->GetAttributes() & FP_SMD )
347             buffer += " smd";
348 
349         if( footprint->GetAttributes() & FP_THROUGH_HOLE )
350             buffer += " none";
351 
352         buffer += "\n";
353 
354         wxPoint footprint_pos = footprint->GetPosition();
355         footprint_pos -= m_place_Offset;
356 
357         sprintf( line, "position %9.6f %9.6f  orientation %.2f\n",
358                  footprint_pos.x * conv_unit,
359                  footprint_pos.y * conv_unit,
360                  footprint->GetOrientation() / 10.0 );
361         buffer += line;
362 
363         if( footprint->GetLayer() == F_Cu )
364             buffer += "layer front\n";
365         else if( footprint->GetLayer() == B_Cu )
366             buffer += "layer back\n";
367         else
368             buffer += "layer other\n";
369 
370         std::vector<PAD*> sortedPads;
371 
372         for( PAD* pad : footprint->Pads() )
373             sortedPads.push_back( pad );
374 
375         std::sort( sortedPads.begin(), sortedPads.end(),
376                    []( PAD* a, PAD* b ) -> bool
377                    {
378                        return StrNumCmp( a->GetNumber(), b->GetNumber(), true ) < 0;
379                    });
380 
381         for( PAD* pad : sortedPads )
382         {
383             sprintf( line, "$PAD \"%s\"\n", TO_UTF8( pad->GetNumber() ) );
384             buffer += line;
385 
386             int layer = 0;
387 
388             if( pad->GetLayerSet()[B_Cu] )
389                 layer = 1;
390 
391             if( pad->GetLayerSet()[F_Cu] )
392                 layer |= 2;
393 
394             static const char* layer_name[4] = { "nocopper", "back", "front", "both" };
395             sprintf( line, "Shape %s Layer %s\n",
396                      TO_UTF8( pad->ShowPadShape() ),
397                      layer_name[layer] );
398             buffer += line;
399 
400             sprintf( line, "position %9.6f %9.6f  size %9.6f %9.6f  orientation %.2f\n",
401                      pad->GetPos0().x * conv_unit,
402                      pad->GetPos0().y * conv_unit,
403                      pad->GetSize().x * conv_unit,
404                      pad->GetSize().y * conv_unit,
405                      ( pad->GetOrientation() - footprint->GetOrientation()) / 10.0 );
406             buffer += line;
407 
408             sprintf( line, "drill %9.6f\n", pad->GetDrillSize().x * conv_unit );
409             buffer += line;
410 
411             sprintf( line, "shape_offset %9.6f %9.6f\n",
412                      pad->GetOffset().x * conv_unit,
413                      pad->GetOffset().y * conv_unit );
414             buffer += line;
415 
416             buffer += "$EndPAD\n";
417         }
418 
419         sprintf( line, "$EndMODULE  %s\n\n", TO_UTF8( ref ) );
420         buffer += line;
421     }
422 
423     // Generate EOF.
424     buffer += "$EndDESCRIPTION\n";
425 
426     return buffer;
427 }
428