diff --git a/common/geometry/shape_poly_set.cpp b/common/geometry/shape_poly_set.cpp index bb3aea3f62..e5c951629c 100644 --- a/common/geometry/shape_poly_set.cpp +++ b/common/geometry/shape_poly_set.cpp @@ -562,14 +562,18 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese ClipperOffset c; - // N.B. using jtSquare here does not create square corners. They end up mitered by - // aFactor. Setting jtMiter and forcing the limit to be aFactor creates sharp corners. - JoinType type = aPreserveCorners ? jtMiter : jtRound; + // N.B. using jtSquare here does not create square corners; they end up mitered by aFactor. + // Setting jtMiter with a sufficiently high MiterLimit will preserve corners, but things + // 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( 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; @@ -595,8 +599,7 @@ void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPrese coeff = arc_tolerance_factor[aCircleSegmentsCount]; c.ArcTolerance = std::abs( aFactor ) * coeff; - c.MiterLimit = std::abs( aFactor ); - + c.MiterLimit = miterLimit; c.Execute( solution, aFactor ); 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* 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, - int aErrorMax, - int aIndex ) +SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax, + int aIndex, + std::set* 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* aPreserveCorners ) { SHAPE_POLY_SET chamfered; - for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ ) - chamfered.m_polys.push_back( ChamferPolygon( aDistance, polygonIdx ) ); + for( unsigned int idx = 0; idx < m_polys.size(); idx++ ) + chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx, aPreserveCorners ) ); 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* aPreserveCorners ) { SHAPE_POLY_SET filleted; - for( size_t polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ ) - filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, polygonIdx ) ); + for( size_t idx = 0; idx < m_polys.size(); idx++ ) + filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx, aPreserveCorners ) ); return filleted; } SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode, - unsigned int aDistance, - int aIndex, - int aErrorMax ) + unsigned int aDistance, int aIndex, int aErrorMax, + std::set* aPreserveCorners ) { // Null segments create serious issues in calculations. Remove them: RemoveNullSegments(); @@ -1656,6 +1660,12 @@ SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode, int x1 = currContour.Point( currVertex ).x; 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. int prevVertex; int nextVertex; diff --git a/include/geometry/shape_poly_set.h b/include/geometry/shape_poly_set.h index d9a6c1853c..9b60997f43 100644 --- a/include/geometry/shape_poly_set.h +++ b/include/geometry/shape_poly_set.h @@ -1041,9 +1041,11 @@ class SHAPE_POLY_SET : public SHAPE * returns a chamfered version of the aIndex-th polygon. * @param aDistance is the chamfering distance. * @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. */ - POLYGON ChamferPolygon( unsigned int aDistance, int aIndex = 0 ); + POLYGON ChamferPolygon( unsigned int aDistance, int aIndex, + std::set* aPreserveCorners ); /** * Function Fillet @@ -1051,26 +1053,32 @@ class SHAPE_POLY_SET : public SHAPE * @param aRadius is the fillet radius. * @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 aPreserveCorners an optional set of corners which should not be filleted. * @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* aPreserveCorners = nullptr ); /** * Function Chamfer * returns a chamfered version of the polygon set. * @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. */ - SHAPE_POLY_SET Chamfer( int aDistance ); + SHAPE_POLY_SET Chamfer( int aDistance, + std::set* aPreserveCorners = nullptr ); /** * Function Fillet * returns a filleted version of the polygon set. * @param aRadius is the fillet radius. * @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. */ - SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax ); + SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax, + std::set* aPreserveCorners = nullptr ); /** * Function DistanceToPolygon @@ -1145,6 +1153,9 @@ class SHAPE_POLY_SET : public SHAPE void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape, 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 * 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 aErrorMax is the maximum allowable deviation of the polygon from the circle * 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. */ POLYGON chamferFilletPolygon( CORNER_MODE aMode, unsigned int aDistance, - int aIndex, int aErrorMax ); + int aIndex, int aErrorMax, + std::set* aPreserveCorners ); ///> Returns true if the polygon set has any holes that touch share a vertex. bool hasTouchingHoles( const POLYGON& aPoly ) const; diff --git a/pcbnew/class_zone.cpp b/pcbnew/class_zone.cpp index c8eb69c957..da6f3eb650 100644 --- a/pcbnew/class_zone.cpp +++ b/pcbnew/class_zone.cpp @@ -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& 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* aPreserveCorners ) const { if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ... return false; @@ -1167,7 +1194,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const switch( m_cornerSmoothingType ) { case ZONE_SETTINGS::SMOOTHING_CHAMFER: - aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius ); + aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius, aPreserveCorners ); break; case ZONE_SETTINGS::SMOOTHING_FILLET: @@ -1178,7 +1205,7 @@ bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const if( board ) maxError = board->GetDesignSettings().m_MaxError; - aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, maxError ); + aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, maxError, aPreserveCorners ); break; } 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, // or left it without chamfer and use only CPOLYGONS_LIST::InflateOutline to create // clearance areas - aSmoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ) ); + aSmoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ), aPreserveCorners ); 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 * aMinClearanceValue and the net clearance * false to use aMinClearanceValue only + * @param aPreserveCorners an optional set of corners which should not be chamfered/filleted */ -void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( - SHAPE_POLY_SET& aCornerBuffer, int aMinClearanceValue, bool aUseNetClearance ) const +void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, + int aMinClearanceValue, + bool aUseNetClearance, + std::set* aPreserveCorners ) const { // Creates the zone outline polygon (with holes if any) SHAPE_POLY_SET polybuffer; - BuildSmoothedPoly( polybuffer ); + BuildSmoothedPoly( polybuffer, aPreserveCorners ); // add clearance to outline int clearance = aMinClearanceValue; diff --git a/pcbnew/class_zone.h b/pcbnew/class_zone.h index 75a48d8dc9..5d0ef1720b 100644 --- a/pcbnew/class_zone.h +++ b/pcbnew/class_zone.h @@ -255,6 +255,13 @@ public: */ 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& aCorners ); + /** * Function TransformSolidAreasShapesToPolygonSet * 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 * aMinClearanceValue and the net clearance * 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. */ void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, - int aMinClearanceValue, - bool aUseNetClearance ) const; + int aMinClearanceValue, bool aUseNetClearance, + std::set* aPreserveCorners = nullptr ) const; /** * Function TransformShapeWithClearanceToPolygon @@ -564,9 +572,11 @@ public: /** * Function GetSmoothedPoly * 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. */ - bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const; + bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, + std::set* aPreserveCorners ) const; void SetCornerSmoothingType( int aType ) { m_cornerSmoothingType = aType; }; diff --git a/pcbnew/pcbplot.h b/pcbnew/pcbplot.h index a98ad9602d..6cff84d28b 100644 --- a/pcbnew/pcbplot.h +++ b/pcbnew/pcbplot.h @@ -117,7 +117,7 @@ public: void PlotDimension( DIMENSION* Dimension ); 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 PlotDrawSegment( DRAWSEGMENT* PtSegm ); diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 2457391eac..1504512e53 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -53,18 +53,16 @@ #include #include -// Local -/* Plot a solder mask layer. - * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers, - * unless the minimum thickness is 0. +/* + * Plot a solder mask layer. Solder mask layers have a minimum thickness value and cannot be + * drawn like standard layers, unless the minimum thickness is 0. */ -static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, - LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, - int aMinThickness ); +static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, + const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness ); -/* Creates the plot for silkscreen layers - * Silkscreen layers have specific requirement for pads (not filled) and texts - * (with option to remove them from some copper areas (pads...) +/* + * Creates the plot for silkscreen layers. Silkscreen layers have specific requirement for + * pads (not filled) and texts (with option to remove them from some copper areas (pads...) */ void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) @@ -120,23 +118,31 @@ void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, if( ! itemplotter.PlotAllTextsModule( module ) ) { wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ), - GetChars( module->GetReference() ) ); + module->GetReference() ); } } // Plot filled areas aPlotter->StartBlock( NULL ); - for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) - { - ZONE_CONTAINER* edge_zone = aBoard->GetArea( ii ); + // Plot all zones together so we don't end up with divots where zones touch each other. + ZONE_CONTAINER* zone = nullptr; + SHAPE_POLY_SET aggregateArea; - if( !aLayerMask[ edge_zone->GetLayer() ] ) + for( ZONE_CONTAINER* candidate : aBoard->Zones() ) + { + if( !aLayerMask[ candidate->GetLayer() ] ) 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 ); } @@ -150,8 +156,8 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer, aPlotter->SetColor( aPlotOpt.GetColor() ); aPlotter->SetTextMode( aPlotOpt.GetTextMode() ); - // Specify that the contents of the "Edges Pcb" layer are to be plotted - // in addition to the contents of the currently specified layer. + // Specify that the contents of the "Edges Pcb" layer are to be plotted in addition to the + // contents of the currently specified layer. LSET layer_mask( aLayer ); if( !aPlotOpt.GetExcludeEdgeLayer() ) @@ -160,8 +166,7 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer, if( IsCopperLayer( aLayer ) ) { // Skip NPTH pads on copper layers ( only if hole size == pad size ): - // Drill mark will be plotted, - // if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE + // Drill mark will be plotted if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) { plotOpt.SetSkipPlotNPTH_Pads( false ); @@ -312,18 +317,8 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, { for( auto item : module->GraphicalItems() ) { - if( !aLayerMask[ item->GetLayer() ] ) - continue; - - switch( item->Type() ) - { - case PCB_MODULE_EDGE_T: + if( item->Type() == PCB_MODULE_EDGE_T && aLayerMask[ item->GetLayer() ] ) itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item ); - break; - - default: - break; - } } } @@ -439,31 +434,29 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, break; case PAD_SHAPE_CUSTOM: + { // inflate/deflate a custom shape is a bit complex. // so build a similar pad shape, and inflate/deflate the polygonal shape - { D_PAD dummy( *pad ); SHAPE_POLY_SET shape; pad->MergePrimitivesAsPolygon( &shape ); - // shape polygon can have holes linked to the main outline. - // So use InflateWithLinkedHoles(), not Inflate() that can create - // bad shapes if margin.x is < 0 - int numSegs = std::max( - GetArcToSegmentCount( margin.x, aBoard->GetDesignSettings().m_MaxError, - 360.0 ), 6 ); + // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate() + // which can create bad shapes if margin.x is < 0 + int maxError = aBoard->GetDesignSettings().m_MaxError; + int numSegs = std::max( GetArcToSegmentCount( margin.x, maxError, 360.0 ), 6 ); shape.InflateWithLinkedHoles( margin.x, numSegs, SHAPE_POLY_SET::PM_FAST ); dummy.DeletePrimitivesList(); dummy.AddPrimitive( shape, 0 ); dummy.MergePrimitivesAsPolygon(); - // Be sure the anchor pad is not bigger than the deflated shape - // because this anchor will be added to the pad shape when plotting - // the pad. So now the polygonal shape is built, we can clamp the anchor size + // Be sure the anchor pad is not bigger than the deflated shape because this + // anchor will be added to the pad shape when plotting the pad. So now the + // polygonal shape is built, we can clamp the anchor size if( margin.x < 0 ) // we expect margin.x = margin.y for custom pads dummy.SetSize( padPlotsSize ); itemplotter.PlotPad( &dummy, color, plotMode ); - } + } break; } @@ -496,9 +489,9 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, if( !Via ) continue; - // vias are not plotted if not on selected layer, but if layer - // is SOLDERMASK_LAYER_BACK or SOLDERMASK_LAYER_FRONT,vias are drawn, - // only if they are on the corresponding external copper layer + // vias are not plotted if not on selected layer, but if layer is SOLDERMASK_LAYER_BACK + // or SOLDERMASK_LAYER_FRONT, vias are drawn only if they are on the corresponding + // external copper layer LSET via_mask_layer = Via->GetLayerSet(); if( aPlotOpt.GetPlotViaOnMaskLayer() ) @@ -516,8 +509,7 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, int via_margin = 0; double width_adj = 0; - // If the current layer is a solder mask, use the global mask - // clearance for vias + // If the current layer is a solder mask, use the global mask clearance for vias if( aLayerMask[B_Mask] || aLayerMask[F_Mask] ) via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin; @@ -537,8 +529,8 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, gbr_metadata.SetNetName( Via->GetNetname() ); COLOR4D color = aBoard->Colors().GetItemColor( LAYER_VIAS + Via->GetViaType() ); - // Set plot color (change WHITE to LIGHTGRAY because - // the white items are not seen on a white paper or screen + // Set plot color (change WHITE to LIGHTGRAY because the white items are not seen on a + // white paper or screen aPlotter->SetColor( color != WHITE ? color : LIGHTGRAY); aPlotter->FlashPadCircle( Via->GetStart(), diameter, plotMode, &gbr_metadata ); } @@ -570,14 +562,34 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, // Plot filled ares 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 plotted; + + for( ZONE_CONTAINER* zone : aBoard->Zones() ) + { + if( !aLayerMask[ zone->GetLayer() ] || plotted.count( zone ) ) 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 ); @@ -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, - LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) +void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, + const PCB_PLOT_PARAMS& 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 ); 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 if( cornerList[0] != cornerList[cornerList.size() - 1] ) @@ -716,7 +728,7 @@ void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, int width; pad->GetOblongDrillGeometry( drl_start, drl_end, width ); 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 * (shapes will be better, and calculations faster) */ -void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, - LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt, - int aMinThickness ) +void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, + const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness ) { 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 ); itemplotter.SetLayerSet( aLayerMask ); - // Plot edge layer and graphic items - // They do not have a solder Mask margin, because they are only graphic items - // on this layer (like logos), not actually areas around pads. + // Plot edge layer and graphic items. They do not have a solder Mask margin, because they + // are only graphic items on this layer (like logos), not actually areas around pads. itemplotter.PlotBoardGraphicItems(); for( auto module : aBoard->Modules() ) { for( auto item : module->GraphicalItems() ) { - if( layer != item->GetLayer() ) - continue; - - switch( item->Type() ) - { - case PCB_MODULE_EDGE_T: + if( item->Type() == PCB_MODULE_EDGE_T && item->GetLayer() == layer ) itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item ); - break; - - default: - break; - } } } - // Build polygons for each pad shape. - // the size of the shape on solder mask should be: - // size of pad + clearance around the pad. - // clearance = solder mask clearance + extra margin - // 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 + // Build polygons for each pad shape. The size of the shape on solder mask should be size + // of pad + clearance around the pad, where clearance = solder mask clearance + extra margin. + // Extra margin is half the min width for solder mask, which 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 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, if( aPlotOpt.GetPlotViaOnMaskLayer() ) { - // The current layer is a solder mask, - // use the global mask clearance for vias + // The current layer is a solder mask, use the global mask clearance for vias int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin; int via_margin = via_clearance + inflate; @@ -827,8 +822,7 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, if( !via ) continue; - // vias are plotted only if they are on the corresponding - // external copper layer + // vias are plotted only if they are on the corresponding external copper layer LSET via_set = via->GetLayerSet(); if( via_set[B_Cu] ) @@ -857,38 +851,43 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, if( zone->GetLayer() != layer ) continue; - zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false ); - zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false ); + // 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 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 handle and plot polygons, because our polygons look exactly like - // filled areas in zones - // 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 very small calculation time for these calculations) + // To avoid a lot of code, use a ZONE_CONTAINER to handle and plot polygons, because our + // polygons look exactly like filled areas in zones. + // 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 + // very small calculation time for these calculations). ZONE_CONTAINER zone( aBoard ); zone.SetMinThickness( 0 ); // trace polygons only zone.SetLayer( layer ); - int numSegs = std::max( - GetArcToSegmentCount( inflate, aBoard->GetDesignSettings().m_MaxError, 360.0 ), 6 ); + int maxError = aBoard->GetDesignSettings().m_MaxError; + int numSegs = std::max( GetArcToSegmentCount( inflate, maxError, 360.0 ), 6 ); 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 - // inflate/deflate transform is not perfect, and we want the initial areas perfectly kept + // Combine the current areas to initial areas. This is mandatory because inflate/deflate + // transform is not perfect, and we want the initial areas perfectly kept areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - zone.SetFilledPolysList( areas ); - - itemplotter.PlotFilledAreas( &zone ); + itemplotter.PlotFilledAreas( &zone, areas ); } - -/** 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: * page size is the 'drawing' page size, * paper size is the physical page size @@ -904,14 +903,12 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard, wxSize pageSizeIU( pageInfo.GetSizeIU() ); bool autocenter = false; - /* Special options: to fit the sheet to an A4 sheet replace - the paper size. However there is a difference between - the autoscale and the a4paper option: - - Autoscale fits the board to the paper size - - A4paper fits the original paper size to an A4 sheet - - Both of them fit the board to an A4 sheet - */ - if( aPlotOpts->GetA4Output() ) // Fit paper to A4 + // Special options: to fit the sheet to an A4 sheet replace the paper size. However there + // is a difference between the autoscale and the a4paper option: + // - Autoscale fits the board to the paper size + // - A4paper fits the original paper size to an A4 sheet + // - Both of them fit the board to an A4 sheet + if( aPlotOpts->GetA4Output() ) { sheet_info = &pageA4; paperSizeIU = pageA4.GetSizeIU(); @@ -934,8 +931,8 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard, double compound_scale; - /* Fit to 80% of the page if asked; it could be that the board is empty, - * in this case regress to 1:1 scale */ + // Fit to 80% of the page if asked; it could be that the board is empty, in this case + // regress to 1:1 scale if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 ) { double xscale = (paperSizeIU.x * 0.8) / boardSize.x; @@ -947,9 +944,8 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard, compound_scale = aPlotOpts->GetScale() * paperscale; - /* For the plot offset we have to keep in mind the auxiliary origin - too: if autoscaling is off we check that plot option (i.e. autoscaling - overrides auxiliary origin) */ + // For the plot offset we have to keep in mind the auxiliary origin too: if autoscaling is + // off we check that plot option (i.e. autoscaling overrides auxiliary origin) wxPoint offset( 0, 0); if( autocenter ) @@ -963,13 +959,10 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard, 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->SetViewport( offset, IU_PER_MILS/10, compound_scale, - aPlotOpts->GetMirror() ); - // has meaning only for gerber plotter. Must be called only after SetViewport + aPlotter->SetViewport( offset, IU_PER_MILS/10, compound_scale, aPlotOpts->GetMirror() ); + // Has meaning only for gerber plotter. Must be called only after SetViewport aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() ); aPlotter->SetDefaultLineWidth( aPlotOpts->GetLineWidth() ); @@ -978,48 +971,47 @@ static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard, 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 ) { const int margin = 5 * IU_PER_MM; // Add a 5 mm margin around the board 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; area.Inflate( margin ); aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILLED_SHAPE ); aPlotter->SetColor( BLACK ); } -/** Calculate the effective size of HPGL pens and set them in the - * plotter object */ -static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter, - PCB_PLOT_PARAMS *aPlotOpts ) + +/** + * Calculate the effective size of HPGL pens and set them in the plotter object + */ +static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter, PCB_PLOT_PARAMS *aPlotOpts ) { - /* Compute pen_dim (the value is given in mils) in pcb units, - with plot scale (if Scale is 2, pen diameter value is always m_HPGLPenDiam - so apparent pen diam is actually pen diam / Scale */ - int pen_diam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS / - aPlotOpts->GetScale() ); + // Compute penDiam (the value is given in mils) in pcb units, with plot scale (if Scale is 2, + // penDiam value is always m_HPGLPenDiam so apparent penDiam is actually penDiam / Scale + int penDiam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS / aPlotOpts->GetScale() ); // Set HPGL-specific options and start aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() ); 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 - * (or has a problem) + +/** + * 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 (or has a problem) */ -PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, - int aLayer, - const wxString& aFullFileName, - const wxString& aSheetDesc ) +PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, int aLayer, + const wxString& aFullFileName, const wxString& aSheetDesc ) { - // Create the plotter driver and set the few plotter specific - // options + // Create the plotter driver and set the few plotter specific options PLOTTER* plotter = NULL; switch( aPlotOpts->GetFormat() ) @@ -1049,8 +1041,7 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, HPGL_PLOTTER* HPGL_plotter; HPGL_plotter = new HPGL_PLOTTER(); - /* HPGL options are a little more convoluted to compute, so - they're split in another function */ + // HPGL options are a little more convoluted to compute, so they get their own function ConfigureHPGLPenSizes( HPGL_plotter, aPlotOpts ); plotter = HPGL_plotter; break; @@ -1070,8 +1061,7 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, // Compute the viewport and set the other options - // page layout is not mirrored, so temporary change mirror option - // just to plot the page layout + // page layout is not mirrored, so temporarily change mirror option for the page layout PCB_PLOT_PARAMS plotOpts = *aPlotOpts; if( plotOpts.GetPlotFrameRef() && plotOpts.GetMirror() ) @@ -1101,19 +1091,16 @@ PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts, // Plot the frame reference if requested if( aPlotOpts->GetPlotFrameRef() ) { - PlotWorkSheet( plotter, aBoard->GetTitleBlock(), - aBoard->GetPageSettings(), - 1, 1, // Only one page - aSheetDesc, aBoard->GetFileName() ); + PlotWorkSheet( plotter, aBoard->GetTitleBlock(), aBoard->GetPageSettings(), + 1, 1, aSheetDesc, aBoard->GetFileName() ); if( aPlotOpts->GetMirror() ) initializePlotter( plotter, aBoard, aPlotOpts ); } - /* When plotting a negative board: draw a black rectangle - * (background for plot board in white) and switch the current - * color to WHITE; note the color inversion is actually done - * in the driver (if supported) */ + // When plotting a negative board: draw a black rectangle (background for plot board + // in white) and switch the current color to WHITE; note the color inversion is actually + // done in the driver (if supported) if( aPlotOpts->GetNegative() ) { EDA_RECT bbox = aBoard->ComputeBoundingBox(); diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index 950614c255..70d0b7ec42 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -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() ) return; diff --git a/pcbnew/router/pns_kicad_iface.cpp b/pcbnew/router/pns_kicad_iface.cpp index 4b0517310b..8578e4ca16 100644 --- a/pcbnew/router/pns_kicad_iface.cpp +++ b/pcbnew/router/pns_kicad_iface.cpp @@ -781,7 +781,13 @@ bool PNS_KICAD_IFACE::syncZone( PNS::NODE* aWorld, ZONE_CONTAINER* aZone ) if( !aZone->GetIsKeepout() || !aZone->GetDoNotAllowTracks() ) 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 colinearCorners; + aZone->GetColinearCorners( m_board, colinearCorners ); + + aZone->BuildSmoothedPoly( poly, &colinearCorners ); poly.CacheTriangulation(); if( !poly.IsTriangulationUpToDate() ) diff --git a/pcbnew/tools/drc.cpp b/pcbnew/tools/drc.cpp index b8a575b1f8..c6516994e5 100644 --- a/pcbnew/tools/drc.cpp +++ b/pcbnew/tools/drc.cpp @@ -192,8 +192,11 @@ int DRC::TestZoneToZoneOutline( ZONE_CONTAINER* aZone, bool aCreateMarkers ) for( int ia = 0; ia < board->GetAreaCount(); ia++ ) { - ZONE_CONTAINER* zoneRef = board->GetArea( ia ); - zoneRef->BuildSmoothedPoly( smoothed_polys[ia] ); + ZONE_CONTAINER* zoneRef = board->GetArea( ia ); + std::set colinearCorners; + zoneRef->GetColinearCorners( board, colinearCorners ); + + zoneRef->BuildSmoothedPoly( smoothed_polys[ia], &colinearCorners ); } // iterate through all areas diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index e3dbbc675b..dc828a3bd0 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -665,6 +665,7 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_ */ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, const SHAPE_POLY_SET& aSmoothedOutline, + std::set* aPreserveCorners, SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys ) { @@ -744,7 +745,7 @@ void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST ); // 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 ) 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 if( aZone->GetFilledPolysUseThickness() ) { - // if we're stroking the zone with a min-width stroke then this will naturally - // inflate the zone + // If we're stroking the zone with a min_width stroke then this will naturally + // inflate the zone by half_min_width } else if( half_min_width - epsilon > epsilon ) // avoid very small outline thickness { 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 ); @@ -789,18 +790,20 @@ bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPol SHAPE_POLY_SET& aFinalPolys ) { SHAPE_POLY_SET smoothedPoly; + std::set colinearCorners; + aZone->GetColinearCorners( m_board, colinearCorners ); /* * 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 * this zone */ - if ( !aZone->BuildSmoothedPoly( smoothedPoly ) ) + if ( !aZone->BuildSmoothedPoly( smoothedPoly, &colinearCorners ) ) return false; if( aZone->IsOnCopperLayer() ) { - computeRawFilledArea( aZone, smoothedPoly, aRawPolys, aFinalPolys ); + computeRawFilledArea( aZone, smoothedPoly, &colinearCorners, aRawPolys, aFinalPolys ); } else { diff --git a/pcbnew/zone_filler.h b/pcbnew/zone_filler.h index a4a58186b8..da3cfcd058 100644 --- a/pcbnew/zone_filler.h +++ b/pcbnew/zone_filler.h @@ -67,7 +67,9 @@ private: * filled copper area polygon (without clearance areas * @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* aPreserveCorners, SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys ); /**