Eeschema: Add option to show unconnected wire crossings as hop-overs.

This commit is contained in:
Dhineshkumar S 2025-06-12 13:02:06 +02:00 committed by jean-pierre charras
parent b621e0ccf5
commit f650999004
10 changed files with 229 additions and 14 deletions

View File

@ -312,6 +312,8 @@ ADVANCED_CFG::ADVANCED_CFG()
m_MaxPastedTextLength = 100;
m_hopOverArcRadius = 2.5;
loadFromConfigFile();
}

View File

@ -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<KIGFX::VIEW::LAYER_ITEM_PAIR> items;
GetCanvas()->GetView()->Query( aItem->GetBoundingBox(), items );
for( const auto& it : items )
{
if( !it.first->IsSCH_ITEM() )
continue;
SCH_ITEM* item = static_cast<SCH_ITEM*>( it.first );
if( item == aItem )
continue;
if( item->IsType( { SCH_ITEM_LOCATE_WIRE_T } ) )
{
GetCanvas()->GetView()->Update( item );
}
}
}

View File

@ -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<SCH_EDIT_FRAME*>( m_toolMgr->GetToolHolder() );
for( COMMIT_LINE& ent : m_changes )
{
SCH_ITEM* schCopyItem = dynamic_cast<SCH_ITEM*>( ent.m_copy );
SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( 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

View File

@ -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 );

View File

@ -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()

View File

@ -260,6 +260,8 @@ public:
bool IsParallel( const SCH_LINE* aLine ) const;
bool ShouldHopOver( const SCH_LINE* aLine ) const;
void GetEndPoints( std::vector<DANGLING_END_ITEM>& aItemList ) override;
bool UpdateDanglingState( std::vector<DANGLING_END_ITEM>& aItemListByType,

View File

@ -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<SCH_LINE*> existingLines;
existingLines.clear();
if( aLine->IsWire() )
{
m_gal->DrawLine( aLine->GetStartPoint(), aLine->GetEndPoint() );
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<SCH_LINE*>( item );
if( line->IsWire() )
existingLines.push_back( line );
}
}
std::vector<VECTOR2I> 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() )
{
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<int>( arcRadius * cos( startAngleRad ) ),
hopMid.y + static_cast<int>( arcRadius * sin( startAngleRad ) )
};
VECTOR2I arcEndPoint = { hopMid.x + static_cast<int>( arcRadius * cos( endAngleRad ) ),
hopMid.y
+ static_cast<int>( arcRadius * sin( endAngleRad ) ) };
VECTOR2I arcMidPoint = {
hopMid.x + static_cast<int>( arcRadius
* cos( ( startAngleRad + endAngleRad ) / 2.0f ) ),
hopMid.y + static_cast<int>( 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

View File

@ -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<KICAD_T> g_ScaledSelectionTypes;

View File

@ -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 );
}

View File

@ -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