1207 lines
41 KiB
C++
1207 lines
41 KiB
C++
/**
|
|
* @file zones_test_and_combine_areas.cpp
|
|
* @brief Functions to test, merge and cut polygons used as copper areas outlines
|
|
* some pieces of code come from FreePCB.
|
|
*/
|
|
|
|
#include "fctsys.h"
|
|
#include "common.h"
|
|
#include "confirm.h"
|
|
#include "class_undoredo_container.h"
|
|
|
|
#include "class_board.h"
|
|
#include "class_zone.h"
|
|
#include "class_marker_pcb.h"
|
|
|
|
#include "pcbnew.h"
|
|
#include "drc_stuff.h"
|
|
|
|
|
|
static bool bDontShowSelfIntersectionArcsWarning;
|
|
static bool bDontShowSelfIntersectionWarning;
|
|
static bool bDontShowIntersectionArcsWarning;
|
|
|
|
|
|
/**
|
|
* Function AddArea
|
|
* Add an empty copper area to board areas list
|
|
* @param aNewZonesList = a PICKED_ITEMS_LIST * where to store new areas pickers (useful
|
|
* in undo commands) can be NULL
|
|
* @param aNetcode = the necode of the copper area (0 = no net)
|
|
* @param aLayer = the layer of area
|
|
* @param aStartPointPosition = position of the first point of the polygon outline of this area
|
|
* @param aHatch = hacth option
|
|
* @return pointer to the new area
|
|
*/
|
|
ZONE_CONTAINER* BOARD::AddArea( PICKED_ITEMS_LIST* aNewZonesList, int aNetcode,
|
|
int aLayer, wxPoint aStartPointPosition, int aHatch )
|
|
{
|
|
ZONE_CONTAINER* new_area = InsertArea( aNetcode,
|
|
m_ZoneDescriptorList.size( ) - 1,
|
|
aLayer, aStartPointPosition.x,
|
|
aStartPointPosition.y, aHatch );
|
|
|
|
if( aNewZonesList )
|
|
{
|
|
ITEM_PICKER picker( new_area, UR_NEW );
|
|
picker.m_PickedItemType = new_area->Type();
|
|
aNewZonesList->PushItem( picker );
|
|
}
|
|
return new_area;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function RemoveArea
|
|
* remove copper area from net, and put it in a deleted list (if exists)
|
|
* @param aDeletedList = a PICKED_ITEMS_LIST * where to store deleted areas (useful in undo
|
|
* commands) can be NULL
|
|
* @param area_to_remove = area to delete or put in deleted list
|
|
*/
|
|
void BOARD::RemoveArea( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_to_remove )
|
|
{
|
|
if( area_to_remove == NULL )
|
|
return;
|
|
|
|
if( aDeletedList )
|
|
{
|
|
ITEM_PICKER picker( area_to_remove, UR_DELETED );
|
|
picker.m_PickedItemType = area_to_remove->Type();
|
|
aDeletedList->PushItem( picker );
|
|
Remove( area_to_remove ); // remove from zone list, but does not delete it
|
|
}
|
|
else
|
|
{
|
|
Delete( area_to_remove );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Function InsertArea
|
|
* add empty copper area to net, inserting after m_ZoneDescriptorList[iarea]
|
|
* @return pointer to the new area
|
|
*/
|
|
ZONE_CONTAINER* BOARD::InsertArea( int netcode, int iarea, int layer, int x, int y, int hatch )
|
|
{
|
|
ZONE_CONTAINER* new_area = new ZONE_CONTAINER( this );
|
|
|
|
new_area->SetNet( netcode );
|
|
new_area->SetLayer( layer );
|
|
new_area->m_TimeStamp = GetTimeStamp();
|
|
|
|
if( iarea < (int) ( m_ZoneDescriptorList.size() - 1 ) )
|
|
m_ZoneDescriptorList.insert( m_ZoneDescriptorList.begin() + iarea + 1, new_area );
|
|
else
|
|
m_ZoneDescriptorList.push_back( new_area );
|
|
|
|
new_area->m_Poly->Start( layer, x, y, hatch );
|
|
return new_area;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function CompleteArea
|
|
* complete copper area contour by adding a line from last to first corner
|
|
* if there is only 1 or 2 corners, remove (delete) the area
|
|
* @param area_to_complete = area to complete or remove
|
|
* @param style = style of last corner
|
|
* @return 1 if Ok, 0 if area removed
|
|
*/
|
|
int BOARD::CompleteArea( ZONE_CONTAINER* area_to_complete, int style )
|
|
{
|
|
if( area_to_complete->m_Poly->GetNumCorners() > 2 )
|
|
{
|
|
area_to_complete->m_Poly->Close( style );
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
Delete( area_to_complete );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function TestAreaPolygon
|
|
* Test an area for self-intersection.
|
|
*
|
|
* @param CurrArea = copper area to test
|
|
* @return :
|
|
* -1 if arcs intersect other sides
|
|
* 0 if no intersecting sides
|
|
* 1 if intersecting sides, but no intersecting arcs
|
|
* Also sets utility2 flag of area with return value
|
|
*/
|
|
int BOARD::TestAreaPolygon( ZONE_CONTAINER* CurrArea )
|
|
{
|
|
CPolyLine* p = CurrArea->m_Poly;
|
|
|
|
// first, check for sides intersecting other sides, especially arcs
|
|
bool bInt = false;
|
|
bool bArcInt = false;
|
|
int n_cont = p->GetNumContours();
|
|
|
|
// make bounding rect for each contour
|
|
std::vector<CRect> cr;
|
|
cr.reserve( n_cont );
|
|
|
|
for( int icont = 0; icont<n_cont; icont++ )
|
|
cr.push_back( p->GetCornerBounds( icont ) );
|
|
|
|
for( int icont = 0; icont<n_cont; icont++ )
|
|
{
|
|
int is_start = p->GetContourStart( icont );
|
|
int is_end = p->GetContourEnd( icont );
|
|
|
|
for( int is = is_start; is<=is_end; is++ )
|
|
{
|
|
int is_prev = is - 1;
|
|
|
|
if( is_prev < is_start )
|
|
is_prev = is_end;
|
|
|
|
int is_next = is + 1;
|
|
|
|
if( is_next > is_end )
|
|
is_next = is_start;
|
|
|
|
int style = p->GetSideStyle( is );
|
|
int x1i = p->GetX( is );
|
|
int y1i = p->GetY( is );
|
|
int x1f = p->GetX( is_next );
|
|
int y1f = p->GetY( is_next );
|
|
|
|
// check for intersection with any other sides
|
|
for( int icont2 = icont; icont2<n_cont; icont2++ )
|
|
{
|
|
if( cr[icont].left > cr[icont2].right
|
|
|| cr[icont].bottom > cr[icont2].top
|
|
|| cr[icont2].left > cr[icont].right
|
|
|| cr[icont2].bottom > cr[icont].top )
|
|
{
|
|
// rectangles don't overlap, do nothing
|
|
}
|
|
else
|
|
{
|
|
int is2_start = p->GetContourStart( icont2 );
|
|
int is2_end = p->GetContourEnd( icont2 );
|
|
|
|
for( int is2 = is2_start; is2<=is2_end; is2++ )
|
|
{
|
|
int is2_prev = is2 - 1;
|
|
|
|
if( is2_prev < is2_start )
|
|
is2_prev = is2_end;
|
|
|
|
int is2_next = is2 + 1;
|
|
|
|
if( is2_next > is2_end )
|
|
is2_next = is2_start;
|
|
|
|
if( icont != icont2
|
|
|| (is2 != is && is2 != is_prev && is2 != is_next && is != is2_prev
|
|
&& is !=
|
|
is2_next ) )
|
|
{
|
|
int style2 = p->GetSideStyle( is2 );
|
|
int x2i = p->GetX( is2 );
|
|
int y2i = p->GetY( is2 );
|
|
int x2f = p->GetX( is2_next );
|
|
int y2f = p->GetY( is2_next );
|
|
int ret = FindSegmentIntersections( x1i, y1i, x1f, y1f, style,
|
|
x2i, y2i, x2f, y2f, style2 );
|
|
if( ret )
|
|
{
|
|
// intersection between non-adjacent sides
|
|
bInt = true;
|
|
|
|
if( style != CPolyLine::STRAIGHT || style2 != CPolyLine::STRAIGHT )
|
|
{
|
|
bArcInt = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( bArcInt )
|
|
CurrArea->utility2 = -1;
|
|
else if( bInt )
|
|
CurrArea->utility2 = 1;
|
|
else
|
|
CurrArea->utility2 = 0;
|
|
|
|
return CurrArea->utility2;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function ClipAreaPolygon
|
|
* Process an area that has been modified, by clipping its polygon against itself.
|
|
* This may change the number and order of copper areas in the net.
|
|
* @param aNewZonesList = a PICKED_ITEMS_LIST * where to store new areas pickers (useful in
|
|
* undo commands) can be NULL
|
|
* @param aCurrArea = the zone to process
|
|
* @param bMessageBoxInt == true, shows message when clipping occurs.
|
|
* @param bMessageBoxArc == true, shows message when clipping can't be done due to arcs.
|
|
* @param bRetainArcs = true to handle arcs (not really used in KiCad)
|
|
* @return:
|
|
* -1 if arcs intersect other sides, so polygon can't be clipped
|
|
* 0 if no intersecting sides
|
|
* 1 if intersecting sides
|
|
* Also sets areas->utility1 flags if areas are modified
|
|
*/
|
|
int BOARD::ClipAreaPolygon( PICKED_ITEMS_LIST * aNewZonesList,
|
|
ZONE_CONTAINER* aCurrArea,
|
|
bool bMessageBoxArc, bool bMessageBoxInt, bool bRetainArcs )
|
|
{
|
|
CPolyLine* curr_polygon = aCurrArea->m_Poly;
|
|
int test = TestAreaPolygon( aCurrArea ); // this sets utility2 flag
|
|
|
|
if( test == -1 && !bRetainArcs )
|
|
test = 1;
|
|
|
|
if( test == -1 )
|
|
{
|
|
// arc intersections, don't clip unless bRetainArcs == false
|
|
if( bMessageBoxArc && bDontShowSelfIntersectionArcsWarning == false )
|
|
{
|
|
wxString str;
|
|
str.Printf( wxT( "Area %8.8X of net \"%s\" has arcs intersecting other sides.\n" ),
|
|
aCurrArea->m_TimeStamp, GetChars( aCurrArea->m_Netname ) );
|
|
str += wxT( "This may cause problems with other editing operations,\n" );
|
|
str += wxT( "such as adding cutouts. It can't be fixed automatically.\n" );
|
|
str += wxT( "Manual correction is recommended." );
|
|
wxMessageBox( str );
|
|
}
|
|
|
|
return -1; // arcs intersect with other sides, error
|
|
}
|
|
|
|
// mark all areas as unmodified except this one
|
|
for( unsigned ia = 0; ia < m_ZoneDescriptorList.size(); ia++ )
|
|
m_ZoneDescriptorList[ia]->utility = 0;
|
|
|
|
aCurrArea->utility = 1;
|
|
|
|
if( test == 1 )
|
|
{
|
|
// non-arc intersections, clip the polygon
|
|
if( bMessageBoxInt && bDontShowSelfIntersectionWarning == false )
|
|
{
|
|
wxString str;
|
|
str.Printf( wxT( "Area %8.8X of net \"%s\" is self-intersecting and will be clipped.\n" ),
|
|
aCurrArea->m_TimeStamp, GetChars( aCurrArea->m_Netname ) );
|
|
str += wxT( "This may result in splitting the area.\n" );
|
|
str += wxT( "If the area is complex, this may take a few seconds." );
|
|
wxMessageBox( str );
|
|
|
|
// bDontShowSelfIntersectionWarning = dlg.bDontShowBoxState;
|
|
}
|
|
}
|
|
|
|
//** TODO test for cutouts outside of area
|
|
//** if( test == 1 )
|
|
{
|
|
std::vector<CPolyLine*>* pa = new std::vector<CPolyLine*>;
|
|
curr_polygon->Undraw();
|
|
int n_poly = aCurrArea->m_Poly->NormalizeAreaOutlines( pa, bRetainArcs );
|
|
|
|
// i.e if clipping has created some polygons, we must add these new copper areas.
|
|
if( n_poly > 1 )
|
|
{
|
|
ZONE_CONTAINER* NewArea;
|
|
|
|
for( int ip = 1; ip < n_poly; ip++ )
|
|
{
|
|
// create new copper area and copy poly into it
|
|
CPolyLine* new_p = (*pa)[ip - 1];
|
|
NewArea = AddArea( aNewZonesList, aCurrArea->GetNet(), aCurrArea->GetLayer(),
|
|
wxPoint(0, 0), CPolyLine::NO_HATCH );
|
|
|
|
// remove the poly that was automatically created for the new area
|
|
// and replace it with a poly from NormalizeWithKbool
|
|
delete NewArea->m_Poly;
|
|
NewArea->m_Poly = new_p;
|
|
NewArea->m_Poly->Draw();
|
|
NewArea->utility = 1;
|
|
}
|
|
}
|
|
|
|
curr_polygon->Draw();
|
|
delete pa;
|
|
}
|
|
|
|
return test;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process an area that has been modified, by clipping its polygon against
|
|
* itself and the polygons for any other areas on the same net.
|
|
* This may change the number and order of copper areas in the net.
|
|
* @param aModifiedZonesList = a PICKED_ITEMS_LIST * where to store deleted or added areas
|
|
* (useful in undo commands can be NULL
|
|
* @param modified_area = area to test
|
|
* @param bMessageBoxArc if true, shows message when clipping can't be done due to arcs.
|
|
* @param bMessageBoxInt == true, shows message when clipping occurs.
|
|
* @return :
|
|
* -1 if arcs intersect other sides, so polygon can't be clipped
|
|
* 0 if no intersecting sides
|
|
* 1 if intersecting sides, polygon clipped
|
|
*/
|
|
int BOARD::AreaPolygonModified( PICKED_ITEMS_LIST* aModifiedZonesList,
|
|
ZONE_CONTAINER* modified_area,
|
|
bool bMessageBoxArc,
|
|
bool bMessageBoxInt )
|
|
{
|
|
// clip polygon against itself
|
|
int test = ClipAreaPolygon( aModifiedZonesList, modified_area, bMessageBoxArc, bMessageBoxInt );
|
|
|
|
if( test == -1 )
|
|
return test;
|
|
|
|
// now see if we need to clip against other areas
|
|
int layer = modified_area->GetLayer();
|
|
bool bCheckAllAreas = false;
|
|
|
|
if( test == 1 )
|
|
bCheckAllAreas = true;
|
|
else
|
|
bCheckAllAreas = TestAreaIntersections( modified_area );
|
|
|
|
if( bCheckAllAreas )
|
|
CombineAllAreasInNet( aModifiedZonesList, modified_area->GetNet(), bMessageBoxInt, true );
|
|
|
|
if( layer >= FIRST_NO_COPPER_LAYER ) // Refill non copper zones on this layer
|
|
{
|
|
if( m_ZoneDescriptorList.size() > 0 )
|
|
{
|
|
for( unsigned ia = 0; ia < m_ZoneDescriptorList.size(); ia++ )
|
|
if( m_ZoneDescriptorList[ia]->GetLayer() == layer )
|
|
m_ZoneDescriptorList[ia]->BuildFilledPolysListData( this );
|
|
}
|
|
}
|
|
|
|
// Test for bad areas: all zones must have more than 2 corners:
|
|
// Note: should not happen, but just in case.
|
|
for( unsigned ia1 = 0; ia1 < m_ZoneDescriptorList.size() - 1; )
|
|
{
|
|
ZONE_CONTAINER* zone = m_ZoneDescriptorList[ia1];
|
|
|
|
if( zone->GetNumCorners() >= 3 )
|
|
ia1++;
|
|
else // Remove zone because it is incorrect:
|
|
RemoveArea( aModifiedZonesList, zone );
|
|
}
|
|
|
|
return test;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function CombineAllAreasInNet
|
|
* Checks all copper areas in net for intersections, combining them if found
|
|
* @param aDeletedList = a PICKED_ITEMS_LIST * where to store deleted areas (useful in
|
|
* undo commands can be NULL
|
|
* @param aNetCode = net to consider
|
|
* @param bMessageBox : if true display warning message box
|
|
* @param bUseUtility : if true, don't check areas if both utility flags are 0
|
|
* Sets utility flag = 1 for any areas modified
|
|
* If an area has self-intersecting arcs, doesn't try to combine it
|
|
*/
|
|
int BOARD::CombineAllAreasInNet( PICKED_ITEMS_LIST* aDeletedList, int aNetCode,
|
|
bool bMessageBox, bool bUseUtility )
|
|
{
|
|
if( m_ZoneDescriptorList.size() <= 1 )
|
|
return 0;
|
|
|
|
// start by testing all area polygons to set utility2 flags
|
|
for( unsigned ia = 0; ia < m_ZoneDescriptorList.size(); ia++ )
|
|
if( m_ZoneDescriptorList[ia]->GetNet() == aNetCode )
|
|
TestAreaPolygon( m_ZoneDescriptorList[ia] );
|
|
|
|
// now loop through all combinations
|
|
for( unsigned ia1 = 0; ia1 < m_ZoneDescriptorList.size() - 1; ia1++ )
|
|
{
|
|
ZONE_CONTAINER* curr_area = m_ZoneDescriptorList[ia1];
|
|
if( curr_area->GetNet() != aNetCode )
|
|
continue;
|
|
|
|
// legal polygon
|
|
CRect b1 = curr_area->m_Poly->GetCornerBounds();
|
|
bool mod_ia1 = false;
|
|
|
|
for( unsigned ia2 = m_ZoneDescriptorList.size() - 1; ia2 > ia1; ia2-- )
|
|
{
|
|
ZONE_CONTAINER* area2 = m_ZoneDescriptorList[ia2];
|
|
|
|
if( area2->GetNet() != aNetCode )
|
|
continue;
|
|
|
|
if( curr_area->GetLayer() == area2->GetLayer()
|
|
&& curr_area->utility2 != -1 && area2->utility2 != -1 )
|
|
{
|
|
CRect b2 = area2->m_Poly->GetCornerBounds();
|
|
if( !( b1.left > b2.right || b1.right < b2.left
|
|
|| b1.bottom > b2.top || b1.top < b2.bottom ) )
|
|
{
|
|
// check area2 against curr_area
|
|
if( curr_area->utility || area2->utility || bUseUtility == false )
|
|
{
|
|
int ret = TestAreaIntersection( curr_area, area2 );
|
|
|
|
if( ret == 1 )
|
|
ret = CombineAreas( aDeletedList, curr_area, area2 );
|
|
|
|
if( ret == 1 )
|
|
{
|
|
mod_ia1 = true;
|
|
}
|
|
else if( ret == 2 )
|
|
{
|
|
if( bMessageBox && bDontShowIntersectionArcsWarning == false )
|
|
{
|
|
wxString str;
|
|
str.Printf( wxT( "Areas %d and %d of net \"%s\" intersect, but some of the intersecting sides are arcs.\n" ),
|
|
ia1 + 1,
|
|
ia2 + 1,
|
|
GetChars( curr_area->m_Netname ) );
|
|
str += wxT( "Therefore, these areas can't be combined." );
|
|
wxMessageBox( str );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( mod_ia1 )
|
|
ia1--; // if modified, we need to check it again
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function TestAreaIntersections
|
|
* Check for intersection of a given copper area with other areas in same net
|
|
* @param area_to_test = area to compare to all other areas in the same net
|
|
*/
|
|
bool BOARD::TestAreaIntersections( ZONE_CONTAINER* area_to_test )
|
|
{
|
|
CPolyLine* poly1 = area_to_test->m_Poly;
|
|
|
|
for( unsigned ia2 = 0; ia2 < m_ZoneDescriptorList.size(); ia2++ )
|
|
{
|
|
ZONE_CONTAINER* area2 = m_ZoneDescriptorList[ia2];
|
|
|
|
if( area_to_test->GetNet() != area2->GetNet() )
|
|
continue;
|
|
|
|
if( area_to_test == area2 )
|
|
continue;
|
|
|
|
// see if areas are on same layer
|
|
if( area_to_test->GetLayer() != area2->GetLayer() )
|
|
continue;
|
|
|
|
CPolyLine* poly2 = area2->m_Poly;
|
|
|
|
// test bounding rects
|
|
CRect b1 = poly1->GetCornerBounds();
|
|
CRect b2 = poly2->GetCornerBounds();
|
|
|
|
if( b1.bottom > b2.top
|
|
|| b1.top < b2.bottom
|
|
|| b1.left > b2.right
|
|
|| b1.right < b2.left )
|
|
continue;
|
|
|
|
// test for intersecting segments
|
|
for( int icont1 = 0; icont1<poly1->GetNumContours(); icont1++ )
|
|
{
|
|
int is1 = poly1->GetContourStart( icont1 );
|
|
int ie1 = poly1->GetContourEnd( icont1 );
|
|
|
|
for( int ic1 = is1; ic1<=ie1; ic1++ )
|
|
{
|
|
int xi1 = poly1->GetX( ic1 );
|
|
int yi1 = poly1->GetY( ic1 );
|
|
int xf1, yf1, style1;
|
|
|
|
if( ic1 < ie1 )
|
|
{
|
|
xf1 = poly1->GetX( ic1 + 1 );
|
|
yf1 = poly1->GetY( ic1 + 1 );
|
|
}
|
|
else
|
|
{
|
|
xf1 = poly1->GetX( is1 );
|
|
yf1 = poly1->GetY( is1 );
|
|
}
|
|
|
|
style1 = poly1->GetSideStyle( ic1 );
|
|
|
|
for( int icont2 = 0; icont2 < poly2->GetNumContours(); icont2++ )
|
|
{
|
|
int is2 = poly2->GetContourStart( icont2 );
|
|
int ie2 = poly2->GetContourEnd( icont2 );
|
|
|
|
for( int ic2 = is2; ic2<=ie2; ic2++ )
|
|
{
|
|
int xi2 = poly2->GetX( ic2 );
|
|
int yi2 = poly2->GetY( ic2 );
|
|
int xf2, yf2, style2;
|
|
|
|
if( ic2 < ie2 )
|
|
{
|
|
xf2 = poly2->GetX( ic2 + 1 );
|
|
yf2 = poly2->GetY( ic2 + 1 );
|
|
}
|
|
else
|
|
{
|
|
xf2 = poly2->GetX( is2 );
|
|
yf2 = poly2->GetY( is2 );
|
|
}
|
|
|
|
style2 = poly2->GetSideStyle( ic2 );
|
|
int n_int = FindSegmentIntersections( xi1, yi1, xf1, yf1, style1,
|
|
xi2, yi2, xf2, yf2, style2 );
|
|
if( n_int )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a contour is inside an other contour, no segments intersects, but the zones can
|
|
// be combined test a corner inside an outline (only one corner is enought)
|
|
for( int ic2 = 0; ic2 < poly2->GetNumCorners(); ic2++ )
|
|
{
|
|
int x = poly2->GetX( ic2 );
|
|
int y = poly2->GetY( ic2 );
|
|
|
|
if( poly1->TestPointInside( x, y ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for( int ic1 = 0; ic1 < poly1->GetNumCorners(); ic1++ )
|
|
{
|
|
int x = poly1->GetX( ic1 );
|
|
int y = poly1->GetY( ic1 );
|
|
|
|
if( poly2->TestPointInside( x, y ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function TestAreaIntersection
|
|
* Test for intersection of 2 copper areas
|
|
* area_to_test must be after area_ref in m_ZoneDescriptorList
|
|
* @param area_ref = area reference
|
|
* @param area_to_test = area to compare for intersection calculations
|
|
* @return : 0 if no intersection
|
|
* 1 if intersection
|
|
* 2 if arcs intersect
|
|
*/
|
|
int BOARD::TestAreaIntersection( ZONE_CONTAINER* area_ref, ZONE_CONTAINER* area_to_test )
|
|
{
|
|
// see if areas are on same layer
|
|
if( area_ref->GetLayer() != area_to_test->GetLayer() )
|
|
return 0;
|
|
|
|
CPolyLine* poly1 = area_ref->m_Poly;
|
|
CPolyLine* poly2 = area_to_test->m_Poly;
|
|
|
|
// test bounding rects
|
|
CRect b1 = poly1->GetCornerBounds();
|
|
CRect b2 = poly2->GetCornerBounds();
|
|
|
|
if( b1.bottom > b2.top
|
|
|| b1.top < b2.bottom
|
|
|| b1.left > b2.right
|
|
|| b1.right < b2.left )
|
|
return 0;
|
|
|
|
// now test for intersecting segments
|
|
bool bInt = false;
|
|
bool bArcInt = false;
|
|
|
|
for( int icont1 = 0; icont1<poly1->GetNumContours(); icont1++ )
|
|
{
|
|
int is1 = poly1->GetContourStart( icont1 );
|
|
int ie1 = poly1->GetContourEnd( icont1 );
|
|
|
|
for( int ic1 = is1; ic1<=ie1; ic1++ )
|
|
{
|
|
int xi1 = poly1->GetX( ic1 );
|
|
int yi1 = poly1->GetY( ic1 );
|
|
int xf1, yf1, style1;
|
|
|
|
if( ic1 < ie1 )
|
|
{
|
|
xf1 = poly1->GetX( ic1 + 1 );
|
|
yf1 = poly1->GetY( ic1 + 1 );
|
|
}
|
|
else
|
|
{
|
|
xf1 = poly1->GetX( is1 );
|
|
yf1 = poly1->GetY( is1 );
|
|
}
|
|
|
|
style1 = poly1->GetSideStyle( ic1 );
|
|
|
|
for( int icont2 = 0; icont2<poly2->GetNumContours(); icont2++ )
|
|
{
|
|
int is2 = poly2->GetContourStart( icont2 );
|
|
int ie2 = poly2->GetContourEnd( icont2 );
|
|
|
|
for( int ic2 = is2; ic2<=ie2; ic2++ )
|
|
{
|
|
int xi2 = poly2->GetX( ic2 );
|
|
int yi2 = poly2->GetY( ic2 );
|
|
int xf2, yf2, style2;
|
|
|
|
if( ic2 < ie2 )
|
|
{
|
|
xf2 = poly2->GetX( ic2 + 1 );
|
|
yf2 = poly2->GetY( ic2 + 1 );
|
|
}
|
|
else
|
|
{
|
|
xf2 = poly2->GetX( is2 );
|
|
yf2 = poly2->GetY( is2 );
|
|
}
|
|
|
|
style2 = poly2->GetSideStyle( ic2 );
|
|
int n_int = FindSegmentIntersections( xi1, yi1, xf1, yf1, style1,
|
|
xi2, yi2, xf2, yf2, style2 );
|
|
if( n_int )
|
|
{
|
|
bInt = true;
|
|
|
|
if( style1 != CPolyLine::STRAIGHT || style2 != CPolyLine::STRAIGHT )
|
|
bArcInt = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( bArcInt )
|
|
break;
|
|
}
|
|
|
|
if( !bInt )
|
|
{
|
|
if( bArcInt )
|
|
return 0;
|
|
|
|
// If a contour is inside an other contour, no segments intersects, but the zones
|
|
// can be combined test a corner inside an outline (only one corner is enought)
|
|
for( int ic2 = 0; ic2 < poly2->GetNumCorners(); ic2++ )
|
|
{
|
|
int x = poly2->GetX( ic2 );
|
|
int y = poly2->GetY( ic2 );
|
|
|
|
if( poly1->TestPointInside( x, y ) )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for( int ic1 = 0; ic1 < poly1->GetNumCorners(); ic1++ )
|
|
{
|
|
int x = poly1->GetX( ic1 );
|
|
int y = poly1->GetY( ic1 );
|
|
|
|
if( poly2->TestPointInside( x, y ) )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if( bArcInt )
|
|
return 2;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function CombineAreas
|
|
* If possible, combine 2 copper areas
|
|
* @param aDeletedList = a PICKED_ITEMS_LIST * where to store deleted areas (useful in undo
|
|
* commands can be NULL
|
|
* @param area_ref = tje main area (zone)
|
|
* @param area_to_combine = the zone that can be merged with area_ref
|
|
* area_ref must be BEFORE area_to_combine
|
|
* area_to_combine will be deleted, if areas are combined
|
|
* @return : 0 if no intersection
|
|
* 1 if intersection
|
|
* 2 if arcs intersect
|
|
*/
|
|
int BOARD::CombineAreas( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_ref,
|
|
ZONE_CONTAINER* area_to_combine )
|
|
{
|
|
if( area_ref == area_to_combine )
|
|
{
|
|
wxASSERT( 0 );
|
|
}
|
|
|
|
// polygons intersect, combine them
|
|
std::vector<CArc> arc_array1;
|
|
std::vector<CArc> arc_array2;
|
|
bool keep_area_to_combine = false;
|
|
|
|
Bool_Engine* booleng = new Bool_Engine();
|
|
ArmBoolEng( booleng );
|
|
|
|
area_ref->m_Poly->AddPolygonsToBoolEng( booleng, GROUP_A, -1, -1 );
|
|
area_to_combine->m_Poly->AddPolygonsToBoolEng( booleng, GROUP_B, -1, -1 );
|
|
booleng->Do_Operation( BOOL_OR );
|
|
|
|
// create area with external contour: Recreate only area edges, NOT holes
|
|
if( booleng->StartPolygonGet() )
|
|
{
|
|
if( booleng->GetPolygonPointEdgeType() == KB_INSIDE_EDGE )
|
|
{
|
|
DisplayError( NULL, wxT( "BOARD::CombineAreas() error: unexpected hole descriptor" ) );
|
|
}
|
|
|
|
area_ref->m_Poly->RemoveAllContours();
|
|
|
|
// foreach point in the polygon
|
|
bool first = true;
|
|
|
|
while( booleng->PolygonHasMorePoints() )
|
|
{
|
|
int x = (int) booleng->GetPolygonXPoint();
|
|
int y = (int) booleng->GetPolygonYPoint();
|
|
|
|
if( first )
|
|
{
|
|
first = false;
|
|
area_ref->m_Poly->Start( area_ref->GetLayer(
|
|
), x, y, area_ref->m_Poly->GetHatchStyle() );
|
|
}
|
|
else
|
|
{
|
|
area_ref->m_Poly->AppendCorner( x, y );
|
|
}
|
|
}
|
|
|
|
booleng->EndPolygonGet();
|
|
area_ref->m_Poly->Close();
|
|
}
|
|
|
|
// Recreate the area_to_combine if a second polygon exists
|
|
// if not exists , the first poly contains the 2 initial polygons
|
|
#if 0 // TestAreaIntersection must be called before combine areas, so
|
|
// 2 intersecting areas are expected, and only one outline contour after combining areas
|
|
else
|
|
{
|
|
area_to_combine->m_Poly->RemoveAllContours();
|
|
keep_area_to_combine = true;
|
|
|
|
// create area with external contour: Recreate only area edges, NOT holes (todo..)
|
|
{
|
|
// foreach point in the polygon
|
|
bool first = true;
|
|
while( booleng->PolygonHasMorePoints() )
|
|
{
|
|
int x = booleng->GetPolygonXPoint();
|
|
int y = booleng->GetPolygonYPoint();
|
|
|
|
if( first )
|
|
{
|
|
first = false;
|
|
area_to_combine->m_Poly->Start( area_ref->GetLayer(), x, y,
|
|
area_ref->m_Poly->GetHatchStyle() );
|
|
}
|
|
else
|
|
{
|
|
area_to_combine->m_Poly->AppendCorner( x, y );
|
|
}
|
|
}
|
|
|
|
booleng->EndPolygonGet();
|
|
area_to_combine->m_Poly->Close();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// add holes
|
|
bool show_error = true;
|
|
|
|
while( booleng->StartPolygonGet() )
|
|
{
|
|
// we expect all vertex are holes inside the main outline
|
|
if( booleng->GetPolygonPointEdgeType() != KB_INSIDE_EDGE )
|
|
{
|
|
if( show_error ) // show this error only once, if happens
|
|
DisplayError( NULL,
|
|
wxT( "BOARD::CombineAreas() error: unexpected outside contour descriptor" ) );
|
|
|
|
show_error = false;
|
|
continue;
|
|
}
|
|
|
|
while( booleng->PolygonHasMorePoints() )
|
|
{
|
|
int x = (int) booleng->GetPolygonXPoint();
|
|
int y = (int) booleng->GetPolygonYPoint();
|
|
area_ref->m_Poly->AppendCorner( x, y );
|
|
}
|
|
|
|
area_ref->m_Poly->Close();
|
|
booleng->EndPolygonGet();
|
|
}
|
|
|
|
if( !keep_area_to_combine )
|
|
RemoveArea( aDeletedList, area_to_combine );
|
|
|
|
area_ref->utility = 1;
|
|
area_ref->m_Poly->RestoreArcs( &arc_array1 );
|
|
area_ref->m_Poly->RestoreArcs( &arc_array2 );
|
|
area_ref->m_Poly->Draw();
|
|
delete booleng;
|
|
return 1;
|
|
}
|
|
|
|
|
|
int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_Examine,
|
|
bool aCreate_Markers )
|
|
{
|
|
wxString str;
|
|
int nerrors = 0;
|
|
|
|
// iterate through all areas
|
|
for( int ia = 0; ia < GetAreaCount(); ia++ )
|
|
{
|
|
ZONE_CONTAINER* Area_Ref = GetArea( ia );
|
|
CPolyLine* refSmoothedPoly = Area_Ref->GetSmoothedPoly();
|
|
|
|
if( !Area_Ref->IsOnCopperLayer() )
|
|
continue;
|
|
|
|
// If testing only a single area, then skip all others
|
|
if( aArea_To_Examine && (aArea_To_Examine != Area_Ref) )
|
|
continue;
|
|
|
|
for( int ia2 = 0; ia2 < GetAreaCount(); ia2++ )
|
|
{
|
|
ZONE_CONTAINER* Area_To_Test = GetArea( ia2 );
|
|
CPolyLine* testSmoothedPoly = Area_To_Test->GetSmoothedPoly();
|
|
|
|
if( Area_Ref == Area_To_Test )
|
|
continue;
|
|
|
|
// test for same layer
|
|
if( Area_Ref->GetLayer() != Area_To_Test->GetLayer() )
|
|
continue;
|
|
|
|
// Test for same net
|
|
if( Area_Ref->GetNet() == Area_To_Test->GetNet() && Area_Ref->GetNet() >= 0 )
|
|
continue;
|
|
|
|
// Examine a candidate zone: compare Area_To_Test to Area_Ref
|
|
|
|
// Get clearance used in zone to zone test. The policy used to
|
|
// obtain that value is now part of the zone object itself by way of
|
|
// ZONE_CONTAINER::GetClearance().
|
|
int zone2zoneClearance = Area_Ref->GetClearance( Area_To_Test );
|
|
|
|
// test for some corners of Area_Ref inside Area_To_Test
|
|
for( int ic = 0; ic < refSmoothedPoly->GetNumCorners(); ic++ )
|
|
{
|
|
int x = refSmoothedPoly->GetX( ic );
|
|
int y = refSmoothedPoly->GetY( ic );
|
|
|
|
if( testSmoothedPoly->TestPointInside( x, y ) )
|
|
{
|
|
// COPPERAREA_COPPERAREA error: copper area ref corner inside copper area
|
|
if( aCreate_Markers )
|
|
{
|
|
wxString msg1 = Area_Ref->GetSelectMenuText();
|
|
wxString msg2 = Area_To_Test->GetSelectMenuText();
|
|
MARKER_PCB* marker = new MARKER_PCB( COPPERAREA_INSIDE_COPPERAREA,
|
|
wxPoint( x, y ),
|
|
msg1, wxPoint( x, y ),
|
|
msg2, wxPoint( x, y ) );
|
|
Add( marker );
|
|
}
|
|
|
|
nerrors++;
|
|
}
|
|
}
|
|
|
|
// test for some corners of Area_To_Test inside Area_Ref
|
|
for( int ic2 = 0; ic2 < testSmoothedPoly->GetNumCorners(); ic2++ )
|
|
{
|
|
int x = testSmoothedPoly->GetX( ic2 );
|
|
int y = testSmoothedPoly->GetY( ic2 );
|
|
|
|
if( refSmoothedPoly->TestPointInside( x, y ) )
|
|
{
|
|
// COPPERAREA_COPPERAREA error: copper area corner inside copper area ref
|
|
if( aCreate_Markers )
|
|
{
|
|
wxString msg1 = Area_To_Test->GetSelectMenuText();
|
|
wxString msg2 = Area_Ref->GetSelectMenuText();
|
|
MARKER_PCB* marker = new MARKER_PCB( COPPERAREA_INSIDE_COPPERAREA,
|
|
wxPoint( x, y ),
|
|
msg1, wxPoint( x, y ),
|
|
msg2, wxPoint( x, y ) );
|
|
Add( marker );
|
|
}
|
|
|
|
nerrors++;
|
|
}
|
|
}
|
|
|
|
// now test spacing between areas
|
|
for( int icont = 0; icont < refSmoothedPoly->GetNumContours(); icont++ )
|
|
{
|
|
int ic_start = refSmoothedPoly->GetContourStart( icont );
|
|
int ic_end = refSmoothedPoly->GetContourEnd( icont );
|
|
|
|
for( int ic = ic_start; ic<=ic_end; ic++ )
|
|
{
|
|
int ax1 = refSmoothedPoly->GetX( ic );
|
|
int ay1 = refSmoothedPoly->GetY( ic );
|
|
int ax2, ay2;
|
|
|
|
if( ic == ic_end )
|
|
{
|
|
ax2 = refSmoothedPoly->GetX( ic_start );
|
|
ay2 = refSmoothedPoly->GetY( ic_start );
|
|
}
|
|
else
|
|
{
|
|
ax2 = refSmoothedPoly->GetX( ic + 1 );
|
|
ay2 = refSmoothedPoly->GetY( ic + 1 );
|
|
}
|
|
|
|
int astyle = refSmoothedPoly->GetSideStyle( ic );
|
|
|
|
for( int icont2 = 0; icont2 < testSmoothedPoly->GetNumContours(); icont2++ )
|
|
{
|
|
int ic_start2 = testSmoothedPoly->GetContourStart( icont2 );
|
|
int ic_end2 = testSmoothedPoly->GetContourEnd( icont2 );
|
|
|
|
for( int ic2 = ic_start2; ic2<=ic_end2; ic2++ )
|
|
{
|
|
int bx1 = testSmoothedPoly->GetX( ic2 );
|
|
int by1 = testSmoothedPoly->GetY( ic2 );
|
|
int bx2, by2;
|
|
|
|
if( ic2 == ic_end2 )
|
|
{
|
|
bx2 = testSmoothedPoly->GetX( ic_start2 );
|
|
by2 = testSmoothedPoly->GetY( ic_start2 );
|
|
}
|
|
else
|
|
{
|
|
bx2 = testSmoothedPoly->GetX( ic2 + 1 );
|
|
by2 = testSmoothedPoly->GetY( ic2 + 1 );
|
|
}
|
|
|
|
int bstyle = testSmoothedPoly->GetSideStyle( ic2 );
|
|
int x, y;
|
|
|
|
int d = GetClearanceBetweenSegments( bx1, by1, bx2, by2, bstyle,
|
|
0,
|
|
ax1, ay1, ax2,
|
|
ay2, astyle,
|
|
0,
|
|
zone2zoneClearance,
|
|
&x, &y );
|
|
|
|
if( d < zone2zoneClearance )
|
|
{
|
|
// COPPERAREA_COPPERAREA error : intersect or too close
|
|
if( aCreate_Markers )
|
|
{
|
|
wxString msg1 = Area_Ref->GetSelectMenuText();
|
|
wxString msg2 = Area_To_Test->GetSelectMenuText();
|
|
MARKER_PCB* marker = new MARKER_PCB( COPPERAREA_CLOSE_TO_COPPERAREA,
|
|
wxPoint( x, y ),
|
|
msg1, wxPoint( x, y ),
|
|
msg2, wxPoint( x, y ) );
|
|
Add( marker );
|
|
}
|
|
|
|
nerrors++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nerrors;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function doEdgeZoneDrc
|
|
* tests a segment in ZONE_CONTAINER * aArea:
|
|
* Test Edge inside other areas
|
|
* Test Edge too close other areas
|
|
* @param aArea The current area.
|
|
* @param aCornerIndex The first corner of the segment to test.
|
|
* @return bool - false if DRC error or true if OK
|
|
*/
|
|
|
|
bool DRC::doEdgeZoneDrc( ZONE_CONTAINER* aArea, int aCornerIndex )
|
|
{
|
|
if( !aArea->IsOnCopperLayer() ) // Cannot have a Drc error if not on copper layer
|
|
return true;
|
|
|
|
wxString str;
|
|
|
|
wxPoint start = aArea->GetCornerPosition( aCornerIndex );
|
|
wxPoint end;
|
|
|
|
// Search the end point of the edge starting at aCornerIndex
|
|
if( aArea->m_Poly->corner[aCornerIndex].end_contour == false
|
|
&& aCornerIndex < (aArea->GetNumCorners() - 1) )
|
|
{
|
|
end = aArea->GetCornerPosition( aCornerIndex + 1 );
|
|
}
|
|
else // aCornerIndex is the last corner of an outline.
|
|
// the corresponding end point of the segment is the first corner of the outline
|
|
{
|
|
int ii = aCornerIndex - 1;
|
|
end = aArea->GetCornerPosition( ii );
|
|
|
|
while( ii >= 0 )
|
|
{
|
|
if( aArea->m_Poly->corner[ii].end_contour )
|
|
break;
|
|
|
|
end = aArea->GetCornerPosition( ii );
|
|
ii--;
|
|
}
|
|
}
|
|
|
|
// iterate through all areas
|
|
for( int ia2 = 0; ia2 < m_pcb->GetAreaCount(); ia2++ )
|
|
{
|
|
ZONE_CONTAINER* Area_To_Test = m_pcb->GetArea( ia2 );
|
|
int zone_clearance = max( Area_To_Test->m_ZoneClearance,
|
|
aArea->m_ZoneClearance );
|
|
|
|
// test for same layer
|
|
if( Area_To_Test->GetLayer() != aArea->GetLayer() )
|
|
continue;
|
|
|
|
// Test for same net
|
|
if( ( aArea->GetNet() == Area_To_Test->GetNet() ) && (aArea->GetNet() >= 0) )
|
|
continue;
|
|
|
|
// test for ending line inside Area_To_Test
|
|
int x = end.x;
|
|
int y = end.y;
|
|
|
|
if( Area_To_Test->m_Poly->TestPointInside( x, y ) )
|
|
{
|
|
// COPPERAREA_COPPERAREA error: corner inside copper area
|
|
m_currentMarker = fillMarker( aArea, wxPoint( x, y ),
|
|
COPPERAREA_INSIDE_COPPERAREA,
|
|
m_currentMarker );
|
|
return false;
|
|
}
|
|
|
|
// now test spacing between areas
|
|
int astyle = CPolyLine::STRAIGHT;
|
|
int ax1 = start.x;
|
|
int ay1 = start.y;
|
|
int ax2 = end.x;
|
|
int ay2 = end.y;
|
|
|
|
for( int icont2 = 0; icont2 < Area_To_Test->m_Poly->GetNumContours(); icont2++ )
|
|
{
|
|
int ic_start2 = Area_To_Test->m_Poly->GetContourStart( icont2 );
|
|
int ic_end2 = Area_To_Test->m_Poly->GetContourEnd( icont2 );
|
|
|
|
for( int ic2 = ic_start2; ic2<=ic_end2; ic2++ )
|
|
{
|
|
int bx1 = Area_To_Test->m_Poly->GetX( ic2 );
|
|
int by1 = Area_To_Test->m_Poly->GetY( ic2 );
|
|
int bx2, by2;
|
|
|
|
if( ic2 == ic_end2 )
|
|
{
|
|
bx2 = Area_To_Test->m_Poly->GetX( ic_start2 );
|
|
by2 = Area_To_Test->m_Poly->GetY( ic_start2 );
|
|
}
|
|
else
|
|
{
|
|
bx2 = Area_To_Test->m_Poly->GetX( ic2 + 1 );
|
|
by2 = Area_To_Test->m_Poly->GetY( ic2 + 1 );
|
|
}
|
|
|
|
int bstyle = Area_To_Test->m_Poly->GetSideStyle( ic2 );
|
|
int x, y;
|
|
int d = GetClearanceBetweenSegments( bx1, by1, bx2, by2, bstyle,
|
|
0,
|
|
ax1, ay1, ax2, ay2, astyle,
|
|
0,
|
|
zone_clearance,
|
|
&x, &y );
|
|
|
|
if( d < zone_clearance )
|
|
{
|
|
// COPPERAREA_COPPERAREA error : edge intersect or too close
|
|
m_currentMarker = fillMarker( aArea, wxPoint( x, y ),
|
|
COPPERAREA_CLOSE_TO_COPPERAREA,
|
|
m_currentMarker );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|