From 07eda5d57e052a29360cd5c21442e2e85950aebc Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Thu, 27 Mar 2025 10:13:31 +0000
Subject: [PATCH] ADDED: report copper area for current selection.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20439
---
 pcbnew/pcb_track.cpp         |   5 ++
 pcbnew/tools/pcb_control.cpp | 111 +++++++++++++++++++++++++++++------
 pcbnew/zone.cpp              |  16 +----
 3 files changed, 100 insertions(+), 32 deletions(-)

diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp
index 6e90110092..e163abe7a5 100644
--- a/pcbnew/pcb_track.cpp
+++ b/pcbnew/pcb_track.cpp
@@ -1749,6 +1749,11 @@ void PCB_TRACK::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_I
         }
     }
 
+    SHAPE_POLY_SET copper;
+    TransformShapeToPolySet( copper, GetLayer(), 0, ARC_LOW_DEF, ERROR_INSIDE );
+    aList.emplace_back( _( "Copper Area" ),
+                        aFrame->MessageTextFromValue( copper.Area(), true, EDA_DATA_TYPE::AREA ) );
+
     wxString source;
     int clearance = GetOwnClearance( GetLayer(), &source );
 
diff --git a/pcbnew/tools/pcb_control.cpp b/pcbnew/tools/pcb_control.cpp
index 856b03db7d..43bd5230f7 100644
--- a/pcbnew/tools/pcb_control.cpp
+++ b/pcbnew/tools/pcb_control.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "pcb_control.h"
+#include "convert_basic_shapes_to_polygon.h"
 
 #include <kiplatform/ui.h>
 #include <tools/edit_tool.h>
@@ -1873,7 +1874,7 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                 PCB_LAYER_ID layer = overlap.CuStack().front();
 
                 constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, a, b, layer );
-                msgItems.emplace_back( _( "Resolved clearance" ),
+                msgItems.emplace_back( _( "Resolved Clearance" ),
                                        m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
 
                 std::shared_ptr<SHAPE> a_shape( a_conn->GetEffectiveShape( layer ) );
@@ -1883,8 +1884,8 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
 
                 if( actual_clearance > -1 && actual_clearance < std::numeric_limits<int>::max() )
                 {
-                    msgItems.emplace_back( _( "Actual clearance" ),
-                                        m_frame->MessageTextFromValue( actual_clearance ) );
+                    msgItems.emplace_back( _( "Actual Clearance" ),
+                                           m_frame->MessageTextFromValue( actual_clearance ) );
                 }
             }
         }
@@ -1926,13 +1927,13 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                 if( actual < std::numeric_limits<int>::max() )
                 {
                     constraint = drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer );
-                    msgItems.emplace_back( _( "Resolved hole clearance" ),
-                            m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
+                    msgItems.emplace_back( _( "Resolved Hole Clearance" ),
+                                           m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
 
                     if( actual > -1 && actual < std::numeric_limits<int>::max() )
                     {
-                        msgItems.emplace_back( _( "Actual hole clearance" ),
-                                m_frame->MessageTextFromValue( actual ) );
+                        msgItems.emplace_back( _( "Actual Hole Clearance" ),
+                                               m_frame->MessageTextFromValue( actual ) );
                     }
                 }
             }
@@ -1964,12 +1965,12 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
 
                 if( edgeLayer == Edge_Cuts )
                 {
-                    msgItems.emplace_back( _( "Resolved edge clearance" ),
+                    msgItems.emplace_back( _( "Resolved Edge Clearance" ),
                                            m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
                 }
                 else
                 {
-                    msgItems.emplace_back( _( "Resolved margin clearance" ),
+                    msgItems.emplace_back( _( "Resolved Margin Clearance" ),
                                            m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
                 }
             }
@@ -1993,8 +1994,7 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                     if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
                     {
                         netNames.insert( UnescapeString( bci->GetNetname() ) );
-                        netClasses.insert( UnescapeString(
-                                bci->GetEffectiveNetClass()->GetHumanReadableName() ) );
+                        netClasses.insert( UnescapeString( bci->GetEffectiveNetClass()->GetHumanReadableName() ) );
 
                         if( netNames.size() > 1 && netClasses.size() > 1 )
                             break;
@@ -2020,16 +2020,21 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
             accumulateTrackLength =
                     [&]( EDA_ITEM* aItem )
                     {
-                        if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( aItem ) )
+                        if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
                         {
-                            selectedLength += track->GetLength();
+                            selectedLength += static_cast<PCB_TRACK*>( aItem )->GetLength();
                         }
-                        else if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aItem ) )
+                        else if( aItem->Type() == PCB_VIA_T )
                         {
-                            const SHAPE_T shapeType = shape->GetShape();
+                            // zero 2D length
+                        }
+                        else if( aItem->Type() == PCB_SHAPE_T )
+                        {
+                            PCB_SHAPE*    shape = static_cast<PCB_SHAPE*>( aItem );
 
-                            if( shapeType == SHAPE_T::SEGMENT || shapeType == SHAPE_T::ARC
-                                || shapeType == SHAPE_T::BEZIER )
+                            if( shape->GetShape() == SHAPE_T::SEGMENT
+                                    || shape->GetShape() == SHAPE_T::ARC
+                                    || shape->GetShape() == SHAPE_T::BEZIER )
                             {
                                 selectedLength += shape->GetLength();
                             }
@@ -2038,6 +2043,7 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                                 lengthValid = false;
                             }
                         }
+                        // Use dynamic_cast to include PCB_GENERATORs.
                         else if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( aItem ) )
                         {
                             group->RunOnChildren( accumulateTrackLength );
@@ -2049,7 +2055,10 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                     };
 
             for( EDA_ITEM* item : selection )
-                accumulateTrackLength( item );
+            {
+                if( lengthValid )
+                    accumulateTrackLength( item );
+            }
 
             if( lengthValid )
             {
@@ -2057,6 +2066,72 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
                                        m_frame->MessageTextFromValue( selectedLength ) );
             }
         }
+
+        if( selection.GetSize() >= 2 && selection.GetSize() < 100 )
+        {
+            LSET enabledCopper = LSET::AllCuMask( m_frame->GetBoard()->GetCopperLayerCount() );
+            bool areaValid = true;
+
+            std::map<PCB_LAYER_ID, SHAPE_POLY_SET> copperPolys;
+            SHAPE_POLY_SET                         holes;
+
+            std::function<void( EDA_ITEM* )> accumulateArea;
+
+            accumulateArea =
+                    [&]( EDA_ITEM* aItem )
+                    {
+                        if( aItem->Type() == PCB_FOOTPRINT_T )
+                        {
+                            areaValid = false;
+                            return;
+                        }
+
+                        if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( aItem ) )
+                        {
+                            boardItem->RunOnChildren( accumulateArea );
+
+                            for( PCB_LAYER_ID layer : LSET( boardItem->GetLayerSet() & enabledCopper ) )
+                            {
+                                boardItem->TransformShapeToPolySet( copperPolys[layer], layer, 0,
+                                                                    ARC_LOW_DEF, ERROR_INSIDE );
+                            }
+
+                            if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->HasHole() )
+                            {
+                                static_cast<PAD*>( aItem )->TransformHoleToPolygon( holes, 0, ARC_LOW_DEF,
+                                                                                    ERROR_OUTSIDE );
+                            }
+                            else if( aItem->Type() == PCB_VIA_T )
+                            {
+                                PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
+                                VECTOR2I center = via->GetPosition();
+                                int      R = via->GetDrillValue() / 2;
+
+                                TransformCircleToPolygon( holes, center, R, ARC_LOW_DEF, ERROR_OUTSIDE );
+                            }
+                        }
+                    };
+
+            for( EDA_ITEM* item : selection )
+            {
+                if( areaValid )
+                    accumulateArea( item );
+            }
+
+            if( areaValid )
+            {
+                double area = 0.0;
+
+                for( auto& [layer, copperPoly] : copperPolys )
+                {
+                    copperPoly.BooleanSubtract( holes );
+                    area += copperPoly.Area();
+                }
+
+                msgItems.emplace_back( _( "Selected 2D Copper Area" ),
+                                       m_frame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) );
+            }
+        }
     }
     else
     {
diff --git a/pcbnew/zone.cpp b/pcbnew/zone.cpp
index b1cf1cad6c..d65214666e 100644
--- a/pcbnew/zone.cpp
+++ b/pcbnew/zone.cpp
@@ -1504,20 +1504,8 @@ double ZONE::CalculateFilledArea()
 {
     m_area = 0.0;
 
-    // Iterate over each outline polygon in the zone and then iterate over
-    // each hole it has to compute the total area.
-    for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
-    {
-        std::shared_ptr<SHAPE_POLY_SET>& poly = pair.second;
-
-        for( int i = 0; i < poly->OutlineCount(); i++ )
-        {
-            m_area += poly->Outline( i ).Area();
-
-            for( int j = 0; j < poly->HoleCount( i ); j++ )
-                m_area -= poly->Hole( i, j ).Area();
-        }
-    }
+    for( const auto& [layer, poly] : m_FilledPolysList )
+        m_area += poly->Area();
 
     return m_area;
 }