diff --git a/api/proto/common/types/base_types.proto b/api/proto/common/types/base_types.proto index 181d770d5c..c452a45219 100644 --- a/api/proto/common/types/base_types.proto +++ b/api/proto/common/types/base_types.proto @@ -371,6 +371,7 @@ message GraphicRectangleAttributes { kiapi.common.types.Vector2 top_left = 1; kiapi.common.types.Vector2 bottom_right = 2; + kiapi.common.types.Distance corner_radius = 3; } message GraphicArcAttributes diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp index a26184ae99..d7ff03bfd3 100644 --- a/common/eda_shape.cpp +++ b/common/eda_shape.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include // for KiROUND @@ -54,6 +55,7 @@ EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) : m_hatchingDirty( true ), m_rectangleHeight( 0 ), m_rectangleWidth( 0 ), + m_cornerRadius( 0 ), m_segmentLength( 0 ), m_editState( 0 ), m_proxyItem( false ) @@ -73,6 +75,7 @@ EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) : m_hatchingDirty( true ), m_rectangleHeight( 0 ), m_rectangleWidth( 0 ), + m_cornerRadius( 0 ), m_segmentLength( 0 ), m_editState( 0 ), m_proxyItem( false ) @@ -188,6 +191,7 @@ void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle(); PackVector2( *rectangle->mutable_top_left(), GetStart() ); PackVector2( *rectangle->mutable_bottom_right(), GetEnd() ); + rectangle->mutable_corner_radius()->set_value_nm( GetCornerRadius() ); break; } @@ -280,6 +284,7 @@ bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer ) SetShape( SHAPE_T::RECTANGLE ); SetStart( UnpackVector2( shape.rectangle().top_left() ) ); SetEnd( UnpackVector2( shape.rectangle().bottom_right() ) ); + SetCornerRadius( shape.rectangle().corner_radius().value_nm() ); } else if( shape.has_arc() ) { @@ -432,6 +437,16 @@ int EDA_SHAPE::GetRectangleWidth() const } } +int EDA_SHAPE::GetCornerRadius() const +{ + return m_cornerRadius; +} + +void EDA_SHAPE::SetCornerRadius( int aRadius ) +{ + m_cornerRadius = aRadius; +} + void EDA_SHAPE::SetLength( const double& aLength ) { @@ -1800,17 +1815,38 @@ std::vector EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh case SHAPE_T::RECTANGLE: { - std::vector pts = GetRectCorners(); - - if( solidFill ) - effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) ); - - if( width > 0 || !solidFill ) + if( m_cornerRadius > 0 ) { - effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) ); - effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) ); - effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) ); - effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) ); + ROUNDRECT rr( SHAPE_RECT( GetStart(), GetRectangleWidth(), GetRectangleHeight() ), + m_cornerRadius ); + SHAPE_POLY_SET poly; + rr.TransformToPolygon( poly ); + SHAPE_LINE_CHAIN outline = poly.Outline( 0 ); + + if( solidFill ) + effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) ); + + if( width > 0 || !solidFill ) + { + for( int i = 0; i < outline.PointCount() - 1; ++i ) + effectiveShapes.emplace_back( + new SHAPE_SEGMENT( outline.CPoint( i ), outline.CPoint( i + 1 ), width ) ); + } + } + else + { + std::vector pts = GetRectCorners(); + + if( solidFill ) + effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) ); + + if( width > 0 || !solidFill ) + { + effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) ); + effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) ); + effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) ); + effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) ); + } } break; } @@ -2643,6 +2679,12 @@ static struct EDA_SHAPE_DESC shapeProps ) .SetAvailableFunc( isRectangle ); + propMgr.AddProperty( new PROPERTY( _HKI( "Corner Radius" ), + &EDA_SHAPE::SetCornerRadius, &EDA_SHAPE::GetCornerRadius, + PROPERTY_DISPLAY::PT_SIZE, ORIGIN_TRANSFORMS::NOT_A_COORD ), + shapeProps ) + .SetAvailableFunc( isRectangle ); + propMgr.AddProperty( new PROPERTY( _HKI( "Line Width" ), &EDA_SHAPE::SetWidth, &EDA_SHAPE::GetWidth, PROPERTY_DISPLAY::PT_SIZE ), shapeProps ); diff --git a/common/plotters/DXF_plotter.cpp b/common/plotters/DXF_plotter.cpp index 8a22d46f4c..b4c7263083 100644 --- a/common/plotters/DXF_plotter.cpp +++ b/common/plotters/DXF_plotter.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -446,10 +447,21 @@ void DXF_PLOTTER::SetColor( const COLOR4D& color ) } -void DXF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) +void DXF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius ) { wxASSERT( m_outputFile ); + if( aCornerRadius > 0 ) + { + BOX2I box( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) ); + box.Normalize(); + SHAPE_RECT rect( box ); + rect.SetRadius( aCornerRadius ); + PlotPoly( rect.Outline(), fill, width, nullptr ); + return; + } + if( p1 != p2 ) { MoveTo( p1 ); @@ -605,6 +617,22 @@ void DXF_PLOTTER::PlotPoly( const std::vector& aCornerList, FILL_T aFi } +void DXF_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, int aWidth, + void* aData ) +{ + std::vector cornerList; + cornerList.reserve( aCornerList.PointCount() ); + + for( int ii = 0; ii < aCornerList.PointCount(); ii++ ) + cornerList.emplace_back( aCornerList.CPoint( ii ) ); + + if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() ) + cornerList.emplace_back( aCornerList.CPoint( 0 ) ); + + PlotPoly( cornerList, aFill, aWidth, aData ); +} + + void DXF_PLOTTER::PenTo( const VECTOR2I& pos, char plume ) { wxASSERT( m_outputFile ); @@ -735,17 +763,17 @@ void DXF_PLOTTER::ThickRect( const VECTOR2I& p1, const VECTOR2I& p2, int width, { VECTOR2I offsetp1( p1.x - width/2, p1.y - width/2 ); VECTOR2I offsetp2( p2.x + width/2, p2.y + width/2 ); - Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DXF_LINE_WIDTH ); + Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DXF_LINE_WIDTH, 0 ); offsetp1.x += width; offsetp1.y += width; offsetp2.x -= width; offsetp2.y -= width; - Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DXF_LINE_WIDTH ); + Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DXF_LINE_WIDTH, 0 ); } else { - Rect( p1, p2, FILL_T::NO_FILL, DXF_LINE_WIDTH ); + Rect( p1, p2, FILL_T::NO_FILL, DXF_LINE_WIDTH, 0 ); } } diff --git a/common/plotters/GERBER_plotter.cpp b/common/plotters/GERBER_plotter.cpp index db079c0e93..5365432962 100644 --- a/common/plotters/GERBER_plotter.cpp +++ b/common/plotters/GERBER_plotter.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include // for KiROUND #include @@ -846,8 +847,19 @@ void GERBER_PLOTTER::PenTo( const VECTOR2I& aPos, char plume ) } -void GERBER_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) +void GERBER_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius ) { + if( aCornerRadius > 0 ) + { + BOX2I box( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) ); + box.Normalize(); + SHAPE_RECT rect( box ); + rect.SetRadius( aCornerRadius ); + PlotPoly( rect.Outline(), fill, width, nullptr ); + return; + } + std::vector cornerList; cornerList.reserve( 5 ); diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp index 4f5edeee24..4ac04f8534 100644 --- a/common/plotters/PDF_plotter.cpp +++ b/common/plotters/PDF_plotter.cpp @@ -51,6 +51,7 @@ #include #include +#include std::string PDF_PLOTTER::encodeStringForPlotter( const wxString& aText ) @@ -227,7 +228,8 @@ void PDF_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle ) } -void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) +void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius ) { wxASSERT( m_workFile ); @@ -236,6 +238,16 @@ void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int SetCurrentLineWidth( width ); + if( aCornerRadius > 0 ) + { + BOX2I box( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) ); + box.Normalize(); + SHAPE_RECT rect( box ); + rect.SetRadius( aCornerRadius ); + PlotPoly( rect.Outline(), fill, width, nullptr ); + return; + } + VECTOR2I size = p2 - p1; if( size.x == 0 && size.y == 0 ) @@ -429,6 +441,22 @@ void PDF_PLOTTER::PlotPoly( const std::vector& aCornerList, FILL_T aFi } +void PDF_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, int aWidth, + void* aData ) +{ + std::vector cornerList; + cornerList.reserve( aCornerList.PointCount() ); + + for( int ii = 0; ii < aCornerList.PointCount(); ii++ ) + cornerList.emplace_back( aCornerList.CPoint( ii ) ); + + if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() ) + cornerList.emplace_back( aCornerList.CPoint( 0 ) ); + + PlotPoly( cornerList, aFill, aWidth, aData ); +} + + void PDF_PLOTTER::PenTo( const VECTOR2I& pos, char plume ) { wxASSERT( m_workFile ); diff --git a/common/plotters/PS_plotter.cpp b/common/plotters/PS_plotter.cpp index eb557d728b..a02dceef74 100644 --- a/common/plotters/PS_plotter.cpp +++ b/common/plotters/PS_plotter.cpp @@ -30,6 +30,7 @@ #include #include #include // for KiROUND +#include #include #include #include @@ -457,13 +458,24 @@ void PS_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle ) } -void PS_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) +void PS_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius ) { SetCurrentLineWidth( width ); if( fill == FILL_T::NO_FILL && GetCurrentLineWidth() <= 0 ) return; + if( aCornerRadius > 0 ) + { + BOX2I box( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) ); + box.Normalize(); + SHAPE_RECT rect( box ); + rect.SetRadius( aCornerRadius ); + PlotPoly( rect.Outline(), fill, width, nullptr ); + return; + } + VECTOR2D p1_dev = userToDeviceCoordinates( p1 ); VECTOR2D p2_dev = userToDeviceCoordinates( p2 ); @@ -556,6 +568,22 @@ void PS_PLOTTER::PlotPoly( const std::vector& aCornerList, FILL_T aFil } +void PS_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, int aWidth, + void* aData ) +{ + std::vector cornerList; + cornerList.reserve( aCornerList.PointCount() ); + + for( int ii = 0; ii < aCornerList.PointCount(); ii++ ) + cornerList.emplace_back( aCornerList.CPoint( ii ) ); + + if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() ) + cornerList.emplace_back( aCornerList.CPoint( 0 ) ); + + PlotPoly( cornerList, aFill, aWidth, aData ); +} + + void PS_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) { VECTOR2I pix_size; // size of the bitmap in pixels diff --git a/common/plotters/SVG_plotter.cpp b/common/plotters/SVG_plotter.cpp index e0606f8c2d..e5ee4ee496 100644 --- a/common/plotters/SVG_plotter.cpp +++ b/common/plotters/SVG_plotter.cpp @@ -372,7 +372,8 @@ void SVG_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle ) } -void SVG_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) +void SVG_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius ) { BOX2I rect( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) ); rect.Normalize(); @@ -412,7 +413,7 @@ void SVG_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int rect_dev.GetPosition().y, rect_dev.GetSize().x, rect_dev.GetSize().y, - 0.0 /* radius of rounded corners */ ); + userToDeviceSize( aCornerRadius ) ); } } diff --git a/common/plotters/plotter.cpp b/common/plotters/plotter.cpp index 4ee21cedd4..60544002d3 100644 --- a/common/plotters/plotter.cpp +++ b/common/plotters/plotter.cpp @@ -267,7 +267,7 @@ void PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aSc end.x += size.x; end.y += size.y; - Rect( start, end, FILL_T::NO_FILL, USE_DEFAULT_LINE_WIDTH ); + Rect( start, end, FILL_T::NO_FILL, USE_DEFAULT_LINE_WIDTH, 0 ); } @@ -584,7 +584,7 @@ void PLOTTER::ThickArc( const EDA_SHAPE& aArcShape, void* aData, int aWidth ) void PLOTTER::ThickRect( const VECTOR2I& p1, const VECTOR2I& p2, int width, void* aData ) { - Rect( p1, p2, FILL_T::NO_FILL, width ); + Rect( p1, p2, FILL_T::NO_FILL, width, 0 ); } diff --git a/common/tool/edit_points.cpp b/common/tool/edit_points.cpp index e747bfe511..96a80a2507 100644 --- a/common/tool/edit_points.cpp +++ b/common/tool/edit_points.cpp @@ -312,7 +312,7 @@ void EDIT_POINTS::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const }; for( const EDIT_POINT& point : m_points ) - drawPoint( point ); + drawPoint( point, point.DrawCircle() ); for( const EDIT_LINE& line : m_lines ) { diff --git a/eeschema/sch_file_versions.h b/eeschema/sch_file_versions.h index c36fc610f9..fbf9cf0200 100644 --- a/eeschema/sch_file_versions.h +++ b/eeschema/sch_file_versions.h @@ -54,7 +54,8 @@ //#define SEXPR_SYMBOL_LIB_FILE_VERSION 20240819 // Embedded Files - Update hash algorithm to Murmur3 //#define SEXPR_SYMBOL_LIB_FILE_VERSION 20241209 // Private flags for SCH_FIELDs //#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250318 // ~ no longer means empty text -#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250324 // Jumper pin groups +//#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250324 // Jumper pin groups +#define SEXPR_SYMBOL_LIB_FILE_VERSION 20250829 // Rounded Rectangles /** * Schematic file version. @@ -124,4 +125,5 @@ //#define SEXPR_SCHEMATIC_FILE_VERSION 20250425 // uuids for tables //#define SEXPR_SCHEMATIC_FILE_VERSION 20250513 // Groups can have design block lib_id //#define SEXPR_SCHEMATIC_FILE_VERSION 20250610 // DNP, etc. flags for rule areas -#define SEXPR_SCHEMATIC_FILE_VERSION 20250827 // Custom body styles +//#define SEXPR_SCHEMATIC_FILE_VERSION 20250827 // Custom body styles +#define SEXPR_SCHEMATIC_FILE_VERSION 20250829 // Rounded Rectangles diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_common.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_common.cpp index 05f591f2c8..83f86e1be6 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_common.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_common.cpp @@ -269,6 +269,9 @@ void formatRect( OUTPUTFORMATTER* aFormatter, EDA_SHAPE* aRect, bool aIsPrivate, aIsPrivate ? "private" : "", formatIU( aRect->GetStart(), aInvertY ).c_str(), formatIU( aRect->GetEnd(), aInvertY ).c_str() ); + if( aRect->GetCornerRadius() > 0 ) + aFormatter->Print( "(radius %s)", + formatIU( aRect->GetCornerRadius() ).c_str() ); aStroke.Format( aFormatter, schIUScale ); formatFill( aFormatter, aFillMode, aFillColor ); diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp index 782b276e55..01a5898a5c 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp @@ -1895,6 +1895,11 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSymbolRectangle() NeedRIGHT(); break; + case T_radius: + rectangle->SetCornerRadius( parseDouble( "corner radius" ) * schIUScale.IU_PER_MM ); + NeedRIGHT(); + break; + case T_stroke: parseStroke( stroke ); rectangle->SetStroke( stroke ); @@ -4186,6 +4191,11 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSchRectangle() NeedRIGHT(); break; + case T_radius: + rectangle->SetCornerRadius( parseDouble( "corner radius" ) * schIUScale.IU_PER_MM ); + NeedRIGHT(); + break; + case T_stroke: parseStroke( stroke ); rectangle->SetStroke( stroke ); diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 7ff702cb39..21228be4a0 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include #include @@ -1588,7 +1590,20 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) break; case SHAPE_T::RECTANGLE: - m_gal->DrawRectangle( shape->GetPosition(), shape->GetEnd() ); + if( shape->GetCornerRadius() > 0 ) + { + ROUNDRECT rr( SHAPE_RECT( shape->GetPosition(), + shape->GetRectangleWidth(), + shape->GetRectangleHeight() ), + shape->GetCornerRadius() ); + SHAPE_POLY_SET poly; + rr.TransformToPolygon( poly ); + m_gal->DrawPolygon( poly ); + } + else + { + m_gal->DrawRectangle( shape->GetPosition(), shape->GetEnd() ); + } break; case SHAPE_T::POLY: diff --git a/eeschema/sch_plotter.cpp b/eeschema/sch_plotter.cpp index 6ff193be17..9ab98f68ca 100644 --- a/eeschema/sch_plotter.cpp +++ b/eeschema/sch_plotter.cpp @@ -462,7 +462,7 @@ bool SCH_PLOTTER::plotOneSheetPS( const wxString& aFileName, SCH_SCREEN* aScreen VECTOR2I end( actualPage.GetWidthIU( schIUScale.IU_PER_MILS ), actualPage.GetHeightIU( schIUScale.IU_PER_MILS ) ); - plotter->Rect( VECTOR2I( 0, 0 ), end, FILL_T::FILLED_SHAPE, 1.0 ); + plotter->Rect( VECTOR2I( 0, 0 ), end, FILL_T::FILLED_SHAPE, 1.0, 0 ); } if( aPlotOpts.m_plotDrawingSheet ) @@ -639,7 +639,7 @@ bool SCH_PLOTTER::plotOneSheetSVG( const wxString& aFileName, SCH_SCREEN* aScree VECTOR2I end( actualPage.GetWidthIU( schIUScale.IU_PER_MILS ), actualPage.GetHeightIU( schIUScale.IU_PER_MILS ) ); - plotter->Rect( VECTOR2I( 0, 0 ), end, FILL_T::FILLED_SHAPE, 1.0 ); + plotter->Rect( VECTOR2I( 0, 0 ), end, FILL_T::FILLED_SHAPE, 1.0, 0 ); } if( aPlotOpts.m_plotDrawingSheet ) diff --git a/eeschema/sch_shape.cpp b/eeschema/sch_shape.cpp index 609763c8d3..25a6c1ae31 100644 --- a/eeschema/sch_shape.cpp +++ b/eeschema/sch_shape.cpp @@ -274,7 +274,7 @@ void SCH_SHAPE::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& break; case SHAPE_T::RECTANGLE: - aPlotter->Rect( start, end, fill, pen_size ); + aPlotter->Rect( start, end, fill, pen_size, GetCornerRadius() ); break; case SHAPE_T::POLY: diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp index 7ab3acfb46..557dd2f425 100644 --- a/eeschema/sch_sheet.cpp +++ b/eeschema/sch_sheet.cpp @@ -1222,14 +1222,14 @@ void SCH_SHEET::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& if( aBackground && backgroundColor.a > 0.0 ) { aPlotter->SetColor( backgroundColor ); - aPlotter->Rect( m_pos, m_pos + m_size, FILL_T::FILLED_SHAPE, 1 ); + aPlotter->Rect( m_pos, m_pos + m_size, FILL_T::FILLED_SHAPE, 1, 0 ); } else { aPlotter->SetColor( borderColor ); int penWidth = GetEffectivePenWidth( getRenderSettings( aPlotter ) ); - aPlotter->Rect( m_pos, m_pos + m_size, FILL_T::NO_FILL, penWidth ); + aPlotter->Rect( m_pos, m_pos + m_size, FILL_T::NO_FILL, penWidth, 0 ); } // Make the sheet object a clickable hyperlink (e.g. for PDF plotter) diff --git a/eeschema/tools/sch_point_editor.cpp b/eeschema/tools/sch_point_editor.cpp index e3a06bdc2b..5342bdc1ca 100644 --- a/eeschema/tools/sch_point_editor.cpp +++ b/eeschema/tools/sch_point_editor.cpp @@ -24,6 +24,7 @@ #include "sch_point_editor.h" +#include #include #include #include @@ -61,7 +62,7 @@ enum ARC_POINTS enum RECTANGLE_POINTS { - RECT_TOPLEFT, RECT_TOPRIGHT, RECT_BOTLEFT, RECT_BOTRIGHT, RECT_CENTER + RECT_TOPLEFT, RECT_RADIUS, RECT_TOPRIGHT, RECT_BOTLEFT, RECT_BOTRIGHT, RECT_CENTER }; @@ -391,6 +392,8 @@ public: VECTOR2I botRight = aRect.GetEnd(); aPoints.AddPoint( topLeft ); + aPoints.AddPoint( VECTOR2I( botRight.x - aRect.GetCornerRadius(), topLeft.y ) ); + aPoints.Point( RECT_RADIUS ).SetDrawCircle(); aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) ); aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) ); aPoints.AddPoint( botRight ); @@ -412,6 +415,7 @@ public: VECTOR2I botRight = aRect.GetEnd(); aPoints.Point( RECT_TOPLEFT ).SetPosition( topLeft ); + aPoints.Point( RECT_RADIUS ).SetPosition( VECTOR2I( botRight.x - aRect.GetCornerRadius(), topLeft.y ) ); aPoints.Point( RECT_TOPRIGHT ).SetPosition( VECTOR2I( botRight.x, topLeft.y ) ); aPoints.Point( RECT_BOTLEFT ).SetPosition( VECTOR2I( topLeft.x, botRight.y ) ); aPoints.Point( RECT_BOTRIGHT ).SetPosition( botRight ); @@ -578,6 +582,16 @@ public: VECTOR2I moveVec = aPoints.Point( RECT_CENTER ).GetPosition() - oldBox.GetCenter(); m_rect.Move( moveVec ); } + else if( isModified( aEditedPoint, aPoints.Point( RECT_RADIUS ) ) ) + { + int width = std::abs( botRight.x - topLeft.x ); + int height = std::abs( botRight.y - topLeft.y ); + int maxRadius = std::min( width, height ) / 2; + int x = aPoints.Point( RECT_RADIUS ).GetX(); + x = std::clamp( x, botRight.x - maxRadius, botRight.x ); + aPoints.Point( RECT_RADIUS ).SetPosition( VECTOR2I( x, topLeft.y ) ); + m_rect.SetCornerRadius( botRight.x - x ); + } else if( isModified( aEditedPoint, aPoints.Line( RECT_TOP ) ) ) { oldSegs = KIGEOM::GetSegsInDirection( oldBox, DIRECTION_45::Directions::N ); diff --git a/include/eda_shape.h b/include/eda_shape.h index 6f245109d5..555d635584 100644 --- a/include/eda_shape.h +++ b/include/eda_shape.h @@ -391,6 +391,9 @@ public: void SetRectangle( const long long int& aHeight, const long long int& aWidth ); + void SetCornerRadius( int aRadius ); + int GetCornerRadius() const; + void SetSegmentAngle( const EDA_ANGLE& aAngle ); bool IsClockwiseArc() const; @@ -496,6 +499,7 @@ protected: long long int m_rectangleHeight; long long int m_rectangleWidth; + int m_cornerRadius; double m_segmentLength; EDA_ANGLE m_segmentAngle; diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h index 50793104f8..9688a5f2ee 100644 --- a/include/plotters/plotter.h +++ b/include/plotters/plotter.h @@ -228,7 +228,8 @@ public: int GetPlotterArcHighDef() const { return m_IUsPerDecimil * 2; } // Low level primitives - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) = 0; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) = 0; virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill, int width ) = 0; virtual void Arc( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd, diff --git a/include/plotters/plotter_dxf.h b/include/plotters/plotter_dxf.h index 960af9e531..2e4a8742ca 100644 --- a/include/plotters/plotter_dxf.h +++ b/include/plotters/plotter_dxf.h @@ -84,7 +84,8 @@ public: /** * DXF rectangle: fill not supported. */ - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) override; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) override; /** * DXF circle: full functionality; it even does 'fills' drawing a @@ -107,6 +108,9 @@ public: virtual void PlotPoly( const std::vector& aCornerList, FILL_T aFill, int aWidth, void* aData = nullptr ) override; + virtual void PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, + int aWidth, void* aData = nullptr ) override; + virtual void ThickSegment( const VECTOR2I& start, const VECTOR2I& end, int width, void* aData ) override; diff --git a/include/plotters/plotter_gerber.h b/include/plotters/plotter_gerber.h index 382c9ee0a4..f4bdd4b458 100644 --- a/include/plotters/plotter_gerber.h +++ b/include/plotters/plotter_gerber.h @@ -60,7 +60,8 @@ public: double aScale, bool aMirror ) override; // Basic plot primitives - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) override; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) override; virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill, int width ) override; virtual void Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth ) override; diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h index 734a5d813d..6cf43692e8 100644 --- a/include/plotters/plotters_pslike.h +++ b/include/plotters/plotters_pslike.h @@ -185,7 +185,8 @@ public: virtual void SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil, double aScale, bool aMirror ) override; - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) override; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) override; virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill, int width ) override; virtual void Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth ) override; @@ -223,6 +224,9 @@ public: const KIFONT::METRICS& aFontMetrics, void* aData = nullptr ) override; + virtual void PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, + int aWidth, void* aData = nullptr ) override; + protected: virtual void emitSetRGBColor( double r, double g, double b, double a ) override; @@ -326,7 +330,8 @@ public: /** * Rectangles in PDF. Supported by the native operator. */ - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) override; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) override; /** * Circle drawing for PDF. They're approximated by curves, but fill is supported @@ -346,6 +351,9 @@ public: virtual void PlotPoly( const std::vector& aCornerList, FILL_T aFill, int aWidth = USE_DEFAULT_LINE_WIDTH, void* aData = nullptr ) override; + virtual void PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, + int aWidth = USE_DEFAULT_LINE_WIDTH, void* aData = nullptr ) override; + virtual void PenTo( const VECTOR2I& pos, char plume ) override; virtual void Text( const VECTOR2I& aPos, @@ -572,7 +580,8 @@ public: virtual void SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil, double aScale, bool aMirror ) override; - virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width ) override; + virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, + int aCornerRadius = 0 ) override; virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill, int width ) override; virtual void Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, diff --git a/include/tool/edit_points.h b/include/tool/edit_points.h index 4fda5f4518..08701395f1 100644 --- a/include/tool/edit_points.h +++ b/include/tool/edit_points.h @@ -54,6 +54,7 @@ public: m_position( aPoint ), m_isActive( false ), m_isHover( false ), + m_drawCircle( false ), m_gridConstraint( SNAP_TO_GRID ), m_snapConstraint( OBJECT_LAYERS ), m_connected( aConnected ) @@ -175,6 +176,9 @@ public: bool IsHover() const { return m_isHover; } void SetHover( bool aHover = true ) { m_isHover = aHover; } + bool DrawCircle() const { return m_drawCircle; } + void SetDrawCircle( bool aDrawCircle = true ) { m_drawCircle = aDrawCircle; } + GRID_CONSTRAINT_TYPE GetGridConstraint() const { return m_gridConstraint; } void SetGridConstraint( GRID_CONSTRAINT_TYPE aConstraint ) { m_gridConstraint = aConstraint; } @@ -201,6 +205,7 @@ private: VECTOR2I m_position; ///< Position of EDIT_POINT. bool m_isActive; ///< True if this point is being manipulated. bool m_isHover; ///< True if this point is being hovered over. + bool m_drawCircle; ///< True if the point is drawn circular. GRID_CONSTRAINT_TYPE m_gridConstraint; ///< Describe the grid snapping behavior. SNAP_CONSTRAINT_TYPE m_snapConstraint; ///< Describe the object snapping behavior. diff --git a/libs/kimath/include/geometry/shape_rect.h b/libs/kimath/include/geometry/shape_rect.h index 01d529e16c..fd05d0621d 100644 --- a/libs/kimath/include/geometry/shape_rect.h +++ b/libs/kimath/include/geometry/shape_rect.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,8 @@ public: SHAPE_RECT() : SHAPE( SH_RECT ), m_w( 0 ), - m_h( 0 ) + m_h( 0 ), + m_radius( 0 ) {} /** @@ -53,7 +55,8 @@ public: SHAPE( SH_RECT ), m_p0( aBox.GetPosition() ), m_w( aBox.GetWidth() ), - m_h( aBox.GetHeight() ) + m_h( aBox.GetHeight() ), + m_radius( 0 ) {} /** @@ -63,7 +66,8 @@ public: SHAPE( SH_RECT ), m_p0( aX0, aY0 ), m_w( aW ), - m_h( aH ) + m_h( aH ), + m_radius( 0 ) {} /** @@ -73,7 +77,8 @@ public: SHAPE( SH_RECT ), m_p0( aP0 ), m_w( aW ), - m_h( aH ) + m_h( aH ), + m_radius( 0 ) {} /** @@ -83,14 +88,16 @@ public: SHAPE( SH_RECT ), m_p0( aP0 ), m_w( aP1.x - aP0.x ), - m_h( aP1.y - aP0.y ) + m_h( aP1.y - aP0.y ), + m_radius( 0 ) {} SHAPE_RECT( const SHAPE_RECT& aOther ) : SHAPE( SH_RECT ), m_p0( aOther.m_p0 ), m_w( aOther.m_w ), - m_h( aOther.m_h ) + m_h( aOther.m_h ), + m_radius( aOther.m_radius ) {}; SHAPE* Clone() const override @@ -112,11 +119,13 @@ public: */ SHAPE_RECT GetInflated( int aOffset ) const { - return SHAPE_RECT{ + SHAPE_RECT r{ m_p0 - VECTOR2I( aOffset, aOffset ), m_w + 2 * aOffset, m_h + 2 * aOffset, }; + r.SetRadius( m_radius + aOffset ); + return r; } /** @@ -186,6 +195,19 @@ public: return m_h; } + /** + * @return the corner radius of the rectangle. + */ + int GetRadius() const + { + return m_radius; + } + + void SetRadius( int aRadius ) + { + m_radius = aRadius; + } + void Move( const VECTOR2I& aVector ) override { m_p0 += aVector; @@ -209,17 +231,7 @@ public: return true; } - const SHAPE_LINE_CHAIN Outline() const - { - SHAPE_LINE_CHAIN rv; - rv.Append( m_p0 ); - rv.Append( m_p0.x, m_p0.y + m_h ); - rv.Append( m_p0.x + m_w, m_p0.y + m_h ); - rv.Append( m_p0.x + m_w, m_p0.y ); - rv.Append( m_p0 ); - rv.SetClosed( true ); - return rv; - } + const SHAPE_LINE_CHAIN Outline() const; virtual const std::string Format( bool aCplusPlus = true ) const override; @@ -230,6 +242,7 @@ private: VECTOR2I m_p0; ///< Top-left corner int m_w; ///< Width int m_h; ///< Height + int m_radius; ///< Corner radius }; #endif // __SHAPE_RECT_H diff --git a/libs/kimath/src/geometry/shape_rect.cpp b/libs/kimath/src/geometry/shape_rect.cpp index 39d90a0c56..ea06dc056d 100644 --- a/libs/kimath/src/geometry/shape_rect.cpp +++ b/libs/kimath/src/geometry/shape_rect.cpp @@ -26,6 +26,7 @@ #include #include +#include bool SHAPE_RECT::Collide( const SEG& aSeg, int aClearance, int* aActual, VECTOR2I* aLocation ) const { @@ -114,6 +115,8 @@ const std::string SHAPE_RECT::Format( bool aCplusPlus ) const ss << m_w; ss << ", "; ss << m_h; + ss << ", "; + ss << m_radius; ss << ");"; return ss.str(); @@ -123,6 +126,13 @@ const std::string SHAPE_RECT::Format( bool aCplusPlus ) const void SHAPE_RECT::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, ERROR_LOC aErrorLoc ) const { + if( m_radius > 0 ) + { + ROUNDRECT rr( *this, m_radius ); + rr.TransformToPolygon( aBuffer ); + return; + } + int idx = aBuffer.NewOutline(); SHAPE_LINE_CHAIN& outline = aBuffer.Outline( idx ); @@ -132,3 +142,23 @@ void SHAPE_RECT::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, outline.Append( { m_p0.x, m_p0.y + m_h } ); outline.SetClosed( true ); } + +const SHAPE_LINE_CHAIN SHAPE_RECT::Outline() const +{ + if( m_radius > 0 ) + { + SHAPE_POLY_SET poly; + ROUNDRECT rr( *this, m_radius ); + rr.TransformToPolygon( poly ); + return poly.Outline( 0 ); + } + + SHAPE_LINE_CHAIN rv; + rv.Append( m_p0 ); + rv.Append( m_p0.x, m_p0.y + m_h ); + rv.Append( m_p0.x + m_w, m_p0.y + m_h ); + rv.Append( m_p0.x + m_w, m_p0.y ); + rv.Append( m_p0 ); + rv.SetClosed( true ); + return rv; +} diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp index 2e70e1b661..6535b6cdbc 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -964,6 +964,9 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_SHAPE* aShape ) const prefix.c_str(), formatInternalUnits( aShape->GetStart(), parentFP ).c_str(), formatInternalUnits( aShape->GetEnd(), parentFP ).c_str() ); + + if( aShape->GetCornerRadius() > 0 ) + m_out->Print( " (radius %s)", formatInternalUnits( aShape->GetCornerRadius() ).c_str() ); break; case SHAPE_T::CIRCLE: @@ -1801,6 +1804,9 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad ) const m_out->Print( "(gr_rect (start %s) (end %s)", formatInternalUnits( primitive->GetStart() ).c_str(), formatInternalUnits( primitive->GetEnd() ).c_str() ); + + if( primitive->GetCornerRadius() > 0 ) + m_out->Print( " (radius %s)", formatInternalUnits( primitive->GetCornerRadius() ).c_str() ); } break; diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h index f9ed7c3fdf..5927f7f159 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -186,7 +186,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl //#define SEXPR_BOARD_FILE_VERSION 20250513 // Groups can have design block lib_id //#define SEXPR_BOARD_FILE_VERSION 20250801 // (island) -> (island yes/no) //#define SEXPR_BOARD_FILE_VERSION 20250811 // press-fit pad fabr prop support -#define SEXPR_BOARD_FILE_VERSION 20250818 // Support for custom layer counts in footprints +//#define SEXPR_BOARD_FILE_VERSION 20250818 // Support for custom layer counts in footprints +#define SEXPR_BOARD_FILE_VERSION 20250829 // Support Rounded Rectangles #define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag #define LEGACY_ARC_FORMATTING 20210925 ///< These were the last to use old arc formatting diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp index ef7fc322ef..8ccbb7f9bb 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp @@ -3192,6 +3192,11 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) NeedRIGHT(); break; + case T_radius: + shape->SetCornerRadius( parseBoardUnits( "corner radius" ) ); + NeedRIGHT(); + break; + case T_stroke: { STROKE_PARAMS_PARSER strokeParser( reader, pcbIUScale.IU_PER_MM ); diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index f90455673d..22703b74ee 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -63,6 +63,8 @@ #include #include #include +#include +#include #include #include #include @@ -1973,50 +1975,97 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) case SHAPE_T::RECTANGLE: { - std::vector pts = aShape->GetRectCorners(); + if( aShape->GetCornerRadius() > 0 ) + { + ROUNDRECT rr( SHAPE_RECT( aShape->GetStart(), aShape->GetRectangleWidth(), + aShape->GetRectangleHeight() ), + aShape->GetCornerRadius() ); + SHAPE_POLY_SET poly; + rr.TransformToPolygon( poly ); + SHAPE_LINE_CHAIN outline = poly.Outline( 0 ); - if( aShape->IsProxyItem() ) - { - m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); - m_gal->DrawLine( pts[0], pts[1] ); - m_gal->DrawLine( pts[1], pts[2] ); - m_gal->DrawLine( pts[2], pts[3] ); - m_gal->DrawLine( pts[3], pts[0] ); - m_gal->DrawLine( pts[0], pts[2] ); - m_gal->DrawLine( pts[1], pts[3] ); - } - else if( outline_mode ) - { - m_gal->DrawSegment( pts[0], pts[1], thickness ); - m_gal->DrawSegment( pts[1], pts[2], thickness ); - m_gal->DrawSegment( pts[2], pts[3], thickness ); - m_gal->DrawSegment( pts[3], pts[0], thickness ); + if( aShape->IsProxyItem() ) + { + m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); + m_gal->DrawPolygon( outline ); + } + else if( outline_mode ) + { + m_gal->DrawSegmentChain( outline, thickness ); + } + else + { + m_gal->SetIsFill( true ); + m_gal->SetIsStroke( false ); + + if( lineStyle == LINE_STYLE::SOLID && thickness > 0 ) + { + m_gal->DrawSegmentChain( outline, thickness ); + } + + if( isSolidFill ) + { + if( thickness < 0 ) + { + SHAPE_POLY_SET deflated_shape = outline; + deflated_shape.Inflate( thickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError ); + m_gal->DrawPolygon( deflated_shape ); + } + else + { + m_gal->DrawPolygon( outline ); + } + } + } } else { - m_gal->SetIsFill( true ); - m_gal->SetIsStroke( false ); + std::vector pts = aShape->GetRectCorners(); - if( lineStyle == LINE_STYLE::SOLID && thickness > 0 ) + if( aShape->IsProxyItem() ) + { + m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); + m_gal->DrawLine( pts[0], pts[1] ); + m_gal->DrawLine( pts[1], pts[2] ); + m_gal->DrawLine( pts[2], pts[3] ); + m_gal->DrawLine( pts[3], pts[0] ); + m_gal->DrawLine( pts[0], pts[2] ); + m_gal->DrawLine( pts[1], pts[3] ); + } + else if( outline_mode ) { m_gal->DrawSegment( pts[0], pts[1], thickness ); m_gal->DrawSegment( pts[1], pts[2], thickness ); m_gal->DrawSegment( pts[2], pts[3], thickness ); m_gal->DrawSegment( pts[3], pts[0], thickness ); } - - if( isSolidFill ) + else { - SHAPE_POLY_SET poly; - poly.NewOutline(); + m_gal->SetIsFill( true ); + m_gal->SetIsStroke( false ); - for( const VECTOR2I& pt : pts ) - poly.Append( pt ); + if( lineStyle == LINE_STYLE::SOLID && thickness > 0 ) + { + m_gal->DrawSegment( pts[0], pts[1], thickness ); + m_gal->DrawSegment( pts[1], pts[2], thickness ); + m_gal->DrawSegment( pts[2], pts[3], thickness ); + m_gal->DrawSegment( pts[3], pts[0], thickness ); + } - if( thickness < 0 ) - poly.Inflate( thickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError ); + if( isSolidFill ) + { + SHAPE_POLY_SET poly; + poly.NewOutline(); - m_gal->DrawPolygon( poly ); + for( const VECTOR2I& pt : pts ) + poly.Append( pt ); + + if( thickness < 0 ) + poly.Inflate( thickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, + m_maxError ); + + m_gal->DrawPolygon( poly ); + } } } diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 926a4e61bb..a41cd5f6f2 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -1180,7 +1180,7 @@ static void FillNegativeKnockout( PLOTTER *aPlotter, const BOX2I &aBbbox ) BOX2I area = aBbbox; area.Inflate( margin ); - aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILL_T::FILLED_SHAPE, 0 ); + aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILL_T::FILLED_SHAPE, 0, 0 ); aPlotter->SetColor( BLACK ); } diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index 6d8e96bc14..dd1bc24a1e 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -29,6 +29,7 @@ #include #include // for SHAPE_LINE_CHAIN #include // for SHAPE_POLY_SET, SHAPE_P... +#include #include #include #include @@ -1081,19 +1082,28 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) case SHAPE_T::RECTANGLE: { - std::vector pts = aShape->GetRectCorners(); + int radius = aShape->GetCornerRadius(); - if( m_plotter->GetPlotterType() == PLOT_FORMAT::DXF && GetDXFPlotMode() == SKETCH ) + if( radius == 0 && m_plotter->GetPlotterType() == PLOT_FORMAT::DXF && + GetDXFPlotMode() == SKETCH ) { + std::vector pts = aShape->GetRectCorners(); m_plotter->ThickRect( pts[0], pts[2], thickness, getMetadata() ); } else { - SHAPE_POLY_SET poly; + BOX2I box( aShape->GetStart(), VECTOR2I( aShape->GetEnd().x - aShape->GetStart().x, + aShape->GetEnd().y - aShape->GetStart().y ) ); + box.Normalize(); + SHAPE_RECT rect( box ); + rect.SetRadius( radius ); + + SHAPE_LINE_CHAIN outline = rect.Outline(); + SHAPE_POLY_SET poly; poly.NewOutline(); - for( const VECTOR2I& pt : pts ) - poly.Append( pt ); + for( int ii = 0; ii < outline.PointCount() - 1; ++ii ) + poly.Append( outline.CPoint( ii ) ); if( margin < 0 ) poly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, aShape->GetMaxError() ); diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index 7bab8c42eb..2b796f6390 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -25,6 +25,7 @@ #include #include +#include using namespace std::placeholders; #include @@ -68,6 +69,7 @@ const unsigned int PCB_POINT_EDITOR::COORDS_PADDING = pcbIUScale.mmToIU( 20 ); enum RECT_POINTS { RECT_TOP_LEFT, + RECT_RADIUS, RECT_TOP_RIGHT, RECT_BOT_RIGHT, RECT_BOT_LEFT, @@ -137,6 +139,8 @@ public: std::swap( topLeft.y, botRight.y ); aPoints.AddPoint( topLeft ); + aPoints.AddPoint( VECTOR2I( botRight.x - aRectangle.GetCornerRadius(), topLeft.y ) ); + aPoints.Point( RECT_RADIUS ).SetDrawCircle(); aPoints.AddPoint( VECTOR2I( botRight.x, topLeft.y ) ); aPoints.AddPoint( botRight ); aPoints.AddPoint( VECTOR2I( topLeft.x, botRight.y ) ); @@ -203,6 +207,16 @@ public: aPoints.Point( RECT_CENTER ).GetPosition() - aRectangle.GetCenter(); aRectangle.Move( moveVector ); } + else if( isModified( aEditedPoint, aPoints.Point( RECT_RADIUS ) ) ) + { + int width = std::abs( botRight.x - topLeft.x ); + int height = std::abs( botRight.y - topLeft.y ); + int maxRadius = std::min( width, height ) / 2; + int x = aPoints.Point( RECT_RADIUS ).GetX(); + x = std::clamp( x, botRight.x - maxRadius, botRight.x ); + aPoints.Point( RECT_RADIUS ).SetPosition( x, topLeft.y ); + aRectangle.SetCornerRadius( botRight.x - x ); + } else if( isModified( aEditedPoint, aPoints.Line( RECT_TOP ) ) ) { setTop( topLeft.y ); @@ -246,6 +260,7 @@ public: std::swap( topLeft.y, botRight.y ); aPoints.Point( RECT_TOP_LEFT ).SetPosition( topLeft ); + aPoints.Point( RECT_RADIUS ).SetPosition( botRight.x - aRectangle.GetCornerRadius(), topLeft.y ); aPoints.Point( RECT_TOP_RIGHT ).SetPosition( botRight.x, topLeft.y ); aPoints.Point( RECT_BOT_RIGHT ).SetPosition( botRight ); aPoints.Point( RECT_BOT_LEFT ).SetPosition( topLeft.x, botRight.y ); diff --git a/qa/tests/eeschema/CMakeLists.txt b/qa/tests/eeschema/CMakeLists.txt index 7bfb87f15a..f5550afad4 100644 --- a/qa/tests/eeschema/CMakeLists.txt +++ b/qa/tests/eeschema/CMakeLists.txt @@ -80,6 +80,7 @@ set( QA_EESCHEMA_SRCS test_incremental_netlister.cpp test_legacy_power_symbols.cpp test_sch_commit.cpp + test_shape_corner_radius.cpp test_sch_group.cpp test_pin_numbers.cpp test_sch_netclass.cpp diff --git a/qa/tests/eeschema/test_shape_corner_radius.cpp b/qa/tests/eeschema/test_shape_corner_radius.cpp new file mode 100644 index 0000000000..34bce5b5a5 --- /dev/null +++ b/qa/tests/eeschema/test_shape_corner_radius.cpp @@ -0,0 +1,36 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +#include + +BOOST_AUTO_TEST_CASE( SCHShapeCornerRadius ) +{ + SCH_SHAPE shape( SHAPE_T::RECTANGLE, LAYER_NOTES ); + shape.SetPosition( VECTOR2I( 0, 0 ) ); + shape.SetEnd( VECTOR2I( 100, 100 ) ); + shape.SetCornerRadius( 20 ); + BOOST_CHECK_EQUAL( shape.GetCornerRadius(), 20 ); +} diff --git a/qa/tests/libs/kimath/CMakeLists.txt b/qa/tests/libs/kimath/CMakeLists.txt index 5d711515e3..0b961ffb4e 100644 --- a/qa/tests/libs/kimath/CMakeLists.txt +++ b/qa/tests/libs/kimath/CMakeLists.txt @@ -51,6 +51,7 @@ set( QA_KIMATH_SRCS geometry/test_shape_line_chain.cpp geometry/test_shape_line_chain_collision.cpp geometry/test_vector_utils.cpp + geometry/test_shape_rect_corner.cpp math/test_box2.cpp math/test_matrix3x3.cpp diff --git a/qa/tests/libs/kimath/geometry/test_shape_rect_corner.cpp b/qa/tests/libs/kimath/geometry/test_shape_rect_corner.cpp new file mode 100644 index 0000000000..07e450fd9d --- /dev/null +++ b/qa/tests/libs/kimath/geometry/test_shape_rect_corner.cpp @@ -0,0 +1,34 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +#include + +BOOST_AUTO_TEST_CASE( ShapeRectCornerRadius ) +{ + SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) ); + rect.SetRadius( 2 ); + BOOST_CHECK_EQUAL( rect.GetRadius(), 2 ); +} diff --git a/qa/tests/pcbnew/CMakeLists.txt b/qa/tests/pcbnew/CMakeLists.txt index cdeb558c56..afe5ebe436 100644 --- a/qa/tests/pcbnew/CMakeLists.txt +++ b/qa/tests/pcbnew/CMakeLists.txt @@ -48,6 +48,7 @@ set( QA_PCBNEW_SRCS test_prettifier.cpp test_libeval_compiler.cpp test_reference_image_load.cpp + test_shape_corner_radius.cpp test_pcb_grid_helper.cpp test_save_load.cpp test_tracks_cleaner.cpp diff --git a/qa/tests/pcbnew/test_shape_corner_radius.cpp b/qa/tests/pcbnew/test_shape_corner_radius.cpp new file mode 100644 index 0000000000..9df2b38702 --- /dev/null +++ b/qa/tests/pcbnew/test_shape_corner_radius.cpp @@ -0,0 +1,36 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +#include + +BOOST_AUTO_TEST_CASE( PCBShapeCornerRadius ) +{ + PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE ); + shape.SetStart( VECTOR2I( 0, 0 ) ); + shape.SetEnd( VECTOR2I( 1000, 1000 ) ); + shape.SetCornerRadius( 200 ); + BOOST_CHECK_EQUAL( shape.GetCornerRadius(), 200 ); +}