From a9379ecf3927d00c69e021777d46d278515f5364 Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Mon, 2 Aug 2021 16:00:34 -0400 Subject: [PATCH] Gerbview: Add ability to sort layers by file extension --- gerbview/gerber_file_image_list.cpp | 196 ++++++++++++++++++++- gerbview/gerber_file_image_list.h | 50 ++++++ gerbview/gerbview_frame.cpp | 12 +- gerbview/gerbview_frame.h | 9 + gerbview/widgets/gerbview_layer_widget.cpp | 12 +- gerbview/widgets/gerbview_layer_widget.h | 5 +- 6 files changed, 277 insertions(+), 7 deletions(-) diff --git a/gerbview/gerber_file_image_list.cpp b/gerbview/gerber_file_image_list.cpp index 782e7936a1..dde575ec58 100644 --- a/gerbview/gerber_file_image_list.cpp +++ b/gerbview/gerber_file_image_list.cpp @@ -200,6 +200,185 @@ const wxString GERBER_FILE_IMAGE_LIST::GetDisplayName( int aIdx, bool aNameOnly, } +struct GERBER_ORDER +{ + std::string m_FilenameMask; + GERBER_ORDER_ENUM m_Order; +}; + + +// clang-format off +static struct GERBER_ORDER gerberFileExtensionOrder[] = +{ + { ".GM1", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".GM3", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".GBR", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".DIM", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".MIL", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".GML", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { "EDGE.CUTS", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + { ".FAB", GERBER_ORDER_ENUM::GERBER_BOARD_OUTLINE }, + + { ".GKO", GERBER_ORDER_ENUM::GERBER_KEEP_OUT }, + + { ".GM?", GERBER_ORDER_ENUM::GERBER_MECHANICAL }, + { ".GM??", GERBER_ORDER_ENUM::GERBER_MECHANICAL }, + + { ".TXT", GERBER_ORDER_ENUM::GERBER_DRILL }, + { ".XLN", GERBER_ORDER_ENUM::GERBER_DRILL }, + { ".TAP", GERBER_ORDER_ENUM::GERBER_DRILL }, + { ".DRD", GERBER_ORDER_ENUM::GERBER_DRILL }, + { ".NC", GERBER_ORDER_ENUM::GERBER_DRILL }, + { ".XNC", GERBER_ORDER_ENUM::GERBER_DRILL }, + + { ".GTP", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + { ".CRC", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + { ".TSP", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + { "F.PASTE", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + { ".SPT", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + { "PT.PHO", GERBER_ORDER_ENUM::GERBER_TOP_PASTE }, + + { ".GTO", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + { ".PLC", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + { ".TSK", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + { "F.SILKS", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + { ".SST", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + { "ST.PHO", GERBER_ORDER_ENUM::GERBER_TOP_SILK_SCREEN }, + + { ".GTS", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + { ".STC", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + { ".TSM", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + { "F.MASK", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + { ".SMT", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + { "MT.PHO", GERBER_ORDER_ENUM::GERBER_TOP_SOLDER_MASK }, + + { ".GTL", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { ".CMP", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { ".TOP", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { "F.CU", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { "L1.PHO", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { ".PHD", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + { ".ART", GERBER_ORDER_ENUM::GERBER_TOP_COPPER }, + + { ".GBL", GERBER_ORDER_ENUM::GERBER_BOTTOM_COPPER }, + { ".SOL", GERBER_ORDER_ENUM::GERBER_BOTTOM_COPPER }, + { ".BOT", GERBER_ORDER_ENUM::GERBER_BOTTOM_COPPER }, + { "B.CU", GERBER_ORDER_ENUM::GERBER_BOTTOM_COPPER }, + { ".BOT", GERBER_ORDER_ENUM::GERBER_BOTTOM_COPPER }, + + { ".GBS", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + { ".STS", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + { ".BSM", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + { "B.MASK", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + { ".SMB", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + { "MB.PHO", GERBER_ORDER_ENUM::GERBER_BOTTOM_SOLDER_MASK }, + + { ".GBO", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + { ".PLS", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + { ".BSK", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + { "B.SILK", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + { ".SSB", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + { "SB.PHO", GERBER_ORDER_ENUM::GERBER_BOTTOM_SILK_SCREEN }, + + { ".GBP", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + { ".CRS", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + { ".BSP", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + { "B.PASTE", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + { ".SMB", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + { "MB.PHO", GERBER_ORDER_ENUM::GERBER_BOTTOM_PASTE }, + + // EAGLE CAD file to explicitly ignore that can match some other + // layers otherwise + { ".GPI", GERBER_ORDER_ENUM::GERBER_LAYER_UNKNOWN }, + + // Inner copper layers need to come last so the wildcard + // number matching doesn't pick up other specific layer names. + { ".GI?", GERBER_ORDER_ENUM::GERBER_INNER }, + { ".GI??", GERBER_ORDER_ENUM::GERBER_INNER }, + { ".G?", GERBER_ORDER_ENUM::GERBER_INNER }, + { ".G??", GERBER_ORDER_ENUM::GERBER_INNER }, + { ".G?L", GERBER_ORDER_ENUM::GERBER_INNER }, + { ".G??L", GERBER_ORDER_ENUM::GERBER_INNER }, +}; +// clang-format on + + +void GERBER_FILE_IMAGE_LIST::GetGerberLayerFromFilename( const wxString& filename, + enum GERBER_ORDER_ENUM& order, + wxString& matchedExtension ) +{ + order = GERBER_ORDER_ENUM::GERBER_LAYER_UNKNOWN; + matchedExtension = ""; + + for( struct GERBER_ORDER o : gerberFileExtensionOrder ) + { + wxString ext = filename.Right( o.m_FilenameMask.length() ).Upper(); + + if( ext.Matches( o.m_FilenameMask ) ) + { + order = o.m_Order; + matchedExtension = ext; + return; + } + } +} + + +static bool sortFileExtension( const GERBER_FILE_IMAGE* const& ref, + const GERBER_FILE_IMAGE* const& test ) +{ + // Do not change order: no criteria to sort items + if( !ref && !test ) + return false; + + // Not used: ref ordered after + if( !ref || !ref->m_InUse ) + return false; + + // Not used: ref ordered before + if( !test || !test->m_InUse ) + return true; + + enum GERBER_ORDER_ENUM ref_layer; + enum GERBER_ORDER_ENUM test_layer; + wxString ref_extension; + wxString test_extension; + + GERBER_FILE_IMAGE_LIST::GetGerberLayerFromFilename( ref->m_FileName, ref_layer, + ref_extension ); + GERBER_FILE_IMAGE_LIST::GetGerberLayerFromFilename( test->m_FileName, test_layer, + test_extension ); + + // Inner layers have a numeric code that we can compare against + if( ref_layer == GERBER_ORDER_ENUM::GERBER_INNER + && test_layer == GERBER_ORDER_ENUM::GERBER_INNER ) + { + unsigned long ref_layer_number = 0; + unsigned long test_layer_number = 0; + + // Strip extensions down to only the numbers in it. Later conversion to int will + // automatically skip the spaces + for( wxString::iterator it = ref_extension.begin(); it != ref_extension.end(); ++it ) + { + if( !isdigit( *it ) ) + *it = ' '; + } + + for( wxString::iterator it = test_extension.begin(); it != test_extension.end(); ++it ) + { + if( !isdigit( *it ) ) + *it = ' '; + } + + ref_extension.ToULong( &ref_layer_number ); + test_extension.ToULong( &test_layer_number ); + + return ref_layer_number < test_layer_number; + } + + return (int) ref_layer < (int) test_layer; +} + // Helper function, for std::sort. // Sort loaded images by Z order priority, if they have the X2 FileFormat info @@ -231,9 +410,10 @@ static bool sortZorder( const GERBER_FILE_IMAGE* const& ref, const GERBER_FILE_I } -std::unordered_map GERBER_FILE_IMAGE_LIST::SortImagesByZOrder() +std::unordered_map +GERBER_FILE_IMAGE_LIST::SortImagesByFunction( LayerSortFunction sortFunction ) { - std::sort( m_GERBER_List.begin(), m_GERBER_List.end(), sortZorder ); + std::sort( m_GERBER_List.begin(), m_GERBER_List.end(), sortFunction ); // The image order has changed. // Graphic layer numbering must be updated to match the widgets layer order @@ -254,3 +434,15 @@ std::unordered_map GERBER_FILE_IMAGE_LIST::SortImagesByZOrder() return tab_lyr; } + + +std::unordered_map GERBER_FILE_IMAGE_LIST::SortImagesByFileExtension() +{ + return SortImagesByFunction( sortFileExtension ); +} + + +std::unordered_map GERBER_FILE_IMAGE_LIST::SortImagesByZOrder() +{ + return SortImagesByFunction( sortZorder ); +} diff --git a/gerbview/gerber_file_image_list.h b/gerbview/gerber_file_image_list.h index 22b6dbd668..2c8ec0a4dc 100644 --- a/gerbview/gerber_file_image_list.h +++ b/gerbview/gerber_file_image_list.h @@ -32,6 +32,26 @@ #include #include + +enum class GERBER_ORDER_ENUM : int +{ + GERBER_TOP_PASTE = 0, + GERBER_TOP_SILK_SCREEN, + GERBER_TOP_SOLDER_MASK, + GERBER_TOP_COPPER, + GERBER_INNER, + GERBER_BOTTOM_COPPER, + GERBER_BOTTOM_SOLDER_MASK, + GERBER_BOTTOM_SILK_SCREEN, + GERBER_BOTTOM_PASTE, + GERBER_KEEP_OUT, + GERBER_MECHANICAL, + GERBER_BOARD_OUTLINE, + GERBER_DRILL, + GERBER_LAYER_UNKNOWN, +}; + + /* gerber files have different parameters to define units and how items must be plotted. * some are for the entire file, and other can change along a file. * In Gerber world: @@ -53,6 +73,9 @@ * But a GERBER_FILE_IMAGE can use more than one GERBER_LAYER. */ +typedef bool( LayerSortFunction )( const GERBER_FILE_IMAGE* const& ref, + const GERBER_FILE_IMAGE* const& test ); + class GERBER_FILE_IMAGE; /** @@ -66,6 +89,19 @@ public: GERBER_FILE_IMAGE_LIST(); ~GERBER_FILE_IMAGE_LIST(); + /** + * @brief Utility function to guess which PCB layer of a gerber/drill file corresponds to based + * on its file extension. + * + * Supports many CAD program file extension patterns. Detects gerbers, drills, edge layers, etc. + * + * @param filename Filename to check against the extension matching table. + * @param order Returns the order that this layer should be placed in a set of layers. + * @param matchedExtension The actual extension pattern that this filename matched. + */ + static void GetGerberLayerFromFilename( const wxString& filename, enum GERBER_ORDER_ENUM& order, + wxString& matchedExtension ); + static GERBER_FILE_IMAGE_LIST& GetImagesList(); GERBER_FILE_IMAGE* GetGbrImage( int aIdx ); @@ -113,6 +149,20 @@ public: */ const wxString GetDisplayName( int aIdx, bool aNameOnly = false, bool aFullName = false ); + /** + * Sort loaded images by file extension matching + * + * @return a mapping of old to new layer index + */ + std::unordered_map SortImagesByFunction( LayerSortFunction sortFunction ); + + /** + * Sort loaded images by file extension matching + * + * @return a mapping of old to new layer index + */ + std::unordered_map SortImagesByFileExtension(); + /** * Sort loaded images by Z order priority, if they have the X2 FileFormat info * (SortImagesByZOrder updates the graphic layer of these items) diff --git a/gerbview/gerbview_frame.cpp b/gerbview/gerbview_frame.cpp index 267d7959bd..5ecc42a195 100644 --- a/gerbview/gerbview_frame.cpp +++ b/gerbview/gerbview_frame.cpp @@ -499,10 +499,20 @@ void GERBVIEW_FRAME::syncLayerBox( bool aRebuildLayerBox ) } +void GERBVIEW_FRAME::SortLayersByFileExtension() +{ + RemapLayers( GetImagesList()->SortImagesByFileExtension() ); +} + + void GERBVIEW_FRAME::SortLayersByX2Attributes() { - auto remapping = GetImagesList()->SortImagesByZOrder(); + RemapLayers( GetImagesList()->SortImagesByZOrder() ); +} + +void GERBVIEW_FRAME::RemapLayers( std::unordered_map remapping ) +{ ReFillLayerWidget(); syncLayerBox( true ); diff --git a/gerbview/gerbview_frame.h b/gerbview/gerbview_frame.h index 1e7addc9e8..d081e6bc23 100644 --- a/gerbview/gerbview_frame.h +++ b/gerbview/gerbview_frame.h @@ -349,8 +349,17 @@ public: bool Clear_DrawLayers( bool query ); void Erase_Current_DrawLayer( bool query ); + void SortLayersByFileExtension(); void SortLayersByX2Attributes(); + /** + * Takes a layer remapping and reorders the layers. + * + * @param remapping A map of old layer number -> new layer number mapping. + * Generally sourced from the SortImagesBy* functions. + */ + void RemapLayers( std::unordered_map remapping ); + /** * Update each layers' differential option. Needed when diff mode changes or the active layer * changes (due to changing rendering order) which matters for diff mode but not otherwise. diff --git a/gerbview/widgets/gerbview_layer_widget.cpp b/gerbview/widgets/gerbview_layer_widget.cpp index 13ec3e9d52..1fd781eb03 100644 --- a/gerbview/widgets/gerbview_layer_widget.cpp +++ b/gerbview/widgets/gerbview_layer_widget.cpp @@ -134,7 +134,11 @@ void GERBER_LAYER_WIDGET::AddRightClickMenuItems( wxMenu* aMenu ) KiBitmap( BITMAPS::show_no_layers ) ); aMenu->AppendSeparator(); - AddMenuItem( aMenu, ID_SORT_GBR_LAYERS, _( "Sort Layers if X2 Mode" ), + + AddMenuItem( aMenu, ID_SORT_GBR_LAYERS_X2, _( "Sort Layers if X2 Mode" ), + KiBitmap( BITMAPS::reload ) ); + + AddMenuItem( aMenu, ID_SORT_GBR_LAYERS_FILE_EXT, _( "Sort Layers by File Extension" ), KiBitmap( BITMAPS::reload ) ); } @@ -189,9 +193,13 @@ void GERBER_LAYER_WIDGET::onPopupSelection( wxCommandEvent& event ) m_frame->GetCanvas()->Refresh(); break; - case ID_SORT_GBR_LAYERS: + case ID_SORT_GBR_LAYERS_X2: m_frame->SortLayersByX2Attributes(); break; + + case ID_SORT_GBR_LAYERS_FILE_EXT: + m_frame->SortLayersByFileExtension(); + break; } } diff --git a/gerbview/widgets/gerbview_layer_widget.h b/gerbview/widgets/gerbview_layer_widget.h index 6ade9df60e..a0355c4c57 100644 --- a/gerbview/widgets/gerbview_layer_widget.h +++ b/gerbview/widgets/gerbview_layer_widget.h @@ -95,8 +95,9 @@ protected: ID_SHOW_NO_LAYERS, ID_SHOW_NO_LAYERS_BUT_ACTIVE, ID_ALWAYS_SHOW_NO_LAYERS_BUT_ACTIVE, - ID_SORT_GBR_LAYERS, - ID_LAYER_MANAGER_END = ID_SORT_GBR_LAYERS, + ID_SORT_GBR_LAYERS_X2, + ID_SORT_GBR_LAYERS_FILE_EXT, + ID_LAYER_MANAGER_END = ID_SORT_GBR_LAYERS_FILE_EXT, }; private: