Eagle SCH import: Improved net label placement algorithm

Eagle support net labels that are naming wires not directly connected to
the labels. In KiCad it is not possible, therefore such detached net
labels need to be moved, so they touch the corresponding wire.

The initial algorithm did not take into account that a moved net label
might be placed on a wire crossing, effectively shorting two nets. This
commit improves the placement algorithm by avoiding the wire crossing
points when placing a label.

Fixes: lp:1748502
* https://bugs.launchpad.net/kicad/+bug/1748502
This commit is contained in:
Maciej Suminski 2018-03-28 16:03:27 +02:00
parent 88915f7940
commit eb9099238a
2 changed files with 162 additions and 66 deletions

View File

@ -25,6 +25,7 @@
#include <wx/filename.h> #include <wx/filename.h>
#include <memory> #include <memory>
#include <algorithm>
#include <sch_junction.h> #include <sch_junction.h>
#include <sch_sheet.h> #include <sch_sheet.h>
@ -637,6 +638,7 @@ void SCH_EAGLE_PLUGIN::loadSheet( wxXmlNode* aSheetNode, int aSheetIndex )
netNode = netNode->GetNext(); netNode = netNode->GetNext();
} }
adjustNetLabels(); // needs to be called before addBusEntries()
addBusEntries(); addBusEntries();
// Loop through all instances // Loop through all instances
@ -745,20 +747,42 @@ void SCH_EAGLE_PLUGIN::loadSegments( wxXmlNode* aSegmentsNode, const wxString& n
{ {
bool labelled = false; // has a label been added to this continously connected segment bool labelled = false; // has a label been added to this continously connected segment
NODE_MAP segmentChildren = MapChildren( currentSegment ); NODE_MAP segmentChildren = MapChildren( currentSegment );
SCH_LINE* firstWire = nullptr;
m_segments.emplace_back();
SEG_DESC& segDesc = m_segments.back();
// Loop through all segment children // Loop through all segment children
wxXmlNode* segmentAttribute = currentSegment->GetChildren(); wxXmlNode* segmentAttribute = currentSegment->GetChildren();
// load wire nodes first
// label positions will then be tested for an underlying wire, since eagle labels can be seperated from the wire
DLIST<SCH_LINE> segmentWires;
segmentWires.SetOwnership( false );
while( segmentAttribute ) while( segmentAttribute )
{ {
if( segmentAttribute->GetName() == "wire" ) if( segmentAttribute->GetName() == "wire" )
segmentWires.push_back( loadWire( segmentAttribute ) ); {
SCH_LINE* wire = loadWire( segmentAttribute );
if( !firstWire )
firstWire = wire;
// Test for intersections with other wires
SEG thisWire( wire->GetStartPoint(), wire->GetEndPoint() );
for( auto& desc : m_segments )
{
if( !desc.labels.empty() && desc.labels.front()->GetText() == netName )
continue; // no point in saving intersections of the same net
for( const auto& seg : desc.segs )
{
auto intersection = thisWire.Intersect( seg, true );
if( intersection )
m_wireIntersections.push_back( *intersection );
}
}
segDesc.segs.push_back( thisWire );
screen->Append( wire );
}
segmentAttribute = segmentAttribute->GetNext(); segmentAttribute = segmentAttribute->GetNext();
} }
@ -775,7 +799,10 @@ void SCH_EAGLE_PLUGIN::loadSegments( wxXmlNode* aSegmentsNode, const wxString& n
} }
else if( nodeName == "label" ) else if( nodeName == "label" )
{ {
screen->Append( loadLabel( segmentAttribute, netName, segmentWires ) ); SCH_TEXT* label = loadLabel( segmentAttribute, netName );
screen->Append( label );
wxASSERT( segDesc.labels.empty() || segDesc.labels.front()->GetText() == label->GetText() );
segDesc.labels.push_back( label );
labelled = true; labelled = true;
} }
else if( nodeName == "pinref" ) else if( nodeName == "pinref" )
@ -797,11 +824,9 @@ void SCH_EAGLE_PLUGIN::loadSegments( wxXmlNode* aSegmentsNode, const wxString& n
segmentAttribute = segmentAttribute->GetNext(); segmentAttribute = segmentAttribute->GetNext();
} }
SCH_LINE* wire = segmentWires.begin();
// Add a small label to the net segment if it hasn't been labelled already // Add a small label to the net segment if it hasn't been labelled already
// this preserves the named net feature of Eagle schematics. // this preserves the named net feature of Eagle schematics.
if( !labelled && wire ) if( !labelled && firstWire )
{ {
wxString netname = escapeName( netName ); wxString netname = escapeName( netName );
std::unique_ptr<SCH_TEXT> label; std::unique_ptr<SCH_TEXT> label;
@ -814,7 +839,7 @@ void SCH_EAGLE_PLUGIN::loadSegments( wxXmlNode* aSegmentsNode, const wxString& n
if( label ) if( label )
{ {
label->SetPosition( wire->GetStartPoint() ); label->SetPosition( firstWire->GetStartPoint() );
label->SetText( netname ); label->SetText( netname );
label->SetTextSize( wxSize( 10, 10 ) ); label->SetTextSize( wxSize( 10, 10 ) );
label->SetLabelSpinStyle( 0 ); label->SetLabelSpinStyle( 0 );
@ -822,16 +847,6 @@ void SCH_EAGLE_PLUGIN::loadSegments( wxXmlNode* aSegmentsNode, const wxString& n
} }
} }
SCH_LINE* next_wire;
while( wire != NULL )
{
next_wire = wire->Next();
screen->Append( wire );
wire = next_wire;
}
currentSegment = currentSegment->GetNext(); currentSegment = currentSegment->GetNext();
} }
} }
@ -872,8 +887,7 @@ SCH_JUNCTION* SCH_EAGLE_PLUGIN::loadJunction( wxXmlNode* aJunction )
} }
SCH_TEXT* SCH_EAGLE_PLUGIN::loadLabel( wxXmlNode* aLabelNode, SCH_TEXT* SCH_EAGLE_PLUGIN::loadLabel( wxXmlNode* aLabelNode, const wxString& aNetName )
const wxString& aNetName, const DLIST<SCH_LINE>& segmentWires )
{ {
auto elabel = ELABEL( aLabelNode, aNetName ); auto elabel = ELABEL( aLabelNode, aNetName );
wxPoint elabelpos( elabel.x.ToSchUnits(), -elabel.y.ToSchUnits() ); wxPoint elabelpos( elabel.x.ToSchUnits(), -elabel.y.ToSchUnits() );
@ -903,80 +917,53 @@ SCH_TEXT* SCH_EAGLE_PLUGIN::loadLabel( wxXmlNode* aLabelNode,
label->SetLabelSpinStyle( (label->GetLabelSpinStyle() + 2 ) % 4 ); label->SetLabelSpinStyle( (label->GetLabelSpinStyle() + 2 ) % 4 );
} }
SCH_LINE* wire;
SCH_LINE* next_wire;
bool labelOnWire = false;
auto labelPosition = label->GetPosition();
// determine if the segment has been labelled.
for( wire = segmentWires.begin(); wire; wire = next_wire )
{
next_wire = wire->Next();
if( wire->HitTest( labelPosition, 0 ) )
{
labelOnWire = true;
break;
}
}
wire = segmentWires.begin();
// Eagle supports detached labels, so a label does not need to be placed on a wire
// to be associated with it. KiCad needs to move them, so the labels actually touch the
// corresponding wires.
if( !labelOnWire && wire )
{
wxPoint newLabelPos = findNearestLinePoint( elabelpos, segmentWires );
label->SetPosition( newLabelPos );
}
return label.release(); return label.release();
} }
wxPoint SCH_EAGLE_PLUGIN::findNearestLinePoint( const wxPoint& aPoint, const DLIST<SCH_LINE>& aLines ) std::pair<VECTOR2I, const SEG*> SCH_EAGLE_PLUGIN::findNearestLinePoint( const wxPoint& aPoint,
const std::vector<SEG>& aLines ) const
{ {
wxPoint nearestPoint; VECTOR2I nearestPoint;
const SEG* nearestLine = nullptr;
float mindistance = std::numeric_limits<float>::max(); float d, mindistance = std::numeric_limits<float>::max();
float d;
SCH_LINE* line = aLines.begin();
// Find the nearest start, middle or end of a line from the list of lines. // Find the nearest start, middle or end of a line from the list of lines.
while( line != NULL ) for( const SEG& line : aLines )
{ {
auto testpoint = line->GetStartPoint(); auto testpoint = line.A;
d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) ); d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) );
if( d < mindistance ) if( d < mindistance )
{ {
mindistance = d; mindistance = d;
nearestPoint = testpoint; nearestPoint = testpoint;
nearestLine = &line;
} }
testpoint = line->MidPoint(); testpoint = line.Center();
d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) ); d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) );
if( d < mindistance ) if( d < mindistance )
{ {
mindistance = d; mindistance = d;
nearestPoint = testpoint; nearestPoint = testpoint;
nearestLine = &line;
} }
testpoint = line->GetEndPoint(); testpoint = line.B;
d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) ); d = sqrt( abs( ( (aPoint.x - testpoint.x) ^ 2 ) + ( (aPoint.y - testpoint.y) ^ 2 ) ) );
if( d < mindistance ) if( d < mindistance )
{ {
mindistance = d; mindistance = d;
nearestPoint = testpoint; nearestPoint = testpoint;
nearestLine = &line;
} }
line = line->Next();
} }
return nearestPoint; return std::make_pair( nearestPoint, nearestLine );
} }
@ -1727,6 +1714,80 @@ void SCH_EAGLE_PLUGIN::loadFieldAttributes( LIB_FIELD* aField, const LIB_TEXT* a
} }
void SCH_EAGLE_PLUGIN::adjustNetLabels()
{
// Eagle supports detached labels, so a label does not need to be placed on a wire
// to be associated with it. KiCad needs to move them, so the labels actually touch the
// corresponding wires.
// Sort the intersection points to speed up the search process
std::sort( m_wireIntersections.begin(), m_wireIntersections.end() );
auto onIntersection = [&]( const VECTOR2I& aPos )
{
return std::binary_search( m_wireIntersections.begin(), m_wireIntersections.end(), aPos );
};
for( auto& segDesc : m_segments )
{
for( SCH_TEXT* label : segDesc.labels )
{
VECTOR2I labelPos( label->GetPosition() );
const SEG* segAttached = segDesc.LabelAttached( label );
if( segAttached && !onIntersection( labelPos ) )
continue; // label is placed correctly
// Move the label to the nearest wire
if( !segAttached )
{
std::tie( labelPos, segAttached ) = findNearestLinePoint( label->GetPosition(), segDesc.segs );
if( !segAttached ) // we cannot do anything
continue;
}
// Create a vector pointing in the direction of the wire, 50 mils long
VECTOR2I wireDirection( segAttached->B - segAttached->A );
wireDirection = wireDirection.Resize( 50 );
const VECTOR2I origPos( labelPos );
// Flags determining the search direction
bool checkPositive = true, checkNegative = true, move = false;
int trial = 0;
// Be sure the label is not placed on a wire intersection
while( ( !move || onIntersection( labelPos ) ) && ( checkPositive || checkNegative ) )
{
move = false;
// Move along the attached wire to find the new label position
if( trial % 2 == 1 )
{
labelPos = wxPoint( origPos + wireDirection * trial / 2 );
move = checkPositive = segAttached->Contains( labelPos );
}
else
{
labelPos = wxPoint( origPos - wireDirection * trial / 2 );
move = checkNegative = segAttached->Contains( labelPos );
}
++trial;
}
if( move )
label->SetPosition( wxPoint( labelPos ) );
}
}
m_segments.clear();
m_wireIntersections.clear();
}
bool SCH_EAGLE_PLUGIN::CheckHeader( const wxString& aFileName ) bool SCH_EAGLE_PLUGIN::CheckHeader( const wxString& aFileName )
{ {
// Open file and check first line // Open file and check first line
@ -2334,3 +2395,17 @@ void SCH_EAGLE_PLUGIN::addBusEntries()
} // for ( line .. } // for ( line ..
} // for ( bus .. } // for ( bus ..
} }
const SEG* SCH_EAGLE_PLUGIN::SEG_DESC::LabelAttached( const SCH_TEXT* aLabel ) const
{
VECTOR2I labelPos( aLabel->GetPosition() );
for( const auto& seg : segs )
{
if( seg.Contains( labelPos ) )
return &seg;
}
return nullptr;
}

View File

@ -29,6 +29,7 @@
#include <sch_io_mgr.h> #include <sch_io_mgr.h>
#include <eagle_parser.h> #include <eagle_parser.h>
#include <lib_draw_item.h> #include <lib_draw_item.h>
#include <geometry/seg.h>
#include <dlist.h> #include <dlist.h>
#include <boost/ptr_container/ptr_map.hpp> #include <boost/ptr_container/ptr_map.hpp>
@ -149,12 +150,13 @@ private:
/// Return the matching layer or return LAYER_NOTES /// Return the matching layer or return LAYER_NOTES
SCH_LAYER_ID kiCadLayer( int aEagleLayer ); SCH_LAYER_ID kiCadLayer( int aEagleLayer );
wxPoint findNearestLinePoint( const wxPoint& aPoint, const DLIST<SCH_LINE>& aLines ); std::pair<VECTOR2I, const SEG*> findNearestLinePoint( const wxPoint& aPoint,
const std::vector<SEG>& aLines ) const;
void loadSegments( wxXmlNode* aSegmentsNode, const wxString& aNetName, void loadSegments( wxXmlNode* aSegmentsNode, const wxString& aNetName,
const wxString& aNetClass ); const wxString& aNetClass );
SCH_LINE* loadWire( wxXmlNode* aWireNode ); SCH_LINE* loadWire( wxXmlNode* aWireNode );
SCH_TEXT* loadLabel( wxXmlNode* aLabelNode, const wxString& aNetName, const DLIST< SCH_LINE >& segmentWires); SCH_TEXT* loadLabel( wxXmlNode* aLabelNode, const wxString& aNetName );
SCH_JUNCTION* loadJunction( wxXmlNode* aJunction ); SCH_JUNCTION* loadJunction( wxXmlNode* aJunction );
SCH_TEXT* loadPlainText( wxXmlNode* aSchText ); SCH_TEXT* loadPlainText( wxXmlNode* aSchText );
@ -169,6 +171,9 @@ private:
void loadTextAttributes( EDA_TEXT* aText, const ETEXT& aAttribs ) const; void loadTextAttributes( EDA_TEXT* aText, const ETEXT& aAttribs ) const;
void loadFieldAttributes( LIB_FIELD* aField, const LIB_TEXT* aText ) const; void loadFieldAttributes( LIB_FIELD* aField, const LIB_TEXT* aText ) const;
///> Moves net labels that are detached from any wire to the nearest wire
void adjustNetLabels();
wxString getLibName(); wxString getLibName();
wxFileName getLibFileName(); wxFileName getLibFileName();
@ -187,6 +192,22 @@ private:
std::map<wxString, int> m_netCounts; std::map<wxString, int> m_netCounts;
std::map<int, SCH_LAYER_ID> m_layerMap; std::map<int, SCH_LAYER_ID> m_layerMap;
///> Wire intersection points, used for quick checks whether placing a net label in a particular
///> place would short two nets.
std::vector<VECTOR2I> m_wireIntersections;
///> Wires and labels of a single connection (segment in Eagle nomenclature)
typedef struct {
///> Tests if a particular label is attached to any of the stored segments
const SEG* LabelAttached( const SCH_TEXT* aLabel ) const;
std::vector<SCH_TEXT*> labels;
std::vector<SEG> segs;
} SEG_DESC;
///> Segments representing wires for intersection checking
std::vector<SEG_DESC> m_segments;
}; };
#endif // _SCH_EAGLE_PLUGIN_H_ #endif // _SCH_EAGLE_PLUGIN_H_