Commit f311bb4d authored by jean-pierre charras's avatar jean-pierre charras

Pcbnew:

    Enhanced algorithms to calculate board connections:
    Previously, a track was seen connected to a pad only if the track end is
    exactly on the pad position.
    Now a track is seen connected to a pad if the track end is inside the pad shape.
    Algorithm to calculate pads connections to track is still very fast.
    However some other functions (drag pads, track len calculation ...)
    still need the track end exactly on the pad position.
Dead code removal.
parent 2ae221d9
...@@ -4,6 +4,16 @@ KiCad ChangeLog 2010 ...@@ -4,6 +4,16 @@ KiCad ChangeLog 2010
Please add newer entries at the top, list the date and your name with Please add newer entries at the top, list the date and your name with
email address. email address.
2011-Dec-04, UPDATE Jean-Pierre Charras <jean-pierre.charras@gipsa-lab.inpg.fr>
================================================================================
Pcbnew:
Enhanced algorithms to calculate board connections:
Previously, a track was seen connected to a pad only if the track end is
exactly on the pad position.
Now a track is seen connected to a pad if the track end is inside the pad shape.
Algorithm to calculate pads connections to track is still very fast.
However some other functions (drag pads, track len calculation ...)
still need the track end exactly on the pad position.
2011-Nov-27 UPDATE Dick Hollenbeck <dick@softplc.com> 2011-Nov-27 UPDATE Dick Hollenbeck <dick@softplc.com>
================================================================================ ================================================================================
...@@ -40,7 +50,7 @@ Pcbnew: ...@@ -40,7 +50,7 @@ Pcbnew:
2011-Sept-01, UPDATE Jean-Pierre Charras <jean-pierre.charras@gipsa-lab.inpg.fr> 2011-Sept-01, UPDATE Jean-Pierre Charras <jean-pierre.charras@gipsa-lab.inpg.fr>
================================================================================ ================================================================================
Add Fabrizio Tappero <fabrizio-dot-tappero[at]gmail-dot-com> in contribuotors list. Add Fabrizio Tappero <fabrizio-dot-tappero[at]gmail-dot-com> in contributors list.
Eeschema: Eeschema:
Graphic texts ans labels: fix fully broken undo/redo code relative to the way undo/redo command Graphic texts ans labels: fix fully broken undo/redo code relative to the way undo/redo command
handles changes (maintly move) for labels handles changes (maintly move) for labels
......
...@@ -933,19 +933,11 @@ public: ...@@ -933,19 +933,11 @@ public:
bool Other_Layer_Route( TRACK* track, wxDC* DC ); bool Other_Layer_Route( TRACK* track, wxDC* DC );
void HighlightUnconnectedPads( wxDC* DC ); void HighlightUnconnectedPads( wxDC* DC );
/**
* Function DisplayNetStatus
* shows the status of the net at the current mouse position or the
* PCB status if no segment selected.
*/
void DisplayNetStatus( wxDC* DC );
/** /**
* Function Delete_Segment * Function Delete_Segment
* removes 1 segment of track. * removes a track segment.
* 2 cases: * If a new track is in progress: delete the current new segment.
* If There is evidence of new track: delete new segment. * Otherwise, delete segment under the mouse cursor.
* Otherwise, delete segment under the cursor.
*/ */
TRACK* Delete_Segment( wxDC* DC, TRACK* Track ); TRACK* Delete_Segment( wxDC* DC, TRACK* Track );
......
...@@ -46,39 +46,51 @@ extern void Merge_SubNets_Connected_By_CopperAreas( BOARD* aPcb, int aNetcode ); ...@@ -46,39 +46,51 @@ extern void Merge_SubNets_Connected_By_CopperAreas( BOARD* aPcb, int aNetcode );
static void RebuildTrackChain( BOARD* pcb ); static void RebuildTrackChain( BOARD* pcb );
// A helper class to handle connection points // A helper class to handle connection points (i.e. candidates) for tracks
class CONNECTED_POINT class CONNECTED_POINT
{ {
public: private:
TRACK * m_Track; // a link to the connected item (track or via) TRACK * m_track; // a link to the parent item (track or via)
wxPoint m_Point; // the connection point wxPoint m_point; // the connection point
public:
CONNECTED_POINT( TRACK * aTrack, wxPoint & aPoint) CONNECTED_POINT( TRACK * aTrack, wxPoint & aPoint)
{ {
m_Track = aTrack; m_track = aTrack;
m_Point = aPoint; m_point = aPoint;
} }
TRACK * GetTrack() const { return m_track; }
const wxPoint & GetPoint() const { return m_point; }
}; };
// A helper class to handle connections calculations: // A helper class to handle connections calculations:
class CONNECTIONS class CONNECTIONS
{ {
public: private:
std::vector <TRACK*> m_Connected; // List of connected tracks/vias std::vector <TRACK*> m_connected; // List of connected tracks/vias
// to a given track or via // to a given track or via
std::vector <CONNECTED_POINT> m_Candidates; // List of points to test std::vector <CONNECTED_POINT> m_candidates; // List of points to test
// (end points of tracks or vias location ) // (end points of tracks or vias location )
private:
BOARD * m_brd; // the master board. BOARD * m_brd; // the master board.
const TRACK * m_firstTrack; // The first track used to build m_Candidates const TRACK * m_firstTrack; // The first track used to build m_Candidates
const TRACK * m_lastTrack; // The last track used to build m_Candidates const TRACK * m_lastTrack; // The last track used to build m_Candidates
std::vector<D_PAD*> m_sortedPads; // list of sorted pads by X (then Y) coordinate
public: public:
CONNECTIONS( BOARD * aBrd ); CONNECTIONS( BOARD * aBrd );
~CONNECTIONS() {}; ~CONNECTIONS() {};
/** Function Build_CurrNet_SubNets_Connections /** Function BuildPadsList
* Fills m_sortedPads with all pads that be connected to tracks
* pads are sorted by > then Y coordinates to allow fast binary search in list
* @param aNetcode = net code to use to filter pads
* if aNetcode < 0, all pads will be put in list (default)
*/
void BuildPadsList( int aNetcode = -1 );
/**
* Function Build_CurrNet_SubNets_Connections
* Connections to pads are assumed to be already initialized, * Connections to pads are assumed to be already initialized,
* and are not recalculated * and are not recalculated
* An be called after a track change (delete or add a track): * An be called after a track change (delete or add a track):
...@@ -93,7 +105,8 @@ public: ...@@ -93,7 +105,8 @@ public:
*/ */
void Build_CurrNet_SubNets_Connections( TRACK* aFirstTrack, TRACK* aLastTrack ); void Build_CurrNet_SubNets_Connections( TRACK* aFirstTrack, TRACK* aLastTrack );
/** Function BuildCandidatesList /**
* Function BuildCandidatesList
* Fills m_Candidates with all connecting points (track ends or via location) * Fills m_Candidates with all connecting points (track ends or via location)
* with tracks from aBegin to aEnd. * with tracks from aBegin to aEnd.
* if aBegin == NULL, use first track in brd list * if aBegin == NULL, use first track in brd list
...@@ -108,6 +121,37 @@ public: ...@@ -108,6 +121,37 @@ public:
*/ */
int SearchConnectedTracks( const TRACK * aTrack ); int SearchConnectedTracks( const TRACK * aTrack );
/**
* Function GetConnectedTracks
* Copy m_Connected that contains the list of tracks connected
* calculated by SearchConnectedTracks
* in aTrack->m_TracksConnected
* @param aTrack = track or via to fill with connected tracks
*/
void GetConnectedTracks(TRACK * aTrack)
{
aTrack->m_TracksConnected = m_connected;
}
/**
* function SearchConnectedToPads
* Explores the list of pads and adds to m_PadsConnected member
* of each track connected the pad(s) connected to
*/
void SearchConnectedToPads();
/**
* function CollectItemsNearTo
* Used by SearchConnectedToPads
* Fills aList with pads near to aPosition
* near means aPosition to pad position <= aDistMax
* @param aList = list to fill
* @param aPosition = aPosition to use as reference
* @param aDistMax = dist max from aPosition to a candidate to select it
*/
void CollectItemsNearTo( std::vector<CONNECTED_POINT*>& aList,
const wxPoint& aPosition, int aDistMax );
/** /**
* Function Propagate_SubNets * Function Propagate_SubNets
* Test a list of tracks, to create or propagate a sub netcode to pads and * Test a list of tracks, to create or propagate a sub netcode to pads and
...@@ -141,7 +185,7 @@ private: ...@@ -141,7 +185,7 @@ private:
* Change a subnet value to a new value, for tracks ans pads which are connected to * Change a subnet value to a new value, for tracks ans pads which are connected to
* corresponding track for pads and tracks, this is the .m_Subnet member that is tested * corresponding track for pads and tracks, this is the .m_Subnet member that is tested
* and modified these members are block numbers (or cluster numbers) for a given net * and modified these members are block numbers (or cluster numbers) for a given net
* The result is merging 2 cluster (or subnets) into only one. * The result is 2 cluster (or subnets) are merged into only one.
* Note: the resulting sub net value is the smallest between aOldSubNet et aNewSubNet * Note: the resulting sub net value is the smallest between aOldSubNet et aNewSubNet
* @return modification count * @return modification count
* @param aOldSubNet = subnet value to modify * @param aOldSubNet = subnet value to modify
...@@ -151,6 +195,144 @@ private: ...@@ -151,6 +195,144 @@ private:
}; };
CONNECTIONS::CONNECTIONS( BOARD * aBrd )
{
m_brd = aBrd;
}
/* Fills m_sortedPads with all pads that be connected to tracks
* pads are sorted by X coordinate ( and Y coordinates for same X value )
* aNetcode = net code to filter pads or < 0 to put all pads in list
*/
void CONNECTIONS::BuildPadsList( int aNetcode )
{
// Creates sorted pad list if not exists
m_sortedPads.clear();
if( aNetcode < 0 )
m_brd->GetSortedPadListByXthenYCoord( m_sortedPads );
else
{
std::vector<D_PAD*> buffer;
m_brd->GetSortedPadListByXthenYCoord( buffer );
int icnt = 0;
for( unsigned ii = 0; ii < buffer.size(); ii++ )
{
if( buffer[ii]->GetNet() == aNetcode )
icnt++;
}
m_sortedPads.reserve(icnt);
for( unsigned ii = 0; ii < buffer.size(); ii++ )
{
if( buffer[ii]->GetNet() == aNetcode )
m_sortedPads.push_back( buffer[ii] );
}
}
}
void CONNECTIONS::SearchConnectedToPads()
{
std::vector<CONNECTED_POINT*> candidates;
for( unsigned ii = 0; ii < m_sortedPads.size(); ii++ )
{
D_PAD * pad = m_sortedPads[ii];
candidates.clear();
CollectItemsNearTo( candidates, pad->ReturnShapePos(), pad->m_ShapeMaxRadius );
// add this pad to track.m_PadsConnected, if it is connected
for( unsigned jj = 0; jj < candidates.size(); jj++ )
{
CONNECTED_POINT * item = candidates[jj];
if( (pad->m_layerMask & item->GetTrack()->ReturnMaskLayer()) == 0 )
continue;
if( pad->HitTest( item->GetPoint() ) )
{
item->GetTrack()->m_PadsConnected.push_back( pad );
pad->m_TracksConnected.push_back( item->GetTrack() );
}
}
}
}
void CONNECTIONS::CollectItemsNearTo( std::vector<CONNECTED_POINT*>& aList,
const wxPoint& aPosition, int aDistMax )
{
/* Search items in m_Candidates that position is <= aDistMax from aPosition
* (Rectilinear distance)
* m_Candidates is sorted by X then Y values, so a fast binary search is used
* to locate the "best" entry point in list
* The best entry is a pad having its m_Pos.x == (or near) aPosition.x
* All candidates are near this candidate in list
* So from this entry point, a linear search is made to find all candidates
*/
int idxmax = m_candidates.size()-1;
int delta = m_candidates.size();
if( delta & 1 && delta > 1 )
delta += 1;
delta /= 2;
int idx = delta; // Starting index is the middle of list
while( delta )
{
if( (delta & 1) && ( delta > 1 ) )
delta++;
delta /= 2;
CONNECTED_POINT& item = m_candidates[idx];
if( item.GetPoint().x == aPosition.x )
break; // A good entry point is found. The list can be scanned from this point.
else if( item.GetPoint().x < aPosition.x ) // We should search after this item
{
idx += delta;
if( idx > idxmax )
idx = idxmax;
}
else // We should search before this item
{
idx -= delta;
if( idx < 0 )
idx = 0;
}
}
/* Now explore the candidate list from the "best" entry point found
* (candidate "near" aPosition.x)
* We explore the list until abs(candidate->m_Point.x - aPosition.x) > aDistMax
* because the list is sorted by X position (and for a given X pos, by Y pos)
* Currently a linear search is made because the number of candidates
* having the right X position is usually small
*/
// search next candidates in list
wxPoint diff;
for( int ii = idx; ii <= idxmax; ii++ )
{
CONNECTED_POINT* item = &m_candidates[ii];
diff = item->GetPoint() - aPosition;
if( abs(diff.x) > aDistMax )
break; // Exit: the distance is to long, we cannot find other candidates
if( abs(diff.y) > aDistMax )
continue; // the y distance is to long, but we can find other candidates
// We have here a good candidate: add it
aList.push_back( item );
}
// search previous candidates in list
for( int ii = idx-1; ii >=0; ii-- )
{
CONNECTED_POINT * item = &m_candidates[ii];
diff = item->GetPoint() - aPosition;
if( abs(diff.x) > aDistMax )
break;
if( abs(diff.y) > aDistMax )
continue;
// We have here a good candidate:add it
aList.push_back( item );
}
}
/* sort function used to sort .m_Connected by X the Y values /* sort function used to sort .m_Connected by X the Y values
* items are sorted by X coordinate value, * items are sorted by X coordinate value,
* and for same X value, by Y coordinate value. * and for same X value, by Y coordinate value.
...@@ -158,19 +340,14 @@ private: ...@@ -158,19 +340,14 @@ private:
static bool sortConnectedPointByXthenYCoordinates( const CONNECTED_POINT & aRef, static bool sortConnectedPointByXthenYCoordinates( const CONNECTED_POINT & aRef,
const CONNECTED_POINT & aTst ) const CONNECTED_POINT & aTst )
{ {
if( aRef.m_Point.x == aTst.m_Point.x ) if( aRef.GetPoint().x == aTst.GetPoint().x )
return aRef.m_Point.y < aTst.m_Point.y; return aRef.GetPoint().y < aTst.GetPoint().y;
return aRef.m_Point.x < aTst.m_Point.x; return aRef.GetPoint().x < aTst.GetPoint().x;
}
CONNECTIONS::CONNECTIONS( BOARD * aBrd )
{
m_brd = aBrd;
} }
void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd) void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd)
{ {
m_Candidates.clear(); m_candidates.clear();
if( aBegin == NULL ) if( aBegin == NULL )
aBegin = m_brd->m_Track; aBegin = m_brd->m_Track;
...@@ -191,16 +368,15 @@ void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd) ...@@ -191,16 +368,15 @@ void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd)
break; break;
} }
// Build candidate list // Build candidate list
m_Candidates.reserve( ii ); m_candidates.reserve( ii );
for( TRACK* track = aBegin; track; track = track->Next() ) for( TRACK* track = aBegin; track; track = track->Next() )
{ {
CONNECTED_POINT candidate( track, track->m_Start); CONNECTED_POINT candidate( track, track->m_Start);
m_Candidates.push_back( candidate ); m_candidates.push_back( candidate );
if( track->Type() != PCB_VIA_T ) if( track->Type() != PCB_VIA_T )
{ {
candidate.m_Track = track; CONNECTED_POINT candidate2( track, track->m_End);
candidate.m_Point = track->m_End; m_candidates.push_back( candidate2 );
m_Candidates.push_back( candidate );
} }
if( track == aEnd ) if( track == aEnd )
...@@ -210,13 +386,13 @@ void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd) ...@@ -210,13 +386,13 @@ void CONNECTIONS::BuildCandidatesList( TRACK * aBegin, TRACK * aEnd)
// Sort list by increasing X coordinate, // Sort list by increasing X coordinate,
// and for increasing Y coordinate when items have the same X coordinate // and for increasing Y coordinate when items have the same X coordinate
// So candidates to the same location are consecutive in list. // So candidates to the same location are consecutive in list.
sort( m_Candidates.begin(), m_Candidates.end(), sortConnectedPointByXthenYCoordinates ); sort( m_candidates.begin(), m_candidates.end(), sortConnectedPointByXthenYCoordinates );
} }
int CONNECTIONS::SearchConnectedTracks( const TRACK * aTrack ) int CONNECTIONS::SearchConnectedTracks( const TRACK * aTrack )
{ {
int count = 0; int count = 0;
m_Connected.clear(); m_connected.clear();
int layerMask = aTrack->ReturnMaskLayer(); int layerMask = aTrack->ReturnMaskLayer();
...@@ -228,24 +404,24 @@ int CONNECTIONS::SearchConnectedTracks( const TRACK * aTrack ) ...@@ -228,24 +404,24 @@ int CONNECTIONS::SearchConnectedTracks( const TRACK * aTrack )
if ( idx >= 0 ) if ( idx >= 0 )
{ {
// search after: // search after:
for ( unsigned ii = idx; ii < m_Candidates.size(); ii ++ ) for ( unsigned ii = idx; ii < m_candidates.size(); ii ++ )
{ {
if( m_Candidates[ii].m_Track == aTrack ) if( m_candidates[ii].GetTrack() == aTrack )
continue; continue;
if( m_Candidates[ii].m_Point != position ) if( m_candidates[ii].GetPoint() != position )
break; break;
if( (m_Candidates[ii].m_Track->ReturnMaskLayer() & layerMask ) != 0 ) if( (m_candidates[ii].GetTrack()->ReturnMaskLayer() & layerMask ) != 0 )
m_Connected.push_back( m_Candidates[ii].m_Track ); m_connected.push_back( m_candidates[ii].GetTrack() );
} }
// search before: // search before:
for ( int ii = idx-1; ii >= 0; ii -- ) for ( int ii = idx-1; ii >= 0; ii -- )
{ {
if( m_Candidates[ii].m_Track == aTrack ) if( m_candidates[ii].GetTrack() == aTrack )
continue; continue;
if( m_Candidates[ii].m_Point != position ) if( m_candidates[ii].GetPoint() != position )
break; break;
if( (m_Candidates[ii].m_Track->ReturnMaskLayer() & layerMask ) != 0 ) if( (m_candidates[ii].GetTrack()->ReturnMaskLayer() & layerMask ) != 0 )
m_Connected.push_back( m_Candidates[ii].m_Track ); m_connected.push_back( m_candidates[ii].GetTrack() );
} }
} }
...@@ -263,9 +439,9 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint) ...@@ -263,9 +439,9 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint)
{ {
// Search the aPoint coordinates in m_Candidates // Search the aPoint coordinates in m_Candidates
// m_Candidates is sorted by X then Y values, and a fast binary search is used // m_Candidates is sorted by X then Y values, and a fast binary search is used
int idxmax = m_Candidates.size()-1; int idxmax = m_candidates.size()-1;
int delta = m_Candidates.size(); int delta = m_candidates.size();
if( delta & 1 && delta > 1 ) if( delta & 1 && delta > 1 )
delta += 1; delta += 1;
delta /= 2; delta /= 2;
...@@ -276,16 +452,16 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint) ...@@ -276,16 +452,16 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint)
delta++; delta++;
delta /= 2; delta /= 2;
CONNECTED_POINT & candidate = m_Candidates[idx]; CONNECTED_POINT & candidate = m_candidates[idx];
if( candidate.m_Point == aPoint ) // candidate found if( candidate.GetPoint() == aPoint ) // candidate found
{ {
return idx; return idx;
} }
// Not found: test the middle of the remaining sub list // Not found: test the middle of the remaining sub list
if( candidate.m_Point.x == aPoint.x ) // Must search considering Y coordinate if( candidate.GetPoint().x == aPoint.x ) // Must search considering Y coordinate
{ {
if(candidate.m_Point.y < aPoint.y) // Must search after this item if(candidate.GetPoint().y < aPoint.y) // Must search after this item
{ {
idx += delta; idx += delta;
if( idx > idxmax ) if( idx > idxmax )
...@@ -298,7 +474,7 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint) ...@@ -298,7 +474,7 @@ int CONNECTIONS::searchEntryPoint( const wxPoint & aPoint)
idx = 0; idx = 0;
} }
} }
else if( candidate.m_Point.x < aPoint.x ) // Must search after this item else if( candidate.GetPoint().x < aPoint.x ) // Must search after this item
{ {
idx += delta; idx += delta;
if( idx > idxmax ) if( idx > idxmax )
...@@ -338,7 +514,7 @@ void CONNECTIONS::Build_CurrNet_SubNets_Connections( TRACK* aFirstTrack, TRACK* ...@@ -338,7 +514,7 @@ void CONNECTIONS::Build_CurrNet_SubNets_Connections( TRACK* aFirstTrack, TRACK*
// Update connections between tracks: // Update connections between tracks:
SearchConnectedTracks( curr_track ); SearchConnectedTracks( curr_track );
curr_track->m_TracksConnected = m_Connected; curr_track->m_TracksConnected = m_connected;
if( curr_track == aLastTrack ) if( curr_track == aLastTrack )
break; break;
...@@ -381,18 +557,11 @@ int CONNECTIONS::Merge_SubNets( int aOldSubNet, int aNewSubNet ) ...@@ -381,18 +557,11 @@ int CONNECTIONS::Merge_SubNets( int aOldSubNet, int aNewSubNet )
change_count++; change_count++;
curr_track->SetSubNet( aNewSubNet ); curr_track->SetSubNet( aNewSubNet );
BOARD_CONNECTED_ITEM * item = curr_track->start; for( unsigned ii = 0; ii < curr_track->m_PadsConnected.size(); ii++ )
if( item && ( item->Type() == PCB_PAD_T) )
{ {
if( item->GetSubNet() == aOldSubNet ) D_PAD * pad = curr_track->m_PadsConnected[ii];
item->SetSubNet( curr_track->GetSubNet() ); if( pad->GetSubNet() == aOldSubNet )
} pad->SetSubNet( curr_track->GetSubNet() );
item = curr_track->end;
if( item && (item->Type() == PCB_PAD_T) )
{
if( item->GetSubNet() == aOldSubNet )
item->SetSubNet( curr_track->GetSubNet() );
} }
if( curr_track == m_lastTrack ) if( curr_track == m_lastTrack )
...@@ -427,46 +596,40 @@ void CONNECTIONS::Propagate_SubNets() ...@@ -427,46 +596,40 @@ void CONNECTIONS::Propagate_SubNets()
for( ; curr_track != NULL; curr_track = curr_track->Next() ) for( ; curr_track != NULL; curr_track = curr_track->Next() )
{ {
/* First: handling connections to pads */ /* First: handling connections to pads */
BOARD_CONNECTED_ITEM* pad = curr_track->start; for( unsigned ii = 0; ii < curr_track->m_PadsConnected.size(); ii++ )
for( int ii = 0; ii < 2; ii++ )
{ {
/* The segment starts on a pad */ D_PAD * pad = curr_track->m_PadsConnected[ii];
if( pad && (pad->Type() == PCB_PAD_T) )
if( curr_track->GetSubNet() ) /* the track segment is already a cluster member */
{ {
if( curr_track->GetSubNet() ) /* the track segment is already a cluster member */ if( pad->GetSubNet() > 0 )
{ {
if( pad->GetSubNet() > 0 ) /* The pad is already a cluster member, so we can merge the 2 clusters */
{ Merge_SubNets( pad->GetSubNet(), curr_track->GetSubNet() );
/* The pad is already a cluster member, so we can merge the 2 clusters */
Merge_SubNets( pad->GetSubNet(), curr_track->GetSubNet() );
}
else
{
/* The pad is not yet attached to a cluster , so we can add this pad to
* the cluster */
pad->SetSubNet( curr_track->GetSubNet() );
}
} }
else /* the track segment is not attached to a cluster */ else
{ {
if( pad->GetSubNet() > 0 ) /* The pad is not yet attached to a cluster , so we can add this pad to
{ * the cluster */
/* it is connected to a pad in a cluster, merge this track */ pad->SetSubNet( curr_track->GetSubNet() );
curr_track->SetSubNet( pad->GetSubNet() ); }
} }
else else /* the track segment is not attached to a cluster */
{ {
/* it is connected to a pad not in a cluster, so we must create a new if( pad->GetSubNet() > 0 )
* cluster (only with the 2 items: the track and the pad) */ {
sub_netcode++; /* it is connected to a pad in a cluster, merge this track */
curr_track->SetSubNet( sub_netcode ); curr_track->SetSubNet( pad->GetSubNet() );
pad->SetSubNet( curr_track->GetSubNet() ); }
} else
{
/* it is connected to a pad not in a cluster, so we must create a new
* cluster (only with the 2 items: the track and the pad) */
sub_netcode++;
curr_track->SetSubNet( sub_netcode );
pad->SetSubNet( curr_track->GetSubNet() );
} }
} }
pad = curr_track->end;
} }
/* Test connections between segments */ /* Test connections between segments */
...@@ -534,7 +697,7 @@ void PCB_BASE_FRAME::TestConnections() ...@@ -534,7 +697,7 @@ void PCB_BASE_FRAME::TestConnections()
// Get last track of the current net // Get last track of the current net
TRACK* lastTrack = track->GetEndNetCode( current_net_code ); TRACK* lastTrack = track->GetEndNetCode( current_net_code );
if( current_net_code ) // do not spend time if net code = 0, this is not a dummy net if( current_net_code ) // do not spend time if net code = 0 ( dummy net )
connections.Build_CurrNet_SubNets_Connections( track, lastTrack ); connections.Build_CurrNet_SubNets_Connections( track, lastTrack );
track = lastTrack->Next(); // this is now the first track of the next net track = lastTrack->Next(); // this is now the first track of the next net
...@@ -560,7 +723,6 @@ void PCB_BASE_FRAME::TestNetConnection( wxDC* aDC, int aNetCode ) ...@@ -560,7 +723,6 @@ void PCB_BASE_FRAME::TestNetConnection( wxDC* aDC, int aNetCode )
for( unsigned i = 0; i < m_Pcb->GetPadsCount(); ++i ) for( unsigned i = 0; i < m_Pcb->GetPadsCount(); ++i )
{ {
D_PAD* pad = m_Pcb->m_NetInfo->GetPad(i); D_PAD* pad = m_Pcb->m_NetInfo->GetPad(i);
int pad_net_code = pad->GetNet(); int pad_net_code = pad->GetNet();
if( pad_net_code < aNetCode ) if( pad_net_code < aNetCode )
...@@ -622,16 +784,15 @@ void PCB_BASE_FRAME::TestNetConnection( wxDC* aDC, int aNetCode ) ...@@ -622,16 +784,15 @@ void PCB_BASE_FRAME::TestNetConnection( wxDC* aDC, int aNetCode )
void PCB_BASE_FRAME::RecalculateAllTracksNetcode() void PCB_BASE_FRAME::RecalculateAllTracksNetcode()
{ {
TRACK* curr_track; TRACK* curr_track;
std::vector<D_PAD*> sortedPads;
// Build the net info list // Build the net info list
GetBoard()->m_NetInfo->BuildListOfNets(); GetBoard()->m_NetInfo->BuildListOfNets();
// Reset variables and flags used in computation // Reset variables and flags used in computation
curr_track = m_Pcb->m_Track; curr_track = m_Pcb->m_Track;
for( ; curr_track != NULL; curr_track = curr_track->Next() ) for( ; curr_track != NULL; curr_track = curr_track->Next() )
{ {
curr_track->m_TracksConnected.clear(); curr_track->m_TracksConnected.clear();
curr_track->m_PadsConnected.clear();
curr_track->start = NULL; curr_track->start = NULL;
curr_track->end = NULL; curr_track->end = NULL;
curr_track->SetState( BUSY | IN_EDIT | BEGIN_ONPAD | END_ONPAD, OFF ); curr_track->SetState( BUSY | IN_EDIT | BEGIN_ONPAD | END_ONPAD, OFF );
...@@ -643,60 +804,32 @@ void PCB_BASE_FRAME::RecalculateAllTracksNetcode() ...@@ -643,60 +804,32 @@ void PCB_BASE_FRAME::RecalculateAllTracksNetcode()
if( m_Pcb->GetPadsCount() == 0 ) if( m_Pcb->GetPadsCount() == 0 )
return; return;
// Prepare connections calculations between tracks and pads */ CONNECTIONS connections( m_Pcb );
m_Pcb->GetSortedPadListByXthenYCoord( sortedPads ); connections.BuildPadsList();
connections.BuildCandidatesList();
// First pass: build connections between track segments and pads.
connections.SearchConnectedToPads();
/* First pass: search connection between a track segment and a pad. /* For tracks connected to at least one pad,
* if found, set the track net code to the pad netcode * set the track net code to the pad netcode
*/ */
curr_track = m_Pcb->m_Track; curr_track = m_Pcb->m_Track;
for( ; curr_track != NULL; curr_track = curr_track->Next() ) for( ; curr_track != NULL; curr_track = curr_track->Next() )
{ {
int layerMask = g_TabOneLayerMask[curr_track->GetLayer()]; if( curr_track->m_PadsConnected.size() )
curr_track->SetNet( curr_track->m_PadsConnected[0]->GetNet() );
/* Search for a pad on the segment starting point */
curr_track->start = m_Pcb->GetPad( sortedPads, curr_track->m_Start, layerMask );
if( curr_track->start != NULL )
{
curr_track->SetState( BEGIN_ONPAD, ON );
curr_track->SetNet( ( (D_PAD*) (curr_track->start) )->GetNet() );
}
/* Search for a pad on the segment ending point */
curr_track->end = m_Pcb->GetPad( sortedPads, curr_track->m_End, layerMask );
if( curr_track->end != NULL )
{
curr_track->SetState( END_ONPAD, ON );
curr_track->SetNet( ( (D_PAD*) (curr_track->end) )->GetNet() );
}
} }
/*****************************************************/ // Pass 2: build connections between track ends
/* Pass 2: search the connections between track ends */
/*****************************************************/
/* the .start and .end member pointers are updated, and point on connected pads
* or are null for tracks whitch are not connection to pads
* Now build connections lists to tracks
*/
CONNECTIONS connections( m_Pcb );
connections.BuildCandidatesList();
for( curr_track = m_Pcb->m_Track; curr_track != NULL; curr_track = curr_track->Next() ) for( curr_track = m_Pcb->m_Track; curr_track != NULL; curr_track = curr_track->Next() )
{ {
if( curr_track->start != NULL && curr_track->end != NULL )
continue;
connections.SearchConnectedTracks( curr_track ); connections.SearchConnectedTracks( curr_track );
curr_track->m_TracksConnected = connections.m_Connected; connections.GetConnectedTracks( curr_track );
} }
// Propagate net codes from a segment to other connected segments // Propagate net codes from a segment to other connected segments
bool new_pass_request = true; // is true if a track has its netcode changes from 0 bool new_pass_request = true; // set to true if a track has its netcode changed from 0
// to a known netcode to re-evaluate netcodes // to a known netcode to re-evaluate netcodes
// of connected items // of connected items
while( new_pass_request ) while( new_pass_request )
...@@ -751,7 +884,7 @@ static bool SortTracksByNetCode( const TRACK* const & ref, const TRACK* const & ...@@ -751,7 +884,7 @@ static bool SortTracksByNetCode( const TRACK* const & ref, const TRACK* const &
} }
/** /**
* Function RebuildTrackChain * Helper function RebuildTrackChain
* rebuilds the track segment linked list in order to have a chain * rebuilds the track segment linked list in order to have a chain
* sorted by increasing netcodes. * sorted by increasing netcodes.
* @param pcb = board to rebuild * @param pcb = board to rebuild
...@@ -766,7 +899,7 @@ static void RebuildTrackChain( BOARD* pcb ) ...@@ -766,7 +899,7 @@ static void RebuildTrackChain( BOARD* pcb )
std::vector<TRACK*> trackList; std::vector<TRACK*> trackList;
trackList.reserve( item_count ); trackList.reserve( item_count );
for( int i = 0; i<item_count; ++i ) for( int i = 0; i < item_count; ++i )
trackList.push_back( pcb->m_Track.PopFront() ); trackList.push_back( pcb->m_Track.PopFront() );
// the list is empty now // the list is empty now
......
...@@ -216,21 +216,6 @@ bool PCB_EDIT_FRAME::Other_Layer_Route( TRACK* aTrack, wxDC* DC ) ...@@ -216,21 +216,6 @@ bool PCB_EDIT_FRAME::Other_Layer_Route( TRACK* aTrack, wxDC* DC )
} }
void PCB_EDIT_FRAME::DisplayNetStatus( wxDC* DC )
{
TRACK* pt_segm;
int layerMask = (1 << getActiveLayer());
wxPoint pos = GetScreen()->RefPos( true );
pt_segm = GetBoard()->GetTrace( GetBoard()->m_Track, pos, layerMask );
if( pt_segm == NULL )
GetBoard()->DisplayInfo( this );
else
TestNetConnection( DC, pt_segm->GetNet() );
}
void PCB_EDIT_FRAME::Show_1_Ratsnest( EDA_ITEM* item, wxDC* DC ) void PCB_EDIT_FRAME::Show_1_Ratsnest( EDA_ITEM* item, wxDC* DC )
{ {
D_PAD* pt_pad = NULL; D_PAD* pt_pad = NULL;
......
...@@ -53,13 +53,13 @@ static void EnsureEndTrackOnPad( D_PAD* Pad ); ...@@ -53,13 +53,13 @@ static void EnsureEndTrackOnPad( D_PAD* Pad );
static PICKED_ITEMS_LIST s_ItemsListPicker; static PICKED_ITEMS_LIST s_ItemsListPicker;
/* Routine to cancel the route if a track is being drawn, or exit the application EDITRACK. /* Function called to abort a track creation
*/ */
static void Abort_Create_Track( EDA_DRAW_PANEL* Panel, wxDC* DC ) static void Abort_Create_Track( EDA_DRAW_PANEL* Panel, wxDC* DC )
{ {
PCB_EDIT_FRAME* frame = (PCB_EDIT_FRAME*) Panel->GetParent(); PCB_EDIT_FRAME* frame = (PCB_EDIT_FRAME*) Panel->GetParent();
BOARD * pcb = frame->GetBoard(); BOARD* pcb = frame->GetBoard();
TRACK* track = (TRACK*) frame->GetCurItem(); TRACK* track = (TRACK*) frame->GetCurItem();
if( track && ( track->Type()==PCB_VIA_T || track->Type()==PCB_TRACE_T ) ) if( track && ( track->Type()==PCB_VIA_T || track->Type()==PCB_TRACE_T ) )
{ {
...@@ -88,16 +88,19 @@ static void Abort_Create_Track( EDA_DRAW_PANEL* Panel, wxDC* DC ) ...@@ -88,16 +88,19 @@ static void Abort_Create_Track( EDA_DRAW_PANEL* Panel, wxDC* DC )
frame->SetCurItem( NULL ); frame->SetCurItem( NULL );
} }
/*
* This function starts a new track segment.
* If a new track segment is in progress, ends this current new segment,
* and created a new one.
*/
TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
{ {
D_PAD* pt_pad = NULL;
TRACK* TrackOnStartPoint = NULL; TRACK* TrackOnStartPoint = NULL;
int layerMask = g_TabOneLayerMask[( (PCB_SCREEN*) GetScreen() )->m_Active_Layer]; int layerMask = g_TabOneLayerMask[( (PCB_SCREEN*) GetScreen() )->m_Active_Layer];
BOARD_CONNECTED_ITEM* LockPoint; BOARD_CONNECTED_ITEM* LockPoint;
wxPoint pos = GetScreen()->GetCrossHairPosition(); wxPoint pos = GetScreen()->GetCrossHairPosition();
if( aTrack == NULL ) /* Starting a new track */ if( aTrack == NULL ) /* Starting a new track segment */
{ {
DrawPanel->SetMouseCapture( ShowNewTrackWhenMovingCursor, Abort_Create_Track ); DrawPanel->SetMouseCapture( ShowNewTrackWhenMovingCursor, Abort_Create_Track );
...@@ -118,15 +121,16 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -118,15 +121,16 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
// Search for a starting point of the new track, a track or pad // Search for a starting point of the new track, a track or pad
LockPoint = GetBoard()->GetLockPoint( pos, layerMask ); LockPoint = GetBoard()->GetLockPoint( pos, layerMask );
D_PAD* pad = NULL;
if( LockPoint ) // An item (pad or track) is found if( LockPoint ) // An item (pad or track) is found
{ {
if( LockPoint->Type() == PCB_PAD_T ) if( LockPoint->Type() == PCB_PAD_T )
{ {
pt_pad = (D_PAD*) LockPoint; pad = (D_PAD*) LockPoint;
/* A pad is found: put the starting point on pad center */ /* A pad is found: put the starting point on pad center */
pos = pt_pad->m_Pos; pos = pad->m_Pos;
GetBoard()->SetHighLightNet( pt_pad->GetNet() ); GetBoard()->SetHighLightNet( pad->GetNet() );
} }
else /* A track segment is found */ else /* A track segment is found */
{ {
...@@ -171,15 +175,8 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -171,15 +175,8 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
g_CurrentTrackSegment->m_Start = pos; g_CurrentTrackSegment->m_Start = pos;
g_CurrentTrackSegment->m_End = pos; g_CurrentTrackSegment->m_End = pos;
if( pt_pad ) if( pad )
{ g_CurrentTrackSegment->m_PadsConnected.push_back( pad );
g_CurrentTrackSegment->start = pt_pad;
g_CurrentTrackSegment->SetState( BEGIN_ONPAD, ON );
}
else
{
g_CurrentTrackSegment->start = TrackOnStartPoint;
}
if( g_TwoSegmentTrackBuild ) if( g_TwoSegmentTrackBuild )
{ {
...@@ -226,7 +223,7 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -226,7 +223,7 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
/* Current track is Ok: current segment is kept, and a new one is /* Current track is Ok: current segment is kept, and a new one is
* created unless the current segment is null, or 2 last are null * created unless the current segment is null, or 2 last are null
* if a 2 segments track build. * if this is a 2 segments track build.
*/ */
bool CanCreateNewSegment = true; bool CanCreateNewSegment = true;
...@@ -248,11 +245,9 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -248,11 +245,9 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
D( g_CurrentTrackList.VerifyListIntegrity(); ); D( g_CurrentTrackList.VerifyListIntegrity(); );
if( g_Raccord_45_Auto ) if( g_Raccord_45_Auto )
{
Add45DegreeSegment( aDC ); Add45DegreeSegment( aDC );
}
TRACK* oneBeforeLatest = g_CurrentTrackSegment; TRACK* previousTrack = g_CurrentTrackSegment;
TRACK* newTrack = g_CurrentTrackSegment->Copy(); TRACK* newTrack = g_CurrentTrackSegment->Copy();
g_CurrentTrackList.PushBack( newTrack ); g_CurrentTrackList.PushBack( newTrack );
...@@ -260,15 +255,15 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -260,15 +255,15 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
newTrack->SetState( BEGIN_ONPAD | END_ONPAD, OFF ); newTrack->SetState( BEGIN_ONPAD | END_ONPAD, OFF );
oneBeforeLatest->end = GetBoard()->GetPad( oneBeforeLatest, END ); D_PAD* pad = GetBoard()->GetPad( previousTrack, END );
if( oneBeforeLatest->end ) if( pad )
{ {
oneBeforeLatest->SetState( END_ONPAD, ON ); newTrack->m_PadsConnected.push_back( pad );
newTrack->SetState( BEGIN_ONPAD, ON ); previousTrack->m_PadsConnected.push_back( pad );
} }
newTrack->start = oneBeforeLatest->end; newTrack->start = previousTrack->end;
D( g_CurrentTrackList.VerifyListIntegrity(); ); D( g_CurrentTrackList.VerifyListIntegrity(); );
...@@ -277,9 +272,7 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -277,9 +272,7 @@ TRACK* PCB_EDIT_FRAME::Begin_Route( TRACK* aTrack, wxDC* aDC )
newTrack->SetLayer( ( (PCB_SCREEN*) GetScreen() )->m_Active_Layer ); newTrack->SetLayer( ( (PCB_SCREEN*) GetScreen() )->m_Active_Layer );
if( !GetBoard()->GetBoardDesignSettings()->m_UseConnectedTrackWidth ) if( !GetBoard()->GetBoardDesignSettings()->m_UseConnectedTrackWidth )
{
newTrack->m_Width = GetBoard()->GetCurrentTrackWidth(); newTrack->m_Width = GetBoard()->GetCurrentTrackWidth();
}
D( g_CurrentTrackList.VerifyListIntegrity(); ); D( g_CurrentTrackList.VerifyListIntegrity(); );
...@@ -441,37 +434,38 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -441,37 +434,38 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC )
D( g_CurrentTrackList.VerifyListIntegrity(); ); D( g_CurrentTrackList.VerifyListIntegrity(); );
/* The track here is non chained to the list of track segments. /* The track here is now chained to the list of track segments.
* It must be seen in the area of net * It must be seen in the area of net
* As close as possible to the segment base (or end), because * As close as possible to the segment base (or end), because
* This helps to reduce the computing time */ * This helps to reduce the computing time */
/* Attaching the end of the track. */ // Attaching the end point of the new track to a pad or a track
BOARD_CONNECTED_ITEM* LockPoint = GetBoard()->GetLockPoint( pos, layerMask ); BOARD_CONNECTED_ITEM* LockPoint = GetBoard()->GetLockPoint( pos, layerMask );
if( LockPoint ) /* End of trace is on a pad. */ if( LockPoint )
{ {
if( LockPoint->Type() == PCB_PAD_T ) if( LockPoint->Type() == PCB_PAD_T ) // End of track is on a pad.
{ {
EnsureEndTrackOnPad( (D_PAD*) LockPoint ); EnsureEndTrackOnPad( (D_PAD*) LockPoint );
} }
else /* End of is on a different track, it will possibly create an anchor. */ else // If end point of is on a different track,
// creates a lock point if not exists
{ {
TRACK* adr_buf = (TRACK*) LockPoint; TRACK* adr_buf = (TRACK*) LockPoint;
GetBoard()->SetHighLightNet( adr_buf->GetNet() ); GetBoard()->SetHighLightNet( adr_buf->GetNet() );
/* Possible establishment of a hanging point. */ // Creates a lock point (if not already exists):
LockPoint = GetBoard()->CreateLockPoint( g_CurrentTrackSegment->m_End, LockPoint = GetBoard()->CreateLockPoint( g_CurrentTrackSegment->m_End,
adr_buf, adr_buf,
&s_ItemsListPicker ); &s_ItemsListPicker );
} }
} }
// Delete Null segments: // Delete null length segments:
DeleteNullTrackSegments( GetBoard(), g_CurrentTrackList ); DeleteNullTrackSegments( GetBoard(), g_CurrentTrackList );
// Insert new segments if they exist. This can be NULL on a double click // Insert new segments if they exist.
// on the start point // g_FirstTrackSegment can be NULL on a double click on the starting point
if( g_FirstTrackSegment != NULL ) if( g_FirstTrackSegment != NULL )
{ {
int netcode = g_FirstTrackSegment->GetNet(); int netcode = g_FirstTrackSegment->GetNet();
...@@ -491,17 +485,15 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -491,17 +485,15 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC )
TraceAirWiresToTargets( aDC ); TraceAirWiresToTargets( aDC );
DrawTraces( DrawPanel, aDC, firstTrack, newCount, GR_OR );
int i = 0; int i = 0;
for( track = firstTrack; track && i<newCount; ++i, track = track->Next() ) for( track = firstTrack; track && i < newCount; ++i, track = track->Next() )
{ {
track->m_Flags = 0; track->m_Flags = 0;
track->SetState( BUSY, OFF ); track->SetState( BUSY, OFF );
} }
// erase the old track, if exists // delete the old track, if it exists and is redundant
if( g_AutoDeleteOldTrack ) if( g_AutoDeleteOldTrack )
{ {
EraseRedundantTrack( aDC, firstTrack, newCount, &s_ItemsListPicker ); EraseRedundantTrack( aDC, firstTrack, newCount, &s_ItemsListPicker );
...@@ -510,10 +502,13 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC ) ...@@ -510,10 +502,13 @@ bool PCB_EDIT_FRAME::End_Route( TRACK* aTrack, wxDC* aDC )
SaveCopyInUndoList( s_ItemsListPicker, UR_UNSPECIFIED ); SaveCopyInUndoList( s_ItemsListPicker, UR_UNSPECIFIED );
s_ItemsListPicker.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items s_ItemsListPicker.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
/* compute the new ratsnest */ // compute the new ratsnest
TestNetConnection( aDC, netcode ); TestNetConnection( aDC, netcode );
OnModify(); OnModify();
GetBoard()->DisplayInfo( this ); GetBoard()->DisplayInfo( this );
// Redraw the entire new track.
DrawTraces( DrawPanel, aDC, firstTrack, newCount, GR_OR );
} }
wxASSERT( g_FirstTrackSegment == NULL ); wxASSERT( g_FirstTrackSegment == NULL );
...@@ -982,7 +977,7 @@ void ComputeBreakPoint( TRACK* track, int SegmentCount, wxPoint end ) ...@@ -982,7 +977,7 @@ void ComputeBreakPoint( TRACK* track, int SegmentCount, wxPoint end )
} }
/* Delete track segments which have len = 0; after creating a new track /* Delete track segments which have len = 0 after creating a new track
* return a pointer on the first segment (start of track list) * return a pointer on the first segment (start of track list)
*/ */
void DeleteNullTrackSegments( BOARD* pcb, DLIST<TRACK>& aTrackList ) void DeleteNullTrackSegments( BOARD* pcb, DLIST<TRACK>& aTrackList )
...@@ -1034,7 +1029,7 @@ void DeleteNullTrackSegments( BOARD* pcb, DLIST<TRACK>& aTrackList ) ...@@ -1034,7 +1029,7 @@ void DeleteNullTrackSegments( BOARD* pcb, DLIST<TRACK>& aTrackList )
firsttrack->start = LockPoint; firsttrack->start = LockPoint;
if( LockPoint && LockPoint->Type()==PCB_PAD_T ) if( LockPoint && LockPoint->Type()==PCB_PAD_T )
firsttrack->SetState( BEGIN_ONPAD, ON ); firsttrack->SetState( BEGIN_ONPAD, ON );
track = firsttrack; track = firsttrack;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment