From f34b86d39e6e6ff39e54e4b775ebfcc45a6ba7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20W=C5=82ostowski?= Date: Mon, 4 Dec 2017 19:06:47 +0100 Subject: [PATCH] pcbnew: made zone filling algorithm thread-safe. - moved zone filling algo outside ZONE_CONTAINER class - const'ified methods that don't need to modify zone's properties - cleanup: m_FillMode -> enum --- pcbnew/CMakeLists.txt | 1 - ...board_items_to_polygon_shape_transform.cpp | 2 +- pcbnew/class_zone.cpp | 57 +- pcbnew/class_zone.h | 120 +-- pcbnew/class_zone_settings.cpp | 2 +- pcbnew/class_zone_settings.h | 12 +- pcbnew/dialogs/dialog_copper_zones.cpp | 4 +- .../dialog_non_copper_zones_properties.cpp | 2 +- pcbnew/exporters/export_vrml.cpp | 9 +- pcbnew/kicad_plugin.cpp | 8 +- pcbnew/legacy_plugin.cpp | 8 +- pcbnew/pcb_parser.cpp | 12 +- pcbnew/plot_board_layers.cpp | 2 +- pcbnew/plot_brditems_plotter.cpp | 4 +- pcbnew/zone_filler.cpp | 736 ++++++++++++++- pcbnew/zone_filler.h | 67 +- pcbnew/zone_filling_algorithm.cpp | 878 ------------------ ...ones_convert_to_polygons_aux_functions.cpp | 8 +- pcbnew/zones_test_and_combine_areas.cpp | 26 +- 19 files changed, 920 insertions(+), 1038 deletions(-) diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index d11f04b807..b66d2d64d8 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -284,7 +284,6 @@ set( PCBNEW_CLASS_SRCS zones_convert_to_polygons_aux_functions.cpp zones_by_polygon.cpp zones_by_polygon_fill_functions.cpp - zone_filling_algorithm.cpp zones_functions_for_undo_redo.cpp zones_test_and_combine_areas.cpp class_footprint_wizard.cpp diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp index 7c8aef7626..7f64d4eab6 100644 --- a/pcbnew/board_items_to_polygon_shape_transform.cpp +++ b/pcbnew/board_items_to_polygon_shape_transform.cpp @@ -826,7 +826,7 @@ bool D_PAD::BuildPadDrillShapePolygon( SHAPE_POLY_SET& aCornerBuffer, * change the shape by creating stubs and destroy their properties. */ void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer, - D_PAD& aPad, + const D_PAD& aPad, int aThermalGap, int aCopperThickness, int aMinThicknessValue, diff --git a/pcbnew/class_zone.cpp b/pcbnew/class_zone.cpp index 54159cf3ea..02ad31afe1 100644 --- a/pcbnew/class_zone.cpp +++ b/pcbnew/class_zone.cpp @@ -55,9 +55,8 @@ ZONE_CONTAINER::ZONE_CONTAINER( BOARD* aBoard ) : { m_CornerSelection = nullptr; // no corner is selected m_IsFilled = false; // fill status : true when the zone is filled - m_FillMode = 0; // How to fill areas: 0 = use filled polygons, != 0 fill with segments + m_FillMode = ZFM_POLYGONS; m_priority = 0; - m_smoothedPoly = NULL; m_cornerSmoothingType = ZONE_SETTINGS::SMOOTHING_NONE; SetIsKeepout( false ); SetDoNotAllowCopperPour( false ); // has meaning only if m_isKeepout == true @@ -73,8 +72,6 @@ ZONE_CONTAINER::ZONE_CONTAINER( BOARD* aBoard ) : ZONE_CONTAINER::ZONE_CONTAINER( const ZONE_CONTAINER& aZone ) : BOARD_CONNECTED_ITEM( aZone ) { - m_smoothedPoly = NULL; - // Should the copy be on the same net? SetNetCode( aZone.GetNetCode() ); m_Poly = new SHAPE_POLY_SET( *aZone.m_Poly ); @@ -143,7 +140,6 @@ ZONE_CONTAINER& ZONE_CONTAINER::operator=( const ZONE_CONTAINER& aOther ) ZONE_CONTAINER::~ZONE_CONTAINER() { delete m_Poly; - delete m_smoothedPoly; delete m_CornerSelection; } @@ -499,7 +495,7 @@ void ZONE_CONTAINER::DrawFilledArea( EDA_DRAW_PANEL* panel, } // Draw areas: - if( m_FillMode == 0 && !outline_mode ) + if( m_FillMode == ZFM_POLYGONS && !outline_mode ) GRPoly( panel->GetClipBox(), DC, CornersBuffer.size(), &CornersBuffer[0], true, 0, color, color ); } @@ -508,8 +504,8 @@ void ZONE_CONTAINER::DrawFilledArea( EDA_DRAW_PANEL* panel, { for( unsigned ic = 0; ic < m_FillSegmList.size(); ic++ ) { - wxPoint start = m_FillSegmList[ic].m_Start + offset; - wxPoint end = m_FillSegmList[ic].m_End + offset; + wxPoint start = (wxPoint) ( m_FillSegmList[ic].A + VECTOR2I(offset) ); + wxPoint end = (wxPoint) ( m_FillSegmList[ic].B + VECTOR2I(offset) ); if( !displ_opts->m_DisplayPcbTrackFill || GetState( FORCE_SKETCH ) ) GRCSegm( panel->GetClipBox(), DC, start.x, start.y, end.x, end.y, @@ -912,8 +908,8 @@ void ZONE_CONTAINER::Move( const wxPoint& offset ) for( unsigned ic = 0; ic < m_FillSegmList.size(); ic++ ) { - m_FillSegmList[ic].m_Start += offset; - m_FillSegmList[ic].m_End += offset; + m_FillSegmList[ic].A += VECTOR2I(offset); + m_FillSegmList[ic].B += VECTOR2I(offset); } } @@ -951,8 +947,12 @@ void ZONE_CONTAINER::Rotate( const wxPoint& centre, double angle ) for( unsigned ic = 0; ic < m_FillSegmList.size(); ic++ ) { - RotatePoint( &m_FillSegmList[ic].m_Start, centre, angle ); - RotatePoint( &m_FillSegmList[ic].m_End, centre, angle ); + wxPoint a ( m_FillSegmList[ic].A ); + RotatePoint( &a, centre, angle ); + m_FillSegmList[ic].A = a; + wxPoint b ( m_FillSegmList[ic].B ); + RotatePoint( &b, centre, angle ); + m_FillSegmList[ic].B = a; } } @@ -991,8 +991,8 @@ void ZONE_CONTAINER::Mirror( const wxPoint& mirror_ref ) for( unsigned ic = 0; ic < m_FillSegmList.size(); ic++ ) { - MIRROR( m_FillSegmList[ic].m_Start.y, mirror_ref.y ); - MIRROR( m_FillSegmList[ic].m_End.y, mirror_ref.y ); + MIRROR( m_FillSegmList[ic].A.y, mirror_ref.y ); + MIRROR( m_FillSegmList[ic].B.y, mirror_ref.y ); } } @@ -1317,3 +1317,32 @@ void ZONE_CONTAINER::CacheTriangulation() { m_FilledPolysList.CacheTriangulation(); } + +bool ZONE_CONTAINER::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const +{ + if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ... + return false; + + // Make a smoothed polygon out of the user-drawn polygon if required + switch( m_cornerSmoothingType ) + { + case ZONE_SETTINGS::SMOOTHING_CHAMFER: + aSmoothedPoly = m_Poly->Chamfer( m_cornerRadius ); + break; + + case ZONE_SETTINGS::SMOOTHING_FILLET: + aSmoothedPoly = m_Poly->Fillet( m_cornerRadius, m_ArcToSegmentsCount ); + break; + + default: + // Acute angles between adjacent edges can create issues in calculations, + // in inflate/deflate outlines transforms, especially when the angle is very small. + // 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 ) ); + break; + } + + return true; +}; diff --git a/pcbnew/class_zone.h b/pcbnew/class_zone.h index 3688359c72..31074f129a 100644 --- a/pcbnew/class_zone.h +++ b/pcbnew/class_zone.h @@ -49,25 +49,7 @@ class BOARD; class ZONE_CONTAINER; class MSG_PANEL_ITEM; - -/** - * Struct SEGMENT - * is a simple container used when filling areas with segments - */ -struct SEGMENT -{ - wxPoint m_Start; // starting point of a segment - wxPoint m_End; // ending point of a segment - - SEGMENT() {} - - SEGMENT( const wxPoint& aStart, const wxPoint& aEnd ) - { - m_Start = aStart; - m_End = aEnd; - } -}; - +typedef std::vector ZONE_SEGMENT_FILL; /** * Class ZONE_CONTAINER @@ -196,9 +178,8 @@ public: virtual void ViewGetLayers( int aLayers[], int& aCount ) const override; - /// How to fill areas: 0 = use filled polygons, 1 => fill with segments. - void SetFillMode( int aFillMode ) { m_FillMode = aFillMode; } - int GetFillMode() const { return m_FillMode; } + void SetFillMode( ZONE_FILL_MODE aFillMode ) { m_FillMode = aFillMode; } + ZONE_FILL_MODE GetFillMode() const { return m_FillMode; } void SetThermalReliefGap( int aThermalReliefGap ) { m_ThermalReliefGap = aThermalReliefGap; } int GetThermalReliefGap( D_PAD* aPad = NULL ) const; @@ -256,8 +237,8 @@ public: int GetLocalFlags() const { return m_localFlgs; } void SetLocalFlags( int aFlags ) { m_localFlgs = aFlags; } - std::vector & FillSegments() { return m_FillSegmList; } - const std::vector & FillSegments() const { return m_FillSegmList; } + ZONE_SEGMENT_FILL& FillSegments() { return m_FillSegmList; } + const ZONE_SEGMENT_FILL& FillSegments() const { return m_FillSegmList; } SHAPE_POLY_SET* Outline() { return m_Poly; } const SHAPE_POLY_SET* Outline() const { return const_cast< SHAPE_POLY_SET* >( m_Poly ); } @@ -306,42 +287,6 @@ public: void TransformSolidAreasShapesToPolygonSet( SHAPE_POLY_SET& aCornerBuffer, int aCircleToSegmentsCount, double aCorrectionFactor ) const; - /** - * Build the filled solid areas polygons from zone outlines (stored in m_Poly) - * The solid areas can be more than one on copper layers, and do not have holes - ( holes are linked by overlapping segments to the main outline) - * in order to have drawable (and plottable) filled polygons. - * @return true if OK, false if the solid polygons cannot be built - * @param aPcb: the current board (can be NULL for non copper zones) - * @param aOutlineBuffer: A reference to a SHAPE_POLY_SET buffer to store polygons, or NULL. - * if NULL (default): - * - m_FilledPolysList is used to store solid areas polygons. - * - on copper layers, tracks and other items shapes of other nets are - * removed from solid areas - * if not null: - * Only the zone outline (with holes, if any) is stored in aOutlineBuffer - * with holes linked. Therefore only one polygon is created - * - * When aOutlineBuffer is not null, his function calls - * AddClearanceAreasPolygonsToPolysList() to add holes for pads and tracks - * and other items not in net. - */ - bool BuildFilledSolidAreasPolygons( BOARD* aPcb, SHAPE_POLY_SET* aOutlineBuffer = NULL ); - - /** - * Function ComputeRawFilledAreas - * Add non copper areas polygons (pads and tracks with clearance) - * to a filled copper area - * used in BuildFilledSolidAreasPolygons when calculating filled areas in a zone - * Non copper areas are pads and track and their clearance area - * The filled copper area must be computed before - * BuildFilledSolidAreasPolygons() call this function just after creating the - * filled copper area polygon (without clearance areas - * @param aPcb: the current board - * _NG version uses SHAPE_POLY_SET instead of Boost.Polygon - */ - void ComputeRawFilledAreas( BOARD* aPcb ); - /** * Function TransformOutlinesShapeWithClearanceToPolygon @@ -359,7 +304,7 @@ public: */ void TransformOutlinesShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aMinClearanceValue, - bool aUseNetClearance ); + bool aUseNetClearance ) const; void TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, @@ -409,15 +354,6 @@ public: */ bool HitTest( const EDA_RECT& aRect, bool aContained = true, int aAccuracy = 0 ) const override; - /** - * Function FillZoneAreasWithSegments - * Fill sub areas in a zone with segments with m_ZoneMinThickness width - * A scan is made line per line, on the whole filled areas, with a step of m_ZoneMinThickness. - * all intersecting points with the horizontal infinite line and polygons to fill are calculated - * a list of SEGZONE items is built, line per line - * @return true if success, false on error - */ - bool FillZoneAreasWithSegments(); /** * Function UnFill @@ -610,27 +546,31 @@ public: void CacheTriangulation(); /** - * Function AddFilledPolysList + * Function SetFilledPolysList * sets the list of filled polygons. */ - void AddFilledPolysList( SHAPE_POLY_SET& aPolysList ) + void SetFilledPolysList( SHAPE_POLY_SET& aPolysList ) { m_FilledPolysList = aPolysList; } + /** + * Function SetFilledPolysList + * sets the list of filled polygons. + */ + void SetRawPolysList( SHAPE_POLY_SET& aPolysList ) + { + m_RawPolysList = aPolysList; + } + + /** * Function GetSmoothedPoly * returns a pointer to the corner-smoothed version of * m_Poly if it exists, otherwise it returns m_Poly. * @return SHAPE_POLY_SET* - pointer to the polygon. */ - SHAPE_POLY_SET* GetSmoothedPoly() const - { - if( m_smoothedPoly ) - return m_smoothedPoly; - else - return m_Poly; - }; + bool BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly ) const; void SetCornerSmoothingType( int aType ) { m_cornerSmoothingType = aType; }; @@ -647,19 +587,9 @@ public: */ void AddPolygon( std::vector< wxPoint >& aPolygon ); - /** - * add a polygon to the zone filled areas list. - * these polygons have no hole, therefore any added polygon is a new - * filled area - */ - void AddFilledPolygon( SHAPE_POLY_SET& aPolygon ) + void SetFillSegments( const ZONE_SEGMENT_FILL& aSegments ) { - m_FilledPolysList.Append( aPolygon ); - } - - void AddFillSegments( std::vector< SEGMENT >& aSegments ) - { - m_FillSegmList.insert( m_FillSegmList.end(), aSegments.begin(), aSegments.end() ); + m_FillSegmList = aSegments; } SHAPE_POLY_SET& RawPolysList() @@ -743,10 +673,8 @@ public: virtual void SwapData( BOARD_ITEM* aImage ) override; private: - void buildFeatureHoleList( BOARD* aPcb, SHAPE_POLY_SET& aFeatures ); SHAPE_POLY_SET* m_Poly; ///< Outline of the zone. - SHAPE_POLY_SET* m_smoothedPoly; // Corner-smoothed version of m_Poly int m_cornerSmoothingType; unsigned int m_cornerRadius; @@ -788,8 +716,8 @@ private: int m_ThermalReliefCopperBridge; - /// How to fill areas: 0 => use filled polygons, 1 => fill with segments. - int m_FillMode; + /// How to fill areas: ZFM_POLYGONS => use filled polygons, ZFM_SEGMENTS => fill with segments. + ZONE_FILL_MODE m_FillMode; /// The index of the corner being moved or nullptr if no corner is selected. SHAPE_POLY_SET::VERTEX_INDEX* m_CornerSelection; @@ -800,7 +728,7 @@ private: /** Segments used to fill the zone (#m_FillMode ==1 ), when fill zone by segment is used. * In this case the segments have #m_ZoneMinThickness width. */ - std::vector m_FillSegmList; + ZONE_SEGMENT_FILL m_FillSegmList; /* set of filled polygons used to draw a zone as a filled area. * from outlines (m_Poly) but unlike m_Poly these filled polygons have no hole diff --git a/pcbnew/class_zone_settings.cpp b/pcbnew/class_zone_settings.cpp index b55efa66b8..bc18e9186b 100644 --- a/pcbnew/class_zone_settings.cpp +++ b/pcbnew/class_zone_settings.cpp @@ -38,7 +38,7 @@ ZONE_SETTINGS::ZONE_SETTINGS() { m_ZonePriority = 0; - m_FillMode = 0; // Mode for filling zone : 1 use segments, 0 use polygons + m_FillMode = ZFM_POLYGONS; // Mode for filling zone : 1 use segments, 0 use polygons // Zone clearance value m_ZoneClearance = Mils2iu( ZONE_CLEARANCE_MIL ); // Min thickness value in filled areas (this is the minimum width of copper to fill solid areas) : diff --git a/pcbnew/class_zone_settings.h b/pcbnew/class_zone_settings.h index 73d6452369..1850d14e54 100644 --- a/pcbnew/class_zone_settings.h +++ b/pcbnew/class_zone_settings.h @@ -32,12 +32,13 @@ #include "zones.h" - -class ZONE_CONTAINER; - - #define MAX_ZONE_CORNER_RADIUS_MILS 400 +enum ZONE_FILL_MODE +{ + ZFM_POLYGONS = 0, // fill zone with polygons + ZFM_SEGMENTS = 1 // fill zone with segments (legacy) +}; /** * Class ZONE_SETTINGS @@ -55,8 +56,7 @@ public: SMOOTHING_LAST }; - /// Mode for filling zone : 1 use segments, 0 use polygons - int m_FillMode; + ZONE_FILL_MODE m_FillMode; int m_ZonePriority; ///< Priority (0 ... N) of the zone diff --git a/pcbnew/dialogs/dialog_copper_zones.cpp b/pcbnew/dialogs/dialog_copper_zones.cpp index 7cb592c179..5243cdaf65 100644 --- a/pcbnew/dialogs/dialog_copper_zones.cpp +++ b/pcbnew/dialogs/dialog_copper_zones.cpp @@ -172,7 +172,7 @@ void DIALOG_COPPER_ZONE::initDialog() if( m_settings.m_Zone_45_Only ) m_OrientEdgesOpt->SetSelection( 1 ); - m_FillModeCtrl->SetSelection( m_settings.m_FillMode ? 1 : 0 ); + m_FillModeCtrl->SetSelection( m_settings.m_FillMode == ZFM_SEGMENTS ? 1 : 0 ); AddUnitSymbol( *m_ClearanceValueTitle, g_UserUnit ); msg = StringFromValue( g_UserUnit, m_settings.m_ZoneClearance ); @@ -381,7 +381,7 @@ bool DIALOG_COPPER_ZONE::AcceptOptions( bool aPromptForErrors, bool aUseExportab } m_netNameShowFilter = m_ShowNetNameFilter->GetValue(); - m_settings.m_FillMode = (m_FillModeCtrl->GetSelection() == 0) ? 0 : 1; + m_settings.m_FillMode = (m_FillModeCtrl->GetSelection() == 0) ? ZFM_POLYGONS : ZFM_SEGMENTS; wxString txtvalue = m_ZoneClearanceCtrl->GetValue(); m_settings.m_ZoneClearance = ValueFromString( g_UserUnit, txtvalue ); diff --git a/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp b/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp index b6cecade0f..fdf827b831 100644 --- a/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp +++ b/pcbnew/dialogs/dialog_non_copper_zones_properties.cpp @@ -198,7 +198,7 @@ void DIALOG_NON_COPPER_ZONES_EDITOR::OnOkClick( wxCommandEvent& event ) return; } - m_settings.m_FillMode = 0; // Use always polygon fill mode + m_settings.m_FillMode = ZFM_POLYGONS; // Use always polygon fill mode switch( m_OutlineAppearanceCtrl->GetSelection() ) { diff --git a/pcbnew/exporters/export_vrml.cpp b/pcbnew/exporters/export_vrml.cpp index cb4f607d94..0f8e313150 100644 --- a/pcbnew/exporters/export_vrml.cpp +++ b/pcbnew/exporters/export_vrml.cpp @@ -49,6 +49,8 @@ #include "wxPcbStruct.h" #include "../../kicad/kicad.h" +#include + // minimum width (mm) of a VRML line #define MIN_VRML_LINEWIDTH 0.12 @@ -951,10 +953,13 @@ static void export_vrml_zones( MODEL_VRML& aModel, BOARD* aPcb ) if( !GetLayer( aModel, zone->GetLayer(), &vl ) ) continue; + // fixme: this modifies the board where it shouldn't, but I don't have the time + // to clean this up - TW if( !zone->IsFilled() ) { - zone->SetFillMode( 0 ); // use filled polygons - zone->BuildFilledSolidAreasPolygons( aPcb ); + ZONE_FILLER filler( aPcb ); + zone->SetFillMode( ZFM_POLYGONS ); // use filled polygons + filler.Fill( { zone } ); } const SHAPE_POLY_SET& poly = zone->GetFilledPolysList(); diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp index 518e1c6dcc..786d4a96fd 100644 --- a/pcbnew/kicad_plugin.cpp +++ b/pcbnew/kicad_plugin.cpp @@ -1869,17 +1869,17 @@ void PCB_IO::format( ZONE_CONTAINER* aZone, int aNestLevel ) const } // Save the filling segments list - const std::vector< SEGMENT >& segs = aZone->FillSegments(); + const auto& segs = aZone->FillSegments(); if( segs.size() ) { m_out->Print( aNestLevel+1, "(fill_segments\n" ); - for( std::vector< SEGMENT >::const_iterator it = segs.begin(); it != segs.end(); ++it ) + for( ZONE_SEGMENT_FILL::const_iterator it = segs.begin(); it != segs.end(); ++it ) { m_out->Print( aNestLevel+2, "(pts (xy %s) (xy %s))\n", - FMT_IU( it->m_Start ).c_str(), - FMT_IU( it->m_End ).c_str() ); + FMT_IU( wxPoint( it->A ) ).c_str(), + FMT_IU( wxPoint( it->B ) ).c_str() ); } m_out->Print( aNestLevel+1, ")\n" ); diff --git a/pcbnew/legacy_plugin.cpp b/pcbnew/legacy_plugin.cpp index 4c73bc3439..c100c78cf5 100644 --- a/pcbnew/legacy_plugin.cpp +++ b/pcbnew/legacy_plugin.cpp @@ -2613,7 +2613,7 @@ void LEGACY_PLUGIN::loadZONE_CONTAINER() BIU thermalReliefGap = biuParse( data += 2 , &data ); // +=2 for " F" BIU thermalReliefCopperBridge = biuParse( data ); - zc->SetFillMode( fillmode ? 1 : 0 ); + zc->SetFillMode( fillmode ? ZFM_SEGMENTS : ZFM_POLYGONS ); // @todo ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF: don't really want pcbnew.h // in here, after all, its a PLUGIN and global data is evil. @@ -2690,7 +2690,7 @@ void LEGACY_PLUGIN::loadZONE_CONTAINER() makeNewOutline = end_contour; } - zc->AddFilledPolysList( polysList ); + zc->SetFilledPolysList( polysList ); } else if( TESTLINE( "$FILLSEGMENTS" ) ) @@ -2706,7 +2706,7 @@ void LEGACY_PLUGIN::loadZONE_CONTAINER() BIU ex = biuParse( data, &data ); BIU ey = biuParse( data ); - zc->FillSegments().push_back( SEGMENT( wxPoint( sx, sy ), wxPoint( ex, ey ) ) ); + zc->FillSegments().push_back( SEG( VECTOR2I( sx, sy ), VECTOR2I( ex, ey ) ) ); } } @@ -2724,7 +2724,7 @@ void LEGACY_PLUGIN::loadZONE_CONTAINER() { if( !zc->IsOnCopperLayer() ) { - zc->SetFillMode( 0 ); + zc->SetFillMode( ZFM_POLYGONS ); zc->SetNetCode( NETINFO_LIST::UNCONNECTED ); } diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp index 4d9faa8d4c..96147abdb4 100644 --- a/pcbnew/pcb_parser.cpp +++ b/pcbnew/pcb_parser.cpp @@ -2948,7 +2948,7 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER() Expecting( "segment or polygon" ); // @todo Create an enum for fill modes. - zone->SetFillMode( token == T_polygon ? 0 : 1 ); + zone->SetFillMode( token == T_polygon ? ZFM_POLYGONS : ZFM_SEGMENTS ); NeedRIGHT(); break; @@ -3092,7 +3092,7 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER() case T_fill_segments: { - std::vector< SEGMENT > segs; + ZONE_SEGMENT_FILL segs; for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { @@ -3104,12 +3104,12 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER() if( token != T_pts ) Expecting( T_pts ); - SEGMENT segment( parseXY(), parseXY() ); + SEG segment( parseXY(), parseXY() ); NeedRIGHT(); segs.push_back( segment ); } - zone->AddFillSegments( segs ); + zone->SetFillSegments( segs ); } break; @@ -3123,7 +3123,7 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER() { if( !zone->IsOnCopperLayer() ) { - zone->SetFillMode( 0 ); + zone->SetFillMode( ZFM_POLYGONS ); zone->SetNetCode( NETINFO_LIST::UNCONNECTED ); } @@ -3132,7 +3132,7 @@ ZONE_CONTAINER* PCB_PARSER::parseZONE_CONTAINER() } if( !pts.IsEmpty() ) - zone->AddFilledPolysList( pts ); + zone->SetFilledPolysList( pts ); // Ensure keepout and non copper zones do not have a net // (which have no sense for these zones) diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 529494bae0..23cb3e2547 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -875,7 +875,7 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST ); areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - zone.AddFilledPolysList( areas ); + zone.SetFilledPolysList( areas ); itemplotter.PlotFilledAreas( &zone ); } diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index bc41c761c0..9a05f083d1 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -687,8 +687,8 @@ void BRDITEMS_PLOTTER::PlotFilledAreas( ZONE_CONTAINER* aZone ) { for( unsigned iseg = 0; iseg < aZone->FillSegments().size(); iseg++ ) { - wxPoint start = aZone->FillSegments()[iseg].m_Start; - wxPoint end = aZone->FillSegments()[iseg].m_End; + wxPoint start = (wxPoint) aZone->FillSegments()[iseg].A; + wxPoint end = (wxPoint) aZone->FillSegments()[iseg].B; m_plotter->ThickSegment( start, end, aZone->GetMinThickness(), GetPlotMode(), &gbr_metadata ); diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 8fca130cea..2d391ab3ff 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -30,21 +30,49 @@ #include #include #include +#include +#include +#include +#include +#include + #include #include #include +#include +#include +#include + #include "zone_filler.h" #ifdef USE_OPENMP #include #endif /* USE_OPENMP */ +extern void BuildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer, + const BOARD* aPcb, + const ZONE_CONTAINER* aZone, + double aArcCorrection, + double aRoundPadThermalRotation ); + + +extern void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer, + const D_PAD& aPad, + int aThermalGap, + int aCopperThickness, + int aMinThicknessValue, + int aCircleToSegmentsCount, + double aCorrectionFactor, + double aThermalRot ); + +static double s_thermalRot = 450; // angle of stubs in thermal reliefs for round pads +static const bool s_DumpZonesWhenFilling = false; ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) : - m_commit( aCommit ), m_board( aBoard ), + m_commit( aCommit ), m_progressReporter( nullptr ) { } @@ -86,7 +114,7 @@ void ZONE_FILLER::Fill( std::vector aZones ) for( int i = 0; i < toFill.size(); i++ ) { - if (m_commit) + if( m_commit ) { m_commit->Modify( toFill[i].m_zone ); } @@ -103,7 +131,14 @@ void ZONE_FILLER::Fill( std::vector aZones ) #endif for( int i = 0; i < toFill.size(); i++ ) { - toFill[i].m_zone->BuildFilledSolidAreasPolygons( m_board ); + SHAPE_POLY_SET rawPolys, finalPolys; + ZONE_SEGMENT_FILL segFill; + fillSingleZone ( toFill[i].m_zone, rawPolys, finalPolys, segFill ); + + toFill[i].m_zone->SetRawPolysList( rawPolys ); + toFill[i].m_zone->SetFilledPolysList( finalPolys ); + toFill[i].m_zone->SetFillSegments( segFill ); + toFill[i].m_zone->SetIsFilled( true ); if( m_progressReporter ) { @@ -130,7 +165,7 @@ void ZONE_FILLER::Fill( std::vector aZones ) poly.DeletePolygon( idx ); } - zone.m_zone->AddFilledPolysList( poly ); + zone.m_zone->SetFilledPolysList( poly ); } if( m_progressReporter ) @@ -150,6 +185,7 @@ void ZONE_FILLER::Fill( std::vector aZones ) { m_progressReporter->AdvanceProgress(); } + toFill[i].m_zone->CacheTriangulation(); } @@ -164,10 +200,700 @@ void ZONE_FILLER::Fill( std::vector aZones ) if( m_commit ) { m_commit->Push( _( "Fill Zone(s)" ), false ); - } else { + } + else + { for( int i = 0; i < toFill.size(); i++ ) { connectivity->Update( toFill[i].m_zone ); } } } + + +void ZONE_FILLER::buildZoneFeatureHoleList( const ZONE_CONTAINER* aZone, + SHAPE_POLY_SET& aFeatures ) const +{ + int segsPerCircle; + double correctionFactor; + + // Set the number of segments in arc approximations + if( aZone->GetArcSegmentCount() == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) + segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; + else + segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; + + /* calculates the coeff to compensate radius reduction of holes clearance + * due to the segment approx. + * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) + * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) + */ + correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); + + aFeatures.RemoveAllContours(); + + int outline_half_thickness = aZone->GetMinThickness() / 2; + + // When removing holes, the holes must be expanded by outline_half_thickness + // to take in account the thickness of the zone outlines + int zone_clearance = aZone->GetClearance() + outline_half_thickness; + + // When holes are created by non copper items (edge cut items), use only + // the m_ZoneClearance parameter (zone clearance with no netclass clearance) + int zone_to_edgecut_clearance = aZone->GetZoneClearance() + outline_half_thickness; + + /* store holes (i.e. tracks and pads areas as polygons outlines) + * in a polygon list + */ + + /* items ouside the zone bounding box are skipped + * the bounding box is the zone bounding box + the biggest clearance found in Netclass list + */ + EDA_RECT item_boundingbox; + EDA_RECT zone_boundingbox = aZone->GetBoundingBox(); + int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue(); + biggest_clearance = std::max( biggest_clearance, zone_clearance ); + zone_boundingbox.Inflate( biggest_clearance ); + + /* + * First : Add pads. Note: pads having the same net as zone are left in zone. + * Thermal shapes will be created later if necessary + */ + + /* Use a dummy pad to calculate hole clearance when a pad is not on all copper layers + * and this pad has a hole + * This dummy pad has the size and shape of the hole + * Therefore, this dummy pad is a circle or an oval. + * A pad must have a parent because some functions expect a non null parent + * to find the parent board, and some other data + */ + MODULE dummymodule( m_board ); // Creates a dummy parent + D_PAD dummypad( &dummymodule ); + + for( MODULE* module = m_board->m_Modules; module; module = module->Next() ) + { + D_PAD* nextpad; + + for( D_PAD* pad = module->PadsList(); pad != NULL; pad = nextpad ) + { + nextpad = pad->Next(); // pad pointer can be modified by next code, so + // calculate the next pad here + + if( !pad->IsOnLayer( aZone->GetLayer() ) ) + { + /* Test for pads that are on top or bottom only and have a hole. + * There are curious pads but they can be used for some components that are + * inside the board (in fact inside the hole. Some photo diodes and Leds are + * like this) + */ + if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 ) + continue; + + // Use a dummy pad to calculate a hole shape that have the same dimension as + // the pad hole + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetOrientation( pad->GetOrientation() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetPosition( pad->GetPosition() ); + + pad = &dummypad; + } + + // Note: netcode <=0 means not connected item + if( ( pad->GetNetCode() != aZone->GetNetCode() ) || ( pad->GetNetCode() <= 0 ) ) + { + int item_clearance = pad->GetClearance() + outline_half_thickness; + item_boundingbox = pad->GetBoundingBox(); + item_boundingbox.Inflate( item_clearance ); + + if( item_boundingbox.Intersects( zone_boundingbox ) ) + { + int clearance = std::max( zone_clearance, item_clearance ); + + // PAD_SHAPE_CUSTOM can have a specific keepout, to avoid to break the shape + if( pad->GetShape() == PAD_SHAPE_CUSTOM + && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + { + // the pad shape in zone can be its convex hull or + // the shape itself + SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() ); + outline.Inflate( KiROUND( clearance * correctionFactor ), segsPerCircle ); + pad->CustomShapeAsPolygonToBoardPosition( &outline, + pad->GetPosition(), pad->GetOrientation() ); + + if( pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + { + std::vector convex_hull; + BuildConvexHull( convex_hull, outline ); + + aFeatures.NewOutline(); + + for( unsigned ii = 0; ii < convex_hull.size(); ++ii ) + aFeatures.Append( convex_hull[ii] ); + } + else + aFeatures.Append( outline ); + } + else + pad->TransformShapeWithClearanceToPolygon( aFeatures, + clearance, + segsPerCircle, + correctionFactor ); + } + + continue; + } + + // Pads are removed from zone if the setup is PAD_ZONE_CONN_NONE + // or if they have a custom shape, because a thermal relief will break + // the shape + if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE + || pad->GetShape() == PAD_SHAPE_CUSTOM ) + { + int gap = zone_clearance; + int thermalGap = aZone->GetThermalReliefGap( pad ); + gap = std::max( gap, thermalGap ); + item_boundingbox = pad->GetBoundingBox(); + item_boundingbox.Inflate( gap ); + + if( item_boundingbox.Intersects( zone_boundingbox ) ) + { + // PAD_SHAPE_CUSTOM has a specific keepout, to avoid to break the shape + // the pad shape in zone can be its convex hull or the shape itself + if( pad->GetShape() == PAD_SHAPE_CUSTOM + && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + { + // the pad shape in zone can be its convex hull or + // the shape itself + SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() ); + outline.Inflate( KiROUND( gap * correctionFactor ), segsPerCircle ); + pad->CustomShapeAsPolygonToBoardPosition( &outline, + pad->GetPosition(), pad->GetOrientation() ); + + std::vector convex_hull; + BuildConvexHull( convex_hull, outline ); + + aFeatures.NewOutline(); + + for( unsigned ii = 0; ii < convex_hull.size(); ++ii ) + aFeatures.Append( convex_hull[ii] ); + } + else + pad->TransformShapeWithClearanceToPolygon( aFeatures, + gap, segsPerCircle, correctionFactor ); + } + } + } + } + + /* Add holes (i.e. tracks and vias areas as polygons outlines) + * in cornerBufferPolysToSubstract + */ + for( auto track : m_board->Tracks() ) + { + if( !track->IsOnLayer( aZone->GetLayer() ) ) + continue; + + if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) ) + continue; + + int item_clearance = track->GetClearance() + outline_half_thickness; + item_boundingbox = track->GetBoundingBox(); + + if( item_boundingbox.Intersects( zone_boundingbox ) ) + { + int clearance = std::max( zone_clearance, item_clearance ); + track->TransformShapeWithClearanceToPolygon( aFeatures, + clearance, + segsPerCircle, + correctionFactor ); + } + } + + /* Add module edge items that are on copper layers + * Pcbnew allows these items to be on copper layers in microwave applictions + * This is a bad thing, but must be handled here, until a better way is found + */ + for( auto module : m_board->Modules() ) + { + for( auto item : module->GraphicalItems() ) + { + if( !item->IsOnLayer( aZone->GetLayer() ) && !item->IsOnLayer( Edge_Cuts ) ) + continue; + + if( item->Type() != PCB_MODULE_EDGE_T ) + continue; + + item_boundingbox = item->GetBoundingBox(); + + if( item_boundingbox.Intersects( zone_boundingbox ) ) + { + int zclearance = zone_clearance; + + if( item->IsOnLayer( Edge_Cuts ) ) + // use only the m_ZoneClearance, not the clearance using + // the netclass value, because we do not have a copper item + zclearance = zone_to_edgecut_clearance; + + ( (EDGE_MODULE*) item )->TransformShapeWithClearanceToPolygon( + aFeatures, zclearance, segsPerCircle, correctionFactor ); + } + } + } + + // Add graphic items (copper texts) and board edges + // Currently copper texts have no net, so only the zone_clearance + // is used. + for( auto item : m_board->Drawings() ) + { + if( item->GetLayer() != aZone->GetLayer() && item->GetLayer() != Edge_Cuts ) + continue; + + int zclearance = zone_clearance; + + if( item->GetLayer() == Edge_Cuts ) + // use only the m_ZoneClearance, not the clearance using + // the netclass value, because we do not have a copper item + zclearance = zone_to_edgecut_clearance; + + switch( item->Type() ) + { + case PCB_LINE_T: + ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( + aFeatures, + zclearance, segsPerCircle, correctionFactor ); + break; + + case PCB_TEXT_T: + ( (TEXTE_PCB*) item )->TransformBoundingBoxWithClearanceToPolygon( + aFeatures, zclearance ); + break; + + default: + break; + } + } + + // Add zones outlines having an higher priority and keepout + for( int ii = 0; ii < m_board->GetAreaCount(); ii++ ) + { + ZONE_CONTAINER* zone = m_board->GetArea( ii ); + + // If the zones share no common layers + if( !aZone->CommonLayerExists( zone->GetLayerSet() ) ) + continue; + + if( !zone->GetIsKeepout() && zone->GetPriority() <= aZone->GetPriority() ) + continue; + + if( zone->GetIsKeepout() && !zone->GetDoNotAllowCopperPour() ) + continue; + + // A highter priority zone or keepout area is found: remove this area + item_boundingbox = zone->GetBoundingBox(); + + if( !item_boundingbox.Intersects( zone_boundingbox ) ) + continue; + + // Add the zone outline area. + // However if the zone has the same net as the current zone, + // do not add any clearance. + // the zone will be connected to the current zone, but filled areas + // will use different parameters (clearance, thermal shapes ) + bool same_net = aZone->GetNetCode() == zone->GetNetCode(); + bool use_net_clearance = true; + int min_clearance = zone_clearance; + + // Do not forget to make room to draw the thick outlines + // of the hole created by the area of the zone to remove + int holeclearance = zone->GetClearance() + outline_half_thickness; + + // The final clearance is obviously the max value of each zone clearance + min_clearance = std::max( min_clearance, holeclearance ); + + if( zone->GetIsKeepout() || same_net ) + { + // Just take in account the fact the outline has a thickness, so + // the actual area to substract is inflated to take in account this fact + min_clearance = outline_half_thickness; + use_net_clearance = false; + } + + zone->TransformOutlinesShapeWithClearanceToPolygon( + aFeatures, min_clearance, use_net_clearance ); + } + + // Remove thermal symbols + for( auto module : m_board->Modules() ) + { + for( auto pad : module->Pads() ) + { + // Rejects non-standard pads with tht-only thermal reliefs + if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL + && pad->GetAttribute() != PAD_ATTRIB_STANDARD ) + continue; + + if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL + && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL ) + continue; + + if( !pad->IsOnLayer( aZone->GetLayer() ) ) + continue; + + if( pad->GetNetCode() != aZone->GetNetCode() ) + continue; + + item_boundingbox = pad->GetBoundingBox(); + int thermalGap = aZone->GetThermalReliefGap( pad ); + item_boundingbox.Inflate( thermalGap, thermalGap ); + + if( item_boundingbox.Intersects( zone_boundingbox ) ) + { + CreateThermalReliefPadPolygon( aFeatures, + *pad, thermalGap, + aZone->GetThermalReliefCopperBridge( pad ), + aZone->GetMinThickness(), + segsPerCircle, + correctionFactor, s_thermalRot ); + } + } + } +} + +/** + * Function ComputeRawFilledAreas + * Supports a min thickness area constraint. + * Add non copper areas polygons (pads and tracks with clearance) + * to the filled copper area found + * in BuildFilledPolysListData after calculating filled areas in a zone + * Non filled copper areas are pads and track and their clearance areas + * The filled copper area must be computed just before. + * BuildFilledPolysListData() call this function just after creating the + * filled copper area polygon (without clearance areas) + * to do that this function: + * 1 - Creates the main outline (zone outline) using a correction to shrink the resulting area + * with m_ZoneMinThickness/2 value. + * The result is areas with a margin of m_ZoneMinThickness/2 + * When drawing outline with segments having a thickness of m_ZoneMinThickness, the + * outlines will match exactly the initial outlines + * 3 - Add all non filled areas (pads, tracks) in group B with a clearance of m_Clearance + + * m_ZoneMinThickness/2 + * in a buffer + * - If Thermal shapes are wanted, add non filled area, in order to create these thermal shapes + * 4 - calculates the polygon A - B + * 5 - put resulting list of polygons (filled areas) in m_FilledPolysList + * This zone contains pads with the same net. + * 6 - Remove insulated copper islands + * 7 - If Thermal shapes are wanted, remove unconnected stubs in thermal shapes: + * creates a buffer of polygons corresponding to stubs to remove + * sub them to the filled areas. + * Remove new insulated copper islands + */ +void ZONE_FILLER::computeRawFilledAreas( const ZONE_CONTAINER* aZone, + const SHAPE_POLY_SET& aSmoothedOutline, + SHAPE_POLY_SET& aRawPolys, + SHAPE_POLY_SET& aFinalPolys ) const +{ + int segsPerCircle; + double correctionFactor; + int outline_half_thickness = aZone->GetMinThickness() / 2; + + std::unique_ptr dumper( new SHAPE_FILE_IO( + s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) ); + + // Set the number of segments in arc approximations + if( aZone->GetArcSegmentCount() == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) + segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; + else + segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; + + /* calculates the coeff to compensate radius reduction of holes clearance + * due to the segment approx. + * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) + * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) + */ + correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); + + if( s_DumpZonesWhenFilling ) + dumper->BeginGroup( "clipper-zone" ); + + SHAPE_POLY_SET solidAreas = aSmoothedOutline; + + solidAreas.Inflate( -outline_half_thickness, segsPerCircle ); + solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST ); + + SHAPE_POLY_SET holes; + + if( s_DumpZonesWhenFilling ) + dumper->Write( &solidAreas, "solid-areas" ); + + buildZoneFeatureHoleList( aZone, holes ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &holes, "feature-holes" ); + + holes.Simplify( SHAPE_POLY_SET::PM_FAST ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &holes, "feature-holes-postsimplify" ); + + // Generate the filled areas (currently, without thermal shapes, which will + // be created later). + // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons + // needed by Gerber files and Fracture() + solidAreas.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &solidAreas, "solid-areas-minus-holes" ); + + SHAPE_POLY_SET areas_fractured = solidAreas; + areas_fractured.Fracture( SHAPE_POLY_SET::PM_FAST ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &areas_fractured, "areas_fractured" ); + + aFinalPolys = areas_fractured; + + SHAPE_POLY_SET thermalHoles; + + // Test thermal stubs connections and add polygons to remove unconnected stubs. + // (this is a refinement for thermal relief shapes) + if( aZone->GetNetCode() > 0 ) + BuildUnconnectedThermalStubsPolygonList( thermalHoles, m_board, aZone, + correctionFactor, s_thermalRot ); + + // remove copper areas corresponding to not connected stubs + if( !thermalHoles.IsEmpty() ) + { + thermalHoles.Simplify( SHAPE_POLY_SET::PM_FAST ); + // Remove unconnected stubs. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to + // generate strictly simple polygons + // needed by Gerber files and Fracture() + solidAreas.BooleanSubtract( thermalHoles, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &thermalHoles, "thermal-holes" ); + + // put these areas in m_FilledPolysList + SHAPE_POLY_SET th_fractured = solidAreas; + th_fractured.Fracture( SHAPE_POLY_SET::PM_FAST ); + + if( s_DumpZonesWhenFilling ) + dumper->Write( &th_fractured, "th_fractured" ); + + aFinalPolys = th_fractured; + } + + aRawPolys = aFinalPolys; + + if( s_DumpZonesWhenFilling ) + dumper->EndGroup(); +} + +/* Build the filled solid areas data from real outlines (stored in m_Poly) + * The solid areas can be more than one on copper layers, and do not have holes + ( holes are linked by overlapping segments to the main outline) + * aPcb: the current board (can be NULL for non copper zones) + * aCornerBuffer: A reference to a buffer to store polygon corners, or NULL + * if aCornerBuffer == NULL: + * - m_FilledPolysList is used to store solid areas polygons. + * - on copper layers, tracks and other items shapes of other nets are + * removed from solid areas + * if not null: + * Only the zone outline (with holes, if any) are stored in aCornerBuffer + * with holes linked. Therefore only one polygon is created + * This function calls ComputeRawFilledAreas() + * to add holes for pads and tracks and other items not in net. + */ +bool ZONE_FILLER::fillSingleZone( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys, ZONE_SEGMENT_FILL& aSegmentFill ) const +{ + SHAPE_POLY_SET smoothedPoly; + + /* 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 ) ) + return false; + + if( aZone->IsOnCopperLayer() ) + { + computeRawFilledAreas( aZone, smoothedPoly, aRawPolys, aFinalPolys ); + + if( aZone->GetFillMode() == ZFM_SEGMENTS ) // if fill mode uses segments, create them: + { + if( !fillZoneWithSegments( aZone, aFinalPolys, aSegmentFill) ) + return false; + } + } + else + { + aRawPolys = smoothedPoly; + aFinalPolys = smoothedPoly; + aFinalPolys.Inflate( -aZone->GetMinThickness() / 2, 16 ); + aFinalPolys.Fracture( SHAPE_POLY_SET::PM_FAST ); + } + + return true; +} + +bool ZONE_FILLER::fillZoneWithSegments( const ZONE_CONTAINER* aZone, const SHAPE_POLY_SET& aFilledPolys, ZONE_SEGMENT_FILL& aFillSegs ) const +{ + bool success = true; + // segments are on something like a grid. Give it a minimal size + // to avoid too many segments, and use the m_ZoneMinThickness when (this is usually the case) + // the size is > mingrid_size. + // This is not perfect, but the actual purpose of this code + // is to allow filling zones on a grid, with grid size > m_ZoneMinThickness, + // in order to have really a grid. + // + // Using a user selectable grid size is for future Kicad versions. + // For now the area is fully filled. + int mingrid_size = Millimeter2iu( 0.05 ); + int grid_size = std::max( mingrid_size, aZone->GetMinThickness() ); + // Make segments slightly overlapping to ensure a good full filling + grid_size -= grid_size/20; + + // Creates the horizontal segments + for ( int index = 0; index < aFilledPolys.OutlineCount(); index++ ) + { + const SHAPE_LINE_CHAIN& outline0 = aFilledPolys.COutline( index ); + success = fillPolygonWithHorizontalSegments( outline0, aFillSegs, grid_size ); + + if( !success ) + break; + + // Creates the vertical segments. Because the filling algo creates horizontal segments, + // to reuse the fillPolygonWithHorizontalSegments function, we rotate the polygons to fill + // then fill them, then inverse rotate the result + SHAPE_LINE_CHAIN outline90; + outline90.Append( outline0 ); + + // Rotate 90 degrees the outline: + for( int ii = 0; ii < outline90.PointCount(); ii++ ) + { + VECTOR2I& point = outline90.Point( ii ); + std::swap( point.x, point.y ); + point.y = -point.y; + } + + int first_point = aFillSegs.size(); + success = fillPolygonWithHorizontalSegments( outline90, aFillSegs, grid_size ); + + if( !success ) + break; + + // Rotate -90 degrees the segments: + for( unsigned ii = first_point; ii < aFillSegs.size(); ii++ ) + { + SEG& segm = aFillSegs[ii]; + std::swap( segm.A.x, segm.A.y ); + std::swap( segm.B.x, segm.B.y ); + segm.A.x = - segm.A.x; + segm.B.x = - segm.B.x; + } + } + + return success; +} + +/** Helper function fillPolygonWithHorizontalSegments + * fills a polygon with horizontal segments. + * It can be used for any angle, if the zone outline to fill is rotated by this angle + * and the result is rotated by -angle + * @param aPolygon = a SHAPE_LINE_CHAIN polygon to fill + * @param aFillSegmList = a std::vector\ which will be populated by filling segments + * @param aStep = the horizontal grid size + */ +bool ZONE_FILLER::fillPolygonWithHorizontalSegments( const SHAPE_LINE_CHAIN& aPolygon, + ZONE_SEGMENT_FILL& aFillSegmList, int aStep ) const +{ + std::vector x_coordinates; + bool success = true; + + // Creates the horizontal segments + const SHAPE_LINE_CHAIN& outline = aPolygon; + const BOX2I& rect = outline.BBox(); + + // Calculate the y limits of the zone + for( int refy = rect.GetY(), endy = rect.GetBottom(); refy < endy; refy += aStep ) + { + // find all intersection points of an infinite line with polyline sides + x_coordinates.clear(); + + for( int v = 0; v < outline.PointCount(); v++ ) + { + + int seg_startX = outline.CPoint( v ).x; + int seg_startY = outline.CPoint( v ).y; + int seg_endX = outline.CPoint( v + 1 ).x; + int seg_endY = outline.CPoint( v + 1 ).y; + + /* Trivial cases: skip if ref above or below the segment to test */ + if( ( seg_startY > refy ) && ( seg_endY > refy ) ) + continue; + + // segment below ref point, or its Y end pos on Y coordinate ref point: skip + if( ( seg_startY <= refy ) && (seg_endY <= refy ) ) + continue; + + /* at this point refy is between seg_startY and seg_endY + * see if an horizontal line at Y = refy is intersecting this segment + */ + // calculate the x position of the intersection of this segment and the + // infinite line this is more easier if we move the X,Y axis origin to + // the segment start point: + + seg_endX -= seg_startX; + seg_endY -= seg_startY; + double newrefy = (double) ( refy - seg_startY ); + double intersec_x; + + if ( seg_endY == 0 ) // horizontal segment on the same line: skip + continue; + + // Now calculate the x intersection coordinate of the horizontal line at + // y = newrefy and the segment from (0,0) to (seg_endX,seg_endY) with the + // horizontal line at the new refy position the line slope is: + // slope = seg_endY/seg_endX; and inv_slope = seg_endX/seg_endY + // and the x pos relative to the new origin is: + // intersec_x = refy/slope = refy * inv_slope + // Note: because horizontal segments are already tested and skipped, slope + // exists (seg_end_y not O) + double inv_slope = (double) seg_endX / seg_endY; + intersec_x = newrefy * inv_slope; + x_coordinates.push_back( (int) intersec_x + seg_startX ); + } + + // A line scan is finished: build list of segments + + // Sort intersection points by increasing x value: + // So 2 consecutive points are the ends of a segment + std::sort( x_coordinates.begin(), x_coordinates.end() ); + + // An even number of coordinates is expected, because a segment has 2 ends. + // An if this algorithm always works, it must always find an even count. + if( ( x_coordinates.size() & 1 ) != 0 ) + { + success = false; + break; + } + + // Create segments having the same Y coordinate + int iimax = x_coordinates.size() - 1; + + for( int ii = 0; ii < iimax; ii += 2 ) + { + VECTOR2I seg_start, seg_end; + seg_start.x = x_coordinates[ii]; + seg_start.y = refy; + seg_end.x = x_coordinates[ii + 1]; + seg_end.y = refy; + SEG segment( seg_start, seg_end ); + aFillSegmList.push_back( segment ); + } + } // End examine segments in one area + + return success; +} diff --git a/pcbnew/zone_filler.h b/pcbnew/zone_filler.h index ade9d23a1e..97bae72223 100644 --- a/pcbnew/zone_filler.h +++ b/pcbnew/zone_filler.h @@ -27,11 +27,13 @@ #define __ZONE_FILLER_H #include +#include class PROGRESS_REPORTER; class BOARD; class COMMIT; -class ZONE_CONTAINER; +class SHAPE_POLY_SET; +class SHAPE_LINE_CHAIN; class ZONE_FILLER { @@ -44,9 +46,70 @@ public: void Unfill( std::vector aZones ); private: + + void buildZoneFeatureHoleList( const ZONE_CONTAINER* aZone, + SHAPE_POLY_SET& aFeatures ) const; + + /** + * Function computeRawFilledAreas + * Add non copper areas polygons (pads and tracks with clearance) + * to a filled copper area + * used in BuildFilledSolidAreasPolygons when calculating filled areas in a zone + * Non copper areas are pads and track and their clearance area + * The filled copper area must be computed before + * BuildFilledSolidAreasPolygons() call this function just after creating the + * filled copper area polygon (without clearance areas + * @param aPcb: the current board + * _NG version uses SHAPE_POLY_SET instead of Boost.Polygon + */ + void computeRawFilledAreas( const ZONE_CONTAINER* aZone, + const SHAPE_POLY_SET& aSmoothedOutline, + SHAPE_POLY_SET& aRawPolys, + SHAPE_POLY_SET& aFinalPolys ) const; + + bool fillPolygonWithHorizontalSegments( const SHAPE_LINE_CHAIN& aPolygon, + ZONE_SEGMENT_FILL& aFillSegmList, int aStep ) const; + + /** + * Function fillZoneWithSegments + * Fill sub areas in a zone with segments with m_ZoneMinThickness width + * A scan is made line per line, on the whole filled areas, with a step of m_ZoneMinThickness. + * all intersecting points with the horizontal infinite line and polygons to fill are calculated + * a list of SEGZONE items is built, line per line + * @return true if success, false on error + */ + bool fillZoneWithSegments( const ZONE_CONTAINER* aZone, + const SHAPE_POLY_SET& aFilledPolys, + ZONE_SEGMENT_FILL& aFillSegs ) const; + + /** + * Build the filled solid areas polygons from zone outlines (stored in m_Poly) + * The solid areas can be more than one on copper layers, and do not have holes + * ( holes are linked by overlapping segments to the main outline) + * in order to have drawable (and plottable) filled polygons. + * @return true if OK, false if the solid polygons cannot be built + * @param aPcb: the current board (can be NULL for non copper zones) + * @param aOutlineBuffer: A reference to a SHAPE_POLY_SET buffer to store polygons, or NULL. + * if NULL (default): + * - m_FilledPolysList is used to store solid areas polygons. + * - on copper layers, tracks and other items shapes of other nets are + * removed from solid areas + * if not null: + * Only the zone outline (with holes, if any) is stored in aOutlineBuffer + * with holes linked. Therefore only one polygon is created + * + * When aOutlineBuffer is not null, his function calls + * AddClearanceAreasPolygonsToPolysList() to add holes for pads and tracks + * and other items not in net. + */ + bool fillSingleZone( const ZONE_CONTAINER* aZone, + SHAPE_POLY_SET& aRawPolys, + SHAPE_POLY_SET& aFinalPolys, + ZONE_SEGMENT_FILL& aSegmentFill ) const; + + BOARD* m_board; COMMIT* m_commit; PROGRESS_REPORTER* m_progressReporter; - BOARD* m_board; }; #endif diff --git a/pcbnew/zone_filling_algorithm.cpp b/pcbnew/zone_filling_algorithm.cpp index ecb65b0361..e69de29bb2 100644 --- a/pcbnew/zone_filling_algorithm.cpp +++ b/pcbnew/zone_filling_algorithm.cpp @@ -1,878 +0,0 @@ -/** - * @file zone_filling_algorithm.cpp: - * Algorithms used to fill a zone defined by a polygon and a filling starting point. - */ -/* - * This program source code file is part of KiCad, a free EDA CAD application. - * - * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, you may find one here: - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * or you may search the http://www.gnu.org website for the version 2 license, - * or you may write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - - -#include // sort - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - - -/* Functions to convert some board items to polygons - * (pads, tracks ..) - * This is used to calculate filled areas in copper zones. - * Filled areas are areas remainder of the full zone area after removed all polygons - * calculated from these items shapes and the clearance area - * - * Important note: - * Because filled areas must have a minimum thickness to match with Design rule, they are - * draw in 2 step: - * 1 - filled polygons are drawn - * 2 - polygon outlines are drawn with a "minimum thickness width" ( or with a minimum - * thickness pen ) - * So outlines of filled polygons are calculated with the constraint they match with clearance, - * taking in account outlines have thickness - * This ensures: - * - areas meet the minimum thickness requirement. - * - shapes are smoothed. - */ - -// Polygon calculations can use fast mode or force strickly simple polygons after calculations -// Forcing strickly simple polygons is time consuming, and we have not see issues in fast mode -// so we use fast mode when possible (intermediate calculations) -// (choice is SHAPE_POLY_SET::PM_STRICTLY_SIMPLE or SHAPE_POLY_SET::PM_FAST) -#define POLY_CALC_MODE SHAPE_POLY_SET::PM_FAST - -/* DEBUG OPTION: - * To emit zone data to a file when filling zones for the debugging purposes, - * set this 'true' and build. - */ -static const bool s_DumpZonesWhenFilling = false; - -extern void BuildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer, - BOARD* aPcb, ZONE_CONTAINER* aZone, - double aArcCorrection, - double aRoundPadThermalRotation); - - -extern void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer, - D_PAD& aPad, - int aThermalGap, - int aCopperThickness, - int aMinThicknessValue, - int aCircleToSegmentsCount, - double aCorrectionFactor, - double aThermalRot ); - -// Local Variables: -static double s_thermalRot = 450; // angle of stubs in thermal reliefs for round pads - - -/* Build the filled solid areas data from real outlines (stored in m_Poly) - * The solid areas can be more than one on copper layers, and do not have holes - ( holes are linked by overlapping segments to the main outline) - * aPcb: the current board (can be NULL for non copper zones) - * aCornerBuffer: A reference to a buffer to store polygon corners, or NULL - * if aCornerBuffer == NULL: - * - m_FilledPolysList is used to store solid areas polygons. - * - on copper layers, tracks and other items shapes of other nets are - * removed from solid areas - * if not null: - * Only the zone outline (with holes, if any) are stored in aCornerBuffer - * with holes linked. Therefore only one polygon is created - * This function calls ComputeRawFilledAreas() - * to add holes for pads and tracks and other items not in net. - */ - -bool ZONE_CONTAINER::BuildFilledSolidAreasPolygons( BOARD* aPcb, SHAPE_POLY_SET* aOutlineBuffer ) -{ - /* 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( GetNumCorners() <= 2 ) // malformed zone. polygon calculations do not like it ... - return false; - - // Make a smoothed polygon out of the user-drawn polygon if required - if( m_smoothedPoly ) - { - delete m_smoothedPoly; - m_smoothedPoly = NULL; - } - - switch( m_cornerSmoothingType ) - { - case ZONE_SETTINGS::SMOOTHING_CHAMFER: - m_smoothedPoly = new SHAPE_POLY_SET(); - *m_smoothedPoly = m_Poly->Chamfer( m_cornerRadius ); - break; - - case ZONE_SETTINGS::SMOOTHING_FILLET: - m_smoothedPoly = new SHAPE_POLY_SET(); - *m_smoothedPoly = m_Poly->Fillet( m_cornerRadius, m_ArcToSegmentsCount ); - break; - - default: - // Acute angles between adjacent edges can create issues in calculations, - // in inflate/deflate outlines transforms, especially when the angle is very small. - // 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 - m_smoothedPoly = new SHAPE_POLY_SET(); - *m_smoothedPoly = m_Poly->Chamfer( Millimeter2iu( 0.0 ) ); - break; - } - - if( aOutlineBuffer ) - aOutlineBuffer->Append( *m_smoothedPoly ); - - /* For copper layers, we now must add holes in the Polygon list. - * holes are pads and tracks with their clearance area - * For non copper layers, just recalculate the m_FilledPolysList - * with m_ZoneMinThickness taken in account - */ - else - { - m_FilledPolysList.RemoveAllContours(); - - if( IsOnCopperLayer() ) - { - ComputeRawFilledAreas( aPcb ); - - if( m_FillMode ) // if fill mode uses segments, create them: - { - if( !FillZoneAreasWithSegments() ) - return false; - } - } - else - { - m_FillMode = 0; // Fill by segments is no more used in non copper layers - // force use solid polygons (usefull only for old boards) - m_FilledPolysList = *m_smoothedPoly; - - // The filled areas are deflated by -m_ZoneMinThickness / 2, because - // the outlines are drawn with a line thickness = m_ZoneMinThickness to - // give a good shape with the minimal thickness - m_FilledPolysList.Inflate( -m_ZoneMinThickness / 2, 16 ); - m_FilledPolysList.Fracture( SHAPE_POLY_SET::PM_FAST ); - } - - m_IsFilled = true; - } - - return true; -} - - -/** Helper function fillPolygonWithHorizontalSegments - * fills a polygon with horizontal segments. - * It can be used for any angle, if the zone outline to fill is rotated by this angle - * and the result is rotated by -angle - * @param aPolygon = a SHAPE_LINE_CHAIN polygon to fill - * @param aFillSegmList = a std::vector\ which will be populated by filling segments - * @param aStep = the horizontal grid size - */ -bool fillPolygonWithHorizontalSegments( const SHAPE_LINE_CHAIN& aPolygon, - std::vector & aFillSegmList, int aStep ); - -bool ZONE_CONTAINER::FillZoneAreasWithSegments() -{ - bool success = true; - // segments are on something like a grid. Give it a minimal size - // to avoid too many segments, and use the m_ZoneMinThickness when (this is usually the case) - // the size is > mingrid_size. - // This is not perfect, but the actual purpose of this code - // is to allow filling zones on a grid, with grid size > m_ZoneMinThickness, - // in order to have really a grid. - // - // Using a user selectable grid size is for future Kicad versions. - // For now the area is fully filled. - int mingrid_size = Millimeter2iu( 0.05 ); - int grid_size = std::max( mingrid_size, m_ZoneMinThickness ); - // Make segments slightly overlapping to ensure a good full filling - grid_size -= grid_size/20; - - // All filled areas are in m_FilledPolysList - // m_FillSegmList will contain the horizontal and vertical segments - // the segment width is m_ZoneMinThickness. - m_FillSegmList.clear(); - - // Creates the horizontal segments - for ( int index = 0; index < m_FilledPolysList.OutlineCount(); index++ ) - { - const SHAPE_LINE_CHAIN& outline0 = m_FilledPolysList.COutline( index ); - success = fillPolygonWithHorizontalSegments( outline0, m_FillSegmList, grid_size ); - - if( !success ) - break; - - // Creates the vertical segments. Because the filling algo creates horizontal segments, - // to reuse the fillPolygonWithHorizontalSegments function, we rotate the polygons to fill - // then fill them, then inverse rotate the result - SHAPE_LINE_CHAIN outline90; - outline90.Append( outline0 ); - - // Rotate 90 degrees the outline: - for( int ii = 0; ii < outline90.PointCount(); ii++ ) - { - VECTOR2I& point = outline90.Point( ii ); - std::swap( point.x, point.y ); - point.y = -point.y; - } - - int first_point = m_FillSegmList.size(); - success = fillPolygonWithHorizontalSegments( outline90, m_FillSegmList, grid_size ); - - if( !success ) - break; - - // Rotate -90 degrees the segments: - for( unsigned ii = first_point; ii < m_FillSegmList.size(); ii++ ) - { - SEGMENT& segm = m_FillSegmList[ii]; - std::swap( segm.m_Start.x, segm.m_Start.y ); - std::swap( segm.m_End.x, segm.m_End.y ); - segm.m_Start.x = - segm.m_Start.x; - segm.m_End.x = - segm.m_End.x; - } - } - - if( success ) - m_IsFilled = true; - else - m_FillSegmList.clear(); - - return success; -} - - -bool fillPolygonWithHorizontalSegments( const SHAPE_LINE_CHAIN& aPolygon, - std::vector & aFillSegmList, int aStep ) -{ - std::vector x_coordinates; - bool success = true; - - // Creates the horizontal segments - const SHAPE_LINE_CHAIN& outline = aPolygon; - const BOX2I& rect = outline.BBox(); - - // Calculate the y limits of the zone - for( int refy = rect.GetY(), endy = rect.GetBottom(); refy < endy; refy += aStep ) - { - // find all intersection points of an infinite line with polyline sides - x_coordinates.clear(); - - for( int v = 0; v < outline.PointCount(); v++ ) - { - - int seg_startX = outline.CPoint( v ).x; - int seg_startY = outline.CPoint( v ).y; - int seg_endX = outline.CPoint( v + 1 ).x; - int seg_endY = outline.CPoint( v + 1 ).y; - - /* Trivial cases: skip if ref above or below the segment to test */ - if( ( seg_startY > refy ) && ( seg_endY > refy ) ) - continue; - - // segment below ref point, or its Y end pos on Y coordinate ref point: skip - if( ( seg_startY <= refy ) && (seg_endY <= refy ) ) - continue; - - /* at this point refy is between seg_startY and seg_endY - * see if an horizontal line at Y = refy is intersecting this segment - */ - // calculate the x position of the intersection of this segment and the - // infinite line this is more easier if we move the X,Y axis origin to - // the segment start point: - - seg_endX -= seg_startX; - seg_endY -= seg_startY; - double newrefy = (double) ( refy - seg_startY ); - double intersec_x; - - if ( seg_endY == 0 ) // horizontal segment on the same line: skip - continue; - - // Now calculate the x intersection coordinate of the horizontal line at - // y = newrefy and the segment from (0,0) to (seg_endX,seg_endY) with the - // horizontal line at the new refy position the line slope is: - // slope = seg_endY/seg_endX; and inv_slope = seg_endX/seg_endY - // and the x pos relative to the new origin is: - // intersec_x = refy/slope = refy * inv_slope - // Note: because horizontal segments are already tested and skipped, slope - // exists (seg_end_y not O) - double inv_slope = (double) seg_endX / seg_endY; - intersec_x = newrefy * inv_slope; - x_coordinates.push_back( (int) intersec_x + seg_startX ); - } - - // A line scan is finished: build list of segments - - // Sort intersection points by increasing x value: - // So 2 consecutive points are the ends of a segment - std::sort( x_coordinates.begin(), x_coordinates.end() ); - - // An even number of coordinates is expected, because a segment has 2 ends. - // An if this algorithm always works, it must always find an even count. - if( ( x_coordinates.size() & 1 ) != 0 ) - { - success = false; - break; - } - - // Create segments having the same Y coordinate - int iimax = x_coordinates.size() - 1; - - for( int ii = 0; ii < iimax; ii += 2 ) - { - wxPoint seg_start, seg_end; - seg_start.x = x_coordinates[ii]; - seg_start.y = refy; - seg_end.x = x_coordinates[ii + 1]; - seg_end.y = refy; - SEGMENT segment( seg_start, seg_end ); - aFillSegmList.push_back( segment ); - } - } // End examine segments in one area - - return success; -} - -void ZONE_CONTAINER::buildFeatureHoleList( BOARD* aPcb, SHAPE_POLY_SET& aFeatures ) -{ - int segsPerCircle; - double correctionFactor; - - // Set the number of segments in arc approximations - if( m_ArcToSegmentsCount == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) - segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; - else - segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; - - /* calculates the coeff to compensate radius reduction of holes clearance - * due to the segment approx. - * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) - * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) - */ - correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); - - aFeatures.RemoveAllContours(); - - int outline_half_thickness = m_ZoneMinThickness / 2; - - // When removing holes, the holes must be expanded by outline_half_thickness - // to take in account the thickness of the zone outlines - int zone_clearance = GetClearance() + outline_half_thickness; - - // When holes are created by non copper items (edge cut items), use only - // the m_ZoneClearance parameter (zone clearance with no netclass clearance) - int zone_to_edgecut_clearance = GetZoneClearance() + outline_half_thickness; - - /* store holes (i.e. tracks and pads areas as polygons outlines) - * in a polygon list - */ - - /* items ouside the zone bounding box are skipped - * the bounding box is the zone bounding box + the biggest clearance found in Netclass list - */ - EDA_RECT item_boundingbox; - EDA_RECT zone_boundingbox = GetBoundingBox(); - int biggest_clearance = aPcb->GetDesignSettings().GetBiggestClearanceValue(); - biggest_clearance = std::max( biggest_clearance, zone_clearance ); - zone_boundingbox.Inflate( biggest_clearance ); - - /* - * First : Add pads. Note: pads having the same net as zone are left in zone. - * Thermal shapes will be created later if necessary - */ - - /* Use a dummy pad to calculate hole clearance when a pad is not on all copper layers - * and this pad has a hole - * This dummy pad has the size and shape of the hole - * Therefore, this dummy pad is a circle or an oval. - * A pad must have a parent because some functions expect a non null parent - * to find the parent board, and some other data - */ - MODULE dummymodule( aPcb ); // Creates a dummy parent - D_PAD dummypad( &dummymodule ); - - for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) - { - D_PAD* nextpad; - - for( D_PAD* pad = module->PadsList(); pad != NULL; pad = nextpad ) - { - nextpad = pad->Next(); // pad pointer can be modified by next code, so - // calculate the next pad here - - if( !pad->IsOnLayer( GetLayer() ) ) - { - /* Test for pads that are on top or bottom only and have a hole. - * There are curious pads but they can be used for some components that are - * inside the board (in fact inside the hole. Some photo diodes and Leds are - * like this) - */ - if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 ) - continue; - - // Use a dummy pad to calculate a hole shape that have the same dimension as - // the pad hole - dummypad.SetSize( pad->GetDrillSize() ); - dummypad.SetOrientation( pad->GetOrientation() ); - dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? - PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); - dummypad.SetPosition( pad->GetPosition() ); - - pad = &dummypad; - } - - // Note: netcode <=0 means not connected item - if( ( pad->GetNetCode() != GetNetCode() ) || ( pad->GetNetCode() <= 0 ) ) - { - int item_clearance = pad->GetClearance() + outline_half_thickness; - item_boundingbox = pad->GetBoundingBox(); - item_boundingbox.Inflate( item_clearance ); - - if( item_boundingbox.Intersects( zone_boundingbox ) ) - { - int clearance = std::max( zone_clearance, item_clearance ); - - // PAD_SHAPE_CUSTOM can have a specific keepout, to avoid to break the shape - if( pad->GetShape() == PAD_SHAPE_CUSTOM && - pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) - { - // the pad shape in zone can be its convex hull or - // the shape itself - SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() ); - outline.Inflate( KiROUND( clearance*correctionFactor) , segsPerCircle ); - pad->CustomShapeAsPolygonToBoardPosition( &outline, - pad->GetPosition(), pad->GetOrientation() ); - - if( pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) - { - std::vector convex_hull; - BuildConvexHull( convex_hull, outline ); - - aFeatures.NewOutline(); - for( unsigned ii = 0; ii < convex_hull.size(); ++ii ) - aFeatures.Append( convex_hull[ii] ); - } - else - aFeatures.Append( outline ); - } - else - pad->TransformShapeWithClearanceToPolygon( aFeatures, - clearance, - segsPerCircle, - correctionFactor ); - } - - continue; - } - - // Pads are removed from zone if the setup is PAD_ZONE_CONN_NONE - // or if they have a custom shape, because a thermal relief will break - // the shape - if( GetPadConnection( pad ) == PAD_ZONE_CONN_NONE || - pad->GetShape() == PAD_SHAPE_CUSTOM ) - { - int gap = zone_clearance; - int thermalGap = GetThermalReliefGap( pad ); - gap = std::max( gap, thermalGap ); - item_boundingbox = pad->GetBoundingBox(); - item_boundingbox.Inflate( gap ); - - if( item_boundingbox.Intersects( zone_boundingbox ) ) - { - // PAD_SHAPE_CUSTOM has a specific keepout, to avoid to break the shape - // the pad shape in zone can be its convex hull or the shape itself - if( pad->GetShape() == PAD_SHAPE_CUSTOM && - pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) - { - // the pad shape in zone can be its convex hull or - // the shape itself - SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() ); - outline.Inflate( KiROUND( gap*correctionFactor) , segsPerCircle ); - pad->CustomShapeAsPolygonToBoardPosition( &outline, - pad->GetPosition(), pad->GetOrientation() ); - - std::vector convex_hull; - BuildConvexHull( convex_hull, outline ); - - aFeatures.NewOutline(); - for( unsigned ii = 0; ii < convex_hull.size(); ++ii ) - aFeatures.Append( convex_hull[ii] ); - } - else - pad->TransformShapeWithClearanceToPolygon( aFeatures, - gap, segsPerCircle, correctionFactor ); - } - } - } - } - - /* Add holes (i.e. tracks and vias areas as polygons outlines) - * in cornerBufferPolysToSubstract - */ - for( TRACK* track = aPcb->m_Track; track; track = track->Next() ) - { - if( !track->IsOnLayer( GetLayer() ) ) - continue; - - if( track->GetNetCode() == GetNetCode() && (GetNetCode() != 0) ) - continue; - - int item_clearance = track->GetClearance() + outline_half_thickness; - item_boundingbox = track->GetBoundingBox(); - - if( item_boundingbox.Intersects( zone_boundingbox ) ) - { - int clearance = std::max( zone_clearance, item_clearance ); - track->TransformShapeWithClearanceToPolygon( aFeatures, - clearance, - segsPerCircle, - correctionFactor ); - } - } - - /* Add module edge items that are on copper layers - * Pcbnew allows these items to be on copper layers in microwave applictions - * This is a bad thing, but must be handled here, until a better way is found - */ - for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) - { - for( BOARD_ITEM* item = module->GraphicalItemsList(); item; item = item->Next() ) - { - if( !item->IsOnLayer( GetLayer() ) && !item->IsOnLayer( Edge_Cuts ) ) - continue; - - if( item->Type() != PCB_MODULE_EDGE_T ) - continue; - - item_boundingbox = item->GetBoundingBox(); - - if( item_boundingbox.Intersects( zone_boundingbox ) ) - { - int zclearance = zone_clearance; - - if( item->IsOnLayer( Edge_Cuts ) ) - // use only the m_ZoneClearance, not the clearance using - // the netclass value, because we do not have a copper item - zclearance = zone_to_edgecut_clearance; - - ( (EDGE_MODULE*) item )->TransformShapeWithClearanceToPolygon( - aFeatures, zclearance, segsPerCircle, correctionFactor ); - } - } - } - - // Add graphic items (copper texts) and board edges - // Currently copper texts have no net, so only the zone_clearance - // is used. - for( auto item : aPcb->Drawings() ) - { - if( item->GetLayer() != GetLayer() && item->GetLayer() != Edge_Cuts ) - continue; - - int zclearance = zone_clearance; - - if( item->GetLayer() == Edge_Cuts ) - // use only the m_ZoneClearance, not the clearance using - // the netclass value, because we do not have a copper item - zclearance = zone_to_edgecut_clearance; - - switch( item->Type() ) - { - case PCB_LINE_T: - ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( - aFeatures, - zclearance, segsPerCircle, correctionFactor ); - break; - - case PCB_TEXT_T: - ( (TEXTE_PCB*) item )->TransformBoundingBoxWithClearanceToPolygon( - aFeatures, zclearance ); - break; - - default: - break; - } - } - - // Add zones outlines having an higher priority and keepout - for( int ii = 0; ii < GetBoard()->GetAreaCount(); ii++ ) - { - ZONE_CONTAINER* zone = GetBoard()->GetArea( ii ); - - // If the zones share no common layers - if( !CommonLayerExists( zone->GetLayerSet() ) ) - continue; - - if( !zone->GetIsKeepout() && zone->GetPriority() <= GetPriority() ) - continue; - - if( zone->GetIsKeepout() && ! zone->GetDoNotAllowCopperPour() ) - continue; - - // A highter priority zone or keepout area is found: remove this area - item_boundingbox = zone->GetBoundingBox(); - - if( !item_boundingbox.Intersects( zone_boundingbox ) ) - continue; - - // Add the zone outline area. - // However if the zone has the same net as the current zone, - // do not add any clearance. - // the zone will be connected to the current zone, but filled areas - // will use different parameters (clearance, thermal shapes ) - bool same_net = GetNetCode() == zone->GetNetCode(); - bool use_net_clearance = true; - int min_clearance = zone_clearance; - - // Do not forget to make room to draw the thick outlines - // of the hole created by the area of the zone to remove - int holeclearance = zone->GetClearance() + outline_half_thickness; - - // The final clearance is obviously the max value of each zone clearance - min_clearance = std::max( min_clearance, holeclearance ); - - if( zone->GetIsKeepout() || same_net ) - { - // Just take in account the fact the outline has a thickness, so - // the actual area to substract is inflated to take in account this fact - min_clearance = outline_half_thickness; - use_net_clearance = false; - } - - zone->TransformOutlinesShapeWithClearanceToPolygon( - aFeatures, min_clearance, use_net_clearance ); - } - - // Remove thermal symbols - for( MODULE* module = aPcb->m_Modules; module; module = module->Next() ) - { - for( D_PAD* pad = module->PadsList(); pad != NULL; pad = pad->Next() ) - { - // Rejects non-standard pads with tht-only thermal reliefs - if( GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL - && pad->GetAttribute() != PAD_ATTRIB_STANDARD ) - continue; - - if( GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL - && GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL ) - continue; - - if( !pad->IsOnLayer( GetLayer() ) ) - continue; - - if( pad->GetNetCode() != GetNetCode() ) - continue; - - item_boundingbox = pad->GetBoundingBox(); - int thermalGap = GetThermalReliefGap( pad ); - item_boundingbox.Inflate( thermalGap, thermalGap ); - - if( item_boundingbox.Intersects( zone_boundingbox ) ) - { - CreateThermalReliefPadPolygon( aFeatures, - *pad, thermalGap, - GetThermalReliefCopperBridge( pad ), - m_ZoneMinThickness, - segsPerCircle, - correctionFactor, s_thermalRot ); - } - } - } - -} - -/** - * Function ComputeRawFilledAreas - * Supports a min thickness area constraint. - * Add non copper areas polygons (pads and tracks with clearance) - * to the filled copper area found - * in BuildFilledPolysListData after calculating filled areas in a zone - * Non filled copper areas are pads and track and their clearance areas - * The filled copper area must be computed just before. - * BuildFilledPolysListData() call this function just after creating the - * filled copper area polygon (without clearance areas) - * to do that this function: - * 1 - Creates the main outline (zone outline) using a correction to shrink the resulting area - * with m_ZoneMinThickness/2 value. - * The result is areas with a margin of m_ZoneMinThickness/2 - * When drawing outline with segments having a thickness of m_ZoneMinThickness, the - * outlines will match exactly the initial outlines - * 3 - Add all non filled areas (pads, tracks) in group B with a clearance of m_Clearance + - * m_ZoneMinThickness/2 - * in a buffer - * - If Thermal shapes are wanted, add non filled area, in order to create these thermal shapes - * 4 - calculates the polygon A - B - * 5 - put resulting list of polygons (filled areas) in m_FilledPolysList - * This zone contains pads with the same net. - * 6 - Remove insulated copper islands - * 7 - If Thermal shapes are wanted, remove unconnected stubs in thermal shapes: - * creates a buffer of polygons corresponding to stubs to remove - * sub them to the filled areas. - * Remove new insulated copper islands - */ - -void ZONE_CONTAINER::ComputeRawFilledAreas( BOARD* aPcb ) -{ - int segsPerCircle; - double correctionFactor; - int outline_half_thickness = m_ZoneMinThickness / 2; - - - std::unique_ptr dumper( new SHAPE_FILE_IO( - s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) ); - - // Set the number of segments in arc approximations - if( m_ArcToSegmentsCount == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ) - segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF; - else - segsPerCircle = ARC_APPROX_SEGMENTS_COUNT_LOW_DEF; - - /* calculates the coeff to compensate radius reduction of holes clearance - * due to the segment approx. - * For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) - * s_Correction is 1 /cos( PI/s_CircleToSegmentsCount ) - */ - correctionFactor = 1.0 / cos( M_PI / (double) segsPerCircle ); - - CPOLYGONS_LIST tmp; - - if(s_DumpZonesWhenFilling) - dumper->BeginGroup("clipper-zone"); - - SHAPE_POLY_SET solidAreas = *m_smoothedPoly; - - solidAreas.Inflate( -outline_half_thickness, segsPerCircle ); - solidAreas.Simplify( POLY_CALC_MODE ); - - SHAPE_POLY_SET holes; - - if(s_DumpZonesWhenFilling) - dumper->Write( &solidAreas, "solid-areas" ); - - tmp.RemoveAllContours(); - buildFeatureHoleList( aPcb, holes ); - - if(s_DumpZonesWhenFilling) - dumper->Write( &holes, "feature-holes" ); - - holes.Simplify( POLY_CALC_MODE ); - - if (s_DumpZonesWhenFilling) - dumper->Write( &holes, "feature-holes-postsimplify" ); - - // Generate the filled areas (currently, without thermal shapes, which will - // be created later). - // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons - // needed by Gerber files and Fracture() - solidAreas.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - - if (s_DumpZonesWhenFilling) - dumper->Write( &solidAreas, "solid-areas-minus-holes" ); - - SHAPE_POLY_SET areas_fractured = solidAreas; - areas_fractured.Fracture( POLY_CALC_MODE ); - - if (s_DumpZonesWhenFilling) - dumper->Write( &areas_fractured, "areas_fractured" ); - - m_FilledPolysList = areas_fractured; - - SHAPE_POLY_SET thermalHoles; - - // Test thermal stubs connections and add polygons to remove unconnected stubs. - // (this is a refinement for thermal relief shapes) - if( GetNetCode() > 0 ) - BuildUnconnectedThermalStubsPolygonList( thermalHoles, aPcb, this, - correctionFactor, s_thermalRot ); - - // remove copper areas corresponding to not connected stubs - if( !thermalHoles.IsEmpty() ) - { - thermalHoles.Simplify( POLY_CALC_MODE ); - // Remove unconnected stubs. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to - // generate strictly simple polygons - // needed by Gerber files and Fracture() - solidAreas.BooleanSubtract( thermalHoles, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - - if( s_DumpZonesWhenFilling ) - dumper->Write( &thermalHoles, "thermal-holes" ); - - // put these areas in m_FilledPolysList - SHAPE_POLY_SET th_fractured = solidAreas; - th_fractured.Fracture( POLY_CALC_MODE ); - - if( s_DumpZonesWhenFilling ) - dumper->Write ( &th_fractured, "th_fractured" ); - - m_FilledPolysList = th_fractured; - - } - - m_RawPolysList = m_FilledPolysList; - - if(s_DumpZonesWhenFilling) - dumper->EndGroup(); -} - -void ZONE_CONTAINER::RemoveInsulatedCopperIslands( BOARD* aPcb ) -{ - std::vector islands; - - auto connectivity = aPcb->GetConnectivity(); - - connectivity->FindIsolatedCopperIslands( this, islands ); - - std::sort( islands.begin(), islands.end(), std::greater() ); - - for( auto idx : islands ) - { - m_FilledPolysList.DeletePolygon( idx ); - } - - connectivity->Update( this ); -} diff --git a/pcbnew/zones_convert_to_polygons_aux_functions.cpp b/pcbnew/zones_convert_to_polygons_aux_functions.cpp index bddd1e766e..f2debb5795 100644 --- a/pcbnew/zones_convert_to_polygons_aux_functions.cpp +++ b/pcbnew/zones_convert_to_polygons_aux_functions.cpp @@ -47,11 +47,11 @@ * false to create the outline polygon. */ void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( - SHAPE_POLY_SET& aCornerBuffer, int aMinClearanceValue, bool aUseNetClearance ) + SHAPE_POLY_SET& aCornerBuffer, int aMinClearanceValue, bool aUseNetClearance ) const { // Creates the zone outline polygon (with holes if any) SHAPE_POLY_SET polybuffer; - BuildFilledSolidAreasPolygons( NULL, &polybuffer ); + BuildSmoothedPoly( polybuffer ); // add clearance to outline int clearance = aMinClearanceValue; @@ -86,8 +86,8 @@ void ZONE_CONTAINER::TransformOutlinesShapeWithClearanceToPolygon( */ void BuildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer, - BOARD* aPcb, - ZONE_CONTAINER* aZone, + const BOARD* aPcb, + const ZONE_CONTAINER* aZone, double aArcCorrection, double aRoundPadThermalRotation ) { diff --git a/pcbnew/zones_test_and_combine_areas.cpp b/pcbnew/zones_test_and_combine_areas.cpp index ba7face3f4..729f18142f 100644 --- a/pcbnew/zones_test_and_combine_areas.cpp +++ b/pcbnew/zones_test_and_combine_areas.cpp @@ -62,6 +62,10 @@ bool BOARD::OnAreaPolygonModified( PICKED_ITEMS_LIST* aModifiedZonesList, CombineAllAreasInNet( aModifiedZonesList, modified_area->GetNetCode(), true ); } + /* + + FIXME : do we really need this? + if( !IsCopperLayer( layer ) ) // Refill non copper zones on this layer { for( unsigned ia = 0; ia < m_ZoneDescriptorList.size(); ia++ ) @@ -69,6 +73,8 @@ bool BOARD::OnAreaPolygonModified( PICKED_ITEMS_LIST* aModifiedZonesList, m_ZoneDescriptorList[ia]->BuildFilledSolidAreasPolygons( this ); } + */ + // Test for bad areas: all zones must have more than 2 corners: // Note: should not happen, but just in case. for( unsigned ii = 0; ii < m_ZoneDescriptorList.size(); ) @@ -284,7 +290,9 @@ int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_E for( int ia = 0; ia < GetAreaCount(); ia++ ) { ZONE_CONTAINER* Area_Ref = GetArea( ia ); - SHAPE_POLY_SET* refSmoothedPoly = Area_Ref->GetSmoothedPoly(); + SHAPE_POLY_SET refSmoothedPoly; + + Area_Ref->BuildSmoothedPoly( refSmoothedPoly ); if( !Area_Ref->IsOnCopperLayer() ) continue; @@ -296,7 +304,9 @@ int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_E for( int ia2 = 0; ia2 < GetAreaCount(); ia2++ ) { ZONE_CONTAINER* area_to_test = GetArea( ia2 ); - SHAPE_POLY_SET* testSmoothedPoly = area_to_test->GetSmoothedPoly(); + SHAPE_POLY_SET testSmoothedPoly; + + area_to_test->BuildSmoothedPoly( testSmoothedPoly ); if( Area_Ref == area_to_test ) continue; @@ -330,11 +340,11 @@ int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_E zone2zoneClearance = 1; // test for some corners of Area_Ref inside area_to_test - for( auto iterator = refSmoothedPoly->IterateWithHoles(); iterator; iterator++ ) + for( auto iterator = refSmoothedPoly.IterateWithHoles(); iterator; iterator++ ) { VECTOR2I currentVertex = *iterator; - if( testSmoothedPoly->Contains( currentVertex ) ) + if( testSmoothedPoly.Contains( currentVertex ) ) { // COPPERAREA_COPPERAREA error: copper area ref corner inside copper area if( aCreate_Markers ) @@ -356,11 +366,11 @@ int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_E } // test for some corners of area_to_test inside Area_Ref - for( auto iterator = testSmoothedPoly->IterateWithHoles(); iterator; iterator++ ) + for( auto iterator = testSmoothedPoly.IterateWithHoles(); iterator; iterator++ ) { VECTOR2I currentVertex = *iterator; - if( refSmoothedPoly->Contains( currentVertex ) ) + if( refSmoothedPoly.Contains( currentVertex ) ) { // COPPERAREA_COPPERAREA error: copper area corner inside copper area ref if( aCreate_Markers ) @@ -383,13 +393,13 @@ int BOARD::Test_Drc_Areas_Outlines_To_Areas_Outlines( ZONE_CONTAINER* aArea_To_E // Iterate through all the segments of refSmoothedPoly - for( auto refIt = refSmoothedPoly->IterateSegmentsWithHoles(); refIt; refIt++ ) + for( auto refIt = refSmoothedPoly.IterateSegmentsWithHoles(); refIt; refIt++ ) { // Build ref segment SEG refSegment = *refIt; // Iterate through all the segments in testSmoothedPoly - for( auto testIt = testSmoothedPoly->IterateSegmentsWithHoles(); testIt; testIt++ ) + for( auto testIt = testSmoothedPoly.IterateSegmentsWithHoles(); testIt; testIt++ ) { // Build test segment SEG testSegment = *testIt;