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.

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