You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
13 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Jean_Pierre Charras <jp.charras at wanadoo.fr>
  5. * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /**
  21. * @file gerber_placefile_writer.cpp
  22. * @brief Functions to create place files in gerber X2 format.
  23. */
  24. #include "gerber_placefile_writer.h"
  25. #include <vector>
  26. #include <plotter.h>
  27. #include <plotters_specific.h>
  28. #include <kicad_string.h>
  29. #include <locale_io.h>
  30. #include <pcb_edit_frame.h>
  31. #include <pgm_base.h>
  32. #include <board.h>
  33. #include <pcbplot.h>
  34. #include <wildcards_and_files_ext.h>
  35. #include <reporter.h>
  36. #include <gbr_metadata.h>
  37. #include <footprint.h>
  38. PLACEFILE_GERBER_WRITER::PLACEFILE_GERBER_WRITER( BOARD* aPcb )
  39. {
  40. m_pcb = aPcb;
  41. m_plotPad1Marker = true; // Place a marker to pin 1 (or A1) position
  42. m_plotOtherPadsMarker = true; // Place a marker to other pins position
  43. m_layer = PCB_LAYER_ID::UNDEFINED_LAYER; // No layer set
  44. }
  45. int PLACEFILE_GERBER_WRITER::CreatePlaceFile( wxString& aFullFilename, PCB_LAYER_ID aLayer,
  46. bool aIncludeBrdEdges )
  47. {
  48. m_layer = aLayer;
  49. PCB_PLOT_PARAMS plotOpts = m_pcb->GetPlotOptions();
  50. if( plotOpts.GetUseAuxOrigin() )
  51. m_offset = m_pcb->GetDesignSettings().m_AuxOrigin;
  52. // Collect footprints on the right layer
  53. std::vector<FOOTPRINT*> fp_list;
  54. for( FOOTPRINT* footprint : m_pcb->Footprints() )
  55. {
  56. if( footprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES )
  57. continue;
  58. if( footprint->GetLayer() == aLayer )
  59. fp_list.push_back( footprint );
  60. }
  61. LOCALE_IO dummy_io; // Use the standard notation for float numbers
  62. GERBER_PLOTTER plotter;
  63. // Gerber drill file imply X2 format:
  64. plotter.UseX2format( true );
  65. plotter.UseX2NetAttributes( true );
  66. // Add the standard X2 header, without FileFunction
  67. AddGerberX2Header( &plotter, m_pcb );
  68. plotter.SetViewport( m_offset, IU_PER_MILS/10, /* scale */ 1.0, /* mirror */false );
  69. // has meaning only for gerber plotter. Must be called only after SetViewport
  70. plotter.SetGerberCoordinatesFormat( 6 );
  71. plotter.SetCreator( wxT( "PCBNEW" ) );
  72. // Add the standard X2 FileFunction for P&P files
  73. // %TF.FileFunction,Component,Ln,[top][bottom]*%
  74. wxString text;
  75. text.Printf( "%%TF.FileFunction,Component,L%d,%s*%%",
  76. aLayer == B_Cu ? m_pcb->GetCopperLayerCount() : 1,
  77. aLayer == B_Cu ? "Bot" : "Top" );
  78. plotter.AddLineToHeader( text );
  79. // Add file polarity (positive)
  80. text = "%TF.FilePolarity,Positive*%";
  81. plotter.AddLineToHeader( text );
  82. if( !plotter.OpenFile( aFullFilename ) )
  83. return -1;
  84. // We need a BRDITEMS_PLOTTER to plot pads
  85. BRDITEMS_PLOTTER brd_plotter( &plotter, m_pcb, plotOpts );
  86. plotter.StartPlot();
  87. // Some tools in P&P files have the type and size defined.
  88. // they are position flash (round), pad1 flash (diamond), other pads flash (round)
  89. // and component outline thickness (polyline)
  90. int flash_position_shape_diam = Millimeter2iu( 0.3 ); // defined size for position shape (circle)
  91. int pad1_mark_size = Millimeter2iu( 0.36 ); // defined size for pad 1 position (diamond)
  92. int other_pads_mark_size = 0; // defined size for position shape (circle)
  93. int line_thickness = Millimeter2iu( 0.1 ); // defined size for component outlines
  94. brd_plotter.SetLayerSet( LSET( aLayer ) );
  95. int cmp_count = 0;
  96. bool allowUtf8 = true;
  97. // Plot components data: position, outlines, pad1 and other pads.
  98. for( FOOTPRINT* footprint : fp_list )
  99. {
  100. // Manage the aperture attribute component position:
  101. GBR_METADATA gbr_metadata;
  102. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_POSITION );
  103. // Add object attribute: component reference to flash (mainly usefull for users)
  104. // using quoted UTF8 string
  105. wxString ref = ConvertNotAllowedCharsInGerber( footprint->Reference().GetShownText(),
  106. allowUtf8, true );
  107. gbr_metadata.SetCmpReference( ref );
  108. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
  109. // Add P&P specific attributes
  110. GBR_CMP_PNP_METADATA pnpAttrib;
  111. // Add rotation info (rotation is CCW, in degrees):
  112. pnpAttrib.m_Orientation = mapRotationAngle( footprint->GetOrientationDegrees() );
  113. pnpAttrib.m_MountType = GBR_CMP_PNP_METADATA::MOUNT_TYPE_UNSPECIFIED;
  114. if( footprint->GetAttributes() & FP_THROUGH_HOLE )
  115. pnpAttrib.m_MountType = GBR_CMP_PNP_METADATA::MOUNT_TYPE_TH;
  116. else if( footprint->GetAttributes() & FP_SMD )
  117. pnpAttrib.m_MountType = GBR_CMP_PNP_METADATA::MOUNT_TYPE_SMD;
  118. // Add component value info:
  119. pnpAttrib.m_Value = ConvertNotAllowedCharsInGerber( footprint->Value().GetShownText(),
  120. allowUtf8, true );
  121. // Add component footprint info:
  122. wxString fp_info = FROM_UTF8( footprint->GetFPID().GetLibItemName().c_str() );
  123. pnpAttrib.m_Footprint = ConvertNotAllowedCharsInGerber( fp_info, allowUtf8, true );
  124. // Add footprint lib name:
  125. fp_info = FROM_UTF8( footprint->GetFPID().GetLibNickname().c_str() );
  126. pnpAttrib.m_LibraryName = ConvertNotAllowedCharsInGerber( fp_info, allowUtf8, true );
  127. gbr_metadata.m_NetlistMetadata.SetExtraData( pnpAttrib.FormatCmpPnPMetadata() );
  128. wxPoint flash_pos = footprint->GetPosition();
  129. plotter.FlashPadCircle( flash_pos, flash_position_shape_diam, FILLED, &gbr_metadata );
  130. gbr_metadata.m_NetlistMetadata.ClearExtraData();
  131. // Now some extra metadata is output, avoid blindly clearing the full metadata list
  132. gbr_metadata.m_NetlistMetadata.m_TryKeepPreviousAttributes = true;
  133. // We plot the footprint courtyard when possible.
  134. // If not, the pads bounding box will be used.
  135. bool useFpPadsBbox = true;
  136. bool onBack = aLayer == B_Cu;
  137. footprint->BuildPolyCourtyards();
  138. int checkFlag = onBack ? MALFORMED_B_COURTYARD : MALFORMED_F_COURTYARD;
  139. if( ( footprint->GetFlags() & checkFlag ) == 0 )
  140. {
  141. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_COURTYARD );
  142. SHAPE_POLY_SET& courtyard = onBack ? footprint->GetPolyCourtyardBack()
  143. : footprint->GetPolyCourtyardFront();
  144. for( int ii = 0; ii < courtyard.OutlineCount(); ii++ )
  145. {
  146. SHAPE_LINE_CHAIN poly = courtyard.Outline( ii );
  147. if( !poly.PointCount() )
  148. continue;
  149. useFpPadsBbox = false;
  150. plotter.PLOTTER::PlotPoly( poly, FILL_TYPE::NO_FILL, line_thickness, &gbr_metadata );
  151. }
  152. }
  153. if( useFpPadsBbox )
  154. {
  155. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_FOOTPRINT );
  156. // bbox of fp pads, pos 0, rot 0, non flipped
  157. EDA_RECT bbox = footprint->GetFpPadsLocalBbox();
  158. // negate bbox Y values if the fp is flipped (always flipped around X axis
  159. // in Gerber P&P files).
  160. int y_sign = aLayer == B_Cu ? -1 : 1;
  161. SHAPE_LINE_CHAIN poly;
  162. poly.Append( bbox.GetLeft(), y_sign*bbox.GetTop() );
  163. poly.Append( bbox.GetLeft(), y_sign*bbox.GetBottom() );
  164. poly.Append( bbox.GetRight(), y_sign*bbox.GetBottom() );
  165. poly.Append( bbox.GetRight(), y_sign*bbox.GetTop() );
  166. poly.SetClosed( true );
  167. poly.Rotate( -footprint->GetOrientationRadians(), VECTOR2I( 0, 0 ) );
  168. poly.Move( footprint->GetPosition() );
  169. plotter.PLOTTER::PlotPoly( poly, FILL_TYPE::NO_FILL, line_thickness, &gbr_metadata );
  170. }
  171. std::vector<PAD*>pad_key_list;
  172. if( m_plotPad1Marker )
  173. {
  174. findPads1( pad_key_list, footprint );
  175. for( PAD* pad1 : pad_key_list )
  176. {
  177. gbr_metadata.SetApertureAttrib(
  178. GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PAD1_POSITION );
  179. gbr_metadata.SetPadName( pad1->GetName(), allowUtf8, true );
  180. gbr_metadata.SetPadPinFunction( pad1->GetPinFunction(), allowUtf8, true );
  181. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD );
  182. // Flashes a diamond at pad position:
  183. plotter.FlashRegularPolygon( pad1->GetPosition(),
  184. pad1_mark_size,
  185. 4, 0.0, FILLED, &gbr_metadata );
  186. }
  187. }
  188. if( m_plotOtherPadsMarker )
  189. {
  190. gbr_metadata.SetApertureAttrib(
  191. GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PADOTHER_POSITION );
  192. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD );
  193. for( PAD* pad: footprint->Pads() )
  194. {
  195. bool skip_pad = false;
  196. for( PAD* pad1 : pad_key_list )
  197. {
  198. if( pad == pad1 ) // Already plotted
  199. {
  200. skip_pad = true;
  201. break;
  202. }
  203. }
  204. if( skip_pad )
  205. continue;
  206. // Skip also pads not on the current layer, like pads only
  207. // on a tech layer
  208. if( !pad->IsOnLayer( aLayer ) )
  209. continue;
  210. gbr_metadata.SetPadName( pad->GetName(), allowUtf8, true );
  211. gbr_metadata.SetPadPinFunction( pad->GetPinFunction(), allowUtf8, true );
  212. // Flashes a round, 0 sized round shape at pad position
  213. plotter.FlashPadCircle( pad->GetPosition(),
  214. other_pads_mark_size,
  215. FILLED, &gbr_metadata );
  216. }
  217. }
  218. plotter.ClearAllAttributes(); // Unconditionally close all .TO attributes
  219. cmp_count++;
  220. }
  221. // Plot board outlines, if requested
  222. if( aIncludeBrdEdges )
  223. {
  224. brd_plotter.SetLayerSet( LSET( Edge_Cuts ) );
  225. // Plot edge layer and graphic items
  226. brd_plotter.PlotBoardGraphicItems();
  227. // Draw footprint other graphic items:
  228. for( FOOTPRINT* footprint : fp_list )
  229. {
  230. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  231. {
  232. if( item->Type() == PCB_FP_SHAPE_T && item->GetLayer() == Edge_Cuts )
  233. brd_plotter.PlotFootprintGraphicItem( (FP_SHAPE*) item );
  234. }
  235. }
  236. }
  237. plotter.EndPlot();
  238. return cmp_count;
  239. }
  240. double PLACEFILE_GERBER_WRITER::mapRotationAngle( double aAngle )
  241. {
  242. // convert a kicad footprint orientation to gerber rotation, depending on the layer
  243. // Currently, same notation as kicad
  244. return aAngle;
  245. }
  246. void PLACEFILE_GERBER_WRITER::findPads1( std::vector<PAD*>& aPadList, FOOTPRINT* aFootprint ) const
  247. {
  248. // Fint the pad "1" or pad "A1"
  249. // this is possible only if only one pad is found
  250. // Usefull to place a marker in this position
  251. for( PAD* pad : aFootprint->Pads() )
  252. {
  253. if( !pad->IsOnLayer( m_layer ) )
  254. continue;
  255. if( pad->GetName() == "1" || pad->GetName() == "A1")
  256. aPadList.push_back( pad );
  257. }
  258. }
  259. const wxString PLACEFILE_GERBER_WRITER::GetPlaceFileName( const wxString& aFullBaseFilename,
  260. PCB_LAYER_ID aLayer ) const
  261. {
  262. // Gerber files extension is always .gbr.
  263. // Therefore, to mark pnp files, add "-pnp" to the filename, and a layer id.
  264. wxFileName fn = aFullBaseFilename;
  265. wxString post_id = "-pnp_";
  266. post_id += aLayer == B_Cu ? "bottom" : "top";
  267. fn.SetName( fn.GetName() + post_id );
  268. fn.SetExt( GerberFileExtension );
  269. return fn.GetFullPath();
  270. }