diff --git a/pcbnew/router/CMakeLists.txt b/pcbnew/router/CMakeLists.txt index b6c3f2166a..c57ee89e29 100644 --- a/pcbnew/router/CMakeLists.txt +++ b/pcbnew/router/CMakeLists.txt @@ -43,6 +43,7 @@ set( PCBNEW_PNS_SRCS pns_utils.cpp pns_via.cpp pns_walkaround.cpp + pns_multi_dragger.cpp router_preview_item.cpp router_status_view_item.cpp router_tool.cpp diff --git a/pcbnew/router/pns_multi_dragger.cpp b/pcbnew/router/pns_multi_dragger.cpp new file mode 100644 index 0000000000..ac72ad1ea6 --- /dev/null +++ b/pcbnew/router/pns_multi_dragger.cpp @@ -0,0 +1,1041 @@ +/* + * KiRouter - a push-and-(sometimes-)shove PCB router + * + * Copyright (C) 2013-2014 CERN + * Copyright (C) 2016-2021 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 "pns_multi_dragger.h" +#include "pns_router.h" +#include "pns_debug_decorator.h" +#include "pns_walkaround.h" +#include "pns_shove.h" + +namespace PNS +{ + +MULTI_DRAGGER::MULTI_DRAGGER( ROUTER* aRouter ) : DRAG_ALGO( aRouter ) +{ + m_world = nullptr; + m_lastNode = nullptr; +} + + +MULTI_DRAGGER::~MULTI_DRAGGER() +{ +} + +// here we initialize everything that's needed for multidrag. this means: +bool MULTI_DRAGGER::Start( const VECTOR2I& aP, ITEM_SET& aPrimitives ) +{ + m_lastNode = nullptr; + m_dragStatus = false; + m_dragStartPoint = aP; + + // check if the initial ("leader") primitive set is empty... + if( aPrimitives.Empty() ) + return false; + + m_mdragLines.clear(); + + // find all LINEs to be dragged. Indicate the LINE that contains the point (aP) + // as the "primary line", the multidrag algo will place all other lines in such way + // that the cursor position lies on the primary line. + for( ITEM* pitem : aPrimitives.Items() ) + { + LINKED_ITEM* litem = static_cast( pitem ); + bool redundant = false; + for( auto& l : m_mdragLines ) + { + if( l.originalLine.ContainsLink( litem ) ) + { + l.originalLeaders.push_back( litem ); + redundant = true; + break; + } + } + + // we can possibly have multiple SEGMENTs in aPrimitives that belong to the same line. + // We reject these. + if( !redundant ) + { + MDRAG_LINE l; + l.originalLine = m_world->AssembleLine( litem ); + l.originalLeaders.push_back( litem ); + l.isDraggable = true; + m_mdragLines.push_back( l ); + } + } + + //fprintf( stderr, "mdraGL: %d\n", (int) m_mdragLines.size() ); + + int n = 0; + + // bool foundCorner = false; +// bool foundMidSegment = false; + + // now, look for the primary line, e.g. the one aP lies on. This trivial + // example only supports dragging corners, not bodies of the tracks (yet). +// MDRAG_LINE* nearestLine = nullptr; + // int nearestCornerDist = std::numeric_limits::max(); +// int nearestMidSegDist = std::numeric_limits::max(); + //VECTOR2I nearestCorner; + bool anyStrictCornersFound = false; + bool anyStrictMidSegsFound = false; + + for( auto& l : m_mdragLines ) + { + const int thr = l.originalLine.Width() / 2; + + const VECTOR2I& origFirst = l.originalLine.CLine().CPoint( 0 ); + const int distFirst = ( origFirst - aP ).EuclideanNorm(); + + const VECTOR2I& origLast = l.originalLine.CLine().CPoint( -1 ); + const int distLast = ( origLast - aP ).EuclideanNorm(); + + l.cornerDistance = std::min( distFirst, distLast ); + + // printf("[%d %d]\n", distFirst, distLast ); + + if( aPrimitives.FindVertex( origLast ) ) + { + // printf("distlast %d thr %d\n", distLast, thr ); + + l.cornerIsLast = true; + l.leaderSegIndex = l.originalLine.SegmentCount() - 1; + l.cornerDistance = distLast; + l.isCorner = true; + + if( distLast <= thr ) + { + l.isStrict = true; + l.cornerDistance = 0; + } + } + + if( aPrimitives.FindVertex( origFirst ) ) + { + // printf("distfirst %d thr %d\n", distFirst, thr ); + + l.cornerIsLast = false; + l.leaderSegIndex = 0; + l.cornerDistance = distFirst; + l.isCorner = true; + + if( distFirst <= thr ) + { + l.isStrict = true; + l.cornerDistance = 0; + } + + } + + + const auto& links = l.originalLine.Links(); + + for( int lidx = 0; lidx < (int) links.size(); lidx++ ) + { + if( auto lseg = dyn_cast( links[lidx] ) ) + { + + if( !aPrimitives.Contains( lseg ) ) + continue; + + int d = lseg->Seg().Distance( aP ); + + l.midSeg = lseg->Seg(); + l.isMidSeg = true; + l.leaderSegIndex = lidx; + l.leaderSegDistance = d + thr; + + if( d < thr && !l.isStrict ) + { + l.isCorner = false; + l.isStrict = true; + l.leaderSegDistance = 0; + } + } + } + + if( l.isStrict ) + { + anyStrictCornersFound |= l.isCorner; + anyStrictMidSegsFound |= !l.isCorner; + } + } + + auto iface = ROUTER::GetInstance()->GetInterface(); + + printf("Drag Start [any-c %d any-m %d]:\n", anyStrictCornersFound?1:0, anyStrictMidSegsFound?1:0 ); + + for( auto& l : m_mdragLines ) + { + printf(" - net %-30s: isCorner %d isStrict %d c-Dist %-10d l-dist %-10d leadIndex %-2d CisLast %d\n", + iface->GetNetName( l.draggedLine.Net() ).c_str().AsChar(), + l.isCorner?1:0, + l.isStrict?1:0, + l.cornerDistance, + l.leaderSegDistance, + l.leaderSegIndex, + l.cornerIsLast?1:0 ); + } + + if( anyStrictCornersFound ) + m_dragMode = DM_CORNER; + else if (anyStrictMidSegsFound ) + m_dragMode = DM_SEGMENT; + else + { + int minLeadSegDist = std::numeric_limits::max(); + int minCornerDist = std::numeric_limits::max(); + MDRAG_LINE *bestSeg = nullptr; + MDRAG_LINE *bestCorner = nullptr; + + for( auto& l : m_mdragLines ) + { + if( l.cornerDistance < minCornerDist ) + { + minCornerDist = l.cornerDistance; + bestCorner = &l; + } + if( l.leaderSegDistance < minLeadSegDist ) + { + minLeadSegDist = l.leaderSegDistance; + bestSeg = &l; + } + } + + if( bestCorner && bestSeg ) + { + if( minCornerDist < minLeadSegDist ) + { + m_dragMode = DM_CORNER; + bestCorner->isPrimaryLine = true; + } + else + { + m_dragMode = DM_SEGMENT; + bestSeg->isPrimaryLine = true; + } + } + else if ( bestCorner ) + { + m_dragMode = DM_CORNER; + bestCorner->isPrimaryLine = true; + } + else if ( bestSeg ) + { + m_dragMode = DM_SEGMENT; + bestSeg->isPrimaryLine = true; + } + else return false; // can it really happen? + } + + if( m_dragMode == DM_CORNER ) + { + for( auto& l : m_mdragLines ) + { + // make sure the corner to drag is the last one + if ( !l.cornerIsLast ) + { + l.originalLine.Reverse(); + l.cornerIsLast = true; + } + // and if it's connected (non-trivial fanout), disregard it + + const JOINT* jt = m_world->FindJoint( l.originalLine.CPoint( -1 ), &l.originalLine ); + + assert (jt != nullptr); + + if( !jt->IsTrivialEndpoint() ) + { + m_dragMode = DM_SEGMENT; // fallback to segment mode if non-trivial endpoints found + } + } + } + + for( auto& l : m_mdragLines ) + { + if( (anyStrictCornersFound || anyStrictMidSegsFound) && l.isStrict ) + { + l.isPrimaryLine = true; + break; + } + } + + m_origDraggedItems = aPrimitives; + + if( Settings().Mode() == RM_Shove ) + { + m_preShoveNode = m_world->Branch(); + + for( auto& l : m_mdragLines ) + { + m_preShoveNode->Remove( l.originalLine ); + } + + m_shove.reset( new SHOVE( m_preShoveNode, Router() ) ); + m_shove->SetLogger( Logger() ); + m_shove->SetDebugDecorator( Dbg() ); + m_shove->SetDefaultShovePolicy( SHOVE::SHP_SHOVE ); + } + + return true; +} + + +void MULTI_DRAGGER::SetMode( PNS::DRAG_MODE aMode ) +{ +} + + +PNS::DRAG_MODE MULTI_DRAGGER::Mode() const +{ + return DM_CORNER; +} + +bool clipToOtherLine( NODE* aNode, const LINE& aRef, LINE& aClipped ) +{ + std::vector obstacles; + COLLISION_SEARCH_CONTEXT ctx( obstacles ); + + constexpr int clipLengthThreshold = 100; + + auto dbg = ROUTER::GetInstance()->GetInterface()->GetDebugDecorator(); + + LINE l( aClipped ); + SHAPE_LINE_CHAIN tightest; + + bool didClip = false; + int curL = l.CLine().Length(); + int step = curL / 2 - 1; + + while( step > clipLengthThreshold ) + { + SHAPE_LINE_CHAIN sl_tmp( aClipped.CLine() ); + VECTOR2I pclip = sl_tmp.PointAlong( curL ); + int idx = sl_tmp.Split( pclip ); + sl_tmp = sl_tmp.Slice(0, idx); + + l.SetShape( sl_tmp ); + + //PNS_DBG( dbg, 3int, pclip, WHITE, 500000, wxT("")); + + + if( l.Collide( &aRef, aNode, &ctx ) ) + { + //printf("step %d curL %d collide\n", step, curL ); + didClip = true; + curL -= step; + step /= 2; + } else { + tightest = sl_tmp; + //printf("step %d curL %d non-collide\n", step, curL ); + if ( didClip ) + { + curL += step; + step /= 2; + } + else + break; + } + } + + aClipped.SetShape( tightest ); + + return didClip; +} + + + + +const std::vector MULTI_DRAGGER::CurrentNets() const +{ + std::set uniqueNets; + for( auto &l : m_mdragLines ) + { + NET_HANDLE net = l.draggedLine.Net(); + if( net ) + uniqueNets.insert( net ); + } + + return std::vector( uniqueNets.begin(), uniqueNets.end() ); +} + +// this is what ultimately gets called when the user clicks/releases the mouse button +// during drag. +bool MULTI_DRAGGER::FixRoute( bool aForceCommit ) +{ + NODE* node = CurrentNode(); + + if( node ) + { + // last drag status is OK? + if( !m_dragStatus && !Settings().AllowDRCViolations() ) + return false; + + // commit the current world state + Router()->CommitRouting( node ); + return true; + } + + return false; +} + +bool MULTI_DRAGGER::tryWalkaround( NODE* aNode, LINE& aOrig, LINE& aWalk, const LINE& aLeader ) +{ + WALKAROUND walkaround( aNode, Router() ); + bool ok = false; + walkaround.SetSolidsOnly( false ); + walkaround.SetDebugDecorator( Dbg() ); + walkaround.SetLogger( Logger() ); + walkaround.SetIterationLimit( Settings().WalkaroundIterationLimit() ); + walkaround.SetLengthLimit( true, 3.0 ); + walkaround.SetAllowedPolicies( { WALKAROUND::WP_SHORTEST } ); + + aWalk = aOrig; + + WALKAROUND::RESULT wr = walkaround.Route( aWalk ); + + if( wr.status[ WALKAROUND::WP_SHORTEST ] == WALKAROUND::ST_DONE ) + { + aWalk = wr.lines[ WALKAROUND::WP_SHORTEST ]; + return true; + } + + return false; +} + +int MULTI_DRAGGER::findNewLeaderSegment2( const MULTI_DRAGGER::MDRAG_LINE& aLine ) const +{ + const SEG origLeader = aLine.preDragLine.CSegment( aLine.leaderSegIndex ); + const DIRECTION_45 origLeaderDir( origLeader ); + + for ( int i = 0; i < aLine.draggedLine.SegmentCount(); i++ ) + { + const SEG& curSeg = aLine.draggedLine.CSegment(i); + const DIRECTION_45 curDir( curSeg ); + + auto ip = curSeg.IntersectLines( m_guide ); + PNS_DBG(Dbg(), Message, wxString::Format("s %d ip=%d c=%s o=%s", i, ip?1:0, curDir.Format(), origLeaderDir.Format() )); + if( ip && curSeg.Contains( *ip ) ) + { + if( curDir == origLeaderDir || curDir == origLeaderDir.Opposite() ) + return i; + } + } + + return -1; +} + + +int MULTI_DRAGGER::findNewLeaderSegment( const LINE& aDraggedRaw, int leaderIndex, const LINE& aPostDrag ) const +{ + if (leaderIndex < 0) + return -1; + auto origLeader = aDraggedRaw.CSegment( leaderIndex ); + + int bestMatchDist = std::numeric_limits::max(); + int newLeaderIdx = -1; + int n = 0; + + for( auto item : aPostDrag.Links() ) + { + if( auto seg = dyn_cast( item ) ) + { + DIRECTION_45 origDir( origLeader ); + DIRECTION_45 newDir( seg->Seg() ); + + if( ! ( origDir.Angle(newDir) & (DIRECTION_45::ANG_STRAIGHT | DIRECTION_45::ANG_HALF_FULL ) ) ) + continue; + + + VECTOR2I va, vb; + SEG::ecoord distSq; + + if( seg->Seg().NearestPoints( origLeader, va, vb, distSq ) ) + { + int dist = std::sqrt( distSq ); + if( dist < bestMatchDist ) + { + bestMatchDist = dist; + newLeaderIdx = n; + } + } + + + + + } + n++; + } + + return newLeaderIdx; +} + + +bool MULTI_DRAGGER::multidragWalkaround( std::vector& aCompletedLines ) +{ + // fixme: rewrite using shared_ptr... + if( m_lastNode ) + { + delete m_lastNode; + m_lastNode = nullptr; + } + + auto compareDragStartDist = []( const MDRAG_LINE& a, const MDRAG_LINE& b ) -> int + { + return a.dragDist < b.dragDist; + }; + + std::sort( aCompletedLines.begin(), aCompletedLines.end(), compareDragStartDist ); + + + NODE* preWalkNode = m_world->Branch(); + + for( auto& l : aCompletedLines ) + { + PNS_DBG( Dbg(), AddItem, &l.originalLine, BLUE, 100000, wxString::Format("prewalk-remove lc=%d", l.originalLine.LinkCount() ) ); + preWalkNode->Remove( l.originalLine ); + } + + bool fail = false; + + NODE* tmpNodes[2]; + int totalLength[2]; + + for( int attempt = 0; attempt < 2; attempt++ ) + { + NODE *node = tmpNodes[attempt] = preWalkNode->Branch(); + totalLength[attempt] = 0; + fail = false; + + for( int lidx = 0; lidx < aCompletedLines.size(); lidx++ ) + { + MDRAG_LINE& l = aCompletedLines[attempt ? aCompletedLines.size() - 1 - lidx : lidx]; + + LINE walk( l.draggedLine ); + auto result = tryWalkaround( node, l.draggedLine, walk, l.draggedLine ); + + PNS_DBG( Dbg(), AddItem, &l.draggedLine, YELLOW, 100000, wxString::Format("dragged lidx=%d attempt=%d dd=%d isPrimary=%d", lidx, attempt, l.dragDist, l.isPrimaryLine?1:0) ); + PNS_DBG( Dbg(), AddItem, &walk, BLUE, 100000, wxString::Format("walk lidx=%d attempt=%d", lidx, attempt) ); + + + if( result ) + { + node->Add( walk ); + totalLength[attempt] += walk.CLine().Length() - l.draggedLine.CLine().Length(); + } + else + { + delete node; + tmpNodes[attempt] = nullptr; + break; + } + } + } + + if( tmpNodes[0] && tmpNodes[1] ) + { + if ( totalLength[0] < totalLength[1] ) + { + delete tmpNodes[1]; + m_lastNode = tmpNodes[0]; + return true; + } + else + { + delete tmpNodes[0]; + m_lastNode = tmpNodes[1]; + return true; + } + } + else if ( tmpNodes[0] ) + { + m_lastNode = tmpNodes[0]; + return true; + } + else if ( tmpNodes[1] ) + { + m_lastNode = tmpNodes[1]; + return true; + } + + return false; +} + + +bool MULTI_DRAGGER::multidragMarkObstacles( std::vector& aCompletedLines ) +{ + +// fixme: rewrite using shared_ptr... + if( m_lastNode ) + { + delete m_lastNode; + m_lastNode = nullptr; + } + + // m_lastNode contains the temporary (post-modification) state. Think of it as + // of an efficient undo buffer. We don't change the PCB directly, but a branch of it + // created below. We can then commit its state (applying the modifications to the host board + // by calling ROUTING::CommitRouting(m_lastNode) or simply discard it. + m_lastNode = m_world->Branch(); + + + int nclipped = 0; + for( int l1 = 0; l1 < aCompletedLines.size(); l1++ ) + { + for( int l2 = l1 + 1; l2 < aCompletedLines.size(); l2++ ) + { + auto l1l = aCompletedLines[l1].draggedLine; + auto l2l = aCompletedLines[l2].draggedLine; + + printf( "chk-clip %d %d\n", l1, l2 ); + + if( clipToOtherLine( m_lastNode, l1l, l2l ) ) + { + aCompletedLines[l2].draggedLine = l2l; + nclipped++; + } + } + } + + //printf( "lines %d, clipped %d\n", (int) completed.size(), nclipped ); + + m_leaderSegments.clear(); + + for( auto& l : aCompletedLines ) + { + if( l.dragOK ) + { + m_lastNode->Remove( l.originalLine ); + m_lastNode->Add( l.draggedLine ); + m_draggedItems.Add( l.draggedLine ); + if( m_dragMode == DM_CORNER ) + { + m_leaderSegments.push_back( + static_cast( l.draggedLine.GetLink( -1 ) ) ); + } + else + { + } + } + } + + return true; +} + +bool MULTI_DRAGGER::multidragShove( std::vector& aCompletedLines ) +{ + if( m_lastNode ) + { + delete m_lastNode; + m_lastNode = nullptr; + } + + if( !m_shove ) + return false; + + auto compareDragStartDist = []( const MDRAG_LINE& a, const MDRAG_LINE& b ) -> int + { + return a.dragDist < b.dragDist; + }; + + std::sort( aCompletedLines.begin(), aCompletedLines.end(), compareDragStartDist ); + + auto iface = Router()->GetInterface(); + + for( auto& l : m_mdragLines ) + { + PNS_DBG( Dbg(), Message, wxString::Format ( wxT("net %-30s: isCorner %d isStrict %d c-Dist %-10d l-dist %-10d leadIndex %-2d CisLast %d dragDist %-10d"), + iface->GetNetName( l.draggedLine.Net() ), + (int) l.isCorner?1:0, + (int) l.isStrict?1:0, + (int) l.cornerDistance, + (int) l.leaderSegDistance, + (int) l.leaderSegIndex, + (int) l.cornerIsLast?1:0, + (int) l.dragDist ) ); + } + + + m_shove->SetDefaultShovePolicy( SHOVE::SHP_SHOVE ); + m_shove->ClearHeads(); + + for( auto& l : aCompletedLines ) + { + PNS_DBG( Dbg(), AddItem, &l.draggedLine, GREEN, 0, "dragged-line" ); + m_shove->AddHeads( &l.draggedLine, SHOVE::SHP_SHOVE | SHOVE::SHP_DONT_OPTIMIZE ); + } + + auto status = m_shove->Run(); + + m_lastNode = m_shove->CurrentNode()->Branch(); + + if( status == SHOVE::SH_OK ) + { + for( int i = 0; i < aCompletedLines.size(); i++ ) + { + MDRAG_LINE&l = aCompletedLines[i]; + if( m_shove->HeadsModified( i ) ) + l.draggedLine = m_shove->GetModifiedHead( i ); + + // this should not be linked (assert in rt-test) + m_lastNode->Add( l.draggedLine ); + } + } + else + { + //delete preShoveNode; + return false; + } + + m_leaderSegments.clear(); + + for( auto& l : aCompletedLines ) + { + if( l.dragOK ) + { + if( m_dragMode == DM_CORNER ) + { + m_leaderSegments.push_back( + static_cast( l.draggedLine.GetLink( -1 ) ) ); + } + else + { + int newLeaderIdx = findNewLeaderSegment2( l ); + if( newLeaderIdx >= 0 ) + { + m_leaderSegments.push_back( + static_cast( l.draggedLine.GetLink( newLeaderIdx ) ) ); + } + } + } + } + return true; +} + +// this is called every time the user moves the mouse while dragging a set of multiple tracks +bool MULTI_DRAGGER::Drag( const VECTOR2I& aP ) +{ + std::optional primaryPreDrag, primaryDragged; + + + + SEG lastPreDrag; + DIRECTION_45 primaryDir; + VECTOR2I perp; + + DIRECTION_45 primaryLastSegDir; + std::vector completed; + + auto tryPosture = [&] ( int aVariant ) -> bool + { + MDRAG_LINE* primaryLine = nullptr; + + printf("tryPosture: %d\n", aVariant ); + + for( auto &l : m_mdragLines ) + { + l.dragOK = false; + l.preDragLine = l.originalLine; + //PNS_DBG( Dbg(), AddItem, &l.originalLine, GREEN, 300000, "par" ); + + if( l.isPrimaryLine ) + { + + //PNS_DBG( Dbg(), AddItem, &l.originalLine, BLUE, 300000, wxT("mdrag-prim")); + + // create a copy of the primary line (pre-drag and post-drag). + // the pre-drag version is necessary for NODE::Remove() to be able to + // find out the segments before modification by the multidrag algorithm + primaryDragged = l.originalLine; + primaryDragged->ClearLinks(); + primaryPreDrag = l.originalLine; + primaryLine = &l; + + } + } + + if( aVariant == 1 && (primaryPreDrag->PointCount() > 2) ) + { + primaryPreDrag->Line().Remove( -1 ); + primaryDragged->Line().Remove( -1 ); + + for( auto&l : m_mdragLines ) + { + l.preDragLine.Line().Remove(-1); + } + } + + completed.clear(); + + int snapThreshold = Settings().SmoothDraggedSegments() ? primaryDragged->Width() / 4 : 0; + + if( m_dragMode == DM_CORNER ) + { + // first, drag only the primary line + // PNS_DBG( Dbg(), AddPoint, primaryDragged->CPoint( -1 ), YELLOW, 600000, wxT("mdrag-sec")); + + lastPreDrag = primaryPreDrag->CSegment( -1 ); + primaryDir = DIRECTION_45( lastPreDrag ); + + primaryDragged->SetSnapThreshhold( snapThreshold ); + primaryDragged->DragCorner( aP, primaryDragged->PointCount() - 1, false ); + + SEG lastPrimDrag = primaryDragged->CSegment( -1 ); + + if ( aVariant == 2 ) + lastPrimDrag = lastPreDrag; + + if( primaryDragged->SegmentCount() > 0 ) + { + auto lastSeg = primaryDragged->CSegment( -1 ); + if( DIRECTION_45( lastSeg ) != primaryDir ) + { + if( lastSeg.Length() < primaryDragged->Width() ) + { + lastPrimDrag = lastPreDrag; + } + } + } + + perp = (lastPrimDrag.B - lastPrimDrag.A).Perpendicular(); + + primaryLastSegDir = DIRECTION_45( lastPrimDrag ); + +// PNS_DBG( Dbg(), AddShape, &ll, LIGHTBLUE, 200000, "par" ); + PNS_DBG( Dbg(), AddItem, &(*primaryDragged), LIGHTGRAY, 100000, "prim" ); + } + else + { + + SHAPE_LINE_CHAIN ll2( { lastPreDrag.A, lastPreDrag.B } ); + PNS_DBG( Dbg(), AddShape, &ll2, LIGHTYELLOW, 300000, "par" ); + lastPreDrag = primaryDragged->CSegment( primaryLine->leaderSegIndex ); + primaryDragged->SetSnapThreshhold( snapThreshold ); + primaryDragged->DragSegment( aP, primaryLine->leaderSegIndex ); + perp = (primaryLine->midSeg.B - primaryLine->midSeg.A).Perpendicular(); + m_guide = SEG( aP, aP + perp ); + } + + + m_leaderSegments = m_origDraggedItems.CItems(); + m_draggedItems.Clear(); + + // now drag all other lines + for( auto& l : m_mdragLines ) + { + //PNS_DBG( Dbg(), AddPoint, l.originalLine.CPoint( l.cornerIndex ), WHITE, 1000000, wxT("l-end")); + if( l.isDraggable ) + { + l.dragOK = false; + //PNS_DBG( Dbg(), AddItem, &l.originalLine, GREEN, 100000, wxT("mdrag-sec")); + + // reject nulls + if( l.preDragLine.SegmentCount() >= 1 ) + { + //printf("processL sc %d anchor %d\n", l.preDragLine.SegmentCount(), l.cornerIndex ); + + //PNS_DBG( Dbg(), AddPoint, l.preDragLine.CPoint( l.cornerIndex ), YELLOW, 600000, wxT("mdrag-sec")); + + // check the direction of the last segment of the line against the direction of + // the last segment of the primary line (both before dragging) and perform drag + // only when the directions are the same. The algorithm here is quite trival and + // otherwise would produce really awkward results. There's of course a TON of + // room for improvement here :-) + + if( m_dragMode == DM_CORNER ) + { + DIRECTION_45 parallelDir( l.preDragLine.CSegment( -1 ) ); + + auto leadAngle = primaryDir.Angle( parallelDir ); + fprintf( stderr, "prim-dir %s par-dir %s lead-angle %d\n", + primaryDir.Format().c_str(), parallelDir.Format().c_str(), + (int) leadAngle ); + + if( leadAngle == DIRECTION_45::ANG_OBTUSE + || leadAngle == DIRECTION_45::ANG_STRAIGHT ) + { + // compute the distance between the primary line and the last point of + // the currently processed line + int dist = lastPreDrag.LineDistance( l.preDragLine.CPoint( -1 ), true ); + + // now project it on the perpendicular line we computed before + auto projected = aP + perp.Resize( dist ); + + + LINE parallelDragged( l.preDragLine ); + + + parallelDragged.ClearLinks(); + //m_lastNode->Remove( parallelDragged ); + // drag the non-primary line's end trying to place it at the projected point + parallelDragged.DragCorner( projected, parallelDragged.PointCount() - 1, + false, primaryLastSegDir ); + + //PNS_DBG( Dbg(), AddPoint, projected, LIGHTYELLOW, 600000, + // wxT( "l-end" ) ); + + if(!l.isPrimaryLine) + { + l.draggedLine = parallelDragged; + completed.push_back( l ); + m_draggedItems.Add( parallelDragged ); + + } + + l.dragOK = true; + + // replace the line with the post-drag version + } + } + else if ( m_dragMode == DM_SEGMENT ) + { + SEG sdrag = l.midSeg; + DIRECTION_45 refDir( lastPreDrag ); + DIRECTION_45 curDir( sdrag ); + auto ang = refDir.Angle( curDir ); + + if( ang == DIRECTION_45::ANG_HALF_FULL || DIRECTION_45::ANG_STRAIGHT ) + { + + + + int dist = lastPreDrag.LineDistance( l.preDragLine.CPoint( l.leaderSegIndex ), true ); + auto projected = aP + perp.Resize( dist ); + + SEG sperp( aP, aP + perp.Resize( 10000000 ) ); + VECTOR2I startProj = sperp.LineProject( m_dragStartPoint ); + + SHAPE_LINE_CHAIN ll( + { sperp.A, sperp.B } ); + + + // PNS_DBG( Dbg(), AddPoint, m_dragStartPoint, LIGHTRED, 400000, "par" ); + + PNS_DBG( Dbg(), AddShape, &ll, LIGHTBLUE, 100000, "par" ); + SHAPE_LINE_CHAIN ll2( + { sdrag.A, sdrag.B } ); + PNS_DBG( Dbg(), AddShape, &ll2, LIGHTBLUE, 100000, "sdrag" ); + VECTOR2I v = projected - startProj; + l.dragDist = v.EuclideanNorm() * sign( v.Dot( perp ) ); + + + if(!l.isPrimaryLine) + { + l.draggedLine = l.preDragLine; + l.draggedLine.ClearLinks(); + l.draggedLine.SetSnapThreshhold( snapThreshold ); + l.draggedLine.DragSegment( projected, l.leaderSegIndex, false ); + //l.dragAnchor = projected; + completed.push_back( l ); + PNS_DBG( Dbg(), AddItem, &l.draggedLine, LIGHTBLUE, 100000, "dragged" ); + } + l.dragOK = true; + + + PNS_DBG( Dbg(), AddPoint, startProj, LIGHTBLUE, 400000, wxT("startProj") ); + PNS_DBG( Dbg(), AddPoint, projected, LIGHTRED, 400000, wxString::Format("pro dd=%d", l.dragDist ) ); + } + } + } + } + + if (l.isPrimaryLine) + { + l.draggedLine = *primaryDragged; + l.dragOK = true; + completed.push_back( l ); + } + } + + if( m_dragMode == DM_SEGMENT ) + return true; + else + { + for ( const auto &l: completed ) + { + if( !l.dragOK && aVariant < 2 ) + return false; + + if( l.isPrimaryLine ) + continue; + + DIRECTION_45 lastDir ( l.draggedLine.CSegment(-1) ); + + if( lastDir != primaryLastSegDir ) + return false; + } + } + + return true; + }; + + bool res = false; + + for( int variant = 0; variant < 3; variant++ ) + { + res = tryPosture( 0 ); + if( res ) + break; + } + + switch( Settings().Mode() ) + { + case RM_Walkaround: + m_dragStatus = multidragWalkaround ( completed ); + break; + + case RM_Shove: + m_dragStatus = multidragShove ( completed ); + break; + + case RM_MarkObstacles: + m_dragStatus = multidragMarkObstacles( completed ); + break; + + + + default: + break; + } + + return m_dragStatus; +} + + +NODE* MULTI_DRAGGER::CurrentNode() const +{ + return m_lastNode ? m_lastNode : m_world; +} + + +const ITEM_SET MULTI_DRAGGER::Traces() +{ + return m_draggedItems; +} + + +int MULTI_DRAGGER::CurrentLayer() const +{ + // fixme: should we care? + return F_Cu; +} + + +} // namespace PNS diff --git a/pcbnew/router/pns_multi_dragger.h b/pcbnew/router/pns_multi_dragger.h new file mode 100644 index 0000000000..9b984fa75b --- /dev/null +++ b/pcbnew/router/pns_multi_dragger.h @@ -0,0 +1,183 @@ +/* + * KiRouter - a push-and-(sometimes-)shove PCB router + * + * Copyright (C) 2013-2014 CERN + * Copyright (C) 2024 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 . + */ + +#ifndef __PNS_MULTI_DRAGGER_H +#define __PNS_MULTI_DRAGGER_H + +#include +#include + +#include "pns_node.h" +#include "pns_via.h" +#include "pns_line.h" +#include "pns_drag_algo.h" +#include "pns_itemset.h" +#include "pns_layerset.h" +#include "pns_mouse_trail_tracer.h" + +namespace PNS { + +class ROUTER; +class SHOVE; +class OPTIMIZER; + +/** + * MULTI_DRAGGER + * + * Dragging algorithm for multiple segments. Very trival version for demonstration purposes. + */ +class MULTI_DRAGGER : public DRAG_ALGO +{ +public: + MULTI_DRAGGER( ROUTER* aRouter ); + ~MULTI_DRAGGER(); + + /** + * Function Start() + * + * Starts routing a single track at point aP, taking item aStartItem as anchor + * (unless NULL). Returns true if a dragging operation has started. + */ + virtual bool Start( const VECTOR2I& aP, ITEM_SET& aPrimitives ) override; + + /** + * Function Drag() + * + * Drags the current segment/corner/via to the point aP. + * @return true, if dragging finished with success. + */ + bool Drag( const VECTOR2I& aP ) override; + + /** + * Function FixRoute() + * + * Checks if the result of current dragging operation is correct + * and eventually commits it to the world. + * @return true, if dragging finished with success. + */ + bool FixRoute( bool aForceCommit ) override; + + /** + * Function CurrentNode() + * + * Returns the most recent world state, including all + * items changed due to dragging operation. + */ + NODE* CurrentNode() const override; + + /** + * Function CurrentNets() + * + * Returns the net code(s) of currently routed track(s). + */ + const std::vector CurrentNets() const override; + + /** + * Function CurrentLayer() + * + * Returns the layer of currently routed track. + */ + int CurrentLayer() const override; + + /** + * Function Traces() + * + * Returns the set of dragged items. + */ + const ITEM_SET Traces() override; + + void SetMode( PNS::DRAG_MODE aDragMode ) override; + + PNS::DRAG_MODE Mode() const override; + +// Use case: we are dragging multiple tracks. The router erases a few of them, adds a few new ones. For the ease of use, it would be good for the tracks the be still selected when +// the drag operation is completed. This method returns a set of the 'leader' (segments/arcs that have been selected for multi-fragging) + virtual std::vector GetLastCommittedLeaderSegments() override { return m_leaderSegments; }; + + virtual bool GetForceMarkObstaclesMode( bool* aDragStatus ) const override + { + *aDragStatus = m_dragStatus; + return false; + } + + + +private: + + + + + struct MDRAG_LINE + { + + ITEM* leaderItem; + std::vector originalLeaders; + + + bool isStrict; + bool isMidSeg; + bool isCorner; + bool isDraggable; + + int leaderSegIndex = -1; + bool cornerIsLast = false; + + PNS::LINE originalLine; // complete line (in a bundle) to drag + PNS::LINE preDragLine; // complete line (in a bundle) to drag + PNS::LINE draggedLine; // result of the drag calculation + PNS::LINE preShoveLine; // result of the drag calculation + bool dragOK = false; + bool isPrimaryLine = false; // when true, it's the "leader"/"primary one" - the one the cursor is attached to + bool clipDone = false; + int offset; // distance between this line and the primary one (only applicable if the respective end segments are parallel) + SEG midSeg; +// VECTOR2I dragAnchor; + int dragDist; + int cornerDistance, leaderSegDistance; + }; + + bool multidragMarkObstacles ( std::vector& aCompletedLines ); + bool multidragShove ( std::vector& aCompletedLines ); + bool multidragWalkaround ( std::vector& aCompletedLines ); + int findNewLeaderSegment( const LINE& aDraggedRaw, int leaderIndex, const LINE& aPostDrag ) const; + int findNewLeaderSegment2( const MDRAG_LINE& aLine ) const; + bool tryWalkaround( NODE* aNode, LINE& aOrig, LINE& aWalk, const LINE& aLeader ); + + + int m_mode; + bool m_dragStatus; + PNS_MODE m_currentMode; + DRAG_MODE m_dragMode; + std::vector m_mdragLines; + std::vector m_leaderSegments; + NODE* m_lastNode; + NODE* m_preShoveNode; + ITEM_SET m_origDraggedItems; + ITEM_SET m_draggedItems; + VECTOR2I m_dragStartPoint; + SEG m_guide; + std::unique_ptr m_shove; + +}; + +} + +#endif \ No newline at end of file