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.

659 lines
20 KiB

4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2013 Cirilo Bernardo
  5. * Copyright (C) 2018-2022 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <list>
  25. #include <locale_io.h>
  26. #include <macros.h>
  27. #include <pcb_edit_frame.h>
  28. #include <board.h>
  29. #include <board_design_settings.h>
  30. #include <footprint.h>
  31. #include <fp_shape.h>
  32. #include <idf_parser.h>
  33. #include <pad.h>
  34. #include <build_version.h>
  35. #include <wx/msgdlg.h>
  36. #include "project.h"
  37. #include "kiway.h"
  38. #include "3d_cache/3d_cache.h"
  39. #include "filename_resolver.h"
  40. #ifndef PCBNEW
  41. #define PCBNEW // needed to define the right value of Millimeter2iu(x)
  42. #endif
  43. #include <convert_to_biu.h> // to define Millimeter2iu(x)
  44. // assumed default graphical line thickness: == 0.1mm
  45. #define LINE_WIDTH (Millimeter2iu( 0.1 ))
  46. static FILENAME_RESOLVER* resolver;
  47. /**
  48. * Retrieve line segment information from the edge layer and compiles the data into a form
  49. * which can be output as an IDFv3 compliant #BOARD_OUTLINE section.
  50. */
  51. static void idf_export_outline( BOARD* aPcb, IDF3_BOARD& aIDFBoard )
  52. {
  53. double scale = aIDFBoard.GetUserScale();
  54. PCB_SHAPE* graphic; // KiCad graphical item
  55. IDF_POINT sp, ep; // start and end points from KiCad item
  56. std::list< IDF_SEGMENT* > lines; // IDF intermediate form of KiCad graphical item
  57. IDF_OUTLINE* outline = nullptr; // graphical items forming an outline or cutout
  58. // NOTE: IMPLEMENTATION
  59. // If/when component cutouts are allowed, we must implement them separately. Cutouts
  60. // must be added to the board outline section and not to the Other Outline section.
  61. // The footprint cutouts should be handled via the idf_export_footprint() routine.
  62. double offX, offY;
  63. aIDFBoard.GetUserOffset( offX, offY );
  64. // Retrieve segments and arcs from the board
  65. for( BOARD_ITEM* item : aPcb->Drawings() )
  66. {
  67. if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
  68. continue;
  69. graphic = (PCB_SHAPE*) item;
  70. switch( graphic->GetShape() )
  71. {
  72. case SHAPE_T::SEGMENT:
  73. {
  74. if( graphic->GetStart() == graphic->GetEnd() )
  75. break;
  76. sp.x = graphic->GetStart().x * scale + offX;
  77. sp.y = -graphic->GetStart().y * scale + offY;
  78. ep.x = graphic->GetEnd().x * scale + offX;
  79. ep.y = -graphic->GetEnd().y * scale + offY;
  80. IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep );
  81. if( seg )
  82. lines.push_back( seg );
  83. break;
  84. }
  85. case SHAPE_T::RECT:
  86. {
  87. if( graphic->GetStart() == graphic->GetEnd() )
  88. break;
  89. double top = graphic->GetStart().y * scale + offY;
  90. double left = graphic->GetStart().x * scale + offX;
  91. double bottom = graphic->GetEnd().y * scale + offY;
  92. double right = graphic->GetEnd().x * scale + offX;
  93. IDF_POINT corners[4];
  94. corners[0] = IDF_POINT( left, top );
  95. corners[1] = IDF_POINT( right, top );
  96. corners[2] = IDF_POINT( right, bottom );
  97. corners[3] = IDF_POINT( left, bottom );
  98. lines.push_back( new IDF_SEGMENT( corners[0], corners[1] ) );
  99. lines.push_back( new IDF_SEGMENT( corners[1], corners[2] ) );
  100. lines.push_back( new IDF_SEGMENT( corners[2], corners[3] ) );
  101. lines.push_back( new IDF_SEGMENT( corners[3], corners[0] ) );
  102. break;
  103. }
  104. case SHAPE_T::ARC:
  105. {
  106. if( graphic->GetCenter() == graphic->GetStart() )
  107. break;
  108. sp.x = graphic->GetCenter().x * scale + offX;
  109. sp.y = -graphic->GetCenter().y * scale + offY;
  110. ep.x = graphic->GetStart().x * scale + offX;
  111. ep.y = -graphic->GetStart().y * scale + offY;
  112. IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, -graphic->GetArcAngle().AsDegrees(), true );
  113. if( seg )
  114. lines.push_back( seg );
  115. break;
  116. }
  117. case SHAPE_T::CIRCLE:
  118. {
  119. if( graphic->GetRadius() == 0 )
  120. break;
  121. sp.x = graphic->GetCenter().x * scale + offX;
  122. sp.y = -graphic->GetCenter().y * scale + offY;
  123. ep.x = sp.x - graphic->GetRadius() * scale;
  124. ep.y = sp.y;
  125. // Circles must always have an angle of +360 deg. to appease
  126. // quirky MCAD implementations of IDF.
  127. IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, 360.0, true );
  128. if( seg )
  129. lines.push_back( seg );
  130. break;
  131. }
  132. default:
  133. break;
  134. }
  135. }
  136. // if there is no outline then use the bounding box
  137. if( lines.empty() )
  138. {
  139. goto UseBoundingBox;
  140. }
  141. // get the board outline and write it out
  142. // note: we do not use a try/catch block here since we intend
  143. // to simply ignore unclosed loops and continue processing
  144. // until we're out of segments to process
  145. outline = new IDF_OUTLINE;
  146. IDF3::GetOutline( lines, *outline );
  147. if( outline->empty() )
  148. goto UseBoundingBox;
  149. aIDFBoard.AddBoardOutline( outline );
  150. outline = nullptr;
  151. // get all cutouts and write them out
  152. while( !lines.empty() )
  153. {
  154. if( !outline )
  155. outline = new IDF_OUTLINE;
  156. IDF3::GetOutline( lines, *outline );
  157. if( outline->empty() )
  158. {
  159. outline->Clear();
  160. continue;
  161. }
  162. aIDFBoard.AddBoardOutline( outline );
  163. outline = nullptr;
  164. }
  165. return;
  166. UseBoundingBox:
  167. // clean up if necessary
  168. while( !lines.empty() )
  169. {
  170. delete lines.front();
  171. lines.pop_front();
  172. }
  173. if( outline )
  174. outline->Clear();
  175. else
  176. outline = new IDF_OUTLINE;
  177. // Fetch a rectangular bounding box for the board; there is always some uncertainty in the
  178. // board dimensions computed via ComputeBoundingBox() since this depends on the individual
  179. // footprint entities.
  180. EDA_RECT bbbox = aPcb->GetBoardEdgesBoundingBox();
  181. // convert to mm and compensate for an assumed LINE_WIDTH line thickness
  182. double x = ( bbbox.GetOrigin().x + LINE_WIDTH / 2 ) * scale + offX;
  183. double y = ( bbbox.GetOrigin().y + LINE_WIDTH / 2 ) * scale + offY;
  184. double dx = ( bbbox.GetSize().x - LINE_WIDTH ) * scale;
  185. double dy = ( bbbox.GetSize().y - LINE_WIDTH ) * scale;
  186. double px[4], py[4];
  187. px[0] = x;
  188. py[0] = y;
  189. px[1] = x;
  190. py[1] = y + dy;
  191. px[2] = x + dx;
  192. py[2] = y + dy;
  193. px[3] = x + dx;
  194. py[3] = y;
  195. IDF_POINT p1, p2;
  196. p1.x = px[3];
  197. p1.y = py[3];
  198. p2.x = px[0];
  199. p2.y = py[0];
  200. outline->push( new IDF_SEGMENT( p1, p2 ) );
  201. for( int i = 1; i < 4; ++i )
  202. {
  203. p1.x = px[i - 1];
  204. p1.y = py[i - 1];
  205. p2.x = px[i];
  206. p2.y = py[i];
  207. outline->push( new IDF_SEGMENT( p1, p2 ) );
  208. }
  209. aIDFBoard.AddBoardOutline( outline );
  210. }
  211. /**
  212. * Retrieve information from all board footprints, adds drill holes to the DRILLED_HOLES or
  213. * BOARD_OUTLINE section as appropriate, Compiles data for the PLACEMENT section and compiles
  214. * data for the library ELECTRICAL section.
  215. */
  216. static void idf_export_footprint( BOARD* aPcb, FOOTPRINT* aFootprint, IDF3_BOARD& aIDFBoard )
  217. {
  218. // Reference Designator
  219. std::string crefdes = TO_UTF8( aFootprint->Reference().GetShownText() );
  220. if( crefdes.empty() || !crefdes.compare( "~" ) )
  221. {
  222. std::string cvalue = TO_UTF8( aFootprint->Value().GetShownText() );
  223. // if both the RefDes and Value are empty or set to '~' the board owns the part,
  224. // otherwise associated parts of the footprint must be marked NOREFDES.
  225. if( cvalue.empty() || !cvalue.compare( "~" ) )
  226. crefdes = "BOARD";
  227. else
  228. crefdes = "NOREFDES";
  229. }
  230. // TODO: If footprint cutouts are supported we must add code here
  231. // for( EDA_ITEM* item = aFootprint->GraphicalItems(); item != NULL; item = item->Next() )
  232. // {
  233. // if( item->Type() != PCB_FP_SHAPE_T || item->GetLayer() != Edge_Cuts )
  234. // continue;
  235. // code to export cutouts
  236. // }
  237. // Export pads
  238. double drill, x, y;
  239. double scale = aIDFBoard.GetUserScale();
  240. IDF3::KEY_PLATING kplate;
  241. std::string pintype;
  242. std::string tstr;
  243. double dx, dy;
  244. aIDFBoard.GetUserOffset( dx, dy );
  245. for( auto pad : aFootprint->Pads() )
  246. {
  247. drill = (double) pad->GetDrillSize().x * scale;
  248. x = pad->GetPosition().x * scale + dx;
  249. y = -pad->GetPosition().y * scale + dy;
  250. // Export the hole on the edge layer
  251. if( drill > 0.0 )
  252. {
  253. // plating
  254. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  255. kplate = IDF3::NPTH;
  256. else
  257. kplate = IDF3::PTH;
  258. // hole type
  259. tstr = TO_UTF8( pad->GetNumber() );
  260. if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" )
  261. || ( kplate == IDF3::NPTH )
  262. || ( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ) )
  263. pintype = "MTG";
  264. else
  265. pintype = "PIN";
  266. // fields:
  267. // 1. hole dia. : float
  268. // 2. X coord : float
  269. // 3. Y coord : float
  270. // 4. plating : PTH | NPTH
  271. // 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"}
  272. // 6. type : PIN | VIA | MTG | TOOL | { "other" }
  273. // 7. owner : MCAD | ECAD | UNOWNED
  274. if( ( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
  275. && ( pad->GetDrillSize().x != pad->GetDrillSize().y ) )
  276. {
  277. // NOTE: IDF does not have direct support for slots;
  278. // slots are implemented as a board cutout and we
  279. // cannot represent plating or reference designators
  280. double dlength = pad->GetDrillSize().y * scale;
  281. // NOTE: The orientation of footprints and pads have
  282. // the opposite sense due to KiCad drawing on a
  283. // screen with a LH coordinate system
  284. double angle = pad->GetOrientation().AsDegrees();
  285. // NOTE: Since this code assumes the scenario where
  286. // GetDrillSize().y is the length but idf_parser.cpp
  287. // assumes a length along the X axis, the orientation
  288. // must be shifted +90 deg when GetDrillSize().y is
  289. // the major axis.
  290. if( dlength < drill )
  291. {
  292. std::swap( drill, dlength );
  293. }
  294. else
  295. {
  296. angle += 90.0;
  297. }
  298. // NOTE: KiCad measures a slot's length from end to end
  299. // rather than between the centers of the arcs
  300. dlength -= drill;
  301. aIDFBoard.AddSlot( drill, dlength, angle, x, y );
  302. }
  303. else
  304. {
  305. IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
  306. pintype, IDF3::ECAD );
  307. if( !aIDFBoard.AddDrill( dp ) )
  308. {
  309. delete dp;
  310. std::ostringstream ostr;
  311. ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
  312. ostr << "(): could not add drill";
  313. throw std::runtime_error( ostr.str() );
  314. }
  315. }
  316. }
  317. }
  318. // add any valid models to the library item list
  319. std::string refdes;
  320. IDF3_COMPONENT* comp = nullptr;
  321. auto sM = aFootprint->Models().begin();
  322. auto eM = aFootprint->Models().end();
  323. wxFileName idfFile;
  324. wxString idfExt;
  325. while( sM != eM )
  326. {
  327. if( !sM->m_Show )
  328. {
  329. ++sM;
  330. continue;
  331. }
  332. idfFile.Assign( resolver->ResolvePath( sM->m_Filename ) );
  333. idfExt = idfFile.GetExt();
  334. if( idfExt.Cmp( wxT( "idf" ) ) && idfExt.Cmp( wxT( "IDF" ) ) )
  335. {
  336. ++sM;
  337. continue;
  338. }
  339. if( refdes.empty() )
  340. {
  341. refdes = TO_UTF8( aFootprint->Reference().GetShownText() );
  342. // NOREFDES cannot be used or else the software gets confused
  343. // when writing out the placement data due to conflicting
  344. // placement and layer specifications; to work around this we
  345. // create a (hopefully) unique refdes for our exported part.
  346. if( refdes.empty() || !refdes.compare( "~" ) )
  347. refdes = aIDFBoard.GetNewRefDes();
  348. }
  349. IDF3_COMP_OUTLINE* outline;
  350. outline = aIDFBoard.GetComponentOutline( idfFile.GetFullPath() );
  351. if( !outline )
  352. throw( std::runtime_error( aIDFBoard.GetError() ) );
  353. double rotz = aFootprint->GetOrientation().AsDegrees();
  354. double locx = sM->m_Offset.x * 25.4; // part offsets are in inches
  355. double locy = sM->m_Offset.y * 25.4;
  356. double locz = sM->m_Offset.z * 25.4;
  357. double lrot = sM->m_Rotation.z;
  358. bool top = ( aFootprint->GetLayer() == B_Cu ) ? false : true;
  359. if( top )
  360. {
  361. locy = -locy;
  362. RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
  363. locy = -locy;
  364. }
  365. if( !top )
  366. {
  367. lrot = -lrot;
  368. RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
  369. locy = -locy;
  370. rotz = 180.0 - rotz;
  371. if( rotz >= 360.0 )
  372. while( rotz >= 360.0 ) rotz -= 360.0;
  373. if( rotz <= -360.0 )
  374. while( rotz <= -360.0 ) rotz += 360.0;
  375. }
  376. if( comp == nullptr )
  377. comp = aIDFBoard.FindComponent( refdes );
  378. if( comp == nullptr )
  379. {
  380. comp = new IDF3_COMPONENT( &aIDFBoard );
  381. if( comp == nullptr )
  382. throw( std::runtime_error( aIDFBoard.GetError() ) );
  383. comp->SetRefDes( refdes );
  384. if( top )
  385. {
  386. comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
  387. -aFootprint->GetPosition().y * scale + dy,
  388. rotz, IDF3::LYR_TOP );
  389. }
  390. else
  391. {
  392. comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
  393. -aFootprint->GetPosition().y * scale + dy,
  394. rotz, IDF3::LYR_BOTTOM );
  395. }
  396. comp->SetPlacement( IDF3::PS_ECAD );
  397. aIDFBoard.AddComponent( comp );
  398. }
  399. else
  400. {
  401. double refX, refY, refA;
  402. IDF3::IDF_LAYER side;
  403. if( ! comp->GetPosition( refX, refY, refA, side ) )
  404. {
  405. // place the item
  406. if( top )
  407. {
  408. comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
  409. -aFootprint->GetPosition().y * scale + dy,
  410. rotz, IDF3::LYR_TOP );
  411. }
  412. else
  413. {
  414. comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
  415. -aFootprint->GetPosition().y * scale + dy,
  416. rotz, IDF3::LYR_BOTTOM );
  417. }
  418. comp->SetPlacement( IDF3::PS_ECAD );
  419. }
  420. else
  421. {
  422. // check that the retrieved component matches this one
  423. refX = refX - ( aFootprint->GetPosition().x * scale + dx );
  424. refY = refY - ( -aFootprint->GetPosition().y * scale + dy );
  425. refA = refA - rotz;
  426. refA *= refA;
  427. refX *= refX;
  428. refY *= refY;
  429. refX += refY;
  430. // conditions: same side, X,Y coordinates within 10 microns,
  431. // angle within 0.01 degree
  432. if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
  433. || ( refA > 0.0001 ) || ( refX > 0.0001 ) )
  434. {
  435. comp->GetPosition( refX, refY, refA, side );
  436. std::ostringstream ostr;
  437. ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
  438. ostr << "* conflicting Reference Designator '" << refdes << "'\n";
  439. ostr << "* X loc: " << ( aFootprint->GetPosition().x * scale + dx);
  440. ostr << " vs. " << refX << "\n";
  441. ostr << "* Y loc: " << ( -aFootprint->GetPosition().y * scale + dy);
  442. ostr << " vs. " << refY << "\n";
  443. ostr << "* angle: " << rotz;
  444. ostr << " vs. " << refA << "\n";
  445. if( top )
  446. ostr << "* TOP vs. ";
  447. else
  448. ostr << "* BOTTOM vs. ";
  449. if( side == IDF3::LYR_TOP )
  450. ostr << "TOP";
  451. else
  452. ostr << "BOTTOM";
  453. throw( std::runtime_error( ostr.str() ) );
  454. }
  455. }
  456. }
  457. // create the local data ...
  458. IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );
  459. data->SetOffsets( locx, locy, locz, lrot );
  460. comp->AddOutlineData( data );
  461. ++sM;
  462. }
  463. }
  464. /**
  465. * Generate IDFv3 compliant board (*.emn) and library (*.emp) files representing the user's
  466. * PCB design.
  467. */
  468. bool PCB_EDIT_FRAME::Export_IDF3( BOARD* aPcb, const wxString& aFullFileName,
  469. bool aUseThou, double aXRef, double aYRef )
  470. {
  471. IDF3_BOARD idfBoard( IDF3::CAD_ELEC );
  472. // Switch the locale to standard C (needed to print floating point numbers)
  473. LOCALE_IO toggle;
  474. resolver = Prj().Get3DCacheManager()->GetResolver();
  475. bool ok = true;
  476. double scale = MM_PER_IU; // we must scale internal units to mm for IDF
  477. IDF3::IDF_UNIT idfUnit;
  478. if( aUseThou )
  479. {
  480. idfUnit = IDF3::UNIT_THOU;
  481. idfBoard.SetUserPrecision( 1 );
  482. }
  483. else
  484. {
  485. idfUnit = IDF3::UNIT_MM;
  486. idfBoard.SetUserPrecision( 5 );
  487. }
  488. wxFileName brdName = aPcb->GetFileName();
  489. idfBoard.SetUserScale( scale );
  490. idfBoard.SetBoardThickness( aPcb->GetDesignSettings().GetBoardThickness() * scale );
  491. idfBoard.SetBoardName( TO_UTF8( brdName.GetFullName() ) );
  492. idfBoard.SetBoardVersion( 0 );
  493. idfBoard.SetLibraryVersion( 0 );
  494. std::ostringstream ostr;
  495. ostr << "KiCad " << TO_UTF8( GetBuildVersion() );
  496. idfBoard.SetIDFSource( ostr.str() );
  497. try
  498. {
  499. // set up the board reference point
  500. idfBoard.SetUserOffset( -aXRef, aYRef );
  501. // Export the board outline
  502. idf_export_outline( aPcb, idfBoard );
  503. // Output the drill holes and footprint (library) data.
  504. for( FOOTPRINT* footprint : aPcb->Footprints() )
  505. idf_export_footprint( aPcb, footprint, idfBoard );
  506. if( !idfBoard.WriteFile( aFullFileName, idfUnit, false ) )
  507. {
  508. wxString msg;
  509. msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( idfBoard.GetError().c_str() );
  510. wxMessageBox( msg );
  511. ok = false;
  512. }
  513. }
  514. catch( const IO_ERROR& ioe )
  515. {
  516. wxString msg;
  517. msg << _( "IDF Export Failed:\n" ) << ioe.What();
  518. wxMessageBox( msg );
  519. ok = false;
  520. }
  521. catch( const std::exception& e )
  522. {
  523. wxString msg;
  524. msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( e.what() );
  525. wxMessageBox( msg );
  526. ok = false;
  527. }
  528. return ok;
  529. }