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.

363 lines
13 KiB

  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 <fctsys.h>
  25. #include "gerber_placefile_writer.h"
  26. #include <vector>
  27. #include <plotter.h>
  28. #include <kicad_string.h>
  29. #include <pcb_edit_frame.h>
  30. #include <pgm_base.h>
  31. #include <build_version.h>
  32. #include <class_board.h>
  33. #include <pcbplot.h>
  34. #include <pcbnew.h>
  35. #include <wildcards_and_files_ext.h>
  36. #include <reporter.h>
  37. #include <gbr_metadata.h>
  38. #include <class_module.h>
  39. #include <pcbplot.h>
  40. PLACEFILE_GERBER_WRITER::PLACEFILE_GERBER_WRITER( BOARD* aPcb )
  41. {
  42. m_pcb = aPcb;
  43. /* Set conversion scale depending on drill file units */
  44. m_conversionUnits = 1.0 / IU_PER_MM; // Gerber units = mm
  45. m_forceSmdItems = false;
  46. m_plotPad1Marker = true; // Place a marker to pin 1 (or A1) position
  47. m_plotOtherPadsMarker = true; // Place a marker to other pins position
  48. m_layer = PCB_LAYER_ID::UNDEFINED_LAYER; // No layer set
  49. }
  50. int PLACEFILE_GERBER_WRITER::CreatePlaceFile( wxString& aFullFilename,
  51. PCB_LAYER_ID aLayer, bool aIncludeBrdEdges
  52. )
  53. {
  54. m_layer = aLayer;
  55. PCB_PLOT_PARAMS plotOpts = m_pcb->GetPlotOptions();
  56. if( plotOpts.GetUseAuxOrigin() )
  57. m_offset = m_pcb->GetAuxOrigin();
  58. // Collect footprints on the right layer
  59. std::vector<MODULE*> fp_list;
  60. for( MODULE* footprint : m_pcb->Modules() )
  61. {
  62. if( footprint->GetAttributes() & MOD_VIRTUAL )
  63. continue;
  64. if( footprint->GetLayer() == aLayer )
  65. fp_list.push_back( footprint );
  66. }
  67. LOCALE_IO dummy_io; // Use the standard notation for float numbers
  68. GERBER_PLOTTER plotter;
  69. // Gerber drill file imply X2 format:
  70. plotter.UseX2format( true );
  71. plotter.UseX2NetAttributes( true );
  72. // Add the standard X2 header, without FileFunction
  73. AddGerberX2Header( &plotter, m_pcb );
  74. plotter.SetViewport( m_offset, IU_PER_MILS/10, /* scale */ 1.0, /* mirror */false );
  75. // has meaning only for gerber plotter. Must be called only after SetViewport
  76. plotter.SetGerberCoordinatesFormat( 6 );
  77. plotter.SetCreator( wxT( "PCBNEW" ) );
  78. // Add the standard X2 FileFunction for P&P files
  79. // %TF.FileFunction,Component,Ln,[top][bottom]*%
  80. wxString text;
  81. text.Printf( "%%TF.FileFunction,Component,L%d,%s*%%",
  82. aLayer == B_Cu ? m_pcb->GetCopperLayerCount() : 1,
  83. aLayer == B_Cu ? "Bot" : "Top" );
  84. plotter.AddLineToHeader( text );
  85. // Add file polarity (positive)
  86. text = "%TF.FilePolarity,Positive*%";
  87. plotter.AddLineToHeader( text );
  88. if( !plotter.OpenFile( aFullFilename ) )
  89. return -1;
  90. // We need a BRDITEMS_PLOTTER to plot pads
  91. BRDITEMS_PLOTTER brd_plotter( &plotter, m_pcb, plotOpts );
  92. plotter.StartPlot();
  93. // Some tools in P&P files have the type and size defined.
  94. // they are position flash (round), pad1 flash (diamond), other pads flash (round)
  95. // and component outline thickness (polyline)
  96. int flash_position_shape_diam = Millimeter2iu( 0.3 ); // defined size for position shape (circle)
  97. int pad1_mark_size = Millimeter2iu( 0.36 ); // defined size for pad 1 position (diamond)
  98. int other_pads_mark_size = 0; // defined size for position shape (circle)
  99. int line_thickness = Millimeter2iu( 0.1 ); // defined size for component outlines
  100. brd_plotter.SetLayerSet( LSET( aLayer ) );
  101. int cmp_count = 0;
  102. bool allowUtf8 = true;
  103. // Plot components data: position, outlines, pad1 and other pads.
  104. for( MODULE* footprint : fp_list )
  105. {
  106. // Manage the aperture attribute component position:
  107. GBR_METADATA gbr_metadata;
  108. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_POSITION );
  109. // Add object attribute: component reference to flash (mainly usefull for users)
  110. // using quoted UTF8 string
  111. wxString ref = ConvertNotAllowedCharsInGerber( footprint->GetReference(),
  112. allowUtf8, true );
  113. gbr_metadata.SetCmpReference( ref );
  114. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
  115. // Add P&P specific attributes
  116. GBR_CMP_PNP_METADATA pnpAttrib;
  117. // Add rotation info (rotation is CCW, in degrees):
  118. pnpAttrib.m_Orientation = mapRotationAngle( footprint->GetOrientationDegrees() );
  119. // Add component type info (SMD or Through Hole):
  120. bool is_smd_mount = footprint->GetAttributes() & MOD_CMS;
  121. // Smd footprints can have through holes (thermal vias).
  122. // but if a footprint is not set as SMD, it will be set as SMD
  123. // if it does not have through hole pads
  124. if( !is_smd_mount && !footprint->HasNonSMDPins() )
  125. is_smd_mount = true;
  126. pnpAttrib.m_MountType = is_smd_mount ? GBR_CMP_PNP_METADATA::MOUNT_TYPE_SMD
  127. : GBR_CMP_PNP_METADATA::MOUNT_TYPE_TH;
  128. // Add component value info:
  129. pnpAttrib.m_Value = ConvertNotAllowedCharsInGerber( footprint->GetValue(), allowUtf8, true );
  130. // Add component footprint info:
  131. wxString fp_info = FROM_UTF8( footprint->GetFPID().GetLibItemName().c_str() );
  132. pnpAttrib.m_Footprint = ConvertNotAllowedCharsInGerber( fp_info, allowUtf8, true );
  133. // Add footprint lib name:
  134. fp_info = FROM_UTF8( footprint->GetFPID().GetLibNickname().c_str() );
  135. pnpAttrib.m_LibraryName = ConvertNotAllowedCharsInGerber( fp_info, allowUtf8, true );
  136. gbr_metadata.m_NetlistMetadata.SetExtraData( pnpAttrib.FormatCmpPnPMetadata() );
  137. wxPoint flash_pos = footprint->GetPosition();
  138. plotter.FlashPadCircle( flash_pos, flash_position_shape_diam, FILLED, &gbr_metadata );
  139. gbr_metadata.m_NetlistMetadata.ClearExtraData();
  140. // Now some extra metadata is output, avoid blindly clearing the full metadata list
  141. gbr_metadata.m_NetlistMetadata.m_TryKeepPreviousAttributes = true;
  142. // We plot the footprint courtyard when possible.
  143. // If not, the pads bounding box will be used.
  144. bool useFpPadsBbox = true;
  145. if( footprint->BuildPolyCourtyard() )
  146. {
  147. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_COURTYARD );
  148. SHAPE_POLY_SET& courtyard = aLayer == B_Cu ?
  149. footprint->GetPolyCourtyardBack():
  150. footprint->GetPolyCourtyardFront();
  151. for( int ii = 0; ii < courtyard.OutlineCount(); ii++ )
  152. {
  153. SHAPE_LINE_CHAIN poly = courtyard.Outline( ii );
  154. if( !poly.PointCount() )
  155. continue;
  156. useFpPadsBbox = false;
  157. plotter.PLOTTER::PlotPoly( poly, NO_FILL, line_thickness, &gbr_metadata );
  158. }
  159. }
  160. if( useFpPadsBbox )
  161. {
  162. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_FOOTPRINT );
  163. // bbox of fp pads, pos 0, rot 0, non flipped
  164. EDA_RECT bbox = footprint->GetFpPadsLocalBbox();
  165. // negate bbox Y values if the fp is flipped (always flipped around X axis
  166. // in Gerber P&P files).
  167. int y_sign = aLayer == B_Cu ? -1 : 1;
  168. SHAPE_LINE_CHAIN poly;
  169. poly.Append( bbox.GetLeft(), y_sign*bbox.GetTop() );
  170. poly.Append( bbox.GetLeft(), y_sign*bbox.GetBottom() );
  171. poly.Append( bbox.GetRight(), y_sign*bbox.GetBottom() );
  172. poly.Append( bbox.GetRight(), y_sign*bbox.GetTop() );
  173. poly.SetClosed( true );
  174. poly.Rotate( -footprint->GetOrientationRadians(), VECTOR2I( 0, 0 ) );
  175. poly.Move( footprint->GetPosition() );
  176. plotter.PLOTTER::PlotPoly( poly, NO_FILL, line_thickness, &gbr_metadata );
  177. }
  178. std::vector<D_PAD*>pad_key_list;
  179. if( m_plotPad1Marker )
  180. {
  181. findPads1( pad_key_list, footprint );
  182. for( D_PAD* pad1 : pad_key_list )
  183. {
  184. gbr_metadata.SetApertureAttrib(
  185. GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PAD1_POSITION );
  186. gbr_metadata.SetPadName( pad1->GetName(), allowUtf8, true );
  187. gbr_metadata.SetPadPinFunction( pad1->GetPinFunction(), allowUtf8, true );
  188. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD );
  189. // Flashes a diamond at pad position:
  190. plotter.FlashRegularPolygon( pad1->GetPosition(),
  191. pad1_mark_size,
  192. 4, 0.0, FILLED, &gbr_metadata );
  193. }
  194. }
  195. if( m_plotOtherPadsMarker )
  196. {
  197. gbr_metadata.SetApertureAttrib(
  198. GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PADOTHER_POSITION );
  199. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD );
  200. for( D_PAD* pad: footprint->Pads() )
  201. {
  202. bool skip_pad = false;
  203. for( D_PAD* pad1 : pad_key_list )
  204. {
  205. if( pad == pad1 ) // Already plotted
  206. {
  207. skip_pad = true;
  208. break;
  209. }
  210. }
  211. if( skip_pad )
  212. continue;
  213. // Skip also pads not on the current layer, like pads only
  214. // on a tech layer
  215. if( !pad->IsOnLayer( aLayer ) )
  216. continue;
  217. gbr_metadata.SetPadName( pad->GetName(), allowUtf8, true );
  218. gbr_metadata.SetPadPinFunction( pad->GetPinFunction(), allowUtf8, true );
  219. // Flashes a round, 0 sized round shape at pad position
  220. plotter.FlashPadCircle( pad->GetPosition(),
  221. other_pads_mark_size,
  222. FILLED, &gbr_metadata );
  223. }
  224. }
  225. plotter.ClearAllAttributes(); // Unconditionally close all .TO attributes
  226. cmp_count++;
  227. }
  228. // Plot board outlines, if requested
  229. if( aIncludeBrdEdges )
  230. {
  231. brd_plotter.SetLayerSet( LSET( Edge_Cuts ) );
  232. // Plot edge layer and graphic items
  233. brd_plotter.PlotBoardGraphicItems();
  234. // Draw footprint other graphic items:
  235. for( MODULE* footprint : fp_list )
  236. {
  237. for( auto item : footprint->GraphicalItems() )
  238. {
  239. if( item->Type() == PCB_MODULE_EDGE_T && item->GetLayer() == Edge_Cuts )
  240. brd_plotter.PlotFootprintGraphicItem((EDGE_MODULE*) item );
  241. }
  242. }
  243. }
  244. plotter.EndPlot();
  245. return cmp_count;
  246. }
  247. double PLACEFILE_GERBER_WRITER::mapRotationAngle( double aAngle )
  248. {
  249. // convert a kicad footprint orientation to gerber rotation, depending on the layer
  250. // Currently, same notation as kicad
  251. return aAngle;
  252. }
  253. void PLACEFILE_GERBER_WRITER::findPads1( std::vector<D_PAD*>& aPadList, MODULE* aFootprint ) const
  254. {
  255. // Fint the pad "1" or pad "A1"
  256. // this is possible only if only one pad is found
  257. // Usefull to place a marker in this position
  258. for( D_PAD* pad : aFootprint->Pads() )
  259. {
  260. if( !pad->IsOnLayer( m_layer ) )
  261. continue;
  262. if( pad->GetName() == "1" || pad->GetName() == "A1")
  263. aPadList.push_back( pad );
  264. }
  265. }
  266. const wxString PLACEFILE_GERBER_WRITER::GetPlaceFileName( const wxString& aFullBaseFilename,
  267. PCB_LAYER_ID aLayer ) const
  268. {
  269. // Gerber files extension is always .gbr.
  270. // Therefore, to mark pnp files, add "-pnp" to the filename, and a layer id.
  271. wxFileName fn = aFullBaseFilename;
  272. wxString post_id = "-pnp_";
  273. post_id += aLayer == B_Cu ? "bottom" : "top";
  274. fn.SetName( fn.GetName() + post_id );
  275. fn.SetExt( GerberFileExtension );
  276. return fn.GetFullPath();
  277. }