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.

588 lines
19 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Alexander Shuklin <Jasuramme@gmail.com>
  5. * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <backannotate.h>
  25. #include <boost/property_tree/ptree.hpp>
  26. #include <confirm.h>
  27. #include <dsnlexer.h>
  28. #include <ptree.h>
  29. #include <reporter.h>
  30. #include <sch_edit_frame.h>
  31. #include <sch_sheet_path.h>
  32. #include <schematic.h>
  33. #include <kicad_string.h>
  34. #include <kiface_i.h>
  35. #include <wildcards_and_files_ext.h>
  36. #include <connection_graph.h>
  37. #include <wx/log.h>
  38. BACK_ANNOTATE::BACK_ANNOTATE( SCH_EDIT_FRAME* aFrame, REPORTER& aReporter, bool aRelinkFootprints,
  39. bool aProcessFootprints, bool aProcessValues,
  40. bool aProcessReferences, bool aProcessNetNames, bool aDryRun ) :
  41. m_reporter( aReporter ),
  42. m_matchByReference( aRelinkFootprints ),
  43. m_processFootprints( aProcessFootprints ),
  44. m_processValues( aProcessValues ),
  45. m_processReferences( aProcessReferences ),
  46. m_processNetNames( aProcessNetNames ),
  47. m_dryRun( aDryRun ),
  48. m_frame( aFrame ),
  49. m_changesCount( 0 ),
  50. m_appendUndo( false )
  51. {
  52. }
  53. BACK_ANNOTATE::~BACK_ANNOTATE()
  54. {
  55. }
  56. bool BACK_ANNOTATE::BackAnnotateSymbols( const std::string& aNetlist )
  57. {
  58. m_changesCount = 0;
  59. m_appendUndo = false;
  60. wxString msg;
  61. if( !m_matchByReference && !m_processValues && !m_processFootprints && !m_processReferences
  62. && !m_processNetNames )
  63. {
  64. m_reporter.ReportTail( _( "Select at least one property to back annotate." ),
  65. RPT_SEVERITY_ERROR );
  66. return false;
  67. }
  68. getPcbModulesFromString( aNetlist );
  69. SCH_SHEET_LIST sheets = m_frame->Schematic().GetSheets();
  70. sheets.GetSymbols( m_refs, false );
  71. sheets.GetMultiUnitSymbols( m_multiUnitsRefs );
  72. getChangeList();
  73. checkForUnusedSymbols();
  74. applyChangelist();
  75. return true;
  76. }
  77. bool BACK_ANNOTATE::FetchNetlistFromPCB( std::string& aNetlist )
  78. {
  79. if( Kiface().IsSingle() )
  80. {
  81. DisplayErrorMessage( m_frame, _( "Cannot fetch PCB netlist because eeschema is opened "
  82. "in stand-alone mode.\n"
  83. "You must launch the KiCad project manager and create "
  84. "a project." ) );
  85. return false;
  86. }
  87. KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_PCB_EDITOR, false );
  88. if( !frame )
  89. {
  90. wxFileName fn( m_frame->Prj().GetProjectFullName() );
  91. fn.SetExt( PcbFileExtension );
  92. frame = m_frame->Kiway().Player( FRAME_PCB_EDITOR, true );
  93. frame->OpenProjectFiles( std::vector<wxString>( 1, fn.GetFullPath() ) );
  94. }
  95. m_frame->Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_PCB_GET_NETLIST, aNetlist );
  96. return true;
  97. }
  98. void BACK_ANNOTATE::PushNewLinksToPCB()
  99. {
  100. std::string nullPayload;
  101. m_frame->Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_PCB_UPDATE_LINKS, nullPayload );
  102. }
  103. void BACK_ANNOTATE::getPcbModulesFromString( const std::string& aPayload )
  104. {
  105. auto getStr = []( const PTREE& pt ) -> wxString
  106. {
  107. return UTF8( pt.front().first );
  108. };
  109. DSNLEXER lexer( aPayload, FROM_UTF8( __func__ ) );
  110. PTREE doc;
  111. // NOTE: KiCad's PTREE scanner constructs a property *name* tree, not a property tree.
  112. // Every token in the s-expr is stored as a property name; the property's value is then
  113. // either the nested s-exprs or an empty PTREE; there are *no* literal property values.
  114. Scan( &doc, &lexer );
  115. PTREE& tree = doc.get_child( "pcb_netlist" );
  116. wxString msg;
  117. m_pcbFootprints.clear();
  118. for( const std::pair<const std::string, PTREE>& item : tree )
  119. {
  120. wxString path, value, footprint;
  121. std::map<wxString, wxString> pinNetMap;
  122. wxASSERT( item.first == "ref" );
  123. wxString ref = getStr( item.second );
  124. try
  125. {
  126. if( m_matchByReference )
  127. path = ref;
  128. else
  129. path = getStr( item.second.get_child( "timestamp" ) );
  130. if( path == "" )
  131. {
  132. msg.Printf( _( "Footprint '%s' has no assigned symbol." ), ref );
  133. m_reporter.ReportHead( msg, RPT_SEVERITY_WARNING );
  134. continue;
  135. }
  136. footprint = getStr( item.second.get_child( "fpid" ) );
  137. value = getStr( item.second.get_child( "value" ) );
  138. boost::optional<const PTREE&> nets = item.second.get_child_optional( "nets" );
  139. if( nets )
  140. {
  141. for( const std::pair<const std::string, PTREE>& pin_net : nets.get() )
  142. {
  143. wxASSERT( pin_net.first == "pin_net" );
  144. wxString pinNumber = UTF8( pin_net.second.front().first );
  145. wxString netName = UTF8( pin_net.second.back().first );
  146. pinNetMap[ pinNumber ] = netName;
  147. }
  148. }
  149. }
  150. catch( ... )
  151. {
  152. wxLogWarning( "Cannot parse PCB netlist for back-annotation." );
  153. }
  154. // Use lower_bound for not to iterate over map twice
  155. auto nearestItem = m_pcbFootprints.lower_bound( path );
  156. if( nearestItem != m_pcbFootprints.end() && nearestItem->first == path )
  157. {
  158. // Module with this path already exists - generate error
  159. msg.Printf( _( "Footprints '%s' and '%s' linked to same symbol." ),
  160. nearestItem->second->m_ref,
  161. ref );
  162. m_reporter.ReportHead( msg, RPT_SEVERITY_ERROR );
  163. }
  164. else
  165. {
  166. // Add footprint to the map
  167. auto data = std::make_shared<PCB_FP_DATA>( ref, footprint, value, pinNetMap );
  168. m_pcbFootprints.insert( nearestItem, std::make_pair( path, data ) );
  169. }
  170. }
  171. }
  172. void BACK_ANNOTATE::getChangeList()
  173. {
  174. for( std::pair<const wxString, std::shared_ptr<PCB_FP_DATA>>& fpData : m_pcbFootprints )
  175. {
  176. const wxString& pcbPath = fpData.first;
  177. auto& pcbData = fpData.second;
  178. int refIndex;
  179. bool foundInMultiunit = false;
  180. for( std::pair<const wxString, SCH_REFERENCE_LIST>& item : m_multiUnitsRefs )
  181. {
  182. SCH_REFERENCE_LIST& refList = item.second;
  183. if( m_matchByReference )
  184. refIndex = refList.FindRef( pcbPath );
  185. else
  186. refIndex = refList.FindRefByPath( pcbPath );
  187. if( refIndex >= 0 )
  188. {
  189. // If footprint linked to multi unit symbol, we add all symbol's units to
  190. // the change list
  191. foundInMultiunit = true;
  192. for( size_t i = 0; i < refList.GetCount(); ++i )
  193. {
  194. refList[ i ].GetSymbol()->ClearFlags(SKIP_STRUCT );
  195. m_changelist.emplace_back( CHANGELIST_ITEM( refList[i], pcbData ) );
  196. }
  197. break;
  198. }
  199. }
  200. if( foundInMultiunit )
  201. continue;
  202. if( m_matchByReference )
  203. refIndex = m_refs.FindRef( pcbPath );
  204. else
  205. refIndex = m_refs.FindRefByPath( pcbPath );
  206. if( refIndex >= 0 )
  207. {
  208. m_refs[ refIndex ].GetSymbol()->ClearFlags( SKIP_STRUCT );
  209. m_changelist.emplace_back( CHANGELIST_ITEM( m_refs[refIndex], pcbData ) );
  210. }
  211. else
  212. {
  213. // Haven't found linked symbol in multiunits or common refs. Generate error
  214. wxString msg = wxString::Format( _( "Cannot find symbol for footprint '%s'." ),
  215. pcbData->m_ref );
  216. m_reporter.ReportTail( msg, RPT_SEVERITY_ERROR );
  217. }
  218. }
  219. }
  220. void BACK_ANNOTATE::checkForUnusedSymbols()
  221. {
  222. m_refs.SortByTimeStamp();
  223. std::sort( m_changelist.begin(), m_changelist.end(),
  224. []( const CHANGELIST_ITEM& a, const CHANGELIST_ITEM& b )
  225. {
  226. return SCH_REFERENCE_LIST::sortByTimeStamp( a.first, b.first );
  227. } );
  228. size_t i = 0;
  229. for( const std::pair<SCH_REFERENCE, std::shared_ptr<PCB_FP_DATA>>& item : m_changelist )
  230. {
  231. // Refs and changelist are both sorted by paths, so we just go over m_refs and
  232. // generate errors before we will find m_refs member to which item linked
  233. while( i < m_refs.GetCount() && m_refs[i].GetPath() != item.first.GetPath() )
  234. {
  235. const SCH_REFERENCE& ref = m_refs[i];
  236. if( ref.GetSymbol()->GetIncludeOnBoard() )
  237. {
  238. wxString msg = wxString::Format( _( "Footprint '%s' is not present on PCB. "
  239. "Corresponding symbols in schematic must be "
  240. "manually deleted (if desired)." ),
  241. m_refs[i].GetFullRef() );
  242. m_reporter.ReportTail( msg, RPT_SEVERITY_WARNING );
  243. }
  244. ++i;
  245. }
  246. ++i;
  247. }
  248. if( m_matchByReference && !m_frame->ReadyToNetlist( _( "Re-linking footprints requires a fully "
  249. "annotated schematic." ) ) )
  250. {
  251. m_reporter.ReportTail( _( "Footprint re-linking cancelled by user." ), RPT_SEVERITY_ERROR );
  252. }
  253. }
  254. void BACK_ANNOTATE::applyChangelist()
  255. {
  256. wxString msg;
  257. // Apply changes from change list
  258. for( CHANGELIST_ITEM& item : m_changelist )
  259. {
  260. SCH_REFERENCE& ref = item.first;
  261. PCB_FP_DATA& fpData = *item.second;
  262. SCH_COMPONENT* symbol = ref.GetSymbol();
  263. SCH_SCREEN* screen = ref.GetSheetPath().LastScreen();
  264. wxString oldFootprint = ref.GetFootprint();
  265. wxString oldValue = ref.GetValue();
  266. bool skip = ( ref.GetSymbol()->GetFlags() & SKIP_STRUCT ) > 0;
  267. if( m_processReferences && ref.GetRef() != fpData.m_ref && !skip )
  268. {
  269. ++m_changesCount;
  270. msg.Printf( _( "Change '%s' reference designator to '%s'." ),
  271. ref.GetFullRef(),
  272. fpData.m_ref );
  273. if( !m_dryRun )
  274. {
  275. m_frame->SaveCopyInUndoList( screen, symbol, UNDO_REDO::CHANGED, m_appendUndo );
  276. m_appendUndo = true;
  277. symbol->SetRef( &ref.GetSheetPath(), fpData.m_ref );
  278. }
  279. m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
  280. }
  281. if( m_processFootprints && oldFootprint != fpData.m_footprint && !skip )
  282. {
  283. ++m_changesCount;
  284. msg.Printf( _( "Change %s footprint assignment from '%s' to '%s'." ),
  285. ref.GetFullRef(),
  286. oldFootprint,
  287. fpData.m_footprint );
  288. if( !m_dryRun )
  289. {
  290. m_frame->SaveCopyInUndoList( screen, symbol, UNDO_REDO::CHANGED, m_appendUndo );
  291. m_appendUndo = true;
  292. symbol->SetFootprint( &ref.GetSheetPath(), fpData.m_footprint );
  293. }
  294. m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
  295. }
  296. if( m_processValues && oldValue != fpData.m_value && !skip )
  297. {
  298. ++m_changesCount;
  299. msg.Printf( _( "Change %s value from '%s' to '%s'." ),
  300. ref.GetFullRef(),
  301. oldValue,
  302. fpData.m_value );
  303. if( !m_dryRun )
  304. {
  305. m_frame->SaveCopyInUndoList( screen, symbol, UNDO_REDO::CHANGED, m_appendUndo );
  306. m_appendUndo = true;
  307. symbol->SetValue( &ref.GetSheetPath(), fpData.m_value );
  308. }
  309. m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
  310. }
  311. if( m_processNetNames )
  312. {
  313. for( const std::pair<const wxString, wxString>& entry : fpData.m_pinMap )
  314. {
  315. const wxString& pinNumber = entry.first;
  316. const wxString& shortNetName = entry.second;
  317. SCH_PIN* pin = symbol->GetPin( pinNumber );
  318. if( !pin )
  319. {
  320. msg.Printf( _( "Cannot find %s pin '%s'." ),
  321. ref.GetFullRef(),
  322. pinNumber );
  323. m_reporter.ReportHead( msg, RPT_SEVERITY_ERROR );
  324. continue;
  325. }
  326. SCH_CONNECTION* connection = pin->Connection( &ref.GetSheetPath() );
  327. if( connection && connection->Name( true ) != shortNetName )
  328. {
  329. processNetNameChange( ref.GetFullRef(), pin, connection,
  330. connection->Name( true ), shortNetName );
  331. }
  332. }
  333. }
  334. }
  335. if( !m_dryRun )
  336. {
  337. m_frame->RecalculateConnections( NO_CLEANUP );
  338. m_frame->UpdateNetHighlightStatus();
  339. }
  340. m_reporter.ReportHead( msg, RPT_SEVERITY_INFO );
  341. }
  342. static LABEL_SPIN_STYLE orientLabel( SCH_PIN* aPin )
  343. {
  344. LABEL_SPIN_STYLE spin = LABEL_SPIN_STYLE::RIGHT;
  345. // Initial orientation from the pin
  346. switch( aPin->GetLibPin()->GetOrientation() )
  347. {
  348. case PIN_UP: spin = LABEL_SPIN_STYLE::BOTTOM; break;
  349. case PIN_DOWN: spin = LABEL_SPIN_STYLE::UP; break;
  350. case PIN_LEFT: spin = LABEL_SPIN_STYLE::RIGHT; break;
  351. case PIN_RIGHT: spin = LABEL_SPIN_STYLE::LEFT; break;
  352. }
  353. // Reorient based on the actual symbol orientation now
  354. struct ORIENT
  355. {
  356. int flag;
  357. int n_rots;
  358. int mirror_x;
  359. int mirror_y;
  360. }
  361. orientations[] =
  362. {
  363. { CMP_ORIENT_0, 0, 0, 0 },
  364. { CMP_ORIENT_90, 1, 0, 0 },
  365. { CMP_ORIENT_180, 2, 0, 0 },
  366. { CMP_ORIENT_270, 3, 0, 0 },
  367. { CMP_MIRROR_X + CMP_ORIENT_0, 0, 1, 0 },
  368. { CMP_MIRROR_X + CMP_ORIENT_90, 1, 1, 0 },
  369. { CMP_MIRROR_Y, 0, 0, 1 },
  370. { CMP_MIRROR_X + CMP_ORIENT_270, 3, 1, 0 },
  371. { CMP_MIRROR_Y + CMP_ORIENT_0, 0, 0, 1 },
  372. { CMP_MIRROR_Y + CMP_ORIENT_90, 1, 0, 1 },
  373. { CMP_MIRROR_Y + CMP_ORIENT_180, 2, 0, 1 },
  374. { CMP_MIRROR_Y + CMP_ORIENT_270, 3, 0, 1 }
  375. };
  376. ORIENT o = orientations[ 0 ];
  377. SCH_COMPONENT* parentSymbol = aPin->GetParentSymbol();
  378. if( !parentSymbol )
  379. return spin;
  380. int symbolOrientation = parentSymbol->GetOrientation();
  381. for( auto& i : orientations )
  382. {
  383. if( i.flag == symbolOrientation )
  384. {
  385. o = i;
  386. break;
  387. }
  388. }
  389. for( int i = 0; i < o.n_rots; i++ )
  390. spin = spin.RotateCCW();
  391. if( o.mirror_x )
  392. spin = spin.MirrorX();
  393. if( o.mirror_y )
  394. spin = spin.MirrorY();
  395. return spin;
  396. }
  397. void addConnections( SCH_ITEM* aItem, const SCH_SHEET_PATH& aSheetPath,
  398. std::set<SCH_ITEM*>& connectedItems )
  399. {
  400. if( connectedItems.insert( aItem ).second )
  401. {
  402. for( SCH_ITEM* connectedItem : aItem->ConnectedItems( aSheetPath ) )
  403. addConnections( connectedItem, aSheetPath, connectedItems );
  404. }
  405. }
  406. void BACK_ANNOTATE::processNetNameChange( const wxString& aRef, SCH_PIN* aPin,
  407. const SCH_CONNECTION* aConnection,
  408. const wxString& aOldName, const wxString& aNewName )
  409. {
  410. wxString msg;
  411. // Find a physically-connected driver. We can't use the SCH_CONNECTION's m_driver because
  412. // it has already been resolved by merging subgraphs with the same label, etc., and our
  413. // name change may cause that resolution to change.
  414. std::set<SCH_ITEM*> connectedItems;
  415. SCH_ITEM* driver = nullptr;
  416. CONNECTION_SUBGRAPH::PRIORITY driverPriority = CONNECTION_SUBGRAPH::PRIORITY::NONE;
  417. addConnections( aPin, aConnection->Sheet(), connectedItems );
  418. for( SCH_ITEM* item : connectedItems )
  419. {
  420. CONNECTION_SUBGRAPH::PRIORITY priority = CONNECTION_SUBGRAPH::GetDriverPriority( item );
  421. if( priority > driverPriority )
  422. {
  423. driver = item;
  424. driverPriority = priority;
  425. }
  426. }
  427. switch( driver->Type() )
  428. {
  429. case SCH_LABEL_T:
  430. case SCH_GLOBAL_LABEL_T:
  431. case SCH_HIER_LABEL_T:
  432. case SCH_SHEET_PIN_T:
  433. ++m_changesCount;
  434. msg.Printf( _( "Change %s pin %s net label from '%s' to '%s'." ),
  435. aRef,
  436. aPin->GetNumber(),
  437. aOldName,
  438. aNewName );
  439. if( !m_dryRun )
  440. {
  441. SCH_SCREEN* screen = aConnection->Sheet().LastScreen();
  442. m_frame->SaveCopyInUndoList( screen, driver, UNDO_REDO::CHANGED, m_appendUndo );
  443. m_appendUndo = true;
  444. static_cast<SCH_TEXT*>( driver )->SetText( aNewName );
  445. }
  446. m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
  447. break;
  448. case SCH_PIN_T:
  449. {
  450. SCH_PIN* schPin = static_cast<SCH_PIN*>( driver );
  451. LABEL_SPIN_STYLE spin = orientLabel( schPin );
  452. if( schPin->IsPowerConnection() )
  453. {
  454. msg.Printf( _( "Net %s cannot be changed to '%s' because it is driven by a power "
  455. "pin." ), aOldName, aNewName );
  456. m_reporter.ReportHead( msg, RPT_SEVERITY_ERROR );
  457. break;
  458. }
  459. ++m_changesCount;
  460. msg.Printf( _( "Add label '%s' to %s pin %s net." ), aNewName, aRef, aPin->GetNumber() );
  461. if( !m_dryRun )
  462. {
  463. SCHEMATIC_SETTINGS& settings = m_frame->Schematic().Settings();
  464. SCH_LABEL* label = new SCH_LABEL( driver->GetPosition(), aNewName );
  465. label->SetParent( &m_frame->Schematic() );
  466. label->SetTextSize( wxSize( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );
  467. label->SetLabelSpinStyle( spin );
  468. label->SetFlags( IS_NEW );
  469. SCH_SCREEN* screen = aConnection->Sheet().LastScreen();
  470. m_frame->AddItemToScreenAndUndoList( screen, label, m_appendUndo );
  471. m_appendUndo = true;
  472. }
  473. m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
  474. }
  475. break;
  476. default:
  477. break;
  478. }
  479. }