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.

605 lines
16 KiB

17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
  1. /**
  2. * @file common_plotGERBER_functions.cpp
  3. * @brief Common GERBER plot routines.
  4. */
  5. #include <fctsys.h>
  6. #include <gr_basic.h>
  7. #include <trigo.h>
  8. #include <wxstruct.h>
  9. #include <base_struct.h>
  10. #include <common.h>
  11. #include <plot_common.h>
  12. #include <macros.h>
  13. #include <kicad_string.h>
  14. #include <build_version.h>
  15. GERBER_PLOTTER::GERBER_PLOTTER()
  16. {
  17. workFile = 0;
  18. finalFile = 0;
  19. currentAperture = apertures.end();
  20. // number of digits after the point (number of digits of the mantissa
  21. // Be carefull: the Gerber coordinates are stored in an integer
  22. // so 6 digits (inches) or 5 digits (mm) is a good value
  23. // To avoid overflow, 7 digits (inches) or 6 digits is a max.
  24. // with lower values than 6 digits (inches) or 5 digits (mm),
  25. // Creating self-intersecting polygons from non-intersecting polygons
  26. // happen easily.
  27. m_gerberUnitInch = false;
  28. m_gerberUnitFmt = 6;
  29. }
  30. void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
  31. double aScale, bool aMirror )
  32. {
  33. wxASSERT( !outputFile );
  34. wxASSERT( aMirror == false );
  35. m_plotMirror = false;
  36. plotOffset = aOffset;
  37. wxASSERT( aScale == 1 ); // aScale parameter is not used in Gerber
  38. plotScale = 1; // Plot scale is *always* 1.0
  39. m_IUsPerDecimil = aIusPerDecimil;
  40. // gives now a default value to iuPerDeviceUnit (because the units of the caller is now known)
  41. // which could be modified later by calling SetGerberCoordinatesFormat()
  42. iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
  43. // We don't handle the filmbox, and it's more useful to keep the
  44. // origin at the origin
  45. paperSize.x = 0;
  46. paperSize.y = 0;
  47. SetDefaultLineWidth( 100 * aIusPerDecimil ); // Arbitrary default
  48. }
  49. void GERBER_PLOTTER::SetGerberCoordinatesFormat( int aResolution, bool aUseInches )
  50. {
  51. m_gerberUnitInch = aUseInches;
  52. m_gerberUnitFmt = aResolution;
  53. iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
  54. if( ! m_gerberUnitInch )
  55. iuPerDeviceUnit *= 25.4; // gerber output in mm
  56. }
  57. /**
  58. * Emit a D-Code record, using proper conversions
  59. * to format a leading zero omitted gerber coordinate
  60. * (for n decimal positions, see header generation in start_plot
  61. */
  62. void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode )
  63. {
  64. fprintf( outputFile, "X%dY%dD%02d*\n",
  65. KiROUND( pt.x ), KiROUND( pt.y ), dcode );
  66. }
  67. /**
  68. * Function start_plot
  69. * Write GERBER header to file
  70. * initialize global variable g_Plot_PlotOutputFile
  71. */
  72. bool GERBER_PLOTTER::StartPlot()
  73. {
  74. wxASSERT( outputFile );
  75. finalFile = outputFile; // the actual gerber file will be created later
  76. // Create a temporary filename to store gerber file
  77. // note tmpfile() does not work under Vista and W7 in user mode
  78. m_workFilename = filename + wxT(".tmp");
  79. workFile = wxFopen( m_workFilename, wxT( "wt" ));
  80. outputFile = workFile;
  81. wxASSERT( outputFile );
  82. if( outputFile == NULL )
  83. return false;
  84. if( ! m_attribFunction.IsEmpty() )
  85. {
  86. fprintf( outputFile, "%%TF.FileFunction,%s*%%\n",
  87. TO_UTF8( m_attribFunction ) );
  88. }
  89. // Set coordinate format to 3.6 or 4.5 absolute, leading zero omitted
  90. // the number of digits for the integer part of coordintes is needed
  91. // in gerber format, but is not very important when omitting leading zeros
  92. // It is fixed here to 3 (inch) or 4 (mm), but is not actually used
  93. int leadingDigitCount = m_gerberUnitInch ? 3 : 4;
  94. fprintf( outputFile, "%%FSLAX%d%dY%d%d*%%\n",
  95. leadingDigitCount, m_gerberUnitFmt,
  96. leadingDigitCount, m_gerberUnitFmt );
  97. fprintf( outputFile,
  98. "G04 Gerber Fmt %d.%d, Leading zero omitted, Abs format (unit %s)*\n",
  99. leadingDigitCount, m_gerberUnitFmt,
  100. m_gerberUnitInch ? "inch" : "mm" );
  101. wxString Title = creator + wxT( " " ) + GetBuildVersion();
  102. fprintf( outputFile, "G04 Created by KiCad (%s) date %s*\n",
  103. TO_UTF8( Title ), TO_UTF8( DateAndTime() ) );
  104. /* Mass parameter: unit = INCHES/MM */
  105. if( m_gerberUnitInch )
  106. fputs( "%MOIN*%\n", outputFile );
  107. else
  108. fputs( "%MOMM*%\n", outputFile );
  109. /* Specify linear interpol (G01) */
  110. fputs( "G01*\n", outputFile );
  111. fputs( "G04 APERTURE LIST*\n", outputFile );
  112. /* Select the default aperture */
  113. SetCurrentLineWidth( -1 );
  114. return true;
  115. }
  116. bool GERBER_PLOTTER::EndPlot()
  117. {
  118. char line[1024];
  119. wxString msg;
  120. wxASSERT( outputFile );
  121. /* Outfile is actually a temporary file i.e. workFile */
  122. fputs( "M02*\n", outputFile );
  123. fflush( outputFile );
  124. fclose( workFile );
  125. workFile = wxFopen( m_workFilename, wxT( "rt" ));
  126. wxASSERT( workFile );
  127. outputFile = finalFile;
  128. // Placement of apertures in RS274X
  129. while( fgets( line, 1024, workFile ) )
  130. {
  131. fputs( line, outputFile );
  132. if( strcmp( strtok( line, "\n\r" ), "G04 APERTURE LIST*" ) == 0 )
  133. {
  134. writeApertureList();
  135. fputs( "G04 APERTURE END LIST*\n", outputFile );
  136. }
  137. }
  138. fclose( workFile );
  139. fclose( finalFile );
  140. ::wxRemoveFile( m_workFilename );
  141. outputFile = 0;
  142. return true;
  143. }
  144. void GERBER_PLOTTER::SetDefaultLineWidth( int width )
  145. {
  146. defaultPenWidth = width;
  147. currentAperture = apertures.end();
  148. }
  149. void GERBER_PLOTTER::SetCurrentLineWidth( int width )
  150. {
  151. int pen_width;
  152. if( width > 0 )
  153. pen_width = width;
  154. else
  155. pen_width = defaultPenWidth;
  156. selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting );
  157. currentPenWidth = pen_width;
  158. }
  159. std::vector<APERTURE>::iterator GERBER_PLOTTER::getAperture( const wxSize& size,
  160. APERTURE::APERTURE_TYPE type )
  161. {
  162. int last_D_code = 9;
  163. // Search an existing aperture
  164. std::vector<APERTURE>::iterator tool = apertures.begin();
  165. while( tool != apertures.end() )
  166. {
  167. last_D_code = tool->DCode;
  168. if( (tool->Type == type) && (tool->Size == size) )
  169. return tool;
  170. tool++;
  171. }
  172. // Allocate a new aperture
  173. APERTURE new_tool;
  174. new_tool.Size = size;
  175. new_tool.Type = type;
  176. new_tool.DCode = last_D_code + 1;
  177. apertures.push_back( new_tool );
  178. return apertures.end() - 1;
  179. }
  180. void GERBER_PLOTTER::selectAperture( const wxSize& size,
  181. APERTURE::APERTURE_TYPE type )
  182. {
  183. wxASSERT( outputFile );
  184. if( ( currentAperture == apertures.end() )
  185. || ( currentAperture->Type != type )
  186. || ( currentAperture->Size != size ) )
  187. {
  188. // Pick an existing aperture or create a new one
  189. currentAperture = getAperture( size, type );
  190. fprintf( outputFile, "D%d*\n", currentAperture->DCode );
  191. }
  192. }
  193. /**
  194. * Generate the table of D codes
  195. */
  196. void GERBER_PLOTTER::writeApertureList()
  197. {
  198. wxASSERT( outputFile );
  199. char cbuf[1024];
  200. // Init
  201. for( std::vector<APERTURE>::iterator tool = apertures.begin();
  202. tool != apertures.end(); tool++ )
  203. {
  204. // apertude sizes are in inch or mm, regardless the
  205. // coordinates format
  206. double fscale = 0.0001 * plotScale / m_IUsPerDecimil; // inches
  207. if(! m_gerberUnitInch )
  208. fscale *= 25.4; // size in mm
  209. char* text = cbuf + sprintf( cbuf, "%%ADD%d", tool->DCode );
  210. /* Please note: the Gerber specs for mass parameters say that
  211. exponential syntax is *not* allowed and the decimal point should
  212. also be always inserted. So the %g format is ruled out, but %f is fine
  213. (the # modifier forces the decimal point). Sadly the %f formatter
  214. can't remove trailing zeros but thats not a problem, since nothing
  215. forbid it (the file is only slightly longer) */
  216. switch( tool->Type )
  217. {
  218. case APERTURE::Circle:
  219. sprintf( text, "C,%#f*%%\n", tool->Size.x * fscale );
  220. break;
  221. case APERTURE::Rect:
  222. sprintf( text, "R,%#fX%#f*%%\n",
  223. tool->Size.x * fscale,
  224. tool->Size.y * fscale );
  225. break;
  226. case APERTURE::Plotting:
  227. sprintf( text, "C,%#f*%%\n", tool->Size.x * fscale );
  228. break;
  229. case APERTURE::Oval:
  230. sprintf( text, "O,%#fX%#f*%%\n",
  231. tool->Size.x * fscale,
  232. tool->Size.y * fscale );
  233. break;
  234. }
  235. fputs( cbuf, outputFile );
  236. }
  237. }
  238. void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume )
  239. {
  240. wxASSERT( outputFile );
  241. DPOINT pos_dev = userToDeviceCoordinates( aPos );
  242. switch( plume )
  243. {
  244. case 'Z':
  245. break;
  246. case 'U':
  247. emitDcode( pos_dev, 2 );
  248. break;
  249. case 'D':
  250. emitDcode( pos_dev, 1 );
  251. }
  252. penState = plume;
  253. }
  254. void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill,
  255. int width )
  256. {
  257. std::vector< wxPoint > cornerList;
  258. // Build corners list
  259. cornerList.push_back( p1 );
  260. wxPoint corner(p1.x, p2.y);
  261. cornerList.push_back( corner );
  262. cornerList.push_back( p2 );
  263. corner.x = p2.x;
  264. corner.y = p1.y;
  265. cornerList.push_back( corner );
  266. cornerList.push_back( p1 );
  267. PlotPoly( cornerList, fill, width );
  268. }
  269. void GERBER_PLOTTER::Circle( const wxPoint& aCenter, int aDiameter, FILL_T aFill,
  270. int aWidth )
  271. {
  272. Arc( aCenter, 0, 3600, aDiameter / 2, aFill, aWidth );
  273. }
  274. void GERBER_PLOTTER::Arc( const wxPoint& aCenter, double aStAngle, double aEndAngle,
  275. int aRadius, FILL_T aFill, int aWidth )
  276. {
  277. wxASSERT( outputFile );
  278. wxPoint start, end;
  279. start.x = aCenter.x + KiROUND( cosdecideg( aRadius, aStAngle ) );
  280. start.y = aCenter.y - KiROUND( sindecideg( aRadius, aStAngle ) );
  281. SetCurrentLineWidth( aWidth );
  282. MoveTo( start );
  283. end.x = aCenter.x + KiROUND( cosdecideg( aRadius, aEndAngle ) );
  284. end.y = aCenter.y - KiROUND( sindecideg( aRadius, aEndAngle ) );
  285. DPOINT devEnd = userToDeviceCoordinates( end );
  286. DPOINT devCenter = userToDeviceCoordinates( aCenter )
  287. - userToDeviceCoordinates( start );
  288. fprintf( outputFile, "G75*\n" ); // Multiquadrant mode
  289. if( aStAngle < aEndAngle )
  290. fprintf( outputFile, "G03" );
  291. else
  292. fprintf( outputFile, "G02" );
  293. fprintf( outputFile, "X%dY%dI%dJ%dD01*\n",
  294. KiROUND( devEnd.x ), KiROUND( devEnd.y ),
  295. KiROUND( devCenter.x ), KiROUND( devCenter.y ) );
  296. fprintf( outputFile, "G74*\nG01*\n" ); // Back to single quadrant and linear interp.
  297. }
  298. /**
  299. * Gerber polygon: they can (and *should*) be filled with the
  300. * appropriate G36/G37 sequence
  301. */
  302. void GERBER_PLOTTER:: PlotPoly( const std::vector< wxPoint >& aCornerList,
  303. FILL_T aFill, int aWidth )
  304. {
  305. if( aCornerList.size() <= 1 )
  306. return;
  307. // Gerber format does not know filled polygons with thick outline
  308. // Therefore, to plot a filled polygon with outline having a thickness,
  309. // one should plot outline as thick segments
  310. SetCurrentLineWidth( aWidth );
  311. if( aFill )
  312. {
  313. fputs( "G36*\n", outputFile );
  314. MoveTo( aCornerList[0] );
  315. for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
  316. LineTo( aCornerList[ii] );
  317. FinishTo( aCornerList[0] );
  318. fputs( "G37*\n", outputFile );
  319. }
  320. if( aWidth > 0 )
  321. {
  322. MoveTo( aCornerList[0] );
  323. for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
  324. LineTo( aCornerList[ii] );
  325. PenFinish();
  326. }
  327. }
  328. /**
  329. * Filled circular flashes are stored as apertures
  330. */
  331. void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
  332. EDA_DRAW_MODE_T trace_mode )
  333. {
  334. wxASSERT( outputFile );
  335. wxSize size( diametre, diametre );
  336. switch( trace_mode )
  337. {
  338. case LINE:
  339. case SKETCH:
  340. SetCurrentLineWidth( -1 );
  341. Circle( pos, diametre - currentPenWidth, NO_FILL );
  342. break;
  343. case FILLED:
  344. DPOINT pos_dev = userToDeviceCoordinates( pos );
  345. selectAperture( size, APERTURE::Circle );
  346. emitDcode( pos_dev, 3 );
  347. break;
  348. }
  349. }
  350. /**
  351. * Filled oval flashes are handled as aperture in the 90 degree positions only
  352. */
  353. void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
  354. EDA_DRAW_MODE_T trace_mode )
  355. {
  356. wxASSERT( outputFile );
  357. int x0, y0, x1, y1, delta;
  358. wxSize size( aSize );
  359. /* Plot a flashed shape. */
  360. if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 )
  361. && trace_mode == FILLED )
  362. {
  363. if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */
  364. EXCHG( size.x, size.y );
  365. DPOINT pos_dev = userToDeviceCoordinates( pos );
  366. selectAperture( size, APERTURE::Oval );
  367. emitDcode( pos_dev, 3 );
  368. }
  369. else /* Plot pad as a segment. */
  370. {
  371. if( size.x > size.y )
  372. {
  373. EXCHG( size.x, size.y );
  374. if( orient < 2700 )
  375. orient += 900;
  376. else
  377. orient -= 2700;
  378. }
  379. if( trace_mode == FILLED )
  380. {
  381. /* XXX to do: use an aperture macro to declare the rotated pad */
  382. /* The pad is reduced to an oval with dy > dx */
  383. delta = size.y - size.x;
  384. x0 = 0;
  385. y0 = -delta / 2;
  386. x1 = 0;
  387. y1 = delta / 2;
  388. RotatePoint( &x0, &y0, orient );
  389. RotatePoint( &x1, &y1, orient );
  390. ThickSegment( wxPoint( pos.x + x0, pos.y + y0 ),
  391. wxPoint( pos.x + x1, pos.y + y1 ),
  392. size.x, trace_mode );
  393. }
  394. else
  395. {
  396. sketchOval( pos, size, orient, -1 );
  397. }
  398. }
  399. }
  400. /**
  401. * Filled rect flashes are handled as aperture in the 90 degree positions only
  402. */
  403. void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
  404. double orient, EDA_DRAW_MODE_T trace_mode )
  405. {
  406. wxASSERT( outputFile );
  407. wxSize size( aSize );
  408. // Plot as an aperture flash
  409. switch( int( orient ) )
  410. {
  411. case 900:
  412. case 2700: // rotation of 90 degrees or 270 swaps sizes
  413. EXCHG( size.x, size.y );
  414. // Pass through
  415. case 0:
  416. case 1800:
  417. switch( trace_mode )
  418. {
  419. case LINE:
  420. case SKETCH:
  421. SetCurrentLineWidth( -1 );
  422. Rect( wxPoint( pos.x - (size.x - currentPenWidth) / 2,
  423. pos.y - (size.y - currentPenWidth) / 2 ),
  424. wxPoint( pos.x + (size.x - currentPenWidth) / 2,
  425. pos.y + (size.y - currentPenWidth) / 2 ),
  426. NO_FILL );
  427. break;
  428. case FILLED:
  429. DPOINT pos_dev = userToDeviceCoordinates( pos );
  430. selectAperture( size, APERTURE::Rect );
  431. emitDcode( pos_dev, 3 );
  432. break;
  433. }
  434. break;
  435. default: // plot pad shape as polygon
  436. {
  437. // XXX to do: use an aperture macro to declare the rotated pad
  438. wxPoint coord[4];
  439. // coord[0] is assumed the lower left
  440. // coord[1] is assumed the upper left
  441. // coord[2] is assumed the upper right
  442. // coord[3] is assumed the lower right
  443. /* Trace the outline. */
  444. coord[0].x = -size.x/2; // lower left
  445. coord[0].y = size.y/2;
  446. coord[1].x = -size.x/2; // upper left
  447. coord[1].y = -size.y/2;
  448. coord[2].x = size.x/2; // upper right
  449. coord[2].y = -size.y/2;
  450. coord[3].x = size.x/2; // lower right
  451. coord[3].y = size.y/2;
  452. FlashPadTrapez( pos, coord, orient, trace_mode );
  453. }
  454. break;
  455. }
  456. }
  457. /**
  458. * Trapezoidal pad at the moment are *never* handled as aperture, since
  459. * they require aperture macros
  460. */
  461. void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
  462. double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode )
  463. {
  464. // XXX to do: use an aperture macro to declare the pad
  465. // polygon corners list
  466. std::vector< wxPoint > cornerList;
  467. for( int ii = 0; ii < 4; ii++ )
  468. cornerList.push_back( aCorners[ii] );
  469. // Draw the polygon and fill the interior as required
  470. for( unsigned ii = 0; ii < 4; ii++ )
  471. {
  472. RotatePoint( &cornerList[ii], aPadOrient );
  473. cornerList[ii] += aPadPos;
  474. }
  475. // Close the polygon
  476. cornerList.push_back( cornerList[0] );
  477. SetCurrentLineWidth( -1 );
  478. PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL );
  479. }
  480. /**
  481. * Change the plot polarity and begin a new layer
  482. * Used to 'scratch off' silk screen away from solder mask
  483. */
  484. void GERBER_PLOTTER::SetLayerPolarity( bool aPositive )
  485. {
  486. if( aPositive )
  487. fprintf( outputFile, "%%LPD*%%\n" );
  488. else
  489. fprintf( outputFile, "%%LPC*%%\n" );
  490. }