From e6b6200de2ebe3014457e01a5aa7752aeb369293 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Thu, 23 Oct 2025 17:43:20 +0100 Subject: [PATCH] Improve SHAPE_LINE_CHAIN arc plotting. It still doesn't get it quite right as it restarts the lineStyle pattern at each segment/arc transition. --- common/plotters/GERBER_plotter.cpp | 9 +--- common/plotters/PDF_plotter.cpp | 82 ++++++++++++++++++++++-------- include/plotters/plotter_gerber.h | 4 ++ include/plotters/plotters_pslike.h | 3 ++ pcbnew/plot_brditems_plotter.cpp | 20 ++++---- 5 files changed, 78 insertions(+), 40 deletions(-) diff --git a/common/plotters/GERBER_plotter.cpp b/common/plotters/GERBER_plotter.cpp index 5365432962..6458c81896 100644 --- a/common/plotters/GERBER_plotter.cpp +++ b/common/plotters/GERBER_plotter.cpp @@ -851,14 +851,7 @@ void GERBER_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, 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; - } + wxFAIL_MSG( wxT( "GERBER_PLOTTER must use PlotPolyAsRegion() for rounded-corner rectangles!" ) ); std::vector cornerList; diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp index faff05793f..95a5729bf3 100644 --- a/common/plotters/PDF_plotter.cpp +++ b/common/plotters/PDF_plotter.cpp @@ -386,18 +386,10 @@ void PDF_PLOTTER::Circle( const VECTOR2I& pos, int diametre, FILL_T aFill, int w } -void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, - const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth ) +std::string PDF_PLOTTER::arcPath( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, + const EDA_ANGLE& aAngle, double aRadius ) { - wxASSERT( m_workFile ); - - SetCurrentLineWidth( aWidth ); - - if( aRadius <= 0 ) - { - Circle( aCenter, GetCurrentLineWidth(), FILL_T::FILLED_SHAPE, 0 ); - return; - } + std::string path; /* * Arcs are not so easily approximated by beziers (in the general case), so we approximate @@ -416,20 +408,39 @@ void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, start.x = KiROUND( aCenter.x + aRadius * ( -startAngle ).Cos() ); start.y = KiROUND( aCenter.y + aRadius * ( -startAngle ).Sin() ); VECTOR2D pos_dev = userToDeviceCoordinates( start ); - fmt::print( m_workFile, "{:g} {:g} m ", pos_dev.x, pos_dev.y ); + path += fmt::format( "{:g} {:g} m ", pos_dev.x, pos_dev.y ); for( EDA_ANGLE ii = startAngle + delta; ii < endAngle; ii += delta ) { end.x = KiROUND( aCenter.x + aRadius * ( -ii ).Cos() ); end.y = KiROUND( aCenter.y + aRadius * ( -ii ).Sin() ); pos_dev = userToDeviceCoordinates( end ); - fmt::print( m_workFile, "{:g} {:g} l ", pos_dev.x, pos_dev.y ); + path += fmt::format( "{:g} {:g} l ", pos_dev.x, pos_dev.y ); } end.x = KiROUND( aCenter.x + aRadius * ( -endAngle ).Cos() ); end.y = KiROUND( aCenter.y + aRadius * ( -endAngle ).Sin() ); pos_dev = userToDeviceCoordinates( end ); - fmt::print( m_workFile, "{:g} {:g} l ", pos_dev.x, pos_dev.y ); + path += fmt::format( "{:g} {:g} l ", pos_dev.x, pos_dev.y ); + + return path; +} + + +void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, + const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth ) +{ + wxASSERT( m_workFile ); + + SetCurrentLineWidth( aWidth ); + + if( aRadius <= 0 ) + { + Circle( aCenter, GetCurrentLineWidth(), FILL_T::FILLED_SHAPE, 0 ); + return; + } + + fmt::print( m_workFile, "{}", arcPath( aCenter, aStartAngle, aAngle, aRadius ) ); // The arc is drawn... if not filled we stroke it, otherwise we finish // closing the pie at the center @@ -439,7 +450,7 @@ void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, } else { - pos_dev = userToDeviceCoordinates( aCenter ); + VECTOR2D pos_dev = userToDeviceCoordinates( aCenter ); fmt::println( m_workFile, "{:g} {:g} l b", pos_dev.x, pos_dev.y ); } } @@ -480,16 +491,43 @@ 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() ); + SetCurrentLineWidth( aWidth ); - for( int ii = 0; ii < aCornerList.PointCount(); ii++ ) - cornerList.emplace_back( aCornerList.CPoint( ii ) ); + std::set handledArcs; - if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() ) - cornerList.emplace_back( aCornerList.CPoint( 0 ) ); + for( int ii = 0; ii < aCornerList.SegmentCount(); ++ii ) + { + if( aCornerList.IsArcSegment( ii ) ) + { + size_t arcIndex = aCornerList.ArcIndex( ii ); - PlotPoly( cornerList, aFill, aWidth, aData ); + if( !handledArcs.contains( arcIndex ) ) + { + handledArcs.insert( arcIndex ); + const SHAPE_ARC& arc( aCornerList.Arc( arcIndex ) ); + fmt::print( m_workFile, "{}", arcPath( arc.GetCenter(), arc.GetStartAngle(), + arc.GetCentralAngle(), arc.GetRadius() ) ); + } + } + else + { + const SEG& seg( aCornerList.Segment( ii ) ); + + VECTOR2D pos = userToDeviceCoordinates( seg.A ); + fmt::println( m_workFile, "{:f} {:f} m", pos.x, pos.y ); + + pos = userToDeviceCoordinates( seg.B ); + fmt::println( m_workFile, "{:f} {:f} l", pos.x, pos.y ); + } + } + + // Close path and stroke and/or fill + if( aFill == FILL_T::NO_FILL ) + fmt::println( m_workFile, "S" ); + else if( aWidth == 0 ) + fmt::println( m_workFile, "f" ); + else + fmt::println( m_workFile, "b" ); } diff --git a/include/plotters/plotter_gerber.h b/include/plotters/plotter_gerber.h index f4bdd4b458..584065503c 100644 --- a/include/plotters/plotter_gerber.h +++ b/include/plotters/plotter_gerber.h @@ -60,9 +60,13 @@ public: double aScale, bool aMirror ) override; // Basic plot primitives + + // Note: do not use Rect() to plot rounded-corner rectangles. Use PlotPolyAsRegion() instead. 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 64612f46a6..b66f8d5b5a 100644 --- a/include/plotters/plotters_pslike.h +++ b/include/plotters/plotters_pslike.h @@ -418,6 +418,9 @@ private: void drawOverbars( const std::vector& aOverbars, const EDA_ANGLE& aOrient, const KIFONT::METRICS& aFontMetrics ); + std::string arcPath( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, + const EDA_ANGLE& aAngle, double aRadius ); + public: virtual void PlotText( const VECTOR2I& aPos, const COLOR4D& aColor, diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index d4097c81a2..5a82938eeb 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -1099,18 +1099,18 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) BOX2I box( aShape->GetStart(), VECTOR2I( aShape->GetEnd().x - aShape->GetStart().x, aShape->GetEnd().y - aShape->GetStart().y ) ); box.Normalize(); + + if( margin < 0 ) + { + box.Inflate( margin ); + radius += margin; + } + SHAPE_RECT rect( box ); rect.SetRadius( radius ); SHAPE_LINE_CHAIN outline = rect.Outline(); - SHAPE_POLY_SET poly; - poly.NewOutline(); - - for( int ii = 0; ii < outline.PointCount(); ++ii ) - poly.Append( outline.CPoint( ii ) ); - - if( margin < 0 ) - poly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, aShape->GetMaxError() ); + SHAPE_POLY_SET poly( outline ); FILL_T fill_mode = isSolidFill ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; @@ -1119,11 +1119,11 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER ) { GERBER_PLOTTER* gbr_plotter = static_cast( m_plotter ); - gbr_plotter->PlotPolyAsRegion( poly.COutline( 0 ), fill_mode, thickness, - &gbr_metadata ); + gbr_plotter->PlotPolyAsRegion( poly.COutline( 0 ), fill_mode, thickness, &gbr_metadata ); } else { + // TODO: PlotPoly needs to handle arcs... m_plotter->PlotPoly( poly.COutline( 0 ), fill_mode, thickness, getMetadata() ); } }