← Back to team overview

kicad-developers team mailing list archive

Re: Group selection idea

 

Yes, It is a complex issue. 

Anyway, I attach 2 patches here. 0001 is the selectiongroup thing you saw in the 
video. 0002 is a patch adding a (group %s) to the kicad_pcb format, making groups 
persistant.

If you ever use selection groups for something, you will notice it is very nice 
to have it persistant between usages.

(footprints, segments, dimensions, vias and text items are persistent)

- Kristoffer

On 2017-01-13 23:08, Wayne Stambaugh wrote:
> The more I think about it, the more complex this issue becomes.  How do
> you instantiate a group multiple times (a hierarchy) in a board and
> maintain proper footprint reference designators and net connectivity?  I
> think the issue is far more complex than everyone realizes.  This is the
> exact same issue as schematic hierarchies.  They are devilishly
> difficult to implement correctly.  Just about everyone (myself included)
> who has ever messed with the schematic hierarchy code gets it wrong the
> first time.  It may be more prudent to just implement grouping that
> isn't persistent in the board file until we have a robust implementation
> that handles all conditions.  The other option is to write groups to
> files outside the board for import into other boards.  This would also
> give you a path to clipboard copy and paste.  When a group is imported
> or pasted into a board it's grouping would be maintained until the
> editing session is terminated.  The file would still be saved as a
> flatten board.  This would give you reuse without the huge development
> effort of nested groups.  It wouldn't be ideal but it also wouldn't be a
> massive development effort that I suspect full group support will entail.
> 
> On 1/13/2017 9:51 AM, Maciej Sumiński wrote:
>> Hi Kristoffer,
>>
>> Please consider the following ideas, perhaps you will find it useful:
>>
>> * Create a PCB_GROUP class inheriting from BOARD_ITEM_CONTAINER
>>
>>   As BOARD_ITEM_CONTAINER is also a BOARD_ITEM, you get an easy way of
>> creating nested groups. This also enables selecting a group with the
>> selection tool, as the group is now a part of the board. Another benefit
>> is a possibility of graphic representation (a border/semitransparent
>> background) to indicate a group. You can also have a single item
>> belonging to several groups, if someone finds it useful.
>>   PCB_GROUPs would have to be stored in a BOARD object.
>>   The downside is dealing with the undo buffer and netlist updates. As a
>> result of certain operations BOARD_ITEMs are added & removed, and their
>> groups have to be updated too. Perhaps a set of shared_ptrs in
>> BOARD_ITEM to its parent GROUPs would fix the problem, but it may need
>> some further consideration.
>>
>> * Room recipe (just to evaluate if it is feasible with the current approach)
>>
>>   Rooms/channels are usually done by embedding a single schematic sheet
>> multiple times. This fact is reflected in the component sheet path, so
>> it is easy to determine corresponding components in different rooms.
>>   Example sheet paths for two components in two different subsheets:
>>
>>    | sheet 1  | sheet 2
>> ---+----------+---------
>> C1 | /12C/FA0 | /81A/FA0
>> R1 | /12C/41E | /81A/41E
>>
>>   Rooms (groups) would be created upon request during netlist import,
>> basing on the schematic sheet path.
>>   Copying one room layout to another:
>> * Find a reference component in both rooms (e.g. C1)
>> * Computing relative position of other components in the source room
>> * Apply the relative position to components in the destination room
>>
>> The tricky part is recreating the tracks/vias. To do so, one needs to
>> find all the room internal tracks and copy them to the destination room,
>> updating the nets. Perhaps that will be simpler with the new
>> connectivity algorithm, but a possible way is:
>> * Iterate through all components pads in the source room
>> * Find the attached tracks and see if they lead to another component in
>> the same room. If it is true, the track should be copied.
>> * Propagate nets from pads.
>>
>> Regards,
>> Orson
>>
>> On 01/13/2017 03:23 PM, Kristoffer Ödmark wrote:
>>> Okay, got it.
>>>
>>> For the group selection thing, I did some quick tests today, and only
>>> letting board items belong to one group at a time seems like a good way,
>>> letting them belong to multiple groups gave me a headache in how to
>>> implement any way of handling the groups.
>>>
>>> This would then change the format for the pcb file with one value under
>>> every saved board item in the kicad_pcb file. suggesting (group <name>)
>>> to be used for this. ( tracks, vias, texts, etc etc )
>>>
>>> This does not hinder the development for hierarchy of the groups later
>>> on. That could be fixed with having another field later which keeps
>>> track of the hierarchy and parents/childs groups.
>>>
>>> I looked over my implementation and changed everything to use wxStrings
>>> and wxArrayStrings, to be more like current things in Kicad, I do not
>>> know if this was an issue or not.
>>>
>>> Is the file format upgrade for the current implementation okay? If it is
>>> I will start implementing it this weekend.
>>>
>>> - Kristoffer
>>>
>>> On 01/13/2017 02:07 PM, Wayne Stambaugh wrote:
>>>> Kristoffer,
>>>>
>>>> I was merely talking about getting your initial implementation correct
>>>> in Pcbnew to prevent issues with the full implementation down the road.
>>>> While I'm OK with opening up a full design specification, I think it's a
>>>> bit premature and will distract us from the immediate work at hand.
>>>> None of the schematic group linking work can happen until the new
>>>> schematic and symbol library file formats are complete.  I want to keep
>>>> the focus on grouping in Pcbnew for the time being and not get bogged
>>>> down in a full implementation specification.
>>>>
>>>> Cheers,
>>>>
>>>> Wayne
>>>>
>>>> On 1/12/2017 3:38 PM, Kristoffer Ödmark wrote:
>>>>> I think Physical design reuse (PDR) is far out of scope of the group
>>>>> selection idea. However it Might be used. I actually put some thinking
>>>>> on howto implement some kind of PDR into Kicad without having to
>>>>> redesign everything existing already. Its in a google doc with comments
>>>>> enabled.
>>>>>
>>>>> [Google docs link]
>>>>> https://docs.google.com/document/d/1ivRRu7F2g6_WU9bgHlaUTaXZw02oluMh4NA41BqEM-8/edit?usp=sharing
>>>>>
>>>>>
>>>>>
>>>>> PDR discussions I think should be in a separate thread, since the amount
>>>>> of work to get there is quite a bit more, involves both eeschema and
>>>>> pcbnew, new file formatting, specifying workflows etc etc.
>>>>>
>>>>> Its called snippets in A****m, PDR in PADS.
>>>>>
>>>>> - Kristoffer
>>>>>
>>>>> On 2017-01-12 18:47, Clemens Koller wrote:
>>>>>> Hello!
>>>>>>
>>>>>> This feature looks really useful in production if it's implemented
>>>>>> properly.
>>>>>> Some comments from my side how things could be extended in the future:
>>>>>>
>>>>>> Group selection (also read: table-based/parametric-based selection!)
>>>>>> seems like a great feature and the step towards physical design reuse
>>>>>> (PDR).
>>>>>> With some intelligent grouping of routed components and
>>>>>> automatic/assisted selection of components based on netlist-topology
>>>>>> (or manual or table based selection) it is possible to create a
>>>>>> physical design reuse (or channels) by duplicating groups with the
>>>>>> same layout but different component references + different net names /
>>>>>> instances of net names.
>>>>>> An additional approach is intelligent "group editing" (table based - a
>>>>>> must have for complex designs!) where there is an automatic / assisted
>>>>>> rename of components and netnames to create reuses. This also applies
>>>>>> to the schematic entry, obviously.
>>>>>>
>>>>>> The word "intelligent" above means obviously, that there is some
>>>>>> infrastructure and coding work to consider.
>>>>>>
>>>>>> An example screenshot of one of my designs using a buggy commercial
>>>>>> product is attached. There are 60 similar channels.
>>>>>> Layouting these manually would be a hell of work, obviously.
>>>>>>
>>>>>> Regards,
>>>>>>
>>>>>> Clemens
>>>>>>
>>>>>>
>>>>>> On 2017-01-12 17:37, Wayne Stambaugh wrote:
>>>>>>> I think this feature would be useful but we should proceed with
>>>>>>> caution
>>>>>>> if we are going to include persistence.  I'm guessing making groups
>>>>>>> persistent will require a change to the pcb file format.  We should
>>>>>>> think this through thoroughly before moving forward.  Is it possible
>>>>>>> that this grouping could be used for an a****m like room feature?  If
>>>>>>> so, than we need to plan this out accordingly rather than just
>>>>>>> commit a
>>>>>>> new feature for the sake of convenience.
>>>>>>>
>>>>>>> On 1/12/2017 6:55 AM, Tomasz Wlostowski wrote:
>>>>>>>> I like it. Give me a few days to review it and I hope it will get
>>>>>>>> merged. You'll also have to make the groups persistent (save to
>>>>>>>> file).
>>>>>>>> Recursive grouping (group of groups) would be also an advantage.
>>>>>>>>
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>> Tom
>>>>>>>>
>>>>>>>> Sent from my Samsung Galaxy smartphone.
>>>>>>>>
>>>>>>>>
>>>>>>>> -------- Original message --------
>>>>>>>> From: Kristoffer Ödmark <kristofferodmark90@xxxxxxxxx>
>>>>>>>> Date: 12/01/2017 12:41 (GMT+01:00)
>>>>>>>> To: kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>>> Subject: Re: [Kicad-developers] Group selection idea
>>>>>>>>
>>>>>>>> Hey again, What would be the chances of seing this getting into the
>>>>>>>> master branches on launchpad, what would I have to add/change to
>>>>>>>> get it
>>>>>>>> there?
>>>>>>>>
>>>>>>>> - Kristoffer
>>>>>>>>
>>>>>>>> On 2017-01-11 21:59, Kristoffer Ödmark wrote:
>>>>>>>>> Attaching Patch!
>>>>>>>>>
>>>>>>>>> ( Thanks Chris! )
>>>>>>>>>
>>>>>>>>> On 2017-01-11 20:51, Kristoffer Ödmark wrote:
>>>>>>>>>> Hello!
>>>>>>>>>>
>>>>>>>>>> I hacked together a group selection concept looking like this:
>>>>>>>>>> https://youtu.be/eJp-aJ8i0H4
>>>>>>>>>>
>>>>>>>>>> It can assign BOARD_ITEM to a specific group for easier
>>>>>>>>>> selection and
>>>>>>>>>> group manipulation. I am open to suggestions on changes, this is
>>>>>>>>>> surely
>>>>>>>>>> not an optimal implementation.
>>>>>>>>>>
>>>>>>>>>> Useful when you may want to keep the relative position of
>>>>>>>>>> something on
>>>>>>>>>> the board like maybe a RF layout etc.
>>>>>>>>>>
>>>>>>>>>> It cannot currently save the group assignments between sessions,
>>>>>>>>>> since
>>>>>>>>>> that would require some changes to the file format. That would
>>>>>>>>>> need some
>>>>>>>>>> agreement that this is indeed wanted.
>>>>>>>>>>
>>>>>>>>>> it also doesnt work on zones right now.
>>>>>>>>>>
>>>>>>>>>> ps: I do not now the best way to attach a patch file.
>>>>>>>>>> I added my feature
>>>>>>>>>> commit
>>>>>>>>>> git pull
>>>>>>>>>> fix conflict
>>>>>>>>>> commit
>>>>>>>>>>
>>>>>>>>>> Anyone have any steps on how to get one patch file for this, now I
>>>>>>>>>> got
>>>>>>>>>> one patch file, and a merge.
>>>>>>>>>>
>>>>>>>>>> - Kristoffer
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>>>>
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>>>>
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>
>>>>> _______________________________________________
>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>
>>>>
>>>> _______________________________________________
>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>> More help   : https://help.launchpad.net/ListHelp
>>>>
>>>
>>
>>
>>
>>
>> _______________________________________________
>> Mailing list: https://launchpad.net/~kicad-developers
>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>> Unsubscribe : https://launchpad.net/~kicad-developers
>> More help   : https://help.launchpad.net/ListHelp
>>
> 
> 
> _______________________________________________
> Mailing list: https://launchpad.net/~kicad-developers
> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~kicad-developers
> More help   : https://help.launchpad.net/ListHelp
> 
>From 1345b2e1a4e1be634e85125b6d5574b5ea203ffc Mon Sep 17 00:00:00 2001
From: totalkrill <kristoffer.odmark90@xxxxxxxxx>
Date: Wed, 11 Jan 2017 19:49:05 +0100
Subject: [PATCH 1/2] Added concept of selectiongroups.     Groups context menu
 Added     Modified SELECT_MENU and the select_tool     Added getters to BOARD
 class     Added std::string groupId to BOARD_ITEM class

---
 include/class_board_item.h          |   5 ++
 pcbnew/class_board.cpp              |  88 +++++++++++++++++++++
 pcbnew/class_board.h                |  22 ++++++
 pcbnew/tools/common_actions.cpp     |  20 +++++
 pcbnew/tools/common_actions.h       |  15 ++++
 pcbnew/tools/pcb_editor_control.cpp | 153 ++++++++++++++++++++++++++++++++++++
 pcbnew/tools/pcb_editor_control.h   |   9 +++
 pcbnew/tools/selection_tool.cpp     |  90 ++++++++++++++++++++-
 pcbnew/tools/selection_tool.h       |   6 ++
 9 files changed, 406 insertions(+), 2 deletions(-)

diff --git a/include/class_board_item.h b/include/class_board_item.h
index 34c275c14..b13ce0912 100644
--- a/include/class_board_item.h
+++ b/include/class_board_item.h
@@ -76,6 +76,8 @@ class BOARD_ITEM : public EDA_ITEM
     void SetNext( EDA_ITEM* aNext )       { Pnext = aNext; }
     void SetBack( EDA_ITEM* aBack )       { Pback = aBack; }
 
+    wxString m_GroupId = "";
+
 protected:
     LAYER_ID    m_Layer;
 
@@ -84,6 +86,9 @@ protected:
 
 public:
 
+    const wxString& GetGroupId() const { return m_GroupId; }
+    void SetGroupId( const wxString& aKeywords ) { m_GroupId = aKeywords; }
+
     BOARD_ITEM( BOARD_ITEM* aParent, KICAD_T idtype ) :
         EDA_ITEM( aParent, idtype ), m_Layer( F_Cu )
     {
diff --git a/pcbnew/class_board.cpp b/pcbnew/class_board.cpp
index 6608c3ce0..9d72d9406 100644
--- a/pcbnew/class_board.cpp
+++ b/pcbnew/class_board.cpp
@@ -1128,6 +1128,94 @@ EDA_RECT BOARD::ComputeBoundingBox( bool aBoardEdgesOnly )
     return area;
 }
 
+std::list<BOARD_ITEM*> BOARD::GetAllItems()
+{
+    std::list<BOARD_ITEM*> itemsList;
+
+    auto tracks = m_Track.GetFirst();
+    auto drawings = m_Drawings.GetFirst();
+    auto modules = m_Modules.GetFirst();
+    auto zones = m_Zone.GetFirst();
+
+    for( BOARD_ITEM* item = tracks; item; item = item->Next() )
+    {
+        if ( item != NULL )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = drawings; item; item = item->Next() )
+    {
+        if ( item != NULL )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = modules; item; item = item->Next() )
+    {
+        if ( item != NULL )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = zones; item; item = item->Next() )
+    {
+        if ( item != NULL )
+            itemsList.push_back( item );
+    }
+
+    return itemsList;
+}
+
+std::list<BOARD_ITEM*> BOARD::GetAllGroupItems()
+{
+    std::list<BOARD_ITEM*> itemsList;
+
+    auto tracks = m_Track.GetFirst();
+    auto drawings = m_Drawings.GetFirst();
+    auto modules = m_Modules.GetFirst();
+    auto zones = m_Zone.GetFirst();
+
+    for( BOARD_ITEM* item = tracks; item; item = item->Next() )
+    {
+        if ( item != NULL && item->GetGroupId() != "" )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = drawings; item; item = item->Next() )
+    {
+        if ( item != NULL && item->GetGroupId() != "" )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = modules; item; item = item->Next() )
+    {
+        if ( item != NULL && item->GetGroupId() != "" )
+            itemsList.push_back( item );
+    }
+    for( BOARD_ITEM* item = zones; item; item = item->Next() )
+    {
+        if ( item != NULL && item->GetGroupId() != "" )
+            itemsList.push_back( item );
+    }
+
+    return itemsList;
+}
+
+wxArrayString BOARD::GetGroups()
+{
+
+    wxArrayString existingGroups;
+
+    std::list<BOARD_ITEM*> itemsList = GetAllGroupItems();
+
+    for( BOARD_ITEM* i : itemsList )
+    {
+        bool groupSeen = false;
+
+        for( wxString s : existingGroups )
+        {
+            if( s == i->GetGroupId() )
+                groupSeen = true;
+        }
+        if( !groupSeen  && i->GetGroupId() != "" )
+            existingGroups.Add( i->GetGroupId() );
+    }
+    return existingGroups;
+}
+
 
 // virtual, see pcbstruct.h
 void BOARD::GetMsgPanelInfo( std::vector< MSG_PANEL_ITEM >& aList )
diff --git a/pcbnew/class_board.h b/pcbnew/class_board.h
index 0ad5e9d89..a1277b44c 100644
--- a/pcbnew/class_board.h
+++ b/pcbnew/class_board.h
@@ -278,6 +278,28 @@ public:
     ///> @copydoc BOARD_ITEM_CONTAINER::Remove()
     void Remove( BOARD_ITEM* aBoardItem ) override;
 
+    /**
+     * Function GetAllItems()
+     * returns a list of ALL items on board.
+     * @return list of BOARD_ITEMS* representing all BOARD_ITEMS currently on the board.
+     */
+    std::list<BOARD_ITEM*> GetAllItems();
+
+    /**
+     * Function GetAllGroupItems()
+     * returns a list of ALL items in a group on the board.
+     * @return list of BOARD_ITEMS* representing all BOARD_ITEMS in a group currently on the board.
+     */
+    std::list<BOARD_ITEM*> GetAllGroupItems();
+
+    /**
+     * Function GetGroups()
+     * returns a list of groups .
+     * @return list of std::string representing all the different groups on the board.
+     */
+    wxArrayString GetGroups();
+
+
     BOARD_ITEM* Duplicate( const BOARD_ITEM* aItem, bool aAddToBoard = false );
 
     /**
diff --git a/pcbnew/tools/common_actions.cpp b/pcbnew/tools/common_actions.cpp
index b9af35915..e303e8575 100644
--- a/pcbnew/tools/common_actions.cpp
+++ b/pcbnew/tools/common_actions.cpp
@@ -66,6 +66,26 @@ TOOL_ACTION COMMON_ACTIONS::selectNet( "pcbnew.InteractiveSelection.SelectNet",
         AS_GLOBAL, 0,
         _( "whole net" ), _( "Selects all tracks & vias belonging to the same net." ) );
 
+TOOL_ACTION COMMON_ACTIONS::selectGroup( "pcbnew.InteractiveSelection.SelectGroup",
+        AS_GLOBAL, 0,
+        _( "group" ), _( "Selects items in specified group." ) );
+
+TOOL_ACTION COMMON_ACTIONS::selectSameGroup( "pcbnew.InteractiveSelection.SelectSameGroup",
+        AS_GLOBAL, 'G',
+        _( "same group" ), _( "Selects items in the same group." ) );
+
+TOOL_ACTION COMMON_ACTIONS::assignGroup( "pcbnew.EditorControl.AssignGroup",
+        AS_GLOBAL, 0,
+        _( "Assign to existing group" ), _( "Assign selection to group." ) );
+
+TOOL_ACTION COMMON_ACTIONS::assignNewGroup( "pcbnew.EditorControl.AssignNewGroup",
+        AS_GLOBAL, 0,
+        _( "Assign to new group" ), _( "Assign selection to group." ) );
+
+TOOL_ACTION COMMON_ACTIONS::clearGroup( "pcbnew.EditorControl.ClearGroup",
+        AS_GLOBAL, 0,
+        _( "Clear group belonging" ), _( "remove group belonging from selection." ) );
+
 TOOL_ACTION COMMON_ACTIONS::find( "pcbnew.InteractiveSelection.Find",
         AS_GLOBAL, 0, //TOOL_ACTION::LegacyHotKey( HK_FIND_ITEM ), // handled by wxWidgets
         _( "Find an item" ), _( "Searches the document for an item" ), find_xpm );
diff --git a/pcbnew/tools/common_actions.h b/pcbnew/tools/common_actions.h
index 962b6b436..8fe45bf63 100644
--- a/pcbnew/tools/common_actions.h
+++ b/pcbnew/tools/common_actions.h
@@ -66,6 +66,21 @@ public:
     /// Selects all connections belonging to a single net.
     static TOOL_ACTION selectNet;
 
+    /// Selects all items belonging the same group
+    static TOOL_ACTION selectGroup;
+
+    /// Selects all items belonging the same group
+    static TOOL_ACTION selectSameGroup;
+
+    /// Assign selected items to group
+    static TOOL_ACTION assignGroup;
+
+    /// Assign selected items to new group
+    static TOOL_ACTION assignNewGroup;
+
+    /// Clear selected items from group
+    static TOOL_ACTION clearGroup;
+
     // Edit Tool
     /// Activation of the edit tool
     static TOOL_ACTION editActivate;
diff --git a/pcbnew/tools/pcb_editor_control.cpp b/pcbnew/tools/pcb_editor_control.cpp
index 8a98ea4d7..b98f33863 100644
--- a/pcbnew/tools/pcb_editor_control.cpp
+++ b/pcbnew/tools/pcb_editor_control.cpp
@@ -102,6 +102,17 @@ public:
     }
 };
 
+class GROUP_CONTEXT_MENU : public CONTEXT_MENU
+{
+public:
+    GROUP_CONTEXT_MENU()
+    {
+        Add( COMMON_ACTIONS::assignGroup );
+        Add( COMMON_ACTIONS::assignNewGroup );
+        Add( COMMON_ACTIONS::clearGroup );
+    }
+};
+
 
 PCB_EDITOR_CONTROL::PCB_EDITOR_CONTROL() :
     TOOL_INTERACTIVE( "pcbnew.EditorControl" ),
@@ -142,6 +153,8 @@ bool PCB_EDITOR_CONTROL::Init()
     auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
     lockMenu->SetTool( this );
 
+    auto groupMenu = std::make_shared<GROUP_CONTEXT_MENU>();
+    groupMenu->SetTool( this );
     // Add the PCB control menus to relevant other tools
 
     SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
@@ -153,12 +166,19 @@ bool PCB_EDITOR_CONTROL::Init()
 
         toolMenu.AddSubMenu( zoneMenu );
         toolMenu.AddSubMenu( lockMenu );
+        toolMenu.AddSubMenu( groupMenu );
 
         menu.AddMenu( zoneMenu.get(), _( "Zones" ), false,
                       SELECTION_CONDITIONS::OnlyType( PCB_ZONE_AREA_T ) );
 
         menu.AddMenu( lockMenu.get(), _( "Locking" ), false,
                       SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
+
+        menu.AddMenu( lockMenu.get(), _( "Locking" ), false,
+                      SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
+
+        menu.AddMenu( groupMenu.get(), _( "Groups" ), false ,
+                                    SELECTION_CONDITIONS::MoreThan( 0 ) );
     }
 
     DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
@@ -863,6 +883,133 @@ int PCB_EDITOR_CONTROL::HighlightNetCursor( const TOOL_EVENT& aEvent )
     return 0;
 }
 
+int PCB_EDITOR_CONTROL::AssignGroup( const TOOL_EVENT& aEvent )
+{
+    SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
+    const SELECTION& selection = selTool->GetSelection();
+
+    if( selection.Empty() )
+        m_toolMgr->RunAction( COMMON_ACTIONS::selectionCursor, true );
+
+    bool modified = false;
+
+    wxString group;
+    wxArrayString existingGroups;
+
+    BOARD* board = getModel<BOARD>();
+    std::list<BOARD_ITEM*> itemsList = board->GetAllGroupItems();
+
+    for( BOARD_ITEM* i : itemsList )
+    {
+        bool groupSeen = false;
+
+        if( i->GetGroupId() == "")
+            continue;
+
+        for( wxString s : existingGroups )
+        {
+            if( s == i->GetGroupId() )
+                groupSeen = true;
+        }
+        if( !groupSeen  && i->GetGroupId() != "" )
+            existingGroups.Add( i->GetGroupId() );
+    }
+
+    wxSingleChoiceDialog dlg( NULL,  _( "Groups:" ), wxEmptyString, existingGroups);
+
+    if ( dlg.ShowModal() == wxID_OK )
+    {
+        // We can be certain that this string contains letters only.
+        group = dlg.GetStringSelection();
+    }
+    else
+    {
+        return 0;
+    }
+
+    for( auto item : selection )
+    {
+
+        item->SetGroupId( group.ToStdString() );
+
+        // Check if we really modified an item
+        modified = true;
+    }
+
+    if( modified )
+        m_frame->OnModify();
+
+    return 0;
+}
+
+int PCB_EDITOR_CONTROL::AssignNewGroup( const TOOL_EVENT& aEvent )
+{
+    SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
+    const SELECTION& selection = selTool->GetSelection();
+
+    if( selection.Empty() )
+        m_toolMgr->RunAction( COMMON_ACTIONS::selectionCursor, true );
+
+    bool modified = false;
+
+    wxString group;
+
+    wxTextEntryDialog dlg( NULL, _( "New group:" ), wxEmptyString, group );
+
+    //We want to limit the group names to some kind of standard
+    wxTextValidator textValidator( wxFILTER_ALPHANUMERIC );
+    dlg.SetTextValidator( textValidator );
+
+    if ( dlg.ShowModal() == wxID_OK )
+    {
+        // We can be certain that this string contains letters only.
+        group = dlg.GetValue();
+    }
+    else
+    {
+        return 0;
+    }
+
+    for( auto item : selection )
+    {
+
+        item->SetGroupId( group.ToStdString() );
+
+        // Check if we really modified an item
+        modified = true;
+    }
+
+    if( modified )
+        m_frame->OnModify();
+
+    return 0;
+}
+
+int PCB_EDITOR_CONTROL::ClearGroup( const TOOL_EVENT& aEvent )
+{
+    SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
+    const SELECTION& selection = selTool->GetSelection();
+
+    if( selection.Empty() )
+        m_toolMgr->RunAction( COMMON_ACTIONS::selectionCursor, true );
+
+    bool modified = false;
+
+    for( auto item : selection )
+    {
+
+        item->SetGroupId( "" );
+
+        // Check if we really modified an item
+        modified = true;
+    }
+
+    if( modified )
+        m_frame->OnModify();
+
+    return 0;
+}
+
 
 void PCB_EDITOR_CONTROL::SetTransitions()
 {
@@ -892,6 +1039,12 @@ void PCB_EDITOR_CONTROL::SetTransitions()
     Go( &PCB_EDITOR_CONTROL::DrillOrigin,         COMMON_ACTIONS::drillOrigin.MakeEvent() );
     Go( &PCB_EDITOR_CONTROL::HighlightNet,        COMMON_ACTIONS::highlightNet.MakeEvent() );
     Go( &PCB_EDITOR_CONTROL::HighlightNetCursor,  COMMON_ACTIONS::highlightNetCursor.MakeEvent() );
+
+    //Group actions
+    Go( &PCB_EDITOR_CONTROL::AssignGroup,  COMMON_ACTIONS::assignGroup.MakeEvent() );
+    Go( &PCB_EDITOR_CONTROL::AssignNewGroup,  COMMON_ACTIONS::assignNewGroup.MakeEvent() );
+    Go( &PCB_EDITOR_CONTROL::ClearGroup,  COMMON_ACTIONS::clearGroup.MakeEvent() );
+
 }
 
 
diff --git a/pcbnew/tools/pcb_editor_control.h b/pcbnew/tools/pcb_editor_control.h
index 1647297d1..810aa7f8b 100644
--- a/pcbnew/tools/pcb_editor_control.h
+++ b/pcbnew/tools/pcb_editor_control.h
@@ -96,6 +96,15 @@ public:
     ///> Highlights net belonging to the item under the cursor.
     int HighlightNet( const TOOL_EVENT& aEvent );
 
+    ///> Assigns group to the current selected items.
+    int AssignGroup( const TOOL_EVENT& aEvent );
+
+    ///> Assign selected items to a new group.
+    int AssignNewGroup( const TOOL_EVENT& aEvent );
+
+    ///> removes group from the current selected items.
+    int ClearGroup( const TOOL_EVENT& aEvent );
+
     ///> Launches a tool to pick the item whose net is going to be highlighted.
     int HighlightNetCursor( const TOOL_EVENT& aEvent );
 
diff --git a/pcbnew/tools/selection_tool.cpp b/pcbnew/tools/selection_tool.cpp
index dde12c6e8..1ba6e114b 100644
--- a/pcbnew/tools/selection_tool.cpp
+++ b/pcbnew/tools/selection_tool.cpp
@@ -58,9 +58,30 @@ class SELECT_MENU: public CONTEXT_MENU
 public:
     SELECT_MENU()
     {
+        SetUpdateHandler( std::bind( &SELECT_MENU::update, this ) );
         Add( COMMON_ACTIONS::selectConnection );
         Add( COMMON_ACTIONS::selectCopper );
         Add( COMMON_ACTIONS::selectNet );
+        Add( COMMON_ACTIONS::selectSameGroup );
+        Add( COMMON_ACTIONS::selectGroup );
+    }
+private:
+    void update()
+    {
+        SELECTION_TOOL* selTool = getToolManager()->GetTool<SELECTION_TOOL>();
+
+        // lines like this make me really think about a better name for SELECTION_CONDITIONS class
+        bool selEnabled =  ( SELECTION_CONDITIONS::OnlyType( PCB_VIA_T )
+                || SELECTION_CONDITIONS::OnlyType( PCB_TRACE_T ) )
+                              ( selTool->GetSelection() );
+
+        bool groupSelEnabled = ( SELECTION_CONDITIONS::Count( 1 ) )
+                              ( selTool->GetSelection() );
+
+        Enable( getMenuId( COMMON_ACTIONS::selectNet ), selEnabled );
+        Enable( getMenuId( COMMON_ACTIONS::selectCopper ), selEnabled );
+        Enable( getMenuId( COMMON_ACTIONS::selectConnection ), selEnabled );
+        Enable( getMenuId( COMMON_ACTIONS::selectSameGroup ), groupSelEnabled );
     }
 };
 
@@ -94,9 +115,9 @@ bool SELECTION_TOOL::Init()
 
     auto& menu = m_menu.GetMenu();
 
-    menu.AddMenu( selectMenu.get(), _( "Select..." ), false, showSelectMenuFunctor );
+    menu.AddMenu( selectMenu.get(), _( "Select..." ), false, S_C::ShowAlways );
     // only show separator if there is a Select menu to show above it
-    menu.AddSeparator( showSelectMenuFunctor, 1000 );
+    menu.AddSeparator( S_C::ShowAlways, 1000 );
 
     m_menu.AddStandardSubMenus( *getEditFrame<PCB_BASE_FRAME>() );
 
@@ -258,6 +279,16 @@ int SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
             selectCopper( *evt );
         }
 
+        else if( evt->IsAction( &COMMON_ACTIONS::selectGroup ) )
+        {
+            selectGroup( *evt );
+        }
+
+        else if( evt->IsAction( &COMMON_ACTIONS::selectSameGroup ) )
+        {
+            selectSameGroup( *evt );
+        }
+
         else if( evt->IsAction( &COMMON_ACTIONS::selectNet ) )
         {
             selectNet( *evt );
@@ -490,6 +521,8 @@ void SELECTION_TOOL::SetTransitions()
     Go( &SELECTION_TOOL::findMove, COMMON_ACTIONS::findMove.MakeEvent() );
     Go( &SELECTION_TOOL::selectConnection, COMMON_ACTIONS::selectConnection.MakeEvent() );
     Go( &SELECTION_TOOL::selectCopper, COMMON_ACTIONS::selectCopper.MakeEvent() );
+    Go( &SELECTION_TOOL::selectGroup, COMMON_ACTIONS::selectGroup.MakeEvent() );
+    Go( &SELECTION_TOOL::selectSameGroup, COMMON_ACTIONS::selectSameGroup.MakeEvent() );
     Go( &SELECTION_TOOL::selectNet, COMMON_ACTIONS::selectNet.MakeEvent() );
 }
 
@@ -646,6 +679,59 @@ int SELECTION_TOOL::selectCopper( const TOOL_EVENT& aEvent )
     return 0;
 }
 
+int SELECTION_TOOL::selectSameGroup( const TOOL_EVENT& aEvent )
+{
+    if( !selectCursor( true ) )
+        return 0;
+
+    auto item = static_cast<BOARD_ITEM*> ( m_selection.Front() );
+
+    if( item->GetGroupId() == "" )
+        return 0;
+
+    clearSelection();
+
+    std::list<BOARD_ITEM*> itemsList = board()->GetAllGroupItems();
+
+    for( BOARD_ITEM* i : itemsList )
+        if( i->GetGroupId() == item->GetGroupId() )
+            select( i );
+
+    // Inform other potentially interested tools
+    if( itemsList.size() > 0 )
+        m_toolMgr->ProcessEvent( SelectedEvent );
+
+    return 0;
+}
+
+int SELECTION_TOOL::selectGroup( const TOOL_EVENT& aEvent )
+{
+    wxArrayString groupList = board()->GetGroups();
+
+    wxSingleChoiceDialog dlg( NULL,  _( "Groups:" ), wxEmptyString, groupList );
+
+    if( dlg.ShowModal() != wxID_OK )
+        return 0;
+
+    clearSelection();
+
+    std::list<BOARD_ITEM*> itemsList = board()->GetAllGroupItems();
+
+    for( BOARD_ITEM* i : itemsList )
+    {
+        if( i->GetGroupId() == dlg.GetStringSelection() )
+        {
+            select( i );
+        }
+    }
+
+    // Inform other potentially interested tools
+    if( itemsList.size() > 0 )
+        m_toolMgr->ProcessEvent( SelectedEvent );
+
+    return 0;
+}
+
 
 int SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent )
 {
diff --git a/pcbnew/tools/selection_tool.h b/pcbnew/tools/selection_tool.h
index ec2edc95c..11b6fd6e9 100644
--- a/pcbnew/tools/selection_tool.h
+++ b/pcbnew/tools/selection_tool.h
@@ -265,6 +265,12 @@ private:
     ///> Selects a continuous copper connection.
     int selectCopper( const TOOL_EVENT& aEvent );
 
+    ///> Selects a group based on selected item.
+    int selectGroup( const TOOL_EVENT& aEvent );
+
+    ///> Selects all in group chosen from list.
+    int selectSameGroup( const TOOL_EVENT& aEvent );
+
     ///> Selects all copper connections belonging to a single net.
     int selectNet( const TOOL_EVENT& aEvent );
 
-- 
2.11.0

>From 280cc95a75242c07f297fd02cdbb7839f9fe232b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20=C3=96dmark?= <kristoffer.odmark90@xxxxxxxxx>
Date: Sat, 14 Jan 2017 00:29:26 +0100
Subject: [PATCH 2/2] Made selection groups persistant

---
 common/pcb.keywords     |  3 ++-
 pcbnew/kicad_plugin.cpp | 24 ++++++++++++++++++++++++
 pcbnew/pcb_parser.cpp   | 47 ++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/common/pcb.keywords b/common/pcb.keywords
index e8ffab2b0..5e7ba3ab8 100644
--- a/common/pcb.keywords
+++ b/common/pcb.keywords
@@ -86,6 +86,7 @@ gr_curve
 gr_line
 gr_poly
 gr_text
+group
 hatch
 hide
 italic
@@ -208,4 +209,4 @@ zone_45_only
 zone_clearance
 zone_connect
 zone_type
-zones
\ No newline at end of file
+zones
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 8df165560..107c39558 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -827,6 +827,10 @@ void PCB_IO::format( DIMENSION* aDimension, int aNestLevel ) const
                   FMT_IU( aDimension->m_arrowG2F.x ).c_str(),
                   FMT_IU( aDimension->m_arrowG2F.y ).c_str() );
 
+   if( aDimension->GetGroupId() != "" )
+       m_out->Print( aNestLevel+1, "(group %s)\n",
+               aDimension->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( aNestLevel, ")\n" );
 }
 
@@ -893,6 +897,10 @@ void PCB_IO::format( DRAWSEGMENT* aSegment, int aNestLevel ) const
     if( aSegment->GetStatus() )
         m_out->Print( 0, " (status %X)", aSegment->GetStatus() );
 
+    if( aSegment->GetGroupId() != "")
+            m_out->Print( 0, " (group %s)",
+                   aSegment->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( 0, ")\n" );
 }
 
@@ -1128,6 +1136,10 @@ void PCB_IO::format( MODULE* aModule, int aNestLevel ) const
         ++bs3D;
     }
 
+    if( aModule->GetGroupId() != "" )
+        m_out->Print( aNestLevel+1, "(group %s)\n",
+                aModule ->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( aNestLevel, ")\n" );
 }
 
@@ -1371,6 +1383,10 @@ void PCB_IO::format( TEXTE_PCB* aText, int aNestLevel ) const
 
     aText->EDA_TEXT::Format( m_out, aNestLevel, m_ctl );
 
+    if( aText->GetGroupId() != "" )
+        m_out->Print( aNestLevel+1, "(group %s)\n",
+                aText->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( aNestLevel, ")\n" );
 }
 
@@ -1478,6 +1494,10 @@ void PCB_IO::format( TRACK* aTrack, int aNestLevel ) const
     if( aTrack->GetStatus() != 0 )
         m_out->Print( 0, " (status %X)", aTrack->GetStatus() );
 
+    if( aTrack->GetGroupId() != "" )
+        m_out->Print( 0, " (group %s)",
+                aTrack->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( 0, ")\n" );
 }
 
@@ -1702,6 +1722,10 @@ void PCB_IO::format( ZONE_CONTAINER* aZone, int aNestLevel ) const
         m_out->Print( aNestLevel+1, ")\n" );
     }
 
+    if( aZone->GetGroupId() != "" )
+        m_out->Print( aNestLevel+1, "(group %s)\n",
+                aZone->GetGroupId().ToStdString().c_str() );
+
     m_out->Print( aNestLevel, ")\n" );
 }
 
diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp
index 89460ec40..ac461cf03 100644
--- a/pcbnew/pcb_parser.cpp
+++ b/pcbnew/pcb_parser.cpp
@@ -1486,8 +1486,13 @@ DRAWSEGMENT* PCB_PARSER::parseDRAWSEGMENT() throw( IO_ERROR, PARSE_ERROR )
             segment->SetStatus( static_cast<STATUS_FLAGS>( parseHex() ) );
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            segment->SetGroupId( FromUTF8() );
+            break;
+
         default:
-            Expecting( "layer, width, tstamp, or status" );
+            Expecting( "layer, width, group, tstamp, or status" );
         }
 
         NeedRIGHT();
@@ -1496,7 +1501,6 @@ DRAWSEGMENT* PCB_PARSER::parseDRAWSEGMENT() throw( IO_ERROR, PARSE_ERROR )
     return segment.release();
 }
 
-
 TEXTE_PCB* PCB_PARSER::parseTEXTE_PCB() throw( IO_ERROR, PARSE_ERROR )
 {
     wxCHECK_MSG( CurTok() == T_gr_text, NULL,
@@ -1556,8 +1560,14 @@ TEXTE_PCB* PCB_PARSER::parseTEXTE_PCB() throw( IO_ERROR, PARSE_ERROR )
             parseEDA_TEXT( (EDA_TEXT*) text.get() );
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            text->SetGroupId( FromUTF8() );
+            NeedRIGHT();
+            break;
+
         default:
-            Expecting( "layer, tstamp or effects" );
+            Expecting( "layer, group, tstamp or effects" );
         }
     }
 
@@ -1707,8 +1717,14 @@ DIMENSION* PCB_PARSER::parseDIMENSION() throw( IO_ERROR, PARSE_ERROR )
             NeedRIGHT();
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            dimension->SetGroupId( FromUTF8() );
+            NeedRIGHT();
+            break;
+
         default:
-            Expecting( "layer, tstamp, gr_text, feature1, feature2 crossbar, arrow1a, "
+            Expecting( "layer, tstamp, gr_text, group, feature1, feature2 crossbar, arrow1a, "
                        "arrow1b, arrow2a, or arrow2b" );
         }
     }
@@ -1970,12 +1986,18 @@ MODULE* PCB_PARSER::parseMODULE_unchecked( wxArrayString* aInitialComments )
             module->Add3DModel( parse3DModel() );
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            module->SetGroupId( FromUTF8() );
+            NeedRIGHT();
+            break;
+
         default:
             Expecting( "locked, placed, tedit, tstamp, at, descr, tags, path, "
                        "autoplace_cost90, autoplace_cost180, solder_mask_margin, "
                        "solder_paste_margin, solder_paste_ratio, clearance, "
                        "zone_connect, thermal_width, thermal_gap, attr, fp_text, "
-                       "fp_arc, fp_circle, fp_curve, fp_line, fp_poly, pad, or model" );
+                       "fp_arc, fp_circle, fp_curve, fp_line, fp_poly, pad, group or model" );
         }
     }
 
@@ -2547,8 +2569,13 @@ TRACK* PCB_PARSER::parseTRACK() throw( IO_ERROR, PARSE_ERROR )
             track->SetStatus( static_cast<STATUS_FLAGS>( parseHex() ) );
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            track->SetGroupId( FromUTF8() );
+            break;
+
         default:
-            Expecting( "start, end, width, layer, net, tstamp, or status" );
+            Expecting( "start, end, width, layer, net, tstamp, group or status" );
         }
 
         NeedRIGHT();
@@ -2632,8 +2659,14 @@ VIA* PCB_PARSER::parseVIA() throw( IO_ERROR, PARSE_ERROR )
             NeedRIGHT();
             break;
 
+        case T_group:
+            NeedSYMBOLorNUMBER();
+            via->SetGroupId( FromUTF8() );
+            NeedRIGHT();
+            break;
+
         default:
-            Expecting( "blind, micro, at, size, drill, layers, net, tstamp, or status" );
+            Expecting( "blind, micro, at, size, drill, layers, net, tstamp, group or status" );
         }
     }
 
-- 
2.11.0


References