/* 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 GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, SHAPE_POLY_SET* aPolygons, EDA_DRAW_MODE_T aTraceMode, void* aData ) { // A Pad custom is plotted as polygon. // A flashed circle @aPadPos is added (anchor pad) // However, because the anchor pad can be circle or rect, we use only // a circle not bigger than the rect. // the main purpose is to print a flashed DCode as pad anchor if( aTraceMode == FILLED ) FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData ); GBR_METADATA gbr_metadata; if( aData ) { gbr_metadata = *static_cast<GBR_METADATA*>( aData ); // If the pad is drawn on a copper layer, // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR if( gbr_metadata.IsCopper() ) gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); wxString attrname( ".P" ); gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers } SHAPE_POLY_SET polyshape = *aPolygons; if( aTraceMode != FILLED ) { SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); polyshape.Inflate( -GetCurrentLineWidth()/2, 16 ); } std::vector< wxPoint > cornerList; for( int cnt = 0; cnt < polyshape.OutlineCount(); ++cnt ) { SHAPE_LINE_CHAIN& poly = polyshape.Outline( cnt ); cornerList.clear(); for( int ii = 0; ii < poly.PointCount(); ++ii ) cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); // Close polygon cornerList.push_back( cornerList[0] ); PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL, aTraceMode == FILLED ? 0 : GetCurrentLineWidth(), &gbr_metadata ); } }
void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize, int aCornerRadius, double aOrient, EDA_DRAW_MODE_T aTraceMode, void* aData ) { GBR_METADATA gbr_metadata; if( aData ) { gbr_metadata = *static_cast<GBR_METADATA*>( aData ); // If the pad is drawn on a copper layer, // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR if( gbr_metadata.IsCopper() ) gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); wxString attrname( ".P" ); gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers } if( aTraceMode != FILLED ) SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); // Currently, a Pad RoundRect is plotted as polygon. // TODO: use Aperture macro and flash it SHAPE_POLY_SET outline; const int segmentToCircleCount = 64; TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient, aCornerRadius, segmentToCircleCount ); if( aTraceMode != FILLED ) outline.Inflate( -GetCurrentLineWidth()/2, 16 ); std::vector< wxPoint > cornerList; // TransformRoundRectToPolygon creates only one convex polygon SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); cornerList.reserve( poly.PointCount() + 1 ); for( int ii = 0; ii < poly.PointCount(); ++ii ) cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); // Close polygon cornerList.push_back( cornerList[0] ); PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL, aTraceMode == FILLED ? 0 : GetCurrentLineWidth(), &gbr_metadata ); // Now, flash a pad anchor, if a netlist attribute is set // (remove me when a Aperture macro will be used) if( aData && aTraceMode == FILLED ) { int diameter = std::min( aSize.x, aSize.y ); FlashPadCircle( aPadPos, diameter, aTraceMode , aData ); } }
/** * Function TransformRoundRectToPolygon * convert a rectangle with rounded corners to a polygon * Convert arcs to multiple straight lines * @param aCornerBuffer = a buffer to store the polygon * @param aPosition = the coordinate of the center of the rectangle * @param aSize = the size of the rectangle * @param aRadius = radius of rounded corners * @param aRotation = rotation in 0.1 degrees of the rectangle * @param aCircleToSegmentsCount = the number of segments to approximate a circle */ void TransformRoundRectToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aPosition, const wxSize& aSize, double aRotation, int aCornerRadius, int aCircleToSegmentsCount ) { wxPoint corners[4]; GetRoundRectCornerCenters( corners, aCornerRadius, aPosition, aSize, aRotation ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ++ii ) outline.Append( corners[ii].x, corners[ii].y ); outline.Inflate( aCornerRadius, aCircleToSegmentsCount ); // Add the outline: aCornerBuffer.Append( outline ); }
void ZONE_CONTAINER::AddClearanceAreasPolygonsToPolysList_NG( BOARD* aPcb ) { int segsPerCircle; double correctionFactor; int outline_half_thickness = m_ZoneMinThickness / 2; std::unique_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 areas_fractured = solidAreas; areas_fractured.Fracture( POLY_CALC_MODE ); if (g_DumpZonesWhenFilling) dumper->Write( &areas_fractured, "areas_fractured" ); m_FilledPolysList = areas_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 th_fractured = solidAreas; th_fractured.Fracture( POLY_CALC_MODE ); if( g_DumpZonesWhenFilling ) dumper->Write ( &th_fractured, "th_fractured" ); m_FilledPolysList = th_fractured; if( GetNetCode() > 0 ) TestForCopperIslandAndRemoveInsulatedIslands( aPcb ); } if(g_DumpZonesWhenFilling) dumper->EndGroup(); }
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; } }
/** * 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<SHAPE_FILE_IO> 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, aZone, aFinalPolys, 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(); }
bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError ) { SHAPE_POLY_SET aux_polyset; for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt ) { const PAD_CS_PRIMITIVE& bshape = m_basicShapes[cnt]; switch( bshape.m_Shape ) { case S_CURVE: { std::vector<wxPoint> ctrlPoints = { bshape.m_Start, bshape.m_Ctrl1, bshape.m_Ctrl2, bshape.m_End }; BEZIER_POLY converter( ctrlPoints ); std::vector< wxPoint> poly; converter.GetPoly( poly, bshape.m_Thickness ); for( unsigned ii = 1; ii < poly.size(); ii++ ) { TransformRoundedEndsSegmentToPolygon( aux_polyset, poly[ii - 1], poly[ii], aError, bshape.m_Thickness ); } break; } case S_SEGMENT: // usual segment : line with rounded ends { TransformRoundedEndsSegmentToPolygon( aux_polyset, bshape.m_Start, bshape.m_End, aError, bshape.m_Thickness ); break; } case S_ARC: // Arc with rounded ends { TransformArcToPolygon( aux_polyset, bshape.m_Start, bshape.m_End, bshape.m_ArcAngle, aError, bshape.m_Thickness ); break; } case S_CIRCLE: // ring or circle { if( bshape.m_Thickness ) // ring TransformRingToPolygon( aux_polyset, bshape.m_Start, bshape.m_Radius, aError, bshape.m_Thickness ); else // Filled circle TransformCircleToPolygon( aux_polyset, bshape.m_Start, bshape.m_Radius, aError ); break; } case S_POLYGON: // polygon if( bshape.m_Poly.size() < 2 ) break; // Malformed polygon. { // Insert the polygon: const std::vector< wxPoint>& poly = bshape.m_Poly; aux_polyset.NewOutline(); if( bshape.m_Thickness ) { SHAPE_POLY_SET polyset; polyset.NewOutline(); for( unsigned ii = 0; ii < poly.size(); ii++ ) { polyset.Append( poly[ii].x, poly[ii].y ); } int numSegs = std::max( GetArcToSegmentCount( bshape.m_Thickness / 2, aError, 360.0 ), 6 ); polyset.Inflate( bshape.m_Thickness / 2, numSegs ); aux_polyset.Append( polyset ); } else for( unsigned ii = 0; ii < poly.size(); ii++ ) aux_polyset.Append( poly[ii].x, poly[ii].y ); } break; default: break; } } aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST ); // Merge all polygons with the initial pad anchor shape if( aux_polyset.OutlineCount() ) { aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } return aMergedPolygon->OutlineCount() <= 1; }
void PCB_PAINTER::draw( const D_PAD* aPad, int aLayer ) { PAD_SHAPE_T shape; double m, n; double orientation = aPad->GetOrientation(); // Draw description layer if( IsNetnameLayer( aLayer ) ) { VECTOR2D position( aPad->ShapePos() ); // Is anything that we can display enabled? if( m_pcbSettings.m_netNamesOnPads || m_pcbSettings.m_padNumbers ) { bool displayNetname = ( m_pcbSettings.m_netNamesOnPads && !aPad->GetNetname().empty() ); VECTOR2D padsize = VECTOR2D( aPad->GetSize() ); double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE; double size = padsize.y; // Keep the size ratio for the font, but make it smaller if( padsize.x < padsize.y ) { orientation += 900.0; size = padsize.x; std::swap( padsize.x, padsize.y ); } else if( padsize.x == padsize.y ) { // If the text is displayed on a symmetrical pad, do not rotate it orientation = 0.0; } // Font size limits if( size > maxSize ) size = maxSize; m_gal->Save(); m_gal->Translate( position ); // do not display descriptions upside down NORMALIZE_ANGLE_90( orientation ); m_gal->Rotate( DECIDEG2RAD( -orientation ) ); // Default font settings m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER ); m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER ); m_gal->SetFontBold( false ); m_gal->SetFontItalic( false ); m_gal->SetTextMirrored( false ); m_gal->SetStrokeColor( m_pcbSettings.GetColor( NULL, aLayer ) ); m_gal->SetIsStroke( true ); m_gal->SetIsFill( false ); // Set the text position to the pad shape position (the pad position is not the best place) VECTOR2D textpos( 0.0, 0.0 ); // Divide the space, to display both pad numbers and netnames // and set the Y text position to display 2 lines if( displayNetname && m_pcbSettings.m_padNumbers ) { size = size / 2.0; textpos.y = size / 2.0; } if( displayNetname ) { // calculate the size of net name text: double tsize = 1.5 * padsize.x / aPad->GetShortNetname().Length(); tsize = std::min( tsize, size ); // Use a smaller text size to handle interline, pen size.. tsize *= 0.7; VECTOR2D namesize( tsize, tsize ); m_gal->SetGlyphSize( namesize ); m_gal->SetLineWidth( namesize.x / 12.0 ); m_gal->BitmapText( aPad->GetShortNetname(), textpos, 0.0 ); } if( m_pcbSettings.m_padNumbers ) { const wxString& padName = aPad->GetName(); textpos.y = -textpos.y; double tsize = 1.5 * padsize.x / padName.Length(); tsize = std::min( tsize, size ); // Use a smaller text size to handle interline, pen size.. tsize *= 0.7; tsize = std::min( tsize, size ); VECTOR2D numsize( tsize, tsize ); m_gal->SetGlyphSize( numsize ); m_gal->SetLineWidth( numsize.x / 12.0 ); m_gal->BitmapText( padName, textpos, 0.0 ); } m_gal->Restore(); } return; } // Pad drawing COLOR4D color; // Pad holes color is type specific if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES ) { // Hole color is the background color for plated holes, but a specific color // for not plated holes (LAYER_NON_PLATEDHOLES color layer ) if( aPad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED ) color = m_pcbSettings.GetColor( nullptr, LAYER_NON_PLATEDHOLES ); // Don't let pads that *should* be NPTH get lost else if( aPad->PadShouldBeNPTH() ) color = m_pcbSettings.GetColor( aPad, aLayer ); else color = m_pcbSettings.GetBackgroundColor(); } else { color = m_pcbSettings.GetColor( aPad, aLayer ); } VECTOR2D size; if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] ) { // Outline mode m_gal->SetIsFill( false ); m_gal->SetIsStroke( true ); m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); m_gal->SetStrokeColor( color ); } else { // Filled mode m_gal->SetIsFill( true ); m_gal->SetIsStroke( false ); m_gal->SetFillColor( color ); } m_gal->Save(); m_gal->Translate( VECTOR2D( aPad->GetPosition() ) ); m_gal->Rotate( -aPad->GetOrientationRadians() ); int custom_margin = 0; // a clearance/margin for custom shape, for solder paste/mask // Choose drawing settings depending on if we are drawing a pad itself or a hole if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES ) { // Drawing hole: has same shape as PAD_CIRCLE or PAD_OVAL size = getDrillSize( aPad ) / 2.0; shape = getDrillShape( aPad ) == PAD_DRILL_SHAPE_OBLONG ? PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE; } else if( aLayer == F_Mask || aLayer == B_Mask ) { // Drawing soldermask int soldermaskMargin = aPad->GetSolderMaskMargin(); custom_margin = soldermaskMargin; m_gal->Translate( VECTOR2D( aPad->GetOffset() ) ); size = VECTOR2D( aPad->GetSize().x / 2.0 + soldermaskMargin, aPad->GetSize().y / 2.0 + soldermaskMargin ); shape = aPad->GetShape(); } else if( aLayer == F_Paste || aLayer == B_Paste ) { // Drawing solderpaste wxSize solderpasteMargin = aPad->GetSolderPasteMargin(); // try to find a clearance which can be used for custom shapes custom_margin = solderpasteMargin.x; m_gal->Translate( VECTOR2D( aPad->GetOffset() ) ); size = VECTOR2D( aPad->GetSize().x / 2.0 + solderpasteMargin.x, aPad->GetSize().y / 2.0 + solderpasteMargin.y ); shape = aPad->GetShape(); } else { // Drawing every kind of pad m_gal->Translate( VECTOR2D( aPad->GetOffset() ) ); size = VECTOR2D( aPad->GetSize() ) / 2.0; shape = aPad->GetShape(); } switch( shape ) { case PAD_SHAPE_OVAL: if( size.y >= size.x ) { m = ( size.y - size.x ); n = size.x; m_gal->DrawArc( VECTOR2D( 0, -m ), n, -M_PI, 0 ); m_gal->DrawArc( VECTOR2D( 0, m ), n, M_PI, 0 ); if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] ) { m_gal->DrawLine( VECTOR2D( -n, -m ), VECTOR2D( -n, m ) ); m_gal->DrawLine( VECTOR2D( n, -m ), VECTOR2D( n, m ) ); } else { m_gal->DrawRectangle( VECTOR2D( -n, -m ), VECTOR2D( n, m ) ); } } else { m = ( size.x - size.y ); n = size.y; m_gal->DrawArc( VECTOR2D( -m, 0 ), n, M_PI / 2, 3 * M_PI / 2 ); m_gal->DrawArc( VECTOR2D( m, 0 ), n, M_PI / 2, -M_PI / 2 ); if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] ) { m_gal->DrawLine( VECTOR2D( -m, -n ), VECTOR2D( m, -n ) ); m_gal->DrawLine( VECTOR2D( -m, n ), VECTOR2D( m, n ) ); } else { m_gal->DrawRectangle( VECTOR2D( -m, -n ), VECTOR2D( m, n ) ); } } break; case PAD_SHAPE_RECT: m_gal->DrawRectangle( VECTOR2D( -size.x, -size.y ), VECTOR2D( size.x, size.y ) ); break; case PAD_SHAPE_ROUNDRECT: { SHAPE_POLY_SET polySet; wxSize prsize( size.x * 2, size.y * 2 ); const int segmentToCircleCount = 64; const int corner_radius = aPad->GetRoundRectCornerRadius( prsize ); TransformRoundRectToPolygon( polySet, wxPoint( 0, 0 ), prsize, 0.0, corner_radius, segmentToCircleCount ); m_gal->DrawPolygon( polySet ); break; } case PAD_SHAPE_CUSTOM: { // Draw the complex custom shape // Use solder[Paste/Mask]size or pad size to build pad shape // however, solder[Paste/Mask] size has no actual meaning for a // custom shape, because it is a set of basic shapes // We use the custom_margin (good for solder mask, but approximative // for solder paste). if( custom_margin ) { SHAPE_POLY_SET outline; outline.Append( aPad->GetCustomShapeAsPolygon() ); const int segmentToCircleCount = ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF; outline.Inflate( custom_margin, segmentToCircleCount ); m_gal->DrawPolygon( outline ); } else { // Draw the polygon: only one polygon is expected // However we provide a multi polygon shape drawing // ( for the future or to show even an incorrect shape m_gal->DrawPolygon( aPad->GetCustomShapeAsPolygon() ); } } break; case PAD_SHAPE_TRAPEZOID: { std::deque<VECTOR2D> pointList; wxPoint corners[4]; VECTOR2D padSize = VECTOR2D( aPad->GetSize().x, aPad->GetSize().y ) / 2; VECTOR2D deltaPadSize = size - padSize; // = solder[Paste/Mask]Margin or 0 aPad->BuildPadPolygon( corners, wxSize( deltaPadSize.x, deltaPadSize.y ), 0.0 ); SHAPE_POLY_SET polySet; polySet.NewOutline(); polySet.Append( VECTOR2I( corners[0] ) ); polySet.Append( VECTOR2I( corners[1] ) ); polySet.Append( VECTOR2I( corners[2] ) ); polySet.Append( VECTOR2I( corners[3] ) ); m_gal->DrawPolygon( polySet ); } break; case PAD_SHAPE_CIRCLE: m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), size.x ); break; } m_gal->Restore(); // Clearance lines // It has to be called after GAL::Restore() as TransformShapeWithClearanceToPolygon() // returns already transformed coordinates constexpr int clearanceFlags = /*PCB_RENDER_SETTINGS::CL_EXISTING |*/ PCB_RENDER_SETTINGS::CL_PADS; if( ( m_pcbSettings.m_clearance & clearanceFlags ) == clearanceFlags && ( aLayer == LAYER_PAD_FR || aLayer == LAYER_PAD_BK || aLayer == LAYER_PADS_TH ) ) { SHAPE_POLY_SET polySet; constexpr int SEGCOUNT = 64; aPad->TransformShapeWithClearanceToPolygon( polySet, aPad->GetClearance(), SEGCOUNT, 1.0 ); m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); m_gal->SetIsStroke( true ); m_gal->SetIsFill( false ); m_gal->SetStrokeColor( color ); m_gal->DrawPolygon( polySet ); } }
/* Function TransformShapeWithClearanceToPolygon * Convert the pad shape to a closed polygon * Used in filling zones calculations and 3D view generation * Circles and arcs are approximated by segments * aCornerBuffer = a SHAPE_POLY_SET to store the polygon corners * aClearanceValue = the clearance around the pad * aCircleToSegmentsCount = the number of segments to approximate a circle * aCorrectionFactor = the correction to apply to circles radius to keep * clearance when the circle is approximated by segment bigger or equal * to the real clearance value (usually near from 1.0) */ void D_PAD:: TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aCircleToSegmentsCount, double aCorrectionFactor ) const { 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: dx = KiROUND( dx * aCorrectionFactor ); TransformCircleToPolygon( aCornerBuffer, PadShapePos, dx, aCircleToSegmentsCount ); 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 { dy = KiROUND( dy * aCorrectionFactor ); shape_offset.y = dy - dx; width = dx * 2; } else //if( dy <= dx ) { dx = KiROUND( dx * aCorrectionFactor ); shape_offset.x = dy - dx; width = dy * 2; } RotatePoint( &shape_offset, angle ); wxPoint start = PadShapePos - shape_offset; wxPoint end = PadShapePos + shape_offset; TransformRoundedEndsSegmentToPolygon( aCornerBuffer, start, end, aCircleToSegmentsCount, width ); } 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 ); } double rounding_radius = aClearanceValue * aCorrectionFactor; outline.Inflate( (int) rounding_radius, aCircleToSegmentsCount ); 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 ); }
void TransformRoundChamferedRectToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aPosition, const wxSize& aSize, double aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aCircleToSegmentsCount ) { // Build the basic shape in orientation 0.0, position 0,0 for chamfered corners // or in actual position/orientation for round rect only wxPoint corners[4]; GetRoundRectCornerCenters( corners, aCornerRadius, aChamferCorners ? wxPoint( 0, 0 ) : aPosition, aSize, aChamferCorners ? 0.0 : aRotation ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ++ii ) outline.Append( corners[ii].x, corners[ii].y ); outline.Inflate( aCornerRadius, aCircleToSegmentsCount ); if( aChamferCorners == RECT_NO_CHAMFER ) // no chamfer { // Add the outline: aCornerBuffer.Append( outline ); return; } // Now we have the round rect outline, in position 0,0 orientation 0.0. // Chamfer the corner(s). int chamfer_value = aChamferRatio * std::min( aSize.x, aSize.y ); SHAPE_POLY_SET chamfered_corner; // corner shape for the current corner to chamfer int corner_id[4] = { RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT, RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_RIGHT }; // Depending on the corner position, signX[] and signY[] give the sign of chamfer // coordinates relative to the corner position // The first corner is the top left corner, then top right, bottom left and bottom right int signX[4] = {1, -1, 1,-1 }; int signY[4] = {1, 1, -1,-1 }; for( int ii = 0; ii < 4; ii++ ) { if( (corner_id[ii] & aChamferCorners) == 0 ) continue; VECTOR2I corner_pos( -signX[ii]*aSize.x/2, -signY[ii]*aSize.y/2 ); if( aCornerRadius ) { // We recreate a rectangular area covering the full rounded corner (max size = aSize/2) // to rebuild the corner before chamfering, to be sure the rounded corner shape does not // overlap the chamfered corner shape: chamfered_corner.RemoveAllContours(); chamfered_corner.NewOutline(); chamfered_corner.Append( 0, 0 ); chamfered_corner.Append( 0, signY[ii]*aSize.y/2 ); chamfered_corner.Append( signX[ii]*aSize.x/2, signY[ii]*aSize.y/2 ); chamfered_corner.Append( signX[ii]*aSize.x/2, 0 ); chamfered_corner.Move( corner_pos ); outline.BooleanAdd( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } // Now chamfer this corner chamfered_corner.RemoveAllContours(); chamfered_corner.NewOutline(); chamfered_corner.Append( 0, 0 ); chamfered_corner.Append( 0, signY[ii]*chamfer_value ); chamfered_corner.Append( signX[ii]*chamfer_value, 0 ); chamfered_corner.Move( corner_pos ); outline.BooleanSubtract( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } // Rotate and move the outline: if( aRotation != 0.0 ) outline.Rotate( DECIDEG2RAD( -aRotation ), VECTOR2I( 0, 0 ) ); outline.Move( VECTOR2I( aPosition ) ); // Add the outline: aCornerBuffer.Append( outline ); }