From 0fc4ccf8d9b6331e083e7e2dbdad4c266db34a52 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sun, 6 Mar 2022 13:35:46 +0000
Subject: [PATCH] Clean up pad handling in 3D viewer.

Most importantly, create F_Cu/B_Cu layers if they're otherwise empty
but we have plated pads to render on them.

Fixes https://gitlab.com/kicad/code/kicad/issues/10207

(cherry picked from commit 0dc857b5ab026be3ab64c1acbdb8defaba4d30bc)
---
 .../3d_canvas/create_3Dgraphic_brd_items.cpp  | 36 +++++++-----
 3d-viewer/3d_canvas/create_layer_items.cpp    |  5 +-
 .../3d_rendering/opengl/create_scene.cpp      | 40 ++++++++++---
 .../3d_rendering/opengl/render_3d_opengl.cpp  | 30 +++-------
 .../3d_rendering/opengl/render_3d_opengl.h    |  2 +
 pcbnew/footprint.cpp                          | 58 ++++++-------------
 pcbnew/pad.cpp                                | 53 ++++++++---------
 7 files changed, 107 insertions(+), 117 deletions(-)

diff --git a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp
index 61efe94c16..e1c97c2d92 100644
--- a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp
+++ b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp
@@ -493,15 +493,10 @@ void BOARD_ADAPTER::addPadsWithClearance( const FOOTPRINT* aFootprint,
 {
     for( PAD* pad : aFootprint->Pads() )
     {
-        if( !pad->IsOnLayer( aLayerId ) )
+        if( !pad->FlashLayer( aLayerId ) )
             continue;
 
-        // Skip pad annulus when not connected on this layer (if removing is enabled)
-        if( !pad->FlashLayer( aLayerId ) && IsCopperLayer( aLayerId ) )
-            continue;
-
-        // NPTH pads are not drawn on layers if the
-        // shape size and pos is the same as their hole:
+        // NPTH pads are not drawn on layers if the shape size and pos is the same as their hole:
         if( aSkipNPTHPadsWihNoCopper && ( pad->GetAttribute() == PAD_ATTRIB::NPTH ) )
         {
             if( pad->GetDrillSize() == pad->GetSize() && pad->GetOffset() == wxPoint( 0, 0 ) )
@@ -526,19 +521,28 @@ void BOARD_ADAPTER::addPadsWithClearance( const FOOTPRINT* aFootprint,
             }
         }
 
-        const bool isPlated = ( ( aLayerId == F_Cu ) && pad->FlashLayer( F_Mask ) ) ||
-                              ( ( aLayerId == B_Cu ) && pad->FlashLayer( B_Mask ) );
-
-        if( aSkipPlatedPads && isPlated )
-            continue;
-
-        if( aSkipNonPlatedPads && !isPlated )
-            continue;
-
         wxSize margin( aInflateValue, aInflateValue );
 
         switch( aLayerId )
         {
+        case F_Cu:
+            if( aSkipPlatedPads && pad->FlashLayer( F_Mask ) )
+                continue;
+
+            if( aSkipNonPlatedPads && !pad->FlashLayer( F_Mask ) )
+                continue;
+
+            break;
+
+        case B_Cu:
+            if( aSkipPlatedPads && pad->FlashLayer( B_Mask ) )
+                continue;
+
+            if( aSkipNonPlatedPads && !pad->FlashLayer( B_Mask ) )
+                continue;
+
+            break;
+
         case F_Mask:
         case B_Mask:
             margin.x += pad->GetSolderMaskMargin();
diff --git a/3d-viewer/3d_canvas/create_layer_items.cpp b/3d-viewer/3d_canvas/create_layer_items.cpp
index e2bfb3a858..f5b7dd2f2c 100644
--- a/3d-viewer/3d_canvas/create_layer_items.cpp
+++ b/3d-viewer/3d_canvas/create_layer_items.cpp
@@ -549,7 +549,6 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
         for( FOOTPRINT* footprint : m_board->Footprints() )
         {
             addPadsWithClearance( footprint, m_platedPadsFront, F_Cu, 0, true, false, true );
-
             addPadsWithClearance( footprint, m_platedPadsBack, B_Cu, 0, true, false, true );
         }
 
@@ -569,8 +568,8 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
             // Add pads to polygon list
             for( FOOTPRINT* footprint : m_board->Footprints() )
             {
-                // Note: NPTH pads are not drawn on copper layers when the pad
-                // has same shape as its hole
+                // Note: NPTH pads are not drawn on copper layers when the pad has same shape as
+                // its hole
                 footprint->TransformPadsWithClearanceToPolygon( *layerPoly, curr_layer_id,
                                                                 0, ARC_HIGH_DEF, ERROR_INSIDE,
                                                                 true, renderPlatedPadsAsPlated,
diff --git a/3d-viewer/3d_rendering/opengl/create_scene.cpp b/3d-viewer/3d_rendering/opengl/create_scene.cpp
index 1d20248762..b465d0cc32 100644
--- a/3d-viewer/3d_rendering/opengl/create_scene.cpp
+++ b/3d-viewer/3d_rendering/opengl/create_scene.cpp
@@ -374,6 +374,22 @@ OPENGL_RENDER_LIST* RENDER_3D_OPENGL::generateLayerList( const BVH_CONTAINER_2D*
 }
 
 
+OPENGL_RENDER_LIST* RENDER_3D_OPENGL::generateEmptyLayerList( PCB_LAYER_ID aLayerId )
+{
+    float layer_z_bot = 0.0f;
+    float layer_z_top = 0.0f;
+
+    getLayerZPos( aLayerId, layer_z_top, layer_z_bot );
+
+    TRIANGLE_DISPLAY_LIST* layerTriangles = new TRIANGLE_DISPLAY_LIST( 1 );
+
+    // store in a list so it will be latter deleted
+    m_triangles.push_back( layerTriangles );
+
+    return new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture, layer_z_bot, layer_z_top );
+}
+
+
 OPENGL_RENDER_LIST* RENDER_3D_OPENGL::createBoard( const SHAPE_POLY_SET& aBoardPoly,
                                                    const BVH_CONTAINER_2D* aThroughHoles )
 {
@@ -388,9 +404,8 @@ OPENGL_RENDER_LIST* RENDER_3D_OPENGL::createBoard( const SHAPE_POLY_SET& aBoardP
 
     if( listBoardObject2d.size() > 0 )
     {
-        // We will set a unitary Z so it will in future used with transformations
-        // since the board poly will be used not only to draw itself but also the
-        // solder mask layers.
+        // We will set a unitary Z so it will in future used with transformations since the
+        // board poly will be used not only to draw itself but also the solder mask layers.
         const float layer_z_top = 1.0f;
         const float layer_z_bot = 0.0f;
 
@@ -563,7 +578,7 @@ void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningRepo
         const BVH_CONTAINER_2D* container2d = ii.second;
 
         SHAPE_POLY_SET polyListSubtracted;
-        SHAPE_POLY_SET* aPolyList = nullptr;
+        SHAPE_POLY_SET* polyList = nullptr;
 
         // Load the vertical (Z axis) component of shapes
 
@@ -580,9 +595,8 @@ void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningRepo
                 {
                     polyListSubtracted.BooleanSubtract( m_boardAdapter.GetThroughHoleOdPolys(),
                                                         SHAPE_POLY_SET::PM_FAST );
-                    polyListSubtracted.BooleanSubtract(
-                            m_boardAdapter.GetOuterNonPlatedThroughHolePoly(),
-                            SHAPE_POLY_SET::PM_FAST );
+                    polyListSubtracted.BooleanSubtract( m_boardAdapter.GetOuterNonPlatedThroughHolePoly(),
+                                                        SHAPE_POLY_SET::PM_FAST );
                 }
 
                 if( m_boardAdapter.GetFlag( FL_SUBTRACT_MASK_FROM_SILK ) )
@@ -600,10 +614,10 @@ void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningRepo
                 }
             }
 
-            aPolyList = &polyListSubtracted;
+            polyList = &polyListSubtracted;
         }
 
-        OPENGL_RENDER_LIST* oglList = generateLayerList( container2d, aPolyList, layer_id,
+        OPENGL_RENDER_LIST* oglList = generateLayerList( container2d, polyList, layer_id,
                                                          &m_boardAdapter.GetThroughHoleIds() );
 
         if( oglList != nullptr )
@@ -626,6 +640,10 @@ void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningRepo
 
             m_platedPadsFront = generateLayerList( m_boardAdapter.GetPlatedPadsFront(),
                                                    &polySubtracted, F_Cu );
+
+            // An entry for F_Cu must exist in m_layers or we'll never look at m_platedPadsFront
+            if( m_layers.count( F_Cu ) == 0 )
+                m_layers[F_Cu] = generateEmptyLayerList( F_Cu );
         }
 
         if( m_boardAdapter.GetBackPlatedPadPolys() )
@@ -640,6 +658,10 @@ void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningRepo
 
             m_platedPadsBack = generateLayerList( m_boardAdapter.GetPlatedPadsBack(),
                                                   &polySubtracted, B_Cu );
+
+            // An entry for B_Cu must exist in m_layers or we'll never look at m_platedPadsBack
+            if( m_layers.count( B_Cu ) == 0 )
+                m_layers[B_Cu] = generateEmptyLayerList( B_Cu );
         }
     }
 
diff --git a/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp b/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp
index 3e81a7ee7c..822376de2d 100644
--- a/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp
+++ b/3d-viewer/3d_rendering/opengl/render_3d_opengl.cpp
@@ -504,7 +504,7 @@ void RENDER_3D_OPENGL::setCopperMaterial()
 void RENDER_3D_OPENGL::setPlatedCopperAndDepthOffset( PCB_LAYER_ID aLayer_id )
 {
     glEnable( GL_POLYGON_OFFSET_FILL );
-    glPolygonOffset(-0.1f, -2.0f );
+    glPolygonOffset( -0.1f, -2.0f );
     setLayerMaterial( aLayer_id );
 }
 
@@ -704,18 +704,16 @@ bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter,
             {
                 pLayerDispList->DrawAllCameraCulled( m_camera.GetPos().z, drawMiddleSegments );
 
-                // Draw copper plated pads
-                if( ( ( layer_id == F_Cu ) || ( layer_id == B_Cu ) ) &&
-                    ( m_platedPadsFront || m_platedPadsBack ) )
-                    setPlatedCopperAndDepthOffset( layer_id );
-
+                // Draw plated pads
                 if( layer_id == F_Cu && m_platedPadsFront )
                 {
+                    setPlatedCopperAndDepthOffset( layer_id );
                     m_platedPadsFront->DrawAllCameraCulled( m_camera.GetPos().z,
                                                             drawMiddleSegments );
                 }
                 else if( layer_id == B_Cu && m_platedPadsBack )
                 {
+                    setPlatedCopperAndDepthOffset( layer_id );
                     m_platedPadsBack->DrawAllCameraCulled( m_camera.GetPos().z,
                                                            drawMiddleSegments );
                 }
@@ -751,16 +749,10 @@ bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter,
                                                                           viasHolesLayer,
                                                                           m_antiBoard );
 
-                        // Draw copper plated pads
-
-                        if( ( ( layer_id == F_Cu ) || ( layer_id == B_Cu ) ) &&
-                            ( m_platedPadsFront || m_platedPadsBack ) )
-                        {
-                            setPlatedCopperAndDepthOffset( layer_id );
-                        }
-
+                        // Draw plated pads
                         if( layer_id == F_Cu && m_platedPadsFront )
                         {
+                            setPlatedCopperAndDepthOffset( layer_id );
                             m_platedPadsFront->DrawAllCameraCulledSubtractLayer(
                                     drawMiddleSegments,
                                     m_outerThroughHoles,
@@ -769,6 +761,7 @@ bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter,
                         }
                         else if( layer_id == B_Cu && m_platedPadsBack )
                         {
+                            setPlatedCopperAndDepthOffset( layer_id );
                             m_platedPadsBack->DrawAllCameraCulledSubtractLayer(
                                     drawMiddleSegments,
                                     m_outerThroughHoles,
@@ -785,21 +778,16 @@ bool RENDER_3D_OPENGL::Redraw( bool aIsMoving, REPORTER* aStatusReporter,
                                                                       m_outerThroughHoles,
                                                                       m_antiBoard );
 
-                    // Draw copper plated pads
-                    if( ( ( layer_id == F_Cu ) || ( layer_id == B_Cu ) ) &&
-                        ( m_platedPadsFront || m_platedPadsBack ) )
-                    {
-                        setPlatedCopperAndDepthOffset( layer_id );
-                    }
-
                     if( layer_id == F_Cu && m_platedPadsFront )
                     {
+                        setPlatedCopperAndDepthOffset( layer_id );
                         m_platedPadsFront->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
                                                                              m_outerThroughHoles,
                                                                              m_antiBoard );
                     }
                     else if( layer_id == B_Cu && m_platedPadsBack )
                     {
+                        setPlatedCopperAndDepthOffset( layer_id );
                         m_platedPadsBack->DrawAllCameraCulledSubtractLayer( drawMiddleSegments,
                                                                             m_outerThroughHoles,
                                                                             m_antiBoard );
diff --git a/3d-viewer/3d_rendering/opengl/render_3d_opengl.h b/3d-viewer/3d_rendering/opengl/render_3d_opengl.h
index 183eebbeef..d5c51abc62 100644
--- a/3d-viewer/3d_rendering/opengl/render_3d_opengl.h
+++ b/3d-viewer/3d_rendering/opengl/render_3d_opengl.h
@@ -84,6 +84,8 @@ private:
                                            PCB_LAYER_ID aLayerId,
                                            const BVH_CONTAINER_2D* aThroughHoles = nullptr );
 
+    OPENGL_RENDER_LIST* generateEmptyLayerList( PCB_LAYER_ID aLayerId );
+
     void addTopAndBottomTriangles( TRIANGLE_DISPLAY_LIST* aDst, const SFVEC2F& v0,
                                    const SFVEC2F& v1, const SFVEC2F& v2, float top, float bot );
 
diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp
index cfe6e85e43..4433156797 100644
--- a/pcbnew/footprint.cpp
+++ b/pcbnew/footprint.cpp
@@ -2214,51 +2214,31 @@ void FOOTPRINT::TransformPadsWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuff
 {
     for( const PAD* pad : m_pads )
     {
-        if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) )
-            continue;
-
-        if( !pad->FlashLayer( aLayer ) && IsCopperLayer( aLayer ) )
-            continue;
-
-        // NPTH pads are not drawn on layers if the shape size and pos is the same
-        // as their hole:
-        if( aSkipNPTHPadsWihNoCopper && pad->GetAttribute() == PAD_ATTRIB::NPTH )
-        {
-            if( pad->GetDrillSize() == pad->GetSize() && pad->GetOffset() == wxPoint( 0, 0 ) )
-            {
-                switch( pad->GetShape() )
-                {
-                case PAD_SHAPE::CIRCLE:
-                    if( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
-                        continue;
-
-                    break;
-
-                case PAD_SHAPE::OVAL:
-                    if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
-                        continue;
-
-                    break;
-
-                default:
-                    break;
-                }
-            }
-        }
-
-        const bool isPlated = ( ( aLayer == F_Cu ) && pad->FlashLayer( F_Mask ) ) ||
-                              ( ( aLayer == B_Cu ) && pad->FlashLayer( B_Mask ) );
-
-        if( aSkipPlatedPads && isPlated )
-            continue;
-
-        if( aSkipNonPlatedPads && !isPlated )
+        if( !pad->FlashLayer( aLayer ) )
             continue;
 
         wxSize clearance( aClearance, aClearance );
 
         switch( aLayer )
         {
+        case F_Cu:
+            if( aSkipPlatedPads && pad->FlashLayer( F_Mask ) )
+                continue;
+
+            if( aSkipNonPlatedPads && !pad->FlashLayer( F_Mask ) )
+                continue;
+
+            break;
+
+        case B_Cu:
+            if( aSkipPlatedPads && pad->FlashLayer( B_Mask ) )
+                continue;
+
+            if( aSkipNonPlatedPads && !pad->FlashLayer( B_Mask ) )
+                continue;
+
+            break;
+
         case F_Mask:
         case B_Mask:
             clearance.x += pad->GetSolderMaskMargin();
diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp
index 5de9dba81a..8ff264ed49 100644
--- a/pcbnew/pad.cpp
+++ b/pcbnew/pad.cpp
@@ -216,31 +216,11 @@ bool PAD::FlashLayer( int aLayer ) const
     std::vector<KICAD_T> types
     { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, PCB_ZONE_T, PCB_FP_ZONE_T };
 
-    const BOARD* board = GetBoard();
+    if( aLayer != UNDEFINED_LAYER && !IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) ) )
+        return false;
 
-    switch( GetAttribute() )
+    if( GetAttribute() == PAD_ATTRIB::NPTH && IsCopperLayer( aLayer ) )
     {
-    case PAD_ATTRIB::PTH:
-        if( aLayer == UNDEFINED_LAYER )
-            return true;
-
-        /// Heat sink pads always get copper
-        if( GetProperty() == PAD_PROP::HEATSINK )
-            return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
-
-        if( !m_removeUnconnectedLayer )
-            return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
-
-        // Plated through hole pads need copper on the top/bottom layers for proper soldering
-        // Unless the user has removed them in the pad dialog
-        if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) )
-            return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
-
-        return board && board->GetConnectivity()->IsConnectedOnLayer( this,
-                                                                      static_cast<int>( aLayer ),
-                                                                      types, true );
-
-    case PAD_ATTRIB::NPTH:
         if( GetShape() == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
         {
             if( GetOffset() == wxPoint( 0, 0 ) && GetDrillSize().x >= GetSize().x )
@@ -254,17 +234,32 @@ bool PAD::FlashLayer( int aLayer ) const
                 return false;
             }
         }
+    }
 
-        KI_FALLTHROUGH;
+    if( LSET::FrontBoardTechMask().test( aLayer ) )
+        aLayer = F_Cu;
+    else if( LSET::BackBoardTechMask().test( aLayer ) )
+        aLayer = B_Cu;
 
-    case PAD_ATTRIB::SMD:
-    case PAD_ATTRIB::CONN:
-    default:
-        if( aLayer == UNDEFINED_LAYER )
+    if( GetAttribute() == PAD_ATTRIB::PTH && IsCopperLayer( aLayer ) )
+    {
+        /// Heat sink pads always get copper
+        if( GetProperty() == PAD_PROP::HEATSINK )
             return true;
 
-        return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
+        if( !m_removeUnconnectedLayer )
+            return true;
+
+        // Plated through hole pads need copper on the top/bottom layers for proper soldering
+        // Unless the user has removed them in the pad dialog
+        if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) )
+            return true;
+
+        if( const BOARD* board = GetBoard() )
+            return board->GetConnectivity()->IsConnectedOnLayer( this, aLayer, types, true );
     }
+
+    return true;
 }