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.

647 lines
18 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
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 CERN
  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. #include <kiface_i.h>
  24. #include <macros.h>
  25. #include <pcb_edit_frame.h>
  26. #include <board.h>
  27. #include <board_design_settings.h>
  28. #include <board_item.h>
  29. #include <footprint.h>
  30. #include <pad.h>
  31. #include <track.h>
  32. #include <zone.h>
  33. #include <cstdio>
  34. #include <vector>
  35. #include <ki_exception.h>
  36. #include <locale_io.h>
  37. #include <reporter.h>
  38. #include <wx/log.h>
  39. #include <exporters/board_exporter_base.h>
  40. static double iu2hyp( double iu )
  41. {
  42. return iu / 1e9 / 0.0254;
  43. }
  44. class HYPERLYNX_EXPORTER;
  45. class HYPERLYNX_PAD_STACK
  46. {
  47. public:
  48. friend class HYPERLYNX_EXPORTER;
  49. HYPERLYNX_PAD_STACK( BOARD* aBoard, const PAD* aPad );
  50. HYPERLYNX_PAD_STACK( BOARD* aBoard, const VIA* aVia );
  51. ~HYPERLYNX_PAD_STACK(){};
  52. bool isThrough() const
  53. {
  54. return m_type == PAD_ATTRIB::NPTH || m_type == PAD_ATTRIB::PTH;
  55. }
  56. bool operator==( const HYPERLYNX_PAD_STACK& other ) const
  57. {
  58. if( m_shape != other.m_shape )
  59. return false;
  60. if( m_type != other.m_type )
  61. return false;
  62. if( isThrough() && other.isThrough() && m_drill != other.m_drill )
  63. return false;
  64. if( m_sx != other.m_sx )
  65. return false;
  66. if( m_sy != other.m_sy )
  67. return false;
  68. if( m_layers != other.m_layers )
  69. return false;
  70. if( m_angle != other.m_angle )
  71. return false;
  72. return true;
  73. }
  74. bool isSMD() const
  75. {
  76. return m_type == PAD_ATTRIB::SMD;
  77. }
  78. PCB_LAYER_ID getSMDLayer() const
  79. {
  80. for( auto l : LSET::AllCuMask().Seq() )
  81. {
  82. if( m_layers[l] )
  83. return l;
  84. }
  85. return F_Cu;
  86. }
  87. void SetId( int id )
  88. {
  89. m_id = id;
  90. }
  91. int GetId() const
  92. {
  93. return m_id;
  94. }
  95. int IsSupportedByExporter() const
  96. {
  97. switch( m_shape )
  98. {
  99. case PAD_SHAPE::CIRCLE:
  100. case PAD_SHAPE::OVAL:
  101. case PAD_SHAPE::ROUNDRECT:
  102. case PAD_SHAPE::RECT: return true;
  103. default: return false;
  104. }
  105. }
  106. bool isEmpty() const
  107. {
  108. LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
  109. LSET outLayers = m_layers & layerMask;
  110. return outLayers.none();
  111. }
  112. private:
  113. BOARD* m_board;
  114. int m_id;
  115. int m_drill;
  116. PAD_SHAPE m_shape;
  117. int m_sx, m_sy;
  118. double m_angle;
  119. LSET m_layers;
  120. PAD_ATTRIB m_type;
  121. };
  122. class HYPERLYNX_EXPORTER : public BOARD_EXPORTER_BASE
  123. {
  124. public:
  125. HYPERLYNX_EXPORTER() : m_polyId( 1 )
  126. {
  127. }
  128. ~HYPERLYNX_EXPORTER(){};
  129. virtual bool Run() override;
  130. private:
  131. HYPERLYNX_PAD_STACK* addPadStack( HYPERLYNX_PAD_STACK stack )
  132. {
  133. for( HYPERLYNX_PAD_STACK* p : m_padStacks )
  134. {
  135. if( *p == stack )
  136. return p;
  137. }
  138. stack.SetId( m_padStacks.size() );
  139. m_padStacks.push_back( new HYPERLYNX_PAD_STACK( stack ) );
  140. return m_padStacks.back();
  141. }
  142. const std::string formatPadShape( const HYPERLYNX_PAD_STACK& aStack )
  143. {
  144. int shapeId = 0;
  145. char buf[1024];
  146. switch( aStack.m_shape )
  147. {
  148. case PAD_SHAPE::CIRCLE:
  149. case PAD_SHAPE::OVAL: shapeId = 0; break;
  150. case PAD_SHAPE::ROUNDRECT: shapeId = 2; break;
  151. case PAD_SHAPE::RECT: shapeId = 1; break;
  152. default:
  153. shapeId = 0;
  154. if( m_reporter )
  155. {
  156. m_reporter->Report(
  157. _( "File contains pad shapes that are not supported by the Hyperlynx exporter\n"
  158. "(Supported shapes are oval, rectangle, circle.)\n"
  159. "They have been exported as oval pads." ),
  160. RPT_SEVERITY_WARNING );
  161. }
  162. break;
  163. }
  164. snprintf( buf, sizeof( buf ), "%d, %.9f, %.9f, %.1f, M", shapeId,
  165. iu2hyp( aStack.m_sx ),
  166. iu2hyp( aStack.m_sy ),
  167. aStack.m_angle );
  168. return buf;
  169. }
  170. bool generateHeaders();
  171. bool writeBoardInfo();
  172. bool writeStackupInfo();
  173. bool writeDevices();
  174. bool writePadStacks();
  175. bool writeNets();
  176. bool writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects );
  177. void writeSinglePadStack( HYPERLYNX_PAD_STACK& aStack );
  178. const std::vector<BOARD_ITEM*> collectNetObjects( int netcode );
  179. std::vector<HYPERLYNX_PAD_STACK*> m_padStacks;
  180. std::map<BOARD_ITEM*, HYPERLYNX_PAD_STACK*> m_padMap;
  181. std::shared_ptr<FILE_OUTPUTFORMATTER> m_out;
  182. int m_polyId;
  183. };
  184. HYPERLYNX_PAD_STACK::HYPERLYNX_PAD_STACK( BOARD* aBoard, const PAD* aPad )
  185. {
  186. m_board = aBoard;
  187. m_sx = aPad->GetSize().x;
  188. m_sy = aPad->GetSize().y;
  189. m_angle = 180.0 - ( aPad->GetOrientation() / 10.0 );
  190. if( m_angle < 0.0 )
  191. {
  192. m_angle += 360.0;
  193. }
  194. m_layers = aPad->GetLayerSet();
  195. m_drill = aPad->GetDrillSize().x;
  196. m_shape = aPad->GetShape();
  197. m_type = PAD_ATTRIB::PTH;
  198. m_id = 0;
  199. }
  200. HYPERLYNX_PAD_STACK::HYPERLYNX_PAD_STACK( BOARD* aBoard, const VIA* aVia )
  201. {
  202. m_board = aBoard;
  203. m_sx = aVia->GetWidth();
  204. m_sy = aVia->GetWidth();
  205. m_angle = 0;
  206. m_layers = LSET::AllCuMask();
  207. m_drill = aVia->GetDrillValue();
  208. m_shape = PAD_SHAPE::CIRCLE;
  209. m_type = PAD_ATTRIB::PTH;
  210. m_id = 0;
  211. }
  212. bool HYPERLYNX_EXPORTER::generateHeaders()
  213. {
  214. m_out->Print( 0, "{VERSION=2.14}\n" );
  215. m_out->Print( 0, "{UNITS=ENGLISH LENGTH}\n\n" );
  216. return true;
  217. }
  218. void HYPERLYNX_EXPORTER::writeSinglePadStack( HYPERLYNX_PAD_STACK& aStack )
  219. {
  220. LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
  221. LSET outLayers = aStack.m_layers & layerMask;
  222. if( outLayers.none() )
  223. return;
  224. m_out->Print( 0, "{PADSTACK=%d, %.9f\n", aStack.m_id, iu2hyp( aStack.m_drill ) );
  225. if( outLayers == layerMask )
  226. {
  227. m_out->Print( 1, "(\"%s\", %s)\n", "MDEF", formatPadShape( aStack ).c_str() );
  228. }
  229. else
  230. {
  231. for( PCB_LAYER_ID l : outLayers.Seq() )
  232. {
  233. m_out->Print( 1, "(\"%s\", %s)\n", (const char*) m_board->GetLayerName( l ).c_str(),
  234. formatPadShape( aStack ).c_str() );
  235. }
  236. }
  237. m_out->Print( 0, "}\n\n" );
  238. }
  239. bool HYPERLYNX_EXPORTER::writeBoardInfo()
  240. {
  241. SHAPE_POLY_SET outlines;
  242. m_out->Print( 0, "{BOARD \"%s\"\n", (const char*) m_board->GetFileName().c_str() );
  243. if( !m_board->GetBoardPolygonOutlines( outlines ) )
  244. {
  245. wxLogError( _( "Board outline is malformed. Run DRC for a full analysis." ) );
  246. return false;
  247. }
  248. for( int o = 0; o < outlines.OutlineCount(); o++ )
  249. {
  250. const SHAPE_LINE_CHAIN& outl = outlines.COutline( o );
  251. for( int i = 0; i < outl.SegmentCount(); i++ )
  252. {
  253. const auto& s = outl.CSegment( i );
  254. m_out->Print( 1, "(PERIMETER_SEGMENT X1=%.9f Y1=%.9f X2=%.9f Y2=%.9f)\n",
  255. iu2hyp( s.A.x ), iu2hyp( s.A.y ), iu2hyp( s.B.x ), iu2hyp( s.B.y ) );
  256. }
  257. }
  258. m_out->Print( 0, "}\n\n" );
  259. return true;
  260. }
  261. bool HYPERLYNX_EXPORTER::writeStackupInfo()
  262. {
  263. /* Format:
  264. * {STACKUP
  265. * (SIGNAL T=thickness [P=plating_thickness] [C=constant] L=layer_name [M=material_name]) [comment]
  266. * (DIELECTRIC T=thickness [C=constant] [L=layer_name] [M=material_name]) [comment]
  267. * }
  268. * name lenght is <= 20 chars
  269. */
  270. LSEQ layers = m_board->GetDesignSettings().GetEnabledLayers().CuStack();
  271. // Get the board physical stackup structure
  272. const BOARD_STACKUP& stackup = m_board->GetDesignSettings().GetStackupDescriptor();
  273. m_out->Print( 0, "{STACKUP\n" );
  274. wxString layer_name; // The last copper layer name used in stackup
  275. for( BOARD_STACKUP_ITEM* item: stackup.GetList() )
  276. {
  277. if( item->GetType() == BS_ITEM_TYPE_COPPER )
  278. {
  279. layer_name = m_board->GetLayerName( item->GetBrdLayerId() );
  280. int plating_thickness = 0;
  281. double resistivity = 1.724e-8; // Good for copper
  282. m_out->Print( 1, "(SIGNAL T=%g P=%g C=%g L=\"%.20s\" M=COPPER)\n",
  283. iu2hyp( item->GetThickness( 0 ) ),
  284. iu2hyp( plating_thickness ),
  285. resistivity,
  286. TO_UTF8( layer_name ) );
  287. }
  288. else if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  289. {
  290. if( item->GetSublayersCount() < 2 )
  291. {
  292. m_out->Print( 1, "(DIELECTRIC T=%g C=%g L=\"DE_%.17s\" M=\"%.20s\")\n",
  293. iu2hyp( item->GetThickness( 0 ) ),
  294. item->GetEpsilonR( 0 ),
  295. TO_UTF8( layer_name ),
  296. TO_UTF8( item->GetMaterial( 0 ) ) );
  297. }
  298. else for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
  299. {
  300. m_out->Print( 1, "(DIELECTRIC T=%g C=%g L=\"DE%d_%.16s\" M=\"%.20s\")\n",
  301. iu2hyp( item->GetThickness( idx ) ),
  302. item->GetEpsilonR( idx ),
  303. idx, TO_UTF8( layer_name ),
  304. TO_UTF8( item->GetMaterial( idx ) ) );
  305. }
  306. }
  307. }
  308. m_out->Print( 0, "}\n\n" );
  309. return true;
  310. }
  311. bool HYPERLYNX_EXPORTER::writeDevices()
  312. {
  313. m_out->Print( 0, "{DEVICES\n" );
  314. for( FOOTPRINT* footprint : m_board->Footprints() )
  315. {
  316. wxString ref = footprint->GetReference();
  317. wxString layerName = m_board->GetLayerName( footprint->GetLayer() );
  318. if( ref.IsEmpty() )
  319. ref = "EMPTY";
  320. m_out->Print( 1, "(? REF=\"%s\" L=\"%s\")\n", (const char*) ref.c_str(),
  321. (const char*) layerName.c_str() );
  322. }
  323. m_out->Print( 0, "}\n\n" );
  324. return true;
  325. }
  326. bool HYPERLYNX_EXPORTER::writePadStacks()
  327. {
  328. for( FOOTPRINT* footprint : m_board->Footprints() )
  329. {
  330. for( PAD* pad : footprint->Pads() )
  331. {
  332. HYPERLYNX_PAD_STACK* ps = addPadStack( HYPERLYNX_PAD_STACK( m_board, pad ) );
  333. m_padMap[pad] = ps;
  334. }
  335. }
  336. for( TRACK* trk : m_board->Tracks() )
  337. {
  338. if( VIA* via = dyn_cast<VIA*>( trk ) )
  339. {
  340. HYPERLYNX_PAD_STACK* ps = addPadStack( HYPERLYNX_PAD_STACK( m_board, via ) );
  341. m_padMap[via] = ps;
  342. }
  343. }
  344. for( HYPERLYNX_PAD_STACK* pstack : m_padStacks )
  345. writeSinglePadStack( *pstack );
  346. return true;
  347. }
  348. bool HYPERLYNX_EXPORTER::writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects )
  349. {
  350. for( BOARD_ITEM* item : aObjects )
  351. {
  352. if( PAD* pad = dyn_cast<PAD*>( item ) )
  353. {
  354. auto pstackIter = m_padMap.find( pad );
  355. if( pstackIter != m_padMap.end() )
  356. {
  357. wxString ref = pad->GetParent()->GetReference();
  358. if( ref.IsEmpty() )
  359. ref = "EMPTY";
  360. wxString padName = pad->GetName();
  361. if( padName.IsEmpty() )
  362. padName = "1";
  363. m_out->Print( 1, "(PIN X=%.10f Y=%.10f R=\"%s.%s\" P=%d)\n",
  364. iu2hyp( pad->GetPosition().x ), iu2hyp( pad->GetPosition().y ),
  365. (const char*) ref.c_str(), (const char*) padName.c_str(),
  366. pstackIter->second->GetId() );
  367. }
  368. }
  369. else if( VIA* via = dyn_cast<VIA*>( item ) )
  370. {
  371. auto pstackIter = m_padMap.find( via );
  372. if( pstackIter != m_padMap.end() )
  373. {
  374. m_out->Print( 1, "(VIA X=%.10f Y=%.10f P=%d)\n", iu2hyp( via->GetPosition().x ),
  375. iu2hyp( via->GetPosition().y ), pstackIter->second->GetId() );
  376. }
  377. }
  378. else if( TRACK* track = dyn_cast<TRACK*>( item ) )
  379. {
  380. const wxString layerName = m_board->GetLayerName( track->GetLayer() );
  381. m_out->Print( 1, "(SEG X1=%.10f Y1=%.10f X2=%.10f Y2=%.10f W=%.10f L=\"%s\")\n",
  382. iu2hyp( track->GetStart().x ), iu2hyp( track->GetStart().y ),
  383. iu2hyp( track->GetEnd().x ), iu2hyp( track->GetEnd().y ),
  384. iu2hyp( track->GetWidth() ), (const char*) layerName.c_str() );
  385. }
  386. else if( ZONE* zone = dyn_cast<ZONE*>( item ) )
  387. {
  388. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  389. {
  390. const wxString layerName = m_board->GetLayerName( layer );
  391. SHAPE_POLY_SET filledShape = zone->GetFilledPolysList( layer );
  392. filledShape.Simplify( SHAPE_POLY_SET::PM_FAST );
  393. for( int i = 0; i < filledShape.OutlineCount(); i++ )
  394. {
  395. const SHAPE_LINE_CHAIN& outl = filledShape.COutline( i );
  396. auto p0 = outl.CPoint( 0 );
  397. m_out->Print( 1, "{POLYGON T=POUR L=\"%s\" ID=%d X=%.10f Y=%.10f\n",
  398. (const char*) layerName.c_str(), m_polyId, iu2hyp( p0.x ),
  399. iu2hyp( p0.y ) );
  400. for( int v = 0; v < outl.PointCount(); v++ )
  401. {
  402. m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( outl.CPoint( v ).x ),
  403. iu2hyp( outl.CPoint( v ).y ) );
  404. }
  405. m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( p0.x ), iu2hyp( p0.y ) );
  406. m_out->Print( 1, "}\n" );
  407. for( int h = 0; h < filledShape.HoleCount( i ); h++ )
  408. {
  409. const SHAPE_LINE_CHAIN& holeShape = filledShape.CHole( i, h );
  410. VECTOR2I ph0 = holeShape.CPoint( 0 );
  411. m_out->Print( 1, "{POLYVOID ID=%d X=%.10f Y=%.10f\n", m_polyId,
  412. iu2hyp( ph0.x ), iu2hyp( ph0.y ) );
  413. for( int v = 0; v < holeShape.PointCount(); v++ )
  414. {
  415. m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n",
  416. iu2hyp( holeShape.CPoint( v ).x ),
  417. iu2hyp( holeShape.CPoint( v ).y ) );
  418. }
  419. m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n",
  420. iu2hyp( ph0.x ), iu2hyp( ph0.y ) );
  421. m_out->Print( 1, "}\n" );
  422. }
  423. m_polyId++;
  424. }
  425. }
  426. }
  427. }
  428. return true;
  429. }
  430. const std::vector<BOARD_ITEM*> HYPERLYNX_EXPORTER::collectNetObjects( int netcode )
  431. {
  432. std::vector<BOARD_ITEM*> rv;
  433. auto check =
  434. [&]( BOARD_CONNECTED_ITEM* item ) -> bool
  435. {
  436. if( ( item->GetLayerSet() & LSET::AllCuMask() ).none() )
  437. return false;
  438. if( item->GetNetCode() == netcode || ( netcode < 0 && item->GetNetCode() <= 0 ) )
  439. return true;
  440. return false;
  441. };
  442. for( FOOTPRINT* footprint : m_board->Footprints() )
  443. {
  444. for( PAD* pad : footprint->Pads() )
  445. {
  446. if( check( pad ) )
  447. rv.push_back( pad );
  448. }
  449. }
  450. for( TRACK* item : m_board->Tracks() )
  451. {
  452. if( check( item ) )
  453. rv.push_back( item );
  454. }
  455. for( ZONE* zone : m_board->Zones() )
  456. {
  457. if( check( zone ) )
  458. rv.push_back( zone );
  459. }
  460. return rv;
  461. }
  462. bool HYPERLYNX_EXPORTER::writeNets()
  463. {
  464. m_polyId = 1;
  465. for( const auto netInfo : m_board->GetNetInfo() )
  466. {
  467. int netcode = netInfo->GetNetCode();
  468. bool isNullNet = netInfo->GetNetCode() <= 0 || netInfo->GetNetname().IsEmpty();
  469. if( isNullNet )
  470. continue;
  471. auto netObjects = collectNetObjects( netcode );
  472. if( netObjects.size() )
  473. {
  474. m_out->Print( 0, "{NET=\"%s\"\n", (const char*) netInfo->GetNetname().c_str() );
  475. writeNetObjects( netObjects );
  476. m_out->Print( 0, "}\n\n" );
  477. }
  478. }
  479. auto nullNetObjects = collectNetObjects( -1 );
  480. int idx = 0;
  481. for( auto item : nullNetObjects )
  482. {
  483. m_out->Print( 0, "{NET=\"EmptyNet%d\"\n", idx );
  484. writeNetObjects( { item } );
  485. m_out->Print( 0, "}\n\n" );
  486. idx++;
  487. }
  488. return true;
  489. }
  490. bool HYPERLYNX_EXPORTER::Run()
  491. {
  492. LOCALE_IO toggle; // toggles on, then off, the C locale.
  493. try
  494. {
  495. m_out.reset( new FILE_OUTPUTFORMATTER( m_outputFilePath.GetFullPath() ) );
  496. generateHeaders();
  497. writeBoardInfo();
  498. writeStackupInfo();
  499. writeDevices();
  500. writePadStacks();
  501. writeNets();
  502. }
  503. catch( IO_ERROR& )
  504. {
  505. return false;
  506. }
  507. return true;
  508. }
  509. bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath )
  510. {
  511. HYPERLYNX_EXPORTER exporter;
  512. exporter.SetBoard( aBoard );
  513. exporter.SetOutputFilename( aPath );
  514. return exporter.Run();
  515. }