Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Discussion: PCB_GROUP items in 5.99 and 6.0 #123

@EeliK

Description

@EeliK

I'll detach discussion about PCB_GROUP items here because this isn't a bug or feature request for specific feature, but I want to give some details which may make decisions and feature plans easier. I just introduce pcbnew groups and possible use cases for especially replicate layout plugin.

The class itself is pretty simple, it has a set of items and ways to handle them as one whole (select, copy, rotate etc.). It's here for reference.

/**
 * A set of BOARD_ITEMs (i.e., without duplicates).
 *
 * The group parent is always board, not logical parent group. The 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.
 */
class PCB_GROUP : public BOARD_ITEM
{
public:
    PCB_GROUP( BOARD_ITEM* aParent );

    static inline bool ClassOf( const EDA_ITEM* aItem )
    {
        return aItem && PCB_GROUP_T == aItem->Type();
    }

    wxString GetClass() const override
    {
        return wxT( "PCB_GROUP" );
    }

    wxString GetName() const { return m_name; }
    void SetName( wxString aName ) { m_name = aName; }

    std::unordered_set<BOARD_ITEM*>& GetItems()
    {
        return m_items;
    }

    const std::unordered_set<BOARD_ITEM*>& GetItems() const
    {
        return m_items;
    }

    /**
     * Add item to group. Does not take ownership of item.
     *
     * @return true if item was added (false if item belongs to a different group).
     */
    bool AddItem( BOARD_ITEM* aItem );

    /**
     * Remove item from group.
     *
     * @return true if item was removed (false if item was not in the group).
     */
    bool RemoveItem( BOARD_ITEM* aItem );

    void RemoveAll();

    /*
     * Search for highest level group containing item.
     *
     * @param aScope restricts the search to groups within the group scope.
     * @param aFootprintEditor true if we should stop promoting at the footprint level
     * @return group containing item, if it exists, otherwise, NULL
     */
    static PCB_GROUP* TopLevelGroup( BOARD_ITEM* aItem, PCB_GROUP* aScope, bool aFootprintEditor );

    static bool WithinScope( BOARD_ITEM* item, PCB_GROUP* scope );

#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& aNewpos ) 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 descendants
     */
    PCB_GROUP* DeepClone() const;

    /*
     * Duplicate() this and all descendants
     */
    PCB_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;

    ///< @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 aInspector, void* aTestData,
                         const KICAD_T aScanTypes[] ) override;

    ///< @copydoc VIEW_ITEM::ViewGetLayers
    void ViewGetLayers( int aLayers[], int& aCount ) const override;

    ///< @copydoc VIEW_ITEM::ViewGetLOD
    double 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;

    /**
     * Invoke a function on all members of the group.
     *
     * @note 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 ) const;

    /**
     * Invoke a function on all descendants of the group.
     *
     * @note This function should not add or remove items to the group or descendant groups.
     * @param aFunction is the function to be invoked.
     */
    void RunOnDescendants( const std::function<void( BOARD_ITEM* )>& aFunction ) const;

private:
    std::unordered_set<BOARD_ITEM*> m_items;     // Members of the group
    wxString                        m_name;      // Optional group name
};

Groups can be nested, but otherwise one item can belong only to one group. Groups can be named -- this is important because it can be utilized by plugins.


image

screencast_kicad-2021-03-08_12.39.36.mp4

It would be relatively easy to decide how to utilize groups for certain plugin, but the user may want to use groups for other purposes for these items, so it's not that simple. For example several non-related groups can intersect a "channel" layout area when it's replicated. The user must know how to use groups for the plugin and the plugin must know how the user uses them. For example, here are some possible situations.

image
image
image
image
image
image
image


There are several possible strategies:

  • Ignore groups altogether (the current behavior).
  • Use groups to select footprints to be affected, i.e. some footprints of a hierarchical sheet can be left out.
  • Use groups to select extra items to be replicated.
  • Use groups to select all items to be replicated, i.e. footprints and extra items.

There are several details to notice:

  • Replicating both groups and their items should be avoided because the items would be duplicated.
  • If groups are ignored in implementation groups aren't replicated but items are (the current behavior). So the original can have a group but the corresponding items aren't grouped in the replicated targets.
  • The items in a user created group can be spread far away.
  • There can be several groups in the bounding box of the footprints. Only some of them can be meant for replication.
  • It's possible to ignore the bounding box altogether if group is used for extra items.
  • A named group can be used: for example, add possibility in the UI to filter replicated groups by regular expression. The name could be for example "replicate_U1" where U1 would be the anchor fooptrint. The group would then be replicated as "replicate_U2" etc. That way each hierarchical sheet would have its own named group which then could be deleted instead of trying to find and delete previously replicated extra items.

The most straightforward strategy would be probably to use one group for the original layout, and all the items which are to be replicated should belong to that group. Then:

  • Positions of all footprints belonging to the group would be replicated, but not other footprints. (Footprints not belonging to the hierarchical sheet should be ignored or the attempt should be rejected.)
  • Extra items belonging to that group would be replicated.
  • In the targets the replicated items should be grouped again.
  • If the plugin is used several times, there are already groups in the targets. Then the extra items from the groups should be first deleted from the targets (but not the footprints).
  • What to do if the user has used groups for other purposes? For example, in one target one footrprint is already group for some other purpose. Groups are exclusive. In that case the old grouping must be removed or grouping can't be used.
  • What to do if the user changes the grouping, for example chooses different subset of the sheet's footprint to be replicated? Must the old groupings be deleted first?
  • There may be other difficult situations, or at least things to be considered, for example the user ungroups items, adds some of them into new subgroups, then groups the items again so that the original grouping is still there but some items are in the group only indirectly... Or the user changes the grouping in one of the targets... Or selects another sheet/"channel" as the original...

As can be seen, grouping can make things easier but also more difficult. Even if groups aren't used for the plugin they must be dealt with if the user uses them. And they can be used in some implicit simple ways, or several options can be added to the UI.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions