Make BuildWireWithHopShape() a member of SCH_LINE

BuildWireWithHopShape(): fix incorrect code, and plot Hop Over shapes
This commit is contained in:
jean-pierre charras 2025-06-13 08:35:50 +02:00
parent f650999004
commit 57bbc96ab2
5 changed files with 248 additions and 145 deletions

View File

@ -232,6 +232,20 @@ void SCH_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags )
if( enteredGroup ) if( enteredGroup )
Modify( enteredGroup ); Modify( enteredGroup );
// Handle wires with Hop Over shapes:
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 );
}
for( COMMIT_LINE& ent : m_changes ) for( COMMIT_LINE& ent : m_changes )
{ {
SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( ent.m_item ); SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( ent.m_item );
@ -463,20 +477,6 @@ void SCH_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags )
void SCH_COMMIT::Push( 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 ) if( m_isLibEditor )
pushLibEdit( aMessage, aCommitFlags ); pushLibEdit( aMessage, aCommitFlags );
else else

View File

@ -1010,7 +1010,7 @@ double SCH_LINE::Similarity( const SCH_ITEM& aOther ) const
bool SCH_LINE::ShouldHopOver( const SCH_LINE* aLine ) const bool SCH_LINE::ShouldHopOver( const SCH_LINE* aLine ) const
{ {
bool isLine1Vertical = ( m_end.x == m_start.x ); bool isLine1Vertical = ( m_end.x == m_start.x );
bool isLine2Vertical = ( aLine->GetEndPoint().x == aLine->GetStartPoint().x ); bool isLine2Vertical = ( aLine->GetEndPoint().x == aLine->GetStartPoint().x );
@ -1022,7 +1022,7 @@ bool SCH_LINE::ShouldHopOver( const SCH_LINE* aLine ) const
return true; return true;
double slope1 = 0; double slope1 = 0;
double slope2 = 0; double slope2 = 0;
slope1 = ( m_end.y - m_start.y ) / (double) ( m_end.x - m_start.x ); slope1 = ( m_end.y - m_start.y ) / (double) ( m_end.x - m_start.x );
slope2 = ( aLine->GetEndPoint().y - aLine->GetStartPoint().y ) slope2 = ( aLine->GetEndPoint().y - aLine->GetStartPoint().y )
@ -1032,6 +1032,140 @@ bool SCH_LINE::ShouldHopOver( const SCH_LINE* aLine ) const
} }
std::vector<VECTOR3I> SCH_LINE::BuildWireWithHopShape( const SCH_SCREEN* aScreen,
double aArcRadius ) const
{
// Note: Points are VECTOR3D, with Z coord used as flag
// for segments: start point and end point have the Z coord = 0
// for arcs: start point middle point and end point have the Z coord = 1
std::vector<VECTOR3I> wire_shape; // List of coordinates:
// 2 points for a segment, 3 points for an arc
if( !IsWire() )
{
wire_shape.emplace_back( GetStartPoint().x,GetStartPoint().y, 0 );
wire_shape.emplace_back( GetEndPoint().x, GetEndPoint().y, 0 );
return wire_shape;
}
std::vector<SCH_LINE*> existingWires; // wires to test (candidates)
std::vector<VECTOR2I> intersections;
for( SCH_ITEM* item : aScreen->Items().Overlapping( SCH_LINE_T, GetBoundingBox() ) )
{
SCH_LINE* line = static_cast<SCH_LINE*>( item );
if( line->IsWire() )
existingWires.push_back( line );
}
VECTOR2I currentLineStartPoint = GetStartPoint();
VECTOR2I currentLineEndPoint = GetEndPoint();
for( SCH_LINE* existingLine : existingWires )
{
VECTOR2I extLineStartPoint = existingLine->GetStartPoint();
VECTOR2I extLineEndPoint = existingLine->GetEndPoint();
if( extLineStartPoint == currentLineStartPoint && extLineEndPoint == currentLineEndPoint )
continue;
if( !ShouldHopOver( existingLine ) )
continue;
SEG currentSegment = SEG( currentLineStartPoint, currentLineEndPoint );
SEG existingSegment = SEG( extLineStartPoint, extLineEndPoint );
if( OPT_VECTOR2I intersect = currentSegment.Intersect( existingSegment, true, false ) )
{
if( IsEndPoint( *intersect ) || existingLine->IsEndPoint( *intersect ) )
continue;
intersections.push_back( *intersect );
}
}
if( intersections.empty() )
{
wire_shape.emplace_back( currentLineStartPoint.x, currentLineStartPoint.y, 0 );
wire_shape.emplace_back( currentLineEndPoint.x, currentLineEndPoint.y, 0 );
}
else
{
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 ) );
};
std::sort( intersections.begin(), intersections.end(),
[&]( const VECTOR2I& a, const VECTOR2I& b )
{
return getDistance( GetStartPoint(), a ) < getDistance( GetStartPoint(), b );
} );
VECTOR2I currentStart = GetStartPoint();
double arcRadius = aArcRadius;
for( const VECTOR2I& hopMid : intersections )
{
// Calculate the angle of the line from start point to end point in radians
double lineAngle = std::atan2( GetEndPoint().y - GetStartPoint().y,
GetEndPoint().x - 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 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
wire_shape.emplace_back( currentStart.x, currentStart.y, 0 );
wire_shape.emplace_back( beforeHop.x, beforeHop.y, 0 );
// Create an arc object
SHAPE_ARC arc( beforeHop, arcMidPoint, afterHop, 0 );
wire_shape.emplace_back( beforeHop.x, beforeHop.y, 1 );
wire_shape.emplace_back( arcMidPoint.x, arcMidPoint.y, 1 );
wire_shape.emplace_back( afterHop.x, afterHop.y, 1 );
currentStart = afterHop;
}
// Draw the final line from the current start point to the end point of the original line
wire_shape.emplace_back( currentStart. x,currentStart.y, 0 );
wire_shape.emplace_back( GetEndPoint().x, GetEndPoint().y, 0 );
}
return wire_shape;
}
static struct SCH_LINE_DESC static struct SCH_LINE_DESC
{ {
SCH_LINE_DESC() SCH_LINE_DESC()

View File

@ -29,6 +29,7 @@
#include <wx/pen.h> // for wxPenStyle #include <wx/pen.h> // for wxPenStyle
#include <list> // for std::list #include <list> // for std::list
#include <geometry/seg.h> #include <geometry/seg.h>
#include <math/vector3.h>
class NETLIST_OBJECT_LIST; class NETLIST_OBJECT_LIST;
@ -260,8 +261,23 @@ public:
bool IsParallel( const SCH_LINE* aLine ) const; bool IsParallel( const SCH_LINE* aLine ) const;
/**
* For wires only: @return true if a wire can accept a hop over arc shape
* (when 2 wires are crossing, only one must accept the hpo over)
*/
bool ShouldHopOver( const SCH_LINE* aLine ) const; bool ShouldHopOver( const SCH_LINE* aLine ) const;
/**
* For wires only: build the list of points to draw the shape using segments and 180 deg arcs
* Points are VECTOR3D, with Z coord used as flag:
* for segments: start point and end point have the Z coord = 0
* for arcs (hop over): start point middle point and end point have the Z coord = 1
* @return the list of points
* @param aScreen is the current screen to draw/plot
* @param aArcRadius is the radius of the hop over arc
*/
std::vector<VECTOR3I> BuildWireWithHopShape( const SCH_SCREEN* aScreen, double aArcRadius ) const;
void GetEndPoints( std::vector<DANGLING_END_ITEM>& aItemList ) override; void GetEndPoints( std::vector<DANGLING_END_ITEM>& aItemList ) override;
bool UpdateDanglingState( std::vector<DANGLING_END_ITEM>& aItemListByType, bool UpdateDanglingState( std::vector<DANGLING_END_ITEM>& aItemListByType,

View File

@ -1480,140 +1480,46 @@ void SCH_PAINTER::draw( const SCH_LINE* aLine, int aLayer )
m_gal->SetStrokeColor( color ); m_gal->SetStrokeColor( color );
m_gal->SetLineWidth( width ); m_gal->SetLineWidth( width );
VECTOR2I currentLineStartPoint = aLine->GetStartPoint(); double lineWidth = getLineWidth( aLine, drawingShadows, drawingNetColorHighlights );
VECTOR2I currentLineEndPoint = aLine->GetEndPoint(); double arcRadius = lineWidth * ADVANCED_CFG::GetCfg().m_hopOverArcRadius;
std::vector<VECTOR3I> curr_wire_shape = aLine->BuildWireWithHopShape( m_schematic->GetCurrentScreen(),
arcRadius );
std::vector<SCH_LINE*> existingLines; for( size_t ii = 1; ii < curr_wire_shape.size(); ii++ )
existingLines.clear();
if( aLine->IsWire() )
{ {
SCH_SCREEN* curr_screen = m_schematic->GetCurrentScreen(); VECTOR2I start( curr_wire_shape[ii-1].x, curr_wire_shape[ii-1].y );
for( SCH_ITEM* item : curr_screen->Items().Overlapping( SCH_LINE_T, aLine->GetBoundingBox() ) ) if( curr_wire_shape[ii-1].z == 0 ) // This is the start point of a segment
// there are always 2 points in list for a segment
{ {
SCH_LINE* line = static_cast<SCH_LINE*>( item ); VECTOR2I end( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
drawLine( start, end, lineStyle,
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
{
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 ) );
};
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 ); ( 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;
} }
else // This is the start point of a arc. there are always 3 points in list for an arc
{
// Hop are a small arc, so use a solid line style gives best results
VECTOR2I arc_middle( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
ii++;
VECTOR2I arc_end( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
ii++;
// Draw the final line from the current start point to the end point of the original line VECTOR2D dstart = start;
drawLine( currentStart, aLine->GetEndPoint(), lineStyle, VECTOR2D dmid = arc_middle;
( lineStyle <= LINE_STYLE::FIRST_TYPE || drawingShadows ), width ); VECTOR2D dend = arc_end;
VECTOR2D center = CalcArcCenter( dstart, dmid, dend );
EDA_ANGLE startAngle( dstart - center );
EDA_ANGLE midAngle( dmid - center );
EDA_ANGLE endAngle( dend - center );
EDA_ANGLE angle1 = midAngle - startAngle;
EDA_ANGLE angle2 = endAngle - midAngle;
EDA_ANGLE angle = angle1.Normalize180() + angle2.Normalize180();
m_gal->DrawArc( center, ( dstart - center ).EuclideanNorm(), startAngle, angle );
}
} }
} }

View File

@ -61,10 +61,12 @@
#include <locale_io.h> #include <locale_io.h>
#include <algorithm> #include <algorithm>
#include <math/vector3.h>
// TODO(JE) Debugging only // TODO(JE) Debugging only
#include <core/profile.h> #include <core/profile.h>
#include "sch_bus_entry.h" #include "sch_bus_entry.h"
#include "sch_shape.h"
/** /**
* Flag to enable profiling of the TestDanglingEnds() function. * Flag to enable profiling of the TestDanglingEnds() function.
@ -913,8 +915,53 @@ void SCH_SCREEN::Plot( PLOTTER* aPlotter, const SCH_PLOT_OPTS& aPlotOpts ) const
// Plot the foreground items // Plot the foreground items
for( SCH_ITEM* item : other ) for( SCH_ITEM* item : other )
{ {
aPlotter->SetCurrentLineWidth( item->GetEffectivePenWidth( renderSettings ) ); double lineWidth = item->GetEffectivePenWidth( renderSettings );
item->Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false ); aPlotter->SetCurrentLineWidth( lineWidth );
if( item->Type() != SCH_LINE_T )
item->Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false );
else
{
SCH_LINE* aLine = static_cast<SCH_LINE*>( item );
if( !aLine->IsWire() )
item->Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false );
else
{
double arcRadius = lineWidth * ADVANCED_CFG::GetCfg().m_hopOverArcRadius;
std::vector<VECTOR3I> curr_wire_shape = aLine->BuildWireWithHopShape( this, arcRadius );
for( size_t ii = 1; ii < curr_wire_shape.size(); ii++ )
{
VECTOR2I start( curr_wire_shape[ii-1].x, curr_wire_shape[ii-1].y );
if( curr_wire_shape[ii-1].z == 0 ) // This is the start point of a segment
// there are always 2 points in list for a segment
{
VECTOR2I end( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
SCH_LINE curr_line( *aLine );
curr_line.SetStartPoint( start );
curr_line.SetEndPoint( end );
curr_line.Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false );
}
else // This is the start point of a arc. there are always 3 points in list for an arc
{
VECTOR2I arc_middle( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
ii++;
VECTOR2I arc_end( curr_wire_shape[ii].x, curr_wire_shape[ii].y );
ii++;
SCH_SHAPE arc( SHAPE_T::ARC, aLine->GetLayer(), lineWidth );
arc.SetArcGeometry( start, arc_middle, arc_end );
// Hop are a small arc, so use a solid line style gives best results
arc.SetLineStyle( LINE_STYLE::SOLID );
arc.Plot( aPlotter, !background, aPlotOpts, 0, 0, { 0, 0 }, false );
}
}
}
}
} }
// After plotting the symbols as a group above (in `other`), we need to overplot the pins // After plotting the symbols as a group above (in `other`), we need to overplot the pins