From f6509990043fdd8f2aec7cd88cb1f9985745c64f Mon Sep 17 00:00:00 2001 From: Dhineshkumar S Date: Thu, 12 Jun 2025 13:02:06 +0200 Subject: [PATCH] Eeschema: Add option to show unconnected wire crossings as hop-overs. --- common/advanced_config.cpp | 2 + eeschema/bus-wire-junction.cpp | 21 ++++ eeschema/sch_commit.cpp | 14 +++ eeschema/sch_edit_frame.h | 2 + eeschema/sch_line.cpp | 23 +++++ eeschema/sch_line.h | 2 + eeschema/sch_painter.cpp | 163 ++++++++++++++++++++++++++++--- eeschema/sch_painter.h | 3 + eeschema/tools/sch_edit_tool.cpp | 4 +- include/advanced_config.h | 9 ++ 10 files changed, 229 insertions(+), 14 deletions(-) diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 99435ca90d..7887d10bc9 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -312,6 +312,8 @@ ADVANCED_CFG::ADVANCED_CFG() m_MaxPastedTextLength = 100; + m_hopOverArcRadius = 2.5; + loadFromConfigFile(); } diff --git a/eeschema/bus-wire-junction.cpp b/eeschema/bus-wire-junction.cpp index 229d56e905..5ff4bfb92c 100644 --- a/eeschema/bus-wire-junction.cpp +++ b/eeschema/bus-wire-junction.cpp @@ -198,4 +198,25 @@ SCH_JUNCTION* SCH_EDIT_FRAME::AddJunction( SCH_COMMIT* aCommit, SCH_SCREEN* aScr return junction; } +void SCH_EDIT_FRAME::UpdateHopOveredWires( SCH_ITEM* aItem ) +{ + std::vector items; + + GetCanvas()->GetView()->Query( aItem->GetBoundingBox(), items ); + + for( const auto& it : items ) + { + if( !it.first->IsSCH_ITEM() ) + continue; + + SCH_ITEM* item = static_cast( it.first ); + if( item == aItem ) + continue; + + if( item->IsType( { SCH_ITEM_LOCATE_WIRE_T } ) ) + { + GetCanvas()->GetView()->Update( item ); + } + } +} diff --git a/eeschema/sch_commit.cpp b/eeschema/sch_commit.cpp index 6217106a47..ec1b089d07 100644 --- a/eeschema/sch_commit.cpp +++ b/eeschema/sch_commit.cpp @@ -463,6 +463,20 @@ void SCH_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags ) void SCH_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) { + SCH_EDIT_FRAME* frame = static_cast( m_toolMgr->GetToolHolder() ); + + for( COMMIT_LINE& ent : m_changes ) + { + SCH_ITEM* schCopyItem = dynamic_cast( ent.m_copy ); + SCH_ITEM* schItem = dynamic_cast( ent.m_item ); + + if( schCopyItem && schCopyItem->Type() == SCH_LINE_T ) + frame->UpdateHopOveredWires( schCopyItem ); + + if( schItem && schItem->Type() == SCH_LINE_T ) + frame->UpdateHopOveredWires( schItem ); + } + if( m_isLibEditor ) pushLibEdit( aMessage, aCommitFlags ); else diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 2065773a68..5b888f6297 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -609,6 +609,8 @@ public: * @param aItem The junction to delete */ void DeleteJunction( SCH_COMMIT* aCommit, SCH_ITEM* aItem ); + + void UpdateHopOveredWires( SCH_ITEM* aItem ); void FlipBodyStyle( SCH_SYMBOL* aSymbol ); diff --git a/eeschema/sch_line.cpp b/eeschema/sch_line.cpp index adc7bd324d..caa5e40be6 100644 --- a/eeschema/sch_line.cpp +++ b/eeschema/sch_line.cpp @@ -1009,6 +1009,29 @@ double SCH_LINE::Similarity( const SCH_ITEM& aOther ) const } +bool SCH_LINE::ShouldHopOver( const SCH_LINE* aLine ) const +{ + bool isLine1Vertical = ( m_end.x == m_start.x ); + bool isLine2Vertical = ( aLine->GetEndPoint().x == aLine->GetStartPoint().x ); + + // Vertical vs. Horizontal: Horizontal should hop + if( isLine1Vertical && !isLine2Vertical ) + return false; + + if( isLine2Vertical && !isLine1Vertical ) + return true; + + double slope1 = 0; + double slope2 = 0; + + slope1 = ( m_end.y - m_start.y ) / (double) ( m_end.x - m_start.x ); + slope2 = ( aLine->GetEndPoint().y - aLine->GetStartPoint().y ) + / (double) ( aLine->GetEndPoint().x - aLine->GetStartPoint().x ); + + return fabs( slope1 ) < fabs( slope2 ); // The shallower line should hop +} + + static struct SCH_LINE_DESC { SCH_LINE_DESC() diff --git a/eeschema/sch_line.h b/eeschema/sch_line.h index fe5c910a4d..dffbcd050f 100644 --- a/eeschema/sch_line.h +++ b/eeschema/sch_line.h @@ -260,6 +260,8 @@ public: bool IsParallel( const SCH_LINE* aLine ) const; + bool ShouldHopOver( const SCH_LINE* aLine ) const; + void GetEndPoints( std::vector& aItemList ) override; bool UpdateDanglingState( std::vector& aItemListByType, diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 8a2ee32647..601ea0298a 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -1480,23 +1480,140 @@ void SCH_PAINTER::draw( const SCH_LINE* aLine, int aLayer ) m_gal->SetStrokeColor( color ); m_gal->SetLineWidth( width ); - if( lineStyle <= LINE_STYLE::FIRST_TYPE || drawingShadows ) + VECTOR2I currentLineStartPoint = aLine->GetStartPoint(); + VECTOR2I currentLineEndPoint = aLine->GetEndPoint(); + + std::vector existingLines; + existingLines.clear(); + + if( aLine->IsWire() ) + { + SCH_SCREEN* curr_screen = m_schematic->GetCurrentScreen(); + + for( SCH_ITEM* item : curr_screen->Items().Overlapping( SCH_LINE_T, aLine->GetBoundingBox() ) ) + { + SCH_LINE* line = static_cast( item ); + + if( line->IsWire() ) + existingLines.push_back( line ); + } + } + + std::vector intersections; + + for( SCH_LINE* existingLine : existingLines ) + { + VECTOR2I extLineStartPoint = existingLine->GetStartPoint(); + VECTOR2I extLineEndPoint = existingLine->GetEndPoint(); + + if( extLineStartPoint == currentLineStartPoint && extLineEndPoint == currentLineEndPoint ) + continue; + + if( !aLine->ShouldHopOver( existingLine ) ) + continue; + + SEG currentSegment = SEG( currentLineStartPoint, currentLineEndPoint ); + SEG existingSegment = SEG( extLineStartPoint, extLineEndPoint ); + + if( OPT_VECTOR2I intersect = currentSegment.Intersect( existingSegment, true, false ) ) + { + if( aLine->IsEndPoint( *intersect ) || existingLine->IsEndPoint( *intersect ) ) + continue; + + intersections.push_back( *intersect ); + } + } + + + if( intersections.empty() ) { - m_gal->DrawLine( aLine->GetStartPoint(), aLine->GetEndPoint() ); + drawLine( currentLineStartPoint, currentLineEndPoint, lineStyle, + ( lineStyle <= LINE_STYLE::FIRST_TYPE || drawingShadows ), width ); } else { - SHAPE_SEGMENT line( aLine->GetStartPoint(), aLine->GetEndPoint() ); + auto getDistance = []( const VECTOR2I& a, const VECTOR2I& b ) -> double + { + return std::sqrt( std::pow( a.x - b.x, 2 ) + std::pow( a.y - b.y, 2 ) ); + }; - STROKE_PARAMS::Stroke( &line, lineStyle, KiROUND( width ), &m_schSettings, - [&]( const VECTOR2I& a, const VECTOR2I& b ) - { - // DrawLine has problem with 0 length lines so enforce minimum - if( a == b ) - m_gal->DrawLine( a+1, b ); - else - m_gal->DrawLine( a, b ); - } ); + std::sort( intersections.begin(), intersections.end(), + [&]( const VECTOR2I& a, const VECTOR2I& b ) + { + return getDistance( aLine->GetStartPoint(), a ) + < getDistance( aLine->GetStartPoint(), b ); + } ); + + VECTOR2I currentStart = aLine->GetStartPoint(); + float lineWidth = getLineWidth( aLine, drawingShadows, drawingNetColorHighlights ); + float arcRadius = lineWidth * ADVANCED_CFG::GetCfg().m_hopOverArcRadius; + + for( const VECTOR2I& hopMid : intersections ) + { + // Calculate the angle of the line from start point to end point in radians + double lineAngle = std::atan2( aLine->GetEndPoint().y - aLine->GetStartPoint().y, + aLine->GetEndPoint().x - aLine->GetStartPoint().x ); + + // Convert the angle from radians to degrees + double lineAngleDeg = lineAngle * ( 180.0f / M_PI ); + + // Normalize the angle to be between 0 and 360 degrees + if( lineAngleDeg < 0 ) + lineAngleDeg += 360; + + double startAngle = lineAngleDeg; + double endAngle = startAngle + 180.0f; + + // Adjust the end angle if it exceeds 360 degrees + if( endAngle >= 360.0 ) + endAngle -= 360.0; + + // Convert start and end angles from degrees to radians + double startAngleRad = startAngle * ( M_PI / 180.0f ); + double endAngleRad = endAngle * ( M_PI / 180.0f ); + + VECTOR2I arcStartPoint = { + hopMid.x + static_cast( arcRadius * cos( startAngleRad ) ), + hopMid.y + static_cast( arcRadius * sin( startAngleRad ) ) + }; + + VECTOR2I arcEndPoint = { hopMid.x + static_cast( arcRadius * cos( endAngleRad ) ), + hopMid.y + + static_cast( arcRadius * sin( endAngleRad ) ) }; + + VECTOR2I arcMidPoint = { + hopMid.x + static_cast( arcRadius + * cos( ( startAngleRad + endAngleRad ) / 2.0f ) ), + hopMid.y + static_cast( arcRadius + * sin( ( startAngleRad + endAngleRad ) / 2.0f ) ) + }; + + VECTOR2I beforeHop = hopMid - VECTOR2I( arcRadius * std::cos( lineAngle ), + arcRadius * std::sin( lineAngle ) ); + VECTOR2I afterHop = hopMid + VECTOR2I( arcRadius * std::cos( lineAngle ), + arcRadius * std::sin( lineAngle ) ); + + // Draw the line from the current start point to the before-hop point + drawLine( currentStart, beforeHop, lineStyle, + ( lineStyle <= LINE_STYLE::FIRST_TYPE || drawingShadows ), width ); + + // Create an arc object and draw it using the stroke function + SHAPE_ARC arc( arcStartPoint, arcMidPoint, arcEndPoint, lineWidth ); + STROKE_PARAMS::Stroke( &arc, LINE_STYLE::DASH, KiROUND( width ), &m_schSettings, + [&]( const VECTOR2I& start, const VECTOR2I& end ) + { + if( start == end ) + m_gal->DrawLine( start + 1, end ); + else + m_gal->DrawLine( start, end ); + } ); + + currentStart = afterHop; + } + + // Draw the final line from the current start point to the end point of the original line + drawLine( currentStart, aLine->GetEndPoint(), lineStyle, + ( lineStyle <= LINE_STYLE::FIRST_TYPE || drawingShadows ), width ); } } @@ -3112,4 +3229,26 @@ void SCH_PAINTER::draw( const SCH_GROUP* aGroup, int aLayer ) } } +void SCH_PAINTER::drawLine( const VECTOR2I& aStartPoint, const VECTOR2I& aEndPoint, + LINE_STYLE aLineStyle, bool aDrawDirectLine, int aWidth ) +{ + if( aDrawDirectLine ) + { + m_gal->DrawLine( aStartPoint, aEndPoint ); + } + else + { + SHAPE_SEGMENT segment( aStartPoint, aEndPoint ); + + STROKE_PARAMS::Stroke( &segment, aLineStyle, KiROUND( aWidth ), &m_schSettings, + [&]( const VECTOR2I& start, const VECTOR2I& end ) + { + if( start == end ) + m_gal->DrawLine( start + 1, end ); + else + m_gal->DrawLine( start, end ); + } ); + } +} + }; // namespace KIGFX diff --git a/eeschema/sch_painter.h b/eeschema/sch_painter.h index cd6509a302..cab4d8fe6a 100644 --- a/eeschema/sch_painter.h +++ b/eeschema/sch_painter.h @@ -140,6 +140,9 @@ private: wxString expandLibItemTextVars( const wxString& aSourceText, const SCH_SYMBOL* aSymbolContext ); + void drawLine( const VECTOR2I& aStartPoint, const VECTOR2I& aEndPoint, LINE_STYLE aLineStyle, + bool aDrawDirectLine = false, int aWidth = 0 ); + public: static std::vector g_ScaledSelectionTypes; diff --git a/eeschema/tools/sch_edit_tool.cpp b/eeschema/tools/sch_edit_tool.cpp index f32cde2780..14b9c5af96 100644 --- a/eeschema/tools/sch_edit_tool.cpp +++ b/eeschema/tools/sch_edit_tool.cpp @@ -813,7 +813,7 @@ int SCH_EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent ) if( head && head->IsMoving() ) moving = true; - + if( principalItemCount == 1 ) { if( moving && selection.HasReferencePoint() ) @@ -993,7 +993,7 @@ int SCH_EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent ) if( item->Type() == SCH_LINE_T ) { - SCH_LINE* line = (SCH_LINE*) item; + SCH_LINE* line = (SCH_LINE*) item; line->Rotate( rotPoint, !clockwise ); } diff --git a/include/advanced_config.h b/include/advanced_config.h index e8aae7e75c..0fa6e8631e 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -713,6 +713,15 @@ public: */ double m_MinimumMarkerSeparationDistance; + /** + * Default value for the Hop-Over Arc Radius, used to calculate the arc radius by multiplying + * the line width when drawing a hop-over in scenarios where a wire crosses another. + * + * Setting name: "HopOverArcRadius" + * Default value: 2.5 + */ + double m_hopOverArcRadius; + /** * When updating the net inspector, it either recalculates all nets or iterates through items * one-by-one. This value controls the threshold at which all nets are recalculated rather than