kicad-developers team mailing list archive
-
kicad-developers team
-
Mailing list archive
-
Message #18633
[PATCH] pcbnew: Improved complex board shape handling from DXF imports
Hi,
Over the past weekend I saw a forum question/discussion re. the DXF
import for complex board shapes (particularly elliptical shapes). A
quick test with a elliptical shape indicated that it did not work properly.
I thought it would be fun to dig into that and found that typically
these shapes tend to be made of many very small segments (exports from
FreeCAD tended to be 0.05mm in length).
There were a few spots in the specctra_export fillBOUNDARY function that
struggled with these small segments. Part of that was the use of the
close_enough function in checking which end of a segment to use when the
segment length was similar in size as the proximity setting. The other
problem was that the check on the closing of the polygon that was also
using the close_enough test and possibly closed the segment prematurely
leaving segments 'dangling' that then got possibly got processed later
but no longer could become a closed polygon anymore.
This patch improves on the board outline and keepout tracing
particularly from data imported from DXF files. It takes a more accurate
decision as to which end of a segment to append to the polygon and only
terminates the polygon when no further segments can be found that are
close. Error messages are updated to make it clear if the error is with
the board outline or with the keep out and shows the coordinates of the
start and the end of the polygon so far with the estimated length
between them and also how many remaining segments there are to be processed.
So while this is an improvement in the complex shape processing, in
testing this I found a couple of interesting issues/quirks around DXF
files that may have to be addressed either in the code or in some
tutorial/documentation at some point:
* Ellipsis in segments in DXF files tend not to be closed. This becomes
quite apparent when segment length is relatively long. When the segment
length is very small (0.05mm) this is barely noticeable and it then
falls within the current limit of 0.05 mm that allows the code to close
the polygon. Using FreeCAD in Draft mode the Max Spline Length can be
adjusted in the preferences.
* When exporting a DXF from a 3D model of a board shape in FreeCAD, the
DXF seems to have both the top and the bottom of the board in the
export. The effect is that the board outline shows in pcbnew, but in the
3D viewer, there is no board visible. This confused me quite a bit but
this is due to the first ellipsis being processed as the outline segment
and the second ellipsis is processed as the keepout layer that produces
a hole the same size of the board. As such no board is visible. There
maybe something that can be done about this in the actual DXF import.
Regards,
Marco
=== modified file 'pcbnew/specctra_export.cpp'
--- pcbnew/specctra_export.cpp 2015-03-16 17:39:35 +0000
+++ pcbnew/specctra_export.cpp 2015-06-15 11:54:10 +0000
@@ -82,7 +82,7 @@
static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight )
{
// Don't need an accurate distance calculation, just something
- // approximating it, for relative orering.
+ // approximating it, for relative ordering.
return unsigned( abs( aLeft.x - aRight.x ) + abs( aLeft.y -
aRight.y ) );
}
@@ -104,6 +104,23 @@
}
+/**
+ * Function close_first
+ * is a local method of qualifying if either the start of end point of
a segment is closest to a point.
+ *
+ * @param aReference is the reference point
+ * @param aFirst is the first point
+ * @param aSecond is the second point
+ * @return bool - true if the the first point is closer to the
reference, otherwise false.
+ */
+inline bool close_first( const wxPoint& aReference, const wxPoint&
aFirst, const wxPoint& aSecond )
+{
+ // We don't use an accurate distance calculation, just something
+ // approximating to find the closest to the reference.
+ return close_ness( aReference, aFirst ) <= close_ness( aReference,
aSecond );
+}
+
+
// see wxPcbStruct.h
void PCB_EDIT_FRAME::ExportToSpecctra( wxCommandEvent& event )
{
@@ -929,6 +946,7 @@
// Find edge point with minimum x, this should be in the outer
polygon
// which will define the perimeter Edge.Cuts polygon.
+
wxPoint xmin = wxPoint( INT_MAX, 0 );
int xmini = 0;
@@ -955,7 +973,7 @@
break;
case S_ARC:
- // freerouter does not yet understand arcs, so approximate
+ // Freerouter does not yet understand arcs, so approximate
// an arc with a series of short lines and put those
// line segments into the !same! PATH.
{
@@ -1023,9 +1041,13 @@
items.Remove( xmini );
// Set maximum proximity threshold for point to point nearness
metric for
- // board perimeter only, not interior keepouts yet.
- prox = Millimeter2iu( 0.01 ); // should be enough to fix
rounding issues
- // is arc start and end point
calculations
+ // board perimeter only, not interior keep outs yet.
+ prox = Millimeter2iu( 0.05 ); // Should be enough to fix
rounding issues
+ // in arc start and end point
calculations
+ // Also FreeCAD DXF export
typically leaves
+ // a gap at the end of the
polygon that can
+ // be around the segment length
used.
+ // So 0.01mm was too small.
// Output the Edge.Cuts perimeter as circle or polygon.
if( graphic->GetShape() == S_CIRCLE )
@@ -1034,8 +1056,10 @@
}
else
{
+ // Polygon start point. Arbitrarily chosen end of the
+ // segment and build the poly from here.
+
wxPoint startPt = wxPoint( graphic->GetEnd() );
-
prevPt = graphic->GetEnd();
path->AppendPoint( mapPt( prevPt ) );
@@ -1050,19 +1074,27 @@
{
wxPoint nextPt;
- if( !close_enough( prevPt, graphic->GetStart(),
prox ) )
- {
- wxASSERT( close_enough( prevPt,
graphic->GetEnd(), prox ) );
+ // Use the line segment end point furthest away
from
+ // prevPt as we assume the other end to be ON
prevPt or
+ // very close to it.
+
+ if( close_first( prevPt, graphic->GetStart(),
graphic->GetEnd() ) )
+ {
+ nextPt = graphic->GetEnd();
+ }
+ else
+ {
nextPt = graphic->GetStart();
}
- else
- {
- wxASSERT( close_enough( prevPt,
graphic->GetStart(), prox ) );
- nextPt = graphic->GetEnd();
- }
path->AppendPoint( mapPt( nextPt ) );
prevPt = nextPt;
+
+ printf( "line GetStart()=%d,%d GetEnd()=%d,%d\n",
+ graphic->GetStart().x,
+ graphic->GetStart().y,
+ graphic->GetEnd().x,
+ graphic->GetEnd().y );
}
break;
@@ -1115,27 +1147,43 @@
break;
}
- if( close_enough( startPt, prevPt, prox ) ) // the
polygon is closed.
- break;
-
+ // Get next closest segment.
+
graphic = findPoint( prevPt, &items, prox );
- if( !graphic )
+ // If there are no more close segments, check if the board
+ // outline polygon can be closed.
+
+ if( !graphic )
{
- wxString error = wxString::Format(
- _( "Unable to find the next segment with an
endpoint of (%s mm, %s mm).\n"
- "Edit Edge.Cuts perimeter graphics, making
them contiguous polygons each." ),
- GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
- GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
- );
- ThrowIOError( error );
+ if( !close_enough( startPt, prevPt, prox ) )
+ {
+ wxString error = wxString::Format(
+ _( "The board outline polygon with start
(x: %s mm, y: %s mm)\n"
+ "and end point (x: %s mm, y: %s mm) seem
to have a gap\n"
+ "of approximately %s mm.\n\n"
+ "Edit the Edge.Cuts perimeter graphics
to make the\n"
+ "board outline a contiguous polygon.\n\n"
+ "There are %d remaining unprocessed line
segments.\n" ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( startPt.x ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( startPt.y ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( close_ness( startPt, prevPt ) ).c_str()
) ),
+ items.GetCount()
+ );
+
+ ThrowIOError( error );
+ }
+ break;
}
}
}
- // Output the interior Edge.Cuts graphics as keepouts, using
nearness metric
- // for sloppy graphical items.
- prox = Millimeter2iu( 0.25 );
+ // Output the interior Edge.Cuts graphics as keepouts, using
same nearness
+ // metric as the board edge as otherwise we have trouble
completing complex
+ // polygons.
+ prox = Millimeter2iu( 0.05 );
while( items.GetCount() )
{
@@ -1155,11 +1203,15 @@
}
else
{
+ // Polygon start point. Arbitrarily chosen end of the
+ // segment and build the poly from here.
+
wxPoint startPt( graphic->GetEnd() );
prevPt = graphic->GetEnd();
poly_ko->AppendPoint( mapPt( prevPt ) );
- // do not append the other end point yet, this first
'graphic' might be an arc
+ // Do not append the other end point yet, this first
'graphic'
+ // might be an arc
for(;;)
{
switch( graphic->GetShape() )
@@ -1168,16 +1220,18 @@
{
wxPoint nextPt;
- if( !close_enough( prevPt,
graphic->GetStart(), prox ) )
- {
- wxASSERT( close_enough( prevPt,
graphic->GetEnd(), prox ) );
+ // Use the line segment end point furthest
away from
+ // prevPt as we assume the other end to be
ON prevPt or
+ // very close to it.
+
+ if( close_first( prevPt,
graphic->GetStart(), graphic->GetEnd() ) )
+ {
+ nextPt = graphic->GetEnd();
+ }
+ else
+ {
nextPt = graphic->GetStart();
}
- else
- {
- wxASSERT( close_enough( prevPt,
graphic->GetStart(), prox ) );
- nextPt = graphic->GetEnd();
- }
prevPt = nextPt;
poly_ko->AppendPoint( mapPt( prevPt ) );
@@ -1185,7 +1239,7 @@
break;
case S_ARC:
- // freerouter does not yet understand arcs, so
approximate
+ // Freerouter does not yet understand arcs, so
approximate
// an arc with a series of short lines and put
those
// line segments into the !same! PATH.
{
@@ -1233,22 +1287,36 @@
}
break;
}
-
- if( close_enough( startPt, prevPt, prox ) )
- break;
-
+
+ // Get next closest segment.
+
graphic = findPoint( prevPt, &items, prox );
+ // If there are no more close segments, check if
polygon
+ // can be closed.
+
if( !graphic )
{
- wxString error = wxString::Format(
- _( "Unable to find the next segment with an
endpoint of (%s mm, %s mm).\n"
- "Edit Edge.Cuts interior graphics,
making them contiguous polygons each." ),
- GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
- GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
- );
+ if( !close_enough( startPt, prevPt, prox ) )
+ {
+ wxString error = wxString::Format(
+ _( "The keep out polygon with start (x:
%s mm, y: %s mm)\n"
+ "and end point (x: %s mm, y: %s mm)
seems to have a gap\n"
+ "of approximately %s mm.\n\n"
+ "Edit the Edge.Cuts perimeter
graphics, making them\n"
+ "all contiguous polygons.\n\n"
+ "There are %d remaining unprocessed
line segments.\n" ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( startPt.x ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( startPt.y ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) ),
+ GetChars( FROM_UTF8(
BOARD_ITEM::FormatInternalUnits( close_ness( startPt, prevPt ) ).c_str()
) ),
+ items.GetCount()
+ );
- ThrowIOError( error );
+ ThrowIOError( error );
+ }
+ break;
}
}
}
--
Marco Hess
Through IP Pty. Ltd. - AUSTRALIA
www.through-ip.com | marco.hess@xxxxxxxxxxxxxx
p: +61 407 78 55 66 | f: +61 8 8121 6191