ADDED: Group/Ungroup function

This implements the group/ungroup functions to mark a set of EDA_ITEMs as a unit, allowing them to be moved and rotated as a unit
This commit is contained in:
Joshua Redstone 2020-08-11 19:37:07 +00:00 committed by Seth Hillbrand
parent f65a0037dc
commit ee428876ec
37 changed files with 2390 additions and 162 deletions

View File

@ -467,6 +467,7 @@ set( PCB_COMMON_SRCS
${CMAKE_SOURCE_DIR}/pcbnew/class_dimension.cpp ${CMAKE_SOURCE_DIR}/pcbnew/class_dimension.cpp
${CMAKE_SOURCE_DIR}/pcbnew/class_drawsegment.cpp ${CMAKE_SOURCE_DIR}/pcbnew/class_drawsegment.cpp
${CMAKE_SOURCE_DIR}/pcbnew/class_edge_mod.cpp ${CMAKE_SOURCE_DIR}/pcbnew/class_edge_mod.cpp
${CMAKE_SOURCE_DIR}/pcbnew/class_group.cpp
${CMAKE_SOURCE_DIR}/pcbnew/class_marker_pcb.cpp ${CMAKE_SOURCE_DIR}/pcbnew/class_marker_pcb.cpp
${CMAKE_SOURCE_DIR}/pcbnew/class_module.cpp ${CMAKE_SOURCE_DIR}/pcbnew/class_module.cpp
${CMAKE_SOURCE_DIR}/pcbnew/netinfo_item.cpp ${CMAKE_SOURCE_DIR}/pcbnew/netinfo_item.cpp

View File

@ -762,6 +762,7 @@ static struct EDA_ITEM_DESC
.Map( PCB_ZONE_AREA_T, _( "Zone" ) ) .Map( PCB_ZONE_AREA_T, _( "Zone" ) )
.Map( PCB_ITEM_LIST_T, _( "Item List" ) ) .Map( PCB_ITEM_LIST_T, _( "Item List" ) )
.Map( PCB_NETINFO_T, _( "Net Info" ) ) .Map( PCB_NETINFO_T, _( "Net Info" ) )
.Map( PCB_GROUP_T, _( "Group" ) )
.Map( SCH_MARKER_T, _( "Schematic Marker" ) ) .Map( SCH_MARKER_T, _( "Schematic Marker" ) )
.Map( SCH_JUNCTION_T, _( "Junction" ) ) .Map( SCH_JUNCTION_T, _( "Junction" ) )

View File

@ -111,6 +111,7 @@ fp_text
full full
general general
grid_origin grid_origin
group
gr_arc gr_arc
gr_circle gr_circle
gr_curve gr_curve
@ -128,6 +129,7 @@ hatch_border_algorithm
hatch_min_hole_area hatch_min_hole_area
hide hide
hole_to_hole_min hole_to_hole_min
id
island island
island_removal_mode island_removal_mode
island_area_min island_area_min
@ -146,6 +148,7 @@ locked
loss_tangent loss_tangent
max_error max_error
material material
members
micro micro
mid mid
min_thickness min_thickness

198
include/class_group.h Normal file
View File

@ -0,0 +1,198 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 Joshua Redstone redstone at gmail.com
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file class_group.h
* @brief Class to handle a set of BOARD_ITEMs.
* Group parent is always board, not logical parent group.
* Group is transparent container - e.g., its position is derived from the position
* of its members.
* A selection containing a group implicitly contains its members. However other operations
* on sets of items, like committing, updating the view, etc the set is explicit.
*/
#ifndef CLASS_GROUP_H_
#define CLASS_GROUP_H_
#include <class_board_item.h>
#include <common.h>
#include <unordered_set>
namespace KIGFX
{
class VIEW;
}
typedef std::unordered_set<BOARD_ITEM*> ITEM_SET;
/**
* GROUP is a set of BOARD_ITEMs (i.e., without duplicates)
*/
class GROUP : public BOARD_ITEM
{
public:
GROUP( BOARD* parent );
static inline bool ClassOf( const EDA_ITEM* aItem )
{
return aItem && PCB_GROUP_T == aItem->Type();
}
wxString GetName() const
{
return m_name;
}
const ITEM_SET& GetItems() const
{
return m_items;
}
void SetName( wxString name )
{
m_name = name;
}
/**
* Adds item to group. Does not take ownership of item.
* @return true if item was added (false if item was already in set).
*/
bool AddItem( BOARD_ITEM* item );
/**
* Removes item from group.
* @return true if item was removed (false if item was not in the group).
*/
bool RemoveItem( const BOARD_ITEM* item );
wxString GetClass() const override
{
return wxT( "GROUP" );
}
#if defined( DEBUG )
void Show( int nestLevel, std::ostream& os ) const override
{
ShowDummy( os );
}
#endif
///> @copydoc EDA_ITEM::GetPosition
wxPoint GetPosition() const override;
///> @copydoc EDA_ITEM::SetPosition
void SetPosition( const wxPoint& ) override;
///> @copydoc BOARD_ITEM::GetLayerSet
LSET GetLayerSet() const override;
///> @copydoc BOARD_ITEM::SetLayer
void SetLayer( PCB_LAYER_ID aLayer ) override
{
wxFAIL_MSG( _( "groups don't support layer SetLayer" ) );
}
///> @copydoc EDA_ITEM::Clone
EDA_ITEM* Clone() const override;
/*
* Clone() this and all descendents
*/
GROUP* DeepClone() const;
/*
* Duplicate() this and all descendents
*/
GROUP* DeepDuplicate() const;
///> @copydoc BOARD_ITEM::SwapData
void SwapData( BOARD_ITEM* aImage ) override;
///> @copydoc BOARD_ITEM::IsOnLayer
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override
{
wxFAIL_MSG( _( "groups don't support layer IsOnLayer" ) );
return false;
}
///> @copydoc EDA_ITEM::HitTest
bool HitTest( const wxPoint& aPosition, int aAccuracy = 0 ) const override;
///> @copydoc EDA_ITEM::HitTest
bool HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy = 0 ) const override;
///> @copydoc EDA_ITEM::GetBoundingBox
const EDA_RECT GetBoundingBox() const override;
///> @copydoc EDA_ITEM::Visit
SEARCH_RESULT Visit( INSPECTOR inspector, void* testData, const KICAD_T scanTypes[] ) override;
///> @copydoc VIEW_ITEM::ViewGetLayers
void ViewGetLayers( int aLayers[], int& aCount ) const override;
///> @copydoc VIEW_ITEM::ViewGetLOD
unsigned int ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const override;
///> @copydoc BOARD_ITEM::Move
void Move( const wxPoint& aMoveVector ) override;
///> @copydoc BOARD_ITEM::Rotate
void Rotate( const wxPoint& aRotCentre, double aAngle ) override;
///> @copydoc BOARD_ITEM::Flip
void Flip( const wxPoint& aCentre, bool aFlipLeftRight ) override;
///> @copydoc EDA_ITEM::GetSelectMenuText
wxString GetSelectMenuText( EDA_UNITS aUnits ) const override;
///> @copydoc EDA_ITEM::GetMenuImage
BITMAP_DEF GetMenuImage() const override;
///> @copydoc EDA_ITEM::GetMsgPanelInfo
void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
/**
* Invokes a function on all members of the group.
* Note that this function should not add or remove items to the group
* @param aFunction is the function to be invoked.
*/
void RunOnChildren( const std::function<void ( BOARD_ITEM* )>& aFunction );
/**
* Invokes a function on all descendents of the group.
* Note that this function should not add or remove items to the group or descendent
* groups.
* @param aFunction is the function to be invoked.
*/
void RunOnDescendants( const std::function<void( BOARD_ITEM* )>& aFunction );
private:
// Members of the group
ITEM_SET m_items;
// Optional group name
wxString m_name;
};
#endif // CLASS_GROUP_H_

View File

@ -102,6 +102,7 @@ enum KICAD_T
PCB_ZONE_AREA_T, ///< class ZONE_CONTAINER, a zone area PCB_ZONE_AREA_T, ///< class ZONE_CONTAINER, a zone area
PCB_ITEM_LIST_T, ///< class BOARD_ITEM_LIST, a list of board items PCB_ITEM_LIST_T, ///< class BOARD_ITEM_LIST, a list of board items
PCB_NETINFO_T, ///< class NETINFO_ITEM, a description of a net PCB_NETINFO_T, ///< class NETINFO_ITEM, a description of a net
PCB_GROUP_T, ///< class GROUP, a set of BOARD_ITEMs
PCB_LOCATE_STDVIA_T, PCB_LOCATE_STDVIA_T,
PCB_LOCATE_UVIA_T, PCB_LOCATE_UVIA_T,

View File

@ -220,6 +220,7 @@ void BOARD_COMMIT::Push( const wxString& aMessage, bool aCreateUndoEntry, bool a
case PCB_DIMENSION_T: // a dimension (graphic item) case PCB_DIMENSION_T: // a dimension (graphic item)
case PCB_TARGET_T: // a target (graphic item) case PCB_TARGET_T: // a target (graphic item)
case PCB_MARKER_T: // a marker used to show something case PCB_MARKER_T: // a marker used to show something
case PCB_GROUP_T: // a group of items
case PCB_ZONE_AREA_T: case PCB_ZONE_AREA_T:
view->Remove( boardItem ); view->Remove( boardItem );
@ -424,3 +425,9 @@ void BOARD_COMMIT::Revert()
clear(); clear();
} }
bool BOARD_COMMIT::HasRemoveEntry( EDA_ITEM* aItem )
{
COMMIT::COMMIT_LINE* line = findEntry( aItem );
return line != nullptr && line->m_type == CHT_REMOVE;
}

View File

@ -51,6 +51,11 @@ public:
COMMIT& Stage( COMMIT& Stage(
const PICKED_ITEMS_LIST& aItems, UNDO_REDO_T aModFlag = UR_UNSPECIFIED ) override; const PICKED_ITEMS_LIST& aItems, UNDO_REDO_T aModFlag = UR_UNSPECIFIED ) override;
/*
* @return true iff the commit has an entry to remove aItem.
*/
bool HasRemoveEntry( EDA_ITEM* aItem );
private: private:
TOOL_MANAGER* m_toolMgr; TOOL_MANAGER* m_toolMgr;
bool m_editModules; bool m_editModules;

View File

@ -31,6 +31,7 @@
#include <pcb_base_frame.h> #include <pcb_base_frame.h>
#include <reporter.h> #include <reporter.h>
#include <ws_proxy_view_item.h> #include <ws_proxy_view_item.h>
#include <board_commit.h>
#include <class_board.h> #include <class_board.h>
#include <class_module.h> #include <class_module.h>
#include <class_track.h> #include <class_track.h>
@ -47,6 +48,7 @@
#include <project/project_local_settings.h> #include <project/project_local_settings.h>
#include <ratsnest/ratsnest_data.h> #include <ratsnest/ratsnest_data.h>
#include <ratsnest/ratsnest_viewitem.h> #include <ratsnest/ratsnest_viewitem.h>
#include <tool/selection_conditions.h>
/* This is an odd place for this, but CvPcb won't link if it is /* This is an odd place for this, but CvPcb won't link if it is
* in class_board_item.cpp like I first tried it. * in class_board_item.cpp like I first tried it.
@ -133,6 +135,8 @@ BOARD::~BOARD()
delete m_CurrentZoneContour; delete m_CurrentZoneContour;
m_CurrentZoneContour = NULL; m_CurrentZoneContour = NULL;
m_groups.clear();
} }
@ -557,6 +561,11 @@ void BOARD::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode )
m_markers.push_back( (MARKER_PCB*) aBoardItem ); m_markers.push_back( (MARKER_PCB*) aBoardItem );
break; break;
// this one uses a vector
case PCB_GROUP_T:
m_groups.push_back( (GROUP*) aBoardItem );
break;
// this one uses a vector // this one uses a vector
case PCB_ZONE_AREA_T: case PCB_ZONE_AREA_T:
m_ZoneDescriptorList.push_back( (ZONE_CONTAINER*) aBoardItem ); m_ZoneDescriptorList.push_back( (ZONE_CONTAINER*) aBoardItem );
@ -647,6 +656,12 @@ void BOARD::Remove( BOARD_ITEM* aBoardItem )
break; break;
case PCB_GROUP_T:
m_groups.erase( std::remove_if( m_groups.begin(), m_groups.end(),
[aBoardItem]( BOARD_ITEM* aItem ){ return aItem == aBoardItem; } ) );
break;
case PCB_ZONE_AREA_T: // this one uses a vector case PCB_ZONE_AREA_T: // this one uses a vector
// find the item in the vector, then delete then erase it. // find the item in the vector, then delete then erase it.
for( unsigned i = 0; i<m_ZoneDescriptorList.size(); ++i ) for( unsigned i = 0; i<m_ZoneDescriptorList.size(); ++i )
@ -788,6 +803,10 @@ BOARD_ITEM* BOARD::GetItem( const KIID& aID )
if( marker->m_Uuid == aID ) if( marker->m_Uuid == aID )
return marker; return marker;
for( GROUP* group : m_groups )
if( group->m_Uuid == aID )
return group;
if( m_Uuid == aID ) if( m_Uuid == aID )
return this; return this;
@ -823,6 +842,9 @@ void BOARD::FillItemMap( std::map<KIID, EDA_ITEM*>& aMap )
for( MARKER_PCB* marker : m_markers ) for( MARKER_PCB* marker : m_markers )
aMap[ marker->m_Uuid ] = marker; aMap[ marker->m_Uuid ] = marker;
for( GROUP* group : m_groups )
aMap[ group->m_Uuid ] = group;
} }
@ -1109,6 +1131,11 @@ SEARCH_RESULT BOARD::Visit( INSPECTOR inspector, void* testData, const KICAD_T s
++p; ++p;
break; break;
case PCB_GROUP_T:
result = IterateForward<GROUP*>( m_groups, inspector, testData, p );
++p;
break;
default: // catch EOT or ANY OTHER type here and return. default: // catch EOT or ANY OTHER type here and return.
done = true; done = true;
break; break;
@ -1960,3 +1987,397 @@ void BOARD::HighLightON( bool aValue )
InvokeListeners( &BOARD_LISTENER::OnBoardHighlightNetChanged, *this ); InvokeListeners( &BOARD_LISTENER::OnBoardHighlightNetChanged, *this );
} }
} }
GROUP* BOARD::TopLevelGroup( BOARD_ITEM* item, GROUP* scope )
{
GROUP* candidate = NULL;
bool foundParent;
do
{
foundParent = false;
for( GROUP* group : m_groups )
{
BOARD_ITEM* toFind = ( candidate == NULL ) ? item : candidate;
if( group->GetItems().find( toFind ) != group->GetItems().end() )
{
if( scope == group && candidate != NULL )
{
wxCHECK( candidate->Type() == PCB_GROUP_T, NULL );
return candidate;
}
candidate = group;
foundParent = true;
}
}
} while( foundParent );
if( scope != NULL )
{
return NULL;
}
return candidate;
}
GROUP* BOARD::ParentGroup( BOARD_ITEM* item )
{
for( GROUP* group : m_groups )
{
if( group->GetItems().find( item ) != group->GetItems().end() )
return group;
}
return NULL;
}
wxString BOARD::GroupsSanityCheck( bool repair )
{
if( repair )
{
while( GroupsSanityCheckInternal( repair ) != wxEmptyString );
return wxEmptyString;
}
return GroupsSanityCheckInternal( repair );
}
wxString BOARD::GroupsSanityCheckInternal( bool repair )
{
BOARD& board = *this;
GROUPS& groups = board.Groups();
std::unordered_set<wxString> groupNames;
std::unordered_set<wxString> allMembers;
// To help with cycle detection, construct a mapping from
// each group to the at most single parent group it could belong to.
std::vector<int> parentGroupIdx( groups.size(), -1 );
for( size_t idx = 0; idx < groups.size(); idx++ )
{
GROUP& group = *( groups[idx] );
BOARD_ITEM* testItem = board.GetItem( group.m_Uuid );
if( testItem != groups[idx] )
{
if( repair )
board.Groups().erase( board.Groups().begin() + idx );
return wxString::Format( _( "Group Uuid %s maps to 2 different BOARD_ITEMS: %p and %p" ),
group.m_Uuid.AsString(),
testItem, groups[idx] );
}
// Non-blank group names must be unique
if( !group.GetName().empty() )
{
if( groupNames.find( group.GetName() ) != groupNames.end() )
{
if( repair )
group.SetName( group.GetName() + "-" + group.m_Uuid.AsString() );
return wxString::Format( _( "Two groups of identical name: %s" ), group.GetName() );
}
wxCHECK( groupNames.insert( group.GetName() ).second == true,
_( "Insert failed of new group" ) );
}
for( const BOARD_ITEM* member : group.GetItems() )
{
BOARD_ITEM* item = board.GetItem( member->m_Uuid );
if( ( item == nullptr ) || ( item->Type() == NOT_USED ) )
{
if( repair )
group.RemoveItem( member );
return wxString::Format( _( "Group %s contains deleted item %s" ),
group.m_Uuid.AsString(),
member->m_Uuid.AsString() );
}
if( item != member )
{
if( repair )
group.RemoveItem( member );
return wxString::Format( _( "Uuid %s maps to 2 different BOARD_ITEMS: %s %p %s and %p %s" ),
member->m_Uuid.AsString(),
item->m_Uuid.AsString(),
item,
item->GetSelectMenuText( EDA_UNITS::MILLIMETRES ),
member,
member->GetSelectMenuText( EDA_UNITS::MILLIMETRES )
);
}
if( allMembers.find( member->m_Uuid.AsString() ) != allMembers.end() )
{
if( repair )
group.RemoveItem( member );
return wxString::Format(
_( "BOARD_ITEM %s appears multiple times in groups (either in the "
"same group or in multiple groups) " ),
item->m_Uuid.AsString() );
}
wxCHECK( allMembers.insert( member->m_Uuid.AsString() ).second == true,
_( "Insert failed of new member" ) );
if( item->Type() == PCB_GROUP_T )
{
// Could speed up with a map structure if needed
size_t childIdx = std::distance(
groups.begin(), std::find( groups.begin(), groups.end(), item ) );
// This check of childIdx should never fail, because if a group
// is not found in the groups list, then the board.GetItem()
// check above should have failed.
wxCHECK( childIdx >= 0 && childIdx < groups.size(),
wxString::Format( _( "Group %s not found in groups list" ),
item->m_Uuid.AsString() ) );
wxCHECK( parentGroupIdx[childIdx] == -1,
wxString::Format( _( "Duplicate group despite allMembers check previously: %s" ),
item->m_Uuid.AsString() ) );
parentGroupIdx[childIdx] = idx;
}
}
if( group.GetItems().size() == 0 )
{
if( repair )
board.Groups().erase( board.Groups().begin() + idx );
return wxString::Format( _( "Group must have at least one member: %s" ), group.m_Uuid.AsString() );
}
}
// Cycle detection
//
// Each group has at most one parent group.
// So we start at group 0 and traverse the parent chain, marking groups seen along the way.
// If we ever see a group that we've already marked, that's a cycle.
// If we reach the end of the chain, we know all groups in that chain are not part of any cycle.
//
// Algorithm below is linear in the # of groups because each group is visited only once.
// There may be extra time taken due to the container access calls and iterators.
//
// Groups we know are cycle free
std::unordered_set<int> knownCycleFreeGroups;
// Groups in the current chain we're exploring.
std::unordered_set<int> currentChainGroups;
// Groups we haven't checked yet.
std::unordered_set<int> toCheckGroups;
// Initialize set of groups to check that could participate in a cycle.
for( size_t idx = 0; idx < groups.size(); idx++ )
{
wxCHECK( toCheckGroups.insert( idx ).second == true, _( "Insert of ints failed" ) );
}
while( !toCheckGroups.empty() )
{
currentChainGroups.clear();
int currIdx = *toCheckGroups.begin();
while( true )
{
if( currentChainGroups.find( currIdx ) != currentChainGroups.end() )
{
if( repair )
board.Groups().erase( board.Groups().begin() + currIdx );
return _( "Cycle detected in group membership" );
}
else if( knownCycleFreeGroups.find( currIdx ) != knownCycleFreeGroups.end() )
{
// Parent is a group we know does not lead to a cycle
break;
}
wxCHECK( currentChainGroups.insert( currIdx ).second == true,
_( "Insert of new group to check failed" ) );
// We haven't visited currIdx yet, so it must be in toCheckGroups
wxCHECK( toCheckGroups.erase( currIdx ) == 1,
_( "Erase of idx for group just checked failed" ) );
currIdx = parentGroupIdx[currIdx];
if( currIdx == -1 )
{
// end of chain and no cycles found in this chain
break;
}
}
// No cycles found in chain, so add it to set of groups we know don't participate in a cycle.
knownCycleFreeGroups.insert( currentChainGroups.begin(), currentChainGroups.end() );
}
// Success
return "";
}
BOARD::GroupLegalOpsField BOARD::GroupLegalOps( const PCBNEW_SELECTION& selection ) const
{
GroupLegalOpsField legalOps = { false, false, false, false, false, false };
std::unordered_set<const BOARD_ITEM*> allMembers;
for( const GROUP* grp : m_groups )
{
for( const BOARD_ITEM* member : grp->GetItems() )
{
// Item can be member of at most one group.
wxCHECK( allMembers.insert( member ).second == true, legalOps );
}
}
bool hasGroup = ( SELECTION_CONDITIONS::HasType( PCB_GROUP_T ) )( selection );
// All elements of selection are groups, and no element is a descendant group of any other.
bool onlyGroups = ( SELECTION_CONDITIONS::OnlyType( PCB_GROUP_T ) )( selection );
// Any elements of the selections are already members of groups
bool anyGrouped = false;
// Any elements of the selections, except the first group, are already members of groups.
bool anyGroupedExceptFirst = false;
// All elements of the selections are already members of groups
bool allGrouped = true;
bool seenFirstGroup = false;
if( onlyGroups )
{
// Check that no groups are descendant subgroups of another group in the selection
for( EDA_ITEM* item : selection )
{
const GROUP* group = static_cast<const GROUP*>( item );
std::unordered_set<const GROUP*> subgroupos;
std::queue<const GROUP*> toCheck;
toCheck.push( group );
while( !toCheck.empty() )
{
const GROUP* candidate = toCheck.front();
toCheck.pop();
for( const BOARD_ITEM* aChild : candidate->GetItems() )
{
if( aChild->Type() == PCB_GROUP_T )
{
const GROUP* childGroup = static_cast<const GROUP*>( aChild );
subgroupos.insert( childGroup );
toCheck.push( childGroup );
}
}
}
for( EDA_ITEM* otherItem : selection )
{
if( otherItem != item
&& subgroupos.find( static_cast<GROUP*>( otherItem ) ) != subgroupos.end() )
{
// otherItem is a descendant subgroup of item
onlyGroups = false;
}
}
}
}
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
bool isFirstGroup = !seenFirstGroup && board_item->Type() == PCB_GROUP_T;
if( isFirstGroup )
{
seenFirstGroup = true;
}
if( allMembers.find( board_item ) == allMembers.end() )
{
allGrouped = false;
}
else
{
anyGrouped = true;
if( !isFirstGroup )
{
anyGroupedExceptFirst = true;
}
}
}
legalOps.create = !anyGrouped;
legalOps.merge = hasGroup && !anyGroupedExceptFirst && ( selection.Size() > 1 );
legalOps.ungroup = onlyGroups;
legalOps.removeItems = allGrouped;
legalOps.flatten = onlyGroups;
legalOps.enter = onlyGroups && selection.Size() == 1;
return legalOps;
}
void BOARD::GroupRemoveItems( const PCBNEW_SELECTION& selection, BOARD_COMMIT* commit )
{
std::unordered_set<BOARD_ITEM*> emptyGroups;
std::unordered_set<GROUP*> emptyGroupParents;
// groups who have had children removed, either items or empty groups.
std::unordered_set<GROUP*> itemParents;
std::unordered_set<BOARD_ITEM*> itemsToRemove;
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
itemsToRemove.insert( board_item );
}
for( BOARD_ITEM* item : itemsToRemove )
{
GROUP* parentGroup = ParentGroup( item );
itemParents.insert( parentGroup );
while( parentGroup != nullptr )
{
// Test if removing this item would make parent empty
bool allRemoved = true;
for( BOARD_ITEM* grpItem : parentGroup->GetItems() )
{
if( ( itemsToRemove.find( grpItem ) == itemsToRemove.end() )
&& ( emptyGroups.find( grpItem ) == emptyGroups.end() ) )
allRemoved = false;
}
if( allRemoved )
{
emptyGroups.insert( parentGroup );
parentGroup = ParentGroup( parentGroup );
if( parentGroup != nullptr )
itemParents.insert( parentGroup );
}
else
{
break;
}
}
}
// Items themselves are removed outside the context of this function
// First let's check the parents of items that are no empty
for( GROUP* grp : itemParents )
{
if( emptyGroups.find( grp ) == emptyGroups.end() )
{
commit->Modify( grp );
ITEM_SET members = grp->GetItems();
bool removedSomething = false;
for( BOARD_ITEM* member : members )
{
if( ( itemsToRemove.find( member ) != itemsToRemove.end() )
|| ( emptyGroups.find( member ) != emptyGroups.end() ) )
{
grp->RemoveItem( member );
removedSomething = true;
}
}
wxCHECK_RET( removedSomething, _( "Item to be removed not found in it's parent group" ) );
}
}
for( BOARD_ITEM* grp : emptyGroups )
{
commit->Remove( grp );
}
}

View File

@ -28,6 +28,7 @@
#include <tuple> #include <tuple>
#include <board_design_settings.h> #include <board_design_settings.h>
#include <board_item_container.h> #include <board_item_container.h>
#include <class_group.h>
#include <class_module.h> #include <class_module.h>
#include <class_pad.h> #include <class_pad.h>
#include <common.h> // PAGE_INFO #include <common.h> // PAGE_INFO
@ -37,9 +38,11 @@
#include <pcb_plot_params.h> #include <pcb_plot_params.h>
#include <title_block.h> #include <title_block.h>
#include <zone_settings.h> #include <zone_settings.h>
#include <tools/pcbnew_selection.h>
#include <memory> #include <memory>
class BOARD_COMMIT;
class PCB_BASE_FRAME; class PCB_BASE_FRAME;
class PCB_EDIT_FRAME; class PCB_EDIT_FRAME;
class PICKED_ITEMS_LIST; class PICKED_ITEMS_LIST;
@ -171,7 +174,8 @@ public:
DECL_VEC_FOR_SWIG( MARKERS, MARKER_PCB* ) DECL_VEC_FOR_SWIG( MARKERS, MARKER_PCB* )
DECL_VEC_FOR_SWIG( ZONE_CONTAINERS, ZONE_CONTAINER* ) DECL_VEC_FOR_SWIG( ZONE_CONTAINERS, ZONE_CONTAINER* )
DECL_DEQ_FOR_SWIG( TRACKS, TRACK* ) DECL_DEQ_FOR_SWIG( TRACKS, TRACK* )
// Dequeue rather than Vector just so we can use moveUnflaggedItems in pcbnew_control.cpp
DECL_DEQ_FOR_SWIG( GROUPS, GROUP* )
/** /**
* BOARD * BOARD
@ -197,6 +201,9 @@ private:
/// TRACKS for traces on the board, owned by pointer. /// TRACKS for traces on the board, owned by pointer.
TRACKS m_tracks; TRACKS m_tracks;
/// GROUPS for groups on the board, owned by pointer.
GROUPS m_groups;
/// edge zone descriptors, owned by pointer. /// edge zone descriptors, owned by pointer.
ZONE_CONTAINERS m_ZoneDescriptorList; ZONE_CONTAINERS m_ZoneDescriptorList;
@ -287,6 +294,19 @@ public:
return m_markers; return m_markers;
} }
/**
* The groups must maintain the folowing invariants. These are checked by
* GroupsSanityCheck():
* - An item may appear in at most one group
* - Each gruop must contain at least one item
* - If a group specifies a name, it must be unique
* - The graph of groups contianing subgroups must be acyclic.
*/
GROUPS& Groups()
{
return m_groups;
}
const std::vector<BOARD_CONNECTED_ITEM*> AllConnectedItems(); const std::vector<BOARD_CONNECTED_ITEM*> AllConnectedItems();
/// zone contour currently in progress /// zone contour currently in progress
@ -343,6 +363,10 @@ public:
m_modules.clear(); m_modules.clear();
} }
/**
* @return null if aID is null. Returns an object of Type() == NOT_USED if
* the aID is not found.
*/
BOARD_ITEM* GetItem( const KIID& aID ); BOARD_ITEM* GetItem( const KIID& aID );
void FillItemMap( std::map<KIID, EDA_ITEM*>& aMap ); void FillItemMap( std::map<KIID, EDA_ITEM*>& aMap );
@ -1190,6 +1214,56 @@ public:
* been modified in some way. * been modified in some way.
*/ */
void OnItemChanged( BOARD_ITEM* aItem ); void OnItemChanged( BOARD_ITEM* aItem );
};
/*
* Consistency check of internal m_groups structure.
* @param repair if true, modify groups structure until it passes the sanity check.
* @return empty string on success. Or error description if there's a problem.
*/
wxString GroupsSanityCheck( bool repair = false );
/*
* @param repair if true, make one modification to groups structure that brings it
* closer to passing the sanity check.
* @return empty string on success. Or error description if there's a problem.
*/
wxString GroupsSanityCheckInternal( bool repair );
/*
* Searches for highest level group containing item.
* @param scope restricts the search to groups within the group scope.
* @return group containing item, if it exists, otherwise, NULL
*/
GROUP* TopLevelGroup( BOARD_ITEM* item, GROUP* scope );
/*
* @return The group containing item as a child, or NULL if there is no
* such group.
*/
GROUP* ParentGroup( BOARD_ITEM* item );
/*
* Given a selection of items, remove them from their groups and also
* recursively remove empty groups that result.
*/
void GroupRemoveItems( const PCBNEW_SELECTION& selection, BOARD_COMMIT* commit );
struct GroupLegalOpsField
{
bool create : 1;
bool merge : 1;
bool ungroup : 1;
bool removeItems : 1;
bool flatten : 1;
bool enter : 1;
};
/*
* Check which selection tool group operations are legal given the selection.
* @return bit field of legal ops.
*/
GroupLegalOpsField GroupLegalOps( const PCBNEW_SELECTION& selection ) const;
};
#endif // CLASS_BOARD_H_ #endif // CLASS_BOARD_H_

309
pcbnew/class_group.cpp Normal file
View File

@ -0,0 +1,309 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 Joshua Redstone redstone at gmail.com
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <bitmaps.h>
#include <class_group.h>
#include <confirm.h>
#include <msgpanel.h>
#include <view/view.h>
GROUP::GROUP( BOARD* parent ) : BOARD_ITEM( (BOARD_ITEM*) parent, PCB_GROUP_T )
{
}
bool GROUP::AddItem( BOARD_ITEM* item )
{
return m_items.insert( item ).second;
}
bool GROUP::RemoveItem( const BOARD_ITEM* item )
{
return m_items.erase( const_cast<BOARD_ITEM*>( item ) ) == 1;
}
wxPoint GROUP::GetPosition() const
{
return GetBoundingBox().Centre();
}
void GROUP::SetPosition( const wxPoint& newpos )
{
wxPoint delta = newpos - GetPosition();
for( auto member : m_items )
{
member->SetPosition( member->GetPosition() + delta );
}
}
EDA_ITEM* GROUP::Clone() const
{
// Use copy constructor to get the same uuid and other fields
GROUP* newGroup = new GROUP( *this );
return newGroup;
}
GROUP* GROUP::DeepClone() const
{
// Use copy constructor to get the same uuid and other fields
GROUP* newGroup = new GROUP( *this );
newGroup->m_items.clear();
for( auto member : m_items )
{
if( member->Type() == PCB_GROUP_T )
{
newGroup->AddItem( static_cast<GROUP*>( member )->DeepClone() );
}
else
{
newGroup->AddItem( static_cast<BOARD_ITEM*>( member->Clone() ) );
}
}
return newGroup;
}
GROUP* GROUP::DeepDuplicate() const
{
GROUP* newGroup = static_cast<GROUP*>( this->Duplicate() );
newGroup->m_items.clear();
for( auto member : m_items )
{
if( member->Type() == PCB_GROUP_T )
{
newGroup->AddItem( static_cast<GROUP*>( member )->DeepDuplicate() );
}
else
{
newGroup->AddItem( static_cast<BOARD_ITEM*>( member->Duplicate() ) );
}
}
return newGroup;
}
void GROUP::SwapData( BOARD_ITEM* aImage )
{
assert( aImage->Type() == PCB_GROUP_T );
std::swap( *( (GROUP*) this ), *( (GROUP*) aImage ) );
}
#if 0
void GROUP::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
int aClearanceValue, int aError = ARC_LOW_DEF, bool ignoreLineWidth = false ) const
{
}
const BOX2I GROUP::ViewBBox() const
{
return GetBoundingBox();
}
#endif
bool GROUP::HitTest( const wxPoint& aPosition, int aAccuracy ) const
{
EDA_RECT rect = GetBoundingBox();
return rect.Inflate( aAccuracy ).Contains( aPosition );
}
bool GROUP::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
{
EDA_RECT arect = aRect;
arect.Inflate( aAccuracy );
EDA_RECT bbox = GetBoundingBox();
if( aContained )
return arect.Contains( bbox );
else
{
// If the rect does not intersect the bounding box, skip any tests
if( !aRect.Intersects( bbox ) )
return false;
for( BOARD_ITEM* member : m_items )
{
if( member->HitTest( arect, false, 0 ) )
return true;
}
// No items were hit
return false;
}
}
const EDA_RECT GROUP::GetBoundingBox() const
{
EDA_RECT area;
bool isFirst = true;
for( BOARD_ITEM* item : m_items )
{
if( isFirst )
{
area = item->GetBoundingBox();
isFirst = false;
}
else
{
area.Merge( item->GetBoundingBox() );
}
}
area.Inflate( Millimeter2iu( 0.25 ) ); // Give a min size to the area
return area;
}
SEARCH_RESULT GROUP::Visit( INSPECTOR inspector, void* testData, const KICAD_T scanTypes[] )
{
for( const KICAD_T* stype = scanTypes; *stype != EOT; ++stype )
{
// If caller wants to inspect my type
if( *stype == Type() )
{
if( SEARCH_RESULT::QUIT == inspector( this, testData ) )
return SEARCH_RESULT::QUIT;
}
}
return SEARCH_RESULT::CONTINUE;
}
LSET GROUP::GetLayerSet() const
{
LSET aSet;
for( BOARD_ITEM* item : m_items )
{
aSet |= item->GetLayerSet();
}
return aSet;
}
void GROUP::ViewGetLayers( int aLayers[], int& aCount ) const
{
// What layer to put bounding box on? change in class_group.cpp
std::unordered_set<int> layers = { LAYER_ANCHOR }; // for bounding box
for( BOARD_ITEM* item : m_items )
{
int member_layers[KIGFX::VIEW::VIEW_MAX_LAYERS], member_layers_count;
item->ViewGetLayers( member_layers, member_layers_count );
for( int i = 0; i < member_layers_count; i++ )
layers.insert( member_layers[i] );
}
aCount = layers.size();
int i = 0;
for( int layer : layers )
aLayers[i++] = layer;
}
unsigned int GROUP::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
{
if( aView->IsLayerVisible( LAYER_ANCHOR ) )
return 0;
return std::numeric_limits<unsigned int>::max();
}
void GROUP::Move( const wxPoint& aMoveVector )
{
wxPoint newpos = GetPosition() + aMoveVector;
SetPosition( newpos );
}
void GROUP::Rotate( const wxPoint& aRotCentre, double aAngle )
{
for( BOARD_ITEM* item : m_items )
{
item->Rotate( aRotCentre, aAngle );
}
}
void GROUP::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
{
for( BOARD_ITEM* item : m_items )
{
item->Flip( aCentre, aFlipLeftRight );
}
}
wxString GROUP::GetSelectMenuText( EDA_UNITS aUnits ) const
{
if( m_name.empty() )
{
return wxString::Format( _( "Anonymous group %s with %ld members" ), m_Uuid.AsString(), m_items.size() );
}
return wxString::Format( _( "Group \"%s\" with %ld members" ), m_name, m_items.size() );
}
BITMAP_DEF GROUP::GetMenuImage() const
{
return module_xpm;
}
void GROUP::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
{
aList.emplace_back( _( "Group" ), m_name.empty() ? _( "Anonymous" ) :
wxString::Format( _( "\"%s\"" ), m_name ), DARKCYAN );
aList.emplace_back( _( "Members" ), wxString::Format( _( "%ld" ), m_items.size() ), BROWN );
}
void GROUP::RunOnChildren( const std::function<void( BOARD_ITEM* )>& aFunction )
{
try
{
for( BOARD_ITEM* item : m_items )
aFunction( item );
}
catch( std::bad_function_call& )
{
DisplayError( NULL, wxT( "Error running GROUP::RunOnChildren" ) );
}
}
void GROUP::RunOnDescendants( const std::function<void( BOARD_ITEM* )>& aFunction )
{
try
{
for( BOARD_ITEM* item : m_items )
{
aFunction( item );
if( item->Type() == PCB_GROUP_T )
static_cast<GROUP*>( item )->RunOnDescendants( aFunction );
}
}
catch( std::bad_function_call& )
{
DisplayError( NULL, wxT( "Error running GROUP::RunOnDescendants" ) );
}
}

View File

@ -32,6 +32,7 @@
#include <class_marker_pcb.h> #include <class_marker_pcb.h>
#include <class_zone.h> #include <class_zone.h>
#include <class_drawsegment.h> #include <class_drawsegment.h>
#include <class_group.h>
#include <macros.h> #include <macros.h>
#include <math/util.h> // for KiROUND #include <math/util.h> // for KiROUND
@ -56,6 +57,7 @@ const KICAD_T GENERAL_COLLECTOR::AllBoardItems[] = {
PCB_PAD_T, // in modules PCB_PAD_T, // in modules
PCB_MODULE_TEXT_T, // in modules PCB_MODULE_TEXT_T, // in modules
PCB_MODULE_T, // in m_Modules PCB_MODULE_T, // in m_Modules
PCB_GROUP_T, // in m_Groups ?
PCB_ZONE_AREA_T, // in m_ZoneDescriptorList PCB_ZONE_AREA_T, // in m_ZoneDescriptorList
EOT EOT
}; };
@ -71,6 +73,7 @@ const KICAD_T GENERAL_COLLECTOR::BoardLevelItems[] = {
PCB_ARC_T, PCB_ARC_T,
PCB_TRACE_T, PCB_TRACE_T,
PCB_MODULE_T, PCB_MODULE_T,
PCB_GROUP_T,
PCB_ZONE_AREA_T, PCB_ZONE_AREA_T,
EOT EOT
}; };
@ -88,6 +91,7 @@ const KICAD_T GENERAL_COLLECTOR::AllButZones[] = {
PCB_PAD_T, PCB_PAD_T,
PCB_MODULE_TEXT_T, PCB_MODULE_TEXT_T,
PCB_MODULE_T, PCB_MODULE_T,
PCB_GROUP_T,
PCB_ZONE_AREA_T, // if it is visible on screen, it should be selectable PCB_ZONE_AREA_T, // if it is visible on screen, it should be selectable
EOT EOT
}; };
@ -144,6 +148,7 @@ const KICAD_T GENERAL_COLLECTOR::Tracks[] = {
const KICAD_T GENERAL_COLLECTOR::LockableItems[] = { const KICAD_T GENERAL_COLLECTOR::LockableItems[] = {
PCB_MODULE_T, PCB_MODULE_T,
PCB_GROUP_T, // Can a group be locked?
PCB_TRACE_T, PCB_TRACE_T,
PCB_ARC_T, PCB_ARC_T,
PCB_VIA_T, PCB_VIA_T,
@ -163,6 +168,7 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData )
{ {
BOARD_ITEM* item = (BOARD_ITEM*) testItem; BOARD_ITEM* item = (BOARD_ITEM*) testItem;
MODULE* module = nullptr; MODULE* module = nullptr;
GROUP* group = nullptr;
D_PAD* pad = nullptr; D_PAD* pad = nullptr;
bool pad_through = false; bool pad_through = false;
VIA* via = nullptr; VIA* via = nullptr;
@ -349,6 +355,10 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData )
module = static_cast<MODULE*>( item ); module = static_cast<MODULE*>( item );
break; break;
case PCB_GROUP_T:
group = static_cast<GROUP*>( item );
break;
case PCB_MARKER_T: case PCB_MARKER_T:
marker = static_cast<MARKER_PCB*>( item ); marker = static_cast<MARKER_PCB*>( item );
break; break;
@ -395,6 +405,15 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData )
goto exit; goto exit;
} }
if( group )
{
// Groups are not sensitive to the layer ... ?
if( group->HitTest( m_RefPos ) )
Append( item );
goto exit;
}
if( via ) if( via )
{ {
auto type = via->GetViaType(); auto type = via->GetViaType();

View File

@ -191,24 +191,40 @@ void CLIPBOARD_IO::SaveSelection( const PCBNEW_SELECTION& aSelected, bool isModE
zone->InitDataFromSrcInCopyCtor( *static_cast<ZONE_CONTAINER*>( item ) ); zone->InitDataFromSrcInCopyCtor( *static_cast<ZONE_CONTAINER*>( item ) );
copy = zone; copy = zone;
} }
else if( item->Type() == PCB_GROUP_T )
{
copy = static_cast<GROUP*>( item )->DeepClone();
}
else else
{ {
copy = static_cast<BOARD_ITEM*>( item->Clone() ); copy = static_cast<BOARD_ITEM*>( item->Clone() );
// locked means "locked in place"; copied items therefore can't be locked
if( MODULE* module = dyn_cast<MODULE*>( copy ) )
module->SetLocked( false );
else if( TRACK* track = dyn_cast<TRACK*>( copy ) )
track->SetLocked( false );
} }
auto prepItem = [&]( BOARD_ITEM* titem ) {
// locked means "locked in place"; copied items therefore can't be locked
if( MODULE* module = dyn_cast<MODULE*>( titem ) )
module->SetLocked( false );
else if( TRACK* track = dyn_cast<TRACK*>( titem ) )
track->SetLocked( false );
};
if( copy ) if( copy )
{ {
prepItem( copy );
// locate the reference point at (0, 0) in the copied items // locate the reference point at (0, 0) in the copied items
copy->Move( (wxPoint) -refPoint ); copy->Move( (wxPoint) -refPoint );
Format( copy, 1 ); Format( copy, 1 );
if( copy->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( copy )->RunOnDescendants( prepItem );
static_cast<GROUP*>( copy )->RunOnDescendants( [&]( BOARD_ITEM* titem ) {
Format( titem, 1 );
} );
}
delete copy; delete copy;
} }
} }

View File

@ -40,6 +40,7 @@
#include <class_drawsegment.h> #include <class_drawsegment.h>
#include <class_pcb_target.h> #include <class_pcb_target.h>
#include <class_edge_mod.h> #include <class_edge_mod.h>
#include <confirm.h>
#include <zones.h> #include <zones.h>
#include <kicad_plugin.h> #include <kicad_plugin.h>
#include <pcb_parser.h> #include <pcb_parser.h>
@ -355,6 +356,21 @@ void PCB_IO::Save( const wxString& aFileName, BOARD* aBoard, const PROPERTIES* a
{ {
LOCALE_IO toggle; // toggles on, then off, the C locale. LOCALE_IO toggle; // toggles on, then off, the C locale.
wxString sanityResult = aBoard->GroupsSanityCheck();
if( sanityResult != wxEmptyString )
{
KIDIALOG dlg( nullptr, wxString::Format(
_( "Please report this bug. Error validating group structure: %s"
"\n\nSave anyways?" ), sanityResult ),
_( "Internal group data structure corrupt" ),
wxOK | wxCANCEL | wxICON_ERROR );
dlg.SetOKLabel( _( "Save Anyway" ) );
if( dlg.ShowModal() == wxID_CANCEL )
return;
}
init( aProperties ); init( aProperties );
m_board = aBoard; // after init() m_board = aBoard; // after init()
@ -439,6 +455,10 @@ void PCB_IO::Format( BOARD_ITEM* aItem, int aNestLevel ) const
format( static_cast<TEXTE_MODULE*>( aItem ), aNestLevel ); format( static_cast<TEXTE_MODULE*>( aItem ), aNestLevel );
break; break;
case PCB_GROUP_T:
format( static_cast<GROUP*>( aItem ), aNestLevel );
break;
case PCB_TRACE_T: case PCB_TRACE_T:
case PCB_ARC_T: case PCB_ARC_T:
case PCB_VIA_T: case PCB_VIA_T:
@ -615,6 +635,8 @@ void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
aBoard->Tracks().end() ); aBoard->Tracks().end() );
std::set<BOARD_ITEM*, BOARD_ITEM::ptr_cmp> sorted_zones( aBoard->Zones().begin(), std::set<BOARD_ITEM*, BOARD_ITEM::ptr_cmp> sorted_zones( aBoard->Zones().begin(),
aBoard->Zones().end() ); aBoard->Zones().end() );
std::set<BOARD_ITEM*, BOARD_ITEM::ptr_cmp> sorted_groups( aBoard->Groups().begin(),
aBoard->Groups().end() );
formatHeader( aBoard, aNestLevel ); formatHeader( aBoard, aNestLevel );
@ -644,6 +666,10 @@ void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
// Save the polygon (which are the newer technology) zones. // Save the polygon (which are the newer technology) zones.
for( auto zone : sorted_zones ) for( auto zone : sorted_zones )
Format( zone, aNestLevel ); Format( zone, aNestLevel );
// Save the groups
for( const auto group : sorted_groups )
Format( group, aNestLevel );
} }
@ -1491,6 +1517,24 @@ void PCB_IO::format( TEXTE_PCB* aText, int aNestLevel ) const
} }
void PCB_IO::format( GROUP* aGroup, int aNestLevel ) const
{
m_out->Print( aNestLevel, "(group %s (id %s)\n", m_out->Quotew( aGroup->GetName() ).c_str(),
TO_UTF8( aGroup->m_Uuid.AsString() ) );
m_out->Print( aNestLevel + 2, "(members\n" );
std::set<BOARD_ITEM*, BOARD_ITEM::ptr_cmp> sorted_items( aGroup->GetItems().begin(),
aGroup->GetItems().end() );
for( const auto& item : sorted_items )
{
m_out->Print( aNestLevel + 4, "%s\n", TO_UTF8( item->m_Uuid.AsString() ) );
}
m_out->Print( 0, " )\n" );
m_out->Print( aNestLevel, ")\n" );
}
void PCB_IO::format( TEXTE_MODULE* aText, int aNestLevel ) const void PCB_IO::format( TEXTE_MODULE* aText, int aNestLevel ) const
{ {
std::string type; std::string type;

View File

@ -41,6 +41,7 @@ class DRAWSEGMENT;
class PCB_TARGET; class PCB_TARGET;
class D_PAD; class D_PAD;
class TEXTE_MODULE; class TEXTE_MODULE;
class GROUP;
class TRACK; class TRACK;
class ZONE_CONTAINER; class ZONE_CONTAINER;
class TEXTE_PCB; class TEXTE_PCB;
@ -75,7 +76,8 @@ class TEXTE_PCB;
//#define SEXPR_BOARD_FILE_VERSION 20200724 // Add KIID to module components //#define SEXPR_BOARD_FILE_VERSION 20200724 // Add KIID to module components
//#define SEXPR_BOARD_FILE_VERSION 20200807 // Add zone hatch advanced settings //#define SEXPR_BOARD_FILE_VERSION 20200807 // Add zone hatch advanced settings
//#define SEXPR_BOARD_FILE_VERSION 20200808 // Add properties to modules //#define SEXPR_BOARD_FILE_VERSION 20200808 // Add properties to modules
#define SEXPR_BOARD_FILE_VERSION 20200809 // Add REMOVE_UNUSED_LAYERS option to vias and THT pads //#define SEXPR_BOARD_FILE_VERSION 20200809 // Add REMOVE_UNUSED_LAYERS option to vias and THT pads
#define SEXPR_BOARD_FILE_VERSION 20200811 // Add groups
#define CTL_STD_LAYER_NAMES (1 << 0) ///< Use English Standard layer names #define CTL_STD_LAYER_NAMES (1 << 0) ///< Use English Standard layer names
#define CTL_OMIT_NETS (1 << 1) ///< Omit pads net names (useless in library) #define CTL_OMIT_NETS (1 << 1) ///< Omit pads net names (useless in library)
@ -242,6 +244,8 @@ private:
void format( EDGE_MODULE* aModuleDrawing, int aNestLevel = 0 ) const; void format( EDGE_MODULE* aModuleDrawing, int aNestLevel = 0 ) const;
void format( GROUP* aGroup, int aNestLevel = 0 ) const;
void format( DRAWSEGMENT* aSegment, int aNestLevel = 0 ) const; void format( DRAWSEGMENT* aSegment, int aNestLevel = 0 ) const;
void format( PCB_TARGET* aTarget, int aNestLevel = 0 ) const; void format( PCB_TARGET* aTarget, int aNestLevel = 0 ) const;

View File

@ -187,6 +187,13 @@ void PCB_BASE_FRAME::FocusOnItem( BOARD_ITEM* aItem )
child->ClearBrightened(); child->ClearBrightened();
}); });
} }
else if( lastItem->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( lastItem )->RunOnChildren( [&] ( BOARD_ITEM* child )
{
child->ClearBrightened();
});
}
GetCanvas()->GetView()->Update( lastItem ); GetCanvas()->GetView()->Update( lastItem );
lastBrightenedItemID = niluuid; lastBrightenedItemID = niluuid;
@ -204,6 +211,13 @@ void PCB_BASE_FRAME::FocusOnItem( BOARD_ITEM* aItem )
child->SetBrightened(); child->SetBrightened();
}); });
} }
else if( aItem->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( aItem )->RunOnChildren( [&] ( BOARD_ITEM* child )
{
child->SetBrightened();
});
}
GetCanvas()->GetView()->Update( aItem ); GetCanvas()->GetView()->Update( aItem );
lastBrightenedItemID = aItem->m_Uuid; lastBrightenedItemID = aItem->m_Uuid;

View File

@ -25,6 +25,7 @@
#include <class_board.h> #include <class_board.h>
#include <class_track.h> #include <class_track.h>
#include <class_group.h>
#include <class_module.h> #include <class_module.h>
#include <class_pad.h> #include <class_pad.h>
#include <class_drawsegment.h> #include <class_drawsegment.h>
@ -412,6 +413,10 @@ bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
draw( static_cast<const MODULE*>( item ), aLayer ); draw( static_cast<const MODULE*>( item ), aLayer );
break; break;
case PCB_GROUP_T:
draw( static_cast<const GROUP*>( item ), aLayer );
break;
case PCB_ZONE_AREA_T: case PCB_ZONE_AREA_T:
draw( static_cast<const ZONE_CONTAINER*>( item ), aLayer ); draw( static_cast<const ZONE_CONTAINER*>( item ), aLayer );
break; break;
@ -1151,6 +1156,27 @@ void PCB_PAINTER::draw( const MODULE* aModule, int aLayer )
} }
void PCB_PAINTER::draw( const GROUP* aGroup, int aLayer )
{
if( aLayer == LAYER_ANCHOR )
{
const COLOR4D color = m_pcbSettings.GetColor( aGroup, LAYER_ANCHOR );
EDA_RECT bbox = aGroup->GetBoundingBox();
m_gal->SetStrokeColor( color );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth * 2.0f );
wxPoint pos = bbox.GetPosition();
m_gal->DrawLine( pos, pos + wxPoint( bbox.GetWidth(), 0 ) );
m_gal->DrawLine( pos + wxPoint( bbox.GetWidth(), 0 ),
pos + wxPoint( bbox.GetWidth(), bbox.GetHeight() ) );
m_gal->DrawLine( pos + wxPoint( bbox.GetWidth(), bbox.GetHeight() ),
pos + wxPoint( 0, bbox.GetHeight() ) );
m_gal->DrawLine( pos + wxPoint( 0, bbox.GetHeight() ), pos );
}
}
void PCB_PAINTER::draw( const ZONE_CONTAINER* aZone, int aLayer ) void PCB_PAINTER::draw( const ZONE_CONTAINER* aZone, int aLayer )
{ {
PCB_LAYER_ID layer = static_cast<PCB_LAYER_ID>( aLayer ); PCB_LAYER_ID layer = static_cast<PCB_LAYER_ID>( aLayer );

View File

@ -42,6 +42,7 @@ class VIA;
class TRACK; class TRACK;
class D_PAD; class D_PAD;
class DRAWSEGMENT; class DRAWSEGMENT;
class GROUP;
class MODULE; class MODULE;
class ZONE_CONTAINER; class ZONE_CONTAINER;
class TEXTE_PCB; class TEXTE_PCB;
@ -303,6 +304,7 @@ protected:
void draw( const TEXTE_PCB* aText, int aLayer ); void draw( const TEXTE_PCB* aText, int aLayer );
void draw( const TEXTE_MODULE* aText, int aLayer ); void draw( const TEXTE_MODULE* aText, int aLayer );
void draw( const MODULE* aModule, int aLayer ); void draw( const MODULE* aModule, int aLayer );
void draw( const GROUP* aGroup, int aLayer );
void draw( const ZONE_CONTAINER* aZone, int aLayer ); void draw( const ZONE_CONTAINER* aZone, int aLayer );
void draw( const DIMENSION* aDimension, int aLayer ); void draw( const DIMENSION* aDimension, int aLayer );
void draw( const PCB_TARGET* aTarget ); void draw( const PCB_TARGET* aTarget );

View File

@ -38,6 +38,7 @@
#include <class_dimension.h> #include <class_dimension.h>
#include <class_drawsegment.h> #include <class_drawsegment.h>
#include <class_edge_mod.h> #include <class_edge_mod.h>
#include <class_group.h>
#include <class_pcb_target.h> #include <class_pcb_target.h>
#include <class_module.h> #include <class_module.h>
#include <netclass.h> #include <netclass.h>
@ -62,6 +63,8 @@ void PCB_PARSER::init()
m_requiredVersion = 0; m_requiredVersion = 0;
m_layerIndices.clear(); m_layerIndices.clear();
m_layerMasks.clear(); m_layerMasks.clear();
m_groupInfos.clear();
m_resetKIIDMap.clear();
// Add untranslated default (i.e. English) layernames. // Add untranslated default (i.e. English) layernames.
// Some may be overridden later if parsing a board rather than a footprint. // Some may be overridden later if parsing a board rather than a footprint.
@ -609,6 +612,10 @@ BOARD* PCB_PARSER::parseBOARD_unchecked()
m_board->Add( parseARC(), ADD_MODE::APPEND ); m_board->Add( parseARC(), ADD_MODE::APPEND );
break; break;
case T_group:
parseGROUP();
break;
case T_via: case T_via:
m_board->Add( parseVIA(), ADD_MODE::APPEND ); m_board->Add( parseVIA(), ADD_MODE::APPEND );
break; break;
@ -707,6 +714,81 @@ BOARD* PCB_PARSER::parseBOARD_unchecked()
m_undefinedLayers.clear(); m_undefinedLayers.clear();
} }
// Now that we've parsed the other Uuids in the file we can resolve
// the uuids referrred to in the group declarations we saw.
//
// First add all group objects so subsequent GetItem() calls for nested
// groups work.
for( size_t idx = 0; idx < m_groupInfos.size(); idx++ )
{
auto& aGrp = m_groupInfos[idx];
GROUP* group = new GROUP( m_board );
group->SetName( aGrp.name );
const_cast<KIID&>( group->m_Uuid ) = aGrp.uuid;
m_board->Add( group );
}
wxString error;
for( size_t idx = 0; idx < m_groupInfos.size(); idx++ )
{
auto& aGrp = m_groupInfos[idx];
BOARD_ITEM* bItem = m_board->GetItem( aGrp.uuid );
if( bItem == nullptr || bItem->Type() != PCB_GROUP_T )
{
error = wxString::Format( _( "Group %s not found in board" ),
aGrp.uuid.AsString() );
continue;
}
GROUP* group = static_cast<GROUP*>( bItem );
for( const auto& aUuid : aGrp.memberUuids )
{
KIID tUuid = aUuid;
if( m_resetKIIDs )
{
if( m_resetKIIDMap.find( aUuid.AsString() ) == m_resetKIIDMap.end() )
{
if( error == wxEmptyString )
error = wxString::Format( _( "Group %s references missing item %s" ),
aGrp.uuid.AsString(), aUuid.AsString() );
}
else
{
tUuid = m_resetKIIDMap[ aUuid.AsString() ];
}
}
BOARD_ITEM* item = m_board->GetItem( tUuid );
if( ( item == nullptr ) || ( item->Type() == NOT_USED ) )
{
if( error == wxEmptyString )
error = wxString::Format( _( "Group %s references missing item %s" ),
aGrp.uuid.AsString(), tUuid.AsString() );
}
else
{
group->AddItem( item );
}
}
}
wxString sanityResult = m_board->GroupsSanityCheck();
if( error != wxEmptyString || sanityResult != wxEmptyString )
{
wxString errMsg = ( error != wxEmptyString ) ? error : sanityResult;
KIDIALOG dlg( nullptr, wxString::Format(
_( "Error in group structure in file: %s\n\nAttempt repair?" ), errMsg ),
_( "File data error" ), wxOK | wxCANCEL | wxICON_ERROR );
dlg.SetOKLabel( _( "Attempt repair" ) );
if( dlg.ShowModal() == wxID_CANCEL )
THROW_IO_ERROR( _( "File read cancelled" ) );
m_board->GroupsSanityCheck( true );
}
return m_board; return m_board;
} }
@ -2165,7 +2247,7 @@ DRAWSEGMENT* PCB_PARSER::parseDRAWSEGMENT( bool aAllowCirclesZeroWidth )
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( segment->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( segment->m_Uuid ) = CurStrToKIID();
break; break;
case T_status: case T_status:
@ -2245,7 +2327,7 @@ TEXTE_PCB* PCB_PARSER::parseTEXTE_PCB()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( text->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( text->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -2297,7 +2379,7 @@ DIMENSION* PCB_PARSER::parseDIMENSION()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( dimension->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( dimension->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -2518,7 +2600,7 @@ MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments )
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( module->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( module->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -2822,7 +2904,7 @@ TEXTE_MODULE* PCB_PARSER::parseTEXTE_MODULE()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( text->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( text->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -3012,7 +3094,7 @@ EDGE_MODULE* PCB_PARSER::parseEDGE_MODULE()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( segment->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( segment->m_Uuid ) = CurStrToKIID();
break; break;
case T_status: case T_status:
@ -3490,7 +3572,7 @@ D_PAD* PCB_PARSER::parseD_PAD( MODULE* aParent )
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( pad->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( pad->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -3575,6 +3657,66 @@ bool PCB_PARSER::parseD_PAD_option( D_PAD* aPad )
} }
// Example of group format:
// (group <(name “groupName”)> (id 12345679)
// (members id_1 id_2 … id_last )
// )
void PCB_PARSER::parseGROUP()
{
wxCHECK_RET( CurTok() == T_group,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as GROUP." ) );
wxPoint pt;
T token;
m_groupInfos.push_back( GroupInfo() );
GroupInfo& groupInfo = m_groupInfos.back();
token = NextTok();
if( token != T_LEFT )
{
// Optional group name present.
if( !IsSymbol( token ) )
Expecting( DSN_SYMBOL );
groupInfo.name = FromUTF8();
}
NeedLEFT();
token = NextTok();
if( token != T_id )
{
Expecting( T_id );
}
NextTok();
groupInfo.uuid = CurStrToKIID();
NeedRIGHT();
NeedLEFT();
token = NextTok();
if( token != T_members )
{
Expecting( T_members );
}
while( ( token = NextTok() ) != T_RIGHT )
{
// This token is the Uuid of the item in the group.
// Since groups are serialized at the end of the file, the
// Uuid should already have been seen and exist in the board.
KIID uuid( CurStr() );
groupInfo.memberUuids.push_back( uuid );
}
NeedRIGHT();
}
ARC* PCB_PARSER::parseARC() ARC* PCB_PARSER::parseARC()
{ {
wxCHECK_MSG( CurTok() == T_arc, NULL, wxCHECK_MSG( CurTok() == T_arc, NULL,
@ -3629,7 +3771,7 @@ ARC* PCB_PARSER::parseARC()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( arc->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( arc->m_Uuid ) = CurStrToKIID();
break; break;
case T_status: case T_status:
@ -3696,7 +3838,7 @@ TRACK* PCB_PARSER::parseTRACK()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( track->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( track->m_Uuid ) = CurStrToKIID();
break; break;
case T_status: case T_status:
@ -3790,7 +3932,7 @@ VIA* PCB_PARSER::parseVIA()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( via->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( via->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -3879,7 +4021,7 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER( BOARD_ITEM_CONTAINER* aParent )
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( zone->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( zone->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -4420,7 +4562,7 @@ PCB_TARGET* PCB_PARSER::parsePCB_TARGET()
case T_tstamp: case T_tstamp:
NextTok(); NextTok();
const_cast<KIID&>( target->m_Uuid ) = m_resetKIIDs ? KIID() : KIID( CurStr() ); const_cast<KIID&>( target->m_Uuid ) = CurStrToKIID();
NeedRIGHT(); NeedRIGHT();
break; break;
@ -4431,3 +4573,18 @@ PCB_TARGET* PCB_PARSER::parsePCB_TARGET()
return target.release(); return target.release();
} }
KIID PCB_PARSER::CurStrToKIID() {
KIID aid;
if( m_resetKIIDs )
{
aid = KIID();
m_resetKIIDMap.insert( std::make_pair( CurStr(), aid ) );
}
else
{
aid = KIID( CurStr() );
}
return aid;
}

View File

@ -53,6 +53,7 @@ class TEXTE_MODULE;
class TEXTE_PCB; class TEXTE_PCB;
class TRACK; class TRACK;
class MODULE; class MODULE;
class GROUP;
class PCB_TARGET; class PCB_TARGET;
class VIA; class VIA;
class ZONE_CONTAINER; class ZONE_CONTAINER;
@ -70,6 +71,7 @@ class PCB_PARSER : public PCB_LEXER
{ {
typedef std::unordered_map< std::string, PCB_LAYER_ID > LAYER_ID_MAP; typedef std::unordered_map< std::string, PCB_LAYER_ID > LAYER_ID_MAP;
typedef std::unordered_map< std::string, LSET > LSET_MAP; typedef std::unordered_map< std::string, LSET > LSET_MAP;
typedef std::unordered_map< wxString, KIID > KIID_MAP;
BOARD* m_board; BOARD* m_board;
LAYER_ID_MAP m_layerIndices; ///< map layer name to it's index LAYER_ID_MAP m_layerIndices; ///< map layer name to it's index
@ -79,9 +81,23 @@ class PCB_PARSER : public PCB_LEXER
bool m_tooRecent; ///< true if version parses as later than supported bool m_tooRecent; ///< true if version parses as later than supported
int m_requiredVersion; ///< set to the KiCad format version this board requires int m_requiredVersion; ///< set to the KiCad format version this board requires
bool m_resetKIIDs; ///< reading into an existing board; reset UUIDs bool m_resetKIIDs; ///< reading into an existing board; reset UUIDs
KIID_MAP m_resetKIIDMap; ///< if resetting UUIDs, record new ones to update groups with
bool m_showLegacyZoneWarning; bool m_showLegacyZoneWarning;
// Group membership info refers to other Uuids in the file.
// We don't want to rely on group declarations being last in the file, so
// we store info about the group declarations here during parsing and then resolve
// them into BOARD_ITEM* after we've parsed the rest of the file.
typedef struct
{
wxString name;
KIID uuid;
std::vector<KIID> memberUuids;
} GroupInfo;
std::vector<GroupInfo> m_groupInfos;
///> Converts net code using the mapping table if available, ///> Converts net code using the mapping table if available,
///> otherwise returns unchanged net code if < 0 or if is is out of range ///> otherwise returns unchanged net code if < 0 or if is is out of range
inline int getNetCode( int aNetCode ) inline int getNetCode( int aNetCode )
@ -165,6 +181,7 @@ class PCB_PARSER : public PCB_LEXER
PCB_TARGET* parsePCB_TARGET(); PCB_TARGET* parsePCB_TARGET();
MARKER_PCB* parseMARKER( BOARD_ITEM_CONTAINER* aParent ); MARKER_PCB* parseMARKER( BOARD_ITEM_CONTAINER* aParent );
BOARD* parseBOARD(); BOARD* parseBOARD();
void parseGROUP();
/** /**
* Function parseBOARD_unchecked * Function parseBOARD_unchecked
@ -316,6 +333,11 @@ class PCB_PARSER : public PCB_LEXER
*/ */
int parseVersion(); int parseVersion();
/*
* @return if m_resetKIIDs, returns new KIID(), otehrwise returns CurStr() as KIID.
*/
KIID CurStrToKIID();
public: public:
PCB_PARSER( LINE_READER* aReader = NULL ) : PCB_PARSER( LINE_READER* aReader = NULL ) :

View File

@ -31,6 +31,7 @@ using namespace std::placeholders;
#include <pcb_display_options.h> #include <pcb_display_options.h>
#include <pcb_painter.h> #include <pcb_painter.h>
#include <class_group.h>
#include <class_module.h> #include <class_module.h>
namespace KIGFX { namespace KIGFX {

View File

@ -50,6 +50,7 @@
class TEXTE_PCB; class TEXTE_PCB;
class DIMENSION; class DIMENSION;
class MODULE; class MODULE;
class GROUP;
class TEXTE_MODULE; class TEXTE_MODULE;
class DRAWSEGMENT; class DRAWSEGMENT;
class MARKER_PCB; class MARKER_PCB;
@ -70,6 +71,7 @@ extern "C" {
static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* ); static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* );
static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* ); static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* );
static MODULE* Cast_to_MODULE( BOARD_ITEM* ); static MODULE* Cast_to_MODULE( BOARD_ITEM* );
static GROUP* Cast_to_GROUP( BOARD_ITEM* );
static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* ); static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* );
static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* ); static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* );
static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* ); static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* );
@ -90,6 +92,7 @@ static PCB_TARGET* Cast_to_PCB_TARGET( BOARD_ITEM* );
static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* ); static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* );
static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* ); static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* );
static MODULE* Cast_to_MODULE( BOARD_ITEM* ); static MODULE* Cast_to_MODULE( BOARD_ITEM* );
static GROUP* Cast_to_GROUP( BOARD_ITEM* );
static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* ); static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* );
static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* ); static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* );
static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* ); static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* );
@ -122,6 +125,8 @@ static PCB_TARGET* Cast_to_PCB_TARGET( BOARD_ITEM* );
return Cast_to_EDGE_MODULE(self) return Cast_to_EDGE_MODULE(self)
elif ct=="MODULE": elif ct=="MODULE":
return Cast_to_MODULE(self) return Cast_to_MODULE(self)
elif ct=="GROUP":
return Cast_to_GROUP(self)
elif ct=="PAD": elif ct=="PAD":
return Cast_to_D_PAD(self) return Cast_to_D_PAD(self)
elif ct=="MTEXT": elif ct=="MTEXT":
@ -165,6 +170,7 @@ static PCB_TARGET* Cast_to_PCB_TARGET( BOARD_ITEM* );
static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* self ) { return dynamic_cast<TEXTE_PCB*>(self); } static TEXTE_PCB* Cast_to_TEXTE_PCB( BOARD_ITEM* self ) { return dynamic_cast<TEXTE_PCB*>(self); }
static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* self ) { return dynamic_cast<DIMENSION*>(self); } static DIMENSION* Cast_to_DIMENSION( BOARD_ITEM* self ) { return dynamic_cast<DIMENSION*>(self); }
static MODULE* Cast_to_MODULE( BOARD_ITEM* self ) { return dynamic_cast<MODULE*>(self); } static MODULE* Cast_to_MODULE( BOARD_ITEM* self ) { return dynamic_cast<MODULE*>(self); }
static GROUP* Cast_to_GROUP( BOARD_ITEM* self ) { return dynamic_cast<GROUP*>(self); }
static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* self ) { return dynamic_cast<TEXTE_MODULE*>(self); } static TEXTE_MODULE* Cast_to_TEXTE_MODULE( BOARD_ITEM* self ) { return dynamic_cast<TEXTE_MODULE*>(self); }
static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* self ) { return dynamic_cast<DRAWSEGMENT*>(self); } static DRAWSEGMENT* Cast_to_DRAWSEGMENT( BOARD_ITEM* self ) { return dynamic_cast<DRAWSEGMENT*>(self); }
static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* self ) { return dynamic_cast<MARKER_PCB*>(self); } static MARKER_PCB* Cast_to_MARKER_PCB( BOARD_ITEM* self ) { return dynamic_cast<MARKER_PCB*>(self); }

View File

@ -58,7 +58,7 @@ using namespace std::placeholders;
#include <zone_filler.h> #include <zone_filler.h>
void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags ) void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags, SELECTION_TOOL* selectionTool )
{ {
// Iterate from the back so we don't have to worry about removals. // Iterate from the back so we don't have to worry about removals.
for( int i = aCollector.GetCount() - 1; i >= 0; --i ) for( int i = aCollector.GetCount() - 1; i >= 0; --i )
@ -110,6 +110,7 @@ void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags )
aCollector.Remove( item ); aCollector.Remove( item );
} }
} }
selectionTool->FilterCollectorForGroups( aCollector );
} }
@ -332,9 +333,9 @@ int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference )
// Be sure that there is at least one item that we can modify. If nothing was selected before, // Be sure that there is at least one item that we can modify. If nothing was selected before,
// try looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection) // try looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection)
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS, sTool );
} ); } );
if( m_dragging || selection.Empty() ) if( m_dragging || selection.Empty() )
@ -346,9 +347,9 @@ int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference )
// Now filter out locked pads. We cannot do this in the first RequestSelection() as we need // Now filter out locked pads. We cannot do this in the first RequestSelection() as we need
// the item_layers when a pad is the selection front (ie: will become curr_tiem). // the item_layers when a pad is the selection front (ie: will become curr_tiem).
selection = m_selectionTool->RequestSelection( selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS, sTool );
} ); } );
if( selection.Empty() ) if( selection.Empty() )
@ -428,6 +429,9 @@ int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference )
for( EDA_ITEM* item : sel_items ) for( EDA_ITEM* item : sel_items )
{ {
// Don't double move footprint pads, fields, etc. // Don't double move footprint pads, fields, etc.
//
// For PCB_GROUP_T, we make sure the selection includes only the top level
// group and not its descendants.
if( !item->GetParent() || !item->GetParent()->IsSelected() ) if( !item->GetParent() || !item->GetParent()->IsSelected() )
static_cast<BOARD_ITEM*>( item )->Move( movement ); static_cast<BOARD_ITEM*>( item )->Move( movement );
} }
@ -463,10 +467,20 @@ int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference )
for( EDA_ITEM* item : selection ) for( EDA_ITEM* item : selection )
{ {
// Don't double move footprint pads, fields, etc. // Don't double move footprint pads, fields, etc.
//
// For PCB_GROUP_T, the parent is the board.
if( item->GetParent() && item->GetParent()->IsSelected() ) if( item->GetParent() && item->GetParent()->IsSelected() )
continue; continue;
m_commit->Modify( item ); m_commit->Modify( item );
// If moving a group, record position of all the descendants for undo
if( item->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Modify( bItem );
});
}
} }
} }
@ -620,8 +634,8 @@ int EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, bool aPickReference )
int EDIT_TOOL::ChangeTrackWidth( const TOOL_EVENT& aEvent ) int EDIT_TOOL::ChangeTrackWidth( const TOOL_EVENT& aEvent )
{ {
const auto& selection = m_selectionTool->RequestSelection( const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) { []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) {
EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS, sTool );
} ); } );
for( EDA_ITEM* item : selection ) for( EDA_ITEM* item : selection )
@ -676,9 +690,9 @@ int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
{ {
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>(); PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
const PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( const PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS, sTool );
} ); } );
// Tracks & vias are treated in a special way: // Tracks & vias are treated in a special way:
@ -730,9 +744,9 @@ int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>(); PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
auto& selection = m_selectionTool->RequestSelection( auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
}, },
nullptr, ! m_dragging ); nullptr, ! m_dragging );
@ -750,8 +764,18 @@ int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
for( auto item : selection ) for( auto item : selection )
{ {
if( !item->IsNew() && !EditingModules() ) if( !item->IsNew() && !EditingModules() )
{
m_commit->Modify( item ); m_commit->Modify( item );
// If rotating a group, record position of all the descendants for undo
if( item->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Modify( bItem );
});
}
}
static_cast<BOARD_ITEM*>( item )->Rotate( refPt, rotateAngle ); static_cast<BOARD_ITEM*>( item )->Rotate( refPt, rotateAngle );
} }
@ -819,9 +843,9 @@ int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
} }
auto& selection = m_selectionTool->RequestSelection( auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
}, },
nullptr, !m_dragging ); nullptr, !m_dragging );
@ -887,6 +911,7 @@ int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
default: default:
// it's likely the commit object is wrong if you get here // it's likely the commit object is wrong if you get here
// Unsure if PCB_GROUP_T needs special attention here.
assert( false ); assert( false );
break; break;
} }
@ -916,9 +941,9 @@ int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
} }
auto& selection = m_selectionTool->RequestSelection( auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
}, },
nullptr, !m_dragging ); nullptr, !m_dragging );
@ -946,6 +971,13 @@ int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
if( !item->IsNew() && !EditingModules() ) if( !item->IsNew() && !EditingModules() )
m_commit->Modify( item ); m_commit->Modify( item );
if( item->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Modify( bItem );
});
}
static_cast<BOARD_ITEM*>( item )->Flip( modPoint, leftRight ); static_cast<BOARD_ITEM*>( item )->Flip( modPoint, leftRight );
} }
@ -994,9 +1026,9 @@ int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
else else
{ {
selectionCopy = m_selectionTool->RequestSelection( selectionCopy = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
} ); } );
} }
@ -1020,9 +1052,9 @@ int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
{ {
// Second RequestSelection removes locked items but keeps a copy of their pointers // Second RequestSelection removes locked items but keeps a copy of their pointers
selectionCopy = m_selectionTool->RequestSelection( selectionCopy = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED, sTool );
}, },
&lockedItems ); &lockedItems );
} }
@ -1031,11 +1063,14 @@ int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
// As we are about to remove items, they have to be removed from the selection first // As we are about to remove items, they have to be removed from the selection first
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
PCBNEW_SELECTION removed;
for( EDA_ITEM* item : selectionCopy ) for( EDA_ITEM* item : selectionCopy )
{ {
if( m_editModules ) if( m_editModules )
{ {
m_commit->Remove( item ); m_commit->Remove( item );
removed.Add( item );
continue; continue;
} }
@ -1116,20 +1151,55 @@ int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
// Remove the entire zone otherwise // Remove the entire zone otherwise
m_commit->Remove( item ); m_commit->Remove( item );
removed.Add( item );
}
break;
case PCB_GROUP_T:
{
m_commit->Remove( item );
removed.Add( item );
static_cast<GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Remove( bItem );
});
} }
break; break;
default: default:
m_commit->Remove( item ); m_commit->Remove( item );
removed.Add( item );
break; break;
} }
} }
// Figure out status of a group containing items to be removed. if entered
// group is not set in the selection tool, then any groups to be removed are
// removed in their entirety and so no empty group could remain. If entered
// group is set, then we could be removing all items of the entered group,
// in which case we need to remove the group itself.
GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
if( enteredGroup != nullptr )
{
board()->GroupRemoveItems( removed, m_commit.get() );
if( m_commit->HasRemoveEntry( enteredGroup ) )
m_selectionTool->exitGroup();
}
if( isCut ) if( isCut )
m_commit->Push( _( "Cut" ) ); m_commit->Push( _( "Cut" ) );
else else
m_commit->Push( _( "Delete" ) ); m_commit->Push( _( "Delete" ) );
if( enteredGroup != nullptr )
{
wxString check = board()->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0,
_( "Remove of items in entered group resulted in inconsistent state: " )+ check );
}
if( !m_lockedSelected && !lockedItems.empty() ) if( !m_lockedSelected && !lockedItems.empty() )
{ {
///> Popup nag for deleting locked items ///> Popup nag for deleting locked items
@ -1168,10 +1238,10 @@ int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
} }
const auto& selection = m_selectionTool->RequestSelection( const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EditToolSelectionFilter( aCollector,
EXCLUDE_LOCKED | EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EXCLUDE_LOCKED | EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
} ); } );
if( selection.Empty() ) if( selection.Empty() )
@ -1205,8 +1275,17 @@ int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selItem ); BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selItem );
if( !item->IsNew() && !EditingModules() ) if( !item->IsNew() && !EditingModules() )
{
m_commit->Modify( item ); m_commit->Modify( item );
if( item->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Modify( bItem );
});
}
}
item->Move( translation ); item->Move( translation );
switch( rotationAnchor ) switch( rotationAnchor )
@ -1256,9 +1335,9 @@ int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
// Be sure that there is at least one item that we can modify // Be sure that there is at least one item that we can modify
const auto& selection = m_selectionTool->RequestSelection( const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
} ); } );
if( selection.Empty() ) if( selection.Empty() )
@ -1273,14 +1352,13 @@ int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
std::vector<BOARD_ITEM*> new_items; std::vector<BOARD_ITEM*> new_items;
new_items.reserve( selection.Size() ); new_items.reserve( selection.Size() );
BOARD_ITEM* orig_item = nullptr;
BOARD_ITEM* dupe_item = nullptr;
// Each selected item is duplicated and pushed to new_items list // Each selected item is duplicated and pushed to new_items list
// Old selection is cleared, and new items are then selected. // Old selection is cleared, and new items are then selected.
for( EDA_ITEM* item : selection ) for( EDA_ITEM* item : selection )
{ {
orig_item = static_cast<BOARD_ITEM*>( item ); BOARD_ITEM* dupe_item = nullptr;
BOARD_ITEM* orig_item = static_cast<BOARD_ITEM*>( item );
if( m_editModules ) if( m_editModules )
{ {
@ -1319,6 +1397,10 @@ int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
dupe_item = orig_item->Duplicate(); dupe_item = orig_item->Duplicate();
break; break;
case PCB_GROUP_T:
dupe_item = static_cast<GROUP*>( orig_item )->DeepDuplicate();
break;
default: default:
// Silently drop other items (such as footprint texts) from duplication // Silently drop other items (such as footprint texts) from duplication
break; break;
@ -1327,6 +1409,13 @@ int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
if( dupe_item ) if( dupe_item )
{ {
if( dupe_item->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( dupe_item )->RunOnDescendants( [&]( BOARD_ITEM* bItem ) {
m_commit->Add( bItem );
});
}
// Clear the selection flag here, otherwise the SELECTION_TOOL // Clear the selection flag here, otherwise the SELECTION_TOOL
// will not properly select it later on // will not properly select it later on
dupe_item->ClearSelected(); dupe_item->ClearSelected();
@ -1373,9 +1462,9 @@ int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
} }
const auto& selection = m_selectionTool->RequestSelection( const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
} ); } );
if( selection.Empty() ) if( selection.Empty() )
@ -1390,7 +1479,7 @@ int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
} }
void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector ) void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
for( int i = aCollector.GetCount() - 1; i >= 0; i-- ) for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{ {
@ -1402,7 +1491,7 @@ void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
} }
void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector ) void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
for( int i = aCollector.GetCount() - 1; i >= 0; i-- ) for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{ {
@ -1517,8 +1606,8 @@ int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
Activate(); Activate();
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) { []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) {
EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED_PADS | EXCLUDE_TRANSIENTS, sTool );
} ); } );
if( selection.Empty() ) if( selection.Empty() )

View File

@ -55,7 +55,7 @@ namespace KIGFX {
#define EXCLUDE_TRANSIENTS 0x0004 #define EXCLUDE_TRANSIENTS 0x0004
#define INCLUDE_PADS_AND_MODULES 0x0008 #define INCLUDE_PADS_AND_MODULES 0x0008
void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags ); void EditToolSelectionFilter( GENERAL_COLLECTOR& aCollector, int aFlags, SELECTION_TOOL* sTool );
/** /**
* EDIT_TOOL * EDIT_TOOL
@ -149,13 +149,13 @@ public:
* Function FootprintFilter() * Function FootprintFilter()
* A selection filter which prunes the selection to contain only items of type PCB_MODULE_T * A selection filter which prunes the selection to contain only items of type PCB_MODULE_T
*/ */
static void FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector ); static void FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool );
/** /**
* Function PadFilter() * Function PadFilter()
* A selection filter which prunes the selection to contain only items of type PCB_PAD_T * A selection filter which prunes the selection to contain only items of type PCB_PAD_T
*/ */
static void PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector ); static void PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool );
///> Sets up handlers for various events. ///> Sets up handlers for various events.
void setTransitions() override; void setTransitions() override;
@ -190,6 +190,7 @@ private:
bool pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage, bool pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
const wxString& aCanceledMessage, VECTOR2I& aReferencePoint ); const wxString& aCanceledMessage, VECTOR2I& aReferencePoint );
private: private:
SELECTION_TOOL* m_selectionTool; // Selection tool used for obtaining selected items SELECTION_TOOL* m_selectionTool; // Selection tool used for obtaining selected items
bool m_dragging; // Indicates objects are being dragged right now bool m_dragging; // Indicates objects are being dragged right now

View File

@ -201,9 +201,9 @@ int GLOBAL_EDIT_TOOL::RemoveUnusedPads( const TOOL_EVENT& aEvent )
{ {
PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>(); PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>();
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS, sTool );
} ); } );
DIALOG_UNUSED_PAD_LAYERS dlg( editFrame, selection, *m_commit ); DIALOG_UNUSED_PAD_LAYERS dlg( editFrame, selection, *m_commit );

View File

@ -604,6 +604,36 @@ TOOL_ACTION PCB_ACTIONS::unlock( "pcbnew.EditorControl.unlock",
_( "Unlock" ), "", _( "Unlock" ), "",
unlocked_xpm ); unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::groupCreate( "pcbnew.EditorControl.groupCreate",
AS_GLOBAL, 0, "",
_( "Group" ), "",
locked_xpm );
TOOL_ACTION PCB_ACTIONS::groupMerge( "pcbnew.EditorControl.groupMerge",
AS_GLOBAL, 0, "",
_( "Merge" ), "",
unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::groupUngroup( "pcbnew.EditorControl.groupUngroup",
AS_GLOBAL, 0, "",
_( "Ungroup" ), "",
unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::groupRemoveItems( "pcbnew.EditorControl.groupRemoveItems",
AS_GLOBAL, 0, "",
_( "Remove Items" ), "",
unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::groupFlatten( "pcbnew.EditorControl.groupFlatten",
AS_GLOBAL, 0, "",
_( "Flatten" ), "",
unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::groupEnter( "pcbnew.EditorControl.groupEnter",
AS_GLOBAL, 0, "",
_( "Enter" ), "",
unlocked_xpm );
TOOL_ACTION PCB_ACTIONS::appendBoard( "pcbnew.EditorControl.appendBoard", TOOL_ACTION PCB_ACTIONS::appendBoard( "pcbnew.EditorControl.appendBoard",
AS_GLOBAL, 0, "", AS_GLOBAL, 0, "",
_( "Append Board..." ), "", _( "Append Board..." ), "",

View File

@ -397,6 +397,14 @@ public:
static TOOL_ACTION lock; static TOOL_ACTION lock;
static TOOL_ACTION unlock; static TOOL_ACTION unlock;
// Grouping
static TOOL_ACTION groupCreate;
static TOOL_ACTION groupMerge;
static TOOL_ACTION groupUngroup;
static TOOL_ACTION groupRemoveItems;
static TOOL_ACTION groupFlatten;
static TOOL_ACTION groupEnter;
// Miscellaneous // Miscellaneous
static TOOL_ACTION selectionTool; static TOOL_ACTION selectionTool;
static TOOL_ACTION pickerTool; static TOOL_ACTION pickerTool;

View File

@ -31,6 +31,7 @@
#include <bitmaps.h> #include <bitmaps.h>
#include <board_commit.h> #include <board_commit.h>
#include <class_board.h> #include <class_board.h>
#include <class_group.h>
#include <class_module.h> #include <class_module.h>
#include <class_pcb_target.h> #include <class_pcb_target.h>
#include <class_track.h> #include <class_track.h>
@ -146,6 +147,50 @@ public:
}; };
class GROUP_CONTEXT_MENU : public ACTION_MENU
{
public:
GROUP_CONTEXT_MENU( ) : ACTION_MENU( true )
{
SetIcon( locked_xpm ); // fixme
SetTitle( _( "Grouping" ) );
Add( PCB_ACTIONS::groupCreate );
Add( PCB_ACTIONS::groupUngroup );
Add( PCB_ACTIONS::groupMerge );
Add( PCB_ACTIONS::groupRemoveItems );
Add( PCB_ACTIONS::groupFlatten );
Add( PCB_ACTIONS::groupEnter );
}
ACTION_MENU* create() const override
{
return new GROUP_CONTEXT_MENU();
}
private:
void update() override
{
SELECTION_TOOL* selTool = getToolManager()->GetTool<SELECTION_TOOL>();
BOARD* board = selTool->GetBoard();
const auto& selection = selTool->GetSelection();
wxString check = board->GroupsSanityCheck();
wxCHECK_RET( check == wxEmptyString, _( "Group is in inconsistent state: " ) + check );
BOARD::GroupLegalOpsField legalOps = board->GroupLegalOps( selection );
Enable( getMenuId( PCB_ACTIONS::groupCreate ), legalOps.create );
Enable( getMenuId( PCB_ACTIONS::groupMerge ), legalOps.merge );
Enable( getMenuId( PCB_ACTIONS::groupUngroup ), legalOps.ungroup );
Enable( getMenuId( PCB_ACTIONS::groupRemoveItems ), legalOps.removeItems );
Enable( getMenuId( PCB_ACTIONS::groupFlatten ), legalOps.flatten );
Enable( getMenuId( PCB_ACTIONS::groupEnter ), legalOps.enter );
}
};
PCB_EDITOR_CONTROL::PCB_EDITOR_CONTROL() : PCB_EDITOR_CONTROL::PCB_EDITOR_CONTROL() :
PCB_TOOL_BASE( "pcbnew.EditorControl" ), PCB_TOOL_BASE( "pcbnew.EditorControl" ),
m_frame( nullptr ) m_frame( nullptr )
@ -206,6 +251,9 @@ bool PCB_EDITOR_CONTROL::Init()
auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>(); auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
lockMenu->SetTool( this ); lockMenu->SetTool( this );
auto groupMenu = std::make_shared<GROUP_CONTEXT_MENU>();
groupMenu->SetTool( this );
// Add the PCB control menus to relevant other tools // Add the PCB control menus to relevant other tools
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>(); SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
@ -221,9 +269,11 @@ bool PCB_EDITOR_CONTROL::Init()
toolMenu.AddSubMenu( zoneMenu ); toolMenu.AddSubMenu( zoneMenu );
toolMenu.AddSubMenu( lockMenu ); toolMenu.AddSubMenu( lockMenu );
toolMenu.AddSubMenu( groupMenu );
menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ), 200 ); menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ), 200 );
menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::LockableItems ), 200 ); menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::LockableItems ), 200 );
menu.AddMenu( groupMenu.get(), SELECTION_CONDITIONS::NotEmpty, 200 );
} }
DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>(); DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
@ -970,6 +1020,268 @@ int PCB_EDITOR_CONTROL::modifyLockSelected( MODIFY_MODE aMode )
} }
int PCB_EDITOR_CONTROL::GroupSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
// why don't we have to update the selection after selectionCursor action?
GROUP* group = new GROUP( board );
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
// Should be impossible to generate a selection with duplicates
wxCHECK_MSG( group->AddItem( board_item ), 0,
wxString::Format( _( "Item %s appears in selection multiple times" ),
board_item->m_Uuid.AsString() ) );
}
commit.Add( group );
commit.Push( _( "GroupCreate" ) );
wxString check = board->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0, _( "Group create resulted in inconsistent state: " ) + check );
selTool->ClearSelection();
selTool->select( group );
// Should I call PostEvent and onModify() ?
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::GroupMergeSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
// why don't we have to update the selection after selectionCursor action?
GROUP* firstGroup = NULL;
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
if( firstGroup == NULL && board_item->Type() == PCB_GROUP_T )
{
firstGroup = static_cast<GROUP*>( board_item );
break;
}
}
// The group submenu update() call only enabled merge if there was a group
// in the selection.
wxCHECK_MSG( firstGroup != NULL, 0, _( "Group not found in selection though selection was checked" ) );
commit.Modify( firstGroup );
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
if( board_item != firstGroup )
{
// Should be impossible to generate a selection with duplicates
wxCHECK_MSG( firstGroup->AddItem( board_item ), 0,
wxString::Format( _( "Item %s is already in group for merge"),
board_item->m_Uuid.AsString() ) );
}
}
commit.Push( _( "GroupMerge" ) );
wxString check = board->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0, _( "Group merge resulted in inconsistent state: " ) + check );
selTool->ClearSelection();
selTool->select( firstGroup );
// Should I call PostEvent and onModify() ?
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::UngroupSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
std::unordered_set<BOARD_ITEM*> ungroupedItems;
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
// why don't we have to update the selection after selectionCursor action?
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
wxCHECK_MSG( board_item->Type() == PCB_GROUP_T, 0,
_( "Selection for ungroup should only have groups in it - was checked." ) );
commit.Remove( board_item );
for( BOARD_ITEM* bItem : static_cast<GROUP*>( board_item )->GetItems() )
{
ungroupedItems.insert( bItem );
}
}
commit.Push( _( "GroupUngroup" ) );
wxString check = board->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0, _( "Group merge resulted in inconsistent state: " ) + check );
selTool->ClearSelection();
for( BOARD_ITEM* item : ungroupedItems )
{
// commit.Remove() on the group recursively removed children from the view.
// Add them back to the view
//getView()->Add( item );
selTool->select( item );
}
// Should I call PostEvent and onModify() ?
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::GroupRemoveItemsSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
// why don't we have to update the selection after selectionCursor action?
board->GroupRemoveItems( selection, &commit );
commit.Push( _( "GroupRemoveItems" ) );
wxString check = board->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0, _( "Group removeItems resulted in inconsistent state: " ) + check );
// Should I call PostEvent and onModify() ?
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::GroupFlattenSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
BOARD* board = getModel<BOARD>();
BOARD_COMMIT commit( m_frame );
const PCBNEW_SELECTION origGroups = selTool->GetSelection();
// These items were moved up to the top-level group that need to be readded to
// the view. That's becuase commit.Remove(group) recursively removed them from
// the view.
//std::unordered_set<BOARD_ITEM*> movedItems;
if( selection.Empty() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
// why don't we have to update the selection after selectionCursor action?
for( EDA_ITEM* item : selection )
{
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
wxCHECK_MSG( board_item->Type() == PCB_GROUP_T, 0,
_( "Selection for ungroup should only have groups in it - was checked." ) );
std::queue<GROUP*> groupsToFlatten;
groupsToFlatten.push( static_cast<GROUP*>( board_item ) );
GROUP* topGroup = groupsToFlatten.front();
commit.Modify( topGroup );
std::unordered_set<BOARD_ITEM*> topSubgroupsToRemove;
while( !groupsToFlatten.empty() )
{
GROUP* grp = groupsToFlatten.front();
groupsToFlatten.pop();
for( BOARD_ITEM* grpItem : grp->GetItems() )
{
if( grpItem->Type() == PCB_GROUP_T )
{
groupsToFlatten.push( static_cast<GROUP*>( grpItem ) );
commit.Remove( grpItem );
if( grp == topGroup )
topSubgroupsToRemove.insert( grpItem );
}
else
{
if( grp != topGroup )
{
wxCHECK( topGroup->AddItem( grpItem ), 0 );
//movedItems.insert( grpItem );
}
}
}
}
for( BOARD_ITEM* group : topSubgroupsToRemove )
{
topGroup->RemoveItem( group );
}
}
commit.Push( _( "GroupFlatten" ) );
wxString check = board->GroupsSanityCheck();
wxCHECK_MSG( check == wxEmptyString, 0, _( "Group flatten resulted in inconsistent state: " ) + check );
// Removing subgroups deselects the items in them. So reselect everything no that it's flattened.
selTool->ClearSelection();
for( EDA_ITEM* item : origGroups )
selTool->select( static_cast<BOARD_ITEM*>( item ) );
// Should I call PostEvent and onModify() ?
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
m_frame->OnModify();
return 0;
}
int PCB_EDITOR_CONTROL::GroupEnterSelected( const TOOL_EVENT& aEvent )
{
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
const PCBNEW_SELECTION& selection = selTool->GetSelection();
wxCHECK( selection.GetSize() == 1, 0 );
BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( selection[0] );
wxCHECK( board_item->Type() == PCB_GROUP_T, 0 );
selTool->EnterGroup();
return 0;
}
int PCB_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent ) int PCB_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent )
{ {
KIGFX::VIEW* view = getView(); KIGFX::VIEW* view = getView();
@ -1336,6 +1648,12 @@ void PCB_EDITOR_CONTROL::setTransitions()
Go( &PCB_EDITOR_CONTROL::ToggleLockSelected, PCB_ACTIONS::toggleLock.MakeEvent() ); Go( &PCB_EDITOR_CONTROL::ToggleLockSelected, PCB_ACTIONS::toggleLock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::LockSelected, PCB_ACTIONS::lock.MakeEvent() ); Go( &PCB_EDITOR_CONTROL::LockSelected, PCB_ACTIONS::lock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UnlockSelected, PCB_ACTIONS::unlock.MakeEvent() ); Go( &PCB_EDITOR_CONTROL::UnlockSelected, PCB_ACTIONS::unlock.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::GroupSelected, PCB_ACTIONS::groupCreate.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::GroupMergeSelected, PCB_ACTIONS::groupMerge.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UngroupSelected, PCB_ACTIONS::groupUngroup.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::GroupRemoveItemsSelected, PCB_ACTIONS::groupRemoveItems.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::GroupFlattenSelected, PCB_ACTIONS::groupFlatten.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::GroupEnterSelected, PCB_ACTIONS::groupEnter.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UpdatePCBFromSchematic, ACTIONS::updatePcbFromSchematic.MakeEvent() ); Go( &PCB_EDITOR_CONTROL::UpdatePCBFromSchematic, ACTIONS::updatePcbFromSchematic.MakeEvent() );
Go( &PCB_EDITOR_CONTROL::UpdateSchematicFromPCB, ACTIONS::updateSchematicFromPcb.MakeEvent() ); Go( &PCB_EDITOR_CONTROL::UpdateSchematicFromPCB, ACTIONS::updateSchematicFromPcb.MakeEvent() );

View File

@ -110,6 +110,24 @@ public:
///> Unlocks selected items. ///> Unlocks selected items.
int UnlockSelected( const TOOL_EVENT& aEvent ); int UnlockSelected( const TOOL_EVENT& aEvent );
///> Groups selected items.
int GroupSelected( const TOOL_EVENT& aEvent );
///> Merges selected items.
int GroupMergeSelected( const TOOL_EVENT& aEvent );
///> Ungroups selected items.
int UngroupSelected( const TOOL_EVENT& aEvent );
///> Remove selection from group.
int GroupRemoveItemsSelected( const TOOL_EVENT& aEvent );
///> Collaps subgroups to single group.
int GroupFlattenSelected( const TOOL_EVENT& aEvent );
///> Restrit seletion to only member of the group.
int GroupEnterSelected( const TOOL_EVENT& aEvent );
///> Runs the drill origin tool for setting the origin for drill and pick-and-place files. ///> Runs the drill origin tool for setting the origin for drill and pick-and-place files.
int DrillOrigin( const TOOL_EVENT& aEvent ); int DrillOrigin( const TOOL_EVENT& aEvent );

View File

@ -816,6 +816,13 @@ int PCBNEW_CONTROL::placeBoardItems( BOARD* aBoard, bool aAnchorAtOrigin )
moveUnflaggedItems( aBoard->Drawings(), items, isNew ); moveUnflaggedItems( aBoard->Drawings(), items, isNew );
moveUnflaggedItems( aBoard->Zones(), items, isNew ); moveUnflaggedItems( aBoard->Zones(), items, isNew );
// Subtlety: When selecting a group via the mouse,
// SELECTION_TOOL::highlightInternal runs, which does a SetSelected() on all
// descendants. In PCBNEW_CONTROL::placeBoardItems, below, we skip that and
// mark items non-recursively. That works because the saving of the
// selection created aBoard that has the group and all descendents in it.
moveUnflaggedItems( aBoard->Groups(), items, isNew );
return placeBoardItems( items, isNew, aAnchorAtOrigin ); return placeBoardItems( items, isNew, aAnchorAtOrigin );
} }
@ -840,16 +847,49 @@ int PCBNEW_CONTROL::placeBoardItems( std::vector<BOARD_ITEM*>& aItems, bool aIsN
static_cast<MODULE*>( item )->SetPath( KIID_PATH() ); static_cast<MODULE*>( item )->SetPath( KIID_PATH() );
} }
item->SetSelected();
selection.Add( item );
// Add or just select items for the move/place command // Add or just select items for the move/place command
if( aIsNew ) if( aIsNew )
editTool->GetCurrentCommit()->Add( item ); editTool->GetCurrentCommit()->Add( item );
else else
editTool->GetCurrentCommit()->Added( item ); editTool->GetCurrentCommit()->Added( item );
// Matching the logic of SELECTION_TOOL::select for PCB_GROUP_T, there
// is a distinction between which items are SetSelected and which are in
// the selection object. Top-level groups or items not in groups are
// added to the selection object (via selection.Add(), below), but all
// items have SetSelected called. This is because much of the selection
// management logic (e.g. move) recursively acts on groups in the
// selection, so descendents of groups should not be in the selection
// object.
item->SetSelected();
} }
// Filter out from selection any items that are in groups that are also in the selection
// For PCB_GROUP_T, a selection including the group should not include its descendants.
std::unordered_set<GROUP*> groups;
for( BOARD_ITEM* item : aItems )
{
if( item->Type() == PCB_GROUP_T )
groups.insert( static_cast<GROUP*>( item ) );
}
for( BOARD_ITEM* item : aItems )
{
bool inGroup = false;
for( GROUP* grp : groups )
{
if( grp->GetItems().find( item ) != grp->GetItems().end() )
{
inGroup = true;
break;
}
}
if( !inGroup )
{
selection.Add( item );
}
}
if( selection.Size() > 0 ) if( selection.Size() > 0 )
{ {
if( aAnchorAtOrigin ) if( aAnchorAtOrigin )
@ -892,6 +932,9 @@ int PCBNEW_CONTROL::AppendBoard( PLUGIN& pi, wxString& fileName )
for( auto module : brd->Modules() ) for( auto module : brd->Modules() )
module->SetFlags( SKIP_STRUCT ); module->SetFlags( SKIP_STRUCT );
for( auto group : brd->Groups() )
group->SetFlags( SKIP_STRUCT );
for( auto drawing : brd->Drawings() ) for( auto drawing : brd->Drawings() )
drawing->SetFlags( SKIP_STRUCT ); drawing->SetFlags( SKIP_STRUCT );

View File

@ -93,6 +93,27 @@ const KIGFX::VIEW_GROUP::ITEMS PCBNEW_SELECTION::updateDrawList() const
{ {
std::vector<VIEW_ITEM*> items; std::vector<VIEW_ITEM*> items;
std::function<void ( EDA_ITEM* )> addItem;
addItem = [&]( EDA_ITEM* item ) {
items.push_back( item );
if( item->Type() == PCB_MODULE_T )
{
MODULE* module = static_cast<MODULE*>( item );
module->RunOnChildren( [&] ( BOARD_ITEM* bitem ) { addItem( bitem ); } );
}
else if( item->Type() == PCB_GROUP_T )
{
GROUP* group = static_cast<GROUP*>( item );
group->RunOnChildren( [&] ( BOARD_ITEM* bitem ) { addItem( bitem ); } );
}
};
for( auto item : m_items )
{
addItem( item );
}
#if 0
for( auto item : m_items ) for( auto item : m_items )
{ {
items.push_back( item ); items.push_back( item );
@ -102,8 +123,13 @@ const KIGFX::VIEW_GROUP::ITEMS PCBNEW_SELECTION::updateDrawList() const
MODULE* module = static_cast<MODULE*>( item ); MODULE* module = static_cast<MODULE*>( item );
module->RunOnChildren( [&] ( BOARD_ITEM* bitem ) { items.push_back( bitem ); } ); module->RunOnChildren( [&] ( BOARD_ITEM* bitem ) { items.push_back( bitem ); } );
} }
else if( item->Type() == PCB_GROUP_T )
{
GROUP* group = static_cast<GROUP*>( item );
group->RunOnChildren( [&] ( BOARD_ITEM* bitem ) { items.push_back( bitem ); } );
} }
}
#endif
return items; return items;
} }

View File

@ -143,16 +143,16 @@ size_t ALIGN_DISTRIBUTE_TOOL::GetSelections( ALIGNMENT_RECTS& aItems, ALIGNMENT_
{ {
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); } ); { EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS, sTool ); } );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
std::vector<BOARD_ITEM*> lockedItems; std::vector<BOARD_ITEM*> lockedItems;
selection = m_selectionTool->RequestSelection( selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED ); }, &lockedItems ); { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED, sTool ); }, &lockedItems );
aItems = GetBoundingBoxes( selection ); aItems = GetBoundingBoxes( selection );
aLocked = GetBoundingBoxes( lockedItems ); aLocked = GetBoundingBoxes( lockedItems );
@ -394,8 +394,8 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent )
{ {
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } ); { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); } );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
@ -496,8 +496,8 @@ void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( ALIGNMENT_RECTS &it
int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent )
{ {
PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection( PCBNEW_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } ); { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); } );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;

View File

@ -67,8 +67,8 @@ int POSITION_RELATIVE_TOOL::PositionRelative( const TOOL_EVENT& aEvent )
PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>(); PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
const auto& selection = m_selectionTool->RequestSelection( const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } ); { EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS, sTool ); } );
if( selection.Empty() ) if( selection.Empty() )
return 0; return 0;
@ -140,9 +140,9 @@ int POSITION_RELATIVE_TOOL::SelectPositionRelativeItem( const TOOL_EVENT& aEvent
{ {
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
const PCBNEW_SELECTION& sel = m_selectionTool->RequestSelection( const PCBNEW_SELECTION& sel = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector ) { []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool ) {
EditToolSelectionFilter( EditToolSelectionFilter(
aCollector, EXCLUDE_TRANSIENTS | INCLUDE_PADS_AND_MODULES ); aCollector, EXCLUDE_TRANSIENTS | INCLUDE_PADS_AND_MODULES, sTool );
} ); } );
if( sel.Empty() ) if( sel.Empty() )

View File

@ -118,6 +118,7 @@ SELECTION_TOOL::SELECTION_TOOL() :
m_multiple( false ), m_multiple( false ),
m_skip_heuristics( false ), m_skip_heuristics( false ),
m_locked( true ), m_locked( true ),
m_enteredGroup( NULL ),
m_priv( std::make_unique<PRIV>() ) m_priv( std::make_unique<PRIV>() )
{ {
m_filter.lockedItems = true; m_filter.lockedItems = true;
@ -182,6 +183,9 @@ void SELECTION_TOOL::Reset( RESET_REASON aReason )
m_frame = getEditFrame<PCB_BASE_FRAME>(); m_frame = getEditFrame<PCB_BASE_FRAME>();
m_locked = true; m_locked = true;
if( m_enteredGroup != NULL )
exitGroup();
if( aReason == TOOL_BASE::MODEL_RELOAD ) if( aReason == TOOL_BASE::MODEL_RELOAD )
{ {
// Deselect any item being currently in edit, to avoid unexpected behavior // Deselect any item being currently in edit, to avoid unexpected behavior
@ -266,8 +270,15 @@ int SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
if( m_selection.Empty() ) if( m_selection.Empty() )
selectPoint( evt->Position() ); selectPoint( evt->Position() );
if( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T )
{
EnterGroup();
}
else
{
m_toolMgr->RunAction( PCB_ACTIONS::properties, true ); m_toolMgr->RunAction( PCB_ACTIONS::properties, true );
} }
}
// drag with LMB? Select multiple objects (or at least draw a selection box) or drag them // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
else if( evt->IsDrag( BUT_LEFT ) ) else if( evt->IsDrag( BUT_LEFT ) )
@ -310,6 +321,9 @@ int SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
{ {
m_frame->FocusOnItem( nullptr ); m_frame->FocusOnItem( nullptr );
if( m_enteredGroup != NULL )
exitGroup();
ClearSelection(); ClearSelection();
if( evt->FirstResponder() == this ) if( evt->FirstResponder() == this )
@ -329,6 +343,29 @@ int SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
} }
void SELECTION_TOOL::EnterGroup()
{
wxCHECK_RET( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T,
_( "EnterGroup called when selection is not a single group") );
GROUP* aGroup = static_cast<GROUP*>( m_selection[0] );
if( m_enteredGroup != NULL )
{
exitGroup();
}
ClearSelection();
m_enteredGroup = aGroup;
m_enteredGroup->RunOnChildren( [&]( BOARD_ITEM* titem ) { select( titem ); } );
}
void SELECTION_TOOL::exitGroup()
{
m_enteredGroup = NULL;
}
PCBNEW_SELECTION& SELECTION_TOOL::GetSelection() PCBNEW_SELECTION& SELECTION_TOOL::GetSelection()
{ {
return m_selection; return m_selection;
@ -367,7 +404,7 @@ PCBNEW_SELECTION& SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aCli
itemDispositions[ item ] = BEFORE; itemDispositions[ item ] = BEFORE;
} }
aClientFilter( VECTOR2I(), collector ); aClientFilter( VECTOR2I(), collector, this );
for( EDA_ITEM* item : collector ) for( EDA_ITEM* item : collector )
{ {
@ -377,6 +414,11 @@ PCBNEW_SELECTION& SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aCli
itemDispositions[ item ] = AFTER; itemDispositions[ item ] = AFTER;
} }
// Unhighlight the BEFORE items before highlighting the AFTER items.
// This is so that in the case of groups, if aClientFilter replaces a selection
// with the enclosing group, the unhighlight of the element doesn't undo the
// recursive highlighting of that elemetn by the group.
for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions ) for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
{ {
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( itemDisposition.first ); BOARD_ITEM* item = static_cast<BOARD_ITEM*>( itemDisposition.first );
@ -389,7 +431,14 @@ PCBNEW_SELECTION& SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aCli
unhighlight( item, SELECTED, &m_selection ); unhighlight( item, SELECTED, &m_selection );
} }
else if( disposition == AFTER ) }
for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( itemDisposition.first );
DISPOSITION disposition = itemDisposition.second;
if( disposition == AFTER )
{ {
highlight( item, SELECTED, &m_selection ); highlight( item, SELECTED, &m_selection );
} }
@ -441,6 +490,10 @@ bool SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
guide.SetIgnoreZoneFills( displayOpts.m_DisplayZonesMode != 0 ); guide.SetIgnoreZoneFills( displayOpts.m_DisplayZonesMode != 0 );
if( m_enteredGroup &&
!m_enteredGroup->GetBoundingBox().Contains( wxPoint( aWhere.x, aWhere.y ) ) )
exitGroup();
collector.Collect( board(), collector.Collect( board(),
m_editModules ? GENERAL_COLLECTOR::ModuleItems : GENERAL_COLLECTOR::AllBoardItems, m_editModules ? GENERAL_COLLECTOR::ModuleItems : GENERAL_COLLECTOR::AllBoardItems,
wxPoint( aWhere.x, aWhere.y ), guide ); wxPoint( aWhere.x, aWhere.y ), guide );
@ -457,7 +510,7 @@ bool SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
// Allow the client to do tool- or action-specific filtering to see if we // Allow the client to do tool- or action-specific filtering to see if we
// can get down to a single item // can get down to a single item
if( aClientFilter ) if( aClientFilter )
aClientFilter( aWhere, collector ); aClientFilter( aWhere, collector, this );
// Apply the stateful filter // Apply the stateful filter
filterCollectedItems( collector ); filterCollectedItems( collector );
@ -809,7 +862,7 @@ void SELECTION_TOOL::UnbrightenItem( BOARD_ITEM* aItem )
} }
void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector ) void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, SELECTION_TOOL* sTool )
{ {
// Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net. // Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
// All other items types are removed. // All other items types are removed.
@ -1433,6 +1486,11 @@ bool SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem )
return false; return false;
} }
if( m_enteredGroup != NULL )
{
return m_enteredGroup->GetItems().find( aItem ) != m_enteredGroup->GetItems().end();
}
return true; return true;
} }
@ -1878,6 +1936,20 @@ bool SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOn
break; break;
} }
case PCB_GROUP_T:
{
GROUP* group = const_cast<GROUP*>( static_cast<const GROUP*>( aItem ) );
// Similar to logic for module, a group is selectable if any of its
// members are. (This recurses)
for( auto item : group->GetItems() )
{
if( Selectable( item, true ) )
return true;
}
return false;
}
case PCB_MARKER_T: // Always selectable case PCB_MARKER_T: // Always selectable
return true; return true;
@ -1924,9 +1996,22 @@ void SELECTION_TOOL::unselect( BOARD_ITEM* aItem )
m_locked = true; m_locked = true;
} }
void SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup ) void SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup )
{ {
highlightInternal( aItem, aMode, aGroup, false );
view()->Update( aItem );
// Many selections are very temporal and updating the display each time just
// creates noise.
if( aMode == BRIGHTENED )
getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
}
void SELECTION_TOOL::highlightInternal( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup, bool isChild )
{
wxLogTrace( "GRP", wxString::Format( _( "highlight() of %s %p" ),
aItem->GetSelectMenuText( m_frame->GetUserUnits() ) ), aItem );
if( aMode == SELECTED ) if( aMode == SELECTED )
aItem->SetSelected(); aItem->SetSelected();
else if( aMode == BRIGHTENED ) else if( aMode == BRIGHTENED )
@ -1937,6 +2022,7 @@ void SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION*
// Hide the original item, so it is shown only on overlay // Hide the original item, so it is shown only on overlay
view()->Hide( aItem, true ); view()->Hide( aItem, true );
if( !isChild || aMode == BRIGHTENED )
aGroup->Add( aItem ); aGroup->Add( aItem );
} }
@ -1944,23 +2030,20 @@ void SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION*
// highlight all the parts that make the module, not the module itself // highlight all the parts that make the module, not the module itself
if( aItem->Type() == PCB_MODULE_T ) if( aItem->Type() == PCB_MODULE_T )
{ {
static_cast<MODULE*>( aItem )->RunOnChildren( static_cast<MODULE*>( aItem )->RunOnChildren( [&]( BOARD_ITEM* titem ) {
[&]( BOARD_ITEM* item ) highlightInternal( titem, aMode, aGroup, true ); } );
{
if( aMode == SELECTED )
item->SetSelected();
else if( aMode == BRIGHTENED )
{
item->SetBrightened();
if( aGroup )
aGroup->Add( item );
} }
else if( aItem->Type() == PCB_GROUP_T )
if( aGroup ) {
view()->Hide( item, true ); static_cast<GROUP*>( aItem )->RunOnChildren( [&]( BOARD_ITEM* titem ) {
}); highlightInternal( titem, aMode, aGroup, true ); } );
} }
}
void SELECTION_TOOL::unhighlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup )
{
unhighlightInternal( aItem, aMode, aGroup, false );
view()->Update( aItem ); view()->Update( aItem );
@ -1971,8 +2054,10 @@ void SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION*
} }
void SELECTION_TOOL::unhighlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup ) void SELECTION_TOOL::unhighlightInternal( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION* aGroup, bool isChild )
{ {
wxLogTrace( "GRP", wxString::Format( _( "unhighlight() of %s %p" ),
aItem->GetSelectMenuText( m_frame->GetUserUnits() ) ), aItem );
if( aMode == SELECTED ) if( aMode == SELECTED )
aItem->ClearSelected(); aItem->ClearSelected();
else if( aMode == BRIGHTENED ) else if( aMode == BRIGHTENED )
@ -1984,38 +2069,25 @@ void SELECTION_TOOL::unhighlight( BOARD_ITEM* aItem, int aMode, PCBNEW_SELECTION
// Restore original item visibility // Restore original item visibility
view()->Hide( aItem, false ); view()->Hide( aItem, false );
// N.B. if we clear the selection flag for sub-elements, we need to also
// remove the element from the selection group (if it exists)
if( isChild )
view()->Update( aItem );
} }
// Modules are treated in a special way - when they are highlighted, we have to // Modules are treated in a special way - when they are highlighted, we have to
// highlight all the parts that make the module, not the module itself // highlight all the parts that make the module, not the module itself
if( aItem->Type() == PCB_MODULE_T ) if( aItem->Type() == PCB_MODULE_T )
{ {
static_cast<MODULE*>( aItem )->RunOnChildren( static_cast<MODULE*>( aItem )->RunOnChildren( [&]( BOARD_ITEM* titem ) {
[&]( BOARD_ITEM* item ) unhighlightInternal( titem, aMode, aGroup, true ); } );
{
if( aMode == SELECTED )
item->ClearSelected();
else if( aMode == BRIGHTENED )
item->ClearBrightened();
// N.B. if we clear the selection flag for sub-elements, we need to also
// remove the element from the selection group (if it exists)
if( aGroup )
{
aGroup->Remove( item );
view()->Hide( item, false );
view()->Update( item );
} }
}); else if( aItem->Type() == PCB_GROUP_T )
{
static_cast<GROUP*>( aItem )->RunOnChildren( [&]( BOARD_ITEM* titem ) {
unhighlightInternal( titem, aMode, aGroup, true ); } );
} }
view()->Update( aItem );
// Many selections are very temporal and updating the display each time just
// creates noise.
if( aMode == BRIGHTENED )
getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
} }
@ -2452,6 +2524,43 @@ void SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector,
aCollector.Transfer( item ); aCollector.Transfer( item );
} }
} }
FilterCollectorForGroups( aCollector );
}
void SELECTION_TOOL::FilterCollectorForGroups( GENERAL_COLLECTOR& aCollector ) const
{
std::unordered_set<BOARD_ITEM*> toAdd;
// If any element is a member of a group, replace those elements with the top containing group.
for( int j = 0; j < aCollector.GetCount(); ++j )
{
GROUP* aTop = board()->TopLevelGroup( aCollector[j], m_enteredGroup );
if( aTop != NULL )
{
if( aTop != aCollector[j] )
{
toAdd.insert( aTop );
aCollector.Remove( aCollector[j] );
}
}
else if( m_enteredGroup != NULL &&
m_enteredGroup->GetItems().find( aCollector[j] ) == m_enteredGroup->GetItems().end() )
{
// If a group is entered, no selections of objects not in the group.
aCollector.Remove( aCollector[j] );
}
}
for( BOARD_ITEM* item : toAdd )
{
if( !aCollector.HasItem( item ) )
{
aCollector.Append( item );
}
}
} }

View File

@ -47,7 +47,7 @@ namespace KIGFX
} }
typedef void (*CLIENT_SELECTION_FILTER)( const VECTOR2I&, GENERAL_COLLECTOR& ); typedef void (*CLIENT_SELECTION_FILTER)( const VECTOR2I&, GENERAL_COLLECTOR&, SELECTION_TOOL* );
/** /**
@ -125,6 +125,14 @@ public:
void BrightenItem( BOARD_ITEM* aItem ); void BrightenItem( BOARD_ITEM* aItem );
void UnbrightenItem( BOARD_ITEM* aItem ); void UnbrightenItem( BOARD_ITEM* aItem );
/**
* Function select()
* Takes necessary action mark an item as selected.
*
* @param aItem is an item to be selected.
*/
void select( BOARD_ITEM* aItem );
/** /**
* Function selectable() * Function selectable()
* Checks conditions for an item to be selected. * Checks conditions for an item to be selected.
@ -168,6 +176,17 @@ public:
///> Zooms the screen to center and fit the current selection. ///> Zooms the screen to center and fit the current selection.
void zoomFitSelection(); void zoomFitSelection();
BOARD* GetBoard()
{
return board();
}
void EnterGroup();
void exitGroup();
void FilterCollectorForGroups( GENERAL_COLLECTOR& aCollector ) const;
GROUP* GetEnteredGroup() { return m_enteredGroup; }
private: private:
/** /**
* Function selectPoint() * Function selectPoint()
@ -289,14 +308,6 @@ private:
*/ */
BOARD_ITEM* pickSmallestComponent( GENERAL_COLLECTOR* aCollector ); BOARD_ITEM* pickSmallestComponent( GENERAL_COLLECTOR* aCollector );
/**
* Function select()
* Takes necessary action mark an item as selected.
*
* @param aItem is an item to be selected.
*/
void select( BOARD_ITEM* aItem );
/** /**
* Function unselect() * Function unselect()
* Takes necessary action mark an item as unselected. * Takes necessary action mark an item as unselected.
@ -342,6 +353,12 @@ private:
const GENERAL_COLLECTORS_GUIDE getCollectorsGuide() const; const GENERAL_COLLECTORS_GUIDE getCollectorsGuide() const;
private: private:
void highlightInternal( BOARD_ITEM* aItem, int aHighlightMode, PCBNEW_SELECTION* aGroup,
bool isChild);
void unhighlightInternal( BOARD_ITEM* aItem, int aHighlightMode, PCBNEW_SELECTION* aGroup,
bool isChild);
PCB_BASE_FRAME* m_frame; // Pointer to the parent frame PCB_BASE_FRAME* m_frame; // Pointer to the parent frame
PCBNEW_SELECTION m_selection; // Current state of selection PCBNEW_SELECTION m_selection; // Current state of selection
@ -353,6 +370,7 @@ private:
bool m_multiple; // Multiple selection mode is active bool m_multiple; // Multiple selection mode is active
bool m_skip_heuristics; // Heuristics are not allowed when choosing item under cursor bool m_skip_heuristics; // Heuristics are not allowed when choosing item under cursor
bool m_locked; // Other tools are not allowed to modify locked items bool m_locked; // Other tools are not allowed to modify locked items
GROUP* m_enteredGroup; // If non-null, selections are limited to members of this group
/// Private state (opaque pointer/compilation firewall) /// Private state (opaque pointer/compilation firewall)
class PRIV; class PRIV;

View File

@ -150,6 +150,13 @@ static bool TestForExistingItem( BOARD* aPcb, BOARD_ITEM* aItem )
return true; return true;
} }
// Append groups:
for( auto item : aPcb->Groups() )
{
if( aItem == static_cast<BOARD_ITEM*>( item ) )
return true;
}
return false; return false;
} }
@ -436,6 +443,7 @@ void PCB_BASE_EDIT_FRAME::PutDataInPreviousState( PICKED_ITEMS_LIST* aList, bool
switch( eda_item->Type() ) switch( eda_item->Type() )
{ {
case PCB_MODULE_T: case PCB_MODULE_T:
case PCB_GROUP_T:
deep_reBuild_ratsnest = true; // Pointers on pads can be invalid deep_reBuild_ratsnest = true; // Pointers on pads can be invalid
KI_FALLTHROUGH; KI_FALLTHROUGH;

View File

@ -44,6 +44,8 @@ set( QA_PCBNEW_SRCS
drc/test_drc_courtyard_invalid.cpp drc/test_drc_courtyard_invalid.cpp
drc/test_drc_courtyard_overlap.cpp drc/test_drc_courtyard_overlap.cpp
group_saveload.cpp
) )
add_executable( qa_pcbnew add_executable( qa_pcbnew

View File

@ -0,0 +1,227 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 Joshua Redstone redstone at gmail.com
* Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <boost/filesystem.hpp>
#include <class_board.h>
#include <class_module.h>
#include <class_pcb_text.h>
#include <common.h>
#include <pcbnew_utils/board_construction_utils.h>
#include <pcbnew_utils/board_file_utils.h>
#include <string>
#include <unit_test_utils/unit_test_utils.h>
BOOST_AUTO_TEST_SUITE( GroupSaveLoad )
// The tests below set up a test case with a spec for the set of groups to create.
// A group can contain members from this list of candidates.
enum ItemType
{
TEXT0,
TEXT1,
TEXT2,
TEXT3,
TEXT4,
TEXT5,
TEXT6,
TEXT7,
TEXT8,
REMOVED_TEXT, // not known to board
GROUP0,
GROUP1,
GROUP2,
NAME_GROUP3,
NAME_GROUP4,
NAME_GROUP3_DUP, // Group with name identical to NAME_GROUP3
NUM_ITEMS
};
/*
* Takes a vector of group specifications for groups to create.
* Each group is a vector of which ItemTypes to put in the group.
* The first group corresponds to GROUP0, the second to GROUP1, and os on.
*/
BOARD* createBoard( const std::vector<std::vector<ItemType>>& spec )
{
BOARD* aBoard = new BOARD();
std::vector<GROUP*> groups;
std::vector<TEXTE_PCB*> textItems;
// Create groups
for( int idx = 0; idx < 6; idx++ )
{
GROUP* gr = new GROUP( aBoard );
if( idx >= ( NAME_GROUP3 - GROUP0 ) )
{
wxString name = wxString::Format(
_( "group-%d" ), ( idx == ( NAME_GROUP3_DUP - GROUP0 ) ) ? 3 : idx );
gr->SetName( name );
BOOST_CHECK_EQUAL( gr->GetName(), name );
}
groups.push_back( gr );
}
// Create text items and add to board.
for( int idx = 0; idx < 10; idx++ )
{
auto textItem = new TEXTE_PCB( aBoard );
textItem->SetText( wxString::Format( _( "some text-%d" ), idx ) );
if( idx < 9 ) // don't add REMOVED_TEXT
{
aBoard->Add( textItem );
}
textItems.push_back( textItem );
}
// Populate groups based on spec
for( int groupIdx = 0; groupIdx < spec.size(); groupIdx++ )
{
auto& groupSpec = spec[groupIdx];
GROUP* group = groups[groupIdx];
int count = 0;
for( ItemType item : groupSpec )
{
if( item <= REMOVED_TEXT )
{
group->AddItem( textItems[item] );
count++;
}
else // it's a group
{
group->AddItem( groups[item - GROUP0] );
count++;
}
}
BOOST_CHECK_EQUAL( group->GetItems().size(), count );
aBoard->Add( group );
}
BOOST_TEST_CHECKPOINT( "Returning fresh board" );
return aBoard;
}
// Check if two groups are identical by comparing the fields (by Uuid).
void testGroupEqual( const GROUP& group1, const GROUP& group2 )
{
BOOST_CHECK_EQUAL( group1.m_Uuid.AsString(), group2.m_Uuid.AsString() );
BOOST_CHECK_EQUAL( group1.GetName(), group2.GetName() );
auto items1 = group1.GetItems();
auto items2 = group2.GetItems();
// Test that the sets items1 and items2 are identical, by checking m_Uuid
BOOST_CHECK_EQUAL( items1.size(), items2.size() );
for( auto item1 : items1 )
{
auto item2 = std::find_if( items2.begin(), items2.end(),
[&]( auto elem ) { return elem->m_Uuid.AsString() == item1->m_Uuid.AsString(); } );
BOOST_CHECK( item2 != items2.end() );
// Could check other properties here...
}
}
// Check if two GROUPS are identical by comparing the groups in each of them.
void testGroupsEqual( const GROUPS& groups1, const GROUPS& groups2 )
{
BOOST_CHECK_EQUAL( groups1.size(), groups2.size() );
for( auto group1 : groups1 )
{
auto group2 = std::find_if( groups2.begin(), groups2.end(),
[&]( auto elem ) { return elem->m_Uuid.AsString() == group1->m_Uuid.AsString(); } );
BOOST_CHECK( group2 != groups2.end() );
testGroupEqual( *group1, **group2 );
}
}
/*
* Create board based on spec, save it to a file, load it, and make sure the
* groups in the resulting board are the same as the groups we started with.
*/
void testSaveLoad( const std::vector<std::vector<ItemType>>& spec )
{
BOARD* aBoard1 = createBoard( spec );
auto path = boost::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb";
::KI_TEST::DumpBoardToFile( *aBoard1, path.string() );
auto aBoard2 = ::KI_TEST::ReadBoardFromFileOrStream( path.string() );
testGroupsEqual( aBoard1->Groups(), aBoard2->Groups() );
}
// Test saving & loading of a few configurations
BOOST_AUTO_TEST_CASE( HealthyCases )
{
//BOOST_TEST_CONTEXT( "happy" );
// Test board with no groups
testSaveLoad( {} );
// Single group
testSaveLoad( { { TEXT0 } } );
testSaveLoad( { { TEXT0, TEXT1 } } );
// Two groups
testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, TEXT3 } } );
testSaveLoad( { { TEXT0, TEXT1 }, { TEXT2, GROUP0 } } );
// Subgroups by no cycle
testSaveLoad( { { TEXT0, GROUP1 }, { TEXT2 }, { TEXT3, GROUP0 } } );
testSaveLoad( { { TEXT0 }, { TEXT2 }, { GROUP1, GROUP0 } } );
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3 } } );
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2, NAME_GROUP3 }, { TEXT3, GROUP0 } } );
testSaveLoad( { { TEXT0 }, { TEXT1 }, { TEXT2 }, { TEXT3 }, { NAME_GROUP3, GROUP0 } } );
}
BOOST_AUTO_TEST_CASE( ErrorCases )
{
// A cycle
BOARD* aBoard1 = createBoard( { { TEXT0, GROUP1 }, { TEXT2, GROUP0 } } );
BOOST_CHECK_EQUAL( aBoard1->GroupsSanityCheck(), "Cycle detected in group membership" );
// More complex cycle
aBoard1 = createBoard( { { TEXT0, GROUP1 }, { TEXT1 }, { TEXT2, NAME_GROUP4 },
{ TEXT3, GROUP2 }, { TEXT4, NAME_GROUP3 } } );
BOOST_CHECK_EQUAL( aBoard1->GroupsSanityCheck(), "Cycle detected in group membership" );
// Reference group not on board
aBoard1 = createBoard( { { TEXT0, GROUP1 } } );
wxString res = aBoard1->GroupsSanityCheck();
BOOST_CHECK_MESSAGE( res.find( "contains deleted item" ) != std::string::npos, res );
// Single empty group
aBoard1 = createBoard( { {} } );
res = aBoard1->GroupsSanityCheck();
BOOST_CHECK_MESSAGE(
res.find( "Group must have at least one member" ) != std::string::npos, res );
// Duplicate group name
aBoard1 = createBoard( { { TEXT0 }, { TEXT1 }, { TEXT2 }, { TEXT3 }, { TEXT4 }, { TEXT5 } } );
res = aBoard1->GroupsSanityCheck();
BOOST_CHECK_MESSAGE( res.find( "Two groups of identical name" ) != std::string::npos, res );
// Group references item that is not on board
aBoard1 = createBoard( { { REMOVED_TEXT } } );
res = aBoard1->GroupsSanityCheck();
BOOST_CHECK_MESSAGE( res.find( "contains deleted item" ) != std::string::npos, res );
}
BOOST_AUTO_TEST_SUITE_END()