mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
414 lines
13 KiB
C++
414 lines
13 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2004-2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
* Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <reporter.h>
|
|
#include <macros.h>
|
|
#include <board_commit.h>
|
|
#include <cleanup_item.h>
|
|
#include <pcb_shape.h>
|
|
#include <pad.h>
|
|
#include <footprint.h>
|
|
#include <graphics_cleaner.h>
|
|
#include <fix_board_shape.h>
|
|
#include <board_design_settings.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tools/pad_tool.h>
|
|
|
|
GRAPHICS_CLEANER::GRAPHICS_CLEANER( const DRAWINGS& aDrawings, FOOTPRINT* aParentFootprint,
|
|
BOARD_COMMIT& aCommit, TOOL_MANAGER* aToolMgr ) :
|
|
m_drawings( aDrawings ),
|
|
m_parentFootprint( aParentFootprint ),
|
|
m_commit( aCommit ),
|
|
m_toolMgr( aToolMgr ),
|
|
m_dryRun( true ),
|
|
m_epsilon( 0 ),
|
|
m_outlinesTolerance( 0 ),
|
|
m_itemsList( nullptr )
|
|
{
|
|
}
|
|
|
|
|
|
void GRAPHICS_CLEANER::CleanupBoard( bool aDryRun,
|
|
std::vector<std::shared_ptr<CLEANUP_ITEM>>* aItemsList,
|
|
bool aMergeRects, bool aDeleteRedundant, bool aMergePads,
|
|
bool aFixBoardOutlines, int aTolerance )
|
|
{
|
|
m_dryRun = aDryRun;
|
|
m_itemsList = aItemsList;
|
|
m_outlinesTolerance = aTolerance;
|
|
|
|
m_epsilon = m_commit.GetBoard()->GetDesignSettings().m_MaxError;
|
|
|
|
// Clear the flag used to mark some shapes as deleted, in dry run:
|
|
for( BOARD_ITEM* drawing : m_drawings )
|
|
drawing->ClearFlags( IS_DELETED );
|
|
|
|
if( aDeleteRedundant )
|
|
cleanupShapes();
|
|
|
|
if( aFixBoardOutlines )
|
|
fixBoardOutlines();
|
|
|
|
if( aMergeRects )
|
|
mergeRects();
|
|
|
|
if( aMergePads )
|
|
mergePads();
|
|
|
|
// Clear the flag used to mark some shapes:
|
|
for( BOARD_ITEM* drawing : m_drawings )
|
|
drawing->ClearFlags( IS_DELETED );
|
|
}
|
|
|
|
|
|
bool equivalent( const VECTOR2I& a, const VECTOR2I& b, int epsilon )
|
|
{
|
|
return abs( a.x - b.x ) < epsilon && abs( a.y - b.y ) < epsilon;
|
|
};
|
|
|
|
|
|
bool GRAPHICS_CLEANER::isNullShape( PCB_SHAPE* aShape )
|
|
{
|
|
switch( aShape->GetShape() )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::RECTANGLE:
|
|
case SHAPE_T::ARC:
|
|
return equivalent( aShape->GetStart(), aShape->GetEnd(), m_epsilon );
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
return aShape->GetRadius() == 0;
|
|
|
|
case SHAPE_T::POLY:
|
|
return aShape->GetPointCount() == 0;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
aShape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
|
|
|
// If the Bezier points list contains 2 points, it is equivalent to a segment
|
|
if( aShape->GetBezierPoints().size() == 2 )
|
|
return equivalent( aShape->GetStart(), aShape->GetEnd(), m_epsilon );
|
|
|
|
// If the Bezier points list contains 1 points, it is equivalent to a point
|
|
return aShape->GetBezierPoints().size() < 2;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( aShape->SHAPE_T_asString() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool GRAPHICS_CLEANER::areEquivalent( PCB_SHAPE* aShape1, PCB_SHAPE* aShape2 )
|
|
{
|
|
if( aShape1->GetShape() != aShape2->GetShape()
|
|
|| aShape1->GetLayer() != aShape2->GetLayer()
|
|
|| aShape1->GetWidth() != aShape2->GetWidth() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch( aShape1->GetShape() )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::RECTANGLE:
|
|
case SHAPE_T::CIRCLE:
|
|
return equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
|
|
&& equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon );
|
|
|
|
case SHAPE_T::ARC:
|
|
return equivalent( aShape1->GetCenter(), aShape2->GetCenter(), m_epsilon )
|
|
&& equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
|
|
&& equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon );
|
|
|
|
case SHAPE_T::POLY:
|
|
// TODO
|
|
return false;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
return equivalent( aShape1->GetStart(), aShape2->GetStart(), m_epsilon )
|
|
&& equivalent( aShape1->GetEnd(), aShape2->GetEnd(), m_epsilon )
|
|
&& equivalent( aShape1->GetBezierC1(), aShape2->GetBezierC1(), m_epsilon )
|
|
&& equivalent( aShape1->GetBezierC2(), aShape2->GetBezierC2(), m_epsilon );
|
|
|
|
default:
|
|
wxFAIL_MSG( wxT( "GRAPHICS_CLEANER::areEquivalent unimplemented for " )
|
|
+ aShape1->SHAPE_T_asString() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void GRAPHICS_CLEANER::cleanupShapes()
|
|
{
|
|
// Remove duplicate shapes (2 superimposed identical shapes):
|
|
for( auto it = m_drawings.begin(); it != m_drawings.end(); it++ )
|
|
{
|
|
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( *it );
|
|
|
|
if( !shape || shape->HasFlag( IS_DELETED ) )
|
|
continue;
|
|
|
|
if( isNullShape( shape ) )
|
|
{
|
|
std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_NULL_GRAPHIC );
|
|
item->SetItems( shape );
|
|
m_itemsList->push_back( item );
|
|
|
|
if( !m_dryRun )
|
|
m_commit.Remove( shape );
|
|
|
|
continue;
|
|
}
|
|
|
|
for( auto it2 = it + 1; it2 != m_drawings.end(); it2++ )
|
|
{
|
|
PCB_SHAPE* shape2 = dynamic_cast<PCB_SHAPE*>( *it2 );
|
|
|
|
if( !shape2 || shape2->HasFlag( IS_DELETED ) )
|
|
continue;
|
|
|
|
if( areEquivalent( shape, shape2 ) )
|
|
{
|
|
std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_DUPLICATE_GRAPHIC );
|
|
item->SetItems( shape2 );
|
|
m_itemsList->push_back( item );
|
|
|
|
shape2->SetFlags(IS_DELETED );
|
|
|
|
if( !m_dryRun )
|
|
m_commit.Remove( shape2 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void GRAPHICS_CLEANER::fixBoardOutlines()
|
|
{
|
|
if( m_dryRun )
|
|
return;
|
|
|
|
std::vector<PCB_SHAPE*> shapeList;
|
|
std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
|
|
|
|
for( BOARD_ITEM* item : m_drawings )
|
|
{
|
|
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
|
|
|
|
if( !shape || !shape->IsOnLayer( Edge_Cuts ) )
|
|
continue;
|
|
|
|
shapeList.push_back( shape );
|
|
|
|
if( !m_dryRun )
|
|
m_commit.Modify( shape );
|
|
}
|
|
|
|
ConnectBoardShapes( shapeList, newShapes, m_outlinesTolerance );
|
|
|
|
std::vector<PCB_SHAPE*> items_to_select;
|
|
|
|
for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
|
|
m_commit.Add( ptr.release() );
|
|
}
|
|
|
|
|
|
void GRAPHICS_CLEANER::mergeRects()
|
|
{
|
|
struct SIDE_CANDIDATE
|
|
{
|
|
SIDE_CANDIDATE( PCB_SHAPE* aShape ) :
|
|
start( aShape->GetStart() ),
|
|
end( aShape->GetEnd() ),
|
|
shape( aShape )
|
|
{
|
|
if( start.x > end.x || start.y > end.y )
|
|
std::swap( start, end );
|
|
}
|
|
|
|
VECTOR2I start;
|
|
VECTOR2I end;
|
|
PCB_SHAPE* shape;
|
|
};
|
|
|
|
std::vector<SIDE_CANDIDATE*> sides;
|
|
std::map<VECTOR2I, std::vector<SIDE_CANDIDATE*>> ptMap;
|
|
|
|
// First load all the candidates into the side vector and layer maps
|
|
for( BOARD_ITEM* item : m_drawings )
|
|
{
|
|
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
|
|
|
|
if( !shape || isNullShape( shape ) || shape->GetShape() != SHAPE_T::SEGMENT )
|
|
continue;
|
|
|
|
if( shape->GetStart().x == shape->GetEnd().x || shape->GetStart().y == shape->GetEnd().y )
|
|
{
|
|
sides.emplace_back( new SIDE_CANDIDATE( shape ) );
|
|
ptMap[ sides.back()->start ].push_back( sides.back() );
|
|
}
|
|
}
|
|
|
|
// Now go through the sides and try and match lines into rectangles
|
|
for( SIDE_CANDIDATE* side : sides )
|
|
{
|
|
if( side->shape->HasFlag( IS_DELETED ) )
|
|
continue;
|
|
|
|
SIDE_CANDIDATE* left = nullptr;
|
|
SIDE_CANDIDATE* top = nullptr;
|
|
SIDE_CANDIDATE* right = nullptr;
|
|
SIDE_CANDIDATE* bottom = nullptr;
|
|
|
|
auto viable = [&]( SIDE_CANDIDATE* aCandidate ) -> bool
|
|
{
|
|
return aCandidate->shape->GetLayer() == side->shape->GetLayer()
|
|
&& aCandidate->shape->GetWidth() == side->shape->GetWidth()
|
|
&& !aCandidate->shape->HasFlag( IS_DELETED );
|
|
};
|
|
|
|
if( side->start.x == side->end.x )
|
|
{
|
|
// We've found a possible left; see if we have a top
|
|
//
|
|
left = side;
|
|
|
|
for( SIDE_CANDIDATE* candidate : ptMap[ left->start ] )
|
|
{
|
|
if( candidate != left && viable( candidate ) )
|
|
{
|
|
top = candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if( side->start.y == side->end.y )
|
|
{
|
|
// We've found a possible top; see if we have a left
|
|
//
|
|
top = side;
|
|
|
|
for( SIDE_CANDIDATE* candidate : ptMap[ top->start ] )
|
|
{
|
|
if( candidate != top && viable( candidate ) )
|
|
{
|
|
left = candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( top && left )
|
|
{
|
|
// See if we can fill in the other two sides
|
|
//
|
|
for( SIDE_CANDIDATE* candidate : ptMap[ top->end ] )
|
|
{
|
|
if( candidate != top && candidate != left && viable( candidate ) )
|
|
{
|
|
right = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( SIDE_CANDIDATE* candidate : ptMap[ left->end ] )
|
|
{
|
|
if( candidate != top && candidate != left && viable( candidate ) )
|
|
{
|
|
bottom = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( right && bottom && right->end == bottom->end )
|
|
{
|
|
left->shape->SetFlags( IS_DELETED );
|
|
top->shape->SetFlags( IS_DELETED );
|
|
right->shape->SetFlags( IS_DELETED );
|
|
bottom->shape->SetFlags( IS_DELETED );
|
|
|
|
std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_LINES_TO_RECT );
|
|
item->SetItems( left->shape, top->shape, right->shape, bottom->shape );
|
|
m_itemsList->push_back( item );
|
|
|
|
if( !m_dryRun )
|
|
{
|
|
PCB_SHAPE* rect = new PCB_SHAPE( m_parentFootprint );
|
|
|
|
rect->SetShape( SHAPE_T::RECTANGLE );
|
|
rect->SetFilled( false );
|
|
rect->SetStart( top->start );
|
|
rect->SetEnd( bottom->end );
|
|
rect->SetLayer( top->shape->GetLayer() );
|
|
rect->SetStroke( top->shape->GetStroke() );
|
|
|
|
m_commit.Add( rect );
|
|
m_commit.Remove( left->shape );
|
|
m_commit.Remove( top->shape );
|
|
m_commit.Remove( right->shape );
|
|
m_commit.Remove( bottom->shape );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for( SIDE_CANDIDATE* side : sides )
|
|
delete side;
|
|
}
|
|
|
|
|
|
void GRAPHICS_CLEANER::mergePads()
|
|
{
|
|
wxCHECK_MSG( m_parentFootprint, /*void*/, wxT( "mergePads() is FootprintEditor only" ) );
|
|
|
|
PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
|
|
std::map<wxString, int> padToNetTieGroupMap = m_parentFootprint->MapPadNumbersToNetTieGroups();
|
|
|
|
for( PAD* pad : m_parentFootprint->Pads() )
|
|
{
|
|
// Don't merge a pad that's in a net-tie pad group. (We don't care which group.)
|
|
if( padToNetTieGroupMap[ pad->GetNumber() ] >= 0 )
|
|
continue;
|
|
|
|
if( m_commit.GetStatus( m_parentFootprint ) == 0 )
|
|
m_commit.Modify( m_parentFootprint );
|
|
|
|
std::vector<PCB_SHAPE*> shapes = padTool->RecombinePad( pad, m_dryRun );
|
|
|
|
if( !shapes.empty() )
|
|
{
|
|
std::shared_ptr<CLEANUP_ITEM> item = std::make_shared<CLEANUP_ITEM>( CLEANUP_MERGE_PAD );
|
|
|
|
for( PCB_SHAPE* shape : shapes )
|
|
item->AddItem( shape );
|
|
|
|
item->AddItem( pad );
|
|
|
|
m_itemsList->push_back( item );
|
|
}
|
|
}
|
|
}
|