// The helper function for D_CODE::ConvertShapeToPolygon(). // Add a hole to a polygon static void addHoleToPolygon( SHAPE_POLY_SET* aPolygon, APERTURE_DEF_HOLETYPE aHoleShape, wxSize aSize, wxPoint aAnchorPos ) { wxPoint currpos; SHAPE_POLY_SET holeBuffer; if( aHoleShape == APT_DEF_ROUND_HOLE ) { TransformCircleToPolygon( holeBuffer, wxPoint( 0, 0 ), aSize.x / 2, SEGS_CNT ); } else if( aHoleShape == APT_DEF_RECT_HOLE ) { holeBuffer.NewOutline(); currpos.x = aSize.x / 2; currpos.y = aSize.y / 2; holeBuffer.Append( VECTOR2I( currpos ) ); // link to hole and begin hole currpos.x -= aSize.x; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.y -= aSize.y; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.x += aSize.x; holeBuffer.Append( VECTOR2I( currpos ) ); currpos.y += aSize.y; holeBuffer.Append( VECTOR2I( currpos ) ); // close hole } aPolygon->BooleanSubtract( holeBuffer, SHAPE_POLY_SET::PM_FAST ); // Needed for legacy canvas only aPolygon->Fracture( SHAPE_POLY_SET::PM_FAST ); }
/* This function is used to extract a board outlines (3D view, automatic zones build ...) * Any closed outline inside the main outline is a hole * All contours should be closed, i.e. valid closed polygon vertices */ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation ) { PCB_TYPE_COLLECTOR items; // Get all the DRAWSEGMENTS and module graphics into 'items', // then keep only those on layer == Edge_Cuts. static const KICAD_T scan_graphics[] = { PCB_LINE_T, PCB_MODULE_EDGE_T, EOT }; items.Collect( aBoard, scan_graphics ); // Make a working copy of aSegList, because the list is modified during calculations std::vector< DRAWSEGMENT* > segList; for( int ii = 0; ii < items.GetCount(); ii++ ) { if( items[ii]->GetLayer() == Edge_Cuts ) segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) ); } bool success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance, aErrorLocation ); if( !success || !aOutlines.OutlineCount() ) { // Creates a valid polygon outline is not possible. // So uses the board edge cuts bounding box to create a // rectangular outline // When no edge cuts items, build a contour // from global bounding box EDA_RECT bbbox = aBoard->GetBoardEdgesBoundingBox(); // If null area, uses the global bounding box. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) ) bbbox = aBoard->ComputeBoundingBox(); // Ensure non null area. If happen, gives a minimal size. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) ) bbbox.Inflate( Millimeter2iu( 1.0 ) ); aOutlines.RemoveAllContours(); aOutlines.NewOutline(); wxPoint corner; aOutlines.Append( bbbox.GetOrigin() ); corner.x = bbbox.GetOrigin().x; corner.y = bbbox.GetEnd().y; aOutlines.Append( corner ); aOutlines.Append( bbbox.GetEnd() ); corner.x = bbbox.GetEnd().x; corner.y = bbbox.GetOrigin().y; aOutlines.Append( corner ); } return success; }
/** * Function TransformRingToPolygon * Creates a polygon from a ring * Convert arcs to multiple straight segments * @param aCornerBuffer = a buffer to store the polygon * @param aCentre = centre of the arc or circle * @param aRadius = radius of the circle * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aWidth = width (thickness) of the ring */ void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aCentre, int aRadius, int aCircleToSegmentsCount, int aWidth ) { // Compute the corners positions and creates the poly wxPoint curr_point; int inner_radius = aRadius - ( aWidth / 2 ); int outer_radius = inner_radius + aWidth; if( inner_radius <= 0 ) { //In this case, the ring is just a circle (no hole inside) TransformCircleToPolygon( aCornerBuffer, aCentre, aRadius + ( aWidth / 2 ), aCircleToSegmentsCount ); return; } aCornerBuffer.NewOutline(); // Draw the inner circle of the ring int delta = 3600 / aCircleToSegmentsCount; // rotate angle in 0.1 degree for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = inner_radius; curr_point.y = 0; RotatePoint( &curr_point, ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of inner circle aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); // Draw the outer circle of the ring // the first point creates also a segment from the inner to the outer polygon for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = outer_radius; curr_point.y = 0; RotatePoint( &curr_point, -ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of outer circle aCornerBuffer.Append( aCentre.x + outer_radius, aCentre.y ); // And connect the outer polygon to the inner polygon,. // because a segment from inner to the outer polygon was already created, // the final polygon is the inner and the outer outlines connected by // 2 overlapping segments aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); }
/** * Function TransformBoundingBoxWithClearanceToPolygon * Convert the text bounding box to a rectangular polygon * Used in filling zones calculations * Circles and arcs are approximated by segments * @param aCornerBuffer = a buffer to store the polygon * @param aClearanceValue = the clearance around the text bounding box */ void TEXTE_PCB::TransformBoundingBoxWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue ) const { if( GetText().Length() == 0 ) return; wxPoint corners[4]; // Buffer of polygon corners EDA_RECT rect = GetTextBox( -1 ); rect.Inflate( aClearanceValue ); corners[0].x = rect.GetOrigin().x; corners[0].y = rect.GetOrigin().y; corners[1].y = corners[0].y; corners[1].x = rect.GetRight(); corners[2].x = corners[1].x; corners[2].y = rect.GetBottom(); corners[3].y = corners[2].y; corners[3].x = corners[0].x; aCornerBuffer.NewOutline(); for( int ii = 0; ii < 4; ii++ ) { // Rotate polygon RotatePoint( &corners[ii].x, &corners[ii].y, m_Pos.x, m_Pos.y, m_Orient ); aCornerBuffer.Append( corners[ii].x, corners[ii].y ); } }
/* Function TransformSolidAreasShapesToPolygonSet * Convert solid areas full shapes to polygon set * (the full shape is the polygon area with a thick outline) * Used in 3D view * Arcs (ends of segments) are approximated by segments * aCornerBuffer = a buffer to store the polygons * aCircleToSegmentsCount = the number of segments to approximate a circle * aCorrectionFactor = the correction to apply to arcs radius to roughly * keep arc radius when approximated by segments */ void ZONE_CONTAINER::TransformSolidAreasShapesToPolygonSet( SHAPE_POLY_SET& aCornerBuffer, int aCircleToSegmentsCount, double aCorrectionFactor ) { if( GetFilledPolysList().IsEmpty() ) return; // add filled areas polygons aCornerBuffer.Append( m_FilledPolysList ); // add filled areas outlines, which are drawn with thick lines for( int i = 0; i < m_FilledPolysList.OutlineCount(); i++ ) { const SHAPE_LINE_CHAIN& path = m_FilledPolysList.COutline( i ); for( int j = 0; j < path.PointCount(); j++ ) { const VECTOR2I& a = path.CPoint( j ); const VECTOR2I& b = path.CPoint( j + 1 ); TransformRoundedEndsSegmentToPolygon( aCornerBuffer, wxPoint( a.x, a.y ), wxPoint( b.x, b.y ), aCircleToSegmentsCount, GetMinThickness() ); } } }
void ZONE_CONTAINER::TransformSolidAreasShapesToPolygonSet( SHAPE_POLY_SET& aCornerBuffer, int aError ) const { if( GetFilledPolysList().IsEmpty() ) return; // add filled areas polygons aCornerBuffer.Append( m_FilledPolysList ); auto board = GetBoard(); int maxError = ARC_HIGH_DEF; if( board ) maxError = board->GetDesignSettings().m_MaxError; // add filled areas outlines, which are drawn with thick lines for( int i = 0; i < m_FilledPolysList.OutlineCount(); i++ ) { const SHAPE_LINE_CHAIN& path = m_FilledPolysList.COutline( i ); for( int j = 0; j < path.PointCount(); j++ ) { const VECTOR2I& a = path.CPoint( j ); const VECTOR2I& b = path.CPoint( j + 1 ); TransformRoundedEndsSegmentToPolygon( aCornerBuffer, wxPoint( a.x, a.y ), wxPoint( b.x, b.y ), maxError, GetMinThickness() ); } } }
/* * Function BuildPadShapePolygon * Build the Corner list of the polygonal shape, * depending on shape, extra size (clearance ...) pad and orientation * Note: for Round and oval pads this function is equivalent to * TransformShapeWithClearanceToPolygon, but not for other shapes */ void D_PAD::BuildPadShapePolygon( SHAPE_POLY_SET& aCornerBuffer, wxSize aInflateValue, int aSegmentsPerCircle, double aCorrectionFactor ) const { wxPoint corners[4]; wxPoint PadShapePos = ShapePos(); /* Note: for pad having a shape offset, * the pad position is NOT the shape position */ switch( GetShape() ) { case PAD_SHAPE_CIRCLE: case PAD_SHAPE_OVAL: TransformShapeWithClearanceToPolygon( aCornerBuffer, aInflateValue.x, aSegmentsPerCircle, aCorrectionFactor ); break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: aCornerBuffer.NewOutline(); BuildPadPolygon( corners, aInflateValue, m_Orient ); for( int ii = 0; ii < 4; ii++ ) { corners[ii] += PadShapePos; // Shift origin to position aCornerBuffer.Append( corners[ii].x, corners[ii].y ); } break; } }
/** * 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 ); }
/** * Function TransformRingToPolygon * Creates a polygon from a ring * Convert arcs to multiple straight segments * @param aCornerBuffer = a buffer to store the polygon * @param aCentre = centre of the arc or circle * @param aRadius = radius of the circle * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aWidth = width (thickness) of the ring */ void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aCentre, int aRadius, int aCircleToSegmentsCount, int aWidth ) { int delta = 3600 / aCircleToSegmentsCount; // rotate angle in 0.1 degree // Compute the corners posituions and creates poly wxPoint curr_point; int inner_radius = aRadius - ( aWidth / 2 ); int outer_radius = inner_radius + aWidth; aCornerBuffer.NewOutline(); // Draw the inner circle of the ring for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = inner_radius; curr_point.y = 0; RotatePoint( &curr_point, ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of inner circle aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); // Draw the outer circle of the ring for( int ii = 0; ii < 3600; ii += delta ) { curr_point.x = outer_radius; curr_point.y = 0; RotatePoint( &curr_point, -ii ); curr_point += aCentre; aCornerBuffer.Append( curr_point.x, curr_point.y ); } // Draw the last point of outer circle aCornerBuffer.Append( aCentre.x + outer_radius, aCentre.y ); aCornerBuffer.Append( aCentre.x + inner_radius, aCentre.y ); }
/* * Function BuildPadShapePolygon * Build the Corner list of the polygonal shape, * depending on shape, extra size (clearance ...) pad and orientation * Note: for Round and oval pads this function is equivalent to * TransformShapeWithClearanceToPolygon, but not for other shapes */ void D_PAD::BuildPadShapePolygon( SHAPE_POLY_SET& aCornerBuffer, wxSize aInflateValue, int aError ) const { wxPoint corners[4]; wxPoint padShapePos = ShapePos(); /* Note: for pad having a shape offset, * the pad position is NOT the shape position */ switch( GetShape() ) { case PAD_SHAPE_CIRCLE: case PAD_SHAPE_OVAL: case PAD_SHAPE_ROUNDRECT: case PAD_SHAPE_CHAMFERED_RECT: { // We are using TransformShapeWithClearanceToPolygon to build the shape. // Currently, this method uses only the same inflate value for X and Y dirs. // so because here this is not the case, we use a inflated dummy pad to build // the polygonal shape // TODO: remove this dummy pad when TransformShapeWithClearanceToPolygon will use // a wxSize to inflate the pad size D_PAD dummy( *this ); dummy.SetSize( GetSize() + aInflateValue + aInflateValue ); dummy.TransformShapeWithClearanceToPolygon( aCornerBuffer, 0 ); } break; case PAD_SHAPE_TRAPEZOID: case PAD_SHAPE_RECT: aCornerBuffer.NewOutline(); BuildPadPolygon( corners, aInflateValue, m_Orient ); for( int ii = 0; ii < 4; ii++ ) { corners[ii] += padShapePos; // Shift origin to position aCornerBuffer.Append( corners[ii].x, corners[ii].y ); } break; case PAD_SHAPE_CUSTOM: // for a custom shape, that is in fact a polygon (with holes), we can use only a inflate value. // so use ( aInflateValue.x + aInflateValue.y ) / 2 as polygon inflate value. // (different values for aInflateValue.x and aInflateValue.y has no sense for a custom pad) TransformShapeWithClearanceToPolygon( aCornerBuffer, ( aInflateValue.x + aInflateValue.y ) / 2 ); break; } }
const SHAPE_POLY_SET ConvertPolyListToPolySet( const CPOLYGONS_LIST& aList ) { SHAPE_POLY_SET rv; unsigned corners_count = aList.GetCornersCount(); // Enter main outline: this is the first contour unsigned ic = 0; if( !corners_count ) return rv; int index = 0; while( ic < corners_count ) { int hole = -1; if( index == 0 ) { rv.NewOutline(); hole = -1; } else { hole = rv.NewHole(); } while( ic < corners_count ) { rv.Append( aList.GetX( ic ), aList.GetY( ic ), 0, hole ); if( aList.IsEndContour( ic ) ) break; ic++; } ic++; index++; } return rv; }
/** * Function TransformCircleToPolygon * convert a circle to a polygon, using multiple straight lines * @param aCornerBuffer = a buffer to store the polygon * @param aCenter = the center of the circle * @param aRadius = the radius of the circle * @param aCircleToSegmentsCount = the number of segments to approximate a circle * Note: the polygon is inside the circle, so if you want to have the polygon * outside the circle, you should give aRadius calculated with a corrrection factor */ void TransformCircleToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aCenter, int aRadius, int aCircleToSegmentsCount ) { wxPoint corner_position; int delta = 3600 / aCircleToSegmentsCount; // rot angle in 0.1 degree int halfstep = 1800 / aCircleToSegmentsCount; // the starting value for rot angles aCornerBuffer.NewOutline(); for( int ii = 0; ii < aCircleToSegmentsCount; ii++ ) { corner_position.x = aRadius; corner_position.y = 0; int angle = (ii * delta) + halfstep; RotatePoint( &corner_position.x, &corner_position.y, angle ); corner_position += aCenter; aCornerBuffer.Append( corner_position.x, corner_position.y ); } }
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 ); }
std::unique_ptr<PNS::SOLID> PNS_KICAD_IFACE::syncPad( D_PAD* aPad ) { LAYER_RANGE layers( 0, MAX_CU_LAYERS - 1 ); // ignore non-copper pads if( ( aPad->GetLayerSet() & LSET::AllCuMask()).none() ) return NULL; switch( aPad->GetAttribute() ) { case PAD_ATTRIB_STANDARD: break; case PAD_ATTRIB_SMD: case PAD_ATTRIB_HOLE_NOT_PLATED: case PAD_ATTRIB_CONN: { LSET lmsk = aPad->GetLayerSet(); bool is_copper = false; for( int i = 0; i < MAX_CU_LAYERS; i++ ) { if( lmsk[i] ) { is_copper = true; if( aPad->GetAttribute() != PAD_ATTRIB_HOLE_NOT_PLATED ) layers = LAYER_RANGE( i ); break; } } if( !is_copper ) return NULL; } break; default: wxLogTrace( "PNS", "unsupported pad type 0x%x", aPad->GetAttribute() ); return NULL; } std::unique_ptr< PNS::SOLID > solid( new PNS::SOLID ); solid->SetLayers( layers ); solid->SetNet( aPad->GetNetCode() ); solid->SetParent( aPad ); wxPoint wx_c = aPad->ShapePos(); wxSize wx_sz = aPad->GetSize(); wxPoint offset = aPad->GetOffset(); VECTOR2I c( wx_c.x, wx_c.y ); VECTOR2I sz( wx_sz.x, wx_sz.y ); RotatePoint( &offset, aPad->GetOrientation() ); solid->SetPos( VECTOR2I( c.x - offset.x, c.y - offset.y ) ); solid->SetOffset( VECTOR2I( offset.x, offset.y ) ); double orient = aPad->GetOrientation() / 10.0; if( aPad->GetShape() == PAD_SHAPE_CIRCLE ) { solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) ); } else if( aPad->GetShape() == PAD_SHAPE_CUSTOM ) { SHAPE_POLY_SET outline; outline.Append( aPad->GetCustomShapeAsPolygon() ); aPad->CustomShapeAsPolygonToBoardPosition( &outline, wx_c, aPad->GetOrientation() ); SHAPE_CONVEX* shape = new SHAPE_CONVEX(); // We use the convex hull of the pad shape, because PnS knows // only convex shapes. std::vector<wxPoint> convex_hull; BuildConvexHull( convex_hull, outline ); for( unsigned ii = 0; ii < convex_hull.size(); ii++ ) shape->Append( convex_hull[ii] ); solid->SetShape( shape ); } else { if( orient == 0.0 || orient == 90.0 || orient == 180.0 || orient == 270.0 ) { if( orient == 90.0 || orient == 270.0 ) sz = VECTOR2I( sz.y, sz.x ); switch( aPad->GetShape() ) { case PAD_SHAPE_OVAL: if( sz.x == sz.y ) solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) ); else { VECTOR2I delta; if( sz.x > sz.y ) delta = VECTOR2I( ( sz.x - sz.y ) / 2, 0 ); else delta = VECTOR2I( 0, ( sz.y - sz.x ) / 2 ); SHAPE_SEGMENT* shape = new SHAPE_SEGMENT( c - delta, c + delta, std::min( sz.x, sz.y ) ); solid->SetShape( shape ); } break; case PAD_SHAPE_RECT: solid->SetShape( new SHAPE_RECT( c - sz / 2, sz.x, sz.y ) ); break; case PAD_SHAPE_TRAPEZOID: { wxPoint coords[4]; aPad->BuildPadPolygon( coords, wxSize( 0, 0 ), aPad->GetOrientation() ); SHAPE_CONVEX* shape = new SHAPE_CONVEX(); for( int ii = 0; ii < 4; ii++ ) { shape->Append( wx_c + coords[ii] ); } solid->SetShape( shape ); break; } case PAD_SHAPE_ROUNDRECT: { SHAPE_POLY_SET outline; const int segmentToCircleCount = 64; aPad->BuildPadShapePolygon( outline, wxSize( 0, 0 ), segmentToCircleCount, 1.0 ); // TransformRoundRectToPolygon creates only one convex polygon SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); SHAPE_CONVEX* shape = new SHAPE_CONVEX(); for( int ii = 0; ii < poly.PointCount(); ++ii ) { shape->Append( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); } solid->SetShape( shape ); } break; default: wxLogTrace( "PNS", "unsupported pad shape" ); return nullptr; } } else { switch( aPad->GetShape() ) { // PAD_SHAPE_CIRCLE and PAD_SHAPE_CUSTOM already handled above case PAD_SHAPE_OVAL: if( sz.x == sz.y ) solid->SetShape( new SHAPE_CIRCLE( c, sz.x / 2 ) ); else { wxPoint start; wxPoint end; wxPoint corner; SHAPE_CONVEX* shape = new SHAPE_CONVEX(); int w = aPad->BuildSegmentFromOvalShape( start, end, 0.0, wxSize( 0, 0 ) ); if( start.y == 0 ) corner = wxPoint( start.x, -( w / 2 ) ); else corner = wxPoint( w / 2, start.y ); RotatePoint( &start, aPad->GetOrientation() ); RotatePoint( &corner, aPad->GetOrientation() ); shape->Append( wx_c + corner ); for( int rot = 100; rot <= 1800; rot += 100 ) { wxPoint p( corner ); RotatePoint( &p, start, rot ); shape->Append( wx_c + p ); } if( end.y == 0 ) corner = wxPoint( end.x, w / 2 ); else corner = wxPoint( -( w / 2 ), end.y ); RotatePoint( &end, aPad->GetOrientation() ); RotatePoint( &corner, aPad->GetOrientation() ); shape->Append( wx_c + corner ); for( int rot = 100; rot <= 1800; rot += 100 ) { wxPoint p( corner ); RotatePoint( &p, end, rot ); shape->Append( wx_c + p ); } solid->SetShape( shape ); } break; case PAD_SHAPE_RECT: case PAD_SHAPE_TRAPEZOID: { wxPoint coords[4]; aPad->BuildPadPolygon( coords, wxSize( 0, 0 ), aPad->GetOrientation() ); SHAPE_CONVEX* shape = new SHAPE_CONVEX(); for( int ii = 0; ii < 4; ii++ ) { shape->Append( wx_c + coords[ii] ); } solid->SetShape( shape ); break; } case PAD_SHAPE_ROUNDRECT: { SHAPE_POLY_SET outline; const int segmentToCircleCount = 32; aPad->BuildPadShapePolygon( outline, wxSize( 0, 0 ), segmentToCircleCount, 1.0 ); // TransformRoundRectToPolygon creates only one convex polygon SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); SHAPE_CONVEX* shape = new SHAPE_CONVEX(); for( int ii = 0; ii < poly.PointCount(); ++ii ) { shape->Append( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); } solid->SetShape( shape ); break; } default: wxLogTrace( "PNS", "unsupported pad shape" ); return nullptr; } } } return solid; }
/* * Note 1: polygons are drawm using outlines witk a thickness = aMinThicknessValue * so shapes must take in account this outline thickness * * Note 2: * Trapezoidal pads are not considered here because they are very special case * and are used in microwave applications and they *DO NOT* have a thermal relief that * change the shape by creating stubs and destroy their properties. */ void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer, const D_PAD& aPad, int aThermalGap, int aCopperThickness, int aMinThicknessValue, int aError, double aThermalRot ) { wxPoint corner, corner_end; wxSize copper_thickness; wxPoint padShapePos = aPad.ShapePos(); // Note: for pad having a shape offset, // the pad position is NOT the shape position /* Keep in account the polygon outline thickness * aThermalGap must be increased by aMinThicknessValue/2 because drawing external outline * with a thickness of aMinThicknessValue will reduce gap by aMinThicknessValue/2 */ aThermalGap += aMinThicknessValue / 2; /* Keep in account the polygon outline thickness * copper_thickness must be decreased by aMinThicknessValue because drawing outlines * with a thickness of aMinThicknessValue will increase real thickness by aMinThicknessValue */ int dx = aPad.GetSize().x / 2; int dy = aPad.GetSize().y / 2; copper_thickness.x = std::min( aPad.GetSize().x, aCopperThickness ) - aMinThicknessValue; copper_thickness.y = std::min( aPad.GetSize().y, aCopperThickness ) - aMinThicknessValue; if( copper_thickness.x < 0 ) copper_thickness.x = 0; if( copper_thickness.y < 0 ) copper_thickness.y = 0; switch( aPad.GetShape() ) { case PAD_SHAPE_CIRCLE: // Add 4 similar holes { /* we create 4 copper holes and put them in position 1, 2, 3 and 4 * here is the area of the rectangular pad + its thermal gap * the 4 copper holes remove the copper in order to create the thermal gap * 4 ------ 1 * | | * | | * | | * | | * 3 ------ 2 * holes 2, 3, 4 are the same as hole 1, rotated 90, 180, 270 deg */ // Build the hole pattern, for the hole in the X >0, Y > 0 plane: // The pattern roughtly is a 90 deg arc pie std::vector <wxPoint> corners_buffer; int numSegs = std::max( GetArcToSegmentCount( dx + aThermalGap, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); double delta = 3600.0 / numSegs; // Radius of outer arcs of the shape corrected for arc approximation by lines int outer_radius = KiROUND( ( dx + aThermalGap ) * correction ); // Crosspoint of thermal spoke sides, the first point of polygon buffer corners_buffer.push_back( wxPoint( copper_thickness.x / 2, copper_thickness.y / 2 ) ); // Add an intermediate point on spoke sides, to allow a > 90 deg angle between side // and first seg of arc approx corner.x = copper_thickness.x / 2; int y = outer_radius - (aThermalGap / 4); corner.y = KiROUND( sqrt( ( (double) y * y - (double) corner.x * corner.x ) ) ); if( aThermalRot != 0 ) corners_buffer.push_back( corner ); // calculate the starting point of the outter arc corner.x = copper_thickness.x / 2; corner.y = KiROUND( sqrt( ( (double) outer_radius * outer_radius ) - ( (double) corner.x * corner.x ) ) ); RotatePoint( &corner, 90 ); // 9 degrees is the spoke fillet size // calculate the ending point of the outer arc corner_end.x = corner.y; corner_end.y = corner.x; // calculate intermediate points (y coordinate from corner.y to corner_end.y while( (corner.y > corner_end.y) && (corner.x < corner_end.x) ) { corners_buffer.push_back( corner ); RotatePoint( &corner, delta ); } corners_buffer.push_back( corner_end ); /* add an intermediate point, to avoid angles < 90 deg between last arc approx line * and radius line */ corner.x = corners_buffer[1].y; corner.y = corners_buffer[1].x; corners_buffer.push_back( corner ); // Now, add the 4 holes ( each is the pattern, rotated by 0, 90, 180 and 270 deg // aThermalRot = 450 (45.0 degrees orientation) work fine. double angle_pad = aPad.GetOrientation(); // Pad orientation double th_angle = aThermalRot; for( unsigned ihole = 0; ihole < 4; ihole++ ) { aCornerBuffer.NewOutline(); for( unsigned ii = 0; ii < corners_buffer.size(); ii++ ) { corner = corners_buffer[ii]; RotatePoint( &corner, th_angle + angle_pad ); // Rotate by segment angle and pad orientation corner += padShapePos; aCornerBuffer.Append( corner.x, corner.y ); } th_angle += 900; // Note: th_angle in in 0.1 deg. } } break; case PAD_SHAPE_OVAL: { // Oval pad support along the lines of round and rectangular pads std::vector <wxPoint> corners_buffer; // Polygon buffer as vector dx = (aPad.GetSize().x / 2) + aThermalGap; // Cutout radius x dy = (aPad.GetSize().y / 2) + aThermalGap; // Cutout radius y wxPoint shape_offset; // We want to calculate an oval shape with dx > dy. // if this is not the case, exchange dx and dy, and rotate the shape 90 deg. int supp_angle = 0; if( dx < dy ) { std::swap( dx, dy ); supp_angle = 900; std::swap( copper_thickness.x, copper_thickness.y ); } int deltasize = dx - dy; // = distance between shape position and the 2 demi-circle ends centre // here we have dx > dy // Radius of outer arcs of the shape: int outer_radius = dy; // The radius of the outer arc is radius end + aThermalGap int numSegs = std::max( GetArcToSegmentCount( outer_radius, aError, 360.0 ), 6 ); double delta = 3600.0 / numSegs; // Some coordinate fiddling, depending on the shape offset direction shape_offset = wxPoint( deltasize, 0 ); // Crosspoint of thermal spoke sides, the first point of polygon buffer corner.x = copper_thickness.x / 2; corner.y = copper_thickness.y / 2; corners_buffer.push_back( corner ); // Arc start point calculation, the intersecting point of cutout arc and thermal spoke edge // If copper thickness is more than shape offset, we need to calculate arc intercept point. if( copper_thickness.x > deltasize ) { corner.x = copper_thickness.x / 2; corner.y = KiROUND( sqrt( ( (double) outer_radius * outer_radius ) - ( (double) ( corner.x - delta ) * ( corner.x - deltasize ) ) ) ); corner.x -= deltasize; /* creates an intermediate point, to have a > 90 deg angle * between the side and the first segment of arc approximation */ wxPoint intpoint = corner; intpoint.y -= aThermalGap / 4; corners_buffer.push_back( intpoint + shape_offset ); RotatePoint( &corner, 90 ); // 9 degrees of thermal fillet } else { corner.x = copper_thickness.x / 2; corner.y = outer_radius; corners_buffer.push_back( corner ); } // Add an intermediate point on spoke sides, to allow a > 90 deg angle between side // and first seg of arc approx wxPoint last_corner; last_corner.y = copper_thickness.y / 2; int px = outer_radius - (aThermalGap / 4); last_corner.x = KiROUND( sqrt( ( ( (double) px * px ) - (double) last_corner.y * last_corner.y ) ) ); // Arc stop point calculation, the intersecting point of cutout arc and thermal spoke edge corner_end.y = copper_thickness.y / 2; corner_end.x = KiROUND( sqrt( ( (double) outer_radius * outer_radius ) - ( (double) corner_end.y * corner_end.y ) ) ); RotatePoint( &corner_end, -90 ); // 9 degrees of thermal fillet // calculate intermediate arc points till limit is reached while( (corner.y > corner_end.y) && (corner.x < corner_end.x) ) { corners_buffer.push_back( corner + shape_offset ); RotatePoint( &corner, delta ); } //corners_buffer.push_back(corner + shape_offset); // TODO: about one mil geometry error forms somewhere. corners_buffer.push_back( corner_end + shape_offset ); corners_buffer.push_back( last_corner + shape_offset ); // Enabling the line above shows intersection point. /* Create 2 holes, rotated by pad rotation. */ double angle = aPad.GetOrientation() + supp_angle; for( int irect = 0; irect < 2; irect++ ) { aCornerBuffer.NewOutline(); for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint cpos = corners_buffer[ic]; RotatePoint( &cpos, angle ); cpos += padShapePos; aCornerBuffer.Append( cpos.x, cpos.y ); } angle = AddAngles( angle, 1800 ); // this is calculate hole 3 } // Create holes, that are the mirrored from the previous holes for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint swap = corners_buffer[ic]; swap.x = -swap.x; corners_buffer[ic] = swap; } // Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg angle = aPad.GetOrientation() + supp_angle; for( int irect = 0; irect < 2; irect++ ) { aCornerBuffer.NewOutline(); for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint cpos = corners_buffer[ic]; RotatePoint( &cpos, angle ); cpos += padShapePos; aCornerBuffer.Append( cpos.x, cpos.y ); } angle = AddAngles( angle, 1800 ); } } break; case PAD_SHAPE_CHAMFERED_RECT: case PAD_SHAPE_ROUNDRECT: // thermal shape is the same for rectangular shapes. case PAD_SHAPE_RECT: { /* we create 4 copper holes and put them in position 1, 2, 3 and 4 * here is the area of the rectangular pad + its thermal gap * the 4 copper holes remove the copper in order to create the thermal gap * 1 ------ 4 * | | * | | * | | * | | * 2 ------ 3 * hole 3 is the same as hole 1, rotated 180 deg * hole 4 is the same as hole 2, rotated 180 deg and is the same as hole 1, mirrored */ // First, create a rectangular hole for position 1 : // 2 ------- 3 // | | // | | // | | // 1 -------4 // Modified rectangles with one corner rounded. TODO: merging with oval thermals // and possibly round too. std::vector <wxPoint> corners_buffer; // Polygon buffer as vector dx = (aPad.GetSize().x / 2) + aThermalGap; // Cutout radius x dy = (aPad.GetSize().y / 2) + aThermalGap; // Cutout radius y // calculation is optimized for pad shape with dy >= dx (vertical rectangle). // if it is not the case, just rotate this shape 90 degrees: double angle = aPad.GetOrientation(); wxPoint corner_origin_pos( -aPad.GetSize().x / 2, -aPad.GetSize().y / 2 ); if( dy < dx ) { std::swap( dx, dy ); std::swap( copper_thickness.x, copper_thickness.y ); std::swap( corner_origin_pos.x, corner_origin_pos.y ); angle += 900.0; } // Now calculate the hole pattern in position 1 ( top left pad corner ) // The first point of polygon buffer is left lower corner, second the crosspoint of // thermal spoke sides, the third is upper right corner and the rest are rounding // vertices going anticlockwise. Note the inverted Y-axis in corners_buffer y coordinates. wxPoint arc_end_point( -dx, -(aThermalGap / 4 + copper_thickness.y / 2) ); corners_buffer.push_back( arc_end_point ); // Adds small miters to zone corners_buffer.push_back( wxPoint( -(dx - aThermalGap / 4), -copper_thickness.y / 2 ) ); // fill and spoke corner corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -copper_thickness.y / 2 ) ); corners_buffer.push_back( wxPoint( -copper_thickness.x / 2, -(dy - aThermalGap / 4) ) ); // The first point to build the rounded corner: wxPoint arc_start_point( -(aThermalGap / 4 + copper_thickness.x / 2) , -dy ); corners_buffer.push_back( arc_start_point ); int numSegs = std::max( GetArcToSegmentCount( aThermalGap, aError, 360.0 ), 6 ); double correction = GetCircletoPolyCorrectionFactor( numSegs ); int rounding_radius = KiROUND( aThermalGap * correction ); // Corner rounding radius // Calculate arc angle parameters. // the start angle id near 900 decidegrees, the final angle is near 1800.0 decidegrees. double arc_increment = 3600.0 / numSegs; // the arc_angle_start is 900.0 or slighly more, depending on the actual arc starting point double arc_angle_start = atan2( -arc_start_point.y -corner_origin_pos.y, arc_start_point.x - corner_origin_pos.x ) * 1800/M_PI; if( arc_angle_start < 900.0 ) arc_angle_start = 900.0; bool first_point = true; for( double curr_angle = arc_angle_start; ; curr_angle += arc_increment ) { wxPoint corner_position = wxPoint( rounding_radius, 0 ); RotatePoint( &corner_position, curr_angle ); // Rounding vector rotation corner_position += corner_origin_pos; // Rounding vector + Pad corner offset // The arc angle is <= 90 degrees, therefore the arc is finished if the x coordinate // decrease or the y coordinate is smaller than the y end point if( !first_point && ( corner_position.x >= corners_buffer.back().x || corner_position.y > arc_end_point.y ) ) break; first_point = false; // Note: for hole in position 1, arc x coordinate is always < x starting point // and arc y coordinate is always <= y ending point if( corner_position != corners_buffer.back() // avoid duplicate corners. && corner_position.x <= arc_start_point.x ) // skip current point at the right of the starting point corners_buffer.push_back( corner_position ); } for( int irect = 0; irect < 2; irect++ ) { aCornerBuffer.NewOutline(); for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint cpos = corners_buffer[ic]; RotatePoint( &cpos, angle ); // Rotate according to module orientation cpos += padShapePos; // Shift origin to position aCornerBuffer.Append( cpos.x, cpos.y ); } angle = AddAngles( angle, 1800 ); // this is calculate hole 3 } // Create holes, that are the mirrored from the previous holes for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint swap = corners_buffer[ic]; swap.x = -swap.x; corners_buffer[ic] = swap; } // Now add corner 4 and 2 (2 is the corner 4 rotated by 180 deg for( int irect = 0; irect < 2; irect++ ) { aCornerBuffer.NewOutline(); for( unsigned ic = 0; ic < corners_buffer.size(); ic++ ) { wxPoint cpos = corners_buffer[ic]; RotatePoint( &cpos, angle ); cpos += padShapePos; aCornerBuffer.Append( cpos.x, cpos.y ); } angle = AddAngles( angle, 1800 ); } } break; case PAD_SHAPE_TRAPEZOID: { SHAPE_POLY_SET antipad; // The full antipad area // We need a length to build the stubs of the thermal reliefs // the value is not very important. The pad bounding box gives a reasonable value EDA_RECT bbox = aPad.GetBoundingBox(); int stub_len = std::max( bbox.GetWidth(), bbox.GetHeight() ); aPad.TransformShapeWithClearanceToPolygon( antipad, aThermalGap ); SHAPE_POLY_SET stub; // A basic stub ( a rectangle) SHAPE_POLY_SET stubs; // the full stubs shape // We now substract the stubs (connections to the copper zone) //ClipperLib::Clipper clip_engine; // Prepare a clipping transform //clip_engine.AddPath( antipad, ClipperLib::ptSubject, true ); // Create stubs and add them to clipper engine wxPoint stubBuffer[4]; stubBuffer[0].x = stub_len; stubBuffer[0].y = copper_thickness.y/2; stubBuffer[1] = stubBuffer[0]; stubBuffer[1].y = -copper_thickness.y/2; stubBuffer[2] = stubBuffer[1]; stubBuffer[2].x = -stub_len; stubBuffer[3] = stubBuffer[2]; stubBuffer[3].y = copper_thickness.y/2; stub.NewOutline(); for( unsigned ii = 0; ii < arrayDim( stubBuffer ); ii++ ) { wxPoint cpos = stubBuffer[ii]; RotatePoint( &cpos, aPad.GetOrientation() ); cpos += padShapePos; stub.Append( cpos.x, cpos.y ); } stubs.Append( stub ); stubBuffer[0].y = stub_len; stubBuffer[0].x = copper_thickness.x/2; stubBuffer[1] = stubBuffer[0]; stubBuffer[1].x = -copper_thickness.x/2; stubBuffer[2] = stubBuffer[1]; stubBuffer[2].y = -stub_len; stubBuffer[3] = stubBuffer[2]; stubBuffer[3].x = copper_thickness.x/2; stub.RemoveAllContours(); stub.NewOutline(); for( unsigned ii = 0; ii < arrayDim( stubBuffer ); ii++ ) { wxPoint cpos = stubBuffer[ii]; RotatePoint( &cpos, aPad.GetOrientation() ); cpos += padShapePos; stub.Append( cpos.x, cpos.y ); } stubs.Append( stub ); stubs.Simplify( SHAPE_POLY_SET::PM_FAST ); antipad.BooleanSubtract( stubs, SHAPE_POLY_SET::PM_FAST ); aCornerBuffer.Append( antipad ); break; } default: ; } }
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; } }
void DRAWSEGMENT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aError, bool ignoreLineWidth ) const { // The full width of the lines to create: int linewidth = ignoreLineWidth ? 0 : m_Width; linewidth += 2 * aClearanceValue; // Creating a reliable clearance shape for circles and arcs is not so easy, due to // the error created by segment approximation. // for a circle this is not so hard: create a polygon from a circle slightly bigger: // thickness = linewidth + s_error_max, and radius = initial radius + s_error_max/2 // giving a shape with a suitable internal radius and external radius // For an arc this is more tricky: TODO switch( m_Shape ) { case S_CIRCLE: TransformRingToPolygon( aCornerBuffer, GetCenter(), GetRadius(), aError, linewidth ); break; case S_ARC: TransformArcToPolygon( aCornerBuffer, GetCenter(), GetArcStart(), m_Angle, aError, linewidth ); break; case S_SEGMENT: TransformOvalClearanceToPolygon( aCornerBuffer, m_Start, m_End, linewidth, aError ); break; case S_POLYGON: if( IsPolyShapeValid() ) { // The polygon is expected to be a simple polygon // not self intersecting, no hole. MODULE* module = GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; wxPoint offset; if( module ) offset = module->GetPosition(); // Build the polygon with the actual position and orientation: std::vector< wxPoint> poly; poly = BuildPolyPointsList(); for( unsigned ii = 0; ii < poly.size(); ii++ ) { RotatePoint( &poly[ii], orientation ); poly[ii] += offset; } // If the polygon is not filled, treat it as a closed set of lines if( !IsPolygonFilled() ) { for( size_t ii = 1; ii < poly.size(); ii++ ) { TransformOvalClearanceToPolygon( aCornerBuffer, poly[ii - 1], poly[ii], linewidth, aError ); } TransformOvalClearanceToPolygon( aCornerBuffer, poly.back(), poly.front(), linewidth, aError ); break; } // Generate polygons for the outline + clearance // This code is compatible with a polygon with holes linked to external outline // by overlapping segments. // Insert the initial polygon: aCornerBuffer.NewOutline(); for( unsigned ii = 0; ii < poly.size(); ii++ ) aCornerBuffer.Append( poly[ii].x, poly[ii].y ); if( linewidth ) // Add thick outlines { wxPoint corner1( poly[poly.size()-1] ); for( unsigned ii = 0; ii < poly.size(); ii++ ) { wxPoint corner2( poly[ii] ); if( corner2 != corner1 ) { TransformRoundedEndsSegmentToPolygon( aCornerBuffer, corner1, corner2, aError, linewidth ); } corner1 = corner2; } } } break; case S_CURVE: // Bezier curve { std::vector<wxPoint> ctrlPoints = { m_Start, m_BezierC1, m_BezierC2, m_End }; BEZIER_POLY converter( ctrlPoints ); std::vector< wxPoint> poly; converter.GetPoly( poly, m_Width ); for( unsigned ii = 1; ii < poly.size(); ii++ ) { TransformRoundedEndsSegmentToPolygon( aCornerBuffer, poly[ii - 1], poly[ii], aError, linewidth ); } } break; default: break; } }
void TransformOvalClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aEnd, int aWidth, int aCircleToSegmentsCount, double aCorrectionFactor ) { // To build the polygonal shape outside the actual shape, we use a bigger // radius to build rounded ends. // However, the width of the segment is too big. // so, later, we will clamp the polygonal shape with the bounding box // of the segment. int radius = aWidth / 2; // Note if we want to compensate the radius reduction of a circle due to // the segment approx, aCorrectionFactor must be calculated like this: // For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) // aCorrectionFactor is 1 /cos( PI/s_CircleToSegmentsCount ) radius = radius * aCorrectionFactor; // make segments outside the circles // end point is the coordinate relative to aStart wxPoint endp = aEnd - aStart; wxPoint startp = aStart; wxPoint corner; SHAPE_POLY_SET polyshape; polyshape.NewOutline(); // normalize the position in order to have endp.x >= 0 // it makes calculations more easy to understand if( endp.x < 0 ) { endp = aStart - aEnd; startp = aEnd; } // delta_angle is in radian double delta_angle = atan2( (double)endp.y, (double)endp.x ); int seg_len = KiROUND( EuclideanNorm( endp ) ); double delta = 3600.0 / aCircleToSegmentsCount; // rot angle in 0.1 degree // Compute the outlines of the segment, and creates a polygon // Note: the polygonal shape is built from the equivalent horizontal // segment starting ar 0,0, and ending at seg_len,0 // add right rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, radius ); RotatePoint( &corner, delta*ii ); corner.x += seg_len; polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( seg_len, -radius ); polyshape.Append( corner.x, corner.y ); // add left rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, -radius ); RotatePoint( &corner, delta*ii ); polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( 0, radius ); polyshape.Append( corner.x, corner.y ); // Now, clamp the polygonal shape (too big) with the segment bounding box // the polygonal shape bbox equivalent to the segment has a too big height, // and the right width if( aCorrectionFactor > 1.0 ) { SHAPE_POLY_SET bbox; bbox.NewOutline(); // Build the bbox (a horizontal rectangle). int halfwidth = aWidth / 2; // Use the exact segment width for the bbox height corner.x = -radius - 2; // use a bbox width slightly bigger to avoid // creating useless corner at segment ends corner.y = halfwidth; bbox.Append( corner.x, corner.y ); corner.y = -halfwidth; bbox.Append( corner.x, corner.y ); corner.x = radius + seg_len + 2; bbox.Append( corner.x, corner.y ); corner.y = halfwidth; bbox.Append( corner.x, corner.y ); // Now, clamp the shape polyshape.BooleanIntersection( bbox, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // Note the final polygon is a simple, convex polygon with no hole // due to the shape of initial polygons } // Rotate and move the polygon to its right location polyshape.Rotate( delta_angle, VECTOR2I( 0, 0 ) ); polyshape.Move( startp ); aCornerBuffer.Append( polyshape); }
void ZONE_FILLER::buildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer, const ZONE_CONTAINER* aZone, const SHAPE_POLY_SET& aRawFilledArea, double aArcCorrection, double aRoundPadThermalRotation ) const { SHAPE_LINE_CHAIN spokes; BOX2I itemBB; VECTOR2I ptTest[4]; auto zoneBB = aRawFilledArea.BBox(); int zone_clearance = aZone->GetZoneClearance(); int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue(); biggest_clearance = std::max( biggest_clearance, zone_clearance ); zoneBB.Inflate( biggest_clearance ); // half size of the pen used to draw/plot zones outlines int pen_radius = aZone->GetMinThickness() / 2; 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; // Calculate thermal bridge half width int thermalBridgeWidth = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness(); if( thermalBridgeWidth <= 0 ) continue; // we need the thermal bridge half width // with a small extra size to be sure we create a stub // slightly larger than the actual stub thermalBridgeWidth = ( thermalBridgeWidth + 4 ) / 2; int thermalReliefGap = aZone->GetThermalReliefGap( pad ); itemBB = pad->GetBoundingBox(); itemBB.Inflate( thermalReliefGap ); if( !( itemBB.Intersects( zoneBB ) ) ) continue; // Thermal bridges are like a segment from a starting point inside the pad // to an ending point outside the pad // calculate the ending point of the thermal pad, outside the pad VECTOR2I endpoint; endpoint.x = ( pad->GetSize().x / 2 ) + thermalReliefGap; endpoint.y = ( pad->GetSize().y / 2 ) + thermalReliefGap; // Calculate the starting point of the thermal stub // inside the pad VECTOR2I startpoint; int copperThickness = aZone->GetThermalReliefCopperBridge( pad ) - aZone->GetMinThickness(); if( copperThickness < 0 ) copperThickness = 0; // Leave a small extra size to the copper area inside to pad copperThickness += KiROUND( IU_PER_MM * 0.04 ); startpoint.x = std::min( pad->GetSize().x, copperThickness ); startpoint.y = std::min( pad->GetSize().y, copperThickness ); startpoint.x /= 2; startpoint.y /= 2; // This is a CIRCLE pad tweak // for circle pads, the thermal stubs orientation is 45 deg double fAngle = pad->GetOrientation(); if( pad->GetShape() == PAD_SHAPE_CIRCLE ) { endpoint.x = KiROUND( endpoint.x * aArcCorrection ); endpoint.y = endpoint.x; fAngle = aRoundPadThermalRotation; } // contour line width has to be taken into calculation to avoid "thermal stub bleed" endpoint.x += pen_radius; endpoint.y += pen_radius; // compute north, south, west and east points for zone connection. ptTest[0] = VECTOR2I( 0, endpoint.y ); // lower point ptTest[1] = VECTOR2I( 0, -endpoint.y ); // upper point ptTest[2] = VECTOR2I( endpoint.x, 0 ); // right point ptTest[3] = VECTOR2I( -endpoint.x, 0 ); // left point // Test all sides for( int i = 0; i < 4; i++ ) { // rotate point RotatePoint( ptTest[i], fAngle ); // translate point ptTest[i] += pad->ShapePos(); if( aRawFilledArea.Contains( ptTest[i] ) ) continue; spokes.Clear(); // polygons are rectangles with width of copper bridge value switch( i ) { case 0: // lower stub spokes.Append( -thermalBridgeWidth, endpoint.y ); spokes.Append( +thermalBridgeWidth, endpoint.y ); spokes.Append( +thermalBridgeWidth, startpoint.y ); spokes.Append( -thermalBridgeWidth, startpoint.y ); break; case 1: // upper stub spokes.Append( -thermalBridgeWidth, -endpoint.y ); spokes.Append( +thermalBridgeWidth, -endpoint.y ); spokes.Append( +thermalBridgeWidth, -startpoint.y ); spokes.Append( -thermalBridgeWidth, -startpoint.y ); break; case 2: // right stub spokes.Append( endpoint.x, -thermalBridgeWidth ); spokes.Append( endpoint.x, thermalBridgeWidth ); spokes.Append( +startpoint.x, thermalBridgeWidth ); spokes.Append( +startpoint.x, -thermalBridgeWidth ); break; case 3: // left stub spokes.Append( -endpoint.x, -thermalBridgeWidth ); spokes.Append( -endpoint.x, thermalBridgeWidth ); spokes.Append( -startpoint.x, thermalBridgeWidth ); spokes.Append( -startpoint.x, -thermalBridgeWidth ); break; } aCornerBuffer.NewOutline(); // add computed polygon to list for( int ic = 0; ic < spokes.PointCount(); ic++ ) { auto cpos = spokes.CPoint( ic ); RotatePoint( cpos, fAngle ); // Rotate according to module orientation cpos += pad->ShapePos(); // Shift origin to position aCornerBuffer.Append( cpos ); } } } } }
/** * Function TransformShapeWithClearanceToPolygon * Convert the track shape to a closed polygon * Used in filling zones calculations * Circles and arcs are approximated by segments * @param aCornerBuffer = a buffer to store the polygon * @param aClearanceValue = the clearance around the pad * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aCorrectionFactor = the correction to apply to circles radius to keep * clearance when the circle is approxiamted by segment bigger or equal * to the real clearance value (usually near from 1.0) */ void DRAWSEGMENT::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aClearanceValue, int aCircleToSegmentsCount, double aCorrectionFactor ) const { // The full width of the lines to create: int linewidth = m_Width + (2 * aClearanceValue); switch( m_Shape ) { case S_CIRCLE: TransformRingToPolygon( aCornerBuffer, GetCenter(), GetRadius(), aCircleToSegmentsCount, linewidth ) ; break; case S_ARC: TransformArcToPolygon( aCornerBuffer, GetCenter(), GetArcStart(), m_Angle, aCircleToSegmentsCount, linewidth ); break; case S_SEGMENT: TransformRoundedEndsSegmentToPolygon( aCornerBuffer, m_Start, m_End, aCircleToSegmentsCount, linewidth ); break; case S_POLYGON: if ( GetPolyPoints().size() < 2 ) break; // Malformed polygon. { // The polygon is expected to be a simple polygon // not self intersecting, no hole. MODULE* module = GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; // Build the polygon with the actual position and orientation: std::vector< wxPoint> poly; poly = GetPolyPoints(); for( unsigned ii = 0; ii < poly.size(); ii++ ) { RotatePoint( &poly[ii], orientation ); poly[ii] += GetPosition(); } // Generate polygons for the outline + clearance // This code is compatible with a polygon with holes linked to external outline // by overlapping segments. // Insert the initial polygon: aCornerBuffer.NewOutline(); for( unsigned ii = 0; ii < poly.size(); ii++ ) aCornerBuffer.Append( poly[ii].x, poly[ii].y ); if( linewidth ) // Add thick outlines { CPolyPt corner1( poly[poly.size()-1] ); for( unsigned ii = 0; ii < poly.size(); ii++ ) { CPolyPt corner2( poly[ii] ); if( corner2 != corner1 ) { TransformRoundedEndsSegmentToPolygon( aCornerBuffer, corner1, corner2, aCircleToSegmentsCount, linewidth ); } corner1 = corner2; } } } break; case S_CURVE: // Bezier curve (TODO: not yet in use) break; default: break; } }
void EDA_3D_CANVAS::buildBoard3DView( GLuint aBoardList, GLuint aBodyOnlyList, REPORTER* aErrorMessages, REPORTER* aActivity ) { BOARD* pcb = GetBoard(); // If FL_RENDER_SHOW_HOLES_IN_ZONES is true, holes are correctly removed from copper zones areas. // If FL_RENDER_SHOW_HOLES_IN_ZONES is false, holes are not removed from copper zones areas, // but the calculation time is twice shorter. bool remove_Holes = isEnabled( FL_RENDER_SHOW_HOLES_IN_ZONES ); bool realistic_mode = isRealisticMode(); bool useTextures = isRealisticMode() && isEnabled( FL_RENDER_TEXTURES ); // Number of segments to convert a circle to polygon // We use 2 values: the first gives a good shape (for instanes rond pads) // the second is used to speed up calculations, when a poor approximation is acceptable (holes) const int segcountforcircle = 18; double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2.0) ); const int segcountLowQuality = 12; // segments to draw a circle with low quality // to reduce time calculations // for holes and items which do not need // a fine representation double correctionFactorLQ = 1.0 / cos( M_PI / (segcountLowQuality * 2.0) ); SHAPE_POLY_SET bufferPolys; // copper areas: tracks, pads and filled zones areas // when holes are removed from zones SHAPE_POLY_SET bufferPcbOutlines; // stores the board main outlines SHAPE_POLY_SET bufferZonesPolys; // copper filled zones areas // when holes are not removed from zones SHAPE_POLY_SET currLayerHoles; // Contains holes for the current layer SHAPE_POLY_SET allLayerHoles; // Contains holes for all layers // Build a polygon from edge cut items wxString msg; if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) ) { if( aErrorMessages ) { msg << wxT("\n") << _("Unable to calculate the board outlines.\n" "Therefore use the board boundary box.") << wxT("\n\n"); aErrorMessages->Report( msg, REPORTER::RPT_WARNING ); } } // Build board holes, with optimization of large holes shape. buildBoardThroughHolesPolygonList( allLayerHoles, segcountLowQuality, true ); LSET cu_set = LSET::AllCuMask( GetPrm3DVisu().m_CopperLayersCount ); glNewList( aBoardList, GL_COMPILE ); for( LSEQ cu = cu_set.CuStack(); cu; ++cu ) { LAYER_ID layer = *cu; // Skip non enabled layers in normal mode, // and internal layers in realistic mode if( !is3DLayerEnabled( layer ) ) continue; if( aActivity ) aActivity->Report( wxString::Format( _( "Build layer %s" ), LSET::Name( layer ) ) ); bufferPolys.RemoveAllContours(); bufferZonesPolys.RemoveAllContours(); currLayerHoles.RemoveAllContours(); // Draw track shapes: for( TRACK* track = pcb->m_Track; track; track = track->Next() ) { if( !track->IsOnLayer( layer ) ) continue; track->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); // Add blind/buried via holes if( track->Type() == PCB_VIA_T ) { VIA *via = static_cast<VIA*>( track ); if( via->GetViaType() == VIA_THROUGH ) continue; // already done int holediameter = via->GetDrillValue(); int thickness = GetPrm3DVisu().GetCopperThicknessBIU(); int hole_outer_radius = (holediameter + thickness) / 2; TransformCircleToPolygon( currLayerHoles, via->GetStart(), hole_outer_radius, segcountLowQuality ); } } // draw pad shapes for( MODULE* module = pcb->m_Modules; module; module = module->Next() ) { // Note: NPTH pads are not drawn on copper layers when the pad // has same shape as its hole module->TransformPadsShapesWithClearanceToPolygon( layer, bufferPolys, 0, segcountforcircle, correctionFactor, true ); // Micro-wave modules may have items on copper layers module->TransformGraphicShapesWithClearanceToPolygonSet( layer, bufferPolys, 0, segcountforcircle, correctionFactor ); // pad holes are already in list. } // Draw copper zones. Note: // * if the holes are removed from copper zones // the polygons are stored in bufferPolys (which contains all other polygons) // * if the holes are NOT removed from copper zones // the polygons are stored in bufferZonesPolys if( isEnabled( FL_ZONE ) ) { for( int ii = 0; ii < pcb->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = pcb->GetArea( ii ); LAYER_NUM zonelayer = zone->GetLayer(); if( zonelayer == layer ) { zone->TransformSolidAreasShapesToPolygonSet( remove_Holes ? bufferPolys : bufferZonesPolys, segcountLowQuality, correctionFactorLQ ); } } } // draw graphic items on copper layers (texts) for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() ) { if( !item->IsOnLayer( layer ) ) continue; switch( item->Type() ) { case PCB_LINE_T: // should not exist on copper layers ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); break; case PCB_TEXT_T: ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet( bufferPolys, 0, segcountLowQuality, correctionFactor ); break; default: break; } } // bufferPolys contains polygons to merge. Many overlaps . // Calculate merged polygons if( bufferPolys.IsEmpty() ) continue; // Use Clipper lib to subtract holes to copper areas if( currLayerHoles.OutlineCount() ) { currLayerHoles.Append(allLayerHoles); currLayerHoles.Simplify(); bufferPolys.BooleanSubtract( currLayerHoles ); } else bufferPolys.BooleanSubtract( allLayerHoles ); int thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer ); int zpos = GetPrm3DVisu().GetLayerZcoordBIU( layer ); float zNormal = 1.0f; // When using thickness it will draw first the top and then botton (with z inverted) // If we are not using thickness, then the z-normal has to match the layer direction // because just one plane will be drawn if( !thickness ) zNormal = Get3DLayer_Z_Orientation( layer ); if( realistic_mode ) { setGLCopperColor(); } else { EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( layer ); SetGLColor( color ); } // If holes are removed from copper zones, bufferPolys contains all polygons // to draw (tracks+zones+texts). Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, zNormal ); // If holes are not removed from copper zones (for calculation time reasons, // the zone polygons are stored in bufferZonesPolys and have to be drawn now: if( !bufferZonesPolys.IsEmpty() ) { Draw3D_SolidHorizontalPolyPolygons( bufferZonesPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, zNormal ); } } if( aActivity ) aActivity->Report( _( "Build board body" ) ); // Draw plated vertical holes inside the board, but not always. They are drawn: // - if the board body is not shown, to show the holes. // - or if the copper thickness is shown if( !isEnabled( FL_SHOW_BOARD_BODY ) || isEnabled( FL_USE_COPPER_THICKNESS ) ) { // Draw vias holes (vertical cylinders) for( const TRACK* track = pcb->m_Track; track; track = track->Next() ) { if( track->Type() == PCB_VIA_T ) { const VIA *via = static_cast<const VIA*>(track); draw3DViaHole( via ); } } // Draw pads holes (vertical cylinders) for( const MODULE* module = pcb->m_Modules; module; module = module->Next() ) { for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() ) if( pad->GetAttribute () != PAD_HOLE_NOT_PLATED ) draw3DPadHole( pad ); } } glEndList(); // Build the body board: glNewList( aBodyOnlyList, GL_COMPILE ); if( isRealisticMode() ) { setGLEpoxyColor( 1.00 ); } else { EDA_COLOR_T color = g_ColorsSettings.GetLayerColor( Edge_Cuts ); SetGLColor( color, 0.7 ); } float copper_thickness = GetPrm3DVisu().GetCopperThicknessBIU(); // a small offset between substrate and external copper layer to avoid artifacts // when drawing copper items on board float epsilon = Millimeter2iu( 0.01 ); float zpos = GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ); float board_thickness = GetPrm3DVisu().GetLayerZcoordBIU( F_Cu ) - GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ); // items on copper layers and having a thickness = copper_thickness // are drawn from zpos - copper_thickness/2 to zpos + copper_thickness // therefore substrate position is copper_thickness/2 to // substrate_height - copper_thickness/2 zpos += (copper_thickness + epsilon) / 2.0f; board_thickness -= copper_thickness + epsilon; bufferPcbOutlines.BooleanSubtract( allLayerHoles ); if( !bufferPcbOutlines.IsEmpty() ) { Draw3D_SolidHorizontalPolyPolygons( bufferPcbOutlines, zpos + board_thickness / 2.0, board_thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, 1.0f ); } glEndList(); }
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 TransformRoundedEndsSegmentToPolygon * convert a segment with rounded ends to a polygon * Convert arcs to multiple straight lines * @param aCornerBuffer = a buffer to store the polygon * @param aStart = the segment start point coordinate * @param aEnd = the segment end point coordinate * @param aCircleToSegmentsCount = the number of segments to approximate a circle * @param aWidth = the segment width * Note: the polygon is inside the arc ends, so if you want to have the polygon * outside the circle, you should give aStart and aEnd calculated with a correction factor */ void TransformRoundedEndsSegmentToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aEnd, int aCircleToSegmentsCount, int aWidth ) { int radius = aWidth / 2; wxPoint endp = aEnd - aStart; // end point coordinate for the same segment starting at (0,0) wxPoint startp = aStart; wxPoint corner; VECTOR2I polypoint; aCornerBuffer.NewOutline(); // normalize the position in order to have endp.x >= 0; if( endp.x < 0 ) { endp = aStart - aEnd; startp = aEnd; } double delta_angle = ArcTangente( endp.y, endp.x ); // delta_angle is in 0.1 degrees int seg_len = KiROUND( EuclideanNorm( endp ) ); int delta = 3600 / aCircleToSegmentsCount; // rot angle in 0.1 degree // Compute the outlines of the segment, and creates a polygon // add right rounded end: for( int ii = 0; ii < 1800; ii += delta ) { corner = wxPoint( 0, radius ); RotatePoint( &corner, ii ); corner.x += seg_len; RotatePoint( &corner, -delta_angle ); corner += startp; polypoint.x = corner.x; polypoint.y = corner.y; aCornerBuffer.Append( polypoint.x, polypoint.y ); } // Finish arc: corner = wxPoint( seg_len, -radius ); RotatePoint( &corner, -delta_angle ); corner += startp; polypoint.x = corner.x; polypoint.y = corner.y; aCornerBuffer.Append( polypoint.x, polypoint.y ); // add left rounded end: for( int ii = 0; ii < 1800; ii += delta ) { corner = wxPoint( 0, -radius ); RotatePoint( &corner, ii ); RotatePoint( &corner, -delta_angle ); corner += startp; polypoint.x = corner.x; polypoint.y = corner.y; aCornerBuffer.Append( polypoint.x, polypoint.y ); } // Finish arc: corner = wxPoint( 0, radius ); RotatePoint( &corner, -delta_angle ); corner += startp; polypoint.x = corner.x; polypoint.y = corner.y; aCornerBuffer.Append( polypoint.x, polypoint.y ); }
bool DRC_COURTYARD_OVERLAP::RunDRC( BOARD& aBoard ) const { wxLogTrace( DRC_COURTYARD_TRACE, "Running DRC: Courtyard" ); // Detects missing (or malformed) footprint courtyard, // and for footprint with courtyard, courtyards overlap. wxString msg; bool success = true; const DRC_MARKER_FACTORY& marker_factory = GetMarkerFactory(); // Update courtyard polygons, and test for missing courtyard definition: for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { wxPoint pos = footprint->GetPosition(); bool is_ok = footprint->BuildPolyCourtyard(); if( !is_ok && aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) { auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( pos, footprint, DRCE_MALFORMED_COURTYARD_IN_FOOTPRINT ) ); HandleMarker( std::move( marker ) ); success = false; } if( !aBoard.GetDesignSettings().m_RequireCourtyards ) continue; if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 && footprint->GetPolyCourtyardBack().OutlineCount() == 0 && is_ok ) { auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( pos, footprint, DRCE_MISSING_COURTYARD_IN_FOOTPRINT ) ); HandleMarker( std::move( marker ) ); success = false; } } if( !aBoard.GetDesignSettings().m_ProhibitOverlappingCourtyards ) return success; wxLogTrace( DRC_COURTYARD_TRACE, "Checking for courtyard overlap" ); // Now test for overlapping on top layer: SHAPE_POLY_SET courtyard; // temporary storage of the courtyard of current footprint for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 ) continue; // No courtyard defined for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) { if( candidate->GetPolyCourtyardFront().OutlineCount() == 0 ) continue; // No courtyard defined courtyard.RemoveAllContours(); courtyard.Append( footprint->GetPolyCourtyardFront() ); // Build the common area between footprint and the candidate: courtyard.BooleanIntersection( candidate->GetPolyCourtyardFront(), SHAPE_POLY_SET::PM_FAST ); // If no overlap, courtyard is empty (no common area). // Therefore if a common polygon exists, this is a DRC error if( courtyard.OutlineCount() ) { //Overlap between footprint and candidate VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); HandleMarker( std::move( marker ) ); success = false; } } } // Test for overlapping on bottom layer: for( MODULE* footprint = aBoard.m_Modules; footprint; footprint = footprint->Next() ) { if( footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) continue; // No courtyard defined for( MODULE* candidate = footprint->Next(); candidate; candidate = candidate->Next() ) { if( candidate->GetPolyCourtyardBack().OutlineCount() == 0 ) continue; // No courtyard defined courtyard.RemoveAllContours(); courtyard.Append( footprint->GetPolyCourtyardBack() ); // Build the common area between footprint and the candidate: courtyard.BooleanIntersection( candidate->GetPolyCourtyardBack(), SHAPE_POLY_SET::PM_FAST ); // If no overlap, courtyard is empty (no common area). // Therefore if a common polygon exists, this is a DRC error if( courtyard.OutlineCount() ) { //Overlap between footprint and candidate VECTOR2I& pos = courtyard.Vertex( 0, 0, -1 ); auto marker = std::unique_ptr<MARKER_PCB>( marker_factory.NewMarker( wxPoint( pos.x, pos.y ), footprint, candidate, DRCE_OVERLAPPING_FOOTPRINTS ) ); HandleMarker( std::move( marker ) ); success = false; } } } return success; }
/** * 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(); }
/* 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; } }
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 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<wxPoint> 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<wxPoint> 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 ); } } } }
void EDA_3D_CANVAS::buildTechLayers3DView( REPORTER* aErrorMessages, REPORTER* aActivity ) { BOARD* pcb = GetBoard(); bool useTextures = isRealisticMode() && isEnabled( FL_RENDER_TEXTURES ); // Number of segments to draw a circle using segments const int segcountforcircle = 18; double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); const int segcountLowQuality = 12; // segments to draw a circle with low quality // to reduce time calculations // for holes and items which do not need // a fine representation double correctionFactorLQ = 1.0 / cos( M_PI / (segcountLowQuality * 2) ); // segments to draw a circle to build texts. Is is used only to build // the shape of each segment of the stroke font, therefore no need to have // many segments per circle. const int segcountInStrokeFont = 8; SHAPE_POLY_SET bufferPolys; SHAPE_POLY_SET allLayerHoles; // Contains through holes, calculated only once SHAPE_POLY_SET bufferPcbOutlines; // stores the board main outlines // Build a polygon from edge cut items wxString msg; if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) ) { if( aErrorMessages ) { msg << wxT("\n") << _("Unable to calculate the board outlines.\n" "Therefore use the board boundary box.") << wxT("\n\n"); aErrorMessages->Report( msg, REPORTER::RPT_WARNING ); } } // Build board holes, with no optimization of large holes shape. buildBoardThroughHolesPolygonList( allLayerHoles, segcountLowQuality, false ); // draw graphic items, on technical layers static const LAYER_ID teckLayerList[] = { B_Adhes, F_Adhes, B_Paste, F_Paste, B_SilkS, F_SilkS, B_Mask, F_Mask, }; // User layers are not drawn here, only technical layers for( LSEQ seq = LSET::AllTechMask().Seq( teckLayerList, DIM( teckLayerList ) ); seq; ++seq ) { LAYER_ID layer = *seq; if( !is3DLayerEnabled( layer ) ) continue; if( layer == Edge_Cuts && isEnabled( FL_SHOW_BOARD_BODY ) ) continue; if( aActivity ) aActivity->Report( wxString::Format( _( "Build layer %s" ), LSET::Name( layer ) ) ); bufferPolys.RemoveAllContours(); for( BOARD_ITEM* item = pcb->m_Drawings; item; item = item->Next() ) { if( !item->IsOnLayer( layer ) ) continue; switch( item->Type() ) { case PCB_LINE_T: ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( bufferPolys, 0, segcountforcircle, correctionFactor ); break; case PCB_TEXT_T: ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet( bufferPolys, 0, segcountLowQuality, 1.0 ); break; default: break; } } for( MODULE* module = pcb->m_Modules; module; module = module->Next() ) { if( layer == F_SilkS || layer == B_SilkS ) { // On silk screen layers, the pad shape is only the pad outline // never a filled shape D_PAD* pad = module->Pads(); int linewidth = g_DrawDefaultLineThickness; for( ; pad; pad = pad->Next() ) { if( !pad->IsOnLayer( layer ) ) continue; buildPadShapeThickOutlineAsPolygon( pad, bufferPolys, linewidth, segcountforcircle, correctionFactor ); } } else module->TransformPadsShapesWithClearanceToPolygon( layer, bufferPolys, 0, segcountforcircle, correctionFactor ); // On tech layers, use a poor circle approximation, only for texts (stroke font) module->TransformGraphicShapesWithClearanceToPolygonSet( layer, bufferPolys, 0, segcountforcircle, correctionFactor, segcountInStrokeFont ); } // Draw non copper zones if( isEnabled( FL_ZONE ) ) { for( int ii = 0; ii < pcb->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = pcb->GetArea( ii ); if( !zone->IsOnLayer( layer ) ) continue; zone->TransformSolidAreasShapesToPolygonSet( bufferPolys, segcountLowQuality, correctionFactorLQ ); } } // bufferPolys contains polygons to merge. Many overlaps . // Calculate merged polygons and remove pads and vias holes if( bufferPolys.IsEmpty() ) continue; // Solder mask layers are "negative" layers. // Shapes should be removed from the full board area. if( layer == B_Mask || layer == F_Mask ) { SHAPE_POLY_SET cuts = bufferPolys; bufferPolys = bufferPcbOutlines; cuts.Append(allLayerHoles); cuts.Simplify(); bufferPolys.BooleanSubtract( cuts ); } // Remove holes from Solder paste layers and silkscreen else if( layer == B_Paste || layer == F_Paste || layer == B_SilkS || layer == F_SilkS ) { bufferPolys.BooleanSubtract( allLayerHoles ); } int thickness = 0; if( layer != B_Mask && layer != F_Mask ) thickness = GetPrm3DVisu().GetLayerObjectThicknessBIU( layer ); int zpos = GetPrm3DVisu().GetLayerZcoordBIU( layer ); if( layer == Edge_Cuts ) { thickness = GetPrm3DVisu().GetLayerZcoordBIU( F_Cu ) - GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ); zpos = GetPrm3DVisu().GetLayerZcoordBIU( B_Cu ) + (thickness / 2); } else { // for Draw3D_SolidHorizontalPolyPolygons, zpos it the middle between bottom and top // sides. // However for top layers, zpos should be the bottom layer pos, // and for bottom layers, zpos should be the top layer pos. if( Get3DLayer_Z_Orientation( layer ) > 0 ) zpos += thickness/2; else zpos -= thickness/2 ; } float zNormal = 1.0f; // When using thickness it will draw first the top and then botton (with z inverted) // If we are not using thickness, then the znormal must face the layer direction // because it will draw just one plane if( !thickness ) zNormal = Get3DLayer_Z_Orientation( layer ); setGLTechLayersColor( layer ); Draw3D_SolidHorizontalPolyPolygons( bufferPolys, zpos, thickness, GetPrm3DVisu().m_BiuTo3Dunits, useTextures, zNormal ); } }
/** * Function ConvertOutlineToPolygon * build a polygon (with holes) from a DRAWSEGMENT list, which is expected to be * a outline, therefore a closed main outline with perhaps closed inner outlines. * These closed inner outlines are considered as holes in the main outline * @param aSegList the initial list of drawsegments (only lines, circles and arcs). * @param aPolygons will contain the complex polygon. * @param aTolerance is the max distance between points that is still accepted as connected (internal units) * @param aErrorText is a wxString to return error message. * @param aErrorLocation is the optional position of the error in the outline */ bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons, wxString* aErrorText, unsigned int aTolerance, wxPoint* aErrorLocation ) { if( aSegList.size() == 0 ) return true; wxString msg; // Make a working copy of aSegList, because the list is modified during calculations std::vector< DRAWSEGMENT* > segList = aSegList; DRAWSEGMENT* graphic; wxPoint prevPt; // Find edge point with minimum x, this should be in the outer polygon // which will define the perimeter Edge.Cuts polygon. wxPoint xmin = wxPoint( INT_MAX, 0 ); int xmini = 0; for( size_t i = 0; i < segList.size(); i++ ) { graphic = (DRAWSEGMENT*) segList[i]; switch( graphic->GetShape() ) { case S_SEGMENT: { if( graphic->GetStart().x < xmin.x ) { xmin = graphic->GetStart(); xmini = i; } if( graphic->GetEnd().x < xmin.x ) { xmin = graphic->GetEnd(); xmini = i; } } break; case S_ARC: // Freerouter does not yet understand arcs, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint center = graphic->GetCenter(); double angle = -graphic->GetAngle(); double radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); wxPoint pt; for( int step = 1; step<=steps; ++step ) { double rotation = ( angle * step ) / steps; pt = pstart; RotatePoint( &pt, center, rotation ); if( pt.x < xmin.x ) { xmin = pt; xmini = i; } } } break; case S_CIRCLE: { wxPoint pt = graphic->GetCenter(); // pt has minimum x point pt.x -= graphic->GetRadius(); // when the radius <= 0, this is a mal-formed circle. Skip it if( graphic->GetRadius() > 0 && pt.x < xmin.x ) { xmin = pt; xmini = i; } } break; case S_CURVE: { graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() ); for( unsigned int jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) { wxPoint pt = graphic->GetBezierPoints()[jj]; if( pt.x < xmin.x ) { xmin = pt; xmini = i; } } } break; case S_POLYGON: { const auto poly = graphic->GetPolyShape(); MODULE* module = aSegList[0]->GetParentModule(); double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); for( auto iter = poly.CIterate(); iter; iter++ ) { auto pt = *iter; RotatePoint( pt, orientation ); pt += offset; if( pt.x < xmin.x ) { xmin.x = pt.x; xmin.y = pt.y; xmini = i; } } } break; default: break; } } // Grab the left most point, assume its on the board's perimeter, and see if we // can put enough graphics together by matching endpoints to formulate a cohesive // polygon. graphic = (DRAWSEGMENT*) segList[xmini]; // The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items' segList.erase( segList.begin() + xmini ); // Output the Edge.Cuts perimeter as circle or polygon. if( graphic->GetShape() == S_CIRCLE ) { int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_LOW_DEF, 360.0 ); TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), steps ); } else if( graphic->GetShape() == S_POLYGON ) { MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); aPolygons.NewOutline(); for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ ) { auto pt = *it; RotatePoint( pt, orientation ); pt += offset; aPolygons.Append( pt ); } } else { // Polygon start point. Arbitrarily chosen end of the // segment and build the poly from here. wxPoint startPt = wxPoint( graphic->GetEnd() ); prevPt = graphic->GetEnd(); aPolygons.NewOutline(); aPolygons.Append( prevPt ); // Do not append the other end point yet of this 'graphic', this first // 'graphic' might be an arc or a curve. for(;;) { switch( graphic->GetShape() ) { case S_SEGMENT: { wxPoint nextPt; // Use the line segment end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else nextPt = graphic->GetStart(); aPolygons.Append( nextPt ); prevPt = nextPt; } break; case S_ARC: // We do not support arcs in polygons, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); double radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); if( !close_enough( prevPt, pstart, aTolerance ) ) { wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) ); angle = -angle; std::swap( pstart, pend ); } wxPoint nextPt; for( int step = 1; step<=steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = pstart; RotatePoint( &nextPt, pcenter, rotation ); aPolygons.Append( nextPt ); } prevPt = nextPt; } break; case S_CURVE: // We do not support Bezier curves in polygons, so approximate // with a series of short lines and put those // line segments into the !same! PATH. { wxPoint nextPt; bool reverse = false; // Use the end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else { nextPt = graphic->GetStart(); reverse = true; } if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) aPolygons.Append( graphic->GetBezierPoints()[jj] ); } else { for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) aPolygons.Append( graphic->GetBezierPoints()[jj] ); } prevPt = nextPt; } break; default: if( aErrorText ) { msg.Printf( "Unsupported DRAWSEGMENT type %s.", BOARD_ITEM::ShowShape( graphic->GetShape() ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = graphic->GetPosition(); return false; } // Get next closest segment. graphic = findPoint( prevPt, segList, aTolerance ); // If there are no more close segments, check if the board // outline polygon can be closed. if( !graphic ) { if( close_enough( startPt, prevPt, aTolerance ) ) { // Close the polygon back to start point // aPolygons.Append( startPt ); // not needed } else { if( aErrorText ) { msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ), StringFromValue( MILLIMETRES, prevPt.x, true ), StringFromValue( MILLIMETRES, prevPt.y, true ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = prevPt; return false; } break; } } } while( segList.size() ) { // emit a signal layers keepout for every interior polygon left... int hole = aPolygons.NewHole(); graphic = (DRAWSEGMENT*) segList[0]; segList.erase( segList.begin() ); // Both circles and polygons on the edge cuts layer are closed items that // do not connect to other elements, so we process them independently if( graphic->GetShape() == S_POLYGON ) { MODULE* module = graphic->GetParentModule(); // NULL for items not in footprints double orientation = module ? module->GetOrientation() : 0.0; VECTOR2I offset = module ? module->GetPosition() : VECTOR2I( 0, 0 ); for( auto it = graphic->GetPolyShape().CIterate(); it; it++ ) { auto val = *it; RotatePoint( val, orientation ); val += offset; aPolygons.Append( val, -1, hole ); } } else if( graphic->GetShape() == S_CIRCLE ) { // make a circle by segments; wxPoint center = graphic->GetCenter(); double angle = 3600.0; wxPoint start = center; int radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, 360.0 ); wxPoint nextPt; start.x += radius; for( int step = 0; step < steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = start; RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation ); aPolygons.Append( nextPt, -1, hole ); } } else { // Polygon start point. Arbitrarily chosen end of the // segment and build the poly from here. wxPoint startPt( graphic->GetEnd() ); prevPt = graphic->GetEnd(); aPolygons.Append( prevPt, -1, hole ); // do not append the other end point yet, this first 'graphic' might be an arc for(;;) { switch( graphic->GetShape() ) { case S_SEGMENT: { wxPoint nextPt; // Use the line segment end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) { nextPt = graphic->GetEnd(); } else { nextPt = graphic->GetStart(); } prevPt = nextPt; aPolygons.Append( prevPt, -1, hole ); } break; case S_ARC: // Freerouter does not yet understand arcs, so approximate // an arc with a series of short lines and put those // line segments into the !same! PATH. { wxPoint pstart = graphic->GetArcStart(); wxPoint pend = graphic->GetArcEnd(); wxPoint pcenter = graphic->GetCenter(); double angle = -graphic->GetAngle(); int radius = graphic->GetRadius(); int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 ); if( !close_enough( prevPt, pstart, aTolerance ) ) { wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) ); angle = -angle; std::swap( pstart, pend ); } wxPoint nextPt; for( int step = 1; step <= steps; ++step ) { double rotation = ( angle * step ) / steps; nextPt = pstart; RotatePoint( &nextPt, pcenter, rotation ); aPolygons.Append( nextPt, -1, hole ); } prevPt = nextPt; } break; case S_CURVE: // We do not support Bezier curves in polygons, so approximate // with a series of short lines and put those // line segments into the !same! PATH. { wxPoint nextPt; bool reverse = false; // Use the end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) nextPt = graphic->GetEnd(); else { nextPt = graphic->GetStart(); reverse = true; } if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole ); } else { for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole ); } prevPt = nextPt; } break; default: if( aErrorText ) { msg.Printf( "Unsupported DRAWSEGMENT type %s.", BOARD_ITEM::ShowShape( graphic->GetShape() ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = graphic->GetPosition(); return false; } // Get next closest segment. graphic = findPoint( prevPt, segList, aTolerance ); // If there are no more close segments, check if polygon // can be closed. if( !graphic ) { if( close_enough( startPt, prevPt, aTolerance ) ) { // Close the polygon back to start point // aPolygons.Append( startPt, -1, hole ); // not needed } else { if( aErrorText ) { msg.Printf( _( "Unable to find segment with an endpoint of (%s, %s)." ), StringFromValue( MILLIMETRES, prevPt.x, true ), StringFromValue( MILLIMETRES, prevPt.y, true ) ); *aErrorText << msg << "\n"; } if( aErrorLocation ) *aErrorLocation = prevPt; return false; } break; } } } } return true; }