From 1333c4c305400cd63d4d789d9317c4d9f77feb55 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sun, 16 Mar 2025 17:26:45 +0000
Subject: [PATCH] Move layer arg parsing up so we can handle user-defined layer
 names.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18773
---
 common/jobs/job_export_pcb_plot.h        |   4 +
 kicad/cli/command_fp_export_svg.cpp      |  17 +---
 kicad/cli/command_pcb_export_base.cpp    | 115 +++--------------------
 kicad/cli/command_pcb_export_base.h      |  20 +---
 kicad/cli/command_pcb_export_dxf.cpp     |  27 +-----
 kicad/cli/command_pcb_export_gencad.cpp  |  16 +---
 kicad/cli/command_pcb_export_gerber.cpp  |  29 ++----
 kicad/cli/command_pcb_export_gerbers.cpp |  30 +-----
 kicad/cli/command_pcb_export_ipc2581.cpp |   9 +-
 kicad/cli/command_pcb_export_ipcd356.cpp |  12 +--
 kicad/cli/command_pcb_export_odb.cpp     |  11 +--
 kicad/cli/command_pcb_export_pdf.cpp     |  27 +-----
 kicad/cli/command_pcb_export_pos.cpp     |  11 +--
 kicad/cli/command_pcb_export_svg.cpp     |  27 +-----
 pcbnew/pcbnew_jobs_handler.cpp           | 112 ++++++++++++++++++++++
 pcbnew/pcbnew_jobs_handler.h             |   2 +
 16 files changed, 166 insertions(+), 303 deletions(-)

diff --git a/common/jobs/job_export_pcb_plot.h b/common/jobs/job_export_pcb_plot.h
index 1a4c22ee54..2a25b641fa 100644
--- a/common/jobs/job_export_pcb_plot.h
+++ b/common/jobs/job_export_pcb_plot.h
@@ -42,6 +42,10 @@ public:
 
     JOB_EXPORT_PCB_PLOT( PLOT_FORMAT aFormat, const std::string& aType, bool aOutputIsDirectory );
 
+public:
+    wxString m_argLayers;
+    wxString m_argCommonLayers;
+
     PLOT_FORMAT m_plotFormat;
 
     wxString m_filename;
diff --git a/kicad/cli/command_fp_export_svg.cpp b/kicad/cli/command_fp_export_svg.cpp
index b2b12a996c..6bf565550d 100644
--- a/kicad/cli/command_fp_export_svg.cpp
+++ b/kicad/cli/command_fp_export_svg.cpp
@@ -22,13 +22,11 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_fp_export_svg.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <wx/crt.h>
 #include <wx/dir.h>
 
 #include <macros.h>
-#include <wx/tokenzr.h>
 
 #define ARG_FOOTPRINT "--footprint"
 
@@ -38,7 +36,7 @@ CLI::FP_EXPORT_SVG_COMMAND::FP_EXPORT_SVG_COMMAND() :
     m_argParser.add_description( UTF8STDSTR( _( "Exports the footprint or entire footprint "
                                                 "library to SVG" ) ) );
 
-    addLayerArg( false );
+    addLayerArg();
     addDefineArg();
 
     m_argParser.add_argument( "-t", ARG_THEME )
@@ -75,10 +73,6 @@ CLI::FP_EXPORT_SVG_COMMAND::FP_EXPORT_SVG_COMMAND() :
 
 int CLI::FP_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_FP_EXPORT_SVG> svgJob = std::make_unique<JOB_FP_EXPORT_SVG>();
 
     svgJob->m_libraryPath = m_argInput;
@@ -99,12 +93,7 @@ int CLI::FP_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
 
     svgJob->m_colorTheme = From_UTF8( m_argParser.get<std::string>( ARG_THEME ).c_str() );
 
-    if( !m_selectedLayers.empty() )
-        svgJob->m_plotLayerSequence = m_selectedLayers;
-    else
-        svgJob->m_plotLayerSequence = LSET::AllLayersMask().SeqStackupForPlotting();
+    svgJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
 
-    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, svgJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, svgJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_base.cpp b/kicad/cli/command_pcb_export_base.cpp
index 45ebdd741e..3667d4950d 100644
--- a/kicad/cli/command_pcb_export_base.cpp
+++ b/kicad/cli/command_pcb_export_base.cpp
@@ -19,126 +19,33 @@
  */
 
 #include "command_pcb_export_base.h"
-#include <cli/exit_codes.h>
-#include <kiface_base.h>
 #include <bitset>
-#include <layer_ids.h>
-#include <lset.h>
-#include <lseq.h>
 #include <string_utils.h>
 
-#include <macros.h>
-#include <wx/tokenzr.h>
-#include <wx/crt.h>
 
 CLI::PCB_EXPORT_BASE_COMMAND::PCB_EXPORT_BASE_COMMAND( const std::string& aName,
-                                                       bool               aInputCanBeDir,
-                                                       bool               aOutputIsDir ) :
+                                                       bool aInputCanBeDir, bool aOutputIsDir ) :
         COMMAND( aName )
 {
-    m_selectedLayersSet = false;
-    m_requireLayers = false;
-    m_hasLayerArg = false;
-
     addCommonArgs( true, true, aInputCanBeDir, aOutputIsDir );
-
-    // Build list of layer names and their layer mask:
-    for( int layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer )
-    {
-        std::string untranslated = TO_UTF8( wxString( LSET::Name( PCB_LAYER_ID( layer ) ) ) );
-
-        //m_layerIndices[untranslated] = PCB_LAYER_ID( layer );
-
-        // Add layer name used in pcb files
-        m_layerMasks[untranslated] = LSET( { PCB_LAYER_ID( layer ) } );
-        // Add layer name using GUI canonical layer name
-        m_layerGuiMasks[ TO_UTF8(LayerName( layer ) ) ] = LSET( { PCB_LAYER_ID( layer ) } );
-    }
-
-    // Add list of grouped layer names used in pcb files
-    m_layerMasks["*"] = LSET::AllLayersMask();
-    m_layerMasks["*.Cu"] = LSET::AllCuMask();
-    m_layerMasks["*In.Cu"] = LSET::InternalCuMask();
-    m_layerMasks["F&B.Cu"] = LSET( { F_Cu, B_Cu } );
-    m_layerMasks["*.Adhes"] = LSET( { B_Adhes, F_Adhes } );
-    m_layerMasks["*.Paste"] = LSET( { B_Paste, F_Paste } );
-    m_layerMasks["*.Mask"] = LSET( { B_Mask, F_Mask } );
-    m_layerMasks["*.SilkS"] = LSET( { B_SilkS, F_SilkS } );
-    m_layerMasks["*.Fab"] = LSET( { B_Fab, F_Fab } );
-    m_layerMasks["*.CrtYd"] = LSET( { B_CrtYd, F_CrtYd } );
-
-    // Add list of grouped layer names using GUI canonical layer names
-    m_layerGuiMasks["*.Adhesive"] = LSET( { B_Adhes, F_Adhes } );
-    m_layerGuiMasks["*.Silkscreen"] = LSET( { B_SilkS, F_SilkS } );
-    m_layerGuiMasks["*.Courtyard"] = LSET( { B_CrtYd, F_CrtYd } );
 }
 
 
-LSEQ CLI::PCB_EXPORT_BASE_COMMAND::convertLayerStringList( wxString& aLayerString ) const
-{
-    LSEQ layerMask;
-
-    if( !aLayerString.IsEmpty() )
-    {
-        wxStringTokenizer layerTokens( aLayerString, "," );
-
-        while( layerTokens.HasMoreTokens() )
-        {
-            std::string token = TO_UTF8( layerTokens.GetNextToken() );
-
-            // Search for a layer name in canonical layer name used in .kicad_pcb files:
-            if( m_layerMasks.count( token ) )
-            {
-                for( PCB_LAYER_ID layer : m_layerMasks.at( token ).Seq() )
-                    layerMask.push_back( layer );
-            }
-            // Search for a layer name in canonical layer name used in GUI (not translated):
-            else if( m_layerGuiMasks.count( token ) )
-            {
-                for( PCB_LAYER_ID layer : m_layerGuiMasks.at( token ).Seq() )
-                    layerMask.push_back( layer );
-            }
-            else
-            {
-                wxFprintf( stderr, _( "Invalid layer name \"%s\"\n" ), token );
-            }
-        }
-    }
-
-    return layerMask;
-}
-
-
-void CLI::PCB_EXPORT_BASE_COMMAND::addLayerArg( bool aRequire )
+void CLI::PCB_EXPORT_BASE_COMMAND::addLayerArg()
 {
     m_argParser.add_argument( "-l", ARG_LAYERS )
             .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Comma separated list of untranslated layer names to include such as "
-                       "F.Cu,B.Cu" ) ) )
+            .help( UTF8STDSTR( _( "Comma separated list of untranslated layer names to include "
+                                  "such as F.Cu,B.Cu" ) ) )
             .metavar( "LAYER_LIST" );
-
-    m_hasLayerArg = true;
-    m_requireLayers = aRequire;
 }
 
 
-int CLI::PCB_EXPORT_BASE_COMMAND::doPerform( KIWAY& aKiway )
+void CLI::PCB_EXPORT_BASE_COMMAND::addCommonLayersArg()
 {
-    if( m_hasLayerArg )
-    {
-        wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
-
-        LSEQ layerMask = convertLayerStringList( layers );
-
-        if( m_requireLayers && layerMask.size() < 1 )
-        {
-            wxFprintf( stderr, _( "At least one layer must be specified\n" ) );
-            return EXIT_CODES::ERR_ARGS;
-        }
-
-        m_selectedLayers = layerMask;
-    }
-
-    return EXIT_CODES::OK;
-}
+    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
+            .default_value( std::string() )
+            .help( UTF8STDSTR( _( "Layers to include on each plot, comma separated list of "
+                                  "untranslated layer names to include such as F.Cu,B.Cu" ) ) )
+            .metavar( "COMMON_LAYER_LIST" );
+}
\ No newline at end of file
diff --git a/kicad/cli/command_pcb_export_base.h b/kicad/cli/command_pcb_export_base.h
index 6e48d135dc..5f9d58583d 100644
--- a/kicad/cli/command_pcb_export_base.h
+++ b/kicad/cli/command_pcb_export_base.h
@@ -22,9 +22,6 @@
 #define COMMAND_EXPORT_PCB_BASE_H
 
 #include "command.h"
-#include <layer_ids.h>
-#include <lset.h>
-#include <lseq.h>
 
 namespace CLI
 {
@@ -74,21 +71,8 @@ struct PCB_EXPORT_BASE_COMMAND : public COMMAND
                              bool aOutputIsDir = false );
 
 protected:
-    int  doPerform( KIWAY& aKiway ) override;
-    LSEQ convertLayerStringList( wxString& aLayerString ) const;
-    void addLayerArg( bool aRequire );
-
-    // The list of canonical layer names used in .kicad_pcb files:
-    std::map<std::string, LSET> m_layerMasks;
-
-    // The list of canonical layer names used in GUI (not translated):
-    std::map<std::string, LSET> m_layerGuiMasks;
-
-    LSEQ                        m_selectedLayers;
-    bool                        m_selectedLayersSet;
-
-    bool                        m_hasLayerArg;
-    bool                        m_requireLayers;
+    void addLayerArg();
+    void addCommonLayersArg();
 };
 } // namespace CLI
 
diff --git a/kicad/cli/command_pcb_export_dxf.cpp b/kicad/cli/command_pcb_export_dxf.cpp
index 2a948dcd47..195b1896d1 100644
--- a/kicad/cli/command_pcb_export_dxf.cpp
+++ b/kicad/cli/command_pcb_export_dxf.cpp
@@ -22,13 +22,10 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_dxf.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <wx/crt.h>
 
 #include <macros.h>
-#include <wx/tokenzr.h>
-
 #include <locale_io.h>
 
 #define ARG_USE_CONTOURS "--use-contours"
@@ -42,7 +39,8 @@ CLI::PCB_EXPORT_DXF_COMMAND::PCB_EXPORT_DXF_COMMAND() :
 {
     m_argParser.add_description( UTF8STDSTR( _( "Generate a DXF from a list of layers" ) ) );
 
-    addLayerArg( true );
+    addLayerArg();
+    addCommonLayersArg();
     addDrawingSheetArg();
     addDefineArg();
 
@@ -96,13 +94,6 @@ CLI::PCB_EXPORT_DXF_COMMAND::PCB_EXPORT_DXF_COMMAND() :
             .scan<'i', int>()
             .default_value( 2 );
 
-    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
-            .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Layers to include on each plot, comma separated list of untranslated "
-                       "layer names to include such as F.Cu,B.Cu" ) ) )
-            .metavar( "COMMON_LAYER_LIST" );
-
     m_argParser.add_argument( ARG_MODE_SINGLE )
             .help( UTF8STDSTR(
                     _( "Generates a single file with the output arg path acting as the complete "
@@ -124,10 +115,6 @@ CLI::PCB_EXPORT_DXF_COMMAND::PCB_EXPORT_DXF_COMMAND() :
 
 int CLI::PCB_EXPORT_DXF_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_EXPORT_PCB_DXF> dxfJob( new JOB_EXPORT_PCB_DXF() );
 
     dxfJob->m_filename = m_argInput;
@@ -175,10 +162,8 @@ int CLI::PCB_EXPORT_DXF_COMMAND::doPerform( KIWAY& aKiway )
         return EXIT_CODES::ERR_ARGS;
     }
 
-    dxfJob->m_plotLayerSequence = m_selectedLayers;
-
-    wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
-    dxfJob->m_plotOnAllLayersSequence = convertLayerStringList( layers );
+    dxfJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+    dxfJob->m_argCommonLayers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
 
     if( m_argParser.get<bool>( ARG_MODE_MULTI ) )
         dxfJob->m_genMode = JOB_EXPORT_PCB_DXF::GEN_MODE::MULTI;
@@ -196,7 +181,5 @@ int CLI::PCB_EXPORT_DXF_COMMAND::doPerform( KIWAY& aKiway )
 
 
     LOCALE_IO dummy;    // Switch to "C" locale
-    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, dxfJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, dxfJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_gencad.cpp b/kicad/cli/command_pcb_export_gencad.cpp
index 2b64535356..1ec08d10a9 100644
--- a/kicad/cli/command_pcb_export_gencad.cpp
+++ b/kicad/cli/command_pcb_export_gencad.cpp
@@ -22,21 +22,16 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_gencad.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <wx/crt.h>
 
-#include <macros.h>
-#include <wx/tokenzr.h>
-
 #include <locale_io.h>
 
 
 CLI::PCB_EXPORT_GENCAD_COMMAND::PCB_EXPORT_GENCAD_COMMAND() :
         PCB_EXPORT_BASE_COMMAND( "gencad", false, true )
 {
-    // TODO: Update string to remove reference to layers
-    m_argParser.add_description( UTF8STDSTR( _( "Generate Gencad from a list of layers" ) ) );
+    m_argParser.add_description( UTF8STDSTR( _( "Export the PCB in Gencad format" ) ) );
 
     addDefineArg();
 
@@ -70,11 +65,6 @@ CLI::PCB_EXPORT_GENCAD_COMMAND::PCB_EXPORT_GENCAD_COMMAND() :
 
 int CLI::PCB_EXPORT_GENCAD_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_EXPORT_PCB_GENCAD> gencadJob( new JOB_EXPORT_PCB_GENCAD() );
 
     gencadJob->m_filename = m_argInput;
@@ -94,7 +84,5 @@ int CLI::PCB_EXPORT_GENCAD_COMMAND::doPerform( KIWAY& aKiway )
     }
 
     LOCALE_IO dummy; // Switch to "C" locale
-    int       exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, gencadJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, gencadJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_gerber.cpp b/kicad/cli/command_pcb_export_gerber.cpp
index c76fc5b9a5..b7c8a51e00 100644
--- a/kicad/cli/command_pcb_export_gerber.cpp
+++ b/kicad/cli/command_pcb_export_gerber.cpp
@@ -22,12 +22,9 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_gerber.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <wx/crt.h>
 
 #include <macros.h>
-#include <wx/tokenzr.h>
-
 #include <locale_io.h>
 #include <string_utils.h>
 
@@ -35,7 +32,8 @@
 CLI::PCB_EXPORT_GERBER_COMMAND::PCB_EXPORT_GERBER_COMMAND( const std::string& aName ) :
         PCB_EXPORT_BASE_COMMAND( aName )
 {
-    addLayerArg( true );
+    addLayerArg();
+    addCommonLayersArg();
     addDrawingSheetArg();
     addDefineArg();
 
@@ -90,13 +88,6 @@ CLI::PCB_EXPORT_GERBER_COMMAND::PCB_EXPORT_GERBER_COMMAND( const std::string& aN
             .help( UTF8STDSTR( _( "Use drill/place file origin" ) ) )
             .flag();
 
-    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
-            .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Layers to include on each plot, comma separated list of untranslated "
-                       "layer names to include such as F.Cu,B.Cu" ) ) )
-            .metavar( "COMMON_LAYER_LIST" );
-
     m_argParser.add_argument( ARG_PRECISION )
             .help( UTF8STDSTR( _( "Precision of Gerber coordinates, valid options: 5 or 6" ) ) )
             .scan<'i', int>()
@@ -140,10 +131,9 @@ int CLI::PCB_EXPORT_GERBER_COMMAND::populateJob( JOB_EXPORT_PCB_GERBER* aJob )
     aJob->m_useDrillOrigin = m_argParser.get<bool>( ARG_USE_DRILL_FILE_ORIGIN );
     aJob->m_useProtelFileExtension = !m_argParser.get<bool>( ARG_NO_PROTEL_EXTENSION );
     aJob->m_precision = m_argParser.get<int>( ARG_PRECISION );
-    aJob->m_plotLayerSequence = m_selectedLayers;
 
-    wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
-    aJob->m_plotOnAllLayersSequence = convertLayerStringList( layers );
+    aJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+    aJob->m_argCommonLayers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
 
     if( m_argParser.get<bool>( DEPRECATED_ARG_PLOT_INVISIBLE_TEXT ) )
         wxFprintf( stdout, DEPRECATED_ARD_PLOT_INVISIBLE_TEXT_WARNING );
@@ -169,20 +159,13 @@ int CLI::PCB_EXPORT_GERBER_COMMAND::doPerform( KIWAY& aKiway )
     wxFprintf( stdout, wxT( "\033[33;1m%s\033[0m\n" ),
                _( "This command is deprecated as of KiCad 9.0, please use \"gerbers\" instead\n" ) );
 
-    int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( exitCode != EXIT_CODES::OK )
-        return exitCode;
-
     std::unique_ptr<JOB_EXPORT_PCB_GERBER> gerberJob( new JOB_EXPORT_PCB_GERBER() );
 
-    exitCode = populateJob( gerberJob.get() );
+    int exitCode = populateJob( gerberJob.get() );
 
     if( exitCode != EXIT_CODES::OK )
         return exitCode;
 
     LOCALE_IO dummy;
-    exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, gerberJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, gerberJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_gerbers.cpp b/kicad/cli/command_pcb_export_gerbers.cpp
index 694f87d4d8..9e669a4cf5 100644
--- a/kicad/cli/command_pcb_export_gerbers.cpp
+++ b/kicad/cli/command_pcb_export_gerbers.cpp
@@ -22,12 +22,7 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_gerbers.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
-#include <wx/crt.h>
-
-#include <macros.h>
-#include <wx/tokenzr.h>
 
 #include <locale_io.h>
 
@@ -37,19 +32,11 @@
 CLI::PCB_EXPORT_GERBERS_COMMAND::PCB_EXPORT_GERBERS_COMMAND() :
         PCB_EXPORT_GERBER_COMMAND( "gerbers" )
 {
-    m_requireLayers = false;
+    addCommonLayersArg();
 
     m_argParser.add_description( UTF8STDSTR( _( "Plot multiple Gerbers for a PCB, including the "
                                                 "ability to use stored board plot settings" ) ) );
 
-    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
-            .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Layers to include on each plot, comma separated list of untranslated "
-                       "layer names to include such as "
-                                  "F.Cu,B.Cu" ) ) )
-            .metavar( "COMMON_LAYER_LIST" );
-
     m_argParser.add_argument( ARG_USE_BOARD_PLOT_PARAMS )
             .help( UTF8STDSTR( _( "Use the Gerber plot settings already configured in the "
                                   "board file" ) ) )
@@ -59,24 +46,17 @@ CLI::PCB_EXPORT_GERBERS_COMMAND::PCB_EXPORT_GERBERS_COMMAND() :
 
 int CLI::PCB_EXPORT_GERBERS_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( exitCode != EXIT_CODES::OK )
-        return exitCode;
-
     std::unique_ptr<JOB_EXPORT_PCB_GERBERS> gerberJob( new JOB_EXPORT_PCB_GERBERS() );
 
-    exitCode = populateJob( gerberJob.get() );
+    int exitCode = populateJob( gerberJob.get() );
 
     if( exitCode != EXIT_CODES::OK )
         return exitCode;
 
-    wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
-    gerberJob->m_plotOnAllLayersSequence = convertLayerStringList( layers );
+    gerberJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+    gerberJob->m_argCommonLayers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
     gerberJob->m_useBoardPlotParams = m_argParser.get<bool>( ARG_USE_BOARD_PLOT_PARAMS );
 
     LOCALE_IO dummy;
-    exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, gerberJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, gerberJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_ipc2581.cpp b/kicad/cli/command_pcb_export_ipc2581.cpp
index 549cf7756b..35851367df 100644
--- a/kicad/cli/command_pcb_export_ipc2581.cpp
+++ b/kicad/cli/command_pcb_export_ipc2581.cpp
@@ -102,11 +102,6 @@ CLI::PCB_EXPORT_IPC2581_COMMAND::PCB_EXPORT_IPC2581_COMMAND() :
 
 int CLI::PCB_EXPORT_IPC2581_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( exitCode != EXIT_CODES::OK )
-        return exitCode;
-
     std::unique_ptr<JOB_EXPORT_PCB_IPC2581> ipc2581Job( new JOB_EXPORT_PCB_IPC2581() );
 
     ipc2581Job->m_filename = m_argInput;
@@ -146,7 +141,5 @@ int CLI::PCB_EXPORT_IPC2581_COMMAND::doPerform( KIWAY& aKiway )
             From_UTF8( m_argParser.get<std::string>( ARG_BOM_COL_DIST ).c_str() );
 
     LOCALE_IO dummy;
-    exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, ipc2581Job.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, ipc2581Job.get() );
 }
diff --git a/kicad/cli/command_pcb_export_ipcd356.cpp b/kicad/cli/command_pcb_export_ipcd356.cpp
index 02046d83be..e1d8d06827 100644
--- a/kicad/cli/command_pcb_export_ipcd356.cpp
+++ b/kicad/cli/command_pcb_export_ipcd356.cpp
@@ -25,9 +25,6 @@
 #include <string_utils.h>
 #include <wx/crt.h>
 
-#include <macros.h>
-#include <wx/tokenzr.h>
-
 #include <locale_io.h>
 
 CLI::PCB_EXPORT_IPCD356_COMMAND::PCB_EXPORT_IPCD356_COMMAND() :
@@ -39,11 +36,6 @@ CLI::PCB_EXPORT_IPCD356_COMMAND::PCB_EXPORT_IPCD356_COMMAND() :
 
 int CLI::PCB_EXPORT_IPCD356_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( exitCode != EXIT_CODES::OK )
-        return exitCode;
-
     std::unique_ptr<JOB_EXPORT_PCB_IPCD356> ipcd356Job( new JOB_EXPORT_PCB_IPCD356() );
 
     ipcd356Job->m_filename = m_argInput;
@@ -55,7 +47,5 @@ int CLI::PCB_EXPORT_IPCD356_COMMAND::doPerform( KIWAY& aKiway )
         return EXIT_CODES::ERR_INVALID_INPUT_FILE;
     }
 
-    exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, ipcd356Job.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, ipcd356Job.get() );
 }
diff --git a/kicad/cli/command_pcb_export_odb.cpp b/kicad/cli/command_pcb_export_odb.cpp
index ae55f309e8..9f6772743d 100644
--- a/kicad/cli/command_pcb_export_odb.cpp
+++ b/kicad/cli/command_pcb_export_odb.cpp
@@ -25,8 +25,6 @@
 #include <wx/crt.h>
 
 #include <macros.h>
-#include <wx/tokenzr.h>
-
 #include <locale_io.h>
 
 #define ARG_COMPRESS "--compression"
@@ -60,11 +58,6 @@ CLI::PCB_EXPORT_ODB_COMMAND::PCB_EXPORT_ODB_COMMAND() :
 
 int CLI::PCB_EXPORT_ODB_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int exitCode = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( exitCode != EXIT_CODES::OK )
-        return exitCode;
-
     std::unique_ptr<JOB_EXPORT_PCB_ODB> job( new JOB_EXPORT_PCB_ODB() );
 
     job->m_filename = m_argInput;
@@ -97,7 +90,5 @@ int CLI::PCB_EXPORT_ODB_COMMAND::doPerform( KIWAY& aKiway )
         job->m_compressionMode = JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE;
 
     LOCALE_IO dummy;
-    exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, job.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, job.get() );
 }
diff --git a/kicad/cli/command_pcb_export_pdf.cpp b/kicad/cli/command_pcb_export_pdf.cpp
index fac73b0d5c..8a3320c083 100644
--- a/kicad/cli/command_pcb_export_pdf.cpp
+++ b/kicad/cli/command_pcb_export_pdf.cpp
@@ -22,7 +22,6 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_pdf.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <wx/crt.h>
 
@@ -37,7 +36,8 @@ CLI::PCB_EXPORT_PDF_COMMAND::PCB_EXPORT_PDF_COMMAND() :
 {
     m_argParser.add_description( UTF8STDSTR( _( "Generate PDF from a list of layers" ) ) );
 
-    addLayerArg( true );
+    addLayerArg();
+    addCommonLayersArg();
     addDrawingSheetArg();
     addDefineArg();
 
@@ -95,14 +95,6 @@ CLI::PCB_EXPORT_PDF_COMMAND::PCB_EXPORT_PDF_COMMAND() :
             .scan<'i', int>()
             .default_value( 2 );
 
-    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
-            .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Layers to include on each plot, comma separated list of untranslated "
-                       "layer names to include such as "
-                       "F.Cu,B.Cu" ) ) )
-            .metavar( "COMMON_LAYER_LIST" );
-
     m_argParser.add_argument( DEPRECATED_ARG_PLOT_INVISIBLE_TEXT )
             .help( UTF8STDSTR( _( DEPRECATED_ARG_PLOT_INVISIBLE_TEXT_DESC ) ) )
             .flag();
@@ -126,11 +118,6 @@ CLI::PCB_EXPORT_PDF_COMMAND::PCB_EXPORT_PDF_COMMAND() :
 
 int CLI::PCB_EXPORT_PDF_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_EXPORT_PCB_PDF> pdfJob( new JOB_EXPORT_PCB_PDF() );
 
     pdfJob->m_filename = m_argInput;
@@ -166,8 +153,6 @@ int CLI::PCB_EXPORT_PDF_COMMAND::doPerform( KIWAY& aKiway )
     int drillShape = m_argParser.get<int>( ARG_DRILL_SHAPE_OPTION );
     pdfJob->m_drillShapeOption = static_cast<JOB_EXPORT_PCB_PDF::DRILL_MARKS>( drillShape );
 
-    pdfJob->m_plotLayerSequence = m_selectedLayers;
-
     bool argModeMulti = m_argParser.get<bool>( ARG_MODE_MULTIPAGE );
     bool argModeSeparate = m_argParser.get<bool>( ARG_MODE_SEPARATE );
     bool argModeSingle = m_argParser.get<bool>( ARG_MODE_SINGLE );
@@ -178,8 +163,8 @@ int CLI::PCB_EXPORT_PDF_COMMAND::doPerform( KIWAY& aKiway )
         return EXIT_CODES::ERR_ARGS;
     }
 
-    wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
-    pdfJob->m_plotOnAllLayersSequence = convertLayerStringList( layers );
+    pdfJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+    pdfJob->m_argCommonLayers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
 
     if( argModeMulti )
         pdfJob->m_pdfGenMode = JOB_EXPORT_PCB_PDF::GEN_MODE::ONE_PAGE_PER_LAYER_ONE_FILE;
@@ -189,7 +174,5 @@ int CLI::PCB_EXPORT_PDF_COMMAND::doPerform( KIWAY& aKiway )
         pdfJob->m_pdfGenMode = JOB_EXPORT_PCB_PDF::GEN_MODE::ALL_LAYERS_ONE_FILE;
 
     LOCALE_IO dummy;    // Switch to "C" locale
-    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, pdfJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, pdfJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_pos.cpp b/kicad/cli/command_pcb_export_pos.cpp
index d2f4b8a87d..28c59bc031 100644
--- a/kicad/cli/command_pcb_export_pos.cpp
+++ b/kicad/cli/command_pcb_export_pos.cpp
@@ -22,7 +22,6 @@
 #include <cli/exit_codes.h>
 #include "jobs/job_export_pcb_pos.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <wx/crt.h>
 
@@ -92,11 +91,6 @@ CLI::PCB_EXPORT_POS_COMMAND::PCB_EXPORT_POS_COMMAND() :
 
 int CLI::PCB_EXPORT_POS_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_EXPORT_PCB_POS> aPosJob( new JOB_EXPORT_PCB_POS() );
 
     aPosJob->m_filename = m_argInput;
@@ -179,9 +173,6 @@ int CLI::PCB_EXPORT_POS_COMMAND::doPerform( KIWAY& aKiway )
         return EXIT_CODES::ERR_ARGS;
     }
 
-
     LOCALE_IO dummy;
-    int       exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, aPosJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, aPosJob.get() );
 }
diff --git a/kicad/cli/command_pcb_export_svg.cpp b/kicad/cli/command_pcb_export_svg.cpp
index b59f8963b2..6bb196e5ef 100644
--- a/kicad/cli/command_pcb_export_svg.cpp
+++ b/kicad/cli/command_pcb_export_svg.cpp
@@ -24,7 +24,6 @@
 #include "command_pcb_export_base.h"
 #include "jobs/job_export_pcb_svg.h"
 #include <kiface_base.h>
-#include <layer_ids.h>
 #include <string_utils.h>
 #include <regex>
 #include <wx/crt.h>
@@ -41,7 +40,8 @@ CLI::PCB_EXPORT_SVG_COMMAND::PCB_EXPORT_SVG_COMMAND() :
 {
     m_argParser.add_description( UTF8STDSTR( _( "Generate SVG outputs of a given layer list" ) ) );
 
-    addLayerArg( true );
+    addLayerArg();
+    addCommonLayersArg();
     addDrawingSheetArg();
     addDefineArg();
 
@@ -103,15 +103,6 @@ CLI::PCB_EXPORT_SVG_COMMAND::PCB_EXPORT_SVG_COMMAND() :
             .default_value( 2 )
             .metavar( "SHAPE_OPTION" );
 
-    m_argParser.add_argument( "--cl", ARG_COMMON_LAYERS )
-            .default_value( std::string() )
-            .help( UTF8STDSTR(
-                    _( "Layers to include on each plot, comma separated list of untranslated "
-                       "layer names to include such as "
-                       "F.Cu,B.Cu" ) ) )
-            .metavar( "COMMON_LAYER_LIST" );
-
-
     m_argParser.add_argument( ARG_MODE_SINGLE )
             .help( UTF8STDSTR(
                     _( "Generates a single file with the output arg path acting as the complete "
@@ -133,10 +124,6 @@ CLI::PCB_EXPORT_SVG_COMMAND::PCB_EXPORT_SVG_COMMAND() :
 
 int CLI::PCB_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
 {
-    int baseExit = PCB_EXPORT_BASE_COMMAND::doPerform( aKiway );
-    if( baseExit != EXIT_CODES::OK )
-        return baseExit;
-
     std::unique_ptr<JOB_EXPORT_PCB_SVG> svgJob( new JOB_EXPORT_PCB_SVG() );
 
     svgJob->m_mirror = m_argParser.get<bool>( ARG_MIRROR );
@@ -179,8 +166,8 @@ int CLI::PCB_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
     svgJob->m_plotDrawingSheet = !m_argParser.get<bool>( ARG_EXCLUDE_DRAWING_SHEET );
     svgJob->SetVarOverrides( m_argDefineVars );
 
-    wxString layers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
-    svgJob->m_plotOnAllLayersSequence = convertLayerStringList( layers );
+    svgJob->m_argLayers = From_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+    svgJob->m_argCommonLayers = From_UTF8( m_argParser.get<std::string>( ARG_COMMON_LAYERS ).c_str() );
 
     if( !wxFile::Exists( svgJob->m_filename ) )
     {
@@ -188,8 +175,6 @@ int CLI::PCB_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
         return EXIT_CODES::ERR_INVALID_INPUT_FILE;
     }
 
-    svgJob->m_plotLayerSequence = m_selectedLayers;
-
     if( m_argParser.get<bool>( ARG_MODE_MULTI ) )
         svgJob->m_genMode = JOB_EXPORT_PCB_SVG::GEN_MODE::MULTI;
     else if( m_argParser.get<bool>( ARG_MODE_SINGLE ) )
@@ -205,7 +190,5 @@ int CLI::PCB_EXPORT_SVG_COMMAND::doPerform( KIWAY& aKiway )
                    _( "The new behavior will match --mode-multi" ) );
     }
 
-    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, svgJob.get() );
-
-    return exitCode;
+    return aKiway.ProcessJob( KIWAY::FACE_PCB, svgJob.get() );
 }
diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp
index 10eebe69fa..8dc4ac927b 100644
--- a/pcbnew/pcbnew_jobs_handler.cpp
+++ b/pcbnew/pcbnew_jobs_handler.cpp
@@ -330,6 +330,75 @@ BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
 }
 
 
+LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
+{
+    std::map<wxString, LSET> layerMasks;
+    std::map<wxString, LSET> layerGuiMasks;
+
+    // Build list of layer names and their layer mask:
+    for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
+    {
+        // Add layer name used in pcb files
+        layerMasks[ LSET::Name( layer ) ] = LSET( { layer } );
+        // Add layer name using GUI canonical layer name
+        layerGuiMasks[ LayerName( layer ) ] = LSET( { layer } );
+
+        // Add user layer name
+        if( aBoard )
+            layerGuiMasks[ aBoard->GetLayerName( layer ) ] = LSET( { layer } );
+    }
+
+    // Add list of grouped layer names used in pcb files
+    layerMasks[ wxT( "*" ) ]       = LSET::AllLayersMask();
+    layerMasks[ wxT( "*.Cu" ) ]    = LSET::AllCuMask();
+    layerMasks[ wxT( "*In.Cu" ) ]  = LSET::InternalCuMask();
+    layerMasks[ wxT( "F&B.Cu" ) ]  = LSET( { F_Cu, B_Cu } );
+    layerMasks[ wxT( "*.Adhes" ) ] = LSET( { B_Adhes, F_Adhes } );
+    layerMasks[ wxT( "*.Paste" ) ] = LSET( { B_Paste, F_Paste } );
+    layerMasks[ wxT( "*.Mask" ) ]  = LSET( { B_Mask, F_Mask } );
+    layerMasks[ wxT( "*.SilkS" ) ] = LSET( { B_SilkS, F_SilkS } );
+    layerMasks[ wxT( "*.Fab" ) ]   = LSET( { B_Fab, F_Fab } );
+    layerMasks[ wxT( "*.CrtYd" ) ] = LSET( { B_CrtYd, F_CrtYd } );
+
+    // Add list of grouped layer names using GUI canonical layer names
+    layerGuiMasks[ wxT( "*.Adhesive" ) ]   = LSET( { B_Adhes, F_Adhes } );
+    layerGuiMasks[ wxT( "*.Silkscreen" ) ] = LSET( { B_SilkS, F_SilkS } );
+    layerGuiMasks[ wxT( "*.Courtyard" ) ]  = LSET( { B_CrtYd, F_CrtYd } );
+
+    LSEQ layerMask;
+
+    if( !aLayerString.IsEmpty() )
+    {
+        wxStringTokenizer layerTokens( aLayerString, "," );
+
+        while( layerTokens.HasMoreTokens() )
+        {
+            std::string token = TO_UTF8( layerTokens.GetNextToken() );
+
+            // Search for a layer name in canonical layer name used in .kicad_pcb files:
+            if( layerMasks.count( token ) )
+            {
+                for( PCB_LAYER_ID layer : layerMasks.at( token ).Seq() )
+                    layerMask.push_back( layer );
+            }
+            // Search for a layer name in canonical layer name used in GUI (not translated):
+            else if( layerGuiMasks.count( token ) )
+            {
+                for( PCB_LAYER_ID layer : layerGuiMasks.at( token ).Seq() )
+                    layerMask.push_back( layer );
+            }
+            else
+            {
+                m_reporter->Report( wxString::Format( _( "Invalid layer name \"%s\"\n" ),
+                                                      token ) );
+            }
+        }
+    }
+
+    return layerMask;
+}
+
+
 int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
 {
     JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
@@ -753,6 +822,14 @@ int PCBNEW_JOBS_HANDLER::JobExportSvg( JOB* aJob )
     loadOverrideDrawingSheet( brd, aSvgJob->m_drawingSheet );
     brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
     brd->SynchronizeProperties();
+    aSvgJob->m_plotLayerSequence = convertLayerArg( aSvgJob->m_argLayers, brd );
+    aSvgJob->m_plotOnAllLayersSequence = convertLayerArg( aSvgJob->m_argCommonLayers, brd );
+
+    if( aSvgJob->m_plotLayerSequence.size() < 1 )
+    {
+        m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
+        return CLI::EXIT_CODES::ERR_ARGS;
+    }
 
     PCB_PLOT_PARAMS plotOpts;
     PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
@@ -802,7 +879,14 @@ int PCBNEW_JOBS_HANDLER::JobExportDxf( JOB* aJob )
     loadOverrideDrawingSheet( brd, aDxfJob->m_drawingSheet );
     brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
     brd->SynchronizeProperties();
+    aDxfJob->m_plotLayerSequence = convertLayerArg( aDxfJob->m_argLayers, brd );
+    aDxfJob->m_plotOnAllLayersSequence = convertLayerArg( aDxfJob->m_argCommonLayers, brd );
 
+    if( aDxfJob->m_plotLayerSequence.size() < 1 )
+    {
+        m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
+        return CLI::EXIT_CODES::ERR_ARGS;
+    }
 
     if( aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE )
     {
@@ -872,6 +956,14 @@ int PCBNEW_JOBS_HANDLER::JobExportPdf( JOB* aJob )
     loadOverrideDrawingSheet( brd, aPdfJob->m_drawingSheet );
     brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
     brd->SynchronizeProperties();
+    aPdfJob->m_plotLayerSequence = convertLayerArg( aPdfJob->m_argLayers, brd );
+    aPdfJob->m_plotOnAllLayersSequence = convertLayerArg( aPdfJob->m_argCommonLayers, brd );
+
+    if( aPdfJob->m_plotLayerSequence.size() < 1 )
+    {
+        m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
+        return CLI::EXIT_CODES::ERR_ARGS;
+    }
 
     if( aPdfJob->m_pdfGenMode == JOB_EXPORT_PCB_PDF::GEN_MODE::ALL_LAYERS_ONE_FILE
         && aPdfJob->GetConfiguredOutputPath().IsEmpty() )
@@ -960,6 +1052,13 @@ int PCBNEW_JOBS_HANDLER::JobExportGerbers( JOB* aJob )
     brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
     brd->SynchronizeProperties();
 
+    if( !aGerberJob->m_argLayers.empty() )
+        aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers, nullptr );
+    else
+        aGerberJob->m_plotLayerSequence = LSET::AllLayersMask().SeqStackupForPlotting();
+
+    aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers, brd );
+
     PCB_PLOT_PARAMS       boardPlotOptions = brd->GetPlotOptions();
     GERBER_JOBFILE_WRITER jobfile_writer( brd );
 
@@ -1177,6 +1276,14 @@ int PCBNEW_JOBS_HANDLER::JobExportGerber( JOB* aJob )
     aJob->SetTitleBlock( brd->GetTitleBlock() );
     brd->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
     brd->SynchronizeProperties();
+    aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers, brd );
+    aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers, brd );
+
+    if( aGerberJob->m_plotLayerSequence.size() < 1 )
+    {
+        m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
+        return CLI::EXIT_CODES::ERR_ARGS;
+    }
 
     if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
     {
@@ -1654,6 +1761,11 @@ int PCBNEW_JOBS_HANDLER::JobExportFpSvg( JOB* aJob )
     PCB_IO_KICAD_SEXPR pcb_io( CTL_FOR_LIBRARY );
     FP_CACHE   fpLib( &pcb_io, svgJob->m_libraryPath );
 
+    if( !svgJob->m_argLayers.empty() )
+        svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers, nullptr );
+    else
+        svgJob->m_plotLayerSequence = LSET::AllLayersMask().SeqStackupForPlotting();
+
     try
     {
         fpLib.Load();
diff --git a/pcbnew/pcbnew_jobs_handler.h b/pcbnew/pcbnew_jobs_handler.h
index db9957a8aa..a54f3f7e6f 100644
--- a/pcbnew/pcbnew_jobs_handler.h
+++ b/pcbnew/pcbnew_jobs_handler.h
@@ -55,6 +55,8 @@ public:
 
 private:
     BOARD* getBoard( const wxString& aPath = wxEmptyString );
+    LSEQ convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const;
+
     void populateGerberPlotOptionsFromJob( PCB_PLOT_PARAMS&  aPlotOpts,
                                            JOB_EXPORT_PCB_GERBER* aJob );
     void populateGerberPlotOptionsFromJob( PCB_PLOT_PARAMS& aPlotOpts,