KiCad PCB EDA Suite
export_hyperlynx.cpp
Go to the documentation of this file.
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 
24 #include <kiface_i.h>
25 #include <pcb_edit_frame.h>
26 #include <pcbnew.h>
27 
28 #include <class_board.h>
29 #include <class_board_item.h>
30 #include <class_drawsegment.h>
31 #include <class_edge_mod.h>
32 #include <class_module.h>
33 #include <class_track.h>
34 #include <class_zone.h>
35 #include <cstdio>
36 #include <vector>
37 
38 #include <ki_exception.h>
39 #include <reporter.h>
40 
42 
43 static double iu2hyp( double iu )
44 {
45  return iu / 1e9 / 0.0254;
46 }
47 
48 
49 class HYPERLYNX_EXPORTER;
50 
52 {
53 public:
54  friend class HYPERLYNX_EXPORTER;
55 
56  HYPERLYNX_PAD_STACK( BOARD* aBoard, const D_PAD* aPad );
57  HYPERLYNX_PAD_STACK( BOARD* aBoard, const VIA* aVia );
59 
60  bool isThrough() const
61  {
63  }
64 
65  bool operator==( const HYPERLYNX_PAD_STACK& other ) const
66  {
67  if( m_shape != other.m_shape )
68  return false;
69 
70  if( m_type != other.m_type )
71  return false;
72 
73  if( isThrough() && other.isThrough() && m_drill != other.m_drill )
74  return false;
75 
76  if( m_sx != other.m_sx )
77  return false;
78 
79  if( m_sy != other.m_sy )
80  return false;
81 
82  if( m_layers != other.m_layers )
83  return false;
84 
85  if( m_angle != other.m_angle )
86  return false;
87 
88  return true;
89  }
90 
91  bool isSMD() const
92  {
93  return m_type == PAD_ATTRIB_SMD;
94  }
95 
97  {
98  for( auto l : LSET::AllCuMask().Seq() )
99  {
100  if( m_layers[l] )
101  return l;
102  }
103  return F_Cu;
104  }
105 
106  void SetId( int id )
107  {
108  m_id = id;
109  }
110 
111  int GetId() const
112  {
113  return m_id;
114  }
115 
117  {
118  switch( m_shape )
119  {
120  case PAD_SHAPE_CIRCLE:
121  case PAD_SHAPE_OVAL:
122  case PAD_SHAPE_ROUNDRECT:
123  case PAD_SHAPE_RECT: return true;
124  default: return false;
125  }
126  }
127 
128  bool isEmpty() const
129  {
130  LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
131  LSET outLayers = m_layers & layerMask;
132 
133  return outLayers.none();
134  }
135 
136 private:
138  int m_id;
139  int m_drill;
141  int m_sx, m_sy;
142  double m_angle;
145 };
146 
147 
149 {
150 public:
152  {
153  }
154 
156 
157  virtual bool Run() override;
158 
159 private:
161  {
162  for( auto p : m_padStacks )
163  {
164  if( *p == stack )
165  return p;
166  }
167 
168  stack.SetId( m_padStacks.size() );
169  m_padStacks.push_back( new HYPERLYNX_PAD_STACK( stack ) );
170 
171  return m_padStacks.back();
172  }
173 
174  const std::string formatPadShape( HYPERLYNX_PAD_STACK& aStack )
175  {
176  int shapeId = 0;
177  char buf[1024];
178 
179  switch( aStack.m_shape )
180  {
181  case PAD_SHAPE_CIRCLE:
182  case PAD_SHAPE_OVAL: shapeId = 0; break;
183  case PAD_SHAPE_ROUNDRECT: shapeId = 2; break;
184  case PAD_SHAPE_RECT: shapeId = 1; break;
185  default:
186  shapeId = 0;
187 
188  if( m_reporter )
189  {
191  _( "File contains pad shapes that are not supported by the Hyperlynx exporter\n"
192  "(Supported shapes are oval, rectangle, circle.)\n"
193  "They have been exported as oval pads." ),
195  }
196  break;
197  }
198 
199  snprintf( buf, sizeof( buf ), "%d, %.9f, %.9f, %.1f, M", shapeId,
200  iu2hyp( aStack.m_sx ),
201  iu2hyp( aStack.m_sy ),
202  aStack.m_angle );
203 
204  return buf;
205  }
206 
207  bool generateHeaders();
208  bool writeBoardInfo();
209  bool writeStackupInfo();
210  bool writeDevices();
211  bool writePadStacks();
212  bool writeNets();
213  bool writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects );
214 
215 
217 
218  const std::vector<BOARD_ITEM*> collectNetObjects( int netcode );
219 
220  std::vector<HYPERLYNX_PAD_STACK*> m_padStacks;
221  std::map<BOARD_ITEM*, HYPERLYNX_PAD_STACK*> m_padMap;
222 
223 
224  std::shared_ptr<FILE_OUTPUTFORMATTER> m_out;
225  int m_polyId;
226 };
227 
228 
230 {
231  m_board = aBoard;
232  m_sx = aPad->GetSize().x;
233  m_sy = aPad->GetSize().y;
234  m_angle = 180.0 - ( aPad->GetOrientation() / 10.0 );
235 
236  if( m_angle < 0.0 )
237  {
238  m_angle += 360.0;
239  }
240 
241  m_layers = aPad->GetLayerSet();
242  m_drill = aPad->GetDrillSize().x;
243  m_shape = aPad->GetShape();
245  m_id = 0;
246 }
247 
248 
250 {
251  m_board = aBoard;
252  m_sx = aVia->GetWidth();
253  m_sy = aVia->GetWidth();
254  m_angle = 0;
256  m_drill = aVia->GetDrillValue();
259  m_id = 0;
260 }
261 
262 
264 {
265  m_out->Print( 0, "{VERSION=2.14}\n" );
266  m_out->Print( 0, "{UNITS=ENGLISH LENGTH}\n\n" );
267  return true;
268 }
269 
270 
272 {
273  LSET layerMask = LSET::AllCuMask() & m_board->GetEnabledLayers();
274  LSET outLayers = aStack.m_layers & layerMask;
275 
276  if( outLayers.none() )
277  return;
278 
279  m_out->Print( 0, "{PADSTACK=%d, %.9f\n", aStack.m_id, iu2hyp( aStack.m_drill ) );
280 
281  if( outLayers == layerMask )
282  {
283  m_out->Print( 1, "(\"%s\", %s)\n", "MDEF", formatPadShape( aStack ).c_str() );
284  }
285  else
286  {
287  for( auto l : outLayers.Seq() )
288  {
289  m_out->Print( 1, "(\"%s\", %s)\n", (const char*) m_board->GetLayerName( l ).c_str(),
290  formatPadShape( aStack ).c_str() );
291  }
292  }
293 
294  m_out->Print( 0, "}\n\n" );
295 }
296 
297 
299 {
300  SHAPE_POLY_SET outlines;
301  wxString errText;
302  wxPoint errLoc;
303 
304  m_out->Print( 0, "{BOARD \"%s\"\n", (const char*) m_board->GetFileName().c_str() );
305 
306  if( !m_board->GetBoardPolygonOutlines( outlines, &errText, &errLoc ) )
307  {
308  return false;
309  }
310 
311  for( int o = 0; o < outlines.OutlineCount(); o++ )
312  {
313  const auto& outl = outlines.COutline( o );
314 
315  for( int i = 0; i < outl.SegmentCount(); i++ )
316  {
317  const auto& s = outl.CSegment( i );
318  m_out->Print( 1, "(PERIMETER_SEGMENT X1=%.9f Y1=%.9f X2=%.9f Y2=%.9f)\n",
319  iu2hyp( s.A.x ), iu2hyp( s.A.y ), iu2hyp( s.B.x ), iu2hyp( s.B.y ) );
320  }
321  }
322 
323  m_out->Print( 0, "}\n\n" );
324 
325  return true;
326 }
327 
328 
330 {
331  auto layers = m_board->GetDesignSettings().GetEnabledLayers().CuStack();
332 
333  m_out->Print( 0, "{STACKUP\n" );
334 
335  for( auto l : layers )
336  {
337  const auto name = m_board->GetLayerName( l );
338 
339  m_out->Print( 1, "(SIGNAL T=0.002284 P=0.000000 C=1.724e-8 L=\"%s\" M=COPPER)\n",
340  (const char*) name.c_str() );
341 
342  if( l != B_Cu )
343  {
344  m_out->Print( 1, "(DIELECTRIC T=0.007087 C=3.660000 L=\"DE_%s\" M=FR4)\n",
345  (const char*) name.c_str() );
346  }
347  }
348  m_out->Print( 0, "}\n\n" );
349 
350  return true;
351 }
352 
353 
355 {
356  m_out->Print( 0, "{DEVICES\n" );
357 
358  for( auto mod : m_board->Modules() )
359  {
360  wxString ref = mod->GetReference();
361  auto layerName = m_board->GetLayerName( mod->GetLayer() );
362 
363  if( ref.IsEmpty() )
364  ref = "EMPTY";
365 
366  m_out->Print( 1, "(? REF=\"%s\" L=\"%s\")\n", (const char*) ref.c_str(),
367  (const char*) layerName.c_str() );
368  }
369  m_out->Print( 0, "}\n\n" );
370 
371  return true;
372 }
373 
374 
376 {
377  for( auto mod : m_board->Modules() )
378  {
379  for( auto pad : mod->Pads() )
380  {
381  auto ps = addPadStack( HYPERLYNX_PAD_STACK( m_board, pad ) );
382  m_padMap[pad] = ps;
383  }
384  }
385 
386  for( auto trk : m_board->Tracks() )
387  {
388  if( VIA* via = dyn_cast<VIA*>( trk ) )
389  {
390  auto ps = addPadStack( HYPERLYNX_PAD_STACK( m_board, via ) );
391  m_padMap[via] = ps;
392  }
393  }
394 
395  for( auto pstack : m_padStacks )
396  writeSinglePadStack( *pstack );
397 
398  return true;
399 }
400 
401 
402 bool HYPERLYNX_EXPORTER::writeNetObjects( const std::vector<BOARD_ITEM*>& aObjects )
403 {
404  for( auto item : aObjects )
405  {
406  if( D_PAD* pad = dyn_cast<D_PAD*>( item ) )
407  {
408  auto pstackIter = m_padMap.find( pad );
409 
410  if( pstackIter != m_padMap.end() )
411  {
412  wxString ref = pad->GetParent()->GetReference();
413 
414  if( ref.IsEmpty() )
415  ref = "EMPTY";
416 
417  auto padName = pad->GetName();
418 
419  if( padName.IsEmpty() )
420  padName = "1";
421 
422 
423  m_out->Print( 1, "(PIN X=%.10f Y=%.10f R=\"%s.%s\" P=%d)\n",
424  iu2hyp( pad->GetPosition().x ), iu2hyp( pad->GetPosition().y ),
425  (const char*) ref.c_str(), (const char*) padName.c_str(),
426  pstackIter->second->GetId() );
427  }
428  }
429  else if( VIA* via = dyn_cast<VIA*>( item ) )
430  {
431  auto pstackIter = m_padMap.find( via );
432 
433  if( pstackIter != m_padMap.end() )
434  {
435  m_out->Print( 1, "(VIA X=%.10f Y=%.10f P=%d)\n", iu2hyp( via->GetPosition().x ),
436  iu2hyp( via->GetPosition().y ), pstackIter->second->GetId() );
437  }
438  }
439  else if( TRACK* track = dyn_cast<TRACK*>( item ) )
440  {
441  const auto layerName = m_board->GetLayerName( track->GetLayer() );
442 
443  m_out->Print( 1, "(SEG X1=%.10f Y1=%.10f X2=%.10f Y2=%.10f W=%.10f L=\"%s\")\n",
444  iu2hyp( track->GetStart().x ), iu2hyp( track->GetStart().y ),
445  iu2hyp( track->GetEnd().x ), iu2hyp( track->GetEnd().y ),
446  iu2hyp( track->GetWidth() ), (const char*) layerName.c_str() );
447  }
448  else if( ZONE_CONTAINER* zone = dyn_cast<ZONE_CONTAINER*>( item ) )
449  {
450  const auto layerName = m_board->GetLayerName( zone->GetLayer() );
451  SHAPE_POLY_SET filledShape = zone->GetFilledPolysList();
452 
453  filledShape.Simplify( SHAPE_POLY_SET::PM_FAST );
454 
455  for( int i = 0; i < filledShape.OutlineCount(); i++ )
456  {
457  const auto& outl = filledShape.COutline( i );
458 
459  auto p0 = outl.CPoint( 0 );
460  m_out->Print( 1, "{POLYGON T=POUR L=\"%s\" ID=%d X=%.10f Y=%.10f\n",
461  (const char*) layerName.c_str(), m_polyId, iu2hyp( p0.x ), iu2hyp( p0.y ) );
462 
463  for( int v = 0; v < outl.PointCount(); v++ )
464  {
465  m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( outl.CPoint( v ).x ),
466  iu2hyp( outl.CPoint( v ).y ) );
467  }
468 
469  m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( p0.x ), iu2hyp( p0.y ) );
470  m_out->Print( 1, "}\n" );
471 
472  for( int h = 0; h < filledShape.HoleCount( i ); h++ )
473  {
474  const auto& holeShape = filledShape.CHole( i, h );
475  auto ph0 = holeShape.CPoint( 0 );
476 
477  m_out->Print( 1, "{POLYVOID ID=%d X=%.10f Y=%.10f\n", m_polyId, iu2hyp( ph0.x ),
478  iu2hyp( ph0.y ) );
479 
480  for( int v = 0; v < holeShape.PointCount(); v++ )
481  {
482  m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n",
483  iu2hyp( holeShape.CPoint( v ).x ),
484  iu2hyp( holeShape.CPoint( v ).y ) );
485  }
486 
487  m_out->Print( 2, "(LINE X=%.10f Y=%.10f)\n", iu2hyp( ph0.x ), iu2hyp( ph0.y ) );
488  m_out->Print( 1, "}\n" );
489  }
490 
491  m_polyId++;
492  }
493  }
494  }
495 
496  return true;
497 }
498 
499 
500 const std::vector<BOARD_ITEM*> HYPERLYNX_EXPORTER::collectNetObjects( int netcode )
501 {
502  std::vector<BOARD_ITEM*> rv;
503 
504  auto check = [&]( BOARD_CONNECTED_ITEM* item ) -> bool {
505  if( ( item->GetLayerSet() & LSET::AllCuMask() ).none() )
506  return false;
507  if( item->GetNetCode() == netcode || ( netcode < 0 && item->GetNetCode() <= 0 ) )
508  return true;
509  return false;
510  };
511 
512  for( auto mod : m_board->Modules() )
513  {
514  for( auto pad : mod->Pads() )
515  {
516  if( check( pad ) )
517  rv.push_back( pad );
518  }
519  }
520 
521  for( auto item : m_board->Tracks() )
522  if( check( item ) )
523  rv.push_back( item );
524 
525  for( int i = 0; i < m_board->GetAreaCount(); i++ )
526  {
527  auto zone = m_board->GetArea( i );
528  if( check( zone ) )
529  rv.push_back( zone );
530  }
531  return rv;
532 }
533 
534 
536 {
537  m_polyId = 1;
538 
539  for( const auto netInfo : m_board->GetNetInfo() )
540  {
541  int netcode = netInfo->GetNet();
542  bool isNullNet = netInfo->GetNet() <= 0 || netInfo->GetNetname().IsEmpty();
543 
544  if( isNullNet )
545  continue;
546 
547  auto netObjects = collectNetObjects( netcode );
548 
549  if( netObjects.size() )
550  {
551  m_out->Print( 0, "{NET=\"%s\"\n", (const char*) netInfo->GetNetname().c_str() );
552  writeNetObjects( netObjects );
553  m_out->Print( 0, "}\n\n" );
554  }
555  }
556 
557  auto nullNetObjects = collectNetObjects( -1 );
558 
559  int idx = 0;
560 
561  for( auto item : nullNetObjects )
562  {
563  m_out->Print( 0, "{NET=\"EmptyNet%d\"\n", idx );
564  writeNetObjects( { item } );
565  m_out->Print( 0, "}\n\n" );
566  idx++;
567  }
568 
569  return true;
570 }
571 
572 
574 {
575  LOCALE_IO toggle; // toggles on, then off, the C locale.
576 
577  try
578  {
579  m_out.reset( new FILE_OUTPUTFORMATTER( m_outputFilePath.GetFullPath() ) );
580 
581  generateHeaders();
582  writeBoardInfo();
584  writeDevices();
585  writePadStacks();
586  writeNets();
587  }
588  catch( IO_ERROR& )
589  {
590  return false;
591  }
592 
593  return true;
594 }
595 
596 
597 bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath )
598 {
599  HYPERLYNX_EXPORTER exporter;
600  exporter.SetBoard( aBoard );
601  exporter.SetOutputFilename( aPath );
602  return exporter.Run();
603 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Function AllCuMask returns a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:710
ZONE_CONTAINER handles a list of polygons defining a copper zone.
Definition: class_zone.h:60
bool writeNetObjects(const std::vector< BOARD_ITEM * > &aObjects)
LSEQ CuStack() const
Function CuStack returns a sequence of copper layers in starting from the front/top and extending to ...
Definition: lset.cpp:155
bool ExportBoardToHyperlynx(BOARD *aBoard, const wxFileName &aPath)
int OutlineCount() const
Returns the number of outlines in the set
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Function GetLayerName returns the name of a layer.
like PAD_STANDARD, but not plated mechanical use only, no connection allowed
Definition: pad_shapes.h:66
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: common.h:214
PAD_ATTR_T
Enum PAD_ATTR_T is the set of pad shapes, used with D_PAD::{Set,Get}Attribute() The double name is fo...
Definition: pad_shapes.h:59
bool operator==(const HYPERLYNX_PAD_STACK &other) const
int IsSupportedByExporter() const
Classes BOARD_ITEM and BOARD_CONNECTED_ITEM.
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:62
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Function GetDesignSettings.
Definition: class_board.h:542
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
LSET GetEnabledLayers() const
Function GetEnabledLayers is a proxy function that calls the corresponding function in m_BoardSetting...
LSEQ Seq(const PCB_LAYER_ID *aWishListSequence, unsigned aCount) const
Function Seq returns an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:377
const wxString & GetFileName() const
Definition: class_board.h:245
BOARD_CONNECTED_ITEM is a base class derived from BOARD_ITEM for items that can be connected and have...
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Function Report is a pure virtual function to override in the derived object.
PAD_SHAPE_T
Enum PAD_SHAPE_T is the set of pad shapes, used with D_PAD::{Set,Get}Shape()
Definition: pad_shapes.h:31
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
std::vector< HYPERLYNX_PAD_STACK * > m_padStacks
void SetOutputFilename(const wxFileName &aPath)
const VECTOR2I & CPoint(int aIndex) const
Function Point()
HYPERLYNX_PAD_STACK * addPadStack(HYPERLYNX_PAD_STACK stack)
PCB_LAYER_ID
A quick note on layer IDs:
int GetAreaCount() const
Function GetAreaCount.
Definition: class_board.h:927
LSET is a set of PCB_LAYER_IDs.
int GetDrillValue() const
Function GetDrillValue "calculates" the drill value for vias (m-Drill if > 0, or default drill value ...
MODULES & Modules()
Definition: class_board.h:256
SHAPE_POLY_SET.
std::map< BOARD_ITEM *, HYPERLYNX_PAD_STACK * > m_padMap
LSET GetLayerSet() const override
Function GetLayerSet returns a "layer mask", which is a bitmap of all layers on which the TRACK segme...
Definition: class_pad.h:435
HYPERLYNX_PAD_STACK(BOARD *aBoard, const D_PAD *aPad)
void Simplify(POLYGON_MODE aFastMode)
Simplifies the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) For aFast...
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, wxString *aErrorText=nullptr, wxPoint *aErrorLocation=nullptr)
Function GetBoardPolygonOutlines Extracts the board outlines and build a closed polygon from lines,...
NETINFO_LIST & GetNetInfo()
Definition: class_board.h:731
int HoleCount(int aOutline) const
Returns the number of holes in a given outline
const std::string formatPadShape(HYPERLYNX_PAD_STACK &aStack)
PCB_LAYER_ID getSMDLayer() const
void writeSinglePadStack(HYPERLYNX_PAD_STACK &aStack)
int GetWidth() const
Definition: class_track.h:112
Class to handle a graphic segment.
const char * name
Definition: DXF_plotter.cpp:60
const SEG CSegment(int aIndex) const
Function CSegment()
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:181
LSET GetEnabledLayers() const
Function GetEnabledLayers returns a bit-mask of all the layers that are enabled.
void SetBoard(BOARD *aBoard)
#define _(s)
Definition: 3d_actions.cpp:33
double GetOrientation() const
Function GetOrientation returns the rotation angle of the pad in tenths of degrees,...
Definition: class_pad.h:411
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const wxSize & GetDrillSize() const
Definition: class_pad.h:291
const std::vector< BOARD_ITEM * > collectNetObjects(int netcode)
std::shared_ptr< FILE_OUTPUTFORMATTER > m_out
virtual bool Run() override
FILE_OUTPUTFORMATTER may be used for text file output.
Definition: richio.h:492
PAD_SHAPE_T GetShape() const
Function GetShape.
Definition: class_pad.h:222
ZONE_CONTAINER * GetArea(int index) const
Function GetArea returns the Area (Zone Container) at a given index.
Definition: class_board.h:892
const wxSize & GetSize() const
Definition: class_pad.h:285
EDGE_MODULE class definition.
Struct IO_ERROR is a class used to hold an error message and may be used when throwing exceptions con...
Definition: ki_exception.h:76
static double iu2hyp(double iu)
TRACKS & Tracks()
Definition: class_board.h:247