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.

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