/* * KiRouter - a push-and-(sometimes-)shove PCB router * * Copyright (C) 2013-2014 CERN * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. * Author: Tomasz Wlostowski * * 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pns_node.h" #include "pns_line_placer.h" #include "pns_line.h" #include "pns_solid.h" #include "pns_utils.h" #include "pns_router.h" #include "pns_shove.h" #include "pns_dragger.h" #include "pns_component_dragger.h" #include "pns_topology.h" #include "pns_diff_pair_placer.h" #include "pns_meander_placer.h" #include "pns_meander_skew_placer.h" #include "pns_dp_meander_placer.h" namespace PNS { // an ugly singleton for drawing debug items within the router context. // To be fixed sometime in the future. static ROUTER* theRouter; ROUTER::ROUTER() { theRouter = this; m_state = IDLE; m_mode = PNS_MODE_ROUTE_SINGLE; m_logger = nullptr; if( ADVANCED_CFG::GetCfg().m_EnableRouterDump ) m_logger = new LOGGER; // Initialize all other variables: m_lastNode = nullptr; m_iterLimit = 0; m_settings = nullptr; m_iface = nullptr; m_visibleViewArea.SetMaximum(); } ROUTER* ROUTER::GetInstance() { return theRouter; } ROUTER::~ROUTER() { ClearWorld(); theRouter = nullptr; delete m_logger; } void ROUTER::SyncWorld() { ClearWorld(); m_world = std::make_unique( ); m_iface->SyncWorld( m_world.get() ); m_world->FixupVirtualVias(); } void ROUTER::ClearWorld() { if( m_world ) { m_world->SetRuleResolver( nullptr ); m_world->KillChildren(); m_world.reset(); } m_placer.reset(); } bool ROUTER::RoutingInProgress() const { return m_state != IDLE; } const ITEM_SET ROUTER::QueryHoverItems( const VECTOR2I& aP, bool aUseClearance ) { NODE* node = nullptr; int clearance = 0; if( m_state == IDLE || m_placer == nullptr ) { node = m_world.get(); clearance = 0; } else if( m_mode == PNS_MODE_ROUTE_SINGLE ) { node = m_placer->CurrentNode(); clearance = m_sizes.Clearance() + m_sizes.TrackWidth() / 2; } else if( m_mode == PNS_MODE_ROUTE_DIFF_PAIR ) { node = m_placer->CurrentNode(); clearance = m_sizes.Clearance() + m_sizes.DiffPairWidth() / 2; } PNS::ITEM_SET ret; wxCHECK( node, ret ); if( aUseClearance ) { NODE::OBSTACLES obs; SEGMENT test( SEG( aP, aP ), nullptr ); COLLISION_SEARCH_OPTIONS opts; test.SetWidth( 1 ); test.SetLayers( LAYER_RANGE::All() ); opts.m_differentNetsOnly = false; opts.m_overrideClearance = clearance; node->QueryColliding( &test, obs, opts ); for( const OBSTACLE& obstacle : obs ) ret.Add( obstacle.m_item, false ); return ret; } else { return node->HitTest( aP ); } } bool ROUTER::StartDragging( const VECTOR2I& aP, ITEM* aItem, int aDragMode ) { return StartDragging( aP, ITEM_SET( aItem ), aDragMode ); } bool ROUTER::StartDragging( const VECTOR2I& aP, ITEM_SET aStartItems, int aDragMode ) { if( aStartItems.Empty() ) return false; GetRuleResolver()->ClearCaches(); if( aStartItems.Count( ITEM::SOLID_T ) == aStartItems.Size() ) { m_dragger = std::make_unique( this ); m_state = DRAG_COMPONENT; } else { m_dragger = std::make_unique( this ); m_state = DRAG_SEGMENT; } m_dragger->SetMode( static_cast( aDragMode ) ); m_dragger->SetWorld( m_world.get() ); m_dragger->SetLogger( m_logger ); m_dragger->SetDebugDecorator( m_iface->GetDebugDecorator() ); if( m_logger ) m_logger->Clear(); if( m_logger && aStartItems.Size() ) m_logger->Log( LOGGER::EVT_START_DRAG, aP, aStartItems[0] ); if( m_dragger->Start( aP, aStartItems ) ) { return true; } else { m_dragger.reset(); m_state = IDLE; return false; } } bool ROUTER::isStartingPointRoutable( const VECTOR2I& aWhere, ITEM* aStartItem, int aLayer ) { if( Settings().AllowDRCViolations() ) return true; if( m_mode == PNS_MODE_ROUTE_DIFF_PAIR ) { if( m_sizes.DiffPairGap() < m_sizes.MinClearance() ) { SetFailureReason( _( "Diff pair gap is less than board minimum clearance." ) ); return false; } } ITEM_SET candidates = QueryHoverItems( aWhere ); wxString failureReason; for( ITEM* item : candidates.Items() ) { // Edge cuts are put on all layers, but they're not *really* on all layers if( item->BoardItem() && item->BoardItem()->GetLayer() == Edge_Cuts ) continue; if( !item->Layers().Overlaps( aLayer ) ) continue; if( item->IsRoutable() ) { failureReason = wxEmptyString; break; } else { BOARD_ITEM* parent = item->BoardItem(); switch( parent->Type() ) { case PCB_PAD_T: { PAD* pad = static_cast( parent ); if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) failureReason = _( "Cannot start routing from a non-plated hole." ); } break; case PCB_ZONE_T: { ZONE* zone = static_cast( parent ); if( !zone->GetZoneName().IsEmpty() ) { failureReason = wxString::Format( _( "Rule area '%s' disallows tracks." ), zone->GetZoneName() ); } else { failureReason = _( "Rule area disallows tracks." ); } } break; case PCB_FIELD_T: case PCB_TEXT_T: case PCB_TEXTBOX_T: failureReason = _( "Cannot start routing from a text item." ); break; default: break; } } } if( !failureReason.IsEmpty() ) { SetFailureReason( failureReason ); return false; } VECTOR2I startPoint = aWhere; if( m_mode == PNS_MODE_ROUTE_SINGLE ) { SHAPE_LINE_CHAIN dummyStartSeg; LINE dummyStartLine; dummyStartSeg.Append( startPoint ); dummyStartSeg.Append( startPoint, true ); dummyStartLine.SetShape( dummyStartSeg ); dummyStartLine.SetLayer( aLayer ); dummyStartLine.SetNet( aStartItem ? aStartItem->Net() : 0 ); dummyStartLine.SetWidth( m_sizes.TrackWidth() ); if( m_world->CheckColliding( &dummyStartLine, ITEM::ANY_T ) ) { // If the only reason we collide is track width; it's better to allow the user to start // anyway and just highlight the resulting collisions, so they can change width later. dummyStartLine.SetWidth( m_sizes.BoardMinTrackWidth() ); if( m_world->CheckColliding( &dummyStartLine, ITEM::ANY_T ) ) { ITEM_SET dummyStartSet( &dummyStartLine ); NODE::ITEM_VECTOR highlightedItems; markViolations( m_world.get(), dummyStartSet, highlightedItems ); for( ITEM* item : highlightedItems ) m_iface->HideItem( item ); SetFailureReason( _( "The routing start point violates DRC." ) ); return false; } } } else if( m_mode == PNS_MODE_ROUTE_DIFF_PAIR ) { if( !aStartItem ) { SetFailureReason( _( "Cannot start a differential pair in the middle of nowhere." ) ); return false; } DP_PRIMITIVE_PAIR dpPair; wxString errorMsg; if( !DIFF_PAIR_PLACER::FindDpPrimitivePair( m_world.get(), startPoint, aStartItem, dpPair, &errorMsg ) ) { SetFailureReason( errorMsg ); return false; } SHAPE_LINE_CHAIN dummyStartSegA; SHAPE_LINE_CHAIN dummyStartSegB; LINE dummyStartLineA; LINE dummyStartLineB; dummyStartSegA.Append( dpPair.AnchorN() ); dummyStartSegA.Append( dpPair.AnchorN(), true ); dummyStartSegB.Append( dpPair.AnchorP() ); dummyStartSegB.Append( dpPair.AnchorP(), true ); dummyStartLineA.SetShape( dummyStartSegA ); dummyStartLineA.SetLayer( aLayer ); dummyStartLineA.SetNet( dpPair.PrimN()->Net() ); dummyStartLineA.SetWidth( m_sizes.DiffPairWidth() ); dummyStartLineB.SetShape( dummyStartSegB ); dummyStartLineB.SetLayer( aLayer ); dummyStartLineB.SetNet( dpPair.PrimP()->Net() ); dummyStartLineB.SetWidth( m_sizes.DiffPairWidth() ); if( m_world->CheckColliding( &dummyStartLineA, ITEM::ANY_T ) || m_world->CheckColliding( &dummyStartLineB, ITEM::ANY_T ) ) { // If the only reason we collide is track width; it's better to allow the user to start // anyway and just highlight the resulting collisions, so they can change width later. dummyStartLineA.SetWidth( m_sizes.BoardMinTrackWidth() ); dummyStartLineB.SetWidth( m_sizes.BoardMinTrackWidth() ); if( m_world->CheckColliding( &dummyStartLineA, ITEM::ANY_T ) || m_world->CheckColliding( &dummyStartLineB, ITEM::ANY_T ) ) { ITEM_SET dummyStartSet; NODE::ITEM_VECTOR highlightedItems; dummyStartSet.Add( dummyStartLineA ); dummyStartSet.Add( dummyStartLineB ); markViolations( m_world.get(), dummyStartSet, highlightedItems ); for( ITEM* item : highlightedItems ) m_iface->HideItem( item ); SetFailureReason( _( "The routing start point violates DRC." ) ); return false; } } } return true; } bool ROUTER::StartRouting( const VECTOR2I& aP, ITEM* aStartItem, int aLayer ) { GetRuleResolver()->ClearCaches(); if( !isStartingPointRoutable( aP, aStartItem, aLayer ) ) return false; switch( m_mode ) { case PNS_MODE_ROUTE_SINGLE: m_placer = std::make_unique( this ); break; case PNS_MODE_ROUTE_DIFF_PAIR: m_placer = std::make_unique( this ); break; case PNS_MODE_TUNE_SINGLE: m_placer = std::make_unique( this ); break; case PNS_MODE_TUNE_DIFF_PAIR: m_placer = std::make_unique( this ); break; case PNS_MODE_TUNE_DIFF_PAIR_SKEW: m_placer = std::make_unique( this ); break; default: return false; } m_placer->UpdateSizes( m_sizes ); m_placer->SetLayer( aLayer ); m_placer->SetDebugDecorator( m_iface->GetDebugDecorator() ); m_placer->SetLogger( m_logger ); if( m_placer->Start( aP, aStartItem ) ) { m_state = ROUTE_TRACK; if( m_logger ) { m_logger->Clear(); m_logger->Log( LOGGER::EVT_START_ROUTE, aP, aStartItem, &m_sizes ); } return true; } else { m_state = IDLE; return false; } } bool ROUTER::Move( const VECTOR2I& aP, ITEM* endItem ) { if( m_logger ) m_logger->Log( LOGGER::EVT_MOVE, aP, endItem ); switch( m_state ) { case ROUTE_TRACK: return movePlacing( aP, endItem ); case DRAG_SEGMENT: case DRAG_COMPONENT: return moveDragging( aP, endItem ); default: break; } GetRuleResolver()->ClearTemporaryCaches(); return false; } bool ROUTER::getNearestRatnestAnchor( VECTOR2I& aOtherEnd, LAYER_RANGE& aOtherEndLayers, ITEM*& aOtherEndItem ) { // Can't finish something with no connections if( GetCurrentNets().empty() ) return false; PLACEMENT_ALGO* placer = Placer(); if( placer == nullptr || placer->Traces().Size() == 0 ) return false; LINE* trace = dynamic_cast( placer->Traces()[0] ); if( trace == nullptr ) return false; PNS::NODE* lastNode = placer->CurrentNode( true ); PNS::TOPOLOGY topo( lastNode ); // If the user has drawn a line, get the anchor nearest to the line end if( trace->SegmentCount() > 0 ) { return topo.NearestUnconnectedAnchorPoint( trace, aOtherEnd, aOtherEndLayers, aOtherEndItem ); } // Otherwise, find the closest anchor to our start point // Get joint from placer start item const JOINT* jt = lastNode->FindJoint( placer->CurrentStart(), placer->CurrentLayer(), placer->CurrentNets()[0] ); if( !jt ) return false; // Get unconnected item from joint int anchor; PNS::ITEM* it = topo.NearestUnconnectedItem( jt, &anchor ); if( !it ) return false; aOtherEnd = it->Anchor( anchor ); aOtherEndLayers = it->Layers(); aOtherEndItem = it; return true; } bool ROUTER::Finish() { if( m_state != ROUTE_TRACK ) return false; PLACEMENT_ALGO* placer = Placer(); if( placer == nullptr || placer->Traces().Size() == 0 ) return false; LINE* current = dynamic_cast( placer->Traces()[0] ); if( current == nullptr ) return false; // Get our current line and position and nearest ratsnest to them if it exists VECTOR2I otherEnd; LAYER_RANGE otherEndLayers; ITEM* otherEndItem = nullptr; // Get the anchor nearest to the end of the trace the user is routing if( !getNearestRatnestAnchor( otherEnd, otherEndLayers, otherEndItem ) ) return false; // Keep moving until we don't change position or hit the limit int triesLeft = 5; VECTOR2I moveResultPoint; do { moveResultPoint = placer->CurrentEnd(); Move( otherEnd, otherEndItem ); triesLeft--; } while( placer->CurrentEnd() != moveResultPoint && triesLeft ); // If we've made it, fix the route and we're done if( moveResultPoint == otherEnd && otherEndLayers.Overlaps( GetCurrentLayer() ) ) return FixRoute( otherEnd, otherEndItem, false ); return false; } bool ROUTER::ContinueFromEnd( ITEM** aNewStartItem ) { PLACEMENT_ALGO* placer = Placer(); if( placer == nullptr || placer->Traces().Size() == 0 ) return false; LINE* current = dynamic_cast( placer->Traces()[0] ); if( current == nullptr ) return false; int currentLayer = GetCurrentLayer(); VECTOR2I currentEnd = placer->CurrentEnd(); VECTOR2I otherEnd; LAYER_RANGE otherEndLayers; ITEM* otherEndItem = nullptr; // Get the anchor nearest to the end of the trace the user is routing if( !getNearestRatnestAnchor( otherEnd, otherEndLayers, otherEndItem ) ) return false; CommitRouting(); // Commit whatever we've fixed and restart routing from the other end int nextLayer = otherEndLayers.Overlaps( currentLayer ) ? currentLayer : otherEndLayers.Start(); if( !StartRouting( otherEnd, otherEndItem, nextLayer ) ) return false; // Attempt to route to our current position Move( currentEnd, nullptr ); *aNewStartItem = otherEndItem; return true; } bool ROUTER::moveDragging( const VECTOR2I& aP, ITEM* aEndItem ) { m_iface->EraseView(); bool ret = m_dragger->Drag( aP ); ITEM_SET dragged = m_dragger->Traces(); updateView( m_dragger->CurrentNode(), dragged, true ); return ret; } void ROUTER::markViolations( NODE* aNode, ITEM_SET& aCurrent, NODE::ITEM_VECTOR& aRemoved ) { auto updateItem = [&]( ITEM* currentItem, ITEM* itemToMark ) { std::unique_ptr tmp( itemToMark->Clone() ); int clearance; bool removeOriginal = true; clearance = aNode->GetClearance( currentItem, itemToMark ); if( itemToMark->Layers().IsMultilayer() && !currentItem->Layers().IsMultilayer() ) tmp->SetLayer( currentItem->Layer() ); if( itemToMark->IsCompoundShapePrimitive() ) { // We're only highlighting one (or more) of several primitives so we don't // want all the other parts of the object to disappear removeOriginal = false; } m_iface->DisplayItem( tmp.get(), clearance ); if( removeOriginal ) aRemoved.push_back( itemToMark ); }; for( ITEM* item : aCurrent.Items() ) { NODE::OBSTACLES obstacles; aNode->QueryColliding( item, obstacles ); if( item->OfKind( ITEM::LINE_T ) ) { LINE* l = static_cast( item ); if( l->EndsWithVia() ) { VIA v( l->Via() ); aNode->QueryColliding( &v, obstacles ); } } ITEM_SET draggedItems; if( GetDragger() ) draggedItems = GetDragger()->Traces(); for( const OBSTACLE& obs : obstacles ) { // Don't mark items being dragged; only board items they collide with if( draggedItems.Contains( obs.m_item ) ) continue; obs.m_item->Mark( obs.m_item->Marker() | MK_VIOLATION ); updateItem( item, obs.m_item ); } if( item->Kind() == ITEM::LINE_T ) { LINE* line = static_cast( item ); // Show clearance on any blocking obstacles if( line->GetBlockingObstacle() ) updateItem( item, line->GetBlockingObstacle() ); } } } void ROUTER::updateView( NODE* aNode, ITEM_SET& aCurrent, bool aDragging ) { NODE::ITEM_VECTOR removed, added; NODE::OBSTACLES obstacles; if( !aNode ) return; markViolations( aNode, aCurrent, removed ); aNode->GetUpdatedItems( removed, added ); std::vector cacheCheckItems( added.begin(), added.end() ); GetRuleResolver()->ClearCacheForItems( cacheCheckItems ); for( ITEM* item : added ) { int clearance = GetRuleResolver()->Clearance( item, nullptr ); m_iface->DisplayItem( item, clearance, aDragging ); } for( ITEM* item : removed ) m_iface->HideItem( item ); } void ROUTER::UpdateSizes( const SIZES_SETTINGS& aSizes ) { m_sizes = aSizes; // Change track/via size settings if( m_state == ROUTE_TRACK ) m_placer->UpdateSizes( m_sizes ); } bool ROUTER::movePlacing( const VECTOR2I& aP, ITEM* aEndItem ) { m_iface->EraseView(); bool ret = m_placer->Move( aP, aEndItem ); ITEM_SET current = m_placer->Traces(); for( const ITEM* item : current.CItems() ) { if( !item->OfKind( ITEM::LINE_T ) ) continue; const LINE* l = static_cast( item ); int clearance = GetRuleResolver()->Clearance( item, nullptr ); m_iface->DisplayItem( l, clearance, false, true ); if( l->EndsWithVia() ) { const VIA& via = l->Via(); clearance = GetRuleResolver()->Clearance( &via, nullptr ); if( via.HasHole() ) { int holeClearance = GetRuleResolver()->Clearance( via.Hole(), nullptr ); int annularWidth = std::max( 0, via.Diameter() - via.Drill() ) / 2; int excessHoleClearance = holeClearance - annularWidth; if( excessHoleClearance > clearance ) clearance = excessHoleClearance; } m_iface->DisplayItem( &l->Via(), clearance, false, true ); } } //ITEM_SET tmp( ¤t ); updateView( m_placer->CurrentNode( true ), current ); return ret; } void ROUTER::GetUpdatedItems( std::vector& aRemoved, std::vector& aAdded, std::vector& aHeads ) { NODE *node = nullptr; ITEM_SET current; if( m_state == ROUTE_TRACK ) { node = m_placer->CurrentNode( true ); current = m_placer->Traces(); } else if ( m_state == DRAG_SEGMENT ) { node = m_dragger->CurrentNode(); current = m_dragger->Traces(); } // There probably should be a debugging assertion and possibly a PNS_LOGGER call here but // I'm not sure how to be proceed WLS. if( !node ) return; node->GetUpdatedItems( aRemoved, aAdded ); for( const ITEM* item : current.CItems() ) aHeads.push_back( item->Clone() ); } void ROUTER::CommitRouting( NODE* aNode ) { if( m_state == ROUTE_TRACK && !m_placer->HasPlacedAnything() ) return; NODE::ITEM_VECTOR removed; NODE::ITEM_VECTOR added; NODE::ITEM_VECTOR changed; aNode->GetUpdatedItems( removed, added ); for( ITEM* item : removed ) { bool is_changed = false; // Items in remove/add that share the same parent are just updated versions // We move them to the updated vector to preserve attributes such as UUID and pad data if( item->Parent() ) { for( NODE::ITEM_VECTOR::iterator added_it = added.begin(); added_it != added.end(); ++added_it ) { if( ( *added_it )->Parent() && ( *added_it )->Parent() == item->Parent() ) { changed.push_back( *added_it ); added.erase( added_it ); is_changed = true; break; } } } if( !is_changed && !item->IsVirtual() ) m_iface->RemoveItem( item ); } for( ITEM* item : added ) { if( !item->IsVirtual() ) m_iface->AddItem( item ); } for( ITEM* item : changed ) { if( !item->IsVirtual() ) m_iface->UpdateItem( item ); } m_iface->Commit(); m_world->Commit( aNode ); } bool ROUTER::FixRoute( const VECTOR2I& aP, ITEM* aEndItem, bool aForceFinish ) { bool rv = false; if( m_logger ) m_logger->Log( LOGGER::EVT_FIX, aP, aEndItem ); switch( m_state ) { case ROUTE_TRACK: rv = m_placer->FixRoute( aP, aEndItem, aForceFinish ); break; case DRAG_SEGMENT: case DRAG_COMPONENT: rv = m_dragger->FixRoute(); break; default: break; } return rv; } std::optional ROUTER::UndoLastSegment() { if( !RoutingInProgress() ) return std::nullopt; if( m_logger ) m_logger->Log( LOGGER::EVT_UNFIX ); return m_placer->UnfixRoute(); } void ROUTER::CommitRouting() { if( m_state == ROUTE_TRACK ) m_placer->CommitPlacement(); StopRouting(); } void ROUTER::StopRouting() { // Update the ratsnest with new changes if( m_placer ) { std::vector nets; m_placer->GetModifiedNets( nets ); // Update the ratsnest with new changes for( NET_HANDLE n : nets ) m_iface->UpdateNet( n ); } if( !RoutingInProgress() ) return; m_placer.reset(); m_dragger.reset(); m_iface->EraseView(); m_state = IDLE; m_world->KillChildren(); m_world->ClearRanks(); } void ROUTER::ClearViewDecorations() { m_iface->EraseView(); } void ROUTER::FlipPosture() { if( m_state == ROUTE_TRACK ) { m_placer->FlipPosture(); } } bool ROUTER::SwitchLayer( int aLayer ) { if( m_state == ROUTE_TRACK ) return m_placer->SetLayer( aLayer ); return false; } void ROUTER::ToggleViaPlacement() { if( m_state == ROUTE_TRACK ) { bool toggle = !m_placer->IsPlacingVia(); m_placer->ToggleVia( toggle ); if( m_logger ) m_logger->Log( LOGGER::EVT_TOGGLE_VIA, VECTOR2I(), nullptr, &m_sizes ); } } const std::vector ROUTER::GetCurrentNets() const { if( m_placer ) return m_placer->CurrentNets(); else if( m_dragger ) return m_dragger->CurrentNets(); return std::vector(); } int ROUTER::GetCurrentLayer() const { if( m_placer ) return m_placer->CurrentLayer(); else if( m_dragger ) return m_dragger->CurrentLayer(); return -1; } LOGGER* ROUTER::Logger() { return m_logger; } bool ROUTER::IsPlacingVia() const { if( !m_placer ) return false; return m_placer->IsPlacingVia(); } void ROUTER::ToggleCornerMode() { DIRECTION_45::CORNER_MODE mode = m_settings->GetCornerMode(); switch( m_settings->GetCornerMode() ) { case DIRECTION_45::CORNER_MODE::MITERED_45: mode = DIRECTION_45::CORNER_MODE::ROUNDED_45; break; case DIRECTION_45::CORNER_MODE::ROUNDED_45: mode = DIRECTION_45::CORNER_MODE::MITERED_90; break; case DIRECTION_45::CORNER_MODE::MITERED_90: mode = DIRECTION_45::CORNER_MODE::ROUNDED_90; break; case DIRECTION_45::CORNER_MODE::ROUNDED_90: mode = DIRECTION_45::CORNER_MODE::MITERED_45; break; } m_settings->SetCornerMode( mode ); } void ROUTER::SetOrthoMode( bool aEnable ) { if( !m_placer ) return; m_placer->SetOrthoMode( aEnable ); } void ROUTER::SetMode( ROUTER_MODE aMode ) { m_mode = aMode; } void ROUTER::SetInterface( ROUTER_IFACE *aIface ) { m_iface = aIface; } void ROUTER::BreakSegment( ITEM *aItem, const VECTOR2I& aP ) { NODE *node = m_world->Branch(); LINE_PLACER placer( this ); if( placer.SplitAdjacentSegments( node, aItem, aP ) ) { CommitRouting( node ); } else { delete node; } } }