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.

3283 lines
113 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. /**
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or (at your
  9. * option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "pcb_io_ipc2581.h"
  20. #include <base_units.h>
  21. #include <bezier_curves.h>
  22. #include <board.h>
  23. #include <board_design_settings.h>
  24. #include <board_stackup_manager/stackup_predefined_prms.h>
  25. #include <build_version.h>
  26. #include <callback_gal.h>
  27. #include <connectivity/connectivity_data.h>
  28. #include <connectivity/connectivity_algo.h>
  29. #include <convert_basic_shapes_to_polygon.h>
  30. #include <font/font.h>
  31. #include <hash_eda.h>
  32. #include <pcb_dimension.h>
  33. #include <pcb_textbox.h>
  34. #include <pgm_base.h>
  35. #include <progress_reporter.h>
  36. #include <settings/settings_manager.h>
  37. #include <wx_fstream_progress.h>
  38. #include <geometry/shape_line_chain.h>
  39. #include <geometry/shape_poly_set.h>
  40. #include <geometry/shape_segment.h>
  41. #include <wx/log.h>
  42. #include <wx/numformatter.h>
  43. #include <wx/xml/xml.h>
  44. /**
  45. * Flag to enable IPC-2581 debugging output.
  46. *
  47. * @ingroup trace_env_vars
  48. */
  49. static const wxChar traceIpc2581[] = wxT( "KICAD_IPC_2581" );
  50. PCB_IO_IPC2581::~PCB_IO_IPC2581()
  51. {
  52. clearLoadedFootprints();
  53. }
  54. void PCB_IO_IPC2581::clearLoadedFootprints()
  55. {
  56. for( FOOTPRINT* fp : m_loaded_footprints )
  57. delete fp;
  58. m_loaded_footprints.clear();
  59. }
  60. std::vector<FOOTPRINT*> PCB_IO_IPC2581::GetImportedCachedLibraryFootprints()
  61. {
  62. std::vector<FOOTPRINT*> retval;
  63. for( FOOTPRINT* fp : m_loaded_footprints )
  64. retval.push_back( static_cast<FOOTPRINT*>( fp->Clone() ) );
  65. return retval;
  66. }
  67. void PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, wxXmlNode* aNode )
  68. {
  69. // insertNode places the node at the start of the list of children
  70. if( aParent->GetChildren() )
  71. aNode->SetNext( aParent->GetChildren() );
  72. else
  73. aNode->SetNext( nullptr );
  74. aParent->SetChildren( aNode );
  75. aNode->SetParent( aParent );
  76. m_total_bytes += 2 * aNode->GetName().size() + 5;
  77. }
  78. void PCB_IO_IPC2581::insertNodeAfter( wxXmlNode* aPrev, wxXmlNode* aNode )
  79. {
  80. // insertNode places the node directly after aPrev
  81. aNode->SetNext( aPrev->GetNext() );
  82. aPrev->SetNext( aNode );
  83. aNode->SetParent( aPrev->GetParent() );
  84. m_total_bytes += 2 * aNode->GetName().size() + 5;
  85. }
  86. wxXmlNode* PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, const wxString& aName )
  87. {
  88. // Opening tag, closing tag, brackets and the closing slash
  89. m_total_bytes += 2 * aName.size() + 5;
  90. wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
  91. insertNode( aParent, node );
  92. return node;
  93. }
  94. void PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, wxXmlNode* aNode )
  95. {
  96. // AddChild iterates through the entire list of children, so we want to avoid
  97. // that if possible. When we share a parent and our next sibling is null,
  98. // then we are the last child and can just append to the end of the list.
  99. static wxXmlNode* lastNode = nullptr;
  100. if( lastNode && lastNode->GetParent() == aParent && lastNode->GetNext() == nullptr )
  101. {
  102. aNode->SetParent( aParent );
  103. lastNode->SetNext( aNode );
  104. }
  105. else
  106. {
  107. aParent->AddChild( aNode );
  108. }
  109. lastNode = aNode;
  110. // Opening tag, closing tag, brackets and the closing slash
  111. m_total_bytes += 2 * aNode->GetName().size() + 5;
  112. }
  113. wxXmlNode* PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, const wxString& aName )
  114. {
  115. wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
  116. appendNode( aParent, node );
  117. return node;
  118. }
  119. wxString PCB_IO_IPC2581::genString( const wxString& aStr, const char* aPrefix ) const
  120. {
  121. // Build a key using the prefix and original string so that repeated calls for the same
  122. // element return the same generated name.
  123. wxString key = aPrefix ? wxString( aPrefix ) + wxT( ":" ) + aStr : aStr;
  124. auto it = m_generated_names.find( key );
  125. if( it != m_generated_names.end() )
  126. return it->second;
  127. wxString str;
  128. if( m_version == 'C' )
  129. {
  130. str = aStr;
  131. str.Replace( wxT( ":" ), wxT( "_" ) );
  132. }
  133. else
  134. {
  135. for( wxString::const_iterator iter = aStr.begin(); iter != aStr.end(); ++iter )
  136. {
  137. if( !m_acceptable_chars.count( *iter ) )
  138. str.Append( '_' );
  139. else
  140. str.Append( *iter );
  141. }
  142. }
  143. wxString base = str;
  144. wxString name = base;
  145. int suffix = 1;
  146. while( m_element_names.count( name ) )
  147. name = wxString::Format( "%s_%d", base, suffix++ );
  148. m_element_names.insert( name );
  149. m_generated_names[key] = name;
  150. return name;
  151. }
  152. wxString PCB_IO_IPC2581::genLayerString( PCB_LAYER_ID aLayer, const char* aPrefix ) const
  153. {
  154. return genString( m_board->GetLayerName( aLayer ), aPrefix );
  155. }
  156. wxString PCB_IO_IPC2581::genLayersString( PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom,
  157. const char* aPrefix ) const
  158. {
  159. return genString( wxString::Format( wxS( "%s_%s" ),
  160. m_board->GetLayerName( aTop ),
  161. m_board->GetLayerName( aBottom ) ), aPrefix );
  162. }
  163. wxString PCB_IO_IPC2581::pinName( const PAD* aPad ) const
  164. {
  165. wxString name = aPad->GetNumber();
  166. FOOTPRINT* fp = aPad->GetParentFootprint();
  167. size_t ii = 0;
  168. if( name.empty() && fp )
  169. {
  170. for( ii = 0; ii < fp->GetPadCount(); ++ii )
  171. {
  172. if( fp->Pads()[ii] == aPad )
  173. break;
  174. }
  175. }
  176. // Pins are required to have names, so if our pad doesn't have a name, we need to
  177. // generate one that is unique
  178. if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
  179. name = wxString::Format( "NPTH%zu", ii );
  180. else if( name.empty() )
  181. name = wxString::Format( "PAD%zu", ii );
  182. return genString( name, "PIN" );
  183. }
  184. wxString PCB_IO_IPC2581::componentName( FOOTPRINT* aFootprint )
  185. {
  186. auto tryInsert =
  187. [&]( const wxString& aName )
  188. {
  189. if( m_footprint_refdes_dict.count( aName ) )
  190. {
  191. if( m_footprint_refdes_dict.at( aName ) != aFootprint )
  192. return false;
  193. }
  194. else
  195. {
  196. m_footprint_refdes_dict.insert( { aName, aFootprint } );
  197. }
  198. return true;
  199. };
  200. if( m_footprint_refdes_reverse_dict.count( aFootprint ) )
  201. return m_footprint_refdes_reverse_dict.at( aFootprint );
  202. wxString baseName = genString( aFootprint->GetReference(), "CMP" );
  203. wxString name = baseName;
  204. int suffix = 1;
  205. while( !tryInsert( name ) )
  206. name = wxString::Format( "%s_%d", baseName, suffix++ );
  207. m_footprint_refdes_reverse_dict[aFootprint] = name;
  208. return name;
  209. }
  210. wxString PCB_IO_IPC2581::floatVal( double aVal, int aSigFig ) const
  211. {
  212. wxString str = wxString::FromCDouble( aVal, aSigFig == -1 ? m_sigfig : aSigFig );
  213. // Remove all but the last trailing zeros from str
  214. while( str.EndsWith( wxT( "00" ) ) )
  215. str.RemoveLast();
  216. // We don't want to output -0.0 as this value is just 0 for fabs
  217. if( str == wxT( "-0.0" ) )
  218. return wxT( "0.0" );
  219. return str;
  220. }
  221. void PCB_IO_IPC2581::addXY( wxXmlNode* aNode, const VECTOR2I& aVec, const char* aXName,
  222. const char* aYName )
  223. {
  224. if( aXName )
  225. addAttribute( aNode, aXName, floatVal( m_scale * aVec.x ) );
  226. else
  227. addAttribute( aNode, "x", floatVal( m_scale * aVec.x ) );
  228. if( aYName )
  229. addAttribute( aNode, aYName, floatVal( -m_scale * aVec.y ) );
  230. else
  231. addAttribute( aNode, "y", floatVal( -m_scale * aVec.y ) );
  232. }
  233. void PCB_IO_IPC2581::addAttribute( wxXmlNode* aNode, const wxString& aName, const wxString& aValue )
  234. {
  235. m_total_bytes += aName.size() + aValue.size() + 4;
  236. aNode->AddAttribute( aName, aValue );
  237. }
  238. wxXmlNode* PCB_IO_IPC2581::generateXmlHeader()
  239. {
  240. wxXmlNode* xmlHeaderNode = new wxXmlNode(wxXML_ELEMENT_NODE, "IPC-2581");
  241. addAttribute( xmlHeaderNode, "revision", m_version);
  242. addAttribute( xmlHeaderNode, "xmlns", "http://webstds.ipc.org/2581");
  243. addAttribute( xmlHeaderNode, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
  244. addAttribute( xmlHeaderNode, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
  245. if( m_version == 'B' )
  246. {
  247. addAttribute( xmlHeaderNode, "xsi:schemaLocation",
  248. "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581B1.xsd" );
  249. }
  250. else
  251. {
  252. addAttribute( xmlHeaderNode, "xsi:schemaLocation",
  253. "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581C.xsd" );
  254. }
  255. m_xml_doc->SetRoot( xmlHeaderNode );
  256. return xmlHeaderNode;
  257. }
  258. wxXmlNode* PCB_IO_IPC2581::generateContentSection()
  259. {
  260. if( m_progressReporter )
  261. m_progressReporter->AdvancePhase( _( "Generating content section" ) );
  262. wxXmlNode* contentNode = appendNode( m_xml_root, "Content" );
  263. addAttribute( contentNode, "roleRef", "Owner" );
  264. wxXmlNode* node = appendNode( contentNode, "FunctionMode" );
  265. addAttribute( node, "mode", "ASSEMBLY" );
  266. // This element is deprecated in revision 'C' and later
  267. if( m_version == 'B' )
  268. addAttribute( node, "level", "3" );
  269. node = appendNode( contentNode, "StepRef" );
  270. wxFileName fn( m_board->GetFileName() );
  271. addAttribute( node, "name", genString( fn.GetName(), "BOARD" ) );
  272. wxXmlNode* color_node = generateContentStackup( contentNode );
  273. if( m_version == 'C' )
  274. {
  275. contentNode->AddChild( color_node );
  276. m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
  277. addAttribute( m_line_node, "units", m_units_str );
  278. wxXmlNode* fillNode = appendNode( contentNode, "DictionaryFillDesc" );
  279. addAttribute( fillNode, "units", m_units_str );
  280. m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
  281. addAttribute( m_shape_std_node, "units", m_units_str );
  282. m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
  283. addAttribute( m_shape_user_node, "units", m_units_str );
  284. }
  285. else
  286. {
  287. m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
  288. addAttribute( m_shape_std_node, "units", m_units_str );
  289. m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
  290. addAttribute( m_shape_user_node, "units", m_units_str );
  291. m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
  292. addAttribute( m_line_node, "units", m_units_str );
  293. contentNode->AddChild( color_node );
  294. }
  295. return contentNode;
  296. }
  297. void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, double aX, double aY )
  298. {
  299. wxXmlNode* location_node = appendNode( aNode, "Location" );
  300. addXY( location_node, VECTOR2I( aX, aY ) );
  301. }
  302. void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PAD& aPad, bool aRelative )
  303. {
  304. VECTOR2D pos{};
  305. if( aRelative )
  306. pos = aPad.GetFPRelativePosition();
  307. else
  308. pos = aPad.GetPosition();
  309. if( aPad.GetOffset( PADSTACK::ALL_LAYERS ).x != 0 || aPad.GetOffset( PADSTACK::ALL_LAYERS ).y != 0 )
  310. pos += aPad.GetOffset( PADSTACK::ALL_LAYERS );
  311. addLocationNode( aNode, pos.x, pos.y );
  312. }
  313. void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PCB_SHAPE& aShape )
  314. {
  315. VECTOR2D pos{};
  316. switch( aShape.GetShape() )
  317. {
  318. // Rectangles in KiCad are mapped by their corner while IPC2581 uses the center
  319. case SHAPE_T::RECTANGLE:
  320. pos = aShape.GetPosition()
  321. + VECTOR2I( aShape.GetRectangleWidth() / 2.0, aShape.GetRectangleHeight() / 2.0 );
  322. break;
  323. // Both KiCad and IPC2581 use the center of the circle
  324. case SHAPE_T::CIRCLE:
  325. pos = aShape.GetPosition();
  326. break;
  327. // KiCad uses the exact points on the board, so we want the reference location to be 0,0
  328. case SHAPE_T::POLY:
  329. case SHAPE_T::BEZIER:
  330. case SHAPE_T::SEGMENT:
  331. case SHAPE_T::ARC:
  332. pos = VECTOR2D( 0, 0 );
  333. break;
  334. case SHAPE_T::UNDEFINED:
  335. wxFAIL;
  336. }
  337. addLocationNode( aNode, pos.x, pos.y );
  338. }
  339. size_t PCB_IO_IPC2581::lineHash( int aWidth, LINE_STYLE aDashType )
  340. {
  341. size_t hash = hash_val( aWidth );
  342. hash_combine( hash, aDashType );
  343. return hash;
  344. }
  345. size_t PCB_IO_IPC2581::shapeHash( const PCB_SHAPE& aShape )
  346. {
  347. return hash_fp_item( &aShape, HASH_POS | REL_COORD );
  348. }
  349. wxXmlNode* PCB_IO_IPC2581::generateContentStackup( wxXmlNode* aContentNode )
  350. {
  351. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  352. BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
  353. stackup.SynchronizeWithBoard( &bds );
  354. wxXmlNode* color_node = new wxXmlNode( wxXML_ELEMENT_NODE, "DictionaryColor" );
  355. for( BOARD_STACKUP_ITEM* item: stackup.GetList() )
  356. {
  357. wxString layer_name = item->GetLayerName();
  358. int sub_layer_count = 1;
  359. if( layer_name.empty() )
  360. layer_name = m_board->GetLayerName( item->GetBrdLayerId() );
  361. layer_name = genString( layer_name, "LAYER" );
  362. if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  363. {
  364. layer_name = genString( wxString::Format( "DIELECTRIC_%d", item->GetDielectricLayerId() ),
  365. "LAYER" );
  366. sub_layer_count = item->GetSublayersCount();
  367. }
  368. else
  369. {
  370. m_layer_name_map.emplace( item->GetBrdLayerId(), layer_name );
  371. }
  372. for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ )
  373. {
  374. wxString sub_layer_name = layer_name;
  375. if( sub_idx > 0 )
  376. sub_layer_name += wxString::Format( "_%d", sub_idx );
  377. wxXmlNode* node = appendNode( aContentNode, "LayerRef" );
  378. addAttribute( node, "name", sub_layer_name );
  379. if( !IsPrmSpecified( item->GetColor( sub_idx ) ) )
  380. continue;
  381. wxXmlNode* entry_color = appendNode( color_node, "EntryColor" );
  382. addAttribute( entry_color, "id", genString( sub_layer_name, "COLOR" ) );
  383. wxXmlNode* color = appendNode( entry_color, "Color" );
  384. wxString colorName = item->GetColor( sub_idx );
  385. if( colorName.StartsWith( wxT( "#" ) ) ) // This is a user defined color,
  386. // not in standard color list.
  387. {
  388. COLOR4D layer_color( colorName );
  389. addAttribute( color, "r", wxString::Format( "%d",
  390. KiROUND( layer_color.r * 255 ) ) );
  391. addAttribute( color, "g", wxString::Format( "%d",
  392. KiROUND( layer_color.g * 255 ) ) );
  393. addAttribute( color, "b", wxString::Format( "%d",
  394. KiROUND( layer_color.b * 255 ) ) );
  395. }
  396. else
  397. {
  398. for( const FAB_LAYER_COLOR& fab_color : GetStandardColors( item->GetType() ) )
  399. {
  400. if( fab_color.GetName() == colorName )
  401. {
  402. addAttribute( color, "r", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).r * 255 ) ) );
  403. addAttribute( color, "g", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).g * 255 ) ) );
  404. addAttribute( color, "b", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).b * 255 ) ) );
  405. break;
  406. }
  407. }
  408. }
  409. }
  410. }
  411. return color_node;
  412. }
  413. void PCB_IO_IPC2581::addFillDesc( wxXmlNode* aNode, FILL_T aFill, bool aForce )
  414. {
  415. if( aFill == FILL_T::FILLED_SHAPE )
  416. {
  417. // By default, we do not fill shapes because FILL is the default value for most.
  418. // But for some outlines, we may need to force a fill.
  419. if( aForce )
  420. {
  421. wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
  422. addAttribute( fillDesc_node, "fillProperty", "FILL" );
  423. }
  424. }
  425. else
  426. {
  427. wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
  428. addAttribute( fillDesc_node, "fillProperty", "HOLLOW" );
  429. }
  430. }
  431. void PCB_IO_IPC2581::addLineDesc( wxXmlNode* aNode, int aWidth, LINE_STYLE aDashType, bool aForce )
  432. {
  433. wxCHECK_RET( aNode, "aNode is null" );
  434. if( aWidth < 0 )
  435. return;
  436. wxXmlNode* entry_node = nullptr;
  437. if( !aForce )
  438. {
  439. size_t hash = lineHash( aWidth, aDashType );
  440. wxString name = wxString::Format( "LINE_%zu", m_line_dict.size() + 1 );
  441. auto[ iter, inserted ] = m_line_dict.emplace( hash, name );
  442. // Either add a new entry or reference an existing one
  443. wxXmlNode* lineDesc_node = appendNode( aNode, "LineDescRef" );
  444. addAttribute( lineDesc_node, "id", iter->second );
  445. if( !inserted )
  446. return;
  447. entry_node = appendNode( m_line_node, "EntryLineDesc" );
  448. addAttribute( entry_node, "id", name );
  449. }
  450. else
  451. {
  452. // Force the LineDesc to be added directly to the parent node
  453. entry_node = aNode;
  454. }
  455. wxXmlNode* line_node = appendNode( entry_node, "LineDesc" );
  456. addAttribute( line_node, "lineWidth", floatVal( m_scale * aWidth ) );
  457. addAttribute( line_node, "lineEnd", "ROUND" );
  458. switch( aDashType )
  459. {
  460. case LINE_STYLE::DOT:
  461. addAttribute( line_node, "lineProperty", "DOTTED" );
  462. break;
  463. case LINE_STYLE::DASH:
  464. addAttribute( line_node, "lineProperty", "DASHED" );
  465. break;
  466. case LINE_STYLE::DASHDOT:
  467. addAttribute( line_node, "lineProperty", "CENTER" );
  468. break;
  469. case LINE_STYLE::DASHDOTDOT:
  470. addAttribute( line_node, "lineProperty", "PHANTOM" );
  471. break;
  472. default:
  473. break;
  474. }
  475. }
  476. void PCB_IO_IPC2581::addKnockoutText( wxXmlNode* aContentNode, PCB_TEXT* aText )
  477. {
  478. SHAPE_POLY_SET finalPoly;
  479. aText->TransformTextToPolySet( finalPoly, 0, ARC_HIGH_DEF, ERROR_INSIDE );
  480. finalPoly.Fracture();
  481. for( int ii = 0; ii < finalPoly.OutlineCount(); ++ii )
  482. addContourNode( aContentNode, finalPoly, ii );
  483. }
  484. void PCB_IO_IPC2581::addText( wxXmlNode* aContentNode, EDA_TEXT* aText,
  485. const KIFONT::METRICS& aFontMetrics )
  486. {
  487. KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
  488. KIFONT::FONT* font = aText->GetDrawFont( nullptr );
  489. TEXT_ATTRIBUTES attrs = aText->GetAttributes();
  490. attrs.m_StrokeWidth = aText->GetEffectiveTextPenWidth();
  491. attrs.m_Angle = aText->GetDrawRotation();
  492. attrs.m_Multiline = false;
  493. wxXmlNode* text_node = appendNode( aContentNode, "UserSpecial" );
  494. std::list<VECTOR2I> pts;
  495. auto push_pts =
  496. [&]()
  497. {
  498. if( pts.size() < 2 )
  499. return;
  500. wxXmlNode* line_node = nullptr;
  501. // Polylines are only allowed for more than 3 points (in version B).
  502. // Otherwise, we have to use a line
  503. if( pts.size() < 3 )
  504. {
  505. line_node = appendNode( text_node, "Line" );
  506. addXY( line_node, pts.front(), "startX", "startY" );
  507. addXY( line_node, pts.back(), "endX", "endY" );
  508. }
  509. else
  510. {
  511. line_node = appendNode( text_node, "Polyline" );
  512. wxXmlNode* point_node = appendNode( line_node, "PolyBegin" );
  513. addXY( point_node, pts.front() );
  514. auto iter = pts.begin();
  515. for( ++iter; iter != pts.end(); ++iter )
  516. {
  517. wxXmlNode* point_node = appendNode( line_node, "PolyStepSegment" );
  518. addXY( point_node, *iter );
  519. }
  520. }
  521. addLineDesc( line_node, attrs.m_StrokeWidth, LINE_STYLE::SOLID );
  522. pts.clear();
  523. };
  524. CALLBACK_GAL callback_gal( empty_opts,
  525. // Stroke callback
  526. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  527. {
  528. if( !pts.empty() )
  529. {
  530. if( aPt1 == pts.back() )
  531. pts.push_back( aPt2 );
  532. else if( aPt2 == pts.front() )
  533. pts.push_front( aPt1 );
  534. else if( aPt1 == pts.front() )
  535. pts.push_front( aPt2 );
  536. else if( aPt2 == pts.back() )
  537. pts.push_back( aPt1 );
  538. else
  539. {
  540. push_pts();
  541. pts.push_back( aPt1 );
  542. pts.push_back( aPt2 );
  543. }
  544. }
  545. else
  546. {
  547. pts.push_back( aPt1 );
  548. pts.push_back( aPt2 );
  549. }
  550. },
  551. // Polygon callback
  552. [&]( const SHAPE_LINE_CHAIN& aPoly )
  553. {
  554. if( aPoly.PointCount() < 3 )
  555. return;
  556. wxXmlNode* outline_node = appendNode( text_node, "Outline" );
  557. wxXmlNode* poly_node = appendNode( outline_node, "Polygon" );
  558. addLineDesc( outline_node, 0, LINE_STYLE::SOLID );
  559. const std::vector<VECTOR2I>& pts = aPoly.CPoints();
  560. wxXmlNode* point_node = appendNode( poly_node, "PolyBegin" );
  561. addXY( point_node, pts.front() );
  562. for( size_t ii = 1; ii < pts.size(); ++ii )
  563. {
  564. wxXmlNode* point_node =
  565. appendNode( poly_node, "PolyStepSegment" );
  566. addXY( point_node, pts[ii] );
  567. }
  568. point_node = appendNode( poly_node, "PolyStepSegment" );
  569. addXY( point_node, pts.front() );
  570. } );
  571. //TODO: handle multiline text
  572. font->Draw( &callback_gal, aText->GetShownText( true ), aText->GetTextPos(), attrs,
  573. aFontMetrics );
  574. if( !pts.empty() )
  575. push_pts();
  576. if( text_node->GetChildren() == nullptr )
  577. {
  578. aContentNode->RemoveChild( text_node );
  579. delete text_node;
  580. }
  581. }
  582. void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAYER_ID aLayer )
  583. {
  584. size_t hash = hash_fp_item( &aPad, 0 );
  585. auto iter = m_std_shape_dict.find( hash );
  586. if( iter != m_std_shape_dict.end() )
  587. {
  588. wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
  589. addAttribute( shape_node, "id", iter->second );
  590. return;
  591. }
  592. int maxError = m_board->GetDesignSettings().m_MaxError;
  593. wxString name;
  594. VECTOR2I expansion{ 0, 0 };
  595. if( LSET( { F_Mask, B_Mask } ).Contains( aLayer ) )
  596. expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion( PADSTACK::ALL_LAYERS );
  597. if( LSET( { F_Paste, B_Paste } ).Contains( aLayer ) )
  598. expansion = 2 * aPad.GetSolderPasteMargin( PADSTACK::ALL_LAYERS );
  599. switch( aPad.GetShape( PADSTACK::ALL_LAYERS ) )
  600. {
  601. case PAD_SHAPE::CIRCLE:
  602. {
  603. name = wxString::Format( "CIRCLE_%zu", m_std_shape_dict.size() + 1 );
  604. m_std_shape_dict.emplace( hash, name );
  605. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  606. addAttribute( entry_node, "id", name );
  607. wxXmlNode* circle_node = appendNode( entry_node, "Circle" );
  608. circle_node->AddAttribute( "diameter",
  609. floatVal( m_scale * ( expansion.x + aPad.GetSizeX() ) ) );
  610. break;
  611. }
  612. case PAD_SHAPE::RECTANGLE:
  613. {
  614. name = wxString::Format( "RECT_%zu", m_std_shape_dict.size() + 1 );
  615. m_std_shape_dict.emplace( hash, name );
  616. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  617. addAttribute( entry_node, "id", name );
  618. wxXmlNode* rect_node = appendNode( entry_node, "RectCenter" );
  619. VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
  620. addAttribute( rect_node, "width", floatVal( m_scale * std::abs( pad_size.x ) ) );
  621. addAttribute( rect_node, "height", floatVal( m_scale * std::abs( pad_size.y ) ) );
  622. break;
  623. }
  624. case PAD_SHAPE::OVAL:
  625. {
  626. name = wxString::Format( "OVAL_%zu", m_std_shape_dict.size() + 1 );
  627. m_std_shape_dict.emplace( hash, name );
  628. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  629. addAttribute( entry_node, "id", name );
  630. wxXmlNode* oval_node = appendNode( entry_node, "Oval" );
  631. VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
  632. addAttribute( oval_node, "width", floatVal( m_scale * pad_size.x ) );
  633. addAttribute( oval_node, "height", floatVal( m_scale * pad_size.y ) );
  634. break;
  635. }
  636. case PAD_SHAPE::ROUNDRECT:
  637. {
  638. name = wxString::Format( "ROUNDRECT_%zu", m_std_shape_dict.size() + 1 );
  639. m_std_shape_dict.emplace( hash, name );
  640. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  641. addAttribute( entry_node, "id", name );
  642. wxXmlNode* roundrect_node = appendNode( entry_node, "RectRound" );
  643. VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
  644. addAttribute( roundrect_node, "width", floatVal( m_scale * pad_size.x ) );
  645. addAttribute( roundrect_node, "height", floatVal( m_scale * pad_size.y ) );
  646. roundrect_node->AddAttribute( "radius",
  647. floatVal( m_scale * aPad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ) ) );
  648. addAttribute( roundrect_node, "upperRight", "true" );
  649. addAttribute( roundrect_node, "upperLeft", "true" );
  650. addAttribute( roundrect_node, "lowerRight", "true" );
  651. addAttribute( roundrect_node, "lowerLeft", "true" );
  652. break;
  653. }
  654. case PAD_SHAPE::CHAMFERED_RECT:
  655. {
  656. name = wxString::Format( "RECTCHAMFERED_%zu", m_std_shape_dict.size() + 1 );
  657. m_std_shape_dict.emplace( hash, name );
  658. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  659. addAttribute( entry_node, "id", name );
  660. wxXmlNode* chamfered_node = appendNode( entry_node, "RectCham" );
  661. VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
  662. addAttribute( chamfered_node, "width", floatVal( m_scale * pad_size.x ) );
  663. addAttribute( chamfered_node, "height", floatVal( m_scale * pad_size.y ) );
  664. int shorterSide = std::min( pad_size.x, pad_size.y );
  665. int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * shorterSide ) );
  666. addAttribute( chamfered_node, "chamfer", floatVal( m_scale * chamfer ) );
  667. int positions = aPad.GetChamferPositions( PADSTACK::ALL_LAYERS );
  668. if( positions & RECT_CHAMFER_TOP_LEFT )
  669. addAttribute( chamfered_node, "upperLeft", "true" );
  670. if( positions & RECT_CHAMFER_TOP_RIGHT )
  671. addAttribute( chamfered_node, "upperRight", "true" );
  672. if( positions & RECT_CHAMFER_BOTTOM_LEFT )
  673. addAttribute( chamfered_node, "lowerLeft", "true" );
  674. if( positions & RECT_CHAMFER_BOTTOM_RIGHT )
  675. addAttribute( chamfered_node, "lowerRight", "true" );
  676. break;
  677. }
  678. case PAD_SHAPE::TRAPEZOID:
  679. {
  680. name = wxString::Format( "TRAPEZOID_%zu", m_std_shape_dict.size() + 1 );
  681. m_std_shape_dict.emplace( hash, name );
  682. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  683. addAttribute( entry_node, "id", name );
  684. VECTOR2I pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS );
  685. VECTOR2I trap_delta = aPad.GetDelta( PADSTACK::ALL_LAYERS );
  686. SHAPE_POLY_SET outline;
  687. outline.NewOutline();
  688. int dx = pad_size.x / 2;
  689. int dy = pad_size.y / 2;
  690. int ddx = trap_delta.x / 2;
  691. int ddy = trap_delta.y / 2;
  692. outline.Append( -dx - ddy, dy + ddx );
  693. outline.Append( dx + ddy, dy - ddx );
  694. outline.Append( dx - ddy, -dy + ddx );
  695. outline.Append( -dx + ddy, -dy - ddx );
  696. // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
  697. // which can create bad shapes if margin.x is < 0
  698. if( expansion.x )
  699. {
  700. outline.InflateWithLinkedHoles( expansion.x, CORNER_STRATEGY::ROUND_ALL_CORNERS,
  701. maxError );
  702. }
  703. addContourNode( entry_node, outline );
  704. break;
  705. }
  706. case PAD_SHAPE::CUSTOM:
  707. {
  708. name = wxString::Format( "CUSTOM_%zu", m_std_shape_dict.size() + 1 );
  709. m_std_shape_dict.emplace( hash, name );
  710. wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
  711. addAttribute( entry_node, "id", name );
  712. SHAPE_POLY_SET shape;
  713. aPad.MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &shape );
  714. if( expansion != VECTOR2I( 0, 0 ) )
  715. {
  716. shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ),
  717. CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError );
  718. }
  719. addContourNode( entry_node, shape );
  720. break;
  721. }
  722. default:
  723. Report( _( "Pad has unsupported type; it was skipped." ), RPT_SEVERITY_WARNING );
  724. break;
  725. }
  726. if( !name.empty() )
  727. {
  728. m_std_shape_dict.emplace( hash, name );
  729. wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
  730. addAttribute( shape_node, "id", name );
  731. }
  732. }
  733. void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape )
  734. {
  735. size_t hash = shapeHash( aShape );
  736. auto iter = m_user_shape_dict.find( hash );
  737. wxString name;
  738. if( iter != m_user_shape_dict.end() )
  739. {
  740. wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
  741. addAttribute( shape_node, "id", iter->second );
  742. return;
  743. }
  744. switch( aShape.GetShape() )
  745. {
  746. case SHAPE_T::CIRCLE:
  747. {
  748. name = wxString::Format( "UCIRCLE_%zu", m_user_shape_dict.size() + 1 );
  749. m_user_shape_dict.emplace( hash, name );
  750. int diameter = aShape.GetRadius() * 2.0;
  751. int width = aShape.GetStroke().GetWidth();
  752. LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
  753. wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
  754. addAttribute( entry_node, "id", name );
  755. wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
  756. wxXmlNode* circle_node = appendNode( special_node, "Circle" );
  757. if( aShape.GetFillMode() == FILL_T::NO_FILL )
  758. {
  759. addAttribute( circle_node, "diameter", floatVal( m_scale * diameter ) );
  760. addLineDesc( circle_node, width, dash, true );
  761. }
  762. else
  763. {
  764. // IPC2581 does not allow strokes on filled elements
  765. addAttribute( circle_node, "diameter", floatVal( m_scale * ( diameter + width ) ) );
  766. }
  767. addFillDesc( circle_node, aShape.GetFillMode() );
  768. break;
  769. }
  770. case SHAPE_T::RECTANGLE:
  771. {
  772. name = wxString::Format( "URECT_%zu", m_user_shape_dict.size() + 1 );
  773. m_user_shape_dict.emplace( hash, name );
  774. wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
  775. addAttribute( entry_node, "id", name );
  776. wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
  777. int width = std::abs( aShape.GetRectangleWidth() );
  778. int height = std::abs( aShape.GetRectangleHeight() );
  779. int stroke_width = aShape.GetStroke().GetWidth();
  780. LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
  781. wxXmlNode* rect_node = appendNode( special_node, "RectRound" );
  782. addLineDesc( rect_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(),
  783. true );
  784. if( aShape.GetFillMode() == FILL_T::NO_FILL )
  785. {
  786. addAttribute( rect_node, "upperRight", "false" );
  787. addAttribute( rect_node, "upperLeft", "false" );
  788. addAttribute( rect_node, "lowerRight", "false" );
  789. addAttribute( rect_node, "lowerLeft", "false" );
  790. }
  791. else
  792. {
  793. addAttribute( rect_node, "upperRight", "true" );
  794. addAttribute( rect_node, "upperLeft", "true" );
  795. addAttribute( rect_node, "lowerRight", "true" );
  796. addAttribute( rect_node, "lowerLeft", "true" );
  797. width += stroke_width;
  798. height += stroke_width;
  799. }
  800. addFillDesc( rect_node, aShape.GetFillMode() );
  801. addAttribute( rect_node, "width", floatVal( m_scale * width ) );
  802. addAttribute( rect_node, "height", floatVal( m_scale * height ) );
  803. addAttribute( rect_node, "radius", floatVal( m_scale * ( stroke_width / 2.0 ) ) );
  804. break;
  805. }
  806. case SHAPE_T::POLY:
  807. {
  808. name = wxString::Format( "UPOLY_%zu", m_user_shape_dict.size() + 1 );
  809. m_user_shape_dict.emplace( hash, name );
  810. wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
  811. addAttribute( entry_node, "id", name );
  812. // If we are stroking a polygon, we need two contours. This is only allowed
  813. // inside a "UserSpecial" shape
  814. wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
  815. const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape();
  816. for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
  817. {
  818. if( aShape.GetFillMode() != FILL_T::NO_FILL )
  819. {
  820. // IPC2581 does not allow strokes on filled elements
  821. addContourNode( special_node, poly_set, ii, FILL_T::FILLED_SHAPE, 0,
  822. LINE_STYLE::SOLID );
  823. }
  824. addContourNode( special_node, poly_set, ii, FILL_T::NO_FILL,
  825. aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle() );
  826. }
  827. break;
  828. }
  829. case SHAPE_T::ARC:
  830. {
  831. wxXmlNode* arc_node = appendNode( aContentNode, "Arc" );
  832. addXY( arc_node, aShape.GetStart(), "startX", "startY" );
  833. addXY( arc_node, aShape.GetEnd(), "endX", "endY" );
  834. addXY( arc_node, aShape.GetCenter(), "centerX", "centerY" );
  835. //N.B. because our coordinate system is flipped, we need to flip the arc direction
  836. addAttribute( arc_node, "clockwise", !aShape.IsClockwiseArc() ? "true" : "false" );
  837. if( aShape.GetStroke().GetWidth() > 0 )
  838. {
  839. addLineDesc( arc_node, aShape.GetStroke().GetWidth(),
  840. aShape.GetStroke().GetLineStyle(), true );
  841. }
  842. break;
  843. }
  844. case SHAPE_T::BEZIER:
  845. {
  846. wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
  847. std::vector<VECTOR2I> ctrlPoints = { aShape.GetStart(), aShape.GetBezierC1(),
  848. aShape.GetBezierC2(), aShape.GetEnd() };
  849. BEZIER_POLY converter( ctrlPoints );
  850. std::vector<VECTOR2I> points;
  851. converter.GetPoly( points, ARC_HIGH_DEF );
  852. wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
  853. addXY( point_node, points[0] );
  854. for( size_t i = 1; i < points.size(); i++ )
  855. {
  856. wxXmlNode* point_node = appendNode( polyline_node, "PolyStepSegment" );
  857. addXY( point_node, points[i] );
  858. }
  859. if( aShape.GetStroke().GetWidth() > 0 )
  860. {
  861. addLineDesc( polyline_node, aShape.GetStroke().GetWidth(),
  862. aShape.GetStroke().GetLineStyle(), true );
  863. }
  864. break;
  865. }
  866. case SHAPE_T::SEGMENT:
  867. {
  868. wxXmlNode* line_node = appendNode( aContentNode, "Line" );
  869. addXY( line_node, aShape.GetStart(), "startX", "startY" );
  870. addXY( line_node, aShape.GetEnd(), "endX", "endY" );
  871. if( aShape.GetStroke().GetWidth() > 0 )
  872. {
  873. addLineDesc( line_node, aShape.GetStroke().GetWidth(),
  874. aShape.GetStroke().GetLineStyle(), true );
  875. }
  876. break;
  877. }
  878. case SHAPE_T::UNDEFINED:
  879. wxFAIL;
  880. }
  881. if( !name.empty() )
  882. {
  883. wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
  884. addAttribute( shape_node, "id", name );
  885. }
  886. }
  887. void PCB_IO_IPC2581::addSlotCavity( wxXmlNode* aNode, const PAD& aPad, const wxString& aName )
  888. {
  889. wxXmlNode* slotNode = appendNode( aNode, "SlotCavity" );
  890. addAttribute( slotNode, "name", aName );
  891. addAttribute( slotNode, "platingStatus", aPad.GetAttribute() == PAD_ATTRIB::PTH ? "PLATED"
  892. : "NONPLATED" );
  893. addAttribute( slotNode, "plusTol", "0.0" );
  894. addAttribute( slotNode, "minusTol", "0.0" );
  895. if( m_version > 'B' )
  896. addLocationNode( slotNode, 0.0, 0.0 );
  897. SHAPE_POLY_SET poly_set;
  898. aPad.GetEffectiveShape( PADSTACK::ALL_LAYERS )->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
  899. addOutlineNode( slotNode, poly_set );
  900. }
  901. wxXmlNode* PCB_IO_IPC2581::generateLogisticSection()
  902. {
  903. wxXmlNode* logisticNode = appendNode( m_xml_root, "LogisticHeader" );
  904. wxXmlNode* roleNode = appendNode( logisticNode, "Role" );
  905. addAttribute( roleNode, "id", "Owner" );
  906. addAttribute( roleNode, "roleFunction", "SENDER" );
  907. m_enterpriseNode = appendNode( logisticNode, "Enterprise" );
  908. addAttribute( m_enterpriseNode, "id", "UNKNOWN" );
  909. addAttribute( m_enterpriseNode, "code", "NONE" );
  910. wxXmlNode* personNode = appendNode( logisticNode, "Person" );
  911. addAttribute( personNode, "name", "UNKNOWN" );
  912. addAttribute( personNode, "enterpriseRef", "UNKNOWN" );
  913. addAttribute( personNode, "roleRef", "Owner" );
  914. return logisticNode;
  915. }
  916. wxXmlNode* PCB_IO_IPC2581::generateHistorySection()
  917. {
  918. if( m_progressReporter )
  919. m_progressReporter->AdvancePhase( _( "Generating history section" ) );
  920. wxXmlNode* historyNode = appendNode( m_xml_root, "HistoryRecord" );
  921. addAttribute( historyNode, "number", "1" );
  922. addAttribute( historyNode, "origination", wxDateTime::Now().FormatISOCombined() );
  923. addAttribute( historyNode, "software", "KiCad EDA" );
  924. addAttribute( historyNode, "lastChange", wxDateTime::Now().FormatISOCombined() );
  925. wxXmlNode* fileRevisionNode = appendNode( historyNode, "FileRevision" );
  926. addAttribute( fileRevisionNode, "fileRevisionId", "1" );
  927. addAttribute( fileRevisionNode, "comment", "NO COMMENT" );
  928. addAttribute( fileRevisionNode, "label", "NO LABEL" );
  929. wxXmlNode* softwarePackageNode = appendNode( fileRevisionNode, "SoftwarePackage" );
  930. addAttribute( softwarePackageNode, "name", "KiCad" );
  931. addAttribute( softwarePackageNode, "revision", GetMajorMinorPatchVersion() );
  932. addAttribute( softwarePackageNode, "vendor", "KiCad EDA" );
  933. wxXmlNode* certificationNode = appendNode( softwarePackageNode, "Certification" );
  934. addAttribute( certificationNode, "certificationStatus", "SELFTEST" );
  935. return historyNode;
  936. }
  937. wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
  938. {
  939. if( m_progressReporter )
  940. m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
  941. struct REFDES
  942. {
  943. wxString m_name;
  944. wxString m_pkg;
  945. bool m_populate;
  946. wxString m_layer;
  947. };
  948. struct BOM_ENTRY
  949. {
  950. BOM_ENTRY()
  951. {
  952. m_refdes = new std::vector<REFDES>();
  953. m_props = new std::map<wxString, wxString>();
  954. m_count = 0;
  955. m_pads = 0;
  956. }
  957. ~BOM_ENTRY()
  958. {
  959. delete m_refdes;
  960. delete m_props;
  961. }
  962. wxString m_OEMDesignRef; // String combining LIB+FP+VALUE
  963. int m_count;
  964. int m_pads;
  965. wxString m_type;
  966. std::vector<REFDES>* m_refdes;
  967. std::map<wxString, wxString>* m_props;
  968. };
  969. std::set<std::unique_ptr<struct BOM_ENTRY>,
  970. std::function<bool( const std::unique_ptr<struct BOM_ENTRY>&,
  971. const std::unique_ptr<struct BOM_ENTRY>& )>> bom_entries(
  972. []( const std::unique_ptr<struct BOM_ENTRY>& a,
  973. const std::unique_ptr<struct BOM_ENTRY>& b )
  974. {
  975. return a->m_OEMDesignRef < b->m_OEMDesignRef;
  976. } );
  977. for( FOOTPRINT* fp_it : m_board->Footprints() )
  978. {
  979. std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( fp_it->Clone() ) );
  980. fp->SetParentGroup( nullptr );
  981. fp->SetPosition( {0, 0} );
  982. fp->SetOrientation( ANGLE_0 );
  983. size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
  984. auto iter = m_footprint_dict.find( hash );
  985. if( iter == m_footprint_dict.end() )
  986. {
  987. Report( wxString::Format( _( "Footprint %s not found in dictionary; BOM data may be incomplete." ),
  988. fp->GetFPID().GetLibItemName().wx_str() ),
  989. RPT_SEVERITY_WARNING );
  990. continue;
  991. }
  992. auto entry = std::make_unique<struct BOM_ENTRY>();
  993. /// We assume that the m_OEMRef_dict is populated already by the generateComponents function
  994. /// This will either place a unique string in the dictionary or field reference.
  995. if( auto it = m_OEMRef_dict.find( fp_it ); it != m_OEMRef_dict.end() )
  996. {
  997. entry->m_OEMDesignRef = it->second;
  998. }
  999. else
  1000. {
  1001. Report( wxString::Format( _( "Component \"%s\" missing OEM reference; BOM entry will be skipped." ),
  1002. fp->GetFPID().GetLibItemName().wx_str() ),
  1003. RPT_SEVERITY_WARNING );
  1004. }
  1005. entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" );
  1006. entry->m_count = 1;
  1007. entry->m_pads = fp->GetPadCount();
  1008. // TODO: The options are "ELECTRICAL", "MECHANICAL", "PROGRAMMABLE", "DOCUMENT", "MATERIAL"
  1009. // We need to figure out how to determine this.
  1010. if( entry->m_pads == 0 || fp_it->GetAttributes() & FP_EXCLUDE_FROM_BOM )
  1011. entry->m_type = "DOCUMENT";
  1012. else
  1013. entry->m_type = "ELECTRICAL";
  1014. auto[ bom_iter, inserted ] = bom_entries.insert( std::move( entry ) );
  1015. if( !inserted )
  1016. ( *bom_iter )->m_count++;
  1017. REFDES refdes;
  1018. refdes.m_name = componentName( fp_it );
  1019. refdes.m_pkg = fp->GetFPID().GetLibItemName().wx_str();
  1020. refdes.m_populate = !fp->IsDNP() && !( fp->GetAttributes() & FP_EXCLUDE_FROM_BOM );
  1021. refdes.m_layer = m_layer_name_map[fp_it->GetLayer()];
  1022. ( *bom_iter )->m_refdes->push_back( refdes );
  1023. // TODO: This amalgamates all the properties from all the footprints. We need to decide
  1024. // if we want to group footprints by their properties
  1025. for( PCB_FIELD* prop : fp->GetFields() )
  1026. {
  1027. // We don't need ref, footprint or datasheet in the BOM characteristics. Just value
  1028. // and any additional fields the user has added. Ref and footprint are captured above.
  1029. if( prop->IsMandatory() && !prop->IsValue() )
  1030. continue;
  1031. ( *bom_iter )->m_props->emplace( prop->GetName(), prop->GetText() );
  1032. }
  1033. }
  1034. if( bom_entries.empty() )
  1035. return nullptr;
  1036. wxFileName fn( m_board->GetFileName() );
  1037. wxXmlNode* bomNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Bom" );
  1038. m_xml_root->InsertChild( bomNode, aEcadNode );
  1039. addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) );
  1040. wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" );
  1041. addAttribute( bomHeaderNode, "revision", "1.0" );
  1042. addAttribute( bomHeaderNode, "assembly", genString( fn.GetName() ) );
  1043. wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" );
  1044. addAttribute( stepRefNode, "name", genString( fn.GetName(), "BOARD" ) );
  1045. for( const auto& entry : bom_entries )
  1046. {
  1047. wxXmlNode* bomEntryNode = appendNode( bomNode, "BomItem" );
  1048. addAttribute( bomEntryNode, "OEMDesignNumberRef", entry->m_OEMDesignRef );
  1049. addAttribute( bomEntryNode, "quantity", wxString::Format( "%d", entry->m_count ) );
  1050. addAttribute( bomEntryNode, "pinCount", wxString::Format( "%d", entry->m_pads ) );
  1051. addAttribute( bomEntryNode, "category", entry->m_type );
  1052. for( const REFDES& refdes : *( entry->m_refdes ) )
  1053. {
  1054. wxXmlNode* refdesNode = appendNode( bomEntryNode, "RefDes" );
  1055. addAttribute( refdesNode, "name", refdes.m_name );
  1056. addAttribute( refdesNode, "packageRef", genString( refdes.m_pkg, "PKG" ) );
  1057. addAttribute( refdesNode, "populate", refdes.m_populate ? "true" : "false" );
  1058. addAttribute( refdesNode, "layerRef", refdes.m_layer );
  1059. }
  1060. wxXmlNode* characteristicsNode = appendNode( bomEntryNode, "Characteristics" );
  1061. addAttribute( characteristicsNode, "category", entry->m_type );
  1062. for( const auto& prop : *( entry->m_props ) )
  1063. {
  1064. wxXmlNode* textualDefNode = appendNode( characteristicsNode, "Textual" );
  1065. addAttribute( textualDefNode, "definitionSource", "KICAD" );
  1066. addAttribute( textualDefNode, "textualCharacteristicName", prop.first );
  1067. addAttribute( textualDefNode, "textualCharacteristicValue", prop.second );
  1068. }
  1069. }
  1070. return bomNode;
  1071. }
  1072. wxXmlNode* PCB_IO_IPC2581::generateEcadSection()
  1073. {
  1074. if( m_progressReporter )
  1075. m_progressReporter->AdvancePhase( _( "Generating CAD data" ) );
  1076. wxXmlNode* ecadNode = appendNode( m_xml_root, "Ecad" );
  1077. addAttribute( ecadNode, "name", "Design" );
  1078. addCadHeader( ecadNode );
  1079. wxXmlNode* cadDataNode = appendNode( ecadNode, "CadData" );
  1080. generateCadLayers( cadDataNode );
  1081. generateDrillLayers( cadDataNode);
  1082. generateStackup( cadDataNode );
  1083. generateStepSection( cadDataNode );
  1084. return ecadNode;
  1085. }
  1086. void PCB_IO_IPC2581::generateCadSpecs( wxXmlNode* aCadLayerNode )
  1087. {
  1088. BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
  1089. BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
  1090. stackup.SynchronizeWithBoard( &dsnSettings );
  1091. std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
  1092. std::set<PCB_LAYER_ID> added_layers;
  1093. for( int i = 0; i < stackup.GetCount(); i++ )
  1094. {
  1095. BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
  1096. for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
  1097. {
  1098. wxString ly_name = stackup_item->GetLayerName();
  1099. if( ly_name.IsEmpty() )
  1100. {
  1101. if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
  1102. ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
  1103. if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  1104. {
  1105. ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
  1106. if( sublayer_id > 0 )
  1107. ly_name += wxString::Format( "_%d", sublayer_id );
  1108. }
  1109. }
  1110. ly_name = genString( ly_name, "SPEC_LAYER" );
  1111. wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
  1112. addAttribute( specNode, "name", ly_name );
  1113. wxXmlNode* generalNode = appendNode( specNode, "General" );
  1114. addAttribute( generalNode, "type", "MATERIAL" );
  1115. wxXmlNode* propertyNode = appendNode( generalNode, "Property" );
  1116. switch ( stackup_item->GetType() )
  1117. {
  1118. case BS_ITEM_TYPE_COPPER:
  1119. {
  1120. addAttribute( propertyNode, "text", "COPPER" );
  1121. wxXmlNode* conductorNode = appendNode( specNode, "Conductor" );
  1122. addAttribute( conductorNode, "type", "CONDUCTIVITY" );
  1123. propertyNode = appendNode( conductorNode, "Property" );
  1124. addAttribute( propertyNode, "unit", wxT( "SIEMENS/M" ) );
  1125. addAttribute( propertyNode, "value", wxT( "5.959E7" ) );
  1126. break;
  1127. }
  1128. case BS_ITEM_TYPE_DIELECTRIC:
  1129. {
  1130. addAttribute( propertyNode, "text", stackup_item->GetMaterial() );
  1131. propertyNode = appendNode( generalNode, "Property" );
  1132. addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
  1133. stackup_item->GetTypeName() ) );
  1134. wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
  1135. addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
  1136. propertyNode = appendNode( dielectricNode, "Property" );
  1137. addAttribute( propertyNode, "value",
  1138. floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
  1139. dielectricNode = appendNode( specNode, "Dielectric" );
  1140. addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
  1141. propertyNode = appendNode( dielectricNode, "Property" );
  1142. addAttribute( propertyNode, "value",
  1143. floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
  1144. break;
  1145. }
  1146. case BS_ITEM_TYPE_SILKSCREEN:
  1147. addAttribute( propertyNode, "text", stackup_item->GetTypeName() );
  1148. propertyNode = appendNode( generalNode, "Property" );
  1149. addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
  1150. stackup_item->GetColor() ) );
  1151. propertyNode = appendNode( generalNode, "Property" );
  1152. addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
  1153. stackup_item->GetTypeName() ) );
  1154. break;
  1155. case BS_ITEM_TYPE_SOLDERMASK:
  1156. addAttribute( propertyNode, "text", "SOLDERMASK" );
  1157. propertyNode = appendNode( generalNode, "Property" );
  1158. addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
  1159. stackup_item->GetColor() ) );
  1160. propertyNode = appendNode( generalNode, "Property" );
  1161. addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
  1162. stackup_item->GetTypeName() ) );
  1163. break;
  1164. default:
  1165. break;
  1166. }
  1167. }
  1168. }
  1169. }
  1170. void PCB_IO_IPC2581::addCadHeader( wxXmlNode* aEcadNode )
  1171. {
  1172. wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" );
  1173. addAttribute( cadHeaderNode, "units", m_units_str );
  1174. generateCadSpecs( cadHeaderNode );
  1175. }
  1176. bool PCB_IO_IPC2581::isValidLayerFor2581( PCB_LAYER_ID aLayer )
  1177. {
  1178. return ( aLayer >= F_Cu && aLayer <= User_9 ) || aLayer == UNDEFINED_LAYER;
  1179. }
  1180. void PCB_IO_IPC2581::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer )
  1181. {
  1182. switch( aLayer )
  1183. {
  1184. case F_Adhes:
  1185. case B_Adhes:
  1186. addAttribute( aNode, "layerFunction", "GLUE" );
  1187. addAttribute( aNode, "polarity", "POSITIVE" );
  1188. addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" );
  1189. break;
  1190. case F_Paste:
  1191. case B_Paste:
  1192. addAttribute( aNode, "layerFunction", "SOLDERPASTE" );
  1193. addAttribute( aNode, "polarity", "POSITIVE" );
  1194. addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" );
  1195. break;
  1196. case F_SilkS:
  1197. case B_SilkS:
  1198. addAttribute( aNode, "layerFunction", "SILKSCREEN" );
  1199. addAttribute( aNode, "polarity", "POSITIVE" );
  1200. addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" );
  1201. break;
  1202. case F_Mask:
  1203. case B_Mask:
  1204. addAttribute( aNode, "layerFunction", "SOLDERMASK" );
  1205. addAttribute( aNode, "polarity", "POSITIVE" );
  1206. addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" );
  1207. break;
  1208. case Edge_Cuts:
  1209. addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" );
  1210. addAttribute( aNode, "polarity", "POSITIVE" );
  1211. addAttribute( aNode, "side", "ALL" );
  1212. break;
  1213. case B_CrtYd:
  1214. case F_CrtYd:
  1215. addAttribute( aNode, "layerFunction", "COURTYARD" );
  1216. addAttribute( aNode, "polarity", "POSITIVE" );
  1217. addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" );
  1218. break;
  1219. case B_Fab:
  1220. case F_Fab:
  1221. addAttribute( aNode, "layerFunction", "ASSEMBLY" );
  1222. addAttribute( aNode, "polarity", "POSITIVE" );
  1223. addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" );
  1224. break;
  1225. case Dwgs_User:
  1226. case Cmts_User:
  1227. case Eco1_User:
  1228. case Eco2_User:
  1229. case Margin:
  1230. case User_1:
  1231. case User_2:
  1232. case User_3:
  1233. case User_4:
  1234. case User_5:
  1235. case User_6:
  1236. case User_7:
  1237. case User_8:
  1238. case User_9:
  1239. addAttribute( aNode, "layerFunction", "DOCUMENT" );
  1240. addAttribute( aNode, "polarity", "POSITIVE" );
  1241. addAttribute( aNode, "side", "NONE" );
  1242. break;
  1243. default:
  1244. if( IsCopperLayer( aLayer ) )
  1245. {
  1246. addAttribute( aNode, "layerFunction", "CONDUCTOR" );
  1247. addAttribute( aNode, "polarity", "POSITIVE" );
  1248. addAttribute( aNode, "side",
  1249. aLayer == F_Cu ? "TOP"
  1250. : aLayer == B_Cu ? "BOTTOM"
  1251. : "INTERNAL" );
  1252. }
  1253. break; // Do not handle other layers
  1254. }
  1255. }
  1256. void PCB_IO_IPC2581::generateStackup( wxXmlNode* aCadLayerNode )
  1257. {
  1258. BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
  1259. BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
  1260. stackup.SynchronizeWithBoard( &dsnSettings );
  1261. wxXmlNode* stackupNode = appendNode( aCadLayerNode, "Stackup" );
  1262. addAttribute( stackupNode, "name", "Primary_Stackup" );
  1263. addAttribute( stackupNode, "overallThickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
  1264. addAttribute( stackupNode, "tolPlus", "0.0" );
  1265. addAttribute( stackupNode, "tolMinus", "0.0" );
  1266. addAttribute( stackupNode, "whereMeasured", "MASK" );
  1267. if( m_version > 'B' )
  1268. addAttribute( stackupNode, "stackupStatus", "PROPOSED" );
  1269. wxXmlNode* stackupGroup = appendNode( stackupNode, "StackupGroup" );
  1270. addAttribute( stackupGroup, "name", "Primary_Stackup_Group" );
  1271. addAttribute( stackupGroup, "thickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
  1272. addAttribute( stackupGroup, "tolPlus", "0.0" );
  1273. addAttribute( stackupGroup, "tolMinus", "0.0" );
  1274. std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
  1275. std::set<PCB_LAYER_ID> added_layers;
  1276. for( int i = 0; i < stackup.GetCount(); i++ )
  1277. {
  1278. BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
  1279. for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
  1280. {
  1281. wxXmlNode* stackupLayer = appendNode( stackupGroup, "StackupLayer" );
  1282. wxString ly_name = stackup_item->GetLayerName();
  1283. if( ly_name.IsEmpty() )
  1284. {
  1285. if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
  1286. ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
  1287. if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  1288. {
  1289. ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
  1290. if( sublayer_id > 0 )
  1291. ly_name += wxString::Format( "_%d", sublayer_id );
  1292. }
  1293. }
  1294. ly_name = genString( ly_name, "LAYER" );
  1295. addAttribute( stackupLayer, "layerOrGroupRef", ly_name );
  1296. addAttribute( stackupLayer, "thickness", floatVal( m_scale * stackup_item->GetThickness() ) );
  1297. addAttribute( stackupLayer, "tolPlus", "0.0" );
  1298. addAttribute( stackupLayer, "tolMinus", "0.0" );
  1299. addAttribute( stackupLayer, "sequence", wxString::Format( "%d", i ) );
  1300. wxXmlNode* specLayerNode = appendNode( stackupLayer, "SpecRef" );
  1301. addAttribute( specLayerNode, "id", wxString::Format( "SPEC_%s", ly_name ) );
  1302. }
  1303. }
  1304. }
  1305. void PCB_IO_IPC2581::generateCadLayers( wxXmlNode* aCadLayerNode )
  1306. {
  1307. BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
  1308. BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
  1309. stackup.SynchronizeWithBoard( &dsnSettings );
  1310. std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
  1311. std::set<PCB_LAYER_ID> added_layers;
  1312. for( int i = 0; i < stackup.GetCount(); i++ )
  1313. {
  1314. BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
  1315. if( !isValidLayerFor2581( stackup_item->GetBrdLayerId() ) )
  1316. continue;
  1317. for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
  1318. {
  1319. wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
  1320. wxString ly_name = stackup_item->GetLayerName();
  1321. if( ly_name.IsEmpty() )
  1322. {
  1323. if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
  1324. ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
  1325. if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  1326. {
  1327. ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
  1328. if( sublayer_id > 0 )
  1329. ly_name += wxString::Format( "_%d", sublayer_id );
  1330. }
  1331. }
  1332. ly_name = genString( ly_name, "LAYER" );
  1333. addAttribute( cadLayerNode, "name", ly_name );
  1334. if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
  1335. {
  1336. if( stackup_item->GetTypeName() == KEY_CORE )
  1337. addAttribute( cadLayerNode, "layerFunction", "DIELCORE" );
  1338. else
  1339. addAttribute( cadLayerNode, "layerFunction", "DIELPREG" );
  1340. addAttribute( cadLayerNode, "polarity", "POSITIVE" );
  1341. addAttribute( cadLayerNode, "side", "INTERNAL" );
  1342. continue;
  1343. }
  1344. else
  1345. {
  1346. added_layers.insert( stackup_item->GetBrdLayerId() );
  1347. addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() );
  1348. m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name );
  1349. }
  1350. }
  1351. }
  1352. LSEQ layer_seq = m_board->GetEnabledLayers().Seq();
  1353. for( PCB_LAYER_ID layer : layer_seq )
  1354. {
  1355. if( added_layers.find( layer ) != added_layers.end() || !isValidLayerFor2581( layer ) )
  1356. continue;
  1357. wxString ly_name = genLayerString( layer, "LAYER" );
  1358. m_layer_name_map.emplace( layer, ly_name );
  1359. added_layers.insert( layer );
  1360. wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
  1361. addAttribute( cadLayerNode, "name", ly_name );
  1362. addLayerAttributes( cadLayerNode, layer );
  1363. }
  1364. }
  1365. void PCB_IO_IPC2581::generateDrillLayers( wxXmlNode* aCadLayerNode )
  1366. {
  1367. for( BOARD_ITEM* item : m_board->Tracks() )
  1368. {
  1369. if( item->Type() == PCB_VIA_T )
  1370. {
  1371. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  1372. m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via );
  1373. }
  1374. }
  1375. for( FOOTPRINT* fp : m_board->Footprints() )
  1376. {
  1377. for( PAD* pad : fp->Pads() )
  1378. {
  1379. if( pad->HasDrilledHole() )
  1380. m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad );
  1381. else if( pad->HasHole() )
  1382. m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad );
  1383. }
  1384. }
  1385. for( const auto& [layers, vec] : m_drill_layers )
  1386. {
  1387. wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
  1388. drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "DRILL" ) );
  1389. addAttribute( drillNode, "layerFunction", "DRILL" );
  1390. addAttribute( drillNode, "polarity", "POSITIVE" );
  1391. addAttribute( drillNode, "side", "ALL" );
  1392. wxXmlNode* spanNode = appendNode( drillNode, "Span" );
  1393. addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
  1394. addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
  1395. }
  1396. for( const auto& [layers, vec] : m_slot_holes )
  1397. {
  1398. wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
  1399. drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "SLOT" ) );
  1400. addAttribute( drillNode, "layerFunction", "ROUT" );
  1401. addAttribute( drillNode, "polarity", "POSITIVE" );
  1402. addAttribute( drillNode, "side", "ALL" );
  1403. wxXmlNode* spanNode = appendNode( drillNode, "Span" );
  1404. addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
  1405. addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
  1406. }
  1407. }
  1408. void PCB_IO_IPC2581::generateStepSection( wxXmlNode* aCadNode )
  1409. {
  1410. wxXmlNode* stepNode = appendNode( aCadNode, "Step" );
  1411. wxFileName fn( m_board->GetFileName() );
  1412. addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) );
  1413. if( m_version > 'B' )
  1414. addAttribute( stepNode, "type", "BOARD" );
  1415. wxXmlNode* datumNode = appendNode( stepNode, "Datum" );
  1416. addAttribute( datumNode, "x", "0.0" );
  1417. addAttribute( datumNode, "y", "0.0" );
  1418. generateProfile( stepNode );
  1419. generateComponents( stepNode );
  1420. m_last_padstack = insertNode( stepNode, "NonstandardAttribute" );
  1421. addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" );
  1422. addAttribute( m_last_padstack, "type", "INTEGER" );
  1423. addAttribute( m_last_padstack, "value",
  1424. wxString::Format( "%zu", m_board->Footprints().size() ) );
  1425. generateLayerFeatures( stepNode );
  1426. generateLayerSetDrill( stepNode );
  1427. }
  1428. void PCB_IO_IPC2581::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer )
  1429. {
  1430. wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
  1431. FOOTPRINT* fp = aPad->GetParentFootprint();
  1432. addPadStack( padNode, aPad );
  1433. if( aPad->GetOrientation() != ANGLE_0 )
  1434. {
  1435. wxXmlNode* xformNode = appendNode( padNode, "Xform" );
  1436. EDA_ANGLE angle = aPad->GetOrientation().Normalize();
  1437. xformNode->AddAttribute( "rotation", floatVal( angle.AsDegrees() ) );
  1438. }
  1439. addLocationNode( padNode, *aPad, false );
  1440. addShape( padNode, *aPad, aLayer );
  1441. if( fp )
  1442. {
  1443. wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" );
  1444. addAttribute( pinRefNode, "componentRef", componentName( fp ) );
  1445. addAttribute( pinRefNode, "pin", pinName( aPad ) );
  1446. }
  1447. }
  1448. void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
  1449. {
  1450. if( !aVia->FlashLayer( aLayer ) )
  1451. return;
  1452. wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
  1453. addPadStack( padNode, aVia );
  1454. addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y );
  1455. PAD dummy( nullptr );
  1456. int hole = aVia->GetDrillValue();
  1457. dummy.SetDrillSize( VECTOR2I( hole, hole ) );
  1458. dummy.SetPosition( aVia->GetStart() );
  1459. dummy.SetSize( aLayer, VECTOR2I( aVia->GetWidth( aLayer ), aVia->GetWidth( aLayer ) ) );
  1460. addShape( padNode, dummy, aLayer );
  1461. }
  1462. void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad )
  1463. {
  1464. size_t hash = hash_fp_item( aPad, 0 );
  1465. wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
  1466. auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name );
  1467. addAttribute( aPadNode, "padstackDefRef", th_pair->second );
  1468. // If we did not insert a new padstack, then we have already added it to the XML
  1469. // and we don't need to add it again.
  1470. if( !success )
  1471. return;
  1472. wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
  1473. addAttribute( padStackDefNode, "name", name );
  1474. m_padstacks.push_back( padStackDefNode );
  1475. if( m_last_padstack )
  1476. {
  1477. insertNodeAfter( m_last_padstack, padStackDefNode );
  1478. m_last_padstack = padStackDefNode;
  1479. }
  1480. // Only handle round holes here because IPC2581 does not support non-round holes
  1481. // These will be handled in a slot layer
  1482. if( aPad->HasDrilledHole() )
  1483. {
  1484. wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
  1485. padStackHoleNode->AddAttribute( "name",
  1486. wxString::Format( "%s%d_%d",
  1487. aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH",
  1488. aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
  1489. addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) );
  1490. addAttribute( padStackHoleNode, "platingStatus",
  1491. aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
  1492. addAttribute( padStackHoleNode, "plusTol", "0.0" );
  1493. addAttribute( padStackHoleNode, "minusTol", "0.0" );
  1494. addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
  1495. }
  1496. LSEQ layer_seq = aPad->GetLayerSet().Seq();
  1497. for( PCB_LAYER_ID layer : layer_seq )
  1498. {
  1499. FOOTPRINT* fp = aPad->GetParentFootprint();
  1500. if( !m_board->IsLayerEnabled( layer ) )
  1501. continue;
  1502. wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
  1503. addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
  1504. addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
  1505. addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y );
  1506. if( aPad->HasHole() || !aPad->FlashLayer( layer ) )
  1507. {
  1508. PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
  1509. shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
  1510. shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 );
  1511. addShape( padStackPadDefNode, shape );
  1512. }
  1513. else
  1514. {
  1515. addShape( padStackPadDefNode, *aPad, layer );
  1516. }
  1517. }
  1518. }
  1519. void PCB_IO_IPC2581::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia )
  1520. {
  1521. size_t hash = hash_fp_item( aVia, 0 );
  1522. wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
  1523. auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name );
  1524. addAttribute( aContentNode, "padstackDefRef", via_pair->second );
  1525. // If we did not insert a new padstack, then we have already added it to the XML
  1526. // and we don't need to add it again.
  1527. if( !success )
  1528. return;
  1529. wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
  1530. insertNodeAfter( m_last_padstack, padStackDefNode );
  1531. m_last_padstack = padStackDefNode;
  1532. addAttribute( padStackDefNode, "name", name );
  1533. wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
  1534. addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) );
  1535. padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) );
  1536. addAttribute( padStackHoleNode, "platingStatus", "VIA" );
  1537. addAttribute( padStackHoleNode, "plusTol", "0.0" );
  1538. addAttribute( padStackHoleNode, "minusTol", "0.0" );
  1539. addAttribute( padStackHoleNode, "x", "0.0" );
  1540. addAttribute( padStackHoleNode, "y", "0.0" );
  1541. LSEQ layer_seq = aVia->GetLayerSet().Seq();
  1542. for( PCB_LAYER_ID layer : layer_seq )
  1543. {
  1544. if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) )
  1545. continue;
  1546. PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
  1547. shape.SetEnd( { KiROUND( aVia->GetWidth( layer ) / 2.0 ), 0 } );
  1548. wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
  1549. addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
  1550. addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
  1551. addLocationNode( padStackPadDefNode, 0.0, 0.0 );
  1552. addShape( padStackPadDefNode, shape );
  1553. }
  1554. }
  1555. bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
  1556. const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
  1557. int aWidth, LINE_STYLE aDashType )
  1558. {
  1559. wxXmlNode* polygonNode = nullptr;
  1560. if( aPolygon.PointCount() < 3 )
  1561. return false;
  1562. auto make_node =
  1563. [&]()
  1564. {
  1565. polygonNode = appendNode( aParentNode, "Polygon" );
  1566. wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
  1567. const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
  1568. addXY( polybeginNode, pts[0] );
  1569. for( size_t ii = 1; ii < pts.size(); ++ii )
  1570. {
  1571. wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
  1572. addXY( polyNode, pts[ii] );
  1573. }
  1574. wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
  1575. addXY( polyendNode, pts[0] );
  1576. };
  1577. // Allow the case where we don't want line/fill information in the polygon
  1578. if( aFillType == FILL_T::NO_FILL )
  1579. {
  1580. make_node();
  1581. // If we specify a line width, we need to add a LineDescRef node and
  1582. // since this is only valid for a non-filled polygon, we need to create
  1583. // the fillNode as well
  1584. if( aWidth > 0 )
  1585. addLineDesc( polygonNode, aWidth, aDashType, true );
  1586. }
  1587. else
  1588. {
  1589. wxCHECK( aWidth == 0, false );
  1590. make_node();
  1591. }
  1592. addFillDesc( polygonNode, aFillType );
  1593. return true;
  1594. }
  1595. bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
  1596. const SHAPE_POLY_SET::POLYGON& aPolygon )
  1597. {
  1598. for( size_t ii = 1; ii < aPolygon.size(); ++ii )
  1599. {
  1600. wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
  1601. wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
  1602. wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
  1603. const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
  1604. addXY( polybeginNode, hole[0] );
  1605. for( size_t jj = 1; jj < hole.size(); ++jj )
  1606. {
  1607. wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
  1608. addXY( polyNode, hole[jj] );
  1609. }
  1610. wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
  1611. addXY( polyendNode, hole[0] );
  1612. }
  1613. return true;
  1614. }
  1615. bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
  1616. int aWidth, LINE_STYLE aDashType )
  1617. {
  1618. if( aPolySet.OutlineCount() == 0 )
  1619. return false;
  1620. wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
  1621. // Outlines can only have one polygon according to the IPC-2581 spec, so
  1622. // if there are more than one, we need to combine them into a single polygon
  1623. const SHAPE_LINE_CHAIN* outline = &aPolySet.Outline( 0 );
  1624. SHAPE_LINE_CHAIN bbox_outline;
  1625. BOX2I bbox = outline->BBox();
  1626. if( aPolySet.OutlineCount() > 1 )
  1627. {
  1628. for( int ii = 1; ii < aPolySet.OutlineCount(); ++ii )
  1629. {
  1630. wxCHECK2( aPolySet.Outline( ii ).PointCount() >= 3, continue );
  1631. bbox.Merge( aPolySet.Outline( ii ).BBox() );
  1632. }
  1633. bbox_outline.Append( bbox.GetLeft(), bbox.GetTop() );
  1634. bbox_outline.Append( bbox.GetRight(), bbox.GetTop() );
  1635. bbox_outline.Append( bbox.GetRight(), bbox.GetBottom() );
  1636. bbox_outline.Append( bbox.GetLeft(), bbox.GetBottom() );
  1637. outline = &bbox_outline;
  1638. }
  1639. if( !addPolygonNode( outlineNode, *outline ) )
  1640. wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
  1641. if( !outlineNode->GetChildren() )
  1642. {
  1643. aParentNode->RemoveChild( outlineNode );
  1644. delete outlineNode;
  1645. return false;
  1646. }
  1647. addLineDesc( outlineNode, aWidth, aDashType );
  1648. return true;
  1649. }
  1650. bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
  1651. int aOutline, FILL_T aFillType, int aWidth,
  1652. LINE_STYLE aDashType )
  1653. {
  1654. if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
  1655. return false;
  1656. wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
  1657. if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
  1658. {
  1659. // Do not attempt to add cutouts to shapes that are already hollow
  1660. if( aFillType != FILL_T::NO_FILL )
  1661. addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
  1662. }
  1663. else
  1664. {
  1665. aParentNode->RemoveChild( contourNode );
  1666. delete contourNode;
  1667. return false;
  1668. }
  1669. return true;
  1670. }
  1671. void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
  1672. {
  1673. SHAPE_POLY_SET board_outline;
  1674. if( ! m_board->GetBoardPolygonOutlines( board_outline ) )
  1675. {
  1676. Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
  1677. return;
  1678. }
  1679. wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
  1680. if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
  1681. {
  1682. wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
  1683. aStepNode->RemoveChild( profileNode );
  1684. delete profileNode;
  1685. }
  1686. }
  1687. static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
  1688. {
  1689. if( !aFootprint )
  1690. return false;
  1691. if( aLayer != F_SilkS && aLayer != B_SilkS )
  1692. return false;
  1693. if( aFootprint->IsFlipped() )
  1694. return aLayer == F_SilkS;
  1695. return aLayer == B_SilkS;
  1696. }
  1697. wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
  1698. {
  1699. std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
  1700. fp->SetParentGroup( nullptr );
  1701. fp->SetPosition( { 0, 0 } );
  1702. fp->SetOrientation( ANGLE_0 );
  1703. size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
  1704. wxString name = genString( wxString::Format( "%s_%zu",
  1705. fp->GetFPID().GetLibItemName().wx_str(),
  1706. m_footprint_dict.size() + 1 ) );
  1707. auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
  1708. addAttribute( aContentNode, "packageRef", iter->second );
  1709. if( !success)
  1710. return nullptr;
  1711. // Package and Component nodes are at the same level, so we need to find the parent
  1712. // which should be the Step node
  1713. wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
  1714. wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
  1715. addAttribute( packageNode, "name", name );
  1716. addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
  1717. // We don't specially identify pin 1 in our footprints, so we need to guess
  1718. if( fp->FindPadByNumber( "1" ) )
  1719. addAttribute( packageNode, "pinOne", "1" );
  1720. else if ( fp->FindPadByNumber( "A1" ) )
  1721. addAttribute( packageNode, "pinOne", "A1" );
  1722. else if ( fp->FindPadByNumber( "A" ) )
  1723. addAttribute( packageNode, "pinOne", "A" );
  1724. else if ( fp->FindPadByNumber( "a" ) )
  1725. addAttribute( packageNode, "pinOne", "a" );
  1726. else if ( fp->FindPadByNumber( "a1" ) )
  1727. addAttribute( packageNode, "pinOne", "a1" );
  1728. else if ( fp->FindPadByNumber( "Anode" ) )
  1729. addAttribute( packageNode, "pinOne", "Anode" );
  1730. else if ( fp->FindPadByNumber( "ANODE" ) )
  1731. addAttribute( packageNode, "pinOne", "ANODE" );
  1732. else
  1733. addAttribute( packageNode, "pinOne", "UNKNOWN" );
  1734. addAttribute( packageNode, "pinOneOrientation", "OTHER" );
  1735. const SHAPE_POLY_SET& courtyard = fp->GetCourtyard( F_CrtYd );
  1736. const SHAPE_POLY_SET& courtyard_back = fp->GetCourtyard( B_CrtYd );
  1737. if( courtyard.OutlineCount() > 0 )
  1738. addOutlineNode( packageNode, courtyard, courtyard.Outline( 0 ).Width(), LINE_STYLE::SOLID );
  1739. if( courtyard_back.OutlineCount() > 0 )
  1740. {
  1741. if( m_version > 'B' )
  1742. {
  1743. otherSideViewNode = appendNode( packageNode, "OtherSideView" );
  1744. addOutlineNode( otherSideViewNode, courtyard_back, courtyard_back.Outline( 0 ).Width(),
  1745. LINE_STYLE::SOLID );
  1746. }
  1747. }
  1748. if( !courtyard.OutlineCount() && !courtyard_back.OutlineCount() )
  1749. {
  1750. SHAPE_POLY_SET bbox = fp->GetBoundingHull();
  1751. addOutlineNode( packageNode, bbox );
  1752. }
  1753. wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
  1754. addAttribute( pickupPointNode, "x", "0.0" );
  1755. addAttribute( pickupPointNode, "y", "0.0" );
  1756. std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
  1757. for( BOARD_ITEM* item : fp->GraphicalItems() )
  1758. {
  1759. PCB_LAYER_ID layer = item->GetLayer();
  1760. /// IPC2581 only supports the documentation layers for production and post-production
  1761. /// All other layers are ignored
  1762. /// TODO: Decide if we should place the other layers from footprints on the board
  1763. if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
  1764. continue;
  1765. if( m_version == 'B' && isOppositeSideSilk( fp.get(), layer ) )
  1766. continue;
  1767. bool is_abs = true;
  1768. if( item->Type() == PCB_SHAPE_T )
  1769. {
  1770. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  1771. // Circles and Rectanges only have size information so we need to place them in
  1772. // a separate node that has a location
  1773. if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
  1774. is_abs = false;
  1775. }
  1776. elements[item->GetLayer()][is_abs].push_back( item );
  1777. }
  1778. auto add_base_node =
  1779. [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
  1780. {
  1781. wxXmlNode* parent = packageNode;
  1782. bool is_back = aLayer == B_SilkS || aLayer == B_Fab;
  1783. if( is_back && m_version > 'B' )
  1784. {
  1785. if( !otherSideViewNode )
  1786. otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
  1787. parent = otherSideViewNode;
  1788. }
  1789. wxString name;
  1790. if( aLayer == F_SilkS || aLayer == B_SilkS )
  1791. name = "SilkScreen";
  1792. else if( aLayer == F_Fab || aLayer == B_Fab )
  1793. name = "AssemblyDrawing";
  1794. else
  1795. wxASSERT( false );
  1796. wxXmlNode* new_node = appendNode( parent, name );
  1797. return new_node;
  1798. };
  1799. auto add_marking_node =
  1800. [&]( wxXmlNode* aNode ) -> wxXmlNode*
  1801. {
  1802. wxXmlNode* marking_node = appendNode( aNode, "Marking" );
  1803. addAttribute( marking_node, "markingUsage", "NONE" );
  1804. return marking_node;
  1805. };
  1806. std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
  1807. std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
  1808. for( auto layer : { F_Fab, B_Fab } )
  1809. {
  1810. if( elements.find( layer ) != elements.end() )
  1811. {
  1812. if( elements[layer][true].size() > 0 )
  1813. layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
  1814. else if( elements[layer][false].size() > 0 )
  1815. layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
  1816. }
  1817. }
  1818. for( auto& [layer, map] : elements )
  1819. {
  1820. wxXmlNode* layer_node = add_base_node( layer );
  1821. wxXmlNode* marking_node = add_marking_node( layer_node );
  1822. wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
  1823. bool update_bbox = false;
  1824. if( layer == F_Fab || layer == B_Fab )
  1825. {
  1826. layer_nodes[layer] = layer_node;
  1827. update_bbox = true;
  1828. }
  1829. for( auto& [is_abs, vec] : map )
  1830. {
  1831. for( BOARD_ITEM* item : vec )
  1832. {
  1833. wxXmlNode* output_node = nullptr;
  1834. if( update_bbox )
  1835. layer_bbox[layer].Merge( item->GetBoundingBox() );
  1836. if( !is_abs )
  1837. output_node = add_marking_node( layer_node );
  1838. else
  1839. output_node = group_node;
  1840. switch( item->Type() )
  1841. {
  1842. case PCB_TEXT_T:
  1843. {
  1844. PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
  1845. if( text->IsKnockout() )
  1846. addKnockoutText( output_node, text );
  1847. else
  1848. addText( output_node, text, text->GetFontMetrics() );
  1849. break;
  1850. }
  1851. case PCB_TEXTBOX_T:
  1852. {
  1853. PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
  1854. addText( output_node, text, text->GetFontMetrics() );
  1855. // We want to force this to be a polygon to get absolute coordinates
  1856. if( text->IsBorderEnabled() )
  1857. {
  1858. SHAPE_POLY_SET poly_set;
  1859. text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
  1860. addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
  1861. text->GetBorderWidth() );
  1862. }
  1863. break;
  1864. }
  1865. case PCB_SHAPE_T:
  1866. {
  1867. if( !is_abs )
  1868. addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
  1869. addShape( output_node, *static_cast<PCB_SHAPE*>( item ) );
  1870. break;
  1871. }
  1872. default: break;
  1873. }
  1874. }
  1875. }
  1876. if( group_node->GetChildren() == nullptr )
  1877. {
  1878. marking_node->RemoveChild( group_node );
  1879. layer_node->RemoveChild( marking_node );
  1880. delete group_node;
  1881. delete marking_node;
  1882. }
  1883. }
  1884. for( auto&[layer, bbox] : layer_bbox )
  1885. {
  1886. if( bbox.GetWidth() > 0 )
  1887. {
  1888. wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
  1889. SHAPE_LINE_CHAIN outline;
  1890. std::vector<VECTOR2I> points( 4 );
  1891. points[0] = bbox.GetPosition();
  1892. points[2] = bbox.GetEnd();
  1893. points[1].x = points[0].x;
  1894. points[1].y = points[2].y;
  1895. points[3].x = points[2].x;
  1896. points[3].y = points[0].y;
  1897. outline.Append( points );
  1898. addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
  1899. addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
  1900. }
  1901. }
  1902. std::map<wxString, wxXmlNode*> pin_nodes;
  1903. for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
  1904. {
  1905. PAD* pad = fp->Pads()[ii];
  1906. wxString name = pinName( pad );
  1907. wxXmlNode* pinNode = nullptr;
  1908. auto [ it, inserted ] = pin_nodes.emplace( name, nullptr );
  1909. if( inserted )
  1910. {
  1911. pinNode = appendNode( packageNode, "Pin" );
  1912. it->second = pinNode;
  1913. addAttribute( pinNode, "number", name );
  1914. m_net_pin_dict[pad->GetNetCode()].emplace_back(
  1915. genString( fp->GetReference(), "CMP" ), name );
  1916. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  1917. addAttribute( pinNode, "electricalType", "MECHANICAL" );
  1918. else if( pad->IsOnCopperLayer() )
  1919. addAttribute( pinNode, "electricalType", "ELECTRICAL" );
  1920. else
  1921. addAttribute( pinNode, "electricalType", "UNDEFINED" );
  1922. if( pad->HasHole() )
  1923. addAttribute( pinNode, "type", "THRU" );
  1924. else
  1925. addAttribute( pinNode, "type", "SURFACE" );
  1926. if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
  1927. {
  1928. wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
  1929. EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
  1930. if( fp->IsFlipped() )
  1931. pad_angle = ( pad_angle.Invert() - ANGLE_180 ).Normalize();
  1932. if( pad_angle != ANGLE_0 )
  1933. xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
  1934. }
  1935. }
  1936. else
  1937. {
  1938. pinNode = it->second;
  1939. }
  1940. addLocationNode( pinNode, *pad, true );
  1941. addShape( pinNode, *pad, pad->GetLayer() );
  1942. // We just need the padstack, we don't need the reference here. The reference will be
  1943. // created in the LayerFeature set
  1944. wxXmlNode dummy;
  1945. addPadStack( &dummy, pad );
  1946. }
  1947. return packageNode;
  1948. }
  1949. void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
  1950. {
  1951. std::vector<wxXmlNode*> componentNodes;
  1952. std::vector<wxXmlNode*> packageNodes;
  1953. std::set<wxString> packageNames;
  1954. bool generate_unique = m_OEMRef.empty();
  1955. for( FOOTPRINT* fp : m_board->Footprints() )
  1956. {
  1957. wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
  1958. addAttribute( componentNode, "refDes", componentName( fp ) );
  1959. wxXmlNode* pkg = addPackage( componentNode, fp );
  1960. if( pkg )
  1961. packageNodes.push_back( pkg );
  1962. wxString name;
  1963. PCB_FIELD* field = nullptr;
  1964. if( !generate_unique )
  1965. field = fp->GetFieldByName( m_OEMRef );
  1966. if( field && !field->GetText().empty() )
  1967. {
  1968. name = field->GetShownText( false );
  1969. }
  1970. else
  1971. {
  1972. name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(),
  1973. fp->GetFPID().GetLibItemName().wx_str(),
  1974. fp->GetValue() );
  1975. }
  1976. if( !m_OEMRef_dict.emplace( fp, name ).second )
  1977. Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
  1978. RPT_SEVERITY_ERROR );
  1979. addAttribute( componentNode, "part", genString( name, "REF" ) );
  1980. addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
  1981. if( fp->GetAttributes() & FP_THROUGH_HOLE )
  1982. addAttribute( componentNode, "mountType", "THMT" );
  1983. else if( fp->GetAttributes() & FP_SMD )
  1984. addAttribute( componentNode, "mountType", "SMT" );
  1985. else
  1986. addAttribute( componentNode, "mountType", "OTHER" );
  1987. if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
  1988. {
  1989. wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
  1990. EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
  1991. if( fp->IsFlipped() )
  1992. fp_angle = ( fp_angle.Invert() - ANGLE_180 ).Normalize();
  1993. if( fp_angle != ANGLE_0 )
  1994. addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
  1995. if( fp->IsFlipped() )
  1996. addAttribute( xformNode, "mirror", "true" );
  1997. }
  1998. addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
  1999. componentNodes.push_back( componentNode );
  2000. }
  2001. for( wxXmlNode* padstack : m_padstacks )
  2002. {
  2003. insertNode( aStepNode, padstack );
  2004. m_last_padstack = padstack;
  2005. }
  2006. for( wxXmlNode* pkg : packageNodes )
  2007. aStepNode->AddChild( pkg );
  2008. for( wxXmlNode* cmp : componentNodes )
  2009. aStepNode->AddChild( cmp );
  2010. }
  2011. void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
  2012. {
  2013. for( auto& [ net, pin_pair] : m_net_pin_dict )
  2014. {
  2015. wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
  2016. addAttribute( netNode, "name",
  2017. genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ;
  2018. for( auto& [cmp, pin] : pin_pair )
  2019. {
  2020. wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
  2021. addAttribute( netPinNode, "componentRef", cmp );
  2022. addAttribute( netPinNode, "pin", pin );
  2023. }
  2024. //TODO: Finish
  2025. }
  2026. }
  2027. //TODO: Add PhyNetGroup section
  2028. void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
  2029. {
  2030. LSEQ layers = m_board->GetEnabledLayers().Seq();
  2031. const NETINFO_LIST& nets = m_board->GetNetInfo();
  2032. std::vector<std::unique_ptr<FOOTPRINT>> footprints;
  2033. // To avoid the overhead of repeatedly cycling through the layers and nets,
  2034. // we pre-sort the board items into a map of layer -> net -> items
  2035. std::map<PCB_LAYER_ID, std::map<int, std::vector<BOARD_ITEM*>>> elements;
  2036. std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(),
  2037. [&layers, &elements]( PCB_TRACK* aTrack )
  2038. {
  2039. if( aTrack->Type() == PCB_VIA_T )
  2040. {
  2041. PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
  2042. for( PCB_LAYER_ID layer : layers )
  2043. {
  2044. if( via->FlashLayer( layer ) )
  2045. elements[layer][via->GetNetCode()].push_back( via );
  2046. }
  2047. }
  2048. else
  2049. {
  2050. elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack );
  2051. }
  2052. } );
  2053. std::for_each( m_board->Zones().begin(), m_board->Zones().end(),
  2054. [ &elements ]( ZONE* zone )
  2055. {
  2056. LSEQ zone_layers = zone->GetLayerSet().Seq();
  2057. for( PCB_LAYER_ID layer : zone_layers )
  2058. elements[layer][zone->GetNetCode()].push_back( zone );
  2059. } );
  2060. for( BOARD_ITEM* item : m_board->Drawings() )
  2061. {
  2062. if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
  2063. elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it );
  2064. else
  2065. elements[item->GetLayer()][0].push_back( item );
  2066. }
  2067. for( FOOTPRINT* fp : m_board->Footprints() )
  2068. {
  2069. for( PCB_FIELD* field : fp->GetFields() )
  2070. elements[field->GetLayer()][0].push_back( field );
  2071. for( BOARD_ITEM* item : fp->GraphicalItems() )
  2072. elements[item->GetLayer()][0].push_back( item );
  2073. for( PAD* pad : fp->Pads() )
  2074. {
  2075. LSEQ pad_layers = pad->GetLayerSet().Seq();
  2076. for( PCB_LAYER_ID layer : pad_layers )
  2077. {
  2078. if( pad->FlashLayer( layer ) )
  2079. elements[layer][pad->GetNetCode()].push_back( pad );
  2080. }
  2081. }
  2082. }
  2083. for( PCB_LAYER_ID layer : layers )
  2084. {
  2085. if( m_progressReporter )
  2086. m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
  2087. wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
  2088. addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
  2089. auto process_net = [&] ( int net )
  2090. {
  2091. std::vector<BOARD_ITEM*>& vec = elements[layer][net];
  2092. if( vec.empty() )
  2093. return;
  2094. std::stable_sort( vec.begin(), vec.end(),
  2095. []( BOARD_ITEM* a, BOARD_ITEM* b )
  2096. {
  2097. if( a->GetParentFootprint() == b->GetParentFootprint() )
  2098. return a->Type() < b->Type();
  2099. return a->GetParentFootprint() < b->GetParentFootprint();
  2100. } );
  2101. generateLayerSetNet( layerNode, layer, vec );
  2102. };
  2103. for( const NETINFO_ITEM* net : nets )
  2104. {
  2105. if( m_progressReporter )
  2106. {
  2107. m_progressReporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ),
  2108. m_board->GetLayerName( layer ),
  2109. net->GetNetname() ) );
  2110. m_progressReporter->AdvanceProgress();
  2111. }
  2112. process_net( net->GetNetCode() );
  2113. }
  2114. if( layerNode->GetChildren() == nullptr )
  2115. {
  2116. aStepNode->RemoveChild( layerNode );
  2117. delete layerNode;
  2118. }
  2119. }
  2120. }
  2121. void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
  2122. {
  2123. int hole_count = 1;
  2124. for( const auto& [layers, vec] : m_drill_layers )
  2125. {
  2126. wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
  2127. layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "DRILL" ) );
  2128. for( BOARD_ITEM* item : vec )
  2129. {
  2130. if( item->Type() == PCB_VIA_T )
  2131. {
  2132. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  2133. auto it = m_padstack_dict.find( hash_fp_item( via, 0 ) );
  2134. if( it == m_padstack_dict.end() )
  2135. {
  2136. Report( _( "Via uses unsupported padstack; omitted from drill data." ),
  2137. RPT_SEVERITY_WARNING );
  2138. continue;
  2139. }
  2140. wxXmlNode* padNode = appendNode( layerNode, "Set" );
  2141. addAttribute( padNode, "geometry", it->second );
  2142. if( via->GetNetCode() > 0 )
  2143. addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) );
  2144. wxXmlNode* holeNode = appendNode( padNode, "Hole" );
  2145. addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
  2146. addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) );
  2147. addAttribute( holeNode, "platingStatus", "VIA" );
  2148. addAttribute( holeNode, "plusTol", "0.0" );
  2149. addAttribute( holeNode, "minusTol", "0.0" );
  2150. addXY( holeNode, via->GetPosition() );
  2151. }
  2152. else if( item->Type() == PCB_PAD_T )
  2153. {
  2154. PAD* pad = static_cast<PAD*>( item );
  2155. auto it = m_padstack_dict.find( hash_fp_item( pad, 0 ) );
  2156. if( it == m_padstack_dict.end() )
  2157. {
  2158. Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
  2159. RPT_SEVERITY_WARNING );
  2160. continue;
  2161. }
  2162. wxXmlNode* padNode = appendNode( layerNode, "Set" );
  2163. addAttribute( padNode, "geometry", it->second );
  2164. if( pad->GetNetCode() > 0 )
  2165. addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
  2166. wxXmlNode* holeNode = appendNode( padNode, "Hole" );
  2167. addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
  2168. addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) );
  2169. addAttribute( holeNode, "platingStatus",
  2170. pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
  2171. addAttribute( holeNode, "plusTol", "0.0" );
  2172. addAttribute( holeNode, "minusTol", "0.0" );
  2173. addXY( holeNode, pad->GetPosition() );
  2174. }
  2175. }
  2176. }
  2177. hole_count = 1;
  2178. for( const auto& [layers, vec] : m_slot_holes )
  2179. {
  2180. wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
  2181. layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
  2182. for( PAD* pad : vec )
  2183. {
  2184. wxXmlNode* padNode = appendNode( layerNode, "Set" );
  2185. if( pad->GetNetCode() > 0 )
  2186. addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
  2187. addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
  2188. }
  2189. }
  2190. }
  2191. void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
  2192. std::vector<BOARD_ITEM*>& aItems )
  2193. {
  2194. auto it = aItems.begin();
  2195. wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
  2196. wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
  2197. wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
  2198. bool has_via = false;
  2199. bool has_pad = false;
  2200. wxXmlNode* padSetNode = nullptr;
  2201. wxXmlNode* viaSetNode = nullptr;
  2202. wxXmlNode* teardropLayerSetNode = nullptr;
  2203. wxXmlNode* teardropFeatureSetNode = nullptr;
  2204. bool teardrop_warning = false;
  2205. if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
  2206. IsCopperLayer( aLayer ) && item )
  2207. {
  2208. if( item->GetNetCode() > 0 )
  2209. addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) );
  2210. }
  2211. auto add_track =
  2212. [&]( PCB_TRACK* track )
  2213. {
  2214. if( track->Type() == PCB_TRACE_T )
  2215. {
  2216. PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
  2217. shape.SetStart( track->GetStart() );
  2218. shape.SetEnd( track->GetEnd() );
  2219. shape.SetWidth( track->GetWidth() );
  2220. addShape( specialNode, shape );
  2221. }
  2222. else if( track->Type() == PCB_ARC_T )
  2223. {
  2224. PCB_ARC* arc = static_cast<PCB_ARC*>( track );
  2225. PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
  2226. shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
  2227. shape.SetWidth( arc->GetWidth() );
  2228. addShape( specialNode, shape );
  2229. }
  2230. else
  2231. {
  2232. if( !viaSetNode )
  2233. {
  2234. if( !has_pad )
  2235. {
  2236. viaSetNode = layerSetNode;
  2237. has_via = true;
  2238. }
  2239. else
  2240. {
  2241. viaSetNode = appendNode( layerSetNode, "Set" );
  2242. if( track->GetNetCode() > 0 )
  2243. addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) );
  2244. }
  2245. addAttribute( viaSetNode, "padUsage", "VIA" );
  2246. }
  2247. addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
  2248. }
  2249. };
  2250. auto add_zone =
  2251. [&]( ZONE* zone )
  2252. {
  2253. wxXmlNode* zoneFeatureNode = specialNode;
  2254. if( zone->IsTeardropArea() )
  2255. {
  2256. if( m_version > 'B' )
  2257. {
  2258. if( !teardropFeatureSetNode )
  2259. {
  2260. teardropLayerSetNode = appendNode( aLayerNode, "Set" );
  2261. addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
  2262. if( zone->GetNetCode() > 0 )
  2263. {
  2264. addAttribute( teardropLayerSetNode, "net",
  2265. genString( zone->GetNetname(), "NET" ) );
  2266. }
  2267. wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
  2268. addLocationNode( new_teardrops, 0.0, 0.0 );
  2269. teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
  2270. }
  2271. zoneFeatureNode = teardropFeatureSetNode;
  2272. }
  2273. else if( !teardrop_warning )
  2274. {
  2275. Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
  2276. RPT_SEVERITY_WARNING );
  2277. teardrop_warning = true;
  2278. }
  2279. }
  2280. else
  2281. {
  2282. if( FOOTPRINT* fp = zone->GetParentFootprint() )
  2283. {
  2284. wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
  2285. wxString refDes = componentName( zone->GetParentFootprint() );
  2286. addAttribute( tempSetNode, "componentRef", refDes );
  2287. wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
  2288. addLocationNode( newFeatures, 0.0, 0.0 );
  2289. zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
  2290. }
  2291. }
  2292. SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
  2293. for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
  2294. addContourNode( zoneFeatureNode, zone_shape, ii );
  2295. };
  2296. auto add_shape =
  2297. [&] ( PCB_SHAPE* shape )
  2298. {
  2299. FOOTPRINT* fp = shape->GetParentFootprint();
  2300. if( fp )
  2301. {
  2302. wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
  2303. if( m_version > 'B' )
  2304. addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
  2305. bool link_to_component = true;
  2306. if( m_version == 'B' && isOppositeSideSilk( fp, shape->GetLayer() ) )
  2307. link_to_component = false;
  2308. if( link_to_component )
  2309. addAttribute( tempSetNode, "componentRef", componentName( fp ) );
  2310. wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
  2311. addLocationNode( tempFeature, *shape );
  2312. addShape( tempFeature, *shape );
  2313. }
  2314. else if( shape->GetShape() == SHAPE_T::CIRCLE
  2315. || shape->GetShape() == SHAPE_T::RECTANGLE
  2316. || shape->GetShape() == SHAPE_T::POLY )
  2317. {
  2318. wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
  2319. if( shape->GetNetCode() > 0 )
  2320. addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) );
  2321. wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
  2322. addLocationNode( tempFeature, *shape );
  2323. addShape( tempFeature, *shape );
  2324. }
  2325. else
  2326. {
  2327. addShape( specialNode, *shape );
  2328. }
  2329. };
  2330. auto add_text =
  2331. [&] ( BOARD_ITEM* text )
  2332. {
  2333. EDA_TEXT* text_item;
  2334. FOOTPRINT* fp = text->GetParentFootprint();
  2335. if( PCB_TEXT* tmp_text = dynamic_cast<PCB_TEXT*>( text ) )
  2336. text_item = static_cast<EDA_TEXT*>( tmp_text );
  2337. else if( PCB_TEXTBOX* tmp_text = dynamic_cast<PCB_TEXTBOX*>( text ) )
  2338. text_item = static_cast<EDA_TEXT*>( tmp_text );
  2339. if( !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
  2340. return;
  2341. wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
  2342. if( m_version > 'B' )
  2343. addAttribute( tempSetNode, "geometryUsage", "TEXT" );
  2344. bool link_to_component = fp != nullptr;
  2345. if( m_version == 'B' && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
  2346. link_to_component = false;
  2347. if( link_to_component )
  2348. addAttribute( tempSetNode, "componentRef", componentName( fp ) );
  2349. wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
  2350. addAttribute( nonStandardAttributeNode, "name", "TEXT" );
  2351. addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) );
  2352. addAttribute( nonStandardAttributeNode, "type", "STRING" );
  2353. wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
  2354. addLocationNode( tempFeature, 0.0, 0.0 );
  2355. if( text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout() )
  2356. addKnockoutText( tempFeature, static_cast<PCB_TEXT*>( text ) );
  2357. else
  2358. addText( tempFeature, text_item, text->GetFontMetrics() );
  2359. if( text->Type() == PCB_TEXTBOX_T )
  2360. {
  2361. PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( text );
  2362. if( textbox->IsBorderEnabled() )
  2363. addShape( tempFeature, *static_cast<PCB_SHAPE*>( textbox ) );
  2364. }
  2365. };
  2366. auto add_pad =
  2367. [&]( PAD* pad )
  2368. {
  2369. if( !padSetNode )
  2370. {
  2371. if( !has_via )
  2372. {
  2373. padSetNode = layerSetNode;
  2374. has_pad = true;
  2375. }
  2376. else
  2377. {
  2378. padSetNode = appendNode( aLayerNode, "Set" );
  2379. if( pad->GetNetCode() > 0 )
  2380. addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) );
  2381. }
  2382. }
  2383. FOOTPRINT* fp = pad->GetParentFootprint();
  2384. if( fp && fp->IsFlipped() )
  2385. addPad( padSetNode, pad, FlipLayer( aLayer ) );
  2386. else
  2387. addPad( padSetNode, pad, aLayer );
  2388. };
  2389. for( BOARD_ITEM* item : aItems )
  2390. {
  2391. switch( item->Type() )
  2392. {
  2393. case PCB_TRACE_T:
  2394. case PCB_ARC_T:
  2395. case PCB_VIA_T:
  2396. add_track( static_cast<PCB_TRACK*>( item ) );
  2397. break;
  2398. case PCB_ZONE_T:
  2399. add_zone( static_cast<ZONE*>( item ) );
  2400. break;
  2401. case PCB_PAD_T:
  2402. add_pad( static_cast<PAD*>( item ) );
  2403. break;
  2404. case PCB_SHAPE_T:
  2405. add_shape( static_cast<PCB_SHAPE*>( item ) );
  2406. break;
  2407. case PCB_TEXT_T:
  2408. case PCB_TEXTBOX_T:
  2409. case PCB_FIELD_T:
  2410. add_text( item );
  2411. break;
  2412. case PCB_DIMENSION_T:
  2413. case PCB_TARGET_T:
  2414. case PCB_DIM_ALIGNED_T:
  2415. case PCB_DIM_LEADER_T:
  2416. case PCB_DIM_CENTER_T:
  2417. case PCB_DIM_RADIAL_T:
  2418. case PCB_DIM_ORTHOGONAL_T:
  2419. //TODO: Add support for dimensions
  2420. break;
  2421. default:
  2422. wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
  2423. ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
  2424. }
  2425. }
  2426. if( specialNode->GetChildren() == nullptr )
  2427. {
  2428. featureSetNode->RemoveChild( specialNode );
  2429. delete specialNode;
  2430. }
  2431. if( featureSetNode->GetChildren() == nullptr )
  2432. {
  2433. layerSetNode->RemoveChild( featureSetNode );
  2434. delete featureSetNode;
  2435. }
  2436. if( layerSetNode->GetChildren() == nullptr )
  2437. {
  2438. aLayerNode->RemoveChild( layerSetNode );
  2439. delete layerSetNode;
  2440. }
  2441. }
  2442. wxXmlNode* PCB_IO_IPC2581::generateAvlSection()
  2443. {
  2444. if( m_progressReporter )
  2445. m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
  2446. wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
  2447. addAttribute( avl, "name", "Primary_Vendor_List" );
  2448. wxXmlNode* header = appendNode( avl, "AvlHeader" );
  2449. addAttribute( header, "title", "BOM" );
  2450. addAttribute( header, "source", "KiCad" );
  2451. addAttribute( header, "author", "OWNER" );
  2452. addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
  2453. addAttribute( header, "version", "1" );
  2454. std::set<wxString> unique_parts;
  2455. std::map<wxString,wxString> unique_vendors;
  2456. for( auto& [fp, name] : m_OEMRef_dict )
  2457. {
  2458. auto [ it, success ] = unique_parts.insert( name );
  2459. if( !success )
  2460. continue;
  2461. wxXmlNode* part = appendNode( avl, "AvlItem" );
  2462. addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
  2463. PCB_FIELD* nums[2] = { fp->GetFieldByName( m_mpn ), fp->GetFieldByName( m_distpn ) };
  2464. PCB_FIELD* company[2] = { fp->GetFieldByName( m_mfg ), nullptr };
  2465. wxString company_name[2] = { m_mfg, m_dist };
  2466. for ( int ii = 0; ii < 2; ++ii )
  2467. {
  2468. if( nums[ii] )
  2469. {
  2470. wxString mpn_name = nums[ii]->GetShownText( false );
  2471. if( mpn_name.empty() )
  2472. continue;
  2473. wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
  2474. addAttribute( vmpn, "qualified", "false" );
  2475. addAttribute( vmpn, "chosen", "false" );
  2476. wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
  2477. addAttribute( mpn, "name", mpn_name );
  2478. wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
  2479. wxString name = wxT( "UNKNOWN" );
  2480. // If the field resolves, then use that field content unless it is empty
  2481. if( !ii && company[ii] )
  2482. {
  2483. wxString tmp = company[ii]->GetShownText( false );
  2484. if( !tmp.empty() )
  2485. name = tmp;
  2486. }
  2487. // If it doesn't resolve but there is content from the dialog, use the static content
  2488. else if( !ii && !company_name[ii].empty() )
  2489. {
  2490. name = company_name[ii];
  2491. }
  2492. else if( ii && !m_dist.empty() )
  2493. {
  2494. name = m_dist;
  2495. }
  2496. auto [vendor_id, inserted] = unique_vendors.emplace(
  2497. name,
  2498. wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
  2499. addAttribute( vendor, "enterpriseRef", vendor_id->second );
  2500. if( inserted )
  2501. {
  2502. wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
  2503. addAttribute( new_vendor, "id", vendor_id->second );
  2504. addAttribute( new_vendor, "name", name );
  2505. addAttribute( new_vendor, "code", "NONE" );
  2506. insertNodeAfter( m_enterpriseNode, new_vendor );
  2507. m_enterpriseNode = new_vendor;
  2508. }
  2509. }
  2510. }
  2511. }
  2512. return avl;
  2513. }
  2514. void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
  2515. const std::map<std::string, UTF8>* aProperties )
  2516. {
  2517. m_board = aBoard;
  2518. m_units_str = "MILLIMETER";
  2519. m_scale = 1.0 / PCB_IU_PER_MM;
  2520. m_sigfig = 6;
  2521. if( auto it = aProperties->find( "units" ); it != aProperties->end() )
  2522. {
  2523. if( it->second == "inch" )
  2524. {
  2525. m_units_str = "INCH";
  2526. m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
  2527. }
  2528. }
  2529. if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
  2530. m_sigfig = std::stoi( it->second );
  2531. if( auto it = aProperties->find( "version" ); it != aProperties->end() )
  2532. m_version = it->second.c_str()[0];
  2533. if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
  2534. m_OEMRef = it->second.wx_str();
  2535. if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
  2536. m_mpn = it->second.wx_str();
  2537. if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
  2538. m_mfg = it->second.wx_str();
  2539. if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
  2540. m_dist = it->second.wx_str();
  2541. if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
  2542. m_distpn = it->second.wx_str();
  2543. if( m_version == 'B' )
  2544. {
  2545. for( char c = 'a'; c <= 'z'; ++c )
  2546. m_acceptable_chars.insert( c );
  2547. for( char c = 'A'; c <= 'Z'; ++c )
  2548. m_acceptable_chars.insert( c );
  2549. for( char c = '0'; c <= '9'; ++c )
  2550. m_acceptable_chars.insert( c );
  2551. // Add special characters
  2552. std::string specialChars = "_\\-.+><";
  2553. for( char c : specialChars )
  2554. m_acceptable_chars.insert( c );
  2555. }
  2556. m_xml_doc = new wxXmlDocument();
  2557. m_xml_root = generateXmlHeader();
  2558. generateContentSection();
  2559. if( m_progressReporter )
  2560. {
  2561. m_progressReporter->SetNumPhases( 7 );
  2562. m_progressReporter->BeginPhase( 1 );
  2563. m_progressReporter->Report( _( "Generating logistic section" ) );
  2564. }
  2565. generateLogisticSection();
  2566. generateHistorySection();
  2567. wxXmlNode* ecad_node = generateEcadSection();
  2568. generateBOMSection( ecad_node );
  2569. generateAvlSection();
  2570. if( m_progressReporter )
  2571. {
  2572. m_progressReporter->AdvancePhase( _( "Saving file" ) );
  2573. }
  2574. wxFileOutputStreamWithProgress out_stream( aFileName );
  2575. double written_bytes = 0.0;
  2576. double last_yield = 0.0;
  2577. // This is a rough estimation of the size of the spaces in the file
  2578. // We just need to total to be slightly larger than the value of the
  2579. // progress bar, so accurately counting spaces is not terribly important
  2580. m_total_bytes += m_total_bytes / 10;
  2581. auto update_progress = [&]( size_t aBytes )
  2582. {
  2583. written_bytes += aBytes;
  2584. double percent = written_bytes / static_cast<double>( m_total_bytes );
  2585. if( m_progressReporter )
  2586. {
  2587. // Only update every percent
  2588. if( last_yield + 0.01 < percent )
  2589. {
  2590. last_yield = percent;
  2591. m_progressReporter->SetCurrentProgress( percent );
  2592. }
  2593. }
  2594. };
  2595. out_stream.SetProgressCallback( update_progress );
  2596. if( !m_xml_doc->Save( out_stream ) )
  2597. {
  2598. Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
  2599. return;
  2600. }
  2601. size_t size = out_stream.GetSize();
  2602. }