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:
parent
f65a0037dc
commit
ee428876ec
|
@ -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
|
||||||
|
|
|
@ -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" ) )
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_
|
||||||
|
|
|
@ -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" ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 ) :
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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); }
|
||||||
|
|
|
@ -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() )
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
||||||
|
|
|
@ -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..." ), "",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() );
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() )
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue