Implement preserved-corners to prevent divots when filling adjacent zones.

Fixes: lp:1460787
* https://bugs.launchpad.net/kicad/+bug/1460787
This commit is contained in:
Jeff Young 2019-07-13 23:34:09 +01:00
parent 9259f3a7c9
commit 928d6c5dff
11 changed files with 272 additions and 211 deletions

View File

@ -562,14 +562,18 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese
ClipperOffset c; ClipperOffset c;
// N.B. using jtSquare here does not create square corners. They end up mitered by // N.B. using jtSquare here does not create square corners; they end up mitered by aFactor.
// aFactor. Setting jtMiter and forcing the limit to be aFactor creates sharp corners. // Setting jtMiter with a sufficiently high MiterLimit will preserve corners, but things
JoinType type = aPreserveCorners ? jtMiter : jtRound; // get ugly at very acute angles (and we don't really want to support those anyway for peeling
// concerns). Setting a MiterLimit of 1.4145 preserves corners up to 90 degrees; we set the
// limit a bit above that.
JoinType joinType = aPreserveCorners ? jtMiter : jtRound;
double miterLimit = 1.5;
for( const POLYGON& poly : m_polys ) for( const POLYGON& poly : m_polys )
{ {
for( size_t i = 0; i < poly.size(); i++ ) for( size_t i = 0; i < poly.size(); i++ )
c.AddPath( poly[i].convertToClipper( i == 0 ), type, etClosedPolygon ); c.AddPath( poly[i].convertToClipper( i == 0 ), joinType, etClosedPolygon );
} }
PolyTree solution; PolyTree solution;
@ -595,8 +599,7 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese
coeff = arc_tolerance_factor[aCircleSegmentsCount]; coeff = arc_tolerance_factor[aCircleSegmentsCount];
c.ArcTolerance = std::abs( aFactor ) * coeff; c.ArcTolerance = std::abs( aFactor ) * coeff;
c.MiterLimit = std::abs( aFactor ); c.MiterLimit = miterLimit;
c.Execute( solution, aFactor ); c.Execute( solution, aFactor );
importTree( &solution ); importTree( &solution );
@ -1480,17 +1483,18 @@ int SHAPE_POLY_SET::TotalVertices() const
} }
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex ) SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex,
std::set<VECTOR2I>* aPreserveCorners )
{ {
return chamferFilletPolygon( CORNER_MODE::CHAMFERED, aDistance, aIndex, 0 ); return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0, aPreserveCorners );
} }
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
int aErrorMax, int aIndex,
int aIndex ) std::set<VECTOR2I>* aPreserveCorners )
{ {
return chamferFilletPolygon( CORNER_MODE::FILLETED, aRadius, aIndex, aErrorMax ); return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax, aPreserveCorners );
} }
@ -1604,32 +1608,32 @@ bool SHAPE_POLY_SET::IsVertexInHole( int aGlobalIdx )
} }
SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance ) SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance, std::set<VECTOR2I>* aPreserveCorners )
{ {
SHAPE_POLY_SET chamfered; SHAPE_POLY_SET chamfered;
for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ ) for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
chamfered.m_polys.push_back( ChamferPolygon( aDistance, polygonIdx ) ); chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx, aPreserveCorners ) );
return chamfered; return chamfered;
} }
SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax ) SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners )
{ {
SHAPE_POLY_SET filleted; SHAPE_POLY_SET filleted;
for( size_t polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ ) for( size_t idx = 0; idx < m_polys.size(); idx++ )
filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, polygonIdx ) ); filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx, aPreserveCorners ) );
return filleted; return filleted;
} }
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode, SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
unsigned int aDistance, unsigned int aDistance, int aIndex, int aErrorMax,
int aIndex, std::set<VECTOR2I>* aPreserveCorners )
int aErrorMax )
{ {
// Null segments create serious issues in calculations. Remove them: // Null segments create serious issues in calculations. Remove them:
RemoveNullSegments(); RemoveNullSegments();
@ -1656,6 +1660,12 @@ SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
int x1 = currContour.Point( currVertex ).x; int x1 = currContour.Point( currVertex ).x;
int y1 = currContour.Point( currVertex ).y; int y1 = currContour.Point( currVertex ).y;
if( aPreserveCorners && aPreserveCorners->count( VECTOR2I( x1, y1 ) ) > 0 )
{
newContour.Append( x1, y1 );
continue;
}
// Indices for previous and next vertices. // Indices for previous and next vertices.
int prevVertex; int prevVertex;
int nextVertex; int nextVertex;

View File

@ -1041,9 +1041,11 @@ class SHAPE_POLY_SET : public SHAPE
* returns a chamfered version of the aIndex-th polygon. * returns a chamfered version of the aIndex-th polygon.
* @param aDistance is the chamfering distance. * @param aDistance is the chamfering distance.
* @param aIndex is the index of the polygon to be chamfered. * @param aIndex is the index of the polygon to be chamfered.
* @param aPreserveCorners an optional set of corners which should not be chamfered.
* @return POLYGON - A polygon containing the chamfered version of the aIndex-th polygon. * @return POLYGON - A polygon containing the chamfered version of the aIndex-th polygon.
*/ */
POLYGON ChamferPolygon( unsigned int aDistance, int aIndex = 0 ); POLYGON ChamferPolygon( unsigned int aDistance, int aIndex,
std::set<VECTOR2I>* aPreserveCorners );
/** /**
* Function Fillet * Function Fillet
@ -1051,26 +1053,32 @@ class SHAPE_POLY_SET : public SHAPE
* @param aRadius is the fillet radius. * @param aRadius is the fillet radius.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle * @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* @param aIndex is the index of the polygon to be filleted * @param aIndex is the index of the polygon to be filleted
* @param aPreserveCorners an optional set of corners which should not be filleted.
* @return POLYGON - A polygon containing the filleted version of the aIndex-th polygon. * @return POLYGON - A polygon containing the filleted version of the aIndex-th polygon.
*/ */
POLYGON FilletPolygon( unsigned int aRadius, int aErrorMax, int aIndex = 0 ); POLYGON FilletPolygon( unsigned int aRadius, int aErrorMax, int aIndex,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
/** /**
* Function Chamfer * Function Chamfer
* returns a chamfered version of the polygon set. * returns a chamfered version of the polygon set.
* @param aDistance is the chamfering distance. * @param aDistance is the chamfering distance.
* @param aPreserveCorners an optional set of corners which should not be chamfered.
* @return SHAPE_POLY_SET - A set containing the chamfered version of this set. * @return SHAPE_POLY_SET - A set containing the chamfered version of this set.
*/ */
SHAPE_POLY_SET Chamfer( int aDistance ); SHAPE_POLY_SET Chamfer( int aDistance,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
/** /**
* Function Fillet * Function Fillet
* returns a filleted version of the polygon set. * returns a filleted version of the polygon set.
* @param aRadius is the fillet radius. * @param aRadius is the fillet radius.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle * @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* @param aPreserveCorners an optional set of corners which should not be filleted.
* @return SHAPE_POLY_SET - A set containing the filleted version of this set. * @return SHAPE_POLY_SET - A set containing the filleted version of this set.
*/ */
SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax ); SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners = nullptr );
/** /**
* Function DistanceToPolygon * Function DistanceToPolygon
@ -1145,6 +1153,9 @@ class SHAPE_POLY_SET : public SHAPE
void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape, void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode );
bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath,
bool aIgnoreEdges, bool aUseBBoxCaches = false ) const;
/** /**
* containsSingle function * containsSingle function
* Checks whether the point aP is inside the aSubpolyIndex-th polygon of the polyset. If * Checks whether the point aP is inside the aSubpolyIndex-th polygon of the polyset. If
@ -1186,10 +1197,12 @@ class SHAPE_POLY_SET : public SHAPE
* @param aIndex is the index of the polygon that will be chamfered/filleted. * @param aIndex is the index of the polygon that will be chamfered/filleted.
* @param aErrorMax is the maximum allowable deviation of the polygon from the circle * @param aErrorMax is the maximum allowable deviation of the polygon from the circle
* if aMode = FILLETED. If aMode = CHAMFERED, it is unused. * if aMode = FILLETED. If aMode = CHAMFERED, it is unused.
* @param aPreserveCorners an optional set of corners which should be skipped.
* @return POLYGON - the chamfered/filleted version of the polygon. * @return POLYGON - the chamfered/filleted version of the polygon.
*/ */
POLYGON chamferFilletPolygon( CORNER_MODE aMode, unsigned int aDistance, POLYGON chamferFilletPolygon( CORNER_MODE aMode, unsigned int aDistance,
int aIndex, int aErrorMax ); int aIndex, int aErrorMax,
std::set<VECTOR2I>* aPreserveCorners );
///> Returns true if the polygon set has any holes that touch share a vertex. ///> Returns true if the polygon set has any holes that touch share a vertex.
bool hasTouchingHoles( const POLYGON& aPoly ) const; bool hasTouchingHoles( const POLYGON& aPoly ) const;

View File

@ -1158,7 +1158,34 @@ void ZONE_CONTAINER::CacheTriangulation()
} }
bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const /*
* Some intersecting zones, despite being on the same layer with the same net, cannot be
* merged due to other parameters such as fillet radius. The copper pour will end up
* effectively merged though, so we want to keep the corners of such intersections sharp.
*/
void ZONE_CONTAINER::GetColinearCorners( BOARD* aBoard, std::set<VECTOR2I>& aCorners )
{
int epsilon = Millimeter2iu( 0.001 );
for( ZONE_CONTAINER* candidate : aBoard->Zones() )
{
if( candidate != this
&& candidate->GetNetCode() == GetNetCode()
&& candidate->GetLayerSet() == GetLayerSet()
&& candidate->GetIsKeepout() == GetIsKeepout() )
{
for( auto iter = m_Poly->CIterate(); iter; iter++ )
{
if( candidate->m_Poly->Collide( iter.Get(), epsilon ) )
aCorners.insert( VECTOR2I( iter.Get() ) );
}
}
}
}
bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
std::set<VECTOR2I>* aPreserveCorners ) const
{ {
if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ... if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ...
return false; return false;
@ -1167,7 +1194,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const
switch( m_cornerSmoothingType ) switch( m_cornerSmoothingType )
{ {
case ZONE_SETTINGS::SMOOTHING_CHAMFER: case ZONE_SETTINGS::SMOOTHING_CHAMFER:
aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius ); aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius, aPreserveCorners );
break; break;
case ZONE_SETTINGS::SMOOTHING_FILLET: case ZONE_SETTINGS::SMOOTHING_FILLET:
@ -1178,7 +1205,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const
if( board ) if( board )
maxError = board->GetDesignSettings().m_MaxError; maxError = board->GetDesignSettings().m_MaxError;
aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, maxError ); aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, maxError, aPreserveCorners );
break; break;
} }
default: default:
@ -1187,7 +1214,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const
// We can avoid issues by creating a very small chamfer which remove acute angles, // We can avoid issues by creating a very small chamfer which remove acute angles,
// or left it without chamfer and use only CPOLYGONS_LIST::InflateOutline to create // or left it without chamfer and use only CPOLYGONS_LIST::InflateOutline to create
// clearance areas // clearance areas
aSmoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ) ); aSmoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ), aPreserveCorners );
break; break;
} }
@ -1202,13 +1229,16 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const
* @param aUseNetClearance true to use a clearance which is the max value between * @param aUseNetClearance true to use a clearance which is the max value between
* aMinClearanceValue and the net clearance * aMinClearanceValue and the net clearance
* false to use aMinClearanceValue only * false to use aMinClearanceValue only
* @param aPreserveCorners an optional set of corners which should not be chamfered/filleted
*/ */
void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
SHAPE_POLY_SET& aCornerBuffer, int aMinClearanceValue, bool aUseNetClearance ) const int aMinClearanceValue,
bool aUseNetClearance,
std::set<VECTOR2I>* aPreserveCorners ) const
{ {
// Creates the zone outline polygon (with holes if any) // Creates the zone outline polygon (with holes if any)
SHAPE_POLY_SET polybuffer; SHAPE_POLY_SET polybuffer;
BuildSmoothedPoly( polybuffer ); BuildSmoothedPoly( polybuffer, aPreserveCorners );
// add clearance to outline // add clearance to outline
int clearance = aMinClearanceValue; int clearance = aMinClearanceValue;

View File

@ -255,6 +255,13 @@ public:
*/ */
bool HitTestFilledArea( const wxPoint& aRefPos ) const; bool HitTestFilledArea( const wxPoint& aRefPos ) const;
/**
* Some intersecting zones, despite being on the same layer with the same net, cannot be
* merged due to other parameters such as fillet radius. The copper pour will end up
* effectively merged though, so we want to keep the corners of such intersections sharp.
*/
void GetColinearCorners( BOARD* aBoard, std::set<VECTOR2I>& aCorners );
/** /**
* Function TransformSolidAreasShapesToPolygonSet * Function TransformSolidAreasShapesToPolygonSet
* Convert solid areas full shapes to polygon set * Convert solid areas full shapes to polygon set
@ -279,11 +286,12 @@ public:
* @param aUseNetClearance = true to use a clearance which is the max value between * @param aUseNetClearance = true to use a clearance which is the max value between
* aMinClearanceValue and the net clearance * aMinClearanceValue and the net clearance
* false to use aMinClearanceValue only * false to use aMinClearanceValue only
* @param aPreserveCorners an optional set of corners which should not be smoothed.
* if both aMinClearanceValue = 0 and aUseNetClearance = false: create the zone outline polygon. * if both aMinClearanceValue = 0 and aUseNetClearance = false: create the zone outline polygon.
*/ */
void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
int aMinClearanceValue, int aMinClearanceValue, bool aUseNetClearance,
bool aUseNetClearance ) const; std::set<VECTOR2I>* aPreserveCorners = nullptr ) const;
/** /**
* Function TransformShapeWithClearanceToPolygon * Function TransformShapeWithClearanceToPolygon
@ -564,9 +572,11 @@ public:
/** /**
* Function GetSmoothedPoly * Function GetSmoothedPoly
* returns a pointer to the corner-smoothed version of m_Poly. * returns a pointer to the corner-smoothed version of m_Poly.
* @param aPreserveCorners - set of corners which should /not/ be smoothed
* @return SHAPE_POLY_SET* - pointer to the polygon. * @return SHAPE_POLY_SET* - pointer to the polygon.
*/ */
bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const; bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly,
std::set<VECTOR2I>* aPreserveCorners ) const;
void SetCornerSmoothingType( int aType ) { m_cornerSmoothingType = aType; }; void SetCornerSmoothingType( int aType ) { m_cornerSmoothingType = aType; };

View File

@ -117,7 +117,7 @@ public:
void PlotDimension( DIMENSION* Dimension ); void PlotDimension( DIMENSION* Dimension );
void PlotPcbTarget( PCB_TARGET* PtMire ); void PlotPcbTarget( PCB_TARGET* PtMire );
void PlotFilledAreas( ZONE_CONTAINER* aZone ); void PlotFilledAreas( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aPolysList );
void PlotTextePcb( TEXTE_PCB* pt_texte ); void PlotTextePcb( TEXTE_PCB* pt_texte );
void PlotDrawSegment( DRAWSEGMENT* PtSegm ); void PlotDrawSegment( DRAWSEGMENT* PtSegm );

View File

@ -53,18 +53,16 @@
#include <pcbplot.h> #include <pcbplot.h>
#include <gbr_metadata.h> #include <gbr_metadata.h>
// Local /*
/* Plot a solder mask layer. * Plot a solder mask layer. Solder mask layers have a minimum thickness value and cannot be
* Solder mask layers have a minimum thickness value and cannot be drawn like standard layers, * drawn like standard layers, unless the minimum thickness is 0.
* unless the minimum thickness is 0.
*/ */
static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness );
int aMinThickness );
/* Creates the plot for silkscreen layers /*
* Silkscreen layers have specific requirement for pads (not filled) and texts * Creates the plot for silkscreen layers. Silkscreen layers have specific requirement for
* (with option to remove them from some copper areas (pads...) * pads (not filled) and texts (with option to remove them from some copper areas (pads...)
*/ */
void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
const PCB_PLOT_PARAMS& aPlotOpt ) const PCB_PLOT_PARAMS& aPlotOpt )
@ -120,23 +118,31 @@ void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
if( ! itemplotter.PlotAllTextsModule( module ) ) if( ! itemplotter.PlotAllTextsModule( module ) )
{ {
wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ), wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ),
GetChars( module->GetReference() ) ); module->GetReference() );
} }
} }
// Plot filled areas // Plot filled areas
aPlotter->StartBlock( NULL ); aPlotter->StartBlock( NULL );
for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) // Plot all zones together so we don't end up with divots where zones touch each other.
{ ZONE_CONTAINER* zone = nullptr;
ZONE_CONTAINER* edge_zone = aBoard->GetArea( ii ); SHAPE_POLY_SET aggregateArea;
if( !aLayerMask[ edge_zone->GetLayer() ] ) for( ZONE_CONTAINER* candidate : aBoard->Zones() )
{
if( !aLayerMask[ candidate->GetLayer() ] )
continue; continue;
itemplotter.PlotFilledAreas( edge_zone ); if( !zone )
zone = candidate;
aggregateArea.BooleanAdd( candidate->GetFilledPolysList(), SHAPE_POLY_SET::PM_FAST );
} }
aggregateArea.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
itemplotter.PlotFilledAreas( zone, aggregateArea );
aPlotter->EndBlock( NULL ); aPlotter->EndBlock( NULL );
} }
@ -150,8 +156,8 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
aPlotter->SetColor( aPlotOpt.GetColor() ); aPlotter->SetColor( aPlotOpt.GetColor() );
aPlotter->SetTextMode( aPlotOpt.GetTextMode() ); aPlotter->SetTextMode( aPlotOpt.GetTextMode() );
// Specify that the contents of the "Edges Pcb" layer are to be plotted // Specify that the contents of the "Edges Pcb" layer are to be plotted in addition to the
// in addition to the contents of the currently specified layer. // contents of the currently specified layer.
LSET layer_mask( aLayer ); LSET layer_mask( aLayer );
if( !aPlotOpt.GetExcludeEdgeLayer() ) if( !aPlotOpt.GetExcludeEdgeLayer() )
@ -160,8 +166,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
if( IsCopperLayer( aLayer ) ) if( IsCopperLayer( aLayer ) )
{ {
// Skip NPTH pads on copper layers ( only if hole size == pad size ): // Skip NPTH pads on copper layers ( only if hole size == pad size ):
// Drill mark will be plotted, // Drill mark will be plotted if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE
// if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE
if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) if( plotOpt.GetFormat() == PLOT_FORMAT_DXF )
{ {
plotOpt.SetSkipPlotNPTH_Pads( false ); plotOpt.SetSkipPlotNPTH_Pads( false );
@ -312,18 +317,8 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
{ {
for( auto item : module->GraphicalItems() ) for( auto item : module->GraphicalItems() )
{ {
if( !aLayerMask[ item->GetLayer() ] ) if( item->Type() == PCB_MODULE_EDGE_T && aLayerMask[ item->GetLayer() ] )
continue;
switch( item->Type() )
{
case PCB_MODULE_EDGE_T:
itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item ); itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
break;
default:
break;
}
} }
} }
@ -439,31 +434,29 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
break; break;
case PAD_SHAPE_CUSTOM: case PAD_SHAPE_CUSTOM:
{
// inflate/deflate a custom shape is a bit complex. // inflate/deflate a custom shape is a bit complex.
// so build a similar pad shape, and inflate/deflate the polygonal shape // so build a similar pad shape, and inflate/deflate the polygonal shape
{
D_PAD dummy( *pad ); D_PAD dummy( *pad );
SHAPE_POLY_SET shape; SHAPE_POLY_SET shape;
pad->MergePrimitivesAsPolygon( &shape ); pad->MergePrimitivesAsPolygon( &shape );
// shape polygon can have holes linked to the main outline. // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
// So use InflateWithLinkedHoles(), not Inflate() that can create // which can create bad shapes if margin.x is < 0
// bad shapes if margin.x is < 0 int maxError = aBoard->GetDesignSettings().m_MaxError;
int numSegs = std::max( int numSegs = std::max( GetArcToSegmentCount( margin.x, maxError, 360.0 ), 6 );
GetArcToSegmentCount( margin.x, aBoard->GetDesignSettings().m_MaxError,
360.0 ), 6 );
shape.InflateWithLinkedHoles( margin.x, numSegs, SHAPE_POLY_SET::PM_FAST ); shape.InflateWithLinkedHoles( margin.x, numSegs, SHAPE_POLY_SET::PM_FAST );
dummy.DeletePrimitivesList(); dummy.DeletePrimitivesList();
dummy.AddPrimitive( shape, 0 ); dummy.AddPrimitive( shape, 0 );
dummy.MergePrimitivesAsPolygon(); dummy.MergePrimitivesAsPolygon();
// Be sure the anchor pad is not bigger than the deflated shape // Be sure the anchor pad is not bigger than the deflated shape because this
// because this anchor will be added to the pad shape when plotting // anchor will be added to the pad shape when plotting the pad. So now the
// the pad. So now the polygonal shape is built, we can clamp the anchor size // polygonal shape is built, we can clamp the anchor size
if( margin.x < 0 ) // we expect margin.x = margin.y for custom pads if( margin.x < 0 ) // we expect margin.x = margin.y for custom pads
dummy.SetSize( padPlotsSize ); dummy.SetSize( padPlotsSize );
itemplotter.PlotPad( &dummy, color, plotMode ); itemplotter.PlotPad( &dummy, color, plotMode );
} }
break; break;
} }
@ -496,9 +489,9 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
if( !Via ) if( !Via )
continue; continue;
// vias are not plotted if not on selected layer, but if layer // vias are not plotted if not on selected layer, but if layer is SOLDERMASK_LAYER_BACK
// is SOLDERMASK_LAYER_BACK or SOLDERMASK_LAYER_FRONT,vias are drawn, // or SOLDERMASK_LAYER_FRONT, vias are drawn only if they are on the corresponding
// only if they are on the corresponding external copper layer // external copper layer
LSET via_mask_layer = Via->GetLayerSet(); LSET via_mask_layer = Via->GetLayerSet();
if( aPlotOpt.GetPlotViaOnMaskLayer() ) if( aPlotOpt.GetPlotViaOnMaskLayer() )
@ -516,8 +509,7 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
int via_margin = 0; int via_margin = 0;
double width_adj = 0; double width_adj = 0;
// If the current layer is a solder mask, use the global mask // If the current layer is a solder mask, use the global mask clearance for vias
// clearance for vias
if( aLayerMask[B_Mask] || aLayerMask[F_Mask] ) if( aLayerMask[B_Mask] || aLayerMask[F_Mask] )
via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin; via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
@ -537,8 +529,8 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
gbr_metadata.SetNetName( Via->GetNetname() ); gbr_metadata.SetNetName( Via->GetNetname() );
COLOR4D color = aBoard->Colors().GetItemColor( LAYER_VIAS + Via->GetViaType() ); COLOR4D color = aBoard->Colors().GetItemColor( LAYER_VIAS + Via->GetViaType() );
// Set plot color (change WHITE to LIGHTGRAY because // Set plot color (change WHITE to LIGHTGRAY because the white items are not seen on a
// the white items are not seen on a white paper or screen // white paper or screen
aPlotter->SetColor( color != WHITE ? color : LIGHTGRAY); aPlotter->SetColor( color != WHITE ? color : LIGHTGRAY);
aPlotter->FlashPadCircle( Via->GetStart(), diameter, plotMode, &gbr_metadata ); aPlotter->FlashPadCircle( Via->GetStart(), diameter, plotMode, &gbr_metadata );
} }
@ -570,14 +562,34 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
// Plot filled ares // Plot filled ares
aPlotter->StartBlock( NULL ); aPlotter->StartBlock( NULL );
for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ )
{
ZONE_CONTAINER* zone = aBoard->GetArea( ii );
if( !aLayerMask[zone->GetLayer()] ) // Plot all zones of the same layer & net together so we don't end up with divots where
// zones touch each other.
std::set<ZONE_CONTAINER*> plotted;
for( ZONE_CONTAINER* zone : aBoard->Zones() )
{
if( !aLayerMask[ zone->GetLayer() ] || plotted.count( zone ) )
continue; continue;
itemplotter.PlotFilledAreas( zone ); plotted.insert( zone );
SHAPE_POLY_SET aggregateArea = zone->GetFilledPolysList();
for( ZONE_CONTAINER* candidate : aBoard->Zones() )
{
if( !aLayerMask[ candidate->GetLayer() ] || plotted.count( candidate ) )
continue;
if( candidate->GetNetCode() != zone->GetNetCode() )
continue;
plotted.insert( candidate );
aggregateArea.BooleanAdd( candidate->GetFilledPolysList(), SHAPE_POLY_SET::PM_FAST );
}
aggregateArea.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
itemplotter.PlotFilledAreas( zone, aggregateArea );
} }
aPlotter->EndBlock( NULL ); aPlotter->EndBlock( NULL );
@ -646,10 +658,11 @@ static const PCB_LAYER_ID plot_seq[] = {
}; };
/* Plot outlines of copper, for copper layer /*
* Plot outlines of copper, for copper layer
*/ */
void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) const PCB_PLOT_PARAMS& aPlotOpt )
{ {
BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
@ -678,8 +691,7 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter,
const SHAPE_LINE_CHAIN& path = (kk == 0) ? outlines.COutline( ii ) : outlines.CHole( ii, kk - 1 ); const SHAPE_LINE_CHAIN& path = (kk == 0) ? outlines.COutline( ii ) : outlines.CHole( ii, kk - 1 );
for( int jj = 0; jj < path.PointCount(); jj++ ) for( int jj = 0; jj < path.PointCount(); jj++ )
cornerList.push_back( wxPoint( path.CPoint( jj ).x , path.CPoint( jj ).y ) ); cornerList.emplace_back( (wxPoint) path.CPoint( jj ) );
// Ensure the polygon is closed // Ensure the polygon is closed
if( cornerList[0] != cornerList[cornerList.size() - 1] ) if( cornerList[0] != cornerList[cornerList.size() - 1] )
@ -716,7 +728,7 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter,
int width; int width;
pad->GetOblongDrillGeometry( drl_start, drl_end, width ); pad->GetOblongDrillGeometry( drl_start, drl_end, width );
aPlotter->ThickSegment( pad->GetPosition() + drl_start, aPlotter->ThickSegment( pad->GetPosition() + drl_start,
pad->GetPosition() + drl_end, width, SKETCH, NULL ); pad->GetPosition() + drl_end, width, SKETCH, NULL );
} }
} }
} }
@ -754,9 +766,8 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter,
* plot all other shapes by flashing the basing shape * plot all other shapes by flashing the basing shape
* (shapes will be better, and calculations faster) * (shapes will be better, and calculations faster)
*/ */
void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness )
int aMinThickness )
{ {
PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask; PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
@ -768,38 +779,23 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
itemplotter.SetLayerSet( aLayerMask ); itemplotter.SetLayerSet( aLayerMask );
// Plot edge layer and graphic items // Plot edge layer and graphic items. They do not have a solder Mask margin, because they
// They do not have a solder Mask margin, because they are only graphic items // are only graphic items on this layer (like logos), not actually areas around pads.
// on this layer (like logos), not actually areas around pads.
itemplotter.PlotBoardGraphicItems(); itemplotter.PlotBoardGraphicItems();
for( auto module : aBoard->Modules() ) for( auto module : aBoard->Modules() )
{ {
for( auto item : module->GraphicalItems() ) for( auto item : module->GraphicalItems() )
{ {
if( layer != item->GetLayer() ) if( item->Type() == PCB_MODULE_EDGE_T && item->GetLayer() == layer )
continue;
switch( item->Type() )
{
case PCB_MODULE_EDGE_T:
itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item ); itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
break;
default:
break;
}
} }
} }
// Build polygons for each pad shape. // Build polygons for each pad shape. The size of the shape on solder mask should be size
// the size of the shape on solder mask should be: // of pad + clearance around the pad, where clearance = solder mask clearance + extra margin.
// size of pad + clearance around the pad. // Extra margin is half the min width for solder mask, which is used to merge too-close shapes
// clearance = solder mask clearance + extra margin // (distance < aMinThickness), and will be removed when creating the actual shapes.
// extra margin is half the min width for solder mask
// This extra margin is used to merge too close shapes
// (distance < aMinThickness), and will be removed when creating
// the actual shapes
SHAPE_POLY_SET areas; // Contains shapes to plot SHAPE_POLY_SET areas; // Contains shapes to plot
SHAPE_POLY_SET initialPolys; // Contains exact shapes to plot SHAPE_POLY_SET initialPolys; // Contains exact shapes to plot
@ -815,8 +811,7 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
// Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true, // Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true,
if( aPlotOpt.GetPlotViaOnMaskLayer() ) if( aPlotOpt.GetPlotViaOnMaskLayer() )
{ {
// The current layer is a solder mask, // The current layer is a solder mask, use the global mask clearance for vias
// use the global mask clearance for vias
int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin; int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin;
int via_margin = via_clearance + inflate; int via_margin = via_clearance + inflate;
@ -827,8 +822,7 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
if( !via ) if( !via )
continue; continue;
// vias are plotted only if they are on the corresponding // vias are plotted only if they are on the corresponding external copper layer
// external copper layer
LSET via_set = via->GetLayerSet(); LSET via_set = via->GetLayerSet();
if( via_set[B_Cu] ) if( via_set[B_Cu] )
@ -857,38 +851,43 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
if( zone->GetLayer() != layer ) if( zone->GetLayer() != layer )
continue; continue;
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false ); // Some intersecting zones, despite being on the same layer with the same net, cannot be
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false ); // merged due to other parameters such as fillet radius. The copper pour will end up
// effectively merged though, so we want to keep the corners of such intersections sharp.
std::set<VECTOR2I> colinearCorners;
zone->GetColinearCorners( aBoard, colinearCorners );
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false,
&colinearCorners );
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false,
&colinearCorners );
} }
// To avoid a lot of code, use a ZONE_CONTAINER // To avoid a lot of code, use a ZONE_CONTAINER to handle and plot polygons, because our
// to handle and plot polygons, because our polygons look exactly like // polygons look exactly like filled areas in zones.
// filled areas in zones // Note, also this code is not optimized: it creates a lot of copy/duplicate data.
// Note, also this code is not optimized: it creates a lot of copy/duplicate data // However it is not complex, and fast enough for plot purposes (copy/convert data is only a
// However it is not complex, and fast enough for plot purposes (copy/convert data // very small calculation time for these calculations).
// is only a very small calculation time for these calculations)
ZONE_CONTAINER zone( aBoard ); ZONE_CONTAINER zone( aBoard );
zone.SetMinThickness( 0 ); // trace polygons only zone.SetMinThickness( 0 ); // trace polygons only
zone.SetLayer( layer ); zone.SetLayer( layer );
int numSegs = std::max( int maxError = aBoard->GetDesignSettings().m_MaxError;
GetArcToSegmentCount( inflate, aBoard->GetDesignSettings().m_MaxError, 360.0 ), 6 ); int numSegs = std::max( GetArcToSegmentCount( inflate, maxError, 360.0 ), 6 );
areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST );
areas.Inflate( -inflate, numSegs ); areas.Deflate( inflate, numSegs );
// Combine the current areas to initial areas. This is mandatory because // Combine the current areas to initial areas. This is mandatory because inflate/deflate
// inflate/deflate transform is not perfect, and we want the initial areas perfectly kept // transform is not perfect, and we want the initial areas perfectly kept
areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST );
areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
zone.SetFilledPolysList( areas ); itemplotter.PlotFilledAreas( &zone, areas );
itemplotter.PlotFilledAreas( &zone );
} }
/**
/** Set up most plot options for plotting a board (especially the viewport) * Set up most plot options for plotting a board (especially the viewport)
* Important thing: * Important thing:
* page size is the 'drawing' page size, * page size is the 'drawing' page size,
* paper size is the physical page size * paper size is the physical page size
@ -904,14 +903,12 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
wxSize pageSizeIU( pageInfo.GetSizeIU() ); wxSize pageSizeIU( pageInfo.GetSizeIU() );
bool autocenter = false; bool autocenter = false;
/* Special options: to fit the sheet to an A4 sheet replace // Special options: to fit the sheet to an A4 sheet replace the paper size. However there
the paper size. However there is a difference between // is a difference between the autoscale and the a4paper option:
the autoscale and the a4paper option: // - Autoscale fits the board to the paper size
- Autoscale fits the board to the paper size // - A4paper fits the original paper size to an A4 sheet
- A4paper fits the original paper size to an A4 sheet // - Both of them fit the board to an A4 sheet
- Both of them fit the board to an A4 sheet if( aPlotOpts->GetA4Output() )
*/
if( aPlotOpts->GetA4Output() ) // Fit paper to A4
{ {
sheet_info = &pageA4; sheet_info = &pageA4;
paperSizeIU = pageA4.GetSizeIU(); paperSizeIU = pageA4.GetSizeIU();
@ -934,8 +931,8 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
double compound_scale; double compound_scale;
/* Fit to 80% of the page if asked; it could be that the board is empty, // Fit to 80% of the page if asked; it could be that the board is empty, in this case
* in this case regress to 1:1 scale */ // regress to 1:1 scale
if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 ) if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 )
{ {
double xscale = (paperSizeIU.x * 0.8) / boardSize.x; double xscale = (paperSizeIU.x * 0.8) / boardSize.x;
@ -947,9 +944,8 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
compound_scale = aPlotOpts->GetScale() * paperscale; compound_scale = aPlotOpts->GetScale() * paperscale;
/* For the plot offset we have to keep in mind the auxiliary origin // For the plot offset we have to keep in mind the auxiliary origin too: if autoscaling is
too: if autoscaling is off we check that plot option (i.e. autoscaling // off we check that plot option (i.e. autoscaling overrides auxiliary origin)
overrides auxiliary origin) */
wxPoint offset( 0, 0); wxPoint offset( 0, 0);
if( autocenter ) if( autocenter )
@ -963,13 +959,10 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
offset = aBoard->GetAuxOrigin(); offset = aBoard->GetAuxOrigin();
} }
/* Configure the plotter object with all the stuff computed and
most of that taken from the options */
aPlotter->SetPageSettings( *sheet_info ); aPlotter->SetPageSettings( *sheet_info );
aPlotter->SetViewport( offset, IU_PER_MILS/10, compound_scale, aPlotter->SetViewport( offset, IU_PER_MILS/10, compound_scale, aPlotOpts->GetMirror() );
aPlotOpts->GetMirror() ); // Has meaning only for gerber plotter. Must be called only after SetViewport
// has meaning only for gerber plotter. Must be called only after SetViewport
aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() ); aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() );
aPlotter->SetDefaultLineWidth( aPlotOpts->GetLineWidth() ); aPlotter->SetDefaultLineWidth( aPlotOpts->GetLineWidth() );
@ -978,48 +971,47 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
aPlotter->SetTextMode( aPlotOpts->GetTextMode() ); aPlotter->SetTextMode( aPlotOpts->GetTextMode() );
} }
/** Prefill in black an area a little bigger than the board to prepare for the
* negative plot */ /**
* Prefill in black an area a little bigger than the board to prepare for the negative plot
*/
static void FillNegativeKnockout( PLOTTER *aPlotter, const EDA_RECT &aBbbox ) static void FillNegativeKnockout( PLOTTER *aPlotter, const EDA_RECT &aBbbox )
{ {
const int margin = 5 * IU_PER_MM; // Add a 5 mm margin around the board const int margin = 5 * IU_PER_MM; // Add a 5 mm margin around the board
aPlotter->SetNegative( true ); aPlotter->SetNegative( true );
aPlotter->SetColor( WHITE ); // Which will be plotted as black aPlotter->SetColor( WHITE ); // Which will be plotted as black
EDA_RECT area = aBbbox; EDA_RECT area = aBbbox;
area.Inflate( margin ); area.Inflate( margin );
aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILLED_SHAPE ); aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILLED_SHAPE );
aPlotter->SetColor( BLACK ); aPlotter->SetColor( BLACK );
} }
/** Calculate the effective size of HPGL pens and set them in the
* plotter object */ /**
static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter, * Calculate the effective size of HPGL pens and set them in the plotter object
PCB_PLOT_PARAMS *aPlotOpts ) */
static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter, PCB_PLOT_PARAMS *aPlotOpts )
{ {
/* Compute pen_dim (the value is given in mils) in pcb units, // Compute penDiam (the value is given in mils) in pcb units, with plot scale (if Scale is 2,
with plot scale (if Scale is 2, pen diameter value is always m_HPGLPenDiam // penDiam value is always m_HPGLPenDiam so apparent penDiam is actually penDiam / Scale
so apparent pen diam is actually pen diam / Scale */ int penDiam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS / aPlotOpts->GetScale() );
int pen_diam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS /
aPlotOpts->GetScale() );
// Set HPGL-specific options and start // Set HPGL-specific options and start
aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() ); aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() );
aPlotter->SetPenNumber( aPlotOpts->GetHPGLPenNum() ); aPlotter->SetPenNumber( aPlotOpts->GetHPGLPenNum() );
aPlotter->SetPenDiameter( pen_diam ); aPlotter->SetPenDiameter( penDiam );
} }
/** Open a new plotfile using the options (and especially the format)
* specified in the options and prepare the page for plotting. /**
* Return the plotter object if OK, NULL if the file is not created * Open a new plotfile using the options (and especially the format) specified in the options
* (or has a problem) * and prepare the page for plotting.
* Return the plotter object if OK, NULL if the file is not created (or has a problem)
*/ */
PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, int aLayer,
int aLayer, const wxString& aFullFileName, const wxString& aSheetDesc )
const wxString& aFullFileName,
const wxString& aSheetDesc )
{ {
// Create the plotter driver and set the few plotter specific // Create the plotter driver and set the few plotter specific options
// options
PLOTTER* plotter = NULL; PLOTTER* plotter = NULL;
switch( aPlotOpts->GetFormat() ) switch( aPlotOpts->GetFormat() )
@ -1049,8 +1041,7 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts,
HPGL_PLOTTER* HPGL_plotter; HPGL_PLOTTER* HPGL_plotter;
HPGL_plotter = new HPGL_PLOTTER(); HPGL_plotter = new HPGL_PLOTTER();
/* HPGL options are a little more convoluted to compute, so // HPGL options are a little more convoluted to compute, so they get their own function
they're split in another function */
ConfigureHPGLPenSizes( HPGL_plotter, aPlotOpts ); ConfigureHPGLPenSizes( HPGL_plotter, aPlotOpts );
plotter = HPGL_plotter; plotter = HPGL_plotter;
break; break;
@ -1070,8 +1061,7 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts,
// Compute the viewport and set the other options // Compute the viewport and set the other options
// page layout is not mirrored, so temporary change mirror option // page layout is not mirrored, so temporarily change mirror option for the page layout
// just to plot the page layout
PCB_PLOT_PARAMS plotOpts = *aPlotOpts; PCB_PLOT_PARAMS plotOpts = *aPlotOpts;
if( plotOpts.GetPlotFrameRef() && plotOpts.GetMirror() ) if( plotOpts.GetPlotFrameRef() && plotOpts.GetMirror() )
@ -1101,19 +1091,16 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts,
// Plot the frame reference if requested // Plot the frame reference if requested
if( aPlotOpts->GetPlotFrameRef() ) if( aPlotOpts->GetPlotFrameRef() )
{ {
PlotWorkSheet( plotter, aBoard->GetTitleBlock(), PlotWorkSheet( plotter, aBoard->GetTitleBlock(), aBoard->GetPageSettings(),
aBoard->GetPageSettings(), 1, 1, aSheetDesc, aBoard->GetFileName() );
1, 1, // Only one page
aSheetDesc, aBoard->GetFileName() );
if( aPlotOpts->GetMirror() ) if( aPlotOpts->GetMirror() )
initializePlotter( plotter, aBoard, aPlotOpts ); initializePlotter( plotter, aBoard, aPlotOpts );
} }
/* When plotting a negative board: draw a black rectangle // When plotting a negative board: draw a black rectangle (background for plot board
* (background for plot board in white) and switch the current // in white) and switch the current color to WHITE; note the color inversion is actually
* color to WHITE; note the color inversion is actually done // done in the driver (if supported)
* in the driver (if supported) */
if( aPlotOpts->GetNegative() ) if( aPlotOpts->GetNegative() )
{ {
EDA_RECT bbox = aBoard->ComputeBoundingBox(); EDA_RECT bbox = aBoard->ComputeBoundingBox();

View File

@ -611,11 +611,8 @@ void BRDITEMS_PLOTTER::PlotTextePcb( TEXTE_PCB* pt_texte )
} }
void BRDITEMS_PLOTTER::PlotFilledAreas( ZONE_CONTAINER* aZone ) void BRDITEMS_PLOTTER::PlotFilledAreas( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& polysList )
{ {
//Plot areas (given by .m_FilledPolysList member) in a zone
const SHAPE_POLY_SET& polysList = aZone->GetFilledPolysList();
if( polysList.IsEmpty() ) if( polysList.IsEmpty() )
return; return;

View File

@ -781,7 +781,13 @@ bool PNS_KICAD_IFACE::syncZone( PNS::NODE* aWorld, ZONE_CONTAINER* aZone )
if( !aZone->GetIsKeepout() || !aZone->GetDoNotAllowTracks() ) if( !aZone->GetIsKeepout() || !aZone->GetDoNotAllowTracks() )
return false; return false;
aZone->BuildSmoothedPoly( poly ); // Some intersecting zones, despite being on the same layer with the same net, cannot be
// merged due to other parameters such as fillet radius. The copper pour will end up
// effectively merged though, so we want to keep the corners of such intersections sharp.
std::set<VECTOR2I> colinearCorners;
aZone->GetColinearCorners( m_board, colinearCorners );
aZone->BuildSmoothedPoly( poly, &colinearCorners );
poly.CacheTriangulation(); poly.CacheTriangulation();
if( !poly.IsTriangulationUpToDate() ) if( !poly.IsTriangulationUpToDate() )

View File

@ -192,8 +192,11 @@ int DRC::TestZoneToZoneOutline( ZONE_CONTAINER* aZone, bool aCreateMarkers )
for( int ia = 0; ia < board->GetAreaCount(); ia++ ) for( int ia = 0; ia < board->GetAreaCount(); ia++ )
{ {
ZONE_CONTAINER* zoneRef = board->GetArea( ia ); ZONE_CONTAINER* zoneRef = board->GetArea( ia );
zoneRef->BuildSmoothedPoly( smoothed_polys[ia] ); std::set<VECTOR2I> colinearCorners;
zoneRef->GetColinearCorners( board, colinearCorners );
zoneRef->BuildSmoothedPoly( smoothed_polys[ia], &colinearCorners );
} }
// iterate through all areas // iterate through all areas

View File

@ -665,6 +665,7 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_
*/ */
void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
const SHAPE_POLY_SET& aSmoothedOutline, const SHAPE_POLY_SET& aSmoothedOutline,
std::set<VECTOR2I>* aPreserveCorners,
SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aRawPolys,
SHAPE_POLY_SET& aFinalPolys ) SHAPE_POLY_SET& aFinalPolys )
{ {
@ -744,7 +745,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
// Prune features that don't meet minimum-width criteria // Prune features that don't meet minimum-width criteria
aRawPolys.Deflate( half_min_width - epsilon, numSegs ); aRawPolys.Deflate( half_min_width - epsilon, numSegs, true );
if( s_DumpZonesWhenFilling ) if( s_DumpZonesWhenFilling )
dumper->Write( &aRawPolys, "solid-areas-before-hatching" ); dumper->Write( &aRawPolys, "solid-areas-before-hatching" );
@ -759,13 +760,13 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
// Re-inflate after pruning of areas that don't meet minimum-width criteria // Re-inflate after pruning of areas that don't meet minimum-width criteria
if( aZone->GetFilledPolysUseThickness() ) if( aZone->GetFilledPolysUseThickness() )
{ {
// if we're stroking the zone with a min-width stroke then this will naturally // If we're stroking the zone with a min_width stroke then this will naturally
// inflate the zone // inflate the zone by half_min_width
} }
else if( half_min_width - epsilon > epsilon ) // avoid very small outline thickness else if( half_min_width - epsilon > epsilon ) // avoid very small outline thickness
{ {
aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST ); aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST );
aRawPolys.Inflate( half_min_width - epsilon, 16 ); aRawPolys.Inflate( half_min_width - epsilon, numSegs, true );
} }
aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST ); aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
@ -789,18 +790,20 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol
SHAPE_POLY_SET& aFinalPolys ) SHAPE_POLY_SET& aFinalPolys )
{ {
SHAPE_POLY_SET smoothedPoly; SHAPE_POLY_SET smoothedPoly;
std::set<VECTOR2I> colinearCorners;
aZone->GetColinearCorners( m_board, colinearCorners );
/* /*
* convert outlines + holes to outlines without holes (adding extra segments if necessary) * convert outlines + holes to outlines without holes (adding extra segments if necessary)
* m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
* this zone * this zone
*/ */
if ( !aZone->BuildSmoothedPoly( smoothedPoly ) ) if ( !aZone->BuildSmoothedPoly( smoothedPoly, &colinearCorners ) )
return false; return false;
if( aZone->IsOnCopperLayer() ) if( aZone->IsOnCopperLayer() )
{ {
computeRawFilledArea( aZone, smoothedPoly, aRawPolys, aFinalPolys ); computeRawFilledArea( aZone, smoothedPoly, &colinearCorners, aRawPolys, aFinalPolys );
} }
else else
{ {

View File

@ -67,7 +67,9 @@ private:
* filled copper area polygon (without clearance areas * filled copper area polygon (without clearance areas
* @param aPcb: the current board * @param aPcb: the current board
*/ */
void computeRawFilledArea( const ZONE_CONTAINER* aZone, const SHAPE_POLY_SET& aSmoothedOutline, void computeRawFilledArea( const ZONE_CONTAINER* aZone,
const SHAPE_POLY_SET& aSmoothedOutline,
std::set<VECTOR2I>* aPreserveCorners,
SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys ); SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys );
/** /**