2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004-2007 Jean-Pierre Charras, jean-pierre.charras@inpg.fr
|
|
|
|
* Copyright (C) 2007 Dick Hollenbeck, dick@softplc.com
|
|
|
|
* Copyright (C) 2007 Kicad Developers, see change_log.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
|
|
|
|
*/
|
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-08-08 20:51:08 +00:00
|
|
|
/****************************/
|
|
|
|
/* DRC control */
|
|
|
|
/****************************/
|
2007-06-05 12:10:51 +00:00
|
|
|
|
|
|
|
#include "fctsys.h"
|
|
|
|
#include "common.h"
|
|
|
|
#include "pcbnew.h"
|
2009-02-04 15:25:03 +00:00
|
|
|
#include "class_drawpanel.h"
|
2007-06-05 12:10:51 +00:00
|
|
|
#include "autorout.h"
|
|
|
|
#include "trigo.h"
|
2009-02-04 15:25:03 +00:00
|
|
|
#include "gestfich.h"
|
2007-06-05 12:10:51 +00:00
|
|
|
|
|
|
|
#include "protos.h"
|
|
|
|
|
2007-11-27 01:34:35 +00:00
|
|
|
#include "drc_stuff.h"
|
|
|
|
|
2009-06-18 13:30:52 +00:00
|
|
|
#include "dialog_drc.h"
|
2007-11-27 01:34:35 +00:00
|
|
|
|
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
/******************************************************/
|
|
|
|
void WinEDA_PcbFrame::Install_Test_DRC_Frame( wxDC* DC )
|
|
|
|
/******************************************************/
|
|
|
|
{
|
|
|
|
m_drc->ShowDialog();
|
|
|
|
}
|
2007-08-08 20:51:08 +00:00
|
|
|
|
2007-06-05 12:10:51 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
void DRC::ShowDialog()
|
|
|
|
{
|
|
|
|
if( !m_ui )
|
|
|
|
{
|
2009-06-18 13:30:52 +00:00
|
|
|
m_ui = new DIALOG_DRC_CONTROL( this, m_mainWindow );
|
2007-12-04 18:23:38 +00:00
|
|
|
updatePointers();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
// copy data retained in this DRC object into the m_ui DrcPanel:
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
PutValueInLocalUnits( *m_ui->m_SetClearance, g_DesignSettings.m_TrackClearence,
|
2009-03-23 19:54:15 +00:00
|
|
|
m_mainWindow->m_InternalUnits );;
|
2009-06-18 13:30:52 +00:00
|
|
|
PutValueInLocalUnits( *m_ui->m_SetTrackMinWidthCtrl, g_DesignSettings.m_TrackMinWidth,
|
|
|
|
m_mainWindow->m_InternalUnits );;
|
|
|
|
PutValueInLocalUnits( *m_ui->m_SetViaMinSizeCtrl, g_DesignSettings.m_ViasMinSize,
|
|
|
|
m_mainWindow->m_InternalUnits );;
|
|
|
|
PutValueInLocalUnits( *m_ui->m_SetMicroViakMinSizeCtrl, g_DesignSettings.m_MicroViasMinSize,
|
|
|
|
m_mainWindow->m_InternalUnits );;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
m_ui->m_CreateRptCtrl->SetValue( m_doCreateRptFile );
|
|
|
|
m_ui->m_RptFilenameCtrl->SetValue( m_rptFilename );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
updatePointers();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
m_ui->Show( true );
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
void DRC::DestroyDialog( int aReason )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2007-12-03 05:14:51 +00:00
|
|
|
if( m_ui )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2007-12-04 18:23:38 +00:00
|
|
|
if( aReason == wxID_OK )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
// if user clicked OK, save his choices in this DRC object.
|
2007-12-04 18:23:38 +00:00
|
|
|
m_doCreateRptFile = m_ui->m_CreateRptCtrl->GetValue();
|
|
|
|
m_rptFilename = m_ui->m_RptFilenameCtrl->GetValue();
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-03 05:14:51 +00:00
|
|
|
m_ui->Destroy();
|
|
|
|
m_ui = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-06-05 12:10:51 +00:00
|
|
|
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
DRC::DRC( WinEDA_PcbFrame* aPcbWindow )
|
|
|
|
{
|
|
|
|
m_mainWindow = aPcbWindow;
|
|
|
|
m_drawPanel = aPcbWindow->DrawPanel;
|
2009-03-23 19:54:15 +00:00
|
|
|
m_pcb = aPcbWindow->GetBoard();
|
|
|
|
m_ui = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// establish initial values for everything:
|
2009-06-27 06:26:41 +00:00
|
|
|
m_doPad2PadTest = true; // enable pad to pad clearance tests
|
|
|
|
m_doUnconnectedTest = true; // enable unconnected tests
|
|
|
|
m_doZonesTest = true; // enable zone to items clearance tests
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
m_doCreateRptFile = false;
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
// m_rptFilename set to empty by its constructor
|
|
|
|
|
|
|
|
m_currentMarker = 0;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_spotcx = 0;
|
|
|
|
m_spotcy = 0;
|
2009-03-23 19:54:15 +00:00
|
|
|
m_finx = 0;
|
|
|
|
m_finy = 0;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
m_segmAngle = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
m_segmLength = 0;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_xcliplo = 0;
|
|
|
|
m_ycliplo = 0;
|
|
|
|
m_xcliphi = 0;
|
|
|
|
m_ycliphi = 0;
|
|
|
|
|
|
|
|
m_drawPanel = 0;
|
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
DRC::~DRC()
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
// maybe someday look at pointainer.h <- google for "pointainer.h"
|
2009-03-23 19:54:15 +00:00
|
|
|
for( unsigned i = 0; i<m_unconnected.size(); ++i )
|
2007-12-04 18:23:38 +00:00
|
|
|
delete m_unconnected[i];
|
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2008-01-20 19:55:22 +00:00
|
|
|
/*********************************************/
|
2007-12-01 03:42:52 +00:00
|
|
|
int DRC::Drc( TRACK* aRefSegm, TRACK* aList )
|
2008-01-20 19:55:22 +00:00
|
|
|
/*********************************************/
|
2007-06-05 12:10:51 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
updatePointers();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( !doTrackDrc( aRefSegm, aList, true ) )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2007-12-01 05:37:44 +00:00
|
|
|
wxASSERT( m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-04-17 08:51:02 +00:00
|
|
|
m_currentMarker->DisplayInfo( m_mainWindow );
|
2007-12-01 03:42:52 +00:00
|
|
|
return BAD_DRC;
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
return OK_DRC;
|
|
|
|
}
|
2007-08-08 20:51:08 +00:00
|
|
|
|
|
|
|
|
2008-01-31 20:53:44 +00:00
|
|
|
/**************************************************************/
|
2009-03-23 19:54:15 +00:00
|
|
|
int DRC::Drc( ZONE_CONTAINER* aArea, int CornerIndex )
|
2008-01-31 20:53:44 +00:00
|
|
|
/*************************************************************/
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2008-01-20 19:55:22 +00:00
|
|
|
/**
|
|
|
|
* Function Drc
|
2008-01-31 20:53:44 +00:00
|
|
|
* tests the outline segment starting at CornerIndex and returns the result and displays the error
|
2008-01-20 19:55:22 +00:00
|
|
|
* in the status panel only if one exists.
|
|
|
|
* Test Edge inside other areas
|
|
|
|
* Test Edge too close other areas
|
2008-01-31 20:53:44 +00:00
|
|
|
* @param aEdge The areaparent which contains the corner.
|
|
|
|
* @param CornerIndex The starting point of the segment to test.
|
2008-01-20 19:55:22 +00:00
|
|
|
* @return int - BAD_DRC (1) if DRC error or OK_DRC (0) if OK
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
updatePointers();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
if( !doEdgeZoneDrc( aArea, CornerIndex ) )
|
2008-01-20 19:55:22 +00:00
|
|
|
{
|
|
|
|
wxASSERT( m_currentMarker );
|
2009-04-17 08:51:02 +00:00
|
|
|
m_currentMarker->DisplayInfo( m_mainWindow );
|
2008-01-20 19:55:22 +00:00
|
|
|
return BAD_DRC;
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-01-20 19:55:22 +00:00
|
|
|
return OK_DRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-18 13:30:52 +00:00
|
|
|
/**
|
|
|
|
* Function RunTests
|
|
|
|
* will actually run all the tests specified with a previous call to
|
|
|
|
* SetSettings()
|
|
|
|
*/
|
2009-06-27 06:26:41 +00:00
|
|
|
void DRC::RunTests(wxTextCtrl * aMessages)
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2009-02-26 09:33:14 +00:00
|
|
|
// Ensure ratsnest is up to date:
|
2009-05-24 18:28:36 +00:00
|
|
|
if( (m_pcb->m_Status_Pcb & LISTE_RATSNEST_ITEM_OK) == 0 )
|
2009-06-27 06:26:41 +00:00
|
|
|
{
|
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("Compile Ratsnest") );
|
2009-02-26 09:33:14 +00:00
|
|
|
m_mainWindow->Compile_Ratsnest( NULL, true );
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _(" Ok\n"));
|
|
|
|
}
|
2009-02-26 09:33:14 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
// someone should have cleared the two lists before calling this.
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
// test pad to pad clearances, nothing to do with tracks, vias or zones.
|
|
|
|
if( m_doPad2PadTest )
|
2009-06-27 06:26:41 +00:00
|
|
|
{
|
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("Test pads to pads clearance") );
|
2007-12-01 03:42:52 +00:00
|
|
|
testPad2Pad();
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("\n"));
|
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2007-12-01 05:37:44 +00:00
|
|
|
// test track and via clearances to other tracks, pads, and vias
|
2007-12-01 03:42:52 +00:00
|
|
|
testTracks();
|
|
|
|
|
2008-12-04 20:53:11 +00:00
|
|
|
// Before testing segments and unconnected, refill all zones:
|
2009-02-26 09:33:14 +00:00
|
|
|
// this is a good caution, because filled areas can be outdated.
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("Fill zones") );
|
2008-12-04 20:53:11 +00:00
|
|
|
m_mainWindow->Fill_All_Zones( false );
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _(" Ok\n"));
|
2008-12-04 20:53:11 +00:00
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// test zone clearances to other zones, pads, tracks, and vias
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages && m_doZonesTest)
|
|
|
|
aMessages->AppendText( _("Test zones") );
|
2008-03-18 04:04:17 +00:00
|
|
|
testZones( m_doZonesTest );
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages && m_doZonesTest)
|
|
|
|
aMessages->AppendText( _("\n"));
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// find and gather unconnected pads.
|
2007-12-01 03:42:52 +00:00
|
|
|
if( m_doUnconnectedTest )
|
2009-06-27 06:26:41 +00:00
|
|
|
{
|
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("List unconnected pads") );
|
2007-12-01 03:42:52 +00:00
|
|
|
testUnconnected();
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("\n"));
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
// update the m_ui listboxes
|
2007-12-03 05:14:51 +00:00
|
|
|
updatePointers();
|
2009-06-27 06:26:41 +00:00
|
|
|
if ( aMessages )
|
|
|
|
aMessages->AppendText( _("Finished\n") );
|
2007-12-03 05:14:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************/
|
|
|
|
void DRC::ListUnconnectedPads()
|
|
|
|
/***************************************************************/
|
|
|
|
{
|
|
|
|
testUnconnected();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
// update the m_ui listboxes
|
|
|
|
updatePointers();
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-03 05:14:51 +00:00
|
|
|
void DRC::updatePointers()
|
|
|
|
{
|
|
|
|
// update my pointers, m_mainWindow is the only unchangable one
|
|
|
|
m_drawPanel = m_mainWindow->DrawPanel;
|
2009-01-05 05:21:35 +00:00
|
|
|
m_pcb = m_mainWindow->GetBoard();
|
2007-12-04 18:23:38 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
if( m_ui ) // Use diag list boxes only in DRC dialog
|
2008-02-09 08:34:45 +00:00
|
|
|
{
|
|
|
|
m_ui->m_ClearanceListBox->SetList( new DRC_LIST_MARKERS( m_pcb ) );
|
|
|
|
|
|
|
|
m_ui->m_UnconnectedListBox->SetList( new DRC_LIST_UNCONNECTED( &m_unconnected ) );
|
|
|
|
}
|
2007-12-03 05:14:51 +00:00
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-03 05:14:51 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
void DRC::testTracks()
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
for( TRACK* segm = m_pcb->m_Track; segm && segm->Next(); segm = segm->Next() )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2008-12-04 04:28:11 +00:00
|
|
|
if( !doTrackDrc( segm, segm->Next(), true ) )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
wxASSERT( m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
m_pcb->Add( m_currentMarker );
|
|
|
|
m_currentMarker = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
/***********************/
|
2007-12-01 03:42:52 +00:00
|
|
|
void DRC::testPad2Pad()
|
2009-03-23 19:54:15 +00:00
|
|
|
/***********************/
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-03-18 04:04:17 +00:00
|
|
|
std::vector<D_PAD*> sortedPads;
|
|
|
|
|
|
|
|
CreateSortedPadListByXCoord( m_pcb, &sortedPads );
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
// find the max size of the pads (used to stop the test)
|
2009-03-23 19:54:15 +00:00
|
|
|
int max_size = 0;
|
2008-03-18 04:04:17 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
for( unsigned i = 0; i<sortedPads.size(); ++i )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-03-18 04:04:17 +00:00
|
|
|
D_PAD* pad = sortedPads[i];
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
if( pad->m_Rayon > max_size ) // m_Rayon is the radius value of the circle containing the pad
|
2007-12-01 03:42:52 +00:00
|
|
|
max_size = pad->m_Rayon;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test the pads
|
2008-03-18 04:04:17 +00:00
|
|
|
D_PAD** listEnd = &sortedPads[ sortedPads.size() ];
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
for( unsigned i = 0; i<sortedPads.size(); ++i )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-03-18 04:04:17 +00:00
|
|
|
D_PAD* pad = sortedPads[i];
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-03-18 04:04:17 +00:00
|
|
|
if( !doPadToPadsDrc( pad, &sortedPads[i], listEnd, max_size ) )
|
2007-11-27 01:34:35 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
wxASSERT( m_currentMarker );
|
|
|
|
m_pcb->Add( m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = 0;
|
2007-11-27 01:34:35 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-11-27 01:34:35 +00:00
|
|
|
|
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
void DRC::testUnconnected()
|
|
|
|
{
|
2009-05-24 18:28:36 +00:00
|
|
|
if( (m_pcb->m_Status_Pcb & LISTE_RATSNEST_ITEM_OK) == 0 )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
wxClientDC dc( m_mainWindow->DrawPanel );
|
|
|
|
m_mainWindow->Compile_Ratsnest( &dc, TRUE );
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-05-28 08:42:24 +00:00
|
|
|
if( m_pcb->GetRatsnestsCount() == 0 )
|
2007-12-01 03:42:52 +00:00
|
|
|
return;
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2009-05-28 08:42:24 +00:00
|
|
|
for( unsigned ii = 0; ii < m_pcb->GetRatsnestsCount(); ++ii )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2009-05-28 08:42:24 +00:00
|
|
|
RATSNEST_ITEM* rat = &m_pcb->m_FullRatsnest[ii];
|
2009-05-24 18:28:36 +00:00
|
|
|
if( (rat->m_Status & CH_ACTIF) == 0 )
|
2007-12-01 03:42:52 +00:00
|
|
|
continue;
|
2007-08-08 20:51:08 +00:00
|
|
|
|
2009-05-24 18:28:36 +00:00
|
|
|
D_PAD* padStart = rat->m_PadStart;
|
|
|
|
D_PAD* padEnd = rat->m_PadEnd;
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2009-07-06 18:02:26 +00:00
|
|
|
DRC_ITEM* uncItem = new DRC_ITEM( DRCE_UNCONNECTED_PADS,
|
2009-03-23 19:54:15 +00:00
|
|
|
padStart->MenuText( m_pcb ), padEnd->MenuText( m_pcb ),
|
|
|
|
padStart->GetPosition(), padEnd->GetPosition() );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
m_unconnected.push_back( uncItem );
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2009-01-24 19:30:39 +00:00
|
|
|
/**********************************************/
|
2009-03-23 19:54:15 +00:00
|
|
|
void DRC::testZones( bool adoTestFillSegments )
|
2009-01-24 19:30:39 +00:00
|
|
|
/**********************************************/
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
// Test copper areas for valide netcodes
|
|
|
|
// if a netcode is < 0 the netname was not found when reading a netlist
|
2009-01-24 19:30:39 +00:00
|
|
|
// if a netcode is == 0 the netname is void, and the zone is not connected.
|
|
|
|
// This is allowed, but i am not sure this is a good idea
|
2008-02-09 08:34:45 +00:00
|
|
|
for( int ii = 0; ii < m_pcb->GetAreaCount(); ii++ )
|
|
|
|
{
|
|
|
|
ZONE_CONTAINER* Area_To_Test = m_pcb->GetArea( ii );
|
2009-03-23 19:54:15 +00:00
|
|
|
if( !Area_To_Test->IsOnCopperLayer() )
|
2008-09-26 19:51:36 +00:00
|
|
|
continue;
|
2009-01-24 19:30:39 +00:00
|
|
|
if( Area_To_Test->GetNet() < 0 )
|
2008-02-09 08:34:45 +00:00
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( Area_To_Test,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_NON_EXISTANT_NET_FOR_ZONE_OUTLINE, m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
m_pcb->Add( m_currentMarker );
|
|
|
|
m_currentMarker = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test copper areas outlines, and create markers when needed
|
|
|
|
m_pcb->Test_Drc_Areas_Outlines_To_Areas_Outlines( NULL, true );
|
2008-01-10 20:53:41 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
TRACK* zoneSeg;
|
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( !adoTestFillSegments )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for( zoneSeg = m_pcb->m_Zone; zoneSeg && zoneSeg->Next(); zoneSeg = zoneSeg->Next() )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
// Test zoneSeg with other zone segments and with all pads
|
2008-12-04 04:28:11 +00:00
|
|
|
if( !doTrackDrc( zoneSeg, zoneSeg->Next(), true ) )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
wxASSERT( m_currentMarker );
|
|
|
|
m_pcb->Add( m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
// Pads already tested: disable pad test
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
bool rc = doTrackDrc( zoneSeg, m_pcb->m_Track, false );
|
2007-12-01 03:42:52 +00:00
|
|
|
if( !rc )
|
|
|
|
{
|
|
|
|
wxASSERT( m_currentMarker );
|
|
|
|
m_pcb->Add( m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
2007-08-08 20:51:08 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
MARKER* DRC::fillMarker( TRACK* aTrack, BOARD_ITEM* aItem, int aErrorCode, MARKER* fillMe )
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
wxString textA = aTrack->MenuText( m_pcb );
|
|
|
|
wxString textB;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
wxPoint position;
|
|
|
|
wxPoint posB;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 20:44:41 +00:00
|
|
|
if( aItem ) // aItem might be NULL
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2007-12-04 20:44:41 +00:00
|
|
|
textB = aItem->MenuText( m_pcb );
|
|
|
|
posB = aItem->GetPosition();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( aItem->Type() == TYPE_PAD )
|
2007-12-04 20:44:41 +00:00
|
|
|
position = aItem->GetPosition();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
else if( aItem->Type() == TYPE_VIA )
|
2007-12-04 20:44:41 +00:00
|
|
|
position = aItem->GetPosition();
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
else if( aItem->Type() == TYPE_TRACK )
|
2007-12-04 20:44:41 +00:00
|
|
|
{
|
|
|
|
TRACK* track = (TRACK*) aItem;
|
|
|
|
wxPoint endPos = track->m_End;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 20:44:41 +00:00
|
|
|
// either of aItem's start or end will be used for the marker position
|
|
|
|
// first assume start, then switch at end if needed. decision made on
|
|
|
|
// distance from end of aTrack.
|
|
|
|
position = track->m_Start;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
double dToEnd = hypot( endPos.x - aTrack->m_End.x,
|
|
|
|
endPos.y - aTrack->m_End.y );
|
2007-12-04 20:44:41 +00:00
|
|
|
double dToStart = hypot( position.x - aTrack->m_End.x,
|
|
|
|
position.y - aTrack->m_End.y );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-04 20:44:41 +00:00
|
|
|
if( dToEnd < dToStart )
|
|
|
|
position = endPos;
|
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
2007-12-04 20:44:41 +00:00
|
|
|
else
|
2008-02-09 08:34:45 +00:00
|
|
|
position = aTrack->GetPosition();
|
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
if( fillMe )
|
2008-02-09 08:34:45 +00:00
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
if( aItem )
|
2008-02-09 08:34:45 +00:00
|
|
|
fillMe->SetData( aErrorCode, position,
|
2009-03-23 19:54:15 +00:00
|
|
|
textA, aTrack->GetPosition(),
|
|
|
|
textB, posB );
|
2008-02-09 08:34:45 +00:00
|
|
|
else
|
|
|
|
fillMe->SetData( aErrorCode, position,
|
2008-01-12 20:31:56 +00:00
|
|
|
textA, aTrack->GetPosition() );
|
2008-02-09 08:34:45 +00:00
|
|
|
}
|
2007-08-08 20:51:08 +00:00
|
|
|
else
|
2008-02-09 08:34:45 +00:00
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
if( aItem )
|
2008-02-09 08:34:45 +00:00
|
|
|
fillMe = new MARKER( aErrorCode, position,
|
2009-03-23 19:54:15 +00:00
|
|
|
textA, aTrack->GetPosition(),
|
|
|
|
textB, posB );
|
2008-02-09 08:34:45 +00:00
|
|
|
else
|
|
|
|
fillMe = new MARKER( aErrorCode, position,
|
2009-03-23 19:54:15 +00:00
|
|
|
textA, aTrack->GetPosition() );
|
2008-02-09 08:34:45 +00:00
|
|
|
}
|
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
return fillMe;
|
|
|
|
}
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
MARKER* DRC::fillMarker( D_PAD* aPad, D_PAD* bPad, int aErrorCode, MARKER* fillMe )
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
wxString textA = aPad->MenuText( m_pcb );
|
|
|
|
wxString textB = bPad->MenuText( m_pcb );
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
wxPoint posA = aPad->GetPosition();
|
|
|
|
wxPoint posB = bPad->GetPosition();
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
if( fillMe )
|
|
|
|
fillMe->SetData( aErrorCode, posA, textA, posA, textB, posB );
|
|
|
|
else
|
|
|
|
fillMe = new MARKER( aErrorCode, posA, textA, posA, textB, posB );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
return fillMe;
|
2007-06-05 12:10:51 +00:00
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
|
|
|
MARKER* DRC::fillMarker( ZONE_CONTAINER* aArea, int aErrorCode, MARKER* fillMe )
|
2008-01-20 19:55:22 +00:00
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
wxString textA = aArea->MenuText( m_pcb );
|
2008-01-20 19:55:22 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
wxPoint posA = aArea->GetPosition();
|
2008-01-20 19:55:22 +00:00
|
|
|
|
|
|
|
if( fillMe )
|
|
|
|
fillMe->SetData( aErrorCode, posA, textA, posA );
|
|
|
|
else
|
|
|
|
fillMe = new MARKER( aErrorCode, posA, textA, posA );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-01-20 19:55:22 +00:00
|
|
|
return fillMe;
|
|
|
|
}
|
|
|
|
|
2008-01-31 20:53:44 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
MARKER* DRC::fillMarker( const ZONE_CONTAINER* aArea,
|
|
|
|
const wxPoint& aPos,
|
|
|
|
int aErrorCode,
|
|
|
|
MARKER* fillMe )
|
2008-01-20 19:55:22 +00:00
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
wxString textA = aArea->MenuText( m_pcb );
|
2008-01-20 19:55:22 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
wxPoint posA = aPos;
|
2008-01-20 19:55:22 +00:00
|
|
|
|
|
|
|
if( fillMe )
|
|
|
|
fillMe->SetData( aErrorCode, posA, textA, posA );
|
|
|
|
else
|
|
|
|
fillMe = new MARKER( aErrorCode, posA, textA, posA );
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-01-20 19:55:22 +00:00
|
|
|
return fillMe;
|
|
|
|
}
|
|
|
|
|
2007-06-05 12:10:51 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
/***********************************************************************/
|
2008-12-04 04:28:11 +00:00
|
|
|
bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart, bool testPads )
|
2007-12-01 03:42:52 +00:00
|
|
|
/***********************************************************************/
|
2007-06-05 12:10:51 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
TRACK* track;
|
|
|
|
int dx, dy; // utilise pour calcul des dim x et dim y des segments
|
|
|
|
int w_dist;
|
|
|
|
int layerMask;
|
|
|
|
int net_code_ref;
|
|
|
|
int org_X, org_Y; // Origine sur le PCB des axes du repere centre sur
|
|
|
|
// l'origine du segment de reference
|
|
|
|
wxPoint shape_pos;
|
2007-08-08 20:51:08 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
org_X = aRefSeg->m_Start.x;
|
|
|
|
org_Y = aRefSeg->m_Start.y;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
m_finx = dx = aRefSeg->m_End.x - org_X;
|
|
|
|
m_finy = dy = aRefSeg->m_End.y - org_Y;
|
|
|
|
|
|
|
|
layerMask = aRefSeg->ReturnMaskLayer();
|
|
|
|
net_code_ref = aRefSeg->GetNet();
|
|
|
|
|
|
|
|
m_segmAngle = 0;
|
2007-12-04 20:44:41 +00:00
|
|
|
|
|
|
|
|
2008-01-12 20:31:56 +00:00
|
|
|
/* Phase 0 : Test vias : */
|
2008-12-04 04:28:11 +00:00
|
|
|
if( aRefSeg->Type() == TYPE_VIA )
|
2007-12-04 20:44:41 +00:00
|
|
|
{
|
2009-06-18 13:30:52 +00:00
|
|
|
// test if the via size is bigger thn min size allowed
|
|
|
|
if( aRefSeg->Shape() == VIA_MICROVIA )
|
|
|
|
{
|
|
|
|
if(aRefSeg->m_Width < g_DesignSettings.m_MicroViasMinSize)
|
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, NULL,
|
|
|
|
DRCE_TOO_SMALL_MICROVIA, m_currentMarker );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(aRefSeg->m_Width < g_DesignSettings.m_ViasMinSize)
|
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, NULL,
|
|
|
|
DRCE_TOO_SMALL_VIA, m_currentMarker );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
// test if via's hole is bigger than its diameter
|
2009-03-23 19:54:15 +00:00
|
|
|
// This test is necessary since the via hole size and width can be modified
|
|
|
|
// and an default via hole can be bigger than some vias sizes
|
2008-02-23 04:53:44 +00:00
|
|
|
if( aRefSeg->GetDrillValue() > aRefSeg->m_Width )
|
2007-12-04 20:44:41 +00:00
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, NULL,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_VIA_HOLE_BIGGER, m_currentMarker );
|
2007-12-04 20:44:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
// For microvias: test if they are blind vias and only between 2 layers
|
2008-02-09 08:34:45 +00:00
|
|
|
// because they are used for very small drill size and are drill by laser
|
|
|
|
// and **only** one layer can be drilled
|
2008-01-12 20:31:56 +00:00
|
|
|
if( aRefSeg->Shape() == VIA_MICROVIA )
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
int layer1, layer2;
|
2008-02-09 08:34:45 +00:00
|
|
|
bool err = true;
|
2009-03-23 19:54:15 +00:00
|
|
|
( (SEGVIA*) aRefSeg )->ReturnLayerPair( &layer1, &layer2 );
|
|
|
|
if( layer1> layer2 )
|
|
|
|
EXCHG( layer1, layer2 );
|
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// test:
|
2009-03-23 19:54:15 +00:00
|
|
|
if( layer1 == COPPER_LAYER_N && layer2 == LAYER_N_2 )
|
|
|
|
err = false;
|
|
|
|
if( layer1 == (g_DesignSettings.m_CopperLayerCount - 2 ) && layer2 == LAYER_CMP_N )
|
|
|
|
err = false;
|
|
|
|
if( err )
|
2008-02-09 08:34:45 +00:00
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, NULL,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_MICRO_VIA_INCORRECT_LAYER_PAIR, m_currentMarker );
|
2008-02-09 08:34:45 +00:00
|
|
|
return false;
|
|
|
|
}
|
2008-01-12 20:31:56 +00:00
|
|
|
}
|
2007-12-04 20:44:41 +00:00
|
|
|
}
|
2009-06-18 13:30:52 +00:00
|
|
|
else // This is a track segment
|
|
|
|
{
|
|
|
|
if(aRefSeg->m_Width < g_DesignSettings.m_TrackMinWidth)
|
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, NULL,
|
|
|
|
DRCE_TOO_SMALL_TRACK_WIDTH, m_currentMarker );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
|
|
|
// for a non horizontal or vertical segment Compute the segment angle
|
|
|
|
// in tenths of degrees and its length
|
2007-12-01 03:42:52 +00:00
|
|
|
if( dx || dy )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
// Compute the segment angle in 0,1 degrees
|
|
|
|
m_segmAngle = ArcTangente( dy, dx );
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
// Compute the segment length: we build an equivalent rotated segment,
|
2008-02-09 08:34:45 +00:00
|
|
|
// this segment is horizontal, therefore dx = length
|
|
|
|
RotatePoint( &dx, &dy, m_segmAngle ); // dx = length, dy = 0
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
2007-11-27 01:34:35 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_segmLength = dx;
|
|
|
|
|
|
|
|
/******************************************/
|
|
|
|
/* Phase 1 : test DRC track to pads : */
|
|
|
|
/******************************************/
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 05:37:44 +00:00
|
|
|
D_PAD pseudo_pad( (MODULE*) NULL ); // construct this once outside following loop
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
// Compute the min distance to pads
|
|
|
|
w_dist = aRefSeg->m_Width >> 1;
|
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( testPads )
|
|
|
|
{
|
2009-06-06 18:08:49 +00:00
|
|
|
for( unsigned ii = 0; ii<m_pcb->GetPadsCount(); ++ii )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2009-06-11 14:26:17 +00:00
|
|
|
D_PAD* pad = m_pcb->m_NetInfo->GetPad( ii );
|
2008-12-04 04:28:11 +00:00
|
|
|
|
|
|
|
/* No problem if pads are on an other layer,
|
|
|
|
* But if a drill hole exists (a pad on a single layer can have a hole!)
|
|
|
|
* we must test the hole
|
2007-12-01 03:42:52 +00:00
|
|
|
*/
|
2008-12-04 04:28:11 +00:00
|
|
|
if( (pad->m_Masque_Layer & layerMask ) == 0 )
|
|
|
|
{
|
|
|
|
/* We must test the pad hole. In order to use the function "checkClearanceSegmToPad",
|
|
|
|
* a pseudo pad is used, with a shape and a size like the hole
|
|
|
|
*/
|
|
|
|
if( pad->m_Drill.x == 0 )
|
|
|
|
continue;
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
pseudo_pad.m_Size = pad->m_Drill;
|
2008-12-04 04:28:11 +00:00
|
|
|
pseudo_pad.SetPosition( pad->GetPosition() );
|
|
|
|
pseudo_pad.m_PadShape = pad->m_DrillShape;
|
|
|
|
pseudo_pad.m_Orient = pad->m_Orient;
|
|
|
|
pseudo_pad.ComputeRayon(); // compute the radius
|
|
|
|
|
|
|
|
m_spotcx = pseudo_pad.GetPosition().x - org_X;
|
|
|
|
m_spotcy = pseudo_pad.GetPosition().y - org_Y;
|
|
|
|
|
|
|
|
if( !checkClearanceSegmToPad( &pseudo_pad, w_dist,
|
2009-03-23 19:54:15 +00:00
|
|
|
g_DesignSettings.m_TrackClearence ) )
|
2008-12-04 04:28:11 +00:00
|
|
|
{
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, pad,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_NEAR_THROUGH_HOLE, m_currentMarker );
|
2008-12-04 04:28:11 +00:00
|
|
|
return false;
|
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
continue;
|
2008-12-04 04:28:11 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
/* The pad must be in a net (i.e pt_pad->GetNet() != 0 )
|
|
|
|
* but no problem if the pad netcode is the current netcode (same net)
|
|
|
|
*/
|
2009-03-23 19:54:15 +00:00
|
|
|
if( pad->GetNet() // the pad must be connected
|
|
|
|
&& net_code_ref == pad->GetNet() ) // the pad net is the same as current net -> Ok
|
2008-12-04 04:28:11 +00:00
|
|
|
continue;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
// DRC for the pad
|
|
|
|
shape_pos = pad->ReturnShapePos();
|
2009-03-23 19:54:15 +00:00
|
|
|
m_spotcx = shape_pos.x - org_X;
|
|
|
|
m_spotcy = shape_pos.y - org_Y;
|
2008-12-04 04:28:11 +00:00
|
|
|
if( !checkClearanceSegmToPad( pad, w_dist, g_DesignSettings.m_TrackClearence ) )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, pad,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_NEAR_PAD, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************/
|
|
|
|
/* Phase 2: test DRC with other track segments */
|
|
|
|
/***********************************************/
|
|
|
|
|
|
|
|
// At this point the reference segment is the X axis
|
|
|
|
|
|
|
|
// Test the reference segment with other track segments
|
2009-03-23 19:54:15 +00:00
|
|
|
for( track = aStart; track; track = track->Next() )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
// coord des extremites du segment teste dans le repere modifie
|
|
|
|
int x0;
|
|
|
|
int y0;
|
|
|
|
int xf;
|
2008-02-09 08:34:45 +00:00
|
|
|
int yf;
|
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
// No problem if segments have the same net code:
|
|
|
|
if( net_code_ref == track->GetNet() )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// No problem if segment are on different layers :
|
|
|
|
if( ( layerMask & track->ReturnMaskLayer() ) == 0 )
|
|
|
|
continue;
|
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// the minimum distance = clearance plus half the reference track
|
2007-12-01 03:42:52 +00:00
|
|
|
// width plus half the other track's width
|
|
|
|
w_dist = aRefSeg->m_Width >> 1;
|
|
|
|
w_dist += track->m_Width >> 1;
|
|
|
|
w_dist += g_DesignSettings.m_TrackClearence;
|
|
|
|
|
|
|
|
// If the reference segment is a via, we test it here
|
2008-12-04 04:28:11 +00:00
|
|
|
if( aRefSeg->Type() == TYPE_VIA )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
int orgx, orgy; // origine du repere d'axe X = segment a comparer
|
|
|
|
int angle = 0; // angle du segment a tester;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
|
|
|
orgx = track->m_Start.x;
|
2007-12-01 03:42:52 +00:00
|
|
|
orgy = track->m_Start.y;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
|
|
|
dx = track->m_End.x - orgx;
|
2007-12-01 03:42:52 +00:00
|
|
|
dy = track->m_End.y - orgy;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
|
|
|
x0 = aRefSeg->m_Start.x - orgx;
|
2007-12-01 03:42:52 +00:00
|
|
|
y0 = aRefSeg->m_Start.y - orgy;
|
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( track->Type() == TYPE_VIA )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
// Test distance between two vias
|
2007-12-01 03:42:52 +00:00
|
|
|
if( (int) hypot( x0, y0 ) < w_dist )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_VIA_NEAR_VIA, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // test via to segment
|
|
|
|
{
|
|
|
|
// Compute l'angle
|
|
|
|
angle = ArcTangente( dy, dx );
|
|
|
|
|
|
|
|
// Compute new coordinates ( the segment become horizontal)
|
|
|
|
RotatePoint( &dx, &dy, angle );
|
|
|
|
RotatePoint( &x0, &y0, angle );
|
|
|
|
|
|
|
|
if( !checkMarginToCircle( x0, y0, w_dist, dx ) )
|
|
|
|
{
|
2008-02-26 21:12:31 +00:00
|
|
|
m_currentMarker = fillMarker( track, aRefSeg,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_VIA_NEAR_TRACK, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-02-26 21:12:31 +00:00
|
|
|
/* We compute x0,y0, xf,yf = starting and ending point coordinates for
|
|
|
|
* the segment to test in the new axis : the new X axis is the
|
|
|
|
* reference segment. We must translate and rotate the segment to test
|
|
|
|
*/
|
2007-12-01 03:42:52 +00:00
|
|
|
x0 = track->m_Start.x - org_X;
|
|
|
|
y0 = track->m_Start.y - org_Y;
|
|
|
|
|
|
|
|
xf = track->m_End.x - org_X;
|
|
|
|
yf = track->m_End.y - org_Y;
|
|
|
|
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
RotatePoint( &xf, &yf, m_segmAngle );
|
|
|
|
|
2008-12-04 04:28:11 +00:00
|
|
|
if( track->Type() == TYPE_VIA )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
if( checkMarginToCircle( x0, y0, w_dist, m_segmLength ) )
|
|
|
|
continue;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_NEAR_VIA, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* We have changed axis:
|
|
|
|
* the reference segment is Horizontal.
|
|
|
|
* 3 cases : the segment to test can be parallel, perpendicular or have an other direction
|
|
|
|
*/
|
|
|
|
if( y0 == yf ) // parallel segments
|
|
|
|
{
|
|
|
|
if( abs( y0 ) >= w_dist )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if( x0 > xf )
|
2009-03-23 19:54:15 +00:00
|
|
|
EXCHG( x0, xf ); /* pour que x0 <= xf */
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
if( x0 > (-w_dist) && x0 < (m_segmLength + w_dist) ) /* possible error drc */
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
/* Fine test : we consider the rounded shape of the ends */
|
|
|
|
if( x0 >= 0 && x0 <= m_segmLength )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_ENDS1, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if( !checkMarginToCircle( x0, y0, w_dist, m_segmLength ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_ENDS2, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( xf > (-w_dist) && xf < (m_segmLength + w_dist) )
|
|
|
|
{
|
|
|
|
/* Fine test : we consider the rounded shape of the ends */
|
|
|
|
if( xf >= 0 && xf <= m_segmLength )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_ENDS3, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if( !checkMarginToCircle( xf, yf, w_dist, m_segmLength ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_ENDS4, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( x0 <=0 && xf >= 0 )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACK_UNKNOWN1, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( x0 == xf ) // perpendicular segments
|
|
|
|
{
|
|
|
|
if( ( x0 <= (-w_dist) ) || ( x0 >= (m_segmLength + w_dist) ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Test if segments are crossing
|
|
|
|
if( y0 > yf )
|
|
|
|
EXCHG( y0, yf );
|
|
|
|
if( (y0 < 0) && (yf > 0) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_TRACKS_CROSSING, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point the drc error is due to an end near a reference segm end
|
|
|
|
if( !checkMarginToCircle( x0, y0, w_dist, m_segmLength ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_ENDS_PROBLEM1, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if( !checkMarginToCircle( xf, yf, w_dist, m_segmLength ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_ENDS_PROBLEM2, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // segments quelconques entre eux */
|
|
|
|
{
|
|
|
|
// calcul de la "surface de securite du segment de reference
|
|
|
|
// First rought 'and fast) test : the track segment is like a rectangle
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_xcliplo = m_ycliplo = -w_dist;
|
2008-02-09 08:34:45 +00:00
|
|
|
m_xcliphi = m_segmLength + w_dist;
|
2007-12-01 03:42:52 +00:00
|
|
|
m_ycliphi = w_dist;
|
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
// A fine test is needed because a serment is not exactly a
|
2007-12-01 03:42:52 +00:00
|
|
|
// rectangle, it has rounded ends
|
|
|
|
if( !checkLine( x0, y0, xf, yf ) )
|
|
|
|
{
|
|
|
|
/* 2eme passe : the track has rounded ends.
|
2008-02-09 08:34:45 +00:00
|
|
|
* we must a fine test for each rounded end and the
|
|
|
|
* rectangular zone
|
2007-12-01 03:42:52 +00:00
|
|
|
*/
|
|
|
|
|
2008-02-09 08:34:45 +00:00
|
|
|
m_xcliplo = 0;
|
2007-12-01 03:42:52 +00:00
|
|
|
m_xcliphi = m_segmLength;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
if( !checkLine( x0, y0, xf, yf ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_ENDS_PROBLEM3, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else // The drc error is due to the starting or the ending point of the reference segment
|
|
|
|
{
|
|
|
|
// Test the starting and the ending point
|
|
|
|
int angle, rx0, ry0, rxf, ryf;
|
|
|
|
x0 = track->m_Start.x;
|
|
|
|
y0 = track->m_Start.y;
|
|
|
|
|
|
|
|
xf = track->m_End.x;
|
|
|
|
yf = track->m_End.y;
|
|
|
|
|
|
|
|
dx = xf - x0;
|
|
|
|
dy = yf - y0;
|
|
|
|
|
|
|
|
/* Compute the segment orientation (angle) en 0,1 degre */
|
|
|
|
angle = ArcTangente( dy, dx );
|
|
|
|
|
|
|
|
/* Compute the segment lenght: dx = longueur */
|
|
|
|
RotatePoint( &dx, &dy, angle );
|
|
|
|
|
|
|
|
/* Comute the reference segment coordinates relatives to a
|
2008-02-09 08:34:45 +00:00
|
|
|
* X axis = current tested segment
|
2007-12-01 03:42:52 +00:00
|
|
|
*/
|
|
|
|
rx0 = aRefSeg->m_Start.x - x0;
|
|
|
|
ry0 = aRefSeg->m_Start.y - y0;
|
|
|
|
rxf = aRefSeg->m_End.x - x0;
|
|
|
|
ryf = aRefSeg->m_End.y - y0;
|
|
|
|
|
|
|
|
RotatePoint( &rx0, &ry0, angle );
|
|
|
|
RotatePoint( &rxf, &ryf, angle );
|
|
|
|
if( !checkMarginToCircle( rx0, ry0, w_dist, dx ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_ENDS_PROBLEM4, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if( !checkMarginToCircle( rxf, ryf, w_dist, dx ) )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefSeg, track,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_ENDS_PROBLEM5, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
bool DRC::doPadToPadsDrc( D_PAD* aRefPad, LISTE_PAD* aStart, LISTE_PAD* aEnd,
|
2009-03-23 19:54:15 +00:00
|
|
|
int max_size )
|
2007-12-01 03:42:52 +00:00
|
|
|
/*****************************************************************************/
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
int layerMask = aRefPad->m_Masque_Layer & ALL_CU_LAYERS;
|
|
|
|
|
|
|
|
int x_limite = max_size + g_DesignSettings.m_TrackClearence +
|
|
|
|
aRefPad->m_Rayon + aRefPad->GetPosition().x;
|
|
|
|
|
|
|
|
static D_PAD dummypad( (MODULE*) NULL ); // used to test DRC pad to holes: this dummypad is the hole to test
|
|
|
|
|
|
|
|
dummypad.m_Masque_Layer = ALL_CU_LAYERS;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
for( LISTE_PAD* pad_list = aStart; pad_list<aEnd; ++pad_list )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
D_PAD* pad = *pad_list;
|
|
|
|
if( pad == aRefPad )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* We can stop the test when pad->m_Pos.x > x_limite
|
|
|
|
* because the list is sorted by X values */
|
|
|
|
if( pad->m_Pos.x > x_limite )
|
|
|
|
break;
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
|
|
|
/* No problem if pads are on different copper layers,
|
|
|
|
* but their hole (if any ) can create RDC error because they are on all copper layers, so we test them
|
|
|
|
*/
|
2007-12-01 03:42:52 +00:00
|
|
|
if( (pad->m_Masque_Layer & layerMask ) == 0 )
|
2009-03-23 19:54:15 +00:00
|
|
|
{
|
|
|
|
/* if holes are in the same location and have the same size and shape, this can be accepted */
|
|
|
|
if( pad->GetPosition() == aRefPad->GetPosition()
|
|
|
|
&& pad->m_Drill == aRefPad->m_Drill
|
|
|
|
&& pad->m_DrillShape == aRefPad->m_DrillShape )
|
|
|
|
{
|
|
|
|
if( aRefPad->m_DrillShape == PAD_CIRCLE )
|
|
|
|
continue;
|
|
|
|
if( pad->m_Orient == aRefPad->m_Orient ) // for oval holes: must also have the same orientation
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Here, we must test clearance between holes and pads
|
|
|
|
* dummypad size and shape is adjusted to pad drill size and shape
|
|
|
|
*/
|
|
|
|
if( pad->m_Drill.x ) // pad under testing has a hole, test this hole against pad reference
|
|
|
|
{
|
|
|
|
dummypad.SetPosition( pad->GetPosition() );
|
|
|
|
dummypad.m_Size = pad->m_Drill;
|
|
|
|
dummypad.m_PadShape = pad->m_DrillShape == PAD_OVAL ? PAD_OVAL : PAD_CIRCLE;
|
|
|
|
dummypad.m_Orient = pad->m_Orient;
|
|
|
|
if( !checkClearancePadToPad( aRefPad, &dummypad, g_DesignSettings.m_TrackClearence ) )
|
|
|
|
{
|
|
|
|
// here we have a drc error on pad!
|
|
|
|
m_currentMarker = fillMarker( pad, aRefPad,
|
|
|
|
DRCE_HOLE_NEAR_PAD, m_currentMarker );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( aRefPad->m_Drill.x ) // pad reference has a hole
|
|
|
|
{
|
|
|
|
dummypad.SetPosition( aRefPad->GetPosition() );
|
|
|
|
dummypad.m_Size = aRefPad->m_Drill;
|
|
|
|
dummypad.m_PadShape = aRefPad->m_DrillShape == PAD_OVAL ? PAD_OVAL : PAD_CIRCLE;
|
|
|
|
dummypad.m_Orient = aRefPad->m_Orient;
|
|
|
|
if( !checkClearancePadToPad( pad, &dummypad, g_DesignSettings.m_TrackClearence ) )
|
|
|
|
{
|
|
|
|
// here we have a drc erroron aRefPad!
|
|
|
|
m_currentMarker = fillMarker( aRefPad, pad,
|
|
|
|
DRCE_HOLE_NEAR_PAD, m_currentMarker );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
continue;
|
2009-03-23 19:54:15 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
/* The pad must be in a net (i.e pt_pad->GetNet() != 0 ),
|
|
|
|
* But no problem if pads have the same netcode (same net)*/
|
2009-03-23 19:54:15 +00:00
|
|
|
if( pad->GetNet() && ( aRefPad->GetNet() == pad->GetNet() ) )
|
2007-12-01 03:42:52 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* No problem if pads are from the same footprint
|
|
|
|
* and have the same pad number ( equivalent pads ) */
|
2009-03-23 19:54:15 +00:00
|
|
|
if( ( pad->GetParent() == aRefPad->GetParent() )
|
|
|
|
&& (pad->m_NumPadName == aRefPad->m_NumPadName) )
|
2007-12-01 03:42:52 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if( !checkClearancePadToPad( aRefPad, pad, g_DesignSettings.m_TrackClearence ) )
|
|
|
|
{
|
|
|
|
// here we have a drc error!
|
2008-02-09 08:34:45 +00:00
|
|
|
m_currentMarker = fillMarker( aRefPad, pad,
|
2009-03-23 19:54:15 +00:00
|
|
|
DRCE_PAD_NEAR_PAD1, m_currentMarker );
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************/
|
|
|
|
bool DRC::checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, const int dist_min )
|
|
|
|
/***************************************************************************************/
|
|
|
|
{
|
|
|
|
wxPoint rel_pos;
|
|
|
|
int dist;;
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
wxPoint shape_pos;
|
|
|
|
int pad_angle;
|
|
|
|
|
|
|
|
rel_pos = aPad->ReturnShapePos();
|
|
|
|
shape_pos = aRefPad->ReturnShapePos();
|
|
|
|
|
|
|
|
// rel_pos is pad position relative to the aRefPad position
|
|
|
|
rel_pos.x -= shape_pos.x;
|
|
|
|
rel_pos.y -= shape_pos.y;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
dist = (int) hypot( rel_pos.x, rel_pos.y );
|
|
|
|
|
|
|
|
bool diag = true;
|
|
|
|
|
|
|
|
/* tst rapide: si les cercles exinscrits sont distants de dist_min au moins,
|
|
|
|
* il n'y a pas de risque: */
|
|
|
|
if( (dist - aRefPad->m_Rayon - aPad->m_Rayon) >= dist_min )
|
2007-12-04 18:23:38 +00:00
|
|
|
goto exit;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
/* Ici les pads sont proches et les cercles exinxcrits sont trop proches
|
|
|
|
* Selon les formes relatives il peut y avoir ou non erreur */
|
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
bool swap_pads;
|
|
|
|
swap_pads = false;
|
2008-01-05 17:30:56 +00:00
|
|
|
if( (aRefPad->m_PadShape != PAD_CIRCLE) && (aPad->m_PadShape == PAD_CIRCLE) )
|
2007-12-01 03:42:52 +00:00
|
|
|
swap_pads = true;
|
2008-01-05 17:30:56 +00:00
|
|
|
else if( (aRefPad->m_PadShape != PAD_OVAL) && (aPad->m_PadShape == PAD_OVAL) )
|
2007-12-01 03:42:52 +00:00
|
|
|
swap_pads = true;
|
|
|
|
|
|
|
|
if( swap_pads )
|
|
|
|
{
|
|
|
|
EXCHG( aRefPad, aPad );
|
|
|
|
rel_pos.x = -rel_pos.x;
|
|
|
|
rel_pos.y = -rel_pos.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch( aRefPad->m_PadShape )
|
|
|
|
{
|
2008-01-05 17:30:56 +00:00
|
|
|
case PAD_CIRCLE: // aRefPad is like a track segment with a null lenght
|
2009-03-23 19:54:15 +00:00
|
|
|
m_segmLength = 0;
|
|
|
|
m_segmAngle = 0;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
m_finx = m_finy = 0;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_spotcx = rel_pos.x;
|
|
|
|
m_spotcy = rel_pos.y;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
diag = checkClearanceSegmToPad( aPad, aRefPad->m_Rayon, dist_min );
|
|
|
|
break;
|
|
|
|
|
2008-01-05 17:30:56 +00:00
|
|
|
case PAD_RECT:
|
2007-12-01 03:42:52 +00:00
|
|
|
RotatePoint( &rel_pos.x, &rel_pos.y, aRefPad->m_Orient );
|
|
|
|
pad_angle = aRefPad->m_Orient + aPad->m_Orient; // pad_angle = pad orient relative to the aRefPad orient
|
|
|
|
NORMALIZE_ANGLE_POS( pad_angle );
|
2008-01-05 17:30:56 +00:00
|
|
|
if( aPad->m_PadShape == PAD_RECT )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
wxSize size = aPad->m_Size;
|
2009-03-23 19:54:15 +00:00
|
|
|
if( (pad_angle == 0) || (pad_angle == 900) || (pad_angle == 1800)
|
|
|
|
|| (pad_angle == 2700) )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
if( (pad_angle == 900) || (pad_angle == 2700) )
|
|
|
|
{
|
|
|
|
EXCHG( size.x, size.y );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test DRC:
|
2009-03-23 19:54:15 +00:00
|
|
|
diag = false;
|
2007-12-01 03:42:52 +00:00
|
|
|
|
|
|
|
rel_pos.x = ABS( rel_pos.x );
|
|
|
|
rel_pos.y = ABS( rel_pos.y );
|
|
|
|
|
|
|
|
if( ( rel_pos.x - ( (size.x + aRefPad->m_Size.x) / 2 ) ) >= dist_min )
|
|
|
|
diag = true;
|
|
|
|
|
|
|
|
if( ( rel_pos.y - ( (size.y + aRefPad->m_Size.y) / 2 ) ) >= dist_min )
|
|
|
|
diag = true;
|
|
|
|
}
|
|
|
|
else // Any other orient
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
/* TODO : any orient ... */
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2008-01-05 17:30:56 +00:00
|
|
|
case PAD_OVAL: /* an oval pad is like a track segment */
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
/* Create and test a track segment with same dimensions */
|
|
|
|
int segm_width;
|
2009-03-23 19:54:15 +00:00
|
|
|
m_segmAngle = aRefPad->m_Orient; // Segment orient.
|
|
|
|
if( aRefPad->m_Size.y < aRefPad->m_Size.x ) /* We suppose the pad is an horizontal oval */
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
segm_width = aRefPad->m_Size.y;
|
|
|
|
m_segmLength = aRefPad->m_Size.x - aRefPad->m_Size.y;
|
2007-12-01 03:42:52 +00:00
|
|
|
}
|
|
|
|
else // it was a vertical oval, change to a rotated horizontal one
|
|
|
|
{
|
2009-03-23 19:54:15 +00:00
|
|
|
segm_width = aRefPad->m_Size.x;
|
|
|
|
m_segmLength = aRefPad->m_Size.y - aRefPad->m_Size.x;
|
2007-12-01 03:42:52 +00:00
|
|
|
m_segmAngle += 900;
|
|
|
|
}
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
/* the start point must be 0,0 and currently rel_pos is relative the center of pad coordinate */
|
2009-03-23 19:54:15 +00:00
|
|
|
int sx = -m_segmLength / 2, sy = 0; // Start point coordinate of the horizontal equivalent segment
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
RotatePoint( &sx, &sy, m_segmAngle ); // True start point coordinate of the equivalent segment
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
m_spotcx = rel_pos.x + sx;
|
|
|
|
m_spotcy = rel_pos.y + sy; // pad position / segment origin
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
m_finx = -sx;
|
|
|
|
m_finy = -sy; // end of segment coordinate
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
diag = checkClearanceSegmToPad( aPad, segm_width / 2, dist_min );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
case PAD_TRAPEZOID:
|
2007-12-01 03:42:52 +00:00
|
|
|
default:
|
|
|
|
/* TODO...*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2007-12-04 18:23:38 +00:00
|
|
|
exit: // the only way out (hopefully) for simpler debugging
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
return diag;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DRC::checkClearanceSegmToPad( const D_PAD* pad_to_test, int w_segm, int dist_min )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
int p_dimx;
|
2007-12-01 03:42:52 +00:00
|
|
|
int p_dimy; // half the dimension of the pad
|
|
|
|
int orient;
|
|
|
|
int x0, y0, xf, yf;
|
|
|
|
int seuil;
|
|
|
|
int deltay;
|
|
|
|
|
|
|
|
seuil = w_segm + dist_min;
|
|
|
|
p_dimx = pad_to_test->m_Size.x >> 1;
|
|
|
|
p_dimy = pad_to_test->m_Size.y >> 1;
|
|
|
|
|
2008-01-05 17:30:56 +00:00
|
|
|
if( pad_to_test->m_PadShape == PAD_CIRCLE )
|
2007-12-01 03:42:52 +00:00
|
|
|
{
|
|
|
|
/* calcul des coord centre du pad dans le repere axe X confondu
|
|
|
|
* avec le segment en tst */
|
|
|
|
RotatePoint( &m_spotcx, &m_spotcy, m_segmAngle );
|
|
|
|
return checkMarginToCircle( m_spotcx, m_spotcy, seuil + p_dimx, m_segmLength );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* calcul de la "surface de securite" du pad de reference */
|
|
|
|
m_xcliplo = m_spotcx - seuil - p_dimx;
|
|
|
|
m_ycliplo = m_spotcy - seuil - p_dimy;
|
|
|
|
m_xcliphi = m_spotcx + seuil + p_dimx;
|
|
|
|
m_ycliphi = m_spotcy + seuil + p_dimy;
|
|
|
|
|
|
|
|
x0 = y0 = 0;
|
|
|
|
|
|
|
|
xf = m_finx;
|
|
|
|
yf = m_finy;
|
|
|
|
|
|
|
|
orient = pad_to_test->m_Orient;
|
|
|
|
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, -orient );
|
|
|
|
RotatePoint( &xf, &yf, m_spotcx, m_spotcy, -orient );
|
|
|
|
|
|
|
|
if( checkLine( x0, y0, xf, yf ) )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* Erreur DRC : analyse fine de la forme de la pastille */
|
|
|
|
|
|
|
|
switch( pad_to_test->m_PadShape )
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
|
2008-01-05 17:30:56 +00:00
|
|
|
case PAD_OVAL:
|
2007-12-01 03:42:52 +00:00
|
|
|
/* test de la pastille ovale ramenee au type ovale vertical */
|
|
|
|
if( p_dimx > p_dimy )
|
|
|
|
{
|
2008-02-09 08:34:45 +00:00
|
|
|
EXCHG( p_dimx, p_dimy );
|
2007-12-01 03:42:52 +00:00
|
|
|
orient += 900;
|
|
|
|
if( orient >= 3600 )
|
|
|
|
orient -= 3600;
|
|
|
|
}
|
|
|
|
deltay = p_dimy - p_dimx;
|
|
|
|
|
|
|
|
/* ici: p_dimx = rayon,
|
|
|
|
* delta = dist centre cercles a centre pad */
|
|
|
|
|
|
|
|
/* Test du rectangle separant les 2 demi cercles */
|
|
|
|
m_xcliplo = m_spotcx - seuil - p_dimx;
|
|
|
|
m_ycliplo = m_spotcy - w_segm - deltay;
|
|
|
|
m_xcliphi = m_spotcx + seuil + p_dimx;
|
|
|
|
m_ycliphi = m_spotcy + w_segm + deltay;
|
|
|
|
|
|
|
|
if( !checkLine( x0, y0, xf, yf ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* test des 2 cercles */
|
|
|
|
x0 = m_spotcx; /* x0,y0 = centre du cercle superieur du pad ovale */
|
|
|
|
y0 = m_spotcy + deltay;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
|
|
|
|
if( !checkMarginToCircle( x0, y0, p_dimx + seuil, m_segmLength ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
x0 = m_spotcx; /* x0,y0 = centre du cercle inferieur du pad ovale */
|
|
|
|
y0 = m_spotcy - deltay;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
|
|
|
|
if( !checkMarginToCircle( x0, y0, p_dimx + seuil, m_segmLength ) )
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
|
2008-01-05 17:30:56 +00:00
|
|
|
case PAD_RECT: /* 2 rectangle + 4 1/4 cercles a tester */
|
2007-12-01 03:42:52 +00:00
|
|
|
/* Test du rectangle dimx + seuil, dimy */
|
|
|
|
m_xcliplo = m_spotcx - p_dimx - seuil;
|
|
|
|
m_ycliplo = m_spotcy - p_dimy;
|
|
|
|
m_xcliphi = m_spotcx + p_dimx + seuil;
|
|
|
|
m_ycliphi = m_spotcy + p_dimy;
|
|
|
|
|
|
|
|
if( !checkLine( x0, y0, xf, yf ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Test du rectangle dimx , dimy + seuil */
|
|
|
|
m_xcliplo = m_spotcx - p_dimx;
|
|
|
|
m_ycliplo = m_spotcy - p_dimy - seuil;
|
|
|
|
m_xcliphi = m_spotcx + p_dimx;
|
|
|
|
m_ycliphi = m_spotcy + p_dimy + seuil;
|
|
|
|
|
|
|
|
if( !checkLine( x0, y0, xf, yf ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test des 4 cercles ( surface d'solation autour des sommets */
|
|
|
|
/* test du coin sup. gauche du pad */
|
|
|
|
x0 = m_spotcx - p_dimx;
|
|
|
|
y0 = m_spotcy - p_dimy;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
if( !checkMarginToCircle( x0, y0, seuil, m_segmLength ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test du coin sup. droit du pad */
|
|
|
|
x0 = m_spotcx + p_dimx;
|
|
|
|
y0 = m_spotcy - p_dimy;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
if( !checkMarginToCircle( x0, y0, seuil, m_segmLength ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test du coin inf. gauche du pad */
|
|
|
|
x0 = m_spotcx - p_dimx;
|
|
|
|
y0 = m_spotcy + p_dimy;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
if( !checkMarginToCircle( x0, y0, seuil, m_segmLength ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test du coin inf. droit du pad */
|
|
|
|
x0 = m_spotcx + p_dimx;
|
|
|
|
y0 = m_spotcy + p_dimy;
|
|
|
|
RotatePoint( &x0, &y0, m_spotcx, m_spotcy, orient );
|
|
|
|
RotatePoint( &x0, &y0, m_segmAngle );
|
|
|
|
if( !checkMarginToCircle( x0, y0, seuil, m_segmLength ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
/**********************************************************************/
|
|
|
|
bool DRC::checkMarginToCircle( int cx, int cy, int radius, int longueur )
|
|
|
|
/**********************************************************************/
|
|
|
|
{
|
|
|
|
if( abs( cy ) > radius )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if( (cx >= -radius ) && ( cx <= (longueur + radius) ) )
|
|
|
|
{
|
|
|
|
if( (cx >= 0) && (cx <= longueur) )
|
|
|
|
return false;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
if( cx > longueur )
|
|
|
|
cx -= longueur;
|
2008-02-09 08:34:45 +00:00
|
|
|
|
2007-12-01 03:42:52 +00:00
|
|
|
if( hypot( cx, cy ) < radius )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************/
|
|
|
|
/* int Tst_Ligne(int x1,int y1,int x2,int y2) */
|
|
|
|
/**********************************************/
|
|
|
|
|
|
|
|
static inline int USCALE( unsigned arg, unsigned num, unsigned den )
|
|
|
|
{
|
|
|
|
int ii;
|
|
|
|
|
|
|
|
ii = (int) ( ( (double) arg * num ) / den );
|
|
|
|
return ii;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define WHEN_OUTSIDE return true
|
|
|
|
#define WHEN_INSIDE
|
|
|
|
|
|
|
|
bool DRC::checkLine( int x1, int y1, int x2, int y2 )
|
|
|
|
{
|
|
|
|
int temp;
|
|
|
|
|
|
|
|
if( x1 > x2 )
|
|
|
|
{
|
|
|
|
EXCHG( x1, x2 );
|
|
|
|
EXCHG( y1, y2 );
|
|
|
|
}
|
|
|
|
if( (x2 < m_xcliplo) || (x1 > m_xcliphi) )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
if( y1 < y2 )
|
|
|
|
{
|
|
|
|
if( (y2 < m_ycliplo) || (y1 > m_ycliphi) )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
if( y1 < m_ycliplo )
|
|
|
|
{
|
|
|
|
temp = USCALE( (x2 - x1), (m_ycliplo - y1), (y2 - y1) );
|
|
|
|
if( (x1 += temp) > m_xcliphi )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
y1 = m_ycliplo;
|
|
|
|
WHEN_INSIDE;
|
|
|
|
}
|
|
|
|
if( y2 > m_ycliphi )
|
|
|
|
{
|
|
|
|
temp = USCALE( (x2 - x1), (y2 - m_ycliphi), (y2 - y1) );
|
|
|
|
if( (x2 -= temp) < m_xcliplo )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
y2 = m_ycliphi;
|
|
|
|
WHEN_INSIDE;
|
|
|
|
}
|
|
|
|
if( x1 < m_xcliplo )
|
|
|
|
{
|
|
|
|
temp = USCALE( (y2 - y1), (m_xcliplo - x1), (x2 - x1) );
|
2008-02-09 08:34:45 +00:00
|
|
|
y1 += temp;
|
2009-03-23 19:54:15 +00:00
|
|
|
x1 = m_xcliplo;
|
2007-12-01 03:42:52 +00:00
|
|
|
WHEN_INSIDE;
|
|
|
|
}
|
|
|
|
if( x2 > m_xcliphi )
|
|
|
|
{
|
|
|
|
temp = USCALE( (y2 - y1), (x2 - m_xcliphi), (x2 - x1) );
|
2008-02-09 08:34:45 +00:00
|
|
|
y2 -= temp;
|
2009-03-23 19:54:15 +00:00
|
|
|
x2 = m_xcliphi;
|
2007-12-01 03:42:52 +00:00
|
|
|
WHEN_INSIDE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( (y1 < m_ycliplo) || (y2 > m_ycliphi) )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
if( y1 > m_ycliphi )
|
|
|
|
{
|
|
|
|
temp = USCALE( (x2 - x1), (y1 - m_ycliphi), (y1 - y2) );
|
|
|
|
if( (x1 += temp) > m_xcliphi )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
y1 = m_ycliphi;
|
|
|
|
WHEN_INSIDE;
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
if( y2 < m_ycliplo )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
temp = USCALE( (x2 - x1), (m_ycliplo - y2), (y1 - y2) );
|
|
|
|
if( (x2 -= temp) < m_xcliplo )
|
|
|
|
{
|
|
|
|
WHEN_OUTSIDE;
|
|
|
|
}
|
|
|
|
y2 = m_ycliplo;
|
|
|
|
WHEN_INSIDE;
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
2007-12-01 03:42:52 +00:00
|
|
|
if( x1 < m_xcliplo )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
temp = USCALE( (y1 - y2), (m_xcliplo - x1), (x2 - x1) );
|
|
|
|
y1 -= temp;
|
2009-03-23 19:54:15 +00:00
|
|
|
x1 = m_xcliplo;
|
2007-12-01 03:42:52 +00:00
|
|
|
WHEN_INSIDE;
|
|
|
|
}
|
|
|
|
if( x2 > m_xcliphi )
|
|
|
|
{
|
|
|
|
temp = USCALE( (y1 - y2), (x2 - m_xcliphi), (x2 - x1) );
|
|
|
|
y2 += temp;
|
2009-03-23 19:54:15 +00:00
|
|
|
x2 = m_xcliphi;
|
2007-12-01 03:42:52 +00:00
|
|
|
WHEN_INSIDE;
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:15 +00:00
|
|
|
if( ( (x2 + x1) / 2 <= m_xcliphi ) && ( (x2 + x1) / 2 >= m_xcliplo ) \
|
|
|
|
&& ( (y2 + y1) / 2 <= m_ycliphi ) && ( (y2 + y1) / 2 >= m_ycliplo ) )
|
2007-08-08 20:51:08 +00:00
|
|
|
{
|
2007-12-01 03:42:52 +00:00
|
|
|
return false;
|
2007-08-08 20:51:08 +00:00
|
|
|
}
|
|
|
|
else
|
2007-12-01 03:42:52 +00:00
|
|
|
return true;
|
2007-06-05 12:10:51 +00:00
|
|
|
}
|