/* 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) */ bool ZONE_FILLER::fillSingleZone( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys ) 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 ); } else { aRawPolys = smoothedPoly; aFinalPolys = smoothedPoly; aFinalPolys.Inflate( -aZone->GetMinThickness() / 2, 16 ); aFinalPolys.Fracture( SHAPE_POLY_SET::PM_FAST ); } return true; }
void D_PAD::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const { wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for pads." ); double angle = m_Orient; int dx = (m_Size.x / 2) + aClearanceValue; int dy = (m_Size.y / 2) + aClearanceValue; wxPoint padShapePos = ShapePos(); /* Note: for pad having a shape offset, * the pad position is NOT the shape position */ switch( GetShape() ) { case PAD_SHAPE_CIRCLE: { TransformCircleToPolygon( aCornerBuffer, padShapePos, dx, aError ); } break; case PAD_SHAPE_OVAL: // An oval pad has the same shape as a segment with rounded ends { int width; wxPoint shape_offset; if( dy > dx ) // Oval pad X/Y ratio for choosing translation axis { shape_offset.y = dy - dx; width = dx * 2; } else //if( dy <= dx ) { shape_offset.x = dy - dx; width = dy * 2; } RotatePoint( &shape_offset, angle ); wxPoint start = padShapePos - shape_offset; wxPoint end = padShapePos + shape_offset; TransformOvalClearanceToPolygon( aCornerBuffer, start, end, width, aError ); } break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: { wxPoint corners[4]; BuildPadPolygon( corners, wxSize( 0, 0 ), angle ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ii++ ) { corners[ii] += padShapePos; outline.Append( corners[ii].x, corners[ii].y ); } int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int rounding_radius = KiROUND( aClearanceValue * correction ); outline.Inflate( rounding_radius, numSegs ); aCornerBuffer.Append( outline ); } break; case PAD_SHAPE_CHAMFERED_RECT: case PAD_SHAPE_ROUNDRECT: { SHAPE_POLY_SET outline; int radius = GetRoundRectCornerRadius() + aClearanceValue; int numSegs = std::max( GetArcToSegmentCount( radius, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int clearance = KiROUND( aClearanceValue * correction ); int rounding_radius = GetRoundRectCornerRadius() + clearance; wxSize shapesize( m_Size ); shapesize.x += clearance * 2; shapesize.y += clearance * 2; bool doChamfer = GetShape() == PAD_SHAPE_CHAMFERED_RECT; TransformRoundChamferedRectToPolygon( outline, padShapePos, shapesize, angle, rounding_radius, doChamfer ? GetChamferRectRatio() : 0.0, doChamfer ? GetChamferPositions() : 0, aError ); aCornerBuffer.Append( outline ); } break; case PAD_SHAPE_CUSTOM: { int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int clearance = KiROUND( aClearanceValue * correction ); SHAPE_POLY_SET outline; // Will contain the corners in board coordinates outline.Append( m_customShapeAsPolygon ); CustomShapeAsPolygonToBoardPosition( &outline, GetPosition(), GetOrientation() ); outline.Simplify( SHAPE_POLY_SET::PM_FAST ); outline.Inflate( clearance, numSegs ); outline.Fracture( SHAPE_POLY_SET::PM_FAST ); aCornerBuffer.Append( outline ); } break; } }
/* 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. * Currently the algo is: * 1 - build all pad shapes as polygons with a size inflated by * mask clearance + (min width solder mask /2) * 2 - Merge shapes * 3 - deflate result by (min width solder mask /2) * 4 - oring result by all pad shapes as polygons with a size inflated by * mask clearance only (because deflate sometimes creates shape artifacts) * 5 - draw result as polygons * * TODO: * make this calculation only for shapes with clearance near than (min width solder mask) * (using DRC algo) * 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 ) { LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask; int inflate = aMinThickness/2; 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. itemplotter.PlotBoardGraphicItems(); for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() ) { if( layer != item->GetLayer() ) continue; switch( item->Type() ) { case PCB_MODULE_EDGE_T: 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 SHAPE_POLY_SET areas; // Contains shapes to plot SHAPE_POLY_SET initialPolys; // Contains exact shapes to plot /* calculates the coeff to compensate radius reduction of holes clearance * due to the segment approx ( 1 /cos( PI/circleToSegmentsCount ) */ int circleToSegmentsCount = 32; double correction = 1.0 / cos( M_PI / circleToSegmentsCount ); // Plot pads for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { // add shapes with exact size module->TransformPadsShapesWithClearanceToPolygon( layer, initialPolys, 0, circleToSegmentsCount, correction ); // add shapes inflated by aMinThickness/2 module->TransformPadsShapesWithClearanceToPolygon( layer, areas, inflate, circleToSegmentsCount, correction ); } // 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 int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin; int via_margin = via_clearance + inflate; for( TRACK* track = aBoard->m_Track; track; track = track->Next() ) { const VIA* via = dyn_cast<const VIA*>( track ); if( !via ) continue; // vias are plotted only if they are on the corresponding // external copper layer LSET via_set = via->GetLayerSet(); if( via_set[B_Cu] ) via_set.set( B_Mask ); if( via_set[F_Cu] ) via_set.set( F_Mask ); if( !( via_set & aLayerMask ).any() ) continue; via->TransformShapeWithClearanceToPolygon( areas, via_margin, circleToSegmentsCount, correction ); via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance, circleToSegmentsCount, correction ); } } // Add filled zone areas. #if 0 // Set to 1 if a solder mask margin must be applied to zones on solder mask int zone_margin = aBoard->GetDesignSettings().m_SolderMaskMargin; #else int zone_margin = 0; #endif for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = aBoard->GetArea( ii ); if( zone->GetLayer() != layer ) continue; zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate+zone_margin, false ); zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false ); } // 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.SetArcSegmentCount( 32 ); zone.SetMinThickness( 0 ); // trace polygons only zone.SetLayer ( layer ); areas.BooleanAdd( initialPolys ); areas.Inflate( -inflate, circleToSegmentsCount ); // 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 ); areas.Fracture(); zone.AddFilledPolysList( areas ); itemplotter.PlotFilledAreas( &zone ); }
/** * DXF polygon: doesn't fill it but at least it close the filled ones * DXF does not know thick outline. * It does not know thhick segments, therefore filled polygons with thick outline * are converted to inflated polygon by aWidth/2 */ void DXF_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList, FILL_T aFill, int aWidth) { if( aCornerList.size() <= 1 ) return; unsigned last = aCornerList.size() - 1; // Plot outlines with lines (thickness = 0) to define the polygon if( aWidth <= 0 ) { MoveTo( aCornerList[0] ); for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) LineTo( aCornerList[ii] ); // Close polygon if 'fill' requested if( aFill ) { if( aCornerList[last] != aCornerList[0] ) LineTo( aCornerList[0] ); } PenFinish(); return; } // if the polygon outline has thickness, and is not filled // (i.e. is a polyline) plot outlines with thick segments if( aWidth > 0 && !aFill ) { MoveTo( aCornerList[0] ); for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) ThickSegment( aCornerList[ii-1], aCornerList[ii], aWidth, FILLED ); return; } // The polygon outline has thickness, and is filled // Build and plot the polygon which contains the initial // polygon and its thick outline SHAPE_POLY_SET bufferOutline; SHAPE_POLY_SET bufferPolybase; const int circleToSegmentsCount = 16; bufferPolybase.NewOutline(); // enter outline as polygon: for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) { TransformRoundedEndsSegmentToPolygon( bufferOutline, aCornerList[ii-1], aCornerList[ii], circleToSegmentsCount, aWidth ); } // enter the initial polygon: for( unsigned ii = 0; ii < aCornerList.size(); ii++ ) { bufferPolybase.Append( aCornerList[ii] ); } // Merge polygons to build the polygon which contains the initial // polygon and its thick outline bufferPolybase.BooleanAdd( bufferOutline ); // create the outline which contains thick outline bufferPolybase.Fracture(); if( bufferPolybase.OutlineCount() < 1 ) // should not happen return; const SHAPE_LINE_CHAIN& path = bufferPolybase.COutline( 0 ); if( path.PointCount() < 2 ) // should not happen return; // Now, output the final polygon to DXF file: last = path.PointCount() - 1; VECTOR2I point = path.CPoint( 0 ); wxPoint startPoint( point.x, point.y ); MoveTo( startPoint ); for( int ii = 1; ii < path.PointCount(); ii++ ) { point = path.CPoint( ii ); LineTo( wxPoint( point.x, point.y ) ); } // Close polygon, if needed point = path.CPoint( last ); wxPoint endPoint( point.x, point.y ); if( endPoint != startPoint ) LineTo( startPoint ); PenFinish(); }
void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList_NG( BOARD* aPcb ) { int segsPerCircle; double correctionFactor; int outline_half_thickness = m_ZoneMinThickness / 2; std::auto_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO( g_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(g_DumpZonesWhenFilling) dumper->BeginGroup("clipper-zone"); SHAPE_POLY_SET solidAreas = ConvertPolyListToPolySet( m_smoothedPoly->m_CornersList ); solidAreas.Inflate( -outline_half_thickness, segsPerCircle ); solidAreas.Simplify( POLY_CALC_MODE ); SHAPE_POLY_SET holes; if(g_DumpZonesWhenFilling) dumper->Write( &solidAreas, "solid-areas" ); tmp.RemoveAllContours(); buildFeatureHoleList( aPcb, holes ); if(g_DumpZonesWhenFilling) dumper->Write( &holes, "feature-holes" ); holes.Simplify( POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &holes, "feature-holes-postsimplify" ); solidAreas.BooleanSubtract( holes, POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &solidAreas, "solid-areas-minus-holes" ); SHAPE_POLY_SET fractured = solidAreas; fractured.Fracture( POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &fractured, "fractured" ); m_FilledPolysList = fractured; // Remove insulated islands: if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); 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 solidAreas.BooleanSubtract( thermalHoles, POLY_CALC_MODE ); if( g_DumpZonesWhenFilling ) dumper->Write( &thermalHoles, "thermal-holes" ); // put these areas in m_FilledPolysList SHAPE_POLY_SET fractured = solidAreas; fractured.Fracture( POLY_CALC_MODE ); if( g_DumpZonesWhenFilling ) dumper->Write ( &fractured, "fractured" ); m_FilledPolysList = fractured; if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); } if(g_DumpZonesWhenFilling) dumper->EndGroup(); }