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.

498 lines
14 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Joshua Redstone redstone at gmail.com
  5. * Copyright The 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 <bitmaps.h>
  25. #include <eda_draw_frame.h>
  26. #include <geometry/shape_compound.h>
  27. #include <board.h>
  28. #include <board_item.h>
  29. #include <footprint.h>
  30. #include <pcb_generator.h>
  31. #include <pcb_group.h>
  32. #include <confirm.h>
  33. #include <widgets/msgpanel.h>
  34. #include <view/view.h>
  35. #include <api/api_enums.h>
  36. #include <api/api_utils.h>
  37. #include <api/api_pcb_utils.h>
  38. #include <api/board/board_types.pb.h>
  39. #include <google/protobuf/any.pb.h>
  40. #include <wx/debug.h>
  41. PCB_GROUP::PCB_GROUP( BOARD_ITEM* aParent ) :
  42. BOARD_ITEM( aParent, PCB_GROUP_T )
  43. {
  44. }
  45. PCB_GROUP::PCB_GROUP( BOARD_ITEM* aParent, KICAD_T idtype, PCB_LAYER_ID aLayer ) :
  46. BOARD_ITEM( aParent, idtype, aLayer )
  47. {
  48. }
  49. void PCB_GROUP::Serialize( google::protobuf::Any &aContainer ) const
  50. {
  51. using namespace kiapi::board::types;
  52. Group group;
  53. group.mutable_id()->set_value( m_Uuid.AsStdString() );
  54. group.set_name( GetName().ToUTF8() );
  55. for( EDA_ITEM* item : GetItems() )
  56. {
  57. kiapi::common::types::KIID* itemId = group.add_items();
  58. itemId->set_value( item->m_Uuid.AsStdString() );
  59. }
  60. aContainer.PackFrom( group );
  61. }
  62. bool PCB_GROUP::Deserialize( const google::protobuf::Any &aContainer )
  63. {
  64. kiapi::board::types::Group group;
  65. if( !aContainer.UnpackTo( &group ) )
  66. return false;
  67. const_cast<KIID&>( m_Uuid ) = KIID( group.id().value() );
  68. SetName( wxString( group.name().c_str(), wxConvUTF8 ) );
  69. BOARD* board = GetBoard();
  70. if( !board )
  71. return false;
  72. for( const kiapi::common::types::KIID& itemId : group.items() )
  73. {
  74. KIID id( itemId.value() );
  75. if( BOARD_ITEM* item = board->ResolveItem( id, true ) )
  76. AddItem( item );
  77. }
  78. return true;
  79. }
  80. std::unordered_set<BOARD_ITEM*> PCB_GROUP::GetBoardItems() const
  81. {
  82. std::unordered_set<BOARD_ITEM*> items;
  83. for( EDA_ITEM* item : m_items )
  84. {
  85. if( item->IsBOARD_ITEM() )
  86. items.insert( static_cast<BOARD_ITEM*>( item ) );
  87. }
  88. return items;
  89. }
  90. /*
  91. * @return if not in the footprint editor and aItem is in a footprint, returns the
  92. * footprint's parent group. Otherwise, returns the aItem's parent group.
  93. */
  94. EDA_GROUP* getClosestGroup( BOARD_ITEM* aItem, bool isFootprintEditor )
  95. {
  96. if( !isFootprintEditor && aItem->GetParent() && aItem->GetParent()->Type() == PCB_FOOTPRINT_T )
  97. return aItem->GetParent()->GetParentGroup();
  98. else
  99. return aItem->GetParentGroup();
  100. }
  101. /// Returns the top level group inside the aScope group, or nullptr
  102. EDA_GROUP* getNestedGroup( BOARD_ITEM* aItem, EDA_GROUP* aScope, bool isFootprintEditor )
  103. {
  104. EDA_GROUP* group = getClosestGroup( aItem, isFootprintEditor );
  105. if( group == aScope )
  106. return nullptr;
  107. while( group && group->AsEdaItem()->GetParentGroup() && group->AsEdaItem()->GetParentGroup() != aScope )
  108. {
  109. if( group->AsEdaItem()->GetParent()->Type() == PCB_FOOTPRINT_T && isFootprintEditor )
  110. break;
  111. group = group->AsEdaItem()->GetParentGroup();
  112. }
  113. return group;
  114. }
  115. EDA_GROUP* PCB_GROUP::TopLevelGroup( BOARD_ITEM* aItem, EDA_GROUP* aScope, bool isFootprintEditor )
  116. {
  117. return getNestedGroup( aItem, aScope, isFootprintEditor );
  118. }
  119. bool PCB_GROUP::WithinScope( BOARD_ITEM* aItem, PCB_GROUP* aScope, bool isFootprintEditor )
  120. {
  121. EDA_GROUP* group = getClosestGroup( aItem, isFootprintEditor );
  122. if( group && group == aScope )
  123. return true;
  124. EDA_GROUP* nested = getNestedGroup( aItem, aScope, isFootprintEditor );
  125. return nested && nested->AsEdaItem()->GetParentGroup() && ( nested->AsEdaItem()->GetParentGroup() == aScope );
  126. }
  127. VECTOR2I PCB_GROUP::GetPosition() const
  128. {
  129. return GetBoundingBox().Centre();
  130. }
  131. void PCB_GROUP::SetPosition( const VECTOR2I& aNewpos )
  132. {
  133. VECTOR2I delta = aNewpos - GetPosition();
  134. Move( delta );
  135. }
  136. void PCB_GROUP::SetLocked( bool aLockState )
  137. {
  138. BOARD_ITEM::SetLocked( aLockState );
  139. RunOnChildren(
  140. [&]( BOARD_ITEM* child )
  141. {
  142. child->SetLocked( aLockState );
  143. },
  144. RECURSE_MODE::NO_RECURSE );
  145. }
  146. EDA_ITEM* PCB_GROUP::Clone() const
  147. {
  148. // Use copy constructor to get the same uuid and other fields
  149. PCB_GROUP* newGroup = new PCB_GROUP( *this );
  150. return newGroup;
  151. }
  152. PCB_GROUP* PCB_GROUP::DeepClone() const
  153. {
  154. // Use copy constructor to get the same uuid and other fields
  155. PCB_GROUP* newGroup = new PCB_GROUP( *this );
  156. newGroup->m_items.clear();
  157. for( EDA_ITEM* member : m_items )
  158. {
  159. if( member->Type() == PCB_GROUP_T )
  160. newGroup->AddItem( static_cast<PCB_GROUP*>( member )->DeepClone() );
  161. else if( member->Type() == PCB_GENERATOR_T )
  162. newGroup->AddItem( static_cast<PCB_GENERATOR*>( member )->DeepClone() );
  163. else
  164. newGroup->AddItem( static_cast<BOARD_ITEM*>( member->Clone() ) );
  165. }
  166. return newGroup;
  167. }
  168. PCB_GROUP* PCB_GROUP::DeepDuplicate( bool addToParentGroup, BOARD_COMMIT* aCommit ) const
  169. {
  170. PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( Duplicate( addToParentGroup, aCommit ) );
  171. newGroup->m_items.clear();
  172. for( EDA_ITEM* member : m_items )
  173. {
  174. if( member->Type() == PCB_GROUP_T )
  175. newGroup->AddItem( static_cast<PCB_GROUP*>( member )->DeepDuplicate( IGNORE_PARENT_GROUP ) );
  176. else
  177. newGroup->AddItem( static_cast<BOARD_ITEM*>( member )->Duplicate( IGNORE_PARENT_GROUP ) );
  178. }
  179. return newGroup;
  180. }
  181. void PCB_GROUP::swapData( BOARD_ITEM* aImage )
  182. {
  183. assert( aImage->Type() == PCB_GROUP_T );
  184. PCB_GROUP* image = static_cast<PCB_GROUP*>( aImage );
  185. std::swap( *this, *image );
  186. }
  187. bool PCB_GROUP::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  188. {
  189. // Groups are selected by promoting a selection of one of their children
  190. return false;
  191. }
  192. bool PCB_GROUP::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  193. {
  194. // Groups are selected by promoting a selection of one of their children
  195. return false;
  196. }
  197. const BOX2I PCB_GROUP::GetBoundingBox() const
  198. {
  199. BOX2I bbox;
  200. for( EDA_ITEM* item : m_items )
  201. {
  202. if( item->Type() == PCB_FOOTPRINT_T )
  203. bbox.Merge( static_cast<FOOTPRINT*>( item )->GetBoundingBox( true ) );
  204. else
  205. bbox.Merge( item->GetBoundingBox() );
  206. }
  207. bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Give a min size to the bbox
  208. return bbox;
  209. }
  210. std::shared_ptr<SHAPE> PCB_GROUP::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  211. {
  212. std::shared_ptr<SHAPE_COMPOUND> shape = std::make_shared<SHAPE_COMPOUND>();
  213. for( BOARD_ITEM* item : GetBoardItems() )
  214. shape->AddShape( item->GetEffectiveShape( aLayer, aFlash )->Clone() );
  215. return shape;
  216. }
  217. INSPECT_RESULT PCB_GROUP::Visit( INSPECTOR aInspector, void* aTestData,
  218. const std::vector<KICAD_T>& aScanTypes )
  219. {
  220. for( KICAD_T scanType : aScanTypes )
  221. {
  222. if( scanType == Type() )
  223. {
  224. if( INSPECT_RESULT::QUIT == aInspector( this, aTestData ) )
  225. return INSPECT_RESULT::QUIT;
  226. }
  227. }
  228. return INSPECT_RESULT::CONTINUE;
  229. }
  230. LSET PCB_GROUP::GetLayerSet() const
  231. {
  232. LSET aSet;
  233. for( EDA_ITEM* item : m_items )
  234. aSet |= static_cast<BOARD_ITEM*>( item )->GetLayerSet();
  235. return aSet;
  236. }
  237. bool PCB_GROUP::IsOnLayer( PCB_LAYER_ID aLayer ) const
  238. {
  239. // A group is on a layer if any item is on the layer
  240. for( EDA_ITEM* item : m_items )
  241. {
  242. if( static_cast<BOARD_ITEM*>( item )->IsOnLayer( aLayer ) )
  243. return true;
  244. }
  245. return false;
  246. }
  247. std::vector<int> PCB_GROUP::ViewGetLayers() const
  248. {
  249. return { LAYER_ANCHOR };
  250. }
  251. double PCB_GROUP::ViewGetLOD( int aLayer, const KIGFX::VIEW* aView ) const
  252. {
  253. if( aView->IsLayerVisible( LAYER_ANCHOR ) )
  254. return LOD_SHOW;
  255. return LOD_HIDE;
  256. }
  257. void PCB_GROUP::Move( const VECTOR2I& aMoveVector )
  258. {
  259. for( EDA_ITEM* member : m_items )
  260. static_cast<BOARD_ITEM*>( member )->Move( aMoveVector );
  261. }
  262. void PCB_GROUP::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  263. {
  264. for( EDA_ITEM* item : m_items )
  265. static_cast<BOARD_ITEM*>( item )->Rotate( aRotCentre, aAngle );
  266. }
  267. void PCB_GROUP::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
  268. {
  269. for( EDA_ITEM* item : m_items )
  270. static_cast<BOARD_ITEM*>( item )->Flip( aCentre, aFlipDirection );
  271. }
  272. void PCB_GROUP::Mirror( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
  273. {
  274. for( EDA_ITEM* item : m_items )
  275. static_cast<BOARD_ITEM*>( item )->Mirror( aCentre, aFlipDirection );
  276. }
  277. wxString PCB_GROUP::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
  278. {
  279. if( m_name.empty() )
  280. return wxString::Format( _( "Unnamed Group, %zu members" ), m_items.size() );
  281. else
  282. return wxString::Format( _( "Group '%s', %zu members" ), m_name, m_items.size() );
  283. }
  284. BITMAPS PCB_GROUP::GetMenuImage() const
  285. {
  286. return BITMAPS::module;
  287. }
  288. void PCB_GROUP::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  289. {
  290. aList.emplace_back( _( "Group" ), m_name.empty() ? _( "<unnamed>" ) : m_name );
  291. aList.emplace_back( _( "Members" ), wxString::Format( wxT( "%zu" ), m_items.size() ) );
  292. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
  293. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  294. }
  295. bool PCB_GROUP::Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const
  296. {
  297. return EDA_ITEM::Matches( UnescapeString( GetName() ), aSearchData );
  298. }
  299. void PCB_GROUP::RunOnChildren( const std::function<void( BOARD_ITEM* )>& aFunction, RECURSE_MODE aMode ) const
  300. {
  301. try
  302. {
  303. for( BOARD_ITEM* item : GetBoardItems() )
  304. {
  305. aFunction( item );
  306. if( aMode == RECURSE_MODE::RECURSE && ( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T ) )
  307. {
  308. item->RunOnChildren( aFunction, RECURSE_MODE::RECURSE );
  309. }
  310. }
  311. }
  312. catch( std::bad_function_call& )
  313. {
  314. wxFAIL_MSG( wxT( "Error calling function in PCB_GROUP::RunOnChildren" ) );
  315. }
  316. }
  317. bool PCB_GROUP::operator==( const BOARD_ITEM& aBoardItem ) const
  318. {
  319. if( aBoardItem.Type() != Type() )
  320. return false;
  321. const PCB_GROUP& other = static_cast<const PCB_GROUP&>( aBoardItem );
  322. return *this == other;
  323. }
  324. bool PCB_GROUP::operator==( const PCB_GROUP& aOther ) const
  325. {
  326. if( m_items.size() != aOther.m_items.size() )
  327. return false;
  328. // The items in groups are in unordered sets hashed by the pointer value, so we need to
  329. // order them by UUID (EDA_ITEM_SET) to compare
  330. EDA_ITEM_SET itemSet( m_items.begin(), m_items.end() );
  331. EDA_ITEM_SET otherItemSet( aOther.m_items.begin(), aOther.m_items.end() );
  332. for( auto it1 = itemSet.begin(), it2 = otherItemSet.begin(); it1 != itemSet.end(); ++it1, ++it2 )
  333. {
  334. // Compare UUID instead of the items themselves because we only care if the contents
  335. // of the group has changed, not which elements in the group have changed
  336. if( ( *it1 )->m_Uuid != ( *it2 )->m_Uuid )
  337. return false;
  338. }
  339. return true;
  340. }
  341. double PCB_GROUP::Similarity( const BOARD_ITEM& aOther ) const
  342. {
  343. if( aOther.Type() != Type() )
  344. return 0.0;
  345. const PCB_GROUP& other = static_cast<const PCB_GROUP&>( aOther );
  346. double similarity = 0.0;
  347. for( EDA_ITEM* item : m_items )
  348. {
  349. for( EDA_ITEM* otherItem : other.m_items )
  350. {
  351. similarity += static_cast<BOARD_ITEM*>( item )->Similarity( *static_cast<BOARD_ITEM*>( otherItem ) );
  352. }
  353. }
  354. return similarity / m_items.size();
  355. }
  356. static struct PCB_GROUP_DESC
  357. {
  358. PCB_GROUP_DESC()
  359. {
  360. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  361. REGISTER_TYPE( PCB_GROUP );
  362. propMgr.AddTypeCast( new TYPE_CAST<PCB_GROUP, BOARD_ITEM> );
  363. propMgr.AddTypeCast( new TYPE_CAST<PCB_GROUP, EDA_GROUP> );
  364. propMgr.InheritsAfter( TYPE_HASH( PCB_GROUP ), TYPE_HASH( BOARD_ITEM ) );
  365. propMgr.InheritsAfter( TYPE_HASH( PCB_GROUP ), TYPE_HASH( EDA_GROUP ) );
  366. propMgr.Mask( TYPE_HASH( PCB_GROUP ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position X" ) );
  367. propMgr.Mask( TYPE_HASH( PCB_GROUP ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position Y" ) );
  368. propMgr.Mask( TYPE_HASH( PCB_GROUP ), TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ) );
  369. const wxString groupTab = _HKI( "Group Properties" );
  370. propMgr.AddProperty(
  371. new PROPERTY<EDA_GROUP, wxString>( _HKI( "Name" ), &PCB_GROUP::SetName, &PCB_GROUP::GetName ),
  372. groupTab );
  373. }
  374. } _PCB_GROUP_DESC;