← Back to team overview

kicad-developers team mailing list archive

[Patch] pcbnew clipboard support

 

Hello all!

Last night I was doing some design that used a PCB antenna. I had this antenna design made in zones, It was very frustrating to try to copy this between an old design and a new one since I had to use a texteditor ( very glad that I could though ).

After this I figured that this should be able to be copy-pasted between pcbs.

So I have now implemented a subclass of the PCB_IO, that can format an entire board or a selection to the clipboard in textformat, and then created a subclass to the PCB_PARSER that parses from the clipboard and reuses the "append board" functionality to add them back. It also does remove the "path" properties of modules. This allows me to copy-paste things between different PCBs in kicad. The shortcut keys are ctrl+shift+c and ctrl+shift+v so that people dont use this by mistake :)

I think that this might be useful for reusing designs in some manner, and if this is in Kicad, my next step would be to start looking into some tool to link the pasted modules to symbols in the schematic.

Give me some insight on what you think about this please :)

video: https://youtu.be/4SuUzma0Ua4

(only tested in linux)

- Kristoffer
>From 02c278a4f9762bc36dd370530a28256fd2a438af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20=C3=96dmark?= <kristoffer.odmark90@xxxxxxxxx>
Date: Sat, 15 Apr 2017 18:08:23 +0200
Subject: [PATCH] Added a new plugin that can save and load to clipboard, using
 ctrl+shift+c/v for copy pasting

Added the needed kicad_clipboard files and made copy pasted modules have no path
---
 common/CMakeLists.txt               |   1 +
 pcbnew/kicad_clipboard.cpp          | 177 ++++++++++++++++++++++++++++++++++++
 pcbnew/kicad_clipboard.h            |  59 ++++++++++++
 pcbnew/kicad_plugin.cpp             |  11 ++-
 pcbnew/kicad_plugin.h               |   8 +-
 pcbnew/pcb_parser.h                 |   7 +-
 pcbnew/tools/pcb_actions.h          |   4 +
 pcbnew/tools/pcb_editor_control.cpp |   4 +
 pcbnew/tools/pcbnew_control.cpp     |  26 +++++-
 pcbnew/tools/pcbnew_control.h       |   5 +-
 pcbnew/tools/selection_tool.cpp     |  23 +++++
 pcbnew/tools/selection_tool.h       |   8 ++
 12 files changed, 320 insertions(+), 13 deletions(-)
 create mode 100644 pcbnew/kicad_clipboard.cpp
 create mode 100644 pcbnew/kicad_clipboard.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index e119d4b7e..7b10e4447 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -393,6 +393,7 @@ set( PCB_COMMON_SRCS
     ../pcbnew/eagle_plugin.cpp
     ../pcbnew/legacy_plugin.cpp
     ../pcbnew/kicad_plugin.cpp
+    ../pcbnew/kicad_clipboard.cpp
     ../pcbnew/gpcb_plugin.cpp
     ../pcbnew/pcb_netlist.cpp
     pcb_plot_params_keywords.cpp
diff --git a/pcbnew/kicad_clipboard.cpp b/pcbnew/kicad_clipboard.cpp
new file mode 100644
index 000000000..d79fb7df2
--- /dev/null
+++ b/pcbnew/kicad_clipboard.cpp
@@ -0,0 +1,177 @@
+/**
+ * @file kicad_clipboard.cpp
+ * @brief Kicad clipboard plugin that piggybacks on the kicad_plugin
+ * @author Kristoffer Ödmark
+ * @version 1.0
+ * @date 2017-05-03
+ */
+
+#include <wx/clipbrd.h>
+#include <common.h>
+#include <pcb_parser.h>
+#include <class_netinfo.h>
+#include <class_board.h>
+#include <build_version.h>
+
+#include <kicad_plugin.h>
+#include <kicad_clipboard.h>
+
+CLIPBOARD_IO::CLIPBOARD_IO():
+    PCB_IO(),
+    m_formatter(),
+    m_parser( new CLIPBOARD_PARSER() )
+{
+    m_out = &m_formatter;
+}
+
+CLIPBOARD_IO::~CLIPBOARD_IO(){}
+
+STRING_FORMATTER* CLIPBOARD_IO::GetFormatter()
+{
+    return &m_formatter;
+}
+
+void CLIPBOARD_IO::setBoard(BOARD* aBoard)
+{
+    m_board = aBoard;
+}
+
+void CLIPBOARD_IO::writeHeader(BOARD* aBoard)
+{
+    formatHeader( aBoard );
+}
+
+
+void CLIPBOARD_IO::SaveSelection( SELECTION& aSelected )
+{
+    LOCALE_IO   toggle;     // toggles on, then off, the C locale.
+
+    // Prepare net mapping that assures that net codes saved in a file are consecutive integers
+    m_mapping->SetBoard( m_board );
+
+    // we will fake being a .kicad_pcb to get the full parser kicking
+    // This means we also need layers and nets
+
+    m_formatter.Print( 0, "(kicad_pcb (version %d) (host pcbnew %s)\n", SEXPR_BOARD_FILE_VERSION,
+            m_formatter.Quotew( GetBuildVersion() ).c_str() );
+
+    if( aSelected.Empty() )
+        return;
+
+    writeHeader( m_board );
+
+    m_formatter.Print( 0, "\n" );
+
+    for( auto i : aSelected )
+    {
+
+        // Dont format stuff that cannot exist standalone!
+        if( ( i->Type() != PCB_MODULE_EDGE_T ) &&
+                ( i->Type() != PCB_MODULE_TEXT_T ) &&
+                ( i->Type() != PCB_PAD_T ) )
+        {
+            std::cout <<"type "<< i->Type() << std::endl;
+            auto item = static_cast<BOARD_ITEM*>( i );
+            Format( item, 1 );
+        }
+
+    }
+    m_formatter.Print( 0, "\n)" );
+
+
+    if( wxTheClipboard->Open() )
+    {
+        wxTheClipboard->SetData( new wxTextDataObject( wxString( m_formatter.GetString().c_str(), wxConvUTF8 ) ) );
+        wxTheClipboard->Close();
+    }
+}
+
+void CLIPBOARD_IO::Save( const wxString& aFileName, BOARD* aBoard,
+                const PROPERTIES* aProperties )
+{
+    LOCALE_IO   toggle;     // toggles on, then off, the C locale.
+
+    init( aProperties );
+
+    m_board = aBoard;       // after init()
+
+    // Prepare net mapping that assures that net codes saved in a file are consecutive integers
+    m_mapping->SetBoard( aBoard );
+
+    STRING_FORMATTER    formatter;
+
+    m_out = &formatter;
+
+    m_out->Print( 0, "(kicad_pcb (version %d) (host pcbnew %s)\n", SEXPR_BOARD_FILE_VERSION,
+                  formatter.Quotew( GetBuildVersion() ).c_str() );
+
+    Format( aBoard, 1 );
+
+    m_out->Print( 0, ")\n" );
+
+    if( wxTheClipboard->Open() )
+    {
+        wxTheClipboard->SetData( new wxTextDataObject( wxString( m_formatter.GetString().c_str(), wxConvUTF8 ) ) );
+        wxTheClipboard->Close();
+    }
+
+}
+
+BOARD* CLIPBOARD_IO::Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties )
+{
+    std::string result;
+
+    if( wxTheClipboard->Open() )
+    {
+        if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
+        {
+            wxTextDataObject data;
+            wxTheClipboard->GetData( data );
+
+            result = data.GetText().mb_str();
+        }
+
+        wxTheClipboard->Close();
+    }
+
+    STRING_LINE_READER    reader(result, wxT( "clipboard" ) );
+
+    init( aProperties );
+
+    m_parser->SetLineReader( &reader );
+    m_parser->SetBoard( aAppendToMe );
+
+    BOARD* board;
+
+    try
+    {
+        board = dynamic_cast<BOARD*>( m_parser->Parse() );
+    }
+    catch( const FUTURE_FORMAT_ERROR& )
+    {
+        // Don't wrap a FUTURE_FORMAT_ERROR in another
+        throw;
+    }
+    catch( const PARSE_ERROR& parse_error )
+    {
+        if( m_parser->IsTooRecent() )
+            throw FUTURE_FORMAT_ERROR( parse_error, m_parser->GetRequiredVersion() );
+        else
+            throw;
+    }
+
+    if( !board )
+    {
+        // The parser loaded something that was valid, but wasn't a board.
+        THROW_PARSE_ERROR( _( "Clipboard content is not Kicad compatible" ),
+                m_parser->CurSource(), m_parser->CurLine(),
+                m_parser->CurLineNumber(), m_parser->CurOffset() );
+    }
+
+    // Give the filename to the board if it's new
+    if( !aAppendToMe )
+        board->SetFileName( aFileName );
+
+    return board;
+}
+
diff --git a/pcbnew/kicad_clipboard.h b/pcbnew/kicad_clipboard.h
new file mode 100644
index 000000000..c49cf5a5b
--- /dev/null
+++ b/pcbnew/kicad_clipboard.h
@@ -0,0 +1,59 @@
+/**
+ * @file kicad_clipboard.h
+ * @brief
+ * @author Kristoffer Ödmark
+ * @version 1.0
+ * @date 2017-05-03
+ */
+#ifndef KICAD_CLIPBOARD_H
+#define KICAD_CLIPBOARD_H
+
+#include <kicad_plugin.h>
+#include <class_board_item.h>
+#include <class_module.h>
+#include <pcb_parser.h>
+#include <memory.h>
+
+#include <tool/selection.h>
+
+class CLIPBOARD_PARSER : public PCB_PARSER
+{
+public:
+    CLIPBOARD_PARSER( LINE_READER* aReader = NULL ): PCB_PARSER( aReader ) {};
+
+    MODULE* parseMODULE( wxArrayString* aInitialComments )
+        throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR ) override
+    {
+       MODULE* mod = PCB_PARSER::parseMODULE(aInitialComments);
+       mod->SetPath( wxT( "" ) );
+       return mod;
+    }
+};
+
+class CLIPBOARD_IO : public PCB_IO
+{
+    STRING_FORMATTER m_formatter;
+    CLIPBOARD_PARSER* m_parser;
+
+public:
+    /* Saves the entire board to the clipboard formatted using the PCB_IO formatting */
+    void Save( const wxString& aFileName, BOARD* aBoard,
+                const PROPERTIES* aProperties = NULL ) override;
+    /* Writes all the settings of the BOARD* set by setBoard() and then adds all
+     * the BOARD_ITEM* found in selection formatted by PCB_IO to clipboard as a text
+     */
+    void SaveSelection( SELECTION& selected );
+
+    BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties = NULL ) override;
+    CLIPBOARD_IO();
+    ~CLIPBOARD_IO();
+
+    void setBoard(BOARD* aBoard);
+    void writeHeader(BOARD* aBoard);
+    STRING_FORMATTER* GetFormatter();
+
+
+};
+
+
+#endif /* KICAD_CLIPBOARD_H */
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 252ee30a2..26d4b543d 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -525,10 +525,9 @@ void PCB_IO::formatLayer( const BOARD_ITEM* aItem ) const
         m_out->Print( 0, " (layer %s)", m_out->Quotew( aItem->GetLayerName() ).c_str() );
 }
 
-
-void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
-    throw( IO_ERROR )
+void PCB_IO::formatHeader( BOARD* aBoard, int aNestLevel ) const throw(IO_ERROR)
 {
+
     const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
 
     m_out->Print( 0, "\n" );
@@ -750,6 +749,12 @@ void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
         filterNetClass( *aBoard, netclass );    // Remove empty nets (from a copy of a netclass)
         netclass.Format( m_out, aNestLevel, m_ctl );
     }
+}
+
+void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
+    throw( IO_ERROR )
+{
+    formatHeader( aBoard );
 
     // Save the modules.
     for( MODULE* module = aBoard->m_Modules;  module;  module = module->Next() )
diff --git a/pcbnew/kicad_plugin.h b/pcbnew/kicad_plugin.h
index bd7abff4a..ed281c25a 100644
--- a/pcbnew/kicad_plugin.h
+++ b/pcbnew/kicad_plugin.h
@@ -107,10 +107,10 @@ public:
         return wxT( "kicad_pcb" );
     }
 
-    void Save( const wxString& aFileName, BOARD* aBoard,
+    virtual void Save( const wxString& aFileName, BOARD* aBoard,
                const PROPERTIES* aProperties = NULL ) override;
 
-    BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties = NULL ) override;
+    virtual BOARD* Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties = NULL ) override;
 
     wxArrayString FootprintEnumerate( const wxString& aLibraryPath,
             const PROPERTIES* aProperties = NULL ) override;
@@ -186,6 +186,10 @@ protected:
 
     void init( const PROPERTIES* aProperties );
 
+    /// writes everything that comes before the board_items, like settings and layers etc
+    void formatHeader( BOARD* aBoard, int aNestLevel = 0 ) const
+        throw( IO_ERROR );
+
 private:
     void format( BOARD* aBoard, int aNestLevel = 0 ) const
         throw( IO_ERROR );
diff --git a/pcbnew/pcb_parser.h b/pcbnew/pcb_parser.h
index 068669ddb..5b3e94b2d 100644
--- a/pcbnew/pcb_parser.h
+++ b/pcbnew/pcb_parser.h
@@ -117,14 +117,14 @@ class PCB_PARSER : public PCB_LEXER
     DRAWSEGMENT*    parseDRAWSEGMENT() throw( IO_ERROR, PARSE_ERROR );
     TEXTE_PCB*      parseTEXTE_PCB() throw( IO_ERROR, PARSE_ERROR );
     DIMENSION*      parseDIMENSION() throw( IO_ERROR, PARSE_ERROR );
-
+protected:
     /**
      * Function parseMODULE
      * @param aInitialComments may be a pointer to a heap allocated initial comment block
      *   or NULL.  If not NULL, then caller has given ownership of a wxArrayString to
      *   this function and care must be taken to delete it even on exception.
      */
-    MODULE*         parseMODULE( wxArrayString* aInitialComments = 0 )
+    virtual MODULE*         parseMODULE( wxArrayString* aInitialComments = 0 )
                         throw( IO_ERROR, PARSE_ERROR, FUTURE_FORMAT_ERROR );
 
     /**
@@ -308,7 +308,7 @@ public:
         m_board = aBoard;
     }
 
-    BOARD_ITEM* Parse() throw( IO_ERROR, PARSE_ERROR );
+    virtual BOARD_ITEM* Parse() throw( IO_ERROR, PARSE_ERROR );
 
     /**
      * Return whether a version number, if any was parsed, was too recent
@@ -327,4 +327,5 @@ public:
 };
 
 
+
 #endif    // _PCBNEW_PARSER_H_
diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h
index 59bf54356..721267fcf 100644
--- a/pcbnew/tools/pcb_actions.h
+++ b/pcbnew/tools/pcb_actions.h
@@ -76,6 +76,9 @@ public:
     /// Filters the items in the current selection (invokes dialog)
     static TOOL_ACTION filterSelection;
 
+    /// Copy selected items to clipboard
+    static TOOL_ACTION selectionToClipboard;
+
     // Edit Tool
     /// Activation of the edit tool
     static TOOL_ACTION editActivate;
@@ -344,6 +347,7 @@ public:
     static TOOL_ACTION drillOrigin;
     static TOOL_ACTION crossProbeSchToPcb;
     static TOOL_ACTION appendBoard;
+    static TOOL_ACTION appendClipboard;
     static TOOL_ACTION showHelp;
     static TOOL_ACTION toBeDone;
 
diff --git a/pcbnew/tools/pcb_editor_control.cpp b/pcbnew/tools/pcb_editor_control.cpp
index cdaf05681..2a385ad92 100644
--- a/pcbnew/tools/pcb_editor_control.cpp
+++ b/pcbnew/tools/pcb_editor_control.cpp
@@ -141,6 +141,10 @@ TOOL_ACTION PCB_ACTIONS::appendBoard( "pcbnew.EditorControl.appendBoard",
         AS_GLOBAL, 0,
         "", "" );
 
+TOOL_ACTION PCB_ACTIONS::appendClipboard( "pcbnew.EditorControl.appendClipboard",
+        AS_GLOBAL, MD_CTRL + MD_SHIFT + int( 'V' ),
+        "", "" );
+
 TOOL_ACTION PCB_ACTIONS::highlightNet( "pcbnew.EditorControl.highlightNet",
         AS_GLOBAL, 0,
         "", "" );
diff --git a/pcbnew/tools/pcbnew_control.cpp b/pcbnew/tools/pcbnew_control.cpp
index 60caa56e2..7e9d42507 100644
--- a/pcbnew/tools/pcbnew_control.cpp
+++ b/pcbnew/tools/pcbnew_control.cpp
@@ -40,6 +40,8 @@
 #include <hotkeys.h>
 #include <properties.h>
 #include <io_mgr.h>
+#include <kicad_plugin.h>
+#include <kicad_clipboard.h>
 
 #include <pcbnew_id.h>
 #include <wxPcbStruct.h>
@@ -775,9 +777,14 @@ int PCBNEW_CONTROL::DeleteItemCursor( const TOOL_EVENT& aEvent )
 
     return 0;
 }
+int PCBNEW_CONTROL::AppendBoardFromClipboard( const TOOL_EVENT& aEvent )
+{
+    CLIPBOARD_IO pi;
+    wxString noString("");
+    return AppendBoard( pi, noString );
+}
 
-
-int PCBNEW_CONTROL::AppendBoard( const TOOL_EVENT& aEvent )
+int PCBNEW_CONTROL::AppendBoardFromFile( const TOOL_EVENT& aEvent )
 {
     int open_ctl;
     wxString fileName;
@@ -794,6 +801,13 @@ int PCBNEW_CONTROL::AppendBoard( const TOOL_EVENT& aEvent )
     IO_MGR::PCB_FILE_T pluginType = plugin_type( fileName, open_ctl );
     PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) );
 
+    return AppendBoard( *pi, fileName );
+
+}
+int PCBNEW_CONTROL::AppendBoard( PLUGIN& pi, wxString& fileName )
+{
+
+    PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
     // Mark existing tracks, in order to know what are the new tracks
     // Tracks are inserted, not appended, so mark existing tracks to be
     // able to select the new tracks only later
@@ -826,7 +840,7 @@ int PCBNEW_CONTROL::AppendBoard( const TOOL_EVENT& aEvent )
         props["page_height"] = ybuf;
 
         editFrame->GetDesignSettings().m_NetClasses.Clear();
-        pi->Load( fileName, board, &props );
+        pi.Load( fileName, board, &props );
     }
     catch( const IO_ERROR& ioe )
     {
@@ -993,9 +1007,13 @@ void PCBNEW_CONTROL::SetTransitions()
     Go( &PCBNEW_CONTROL::SwitchCursor,       PCB_ACTIONS::switchCursor.MakeEvent() );
     Go( &PCBNEW_CONTROL::SwitchUnits,        PCB_ACTIONS::switchUnits.MakeEvent() );
     Go( &PCBNEW_CONTROL::DeleteItemCursor,   PCB_ACTIONS::deleteItemCursor.MakeEvent() );
-    Go( &PCBNEW_CONTROL::AppendBoard,        PCB_ACTIONS::appendBoard.MakeEvent() );
     Go( &PCBNEW_CONTROL::ShowHelp,           PCB_ACTIONS::showHelp.MakeEvent() );
     Go( &PCBNEW_CONTROL::ToBeDone,           PCB_ACTIONS::toBeDone.MakeEvent() );
+
+    // Append control
+    Go( &PCBNEW_CONTROL::AppendBoardFromFile,PCB_ACTIONS::appendBoard.MakeEvent() );
+    Go( &PCBNEW_CONTROL::AppendBoardFromClipboard
+            ,PCB_ACTIONS::appendClipboard.MakeEvent() );
 }
 
 
diff --git a/pcbnew/tools/pcbnew_control.h b/pcbnew/tools/pcbnew_control.h
index 946b185c1..20c08738e 100644
--- a/pcbnew/tools/pcbnew_control.h
+++ b/pcbnew/tools/pcbnew_control.h
@@ -26,6 +26,7 @@
 #define PCBNEW_CONTROL_H
 
 #include <tool/tool_interactive.h>
+#include <io_mgr.h>
 #include <memory>
 
 namespace KIGFX {
@@ -79,7 +80,9 @@ public:
     int SwitchCursor( const TOOL_EVENT& aEvent );
     int SwitchUnits( const TOOL_EVENT& aEvent );
     int DeleteItemCursor( const TOOL_EVENT& aEvent );
-    int AppendBoard( const TOOL_EVENT& aEvent );
+    int AppendBoardFromClipboard( const TOOL_EVENT& aEvent );
+    int AppendBoardFromFile( const TOOL_EVENT& aEvent );
+    int AppendBoard( PLUGIN& pi, wxString& fileName );
     int ShowHelp( const TOOL_EVENT& aEvent );
     int ToBeDone( const TOOL_EVENT& aEvent );
 
diff --git a/pcbnew/tools/selection_tool.cpp b/pcbnew/tools/selection_tool.cpp
index 494fb1cf1..d51de2747 100644
--- a/pcbnew/tools/selection_tool.cpp
+++ b/pcbnew/tools/selection_tool.cpp
@@ -57,6 +57,9 @@ using namespace std::placeholders;
 #include "pcb_bright_box.h"
 #include "pcb_actions.h"
 
+#include "kicad_plugin.h"
+#include "kicad_clipboard.h"
+
 // Selection tool actions
 TOOL_ACTION PCB_ACTIONS::selectionActivate( "pcbnew.InteractiveSelection",
         AS_GLOBAL, 0,
@@ -111,6 +114,11 @@ TOOL_ACTION PCB_ACTIONS::filterSelection( "pcbnew.InteractiveSelection.FilterSel
         _( "Filter Selection" ), _( "Filter the types of items in the selection" ),
         nullptr );
 
+TOOL_ACTION PCB_ACTIONS::selectionToClipboard( "pcbnew.InteractiveSelection.CopyToClipboard",
+        AS_GLOBAL, MD_CTRL + MD_SHIFT + int( 'C' ),
+        _( "Copy to Clipboard" ), _( "Copy selected content to clipboard" ),
+        nullptr );
+
 
 
 class SELECT_MENU: public CONTEXT_MENU
@@ -553,11 +561,26 @@ void SELECTION_TOOL::SetTransitions()
     Go( &SELECTION_TOOL::selectCopper, PCB_ACTIONS::selectCopper.MakeEvent() );
     Go( &SELECTION_TOOL::selectNet, PCB_ACTIONS::selectNet.MakeEvent() );
     Go( &SELECTION_TOOL::selectSameSheet, PCB_ACTIONS::selectSameSheet.MakeEvent() );
+
+    Go( &SELECTION_TOOL::selectionToClipboard, PCB_ACTIONS::selectionToClipboard.MakeEvent() );
+
     Go( &SELECTION_TOOL::selectOnSheetFromEeschema, PCB_ACTIONS::selectOnSheetFromEeschema.MakeEvent() );
     Go( &SELECTION_TOOL::updateSelection, PCB_ACTIONS::selectionModified.MakeEvent() );
 }
 
 
+int SELECTION_TOOL::selectionToClipboard( const TOOL_EVENT& aEvent )
+{
+    CLIPBOARD_IO io;
+    BOARD*  board = getModel<BOARD>();
+
+    io.setBoard( board );
+    auto& selection = RequestSelection( SELECTION_DELETABLE | SELECTION_SANITIZE_PADS );
+    io.SaveSelection( selection );
+
+    return 0;
+}
+
 SELECTION_LOCK_FLAGS SELECTION_TOOL::CheckLock()
 {
     if( !m_locked || m_editModules )
diff --git a/pcbnew/tools/selection_tool.h b/pcbnew/tools/selection_tool.h
index a285370ab..a6ce89608 100644
--- a/pcbnew/tools/selection_tool.h
+++ b/pcbnew/tools/selection_tool.h
@@ -294,6 +294,14 @@ private:
     void unselectVisually( BOARD_ITEM* aItem );
 
     /**
+     * Function selectionToClipboard()
+     * Sends the current selection to the clipboard by formatting it as a fake pcb
+     * see AppendBoardFromClipboard for importing
+     * @return True if it was sent succesfully
+     */
+    int selectionToClipboard( const TOOL_EVENT& aEvent );
+
+    /**
      * Function selectionContains()
      * Checks if the given point is placed within any of selected items' bounding box.
      *
-- 
2.12.2


Follow ups