diff --git a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp index 091c6a50bf..6257e8a8f1 100644 --- a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp +++ b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp @@ -645,7 +645,7 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta float innerR3DU = TO_3DU( aShape->GetRadius() ) - linewidth3DU / 2.0; float outerR3DU = TO_3DU( aShape->GetRadius() ) + linewidth3DU / 2.0; - if( aShape->IsFilled() || innerR3DU <= 0.0 ) + if( aShape->IsSolidFill() || innerR3DU <= 0.0 ) addFILLED_CIRCLE_2D( aContainer, center3DU, outerR3DU, *aOwner ); else addRING_2D( aContainer, center3DU, innerR3DU, outerR3DU, *aOwner ); @@ -654,7 +654,7 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta } case SHAPE_T::RECTANGLE: - if( aShape->IsFilled() ) + if( aShape->IsSolidFill() ) { SHAPE_POLY_SET polyList; @@ -759,6 +759,9 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta for( SHAPE* shape : shapes ) delete shape; } + + if( aShape->IsHatchedFill() ) + ConvertPolygonToTriangles( aShape->GetHatching(), *aContainer, m_biuTo3Dunits, *aOwner ); } diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp index e7593553ff..a9935cd685 100644 --- a/common/eda_shape.cpp +++ b/common/eda_shape.cpp @@ -533,6 +533,118 @@ bool EDA_SHAPE::IsClosed() const } +void EDA_SHAPE::SetFillMode( FILL_T aFill ) +{ + m_fill = aFill; + m_hatchingDirty = true; +} + + +void EDA_SHAPE::SetFillModeProp( UI_FILL_MODE aFill ) +{ + switch( aFill ) + { + case UI_FILL_MODE::NONE: SetFillMode( FILL_T::NO_FILL ); break; + case UI_FILL_MODE::HATCH: SetFillMode( FILL_T::HATCH ); break; + case UI_FILL_MODE::REVERSE_HATCH: SetFillMode( FILL_T::REVERSE_HATCH ); break; + case UI_FILL_MODE::CROSS_HATCH: SetFillMode( FILL_T::CROSS_HATCH ); break; + default: SetFillMode( FILL_T::FILLED_SHAPE ); break; + } +} + + +UI_FILL_MODE EDA_SHAPE::GetFillModeProp() const +{ + switch( m_fill ) + { + case FILL_T::NO_FILL: return UI_FILL_MODE::NONE; + case FILL_T::HATCH: return UI_FILL_MODE::HATCH; + case FILL_T::REVERSE_HATCH: return UI_FILL_MODE::REVERSE_HATCH; + case FILL_T::CROSS_HATCH: return UI_FILL_MODE::CROSS_HATCH; + default: return UI_FILL_MODE::SOLID; + } +} + + +const SHAPE_POLY_SET& EDA_SHAPE::GetHatching() const +{ + if( m_hatchingDirty ) + { + updateHatching(); + m_hatchingDirty = false; + } + + return m_hatching; +} + + +void EDA_SHAPE::updateHatching() const +{ + m_hatching.RemoveAllContours(); + + std::vector slopes; + int lineWidth = GetHatchLineWidth(); + int spacing = GetHatchLineSpacing(); + SHAPE_POLY_SET shapeBuffer; + + if( GetFillMode() == FILL_T::CROSS_HATCH ) + slopes = { 1.0, -1.0 }; + else if( GetFillMode() == FILL_T::HATCH ) + slopes = { -1.0 }; + else if( GetFillMode() == FILL_T::REVERSE_HATCH ) + slopes = { 1.0 }; + else + return; + + if( spacing == 0 ) + return; + + auto addHatchLines = + [&]( const std::vector& hatchLines ) + { + for( const SEG& seg : hatchLines ) + { + TransformOvalToPolygon( m_hatching, seg.A, seg.B, lineWidth, ARC_LOW_DEF, + ERROR_INSIDE ); + } + }; + + switch( m_shape ) + { + case SHAPE_T::ARC: + case SHAPE_T::SEGMENT: + case SHAPE_T::BEZIER: + break; + + case SHAPE_T::RECTANGLE: + shapeBuffer.NewOutline(); + + for( const VECTOR2I& pt : GetRectCorners() ) + shapeBuffer.Append( pt ); + + addHatchLines( shapeBuffer.GenerateHatchLines( slopes, spacing, -1 ) ); + break; + + case SHAPE_T::CIRCLE: + TransformShapeToPolygon( shapeBuffer, 0, ARC_HIGH_DEF, ERROR_INSIDE, true ); + addHatchLines( shapeBuffer.GenerateHatchLines( slopes, spacing, -1 ) ); + break; + + case SHAPE_T::POLY: + if( IsClosed() ) + addHatchLines( m_poly.GenerateHatchLines( slopes, spacing, -1 ) ); + + break; + + default: + UNIMPLEMENTED_FOR( SHAPE_T_asString() ); + break; + } + + m_hatching.Simplify(); +} + + void EDA_SHAPE::move( const VECTOR2I& aMoveVector ) { switch ( m_shape ) @@ -571,6 +683,8 @@ void EDA_SHAPE::move( const VECTOR2I& aMoveVector ) UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } + + m_hatchingDirty = true; } @@ -629,6 +743,8 @@ void EDA_SHAPE::scale( double aScale ) UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } + + m_hatchingDirty = true; } @@ -690,6 +806,8 @@ void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } + + m_hatchingDirty = true; } @@ -733,6 +851,8 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } + + m_hatchingDirty = true; } @@ -799,6 +919,7 @@ void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter ) case SHAPE_T::CIRCLE: m_start = aCenter; + m_hatchingDirty = true; break; default: @@ -1108,8 +1229,13 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const if( IsFilledForHitTesting() ) return dist <= radius + maxdist; // Filled circle hit-test - else - return abs( radius - dist ) <= maxdist; // Ring hit-test + else if( abs( radius - dist ) <= maxdist ) // Ring hit-test + return true; + + if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) ) + return true; + + return false; } case SHAPE_T::ARC: @@ -1188,14 +1314,22 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const return poly.Collide( aPosition, maxdist ); } - else // Open rect hit-test + else { std::vector pts = GetRectCorners(); - return TestSegmentHit( aPosition, pts[0], pts[1], maxdist ) + if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist ) || TestSegmentHit( aPosition, pts[1], pts[2], maxdist ) || TestSegmentHit( aPosition, pts[2], pts[3], maxdist ) - || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ); + || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) ) + { + return true; + } + + if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) ) + return true; + + return false; } case SHAPE_T::POLY: @@ -1215,7 +1349,13 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const } else { - return m_poly.CollideEdge( aPosition, nullptr, maxdist ); + if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) ) + return true; + + if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) ) + return true; + + return false; } default: @@ -1262,7 +1402,7 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co if( !arect.Intersects( bbox ) ) return false; - if( IsFilled() ) + if( IsAnyFill() ) { return ( arect.Intersects( getCenter(), GetStart() ) || arect.Intersects( getCenter(), GetEnd() ) @@ -1424,7 +1564,7 @@ void EDA_SHAPE::computeArcBBox( BOX2I& aBBox ) const aBBox.SetOrigin( m_start ); aBBox.Merge( m_end ); - if( IsFilled() ) + if( IsAnyFill() ) aBBox.Merge( m_arcCenter ); int radius = GetRadius(); @@ -1495,27 +1635,29 @@ std::vector EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh { std::vector pts = GetRectCorners(); - if( ( IsFilled() || IsProxyItem() ) && !aEdgeOnly ) + if( ( IsSolidFill() || IsProxyItem() ) && !aEdgeOnly ) effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) ); - if( width > 0 || !IsFilled() || aEdgeOnly ) + if( width > 0 || !IsSolidFill() || aEdgeOnly ) { 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; + } case SHAPE_T::CIRCLE: { - if( IsFilled() && !aEdgeOnly ) + if( IsSolidFill() && !aEdgeOnly ) effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) ); - if( width > 0 || !IsFilled() || aEdgeOnly ) + if( width > 0 || !IsSolidFill() || aEdgeOnly ) + { effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) ); + } break; } @@ -1544,10 +1686,10 @@ std::vector EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh { const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii ); - if( IsFilled() && !aEdgeOnly ) + if( IsSolidFill() && !aEdgeOnly ) effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) ); - if( width > 0 || !IsFilled() || aEdgeOnly ) + if( width > 0 || !IsSolidFill() || aEdgeOnly ) { int segCount = l.SegmentCount(); @@ -1566,6 +1708,12 @@ std::vector EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh break; } + if( IsHatchedFill() ) + { + for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii ) + effectiveShapes.emplace_back( new SHAPE_SIMPLE( GetHatching().COutline( ii ) ) ); + } + return effectiveShapes; } @@ -1945,7 +2093,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance { int r = GetRadius(); - if( IsFilled() ) + if( IsSolidFill() ) TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc ); else TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc ); @@ -1957,7 +2105,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance { std::vector pts = GetRectCorners(); - if( IsFilled() || IsProxyItem() ) + if( IsSolidFill() || IsProxyItem() ) { aBuffer.NewOutline(); @@ -1965,7 +2113,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance aBuffer.Append( pt ); } - if( width > 0 || !IsFilled() ) + if( width > 0 || !IsSolidFill() ) { // Add in segments TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc ); @@ -1991,7 +2139,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance if( !IsPolyShapeValid() ) break; - if( IsFilled() ) + if( IsSolidFill() ) { for( int ii = 0; ii < m_poly.OutlineCount(); ++ii ) { @@ -2049,6 +2197,19 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } + + if( IsHatchedFill() ) + { + for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii ) + aBuffer.AddOutline( GetHatching().COutline( ii ) ); + } +} + + +void EDA_SHAPE::SetWidth( int aWidth ) +{ + m_stroke.SetWidth( aWidth ); + m_hatchingDirty = true; } @@ -2192,6 +2353,7 @@ double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const IMPLEMENT_ENUM_TO_WXANY( SHAPE_T ) IMPLEMENT_ENUM_TO_WXANY( LINE_STYLE ) +IMPLEMENT_ENUM_TO_WXANY( UI_FILL_MODE ) static struct EDA_SHAPE_DESC @@ -2217,6 +2379,17 @@ static struct EDA_SHAPE_DESC .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) ); } + ENUM_MAP& hatchModeEnum = ENUM_MAP::Instance(); + + if( hatchModeEnum.Choices().GetCount() == 0 ) + { + hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) ); + hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) ); + hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) ); + hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) ); + hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) ); + } + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( EDA_SHAPE ); @@ -2361,8 +2534,8 @@ static struct EDA_SHAPE_DESC return false; }; - propMgr.AddProperty( new PROPERTY( _HKI( "Filled" ), - &EDA_SHAPE::SetFilled, &EDA_SHAPE::IsFilled ), + propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Fill" ), + &EDA_SHAPE::SetFillModeProp, &EDA_SHAPE::GetFillModeProp ), shapeProps ) .SetAvailableFunc( fillAvailable ); diff --git a/common/hash_eda.cpp b/common/hash_eda.cpp index 888f681b95..6f500e9150 100644 --- a/common/hash_eda.cpp +++ b/common/hash_eda.cpp @@ -214,7 +214,7 @@ size_t hash_fp_item( const EDA_ITEM* aItem, int aFlags ) ret = hash_board_item( shape, aFlags ); hash_combine( ret, shape->GetShape() ); hash_combine( ret, shape->GetWidth() ); - hash_combine( ret, shape->IsFilled() ); + hash_combine( ret, shape->GetFillMode() ); hash_combine( ret, shape->GetLineStyle() ); if( shape->GetShape() == SHAPE_T::ARC || shape->GetShape() == SHAPE_T::CIRCLE ) diff --git a/common/pcb.keywords b/common/pcb.keywords index f7b4bfdb3b..effdcf267e 100644 --- a/common/pcb.keywords +++ b/common/pcb.keywords @@ -82,6 +82,7 @@ connect_pads copperpour copper_finish crossbar +cross_hatch curve_points curved_edges custom @@ -288,6 +289,7 @@ rect_delta reference remove_unused_layers render_cache +reverse_hatch right rotate roundrect diff --git a/common/plotters/SVG_plotter.cpp b/common/plotters/SVG_plotter.cpp index b3d113578e..5c43bbb3ee 100644 --- a/common/plotters/SVG_plotter.cpp +++ b/common/plotters/SVG_plotter.cpp @@ -605,6 +605,9 @@ void SVG_PLOTTER::PlotPoly( const std::vector& aCornerList, FILL_T aFi switch( aFill ) { case FILL_T::NO_FILL: + case FILL_T::HATCH: + case FILL_T::REVERSE_HATCH: + case FILL_T::CROSS_HATCH: setSVGPlotStyle( aWidth, false, "fill:none" ); break; diff --git a/eeschema/dialogs/dialog_shape_properties.cpp b/eeschema/dialogs/dialog_shape_properties.cpp index 17bd503c85..41b4e439bd 100644 --- a/eeschema/dialogs/dialog_shape_properties.cpp +++ b/eeschema/dialogs/dialog_shape_properties.cpp @@ -201,7 +201,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataToWindow() } else { - m_filledCtrl->SetValue( m_shape->IsFilled() ); + m_fillCtrl->SetSelection( m_shape->GetFillModeProp() ); m_fillColorSwatch->SetSwatchColor( m_shape->GetFillColor(), false ); } @@ -233,12 +233,10 @@ void DIALOG_SHAPE_PROPERTIES::onBorderChecked( wxCommandEvent& event ) } -void DIALOG_SHAPE_PROPERTIES::onFillChecked( wxCommandEvent& aEvent ) +void DIALOG_SHAPE_PROPERTIES::onFillChoice( wxCommandEvent& event ) { - bool fill = m_filledCtrl->GetValue(); - - m_fillColorLabel->Enable( fill ); - m_fillColorSwatch->Enable( fill ); + m_fillColorLabel->Enable( m_fillCtrl->GetSelection() == UI_FILL_MODE::SOLID ); + m_fillColorSwatch->Enable( m_fillCtrl->GetSelection() == UI_FILL_MODE::SOLID ); } @@ -362,11 +360,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow() } else { - if( m_filledCtrl->GetValue() ) - m_shape->SetFillMode( FILL_T::FILLED_WITH_COLOR ); - else - m_shape->SetFillMode( FILL_T::NO_FILL ); - + m_shape->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() ); m_shape->SetFillColor( m_fillColorSwatch->GetSwatchColor() ); } diff --git a/eeschema/dialogs/dialog_shape_properties.h b/eeschema/dialogs/dialog_shape_properties.h index 12dc8b243e..8c27810f83 100644 --- a/eeschema/dialogs/dialog_shape_properties.h +++ b/eeschema/dialogs/dialog_shape_properties.h @@ -48,7 +48,7 @@ public: private: void onBorderChecked( wxCommandEvent& aEvent) override; void onBorderSwatch( wxCommandEvent& aEvent ); - void onFillChecked( wxCommandEvent& aEvent ) override; + void onFillChoice( wxCommandEvent& event ) override; void onFillRadioButton(wxCommandEvent &aEvent) override; void onCustomColorSwatch( wxCommandEvent& aEvent ); diff --git a/eeschema/dialogs/dialog_shape_properties_base.cpp b/eeschema/dialogs/dialog_shape_properties_base.cpp index bae739dc84..1d2ac09622 100644 --- a/eeschema/dialogs/dialog_shape_properties_base.cpp +++ b/eeschema/dialogs/dialog_shape_properties_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf02) +// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -14,7 +14,7 @@ BEGIN_EVENT_TABLE( DIALOG_SHAPE_PROPERTIES_BASE, DIALOG_SHIM ) EVT_CHECKBOX( wxID_ANY, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onBorderChecked ) - EVT_CHECKBOX( wxID_ANY, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onFillChecked ) + EVT_CHOICE( wxID_ANY, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onFillChoice ) EVT_RADIOBUTTON( NO_FILL, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onFillRadioButton ) EVT_RADIOBUTTON( FILLED_SHAPE, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onFillRadioButton ) EVT_RADIOBUTTON( FILLED_WITH_BG_BODYCOLOR, DIALOG_SHAPE_PROPERTIES_BASE::_wxFB_onFillRadioButton ) @@ -112,15 +112,19 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx m_fillSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); m_fillSizer->SetEmptyCellSize( wxSize( 36,12 ) ); - m_filledCtrl = new wxCheckBox( m_schematicPage, wxID_ANY, _("Filled shape"), wxDefaultPosition, wxDefaultSize, 0 ); - m_fillSizer->Add( m_filledCtrl, wxGBPosition( 0, 0 ), wxGBSpan( 1, 2 ), wxBOTTOM, 2 ); + m_fillLabel = new wxStaticText( m_schematicPage, wxID_ANY, _("Fill:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_fillLabel->Wrap( -1 ); + m_fillSizer->Add( m_fillLabel, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL, 5 ); - wxBoxSizer* bSizer81; - bSizer81 = new wxBoxSizer( wxHORIZONTAL ); + wxString m_fillCtrlChoices[] = { _("None"), _("Solid"), _("Hatch"), _("Reverse Hatch"), _("Cross-hatch") }; + int m_fillCtrlNChoices = sizeof( m_fillCtrlChoices ) / sizeof( wxString ); + m_fillCtrl = new wxChoice( m_schematicPage, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_fillCtrlNChoices, m_fillCtrlChoices, 0 ); + m_fillCtrl->SetSelection( 0 ); + m_fillSizer->Add( m_fillCtrl, wxGBPosition( 0, 1 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); m_fillColorLabel = new wxStaticText( m_schematicPage, wxID_ANY, _("Fill color:"), wxDefaultPosition, wxDefaultSize, 0 ); m_fillColorLabel->Wrap( -1 ); - bSizer81->Add( m_fillColorLabel, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); + m_fillSizer->Add( m_fillColorLabel, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL, 5 ); m_panelFillColor = new wxPanel( m_schematicPage, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE|wxTAB_TRAVERSAL ); wxBoxSizer* bSizer21; @@ -133,10 +137,7 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx m_panelFillColor->SetSizer( bSizer21 ); m_panelFillColor->Layout(); bSizer21->Fit( m_panelFillColor ); - bSizer81->Add( m_panelFillColor, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - - m_fillSizer->Add( bSizer81, wxGBPosition( 1, 0 ), wxGBSpan( 1, 2 ), wxEXPAND, 5 ); + m_fillSizer->Add( m_panelFillColor, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL, 5 ); m_helpLabel2 = new wxStaticText( m_schematicPage, wxID_ANY, _("Clear colors to use Schematic Editor colors."), wxDefaultPosition, wxDefaultSize, 0 ); m_helpLabel2->Wrap( -1 ); diff --git a/eeschema/dialogs/dialog_shape_properties_base.fbp b/eeschema/dialogs/dialog_shape_properties_base.fbp index 8a2c9713e5..f9a0738a58 100644 --- a/eeschema/dialogs/dialog_shape_properties_base.fbp +++ b/eeschema/dialogs/dialog_shape_properties_base.fbp @@ -1,34 +1,36 @@ - + - C++ - 1 - source_name - 0 - 0 + + 1 + table + none + + + 0 + 1 res UTF-8 - table dialog_shape_properties_base 1000 - none - - 1 + 1 + UI dialog_shape_properties - . - + 0 + source_name + 1 + 0 + source_name + + + 1 1 - 1 - 1 - 1 - UI - 0 - 1 0 + 0 0 wxAUI_MGR_DEFAULT @@ -71,10 +73,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -159,10 +161,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -228,10 +230,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -302,10 +304,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -367,10 +369,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -429,10 +431,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -501,10 +503,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -562,10 +564,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -633,10 +635,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -698,10 +700,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -767,10 +769,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -841,10 +843,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -896,10 +898,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -964,27 +966,26 @@ protected 3 - 2 - 2 + 5 + 1 0 - wxBOTTOM + wxALIGN_CENTER_VERTICAL 0 1 - + 1 1 1 1 - + 0 - - + 0 + 0 1 0 - 0 1 1 @@ -1000,7 +1001,8 @@ 0 0 wxID_ANY - Filled shape + Fill: + 0 0 @@ -1008,7 +1010,7 @@ 0 1 - m_filledCtrl + m_fillLabel 1 @@ -1022,6 +1024,71 @@ ; ; forward_declare 0 + + + + -1 + + + + 5 + 1 + 1 + wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + "None" "Solid" "Hatch" "Reverse Hatch" "Cross-hatch" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_fillCtrl + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ; ; forward_declare + 0 + wxFILTER_NONE wxDefaultValidator @@ -1029,207 +1096,199 @@ - onFillChecked + onFillChoice 5 - 2 + 1 0 - wxEXPAND + wxALIGN_CENTER_VERTICAL 1 1 - + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Fill color: + 0 + + 0 + + + 0 - bSizer81 - wxHORIZONTAL - none - - 5 - wxRIGHT|wxALIGN_CENTER_VERTICAL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - Fill color: - 0 - - 0 - - - 0 - - 1 - m_fillColorLabel - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - - - 5 - wxALIGN_CENTER_VERTICAL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - m_panelFillColor - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxBORDER_SIMPLE|wxTAB_TRAVERSAL - + 1 + m_fillColorLabel + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + 1 + 1 + wxALIGN_CENTER_VERTICAL + 1 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panelFillColor + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxBORDER_SIMPLE|wxTAB_TRAVERSAL + + + bSizer21 + wxVERTICAL + none + + 5 + wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + COLOR_SWATCH + 1 + + + 1 + + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 - bSizer21 - wxVERTICAL - none - - 5 - wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - COLOR_SWATCH - 1 - - - 1 - - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - - 0 - - - 0 - - 1 - m_fillColorSwatch - 1 - - - protected - 1 - - Resizable - - 1 - - COLOR_SWATCH; widgets/color_swatch.h; forward_declare - 0 - - - - - - + 1 + m_fillColorSwatch + 1 + + + protected + 1 + + Resizable + + 1 + + COLOR_SWATCH; widgets/color_swatch.h; forward_declare + 0 + + + + @@ -1247,10 +1306,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1313,10 +1372,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1404,10 +1463,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1473,10 +1532,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1542,10 +1601,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1611,10 +1670,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1680,10 +1739,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1791,10 +1850,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1877,10 +1936,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1942,10 +2001,10 @@ 1 1 1 - + 0 - - + 0 + 0 diff --git a/eeschema/dialogs/dialog_shape_properties_base.h b/eeschema/dialogs/dialog_shape_properties_base.h index 6538ca6b25..bfb8c9d7c9 100644 --- a/eeschema/dialogs/dialog_shape_properties_base.h +++ b/eeschema/dialogs/dialog_shape_properties_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf02) +// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -27,6 +27,7 @@ class WX_INFOBAR; #include #include #include +#include #include #include #include @@ -45,7 +46,7 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM // Private event handlers void _wxFB_onBorderChecked( wxCommandEvent& event ){ onBorderChecked( event ); } - void _wxFB_onFillChecked( wxCommandEvent& event ){ onFillChecked( event ); } + void _wxFB_onFillChoice( wxCommandEvent& event ){ onFillChoice( event ); } void _wxFB_onFillRadioButton( wxCommandEvent& event ){ onFillRadioButton( event ); } @@ -55,7 +56,7 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM NO_FILL = 1000, FILLED_SHAPE, FILLED_WITH_BG_BODYCOLOR, - FILLED_WITH_COLOR + FILLED_WITH_COLOR, }; WX_INFOBAR* m_infoBar; @@ -73,7 +74,8 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM wxSimplebook* m_fillBook; wxPanel* m_schematicPage; wxGridBagSizer* m_fillSizer; - wxCheckBox* m_filledCtrl; + wxStaticText* m_fillLabel; + wxChoice* m_fillCtrl; wxStaticText* m_fillColorLabel; wxPanel* m_panelFillColor; COLOR_SWATCH* m_fillColorSwatch; @@ -94,7 +96,7 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM // Virtual event handlers, override them in your derived class virtual void onBorderChecked( wxCommandEvent& event ) { event.Skip(); } - virtual void onFillChecked( wxCommandEvent& event ) { event.Skip(); } + virtual void onFillChoice( wxCommandEvent& event ) { event.Skip(); } virtual void onFillRadioButton( wxCommandEvent& event ) { event.Skip(); } diff --git a/eeschema/dialogs/dialog_tablecell_properties.cpp b/eeschema/dialogs/dialog_tablecell_properties.cpp index c9b104eb73..3a7f2ae081 100644 --- a/eeschema/dialogs/dialog_tablecell_properties.cpp +++ b/eeschema/dialogs/dialog_tablecell_properties.cpp @@ -196,7 +196,7 @@ bool DIALOG_TABLECELL_PROPERTIES::TransferDataToWindow() m_fillColorBook->SetSelection( 1 ); - if( cell->IsFilled() ) + if( cell->IsSolidFill() ) m_fillColorSwatch->SetSwatchColor( cell->GetFillColor(), false ); else m_fillColorSwatch->SetSwatchColor( COLOR4D::UNSPECIFIED, false ); @@ -241,7 +241,7 @@ bool DIALOG_TABLECELL_PROPERTIES::TransferDataToWindow() m_textColorPopup->SetSelection( 0 ); } - COLOR4D fillColor = cell->IsFilled() ? cell->GetFillColor() : COLOR4D::UNSPECIFIED; + COLOR4D fillColor = cell->IsSolidFill() ? cell->GetFillColor() : COLOR4D::UNSPECIFIED; if( fillColor != m_fillColorSwatch->GetSwatchColor() ) { diff --git a/eeschema/dialogs/dialog_text_properties.cpp b/eeschema/dialogs/dialog_text_properties.cpp index 02461966bc..42e2157dc5 100644 --- a/eeschema/dialogs/dialog_text_properties.cpp +++ b/eeschema/dialogs/dialog_text_properties.cpp @@ -332,11 +332,11 @@ bool DIALOG_TEXT_PROPERTIES::TransferDataToWindow() m_borderStyleLabel->Enable( textBox->GetWidth() >= 0 ); m_borderStyleCombo->Enable( textBox->GetWidth() >= 0 ); - m_filledCtrl->SetValue( textBox->IsFilled() ); + m_filledCtrl->SetValue( textBox->IsSolidFill() ); m_fillColorSwatch->SetSwatchColor( textBox->GetFillColor(), false ); - m_fillColorLabel->Enable( textBox->IsFilled() ); - m_fillColorSwatch->Enable( textBox->IsFilled() ); + m_fillColorLabel->Enable( textBox->IsSolidFill() ); + m_fillColorSwatch->Enable( textBox->IsSolidFill() ); } if( m_isSymbolEditor ) diff --git a/eeschema/sch_file_versions.h b/eeschema/sch_file_versions.h index 2324d4afca..528b745c36 100644 --- a/eeschema/sch_file_versions.h +++ b/eeschema/sch_file_versions.h @@ -114,4 +114,5 @@ //#define SEXPR_SCHEMATIC_FILE_VERSION 20240819 // Embedded Files - Update hash algorithm to Murmur3 //#define SEXPR_SCHEMATIC_FILE_VERSION 20241004 // Use booleans for 'hide' in symbols //#define SEXPR_SCHEMATIC_FILE_VERSION 20241209 // Private flags for SCH_FIELDs -#define SEXPR_SCHEMATIC_FILE_VERSION 20250114 // Full paths for text variable cross references +//#define SEXPR_SCHEMATIC_FILE_VERSION 20250114 // Full paths for text variable cross references +#define SEXPR_SCHEMATIC_FILE_VERSION 20250222 // Hatched fills for shapes 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 1ce5d1ff72..7f1c14b34e 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 @@ -37,10 +37,13 @@ void formatFill( OUTPUTFORMATTER* aFormatter, FILL_T aFillMode, const COLOR4D& a switch( aFillMode ) { default: - case FILL_T::NO_FILL: fillType = "none"; break; - case FILL_T::FILLED_SHAPE: fillType = "outline"; break; - case FILL_T::FILLED_WITH_BG_BODYCOLOR: fillType = "background"; break; - case FILL_T::FILLED_WITH_COLOR: fillType = "color"; break; + case FILL_T::NO_FILL: fillType = "none"; break; + case FILL_T::FILLED_SHAPE: fillType = "outline"; break; + case FILL_T::FILLED_WITH_BG_BODYCOLOR: fillType = "background"; break; + case FILL_T::FILLED_WITH_COLOR: fillType = "color"; break; + case FILL_T::HATCH: fillType = "hatch"; break; + case FILL_T::REVERSE_HATCH: fillType = "reverse_hatch"; break; + case FILL_T::CROSS_HATCH: fillType = "cross_hatch"; break; } if( aFillMode == FILL_T::FILLED_WITH_COLOR ) 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 c81d8f17af..6e2196219a 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 @@ -660,11 +660,15 @@ void SCH_IO_KICAD_SEXPR_PARSER::parseFill( FILL_PARAMS& aFill ) switch( token ) { - case T_none: aFill.m_FillType = FILL_T::NO_FILL; break; - case T_outline: aFill.m_FillType = FILL_T::FILLED_SHAPE; break; - case T_background: aFill.m_FillType = FILL_T::FILLED_WITH_BG_BODYCOLOR; break; - case T_color: aFill.m_FillType = FILL_T::FILLED_WITH_COLOR; break; - default: Expecting( "none, outline, color or background" ); + case T_none: aFill.m_FillType = FILL_T::NO_FILL; break; + case T_outline: aFill.m_FillType = FILL_T::FILLED_SHAPE; break; + case T_background: aFill.m_FillType = FILL_T::FILLED_WITH_BG_BODYCOLOR; break; + case T_color: aFill.m_FillType = FILL_T::FILLED_WITH_COLOR; break; + case T_hatch: aFill.m_FillType = FILL_T::HATCH; break; + case T_reverse_hatch: aFill.m_FillType = FILL_T::REVERSE_HATCH; break; + case T_cross_hatch: aFill.m_FillType = FILL_T::CROSS_HATCH; break; + default: Expecting( "none, outline, hatch, reverse_hatch, " + "cross_hatch, color or background" ); } NeedRIGHT(); diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 60753ce675..4c9238e679 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -1503,7 +1503,7 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) // Consider a NAND gate. We have no idea which side of the arc is "inside" // so we can't reliably fill. if( aShape->GetShape() == SHAPE_T::ARC ) - m_gal->SetIsFill( aShape->IsFilled() ); + m_gal->SetIsFill( aShape->IsSolidFill() ); else m_gal->SetIsFill( true ); @@ -1534,6 +1534,20 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) // Fill in the foreground layer break; + case FILL_T::HATCH: + case FILL_T::REVERSE_HATCH: + case FILL_T::CROSS_HATCH: + if( aShape->IsSelected() ) + color.a = color.a * 0.8; // selected items already have reduced-alpha backgrounds + else + color.a = color.a * 0.4; + + m_gal->SetIsFill( true ); + m_gal->SetIsStroke( false ); + m_gal->SetFillColor( color ); + m_gal->DrawPolygon( aShape->GetHatching() ); + break; + case FILL_T::FILLED_WITH_COLOR: case FILL_T::FILLED_WITH_BG_BODYCOLOR: // Do not fill the shape in B&W print mode, to avoid to visible items inside the shape @@ -1546,6 +1560,9 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) drawShape( aShape ); } break; + + default: + wxFAIL_MSG( wxT( "Unsupported fill type" ) ); } } else if( aLayer == LAYER_DEVICE || aLayer == LAYER_NOTES || aLayer == LAYER_PRIVATE_NOTES @@ -1919,7 +1936,7 @@ void SCH_PAINTER::draw( const SCH_TEXTBOX* aTextBox, int aLayer, bool aDimmed ) { // Do not fill the shape in B&W print mode, to avoid to visible items // inside the shape - if( aTextBox->IsFilled() && !m_schSettings.PrintBlackAndWhiteReq() ) + if( aTextBox->IsSolidFill() && !m_schSettings.PrintBlackAndWhiteReq() ) { m_gal->SetIsFill( true ); m_gal->SetIsStroke( false ); diff --git a/eeschema/sch_rule_area.cpp b/eeschema/sch_rule_area.cpp index 095b0434c1..6800ac312d 100644 --- a/eeschema/sch_rule_area.cpp +++ b/eeschema/sch_rule_area.cpp @@ -79,10 +79,10 @@ std::vector SCH_RULE_AREA::MakeEffectiveShapes( bool aEdgeOnly ) const { const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii ); - if( IsFilled() && !aEdgeOnly ) + if( IsSolidFill() && !aEdgeOnly ) effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) ); - if( width > 0 || !IsFilled() || aEdgeOnly ) + if( width > 0 || !IsSolidFill() || aEdgeOnly ) { int segCount = l.SegmentCount(); @@ -95,7 +95,6 @@ std::vector SCH_RULE_AREA::MakeEffectiveShapes( bool aEdgeOnly ) const default: return SCH_SHAPE::MakeEffectiveShapes( aEdgeOnly ); - break; } return effectiveShapes; diff --git a/eeschema/sch_shape.cpp b/eeschema/sch_shape.cpp index e62ca2ac21..a8e3f2a7e5 100644 --- a/eeschema/sch_shape.cpp +++ b/eeschema/sch_shape.cpp @@ -170,19 +170,34 @@ void SCH_SHAPE::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& if( aBackground ) { - if( !aPlotter->GetColorMode() ) - return; - switch( m_fill ) { case FILL_T::FILLED_SHAPE: + // Fill in the foreground layer return; + case FILL_T::HATCH: + case FILL_T::REVERSE_HATCH: + case FILL_T::CROSS_HATCH: + if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED ) + color = renderSettings->GetLayerColor( m_layer ); + + color.a = color.a * 0.4; + break; + case FILL_T::FILLED_WITH_COLOR: + // drop fill in B&W mode + if( !aPlotter->GetColorMode() ) + return; + color = GetFillColor(); break; case FILL_T::FILLED_WITH_BG_BODYCOLOR: + // drop fill in B&W mode + if( !aPlotter->GetColorMode() ) + return; + color = renderSettings->GetLayerColor( LAYER_DEVICE_BACKGROUND ); break; @@ -219,6 +234,15 @@ void SCH_SHAPE::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& } aPlotter->SetColor( color ); + + if( aBackground && IsHatchedFill() ) + { + for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii ) + aPlotter->PlotPoly( GetHatching().COutline( ii ), FILL_T::FILLED_SHAPE, 0 ); + + return; + } + aPlotter->SetCurrentLineWidth( pen_size ); aPlotter->SetDash( pen_size, lineStyle ); @@ -505,7 +529,7 @@ static struct SCH_SHAPE_DESC if( shape->GetParentSymbol() ) return shape->GetFillMode() == FILL_T::FILLED_WITH_COLOR; else - return shape->IsFilled(); + return shape->IsSolidFill(); } return true; @@ -525,7 +549,7 @@ static struct SCH_SHAPE_DESC void ( SCH_SHAPE::*fillModeSetter )( FILL_T ) = &SCH_SHAPE::SetFillMode; FILL_T ( SCH_SHAPE::*fillModeGetter )() const = &SCH_SHAPE::GetFillMode; - propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Fill" ), + propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Fill Mode" ), fillModeSetter, fillModeGetter ), _HKI( "Shape Properties" ) ) .SetAvailableFunc( isSymbolItem ); diff --git a/eeschema/sch_shape.h b/eeschema/sch_shape.h index 404afd7e46..2d9b4eb5f7 100644 --- a/eeschema/sch_shape.h +++ b/eeschema/sch_shape.h @@ -66,6 +66,16 @@ public: return m_stroke.GetLineStyle(); } + int GetHatchLineWidth() const override + { + return GetEffectiveWidth() / 2; + } + + int GetHatchLineSpacing() const override + { + return GetHatchLineWidth() * 40; + } + void SetFilled( bool aFilled ) override; const BOX2I GetBoundingBox() const override; diff --git a/eeschema/schematic.keywords b/eeschema/schematic.keywords index 9ff47e346e..7fc44eb0e9 100644 --- a/eeschema/schematic.keywords +++ b/eeschema/schematic.keywords @@ -27,6 +27,7 @@ column_widths comment company convert +cross_hatch data date default @@ -55,6 +56,7 @@ free generator generator_version global_label +hatch header hide hierarchical_label @@ -128,6 +130,7 @@ rectangle reference required rev +reverse_hatch right round rows diff --git a/eeschema/tools/ee_point_editor.cpp b/eeschema/tools/ee_point_editor.cpp index 8b0529306e..1e14f9db1f 100644 --- a/eeschema/tools/ee_point_editor.cpp +++ b/eeschema/tools/ee_point_editor.cpp @@ -1368,6 +1368,8 @@ int EE_POINT_EDITOR::removeCorner( const TOOL_EVENT& aEvent ) poly.Remove( idx ); } + shape->SetHatchingDirty(); + setEditedPoint( nullptr ); updateItem( shape, true ); diff --git a/eeschema/tools/ee_selection_tool.cpp b/eeschema/tools/ee_selection_tool.cpp index f9345ce182..298a71ae57 100644 --- a/eeschema/tools/ee_selection_tool.cpp +++ b/eeschema/tools/ee_selection_tool.cpp @@ -1631,7 +1631,7 @@ void EE_SELECTION_TOOL::GuessSelectionCandidates( EE_COLLECTOR& collector, const } // Filled shapes win hit tests anywhere inside them - dominating = shape->IsFilled(); + dominating = shape->IsFilledForHitTesting(); } else if( symbol ) { diff --git a/include/eda_shape.h b/include/eda_shape.h index 4b6cca3bd7..0c5376a593 100644 --- a/include/eda_shape.h +++ b/include/eda_shape.h @@ -57,7 +57,20 @@ enum class FILL_T : int NO_FILL = 1, FILLED_SHAPE, ///< Fill with object color. FILLED_WITH_BG_BODYCOLOR, //< Fill with background body color. - FILLED_WITH_COLOR //< Fill with a separate color. + FILLED_WITH_COLOR, //< Fill with a separate color. + HATCH, + REVERSE_HATCH, + CROSS_HATCH +}; + + +enum UI_FILL_MODE +{ + NONE = 0, + SOLID, + HATCH, + REVERSE_HATCH, + CROSS_HATCH }; @@ -70,6 +83,7 @@ struct ARC_MID VECTOR2I center; }; + class EDA_SHAPE : public SERIALIZABLE { public: @@ -95,14 +109,28 @@ public: virtual bool IsProxyItem() const { return m_proxyItem; } virtual void SetIsProxyItem( bool aIsProxy = true ) { m_proxyItem = aIsProxy; } - bool IsFilled() const + bool IsAnyFill() const { return GetFillMode() != FILL_T::NO_FILL; } + bool IsSolidFill() const + { + return GetFillMode() == FILL_T::FILLED_SHAPE + || GetFillMode() == FILL_T::FILLED_WITH_COLOR + || GetFillMode() == FILL_T::FILLED_WITH_BG_BODYCOLOR; + } + + bool IsHatchedFill() const + { + return GetFillMode() == FILL_T::HATCH + || GetFillMode() == FILL_T::REVERSE_HATCH + || GetFillMode() == FILL_T::CROSS_HATCH; + } + virtual bool IsFilledForHitTesting() const { - return IsFilled(); + return IsSolidFill(); } virtual void SetFilled( bool aFlag ) @@ -110,17 +138,25 @@ public: setFilled( aFlag ); } - void SetFillMode( FILL_T aFill ) { m_fill = aFill; } + void SetFillMode( FILL_T aFill ); FILL_T GetFillMode() const { return m_fill; } + void SetFillModeProp( UI_FILL_MODE ); + UI_FILL_MODE GetFillModeProp() const; + + void SetHatchingDirty() { m_hatchingDirty = true; } + const SHAPE_POLY_SET& GetHatching() const; + bool IsClosed() const; COLOR4D GetFillColor() const { return m_fillColor; } void SetFillColor( const COLOR4D& aColor ) { m_fillColor = aColor; } - void SetWidth( int aWidth ) { m_stroke.SetWidth( aWidth ); } + void SetWidth( int aWidth ); virtual int GetWidth() const { return m_stroke.GetWidth(); } virtual int GetEffectiveWidth() const { return GetWidth(); } + virtual int GetHatchLineWidth() const { return GetEffectiveWidth(); } + virtual int GetHatchLineSpacing() const { return GetHatchLineWidth() * 10; } void SetLineStyle( const LINE_STYLE aStyle ); LINE_STYLE GetLineStyle() const; @@ -142,30 +178,35 @@ public: { m_start = aStart; m_endsSwapped = false; + m_hatchingDirty = true; } void SetStartY( int y ) { m_start.y = y; m_endsSwapped = false; + m_hatchingDirty = true; } void SetStartX( int x ) { m_start.x = x; m_endsSwapped = false; + m_hatchingDirty = true; } void SetCenterY( int y ) { m_end.y += y - m_start.y; m_start.y = y; + m_hatchingDirty = true; } void SetCenterX( int x ) { m_end.x += x - m_start.x; m_start.x = x; + m_hatchingDirty = true; } /** @@ -179,23 +220,27 @@ public: { m_end = aEnd; m_endsSwapped = false; + m_hatchingDirty = true; } void SetEndY( int aY ) { m_end.y = aY; m_endsSwapped = false; + m_hatchingDirty = true; } void SetEndX( int aX ) { m_end.x = aX; m_endsSwapped = false; + m_hatchingDirty = true; } void SetRadius( int aX ) { m_end = m_start + VECTOR2I( aX, 0 ); + m_hatchingDirty = true; } virtual VECTOR2I GetTopLeft() const { return GetStart(); } @@ -415,6 +460,8 @@ protected: void endEdit( bool aClosed = true ); void setEditState( int aState ) { m_editState = aState; } + virtual void updateHatching() const; + /** * Make a set of #SHAPE objects representing the #EDA_SHAPE. * @@ -429,36 +476,40 @@ protected: std::vector makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly = false ) const; protected: - bool m_endsSwapped; // true if start/end were swapped e.g. SetArcAngleAndEnd - SHAPE_T m_shape; // Shape: line, Circle, Arc - STROKE_PARAMS m_stroke; // Line style, width, etc. - FILL_T m_fill; - COLOR4D m_fillColor; + bool m_endsSwapped; // true if start/end were swapped e.g. SetArcAngleAndEnd + SHAPE_T m_shape; // Shape: line, Circle, Arc + STROKE_PARAMS m_stroke; // Line style, width, etc. + FILL_T m_fill; + COLOR4D m_fillColor; - long long int m_rectangleHeight; - long long int m_rectangleWidth; + mutable SHAPE_POLY_SET m_hatching; + mutable bool m_hatchingDirty; - double m_segmentLength; - EDA_ANGLE m_segmentAngle; + long long int m_rectangleHeight; + long long int m_rectangleWidth; - VECTOR2I m_start; // Line start point or Circle center - VECTOR2I m_end; // Line end point or Circle 3 o'clock point + double m_segmentLength; + EDA_ANGLE m_segmentAngle; - VECTOR2I m_arcCenter; // Used only for Arcs: arc end point - ARC_MID m_arcMidData; // Used to store originating data + VECTOR2I m_start; // Line start point or Circle center + VECTOR2I m_end; // Line end point or Circle 3 o'clock point - VECTOR2I m_bezierC1; // Bezier Control Point 1 - VECTOR2I m_bezierC2; // Bezier Control Point 2 + VECTOR2I m_arcCenter; // Used only for Arcs: arc end point + ARC_MID m_arcMidData; // Used to store originating data - std::vector m_bezierPoints; - SHAPE_POLY_SET m_poly; // Stores the S_POLYGON shape + VECTOR2I m_bezierC1; // Bezier Control Point 1 + VECTOR2I m_bezierC2; // Bezier Control Point 2 - int m_editState; - bool m_proxyItem; // A shape storing proxy information (ie: a pad - // number box, thermal spoke template, etc.) + std::vector m_bezierPoints; + SHAPE_POLY_SET m_poly; // Stores the S_POLYGON shape + + int m_editState; + bool m_proxyItem; // A shape storing proxy information (ie: a pad + // number box, thermal spoke template, etc.) }; #ifndef SWIG DECLARE_ENUM_TO_WXANY( SHAPE_T ); DECLARE_ENUM_TO_WXANY( LINE_STYLE ); +DECLARE_ENUM_TO_WXANY( UI_FILL_MODE ); #endif diff --git a/include/tool/point_editor_behavior.h b/include/tool/point_editor_behavior.h index 1d53ff0f55..1761e7479d 100644 --- a/include/tool/point_editor_behavior.h +++ b/include/tool/point_editor_behavior.h @@ -180,10 +180,21 @@ class EDA_POLYGON_POINT_EDIT_BEHAVIOR : public POLYGON_POINT_EDIT_BEHAVIOR public: // Editing the underlying polygon shape in-place is enough EDA_POLYGON_POINT_EDIT_BEHAVIOR( EDA_SHAPE& aPolygon ) : - POLYGON_POINT_EDIT_BEHAVIOR( aPolygon.GetPolyShape() ) + POLYGON_POINT_EDIT_BEHAVIOR( aPolygon.GetPolyShape() ), + m_shape( aPolygon ) { wxASSERT( aPolygon.GetShape() == SHAPE_T::POLY ); } + + void UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit, + std::vector& aUpdatedItems ) override + { + POLYGON_POINT_EDIT_BEHAVIOR::UpdateItem( aEditedPoint, aPoints, aCommit, aUpdatedItems ); + m_shape.SetHatchingDirty(); + } + +private: + EDA_SHAPE& m_shape; }; diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 08baeb5d8d..ddb9fb5e11 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -1433,10 +1433,12 @@ public: /** * Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order. * - * @param aPath set of closed outlines forming the polygon. Positive orientation = outline, negative = hole + * @param aPath set of closed outlines forming the polygon. + * Positive orientation = outline, negative = hole * @param aEvenOdd forces the even-off fill rule (default is non zero) */ - void BuildPolysetFromOrientedPaths( const std::vector& aPaths, bool aEvenOdd = false ); + void BuildPolysetFromOrientedPaths( const std::vector& aPaths, + bool aEvenOdd = false ); void TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, ERROR_LOC aErrorLoc ) const override @@ -1444,6 +1446,9 @@ public: aBuffer.Append( *this ); } + const std::vector GenerateHatchLines( const std::vector& aSlopes, int aSpacing, + int aLineLength ) const; + protected: void cacheTriangulation( bool aPartition, bool aSimplify, std::vector>* aHintData ); diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index b224d96c2e..5a123f3a8b 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include #include // for SEG, OPT_VECTOR2I @@ -3140,3 +3141,114 @@ bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseB return false; } + +const std::vector SHAPE_POLY_SET::GenerateHatchLines( const std::vector& aSlopes, + int aSpacing, int aLineLength ) const +{ + std::vector hatchLines; + + // define range for hatch lines + int min_x = CVertex( 0 ).x; + int max_x = CVertex( 0 ).x; + int min_y = CVertex( 0 ).y; + int max_y = CVertex( 0 ).y; + + for( auto iterator = CIterateWithHoles(); iterator; iterator++ ) + { + if( iterator->x < min_x ) + min_x = iterator->x; + + if( iterator->x > max_x ) + max_x = iterator->x; + + if( iterator->y < min_y ) + min_y = iterator->y; + + if( iterator->y > max_y ) + max_y = iterator->y; + } + + auto sortEndsByDescendingX = + []( const VECTOR2I& ref, const VECTOR2I& tst ) + { + return tst.x < ref.x; + }; + + for( double slope : aSlopes ) + { + int64_t max_a, min_a; + + if( slope > 0 ) + { + max_a = KiROUND( max_y - slope * min_x ); + min_a = KiROUND( min_y - slope * max_x ); + } + else + { + max_a = KiROUND( max_y - slope * max_x ); + min_a = KiROUND( min_y - slope * min_x ); + } + + min_a = ( min_a / aSpacing ) * aSpacing; + + // loop through hatch lines + std::vector pointbuffer; + pointbuffer.reserve( 256 ); + + for( int64_t a = min_a; a < max_a; a += aSpacing ) + { + pointbuffer.clear(); + + // Iterate through all vertices + for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ ) + { + const SEG seg = *iterator; + double x, y; + + if( FindLineSegmentIntersection( a, slope, seg.A.x, seg.A.y, seg.B.x, seg.B.y, x, y ) ) + pointbuffer.emplace_back( KiROUND( x ), KiROUND( y ) ); + } + + // sort points in order of descending x (if more than 2) to + // ensure the starting point and the ending point of the same segment + // are stored one just after the other. + if( pointbuffer.size() > 2 ) + sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX ); + + // creates lines or short segments inside the complex polygon + for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip += 2 ) + { + int dx = pointbuffer[ip + 1].x - pointbuffer[ip].x; + + // Push only one line for diagonal hatch or for small lines < twice the line + // length; else push 2 small lines + if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength ) + { + hatchLines.emplace_back( SEG( pointbuffer[ip], pointbuffer[ ip + 1] ) ); + } + else + { + double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y; + slope = dy / dx; + + if( dx > 0 ) + dx = aLineLength; + else + dx = -aLineLength; + + int x1 = KiROUND( pointbuffer[ip].x + dx ); + int x2 = KiROUND( pointbuffer[ip + 1].x - dx ); + int y1 = KiROUND( pointbuffer[ip].y + dx * slope ); + int y2 = KiROUND( pointbuffer[ip + 1].y - dx * slope ); + + hatchLines.emplace_back( SEG( pointbuffer[ip].x, pointbuffer[ip].y, x1, y1 ) ); + + hatchLines.emplace_back( SEG( pointbuffer[ip+1].x, pointbuffer[ip+1].y, x2, + y2 ) ); + } + } + } + } + + return hatchLines; +} diff --git a/pcbnew/board_commit.cpp b/pcbnew/board_commit.cpp index e61b70e436..5d3af59573 100644 --- a/pcbnew/board_commit.cpp +++ b/pcbnew/board_commit.cpp @@ -23,12 +23,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include "thread_pool.h" #include #include #include #include #include #include +#include #include #include #include @@ -125,27 +127,30 @@ COMMIT& BOARD_COMMIT::Stage( const PICKED_ITEMS_LIST& aItems, UNDO_REDO aModFlag } -void BOARD_COMMIT::dirtyIntersectingZones( BOARD_ITEM* item, int aChangeType ) +void BOARD_COMMIT::propagateDamage( BOARD_ITEM* aItem, std::vector* aStaleZones, + std::vector* aStaleHatchedShapes ) { - wxCHECK( item, /* void */ ); + wxCHECK( aItem, /* void */ ); - ZONE_FILLER_TOOL* zoneFillerTool = m_toolMgr->GetTool(); + if( aStaleZones && aItem->Type() == PCB_ZONE_T ) + aStaleZones->push_back( static_cast( aItem ) ); - if( item->Type() == PCB_ZONE_T ) - zoneFillerTool->DirtyZone( static_cast( item ) ); - - item->RunOnChildren( std::bind( &BOARD_COMMIT::dirtyIntersectingZones, this, _1, aChangeType ) ); + aItem->RunOnChildren( std::bind( &BOARD_COMMIT::propagateDamage, this, _1, aStaleZones, + aStaleHatchedShapes ) ); BOARD* board = static_cast( m_toolMgr->GetModel() ); - BOX2I bbox = item->GetBoundingBox(); - LSET layers = item->GetLayerSet(); + BOX2I bbox = aItem->GetBoundingBox(); + LSET layers = aItem->GetLayerSet(); if( layers.test( Edge_Cuts ) || layers.test( Margin ) ) layers = LSET::PhysicalLayersMask(); else layers &= LSET::AllCuMask(); - if( layers.any() ) + if( layers.empty() ) + return; + + if( aStaleZones ) { for( ZONE* zone : board->Zones() ) { @@ -155,7 +160,29 @@ void BOARD_COMMIT::dirtyIntersectingZones( BOARD_ITEM* item, int aChangeType ) if( ( zone->GetLayerSet() & layers ).any() && zone->GetBoundingBox().Intersects( bbox ) ) { - zoneFillerTool->DirtyZone( zone ); + aStaleZones->push_back( zone ); + } + } + } + + if( aStaleHatchedShapes && ( aItem->Type() == PCB_TEXT_T + || aItem->Type() == PCB_TEXTBOX_T + || aItem->Type() == PCB_SHAPE_T ) ) + { + for( BOARD_ITEM* item : board->Drawings() ) + { + if( item->Type() != PCB_SHAPE_T ) + continue; + + PCB_SHAPE* shape = static_cast( item ); + + if( !shape->IsHatchedFill() ) + continue; + + if( ( shape->GetLayerSet() & layers ).any() + && shape->GetBoundingBox().Intersects( bbox ) ) + { + aStaleHatchedShapes->push_back( shape ); } } } @@ -180,6 +207,9 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) std::vector staleTeardropPadsAndVias; std::set staleTeardropTracks; PCB_GROUP* addedGroup = nullptr; + std::vector staleZonesStorage; + std::vector* staleZones = nullptr; + std::vector staleHatchedShapes; if( Empty() ) return; @@ -200,6 +230,7 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) && ( frame && frame->GetPcbNewSettings()->m_AutoRefillZones ) ) { autofillZones = true; + staleZones = &staleZonesStorage; for( ZONE* zone : board->Zones() ) zone->CacheBoundingBox(); @@ -303,8 +334,8 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) if( boardItem->Type() == PCB_GROUP_T || boardItem->Type() == PCB_GENERATOR_T ) addedGroup = static_cast( boardItem ); - if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T ) - dirtyIntersectingZones( boardItem, changeType ); + if( boardItem->Type() != PCB_MARKER_T ) + propagateDamage( boardItem, staleZones, &staleHatchedShapes ); if( view && boardItem->Type() != PCB_NETINFO_T ) view->Add( boardItem ); @@ -333,8 +364,8 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) if( parentFP && !( parentFP->GetFlags() & STRUCT_DELETED ) ) ent.m_parent = parentFP->m_Uuid; - if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T ) - dirtyIntersectingZones( boardItem, changeType ); + if( boardItem->Type() != PCB_MARKER_T ) + propagateDamage( boardItem, staleZones, &staleHatchedShapes ); switch( boardItem->Type() ) { @@ -446,10 +477,10 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) connectivity->Update( boardItem ); } - if( m_isBoardEditor && autofillZones && boardItem->Type() != PCB_MARKER_T ) + if( boardItem->Type() != PCB_MARKER_T ) { - dirtyIntersectingZones( boardItemCopy, changeType ); // before - dirtyIntersectingZones( boardItem, changeType ); // after + propagateDamage( boardItemCopy, staleZones, &staleHatchedShapes ); // before + propagateDamage( boardItem, staleZones, &staleHatchedShapes ); // after } if( view ) @@ -570,7 +601,22 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) m_toolMgr->PostEvent( EVENTS::UnselectedEvent ); if( autofillZones ) + { + ZONE_FILLER_TOOL* zoneFillerTool = m_toolMgr->GetTool(); + + for( ZONE* zone : *staleZones ) + zoneFillerTool->DirtyZone( zone ); + m_toolMgr->PostAction( PCB_ACTIONS::zoneFillDirty ); + } + + for( PCB_SHAPE* shape : staleHatchedShapes ) + { + shape->SetHatchingDirty(); + + if( view ) + view->Update( shape ); + } if( selectedModified ) m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); diff --git a/pcbnew/board_commit.h b/pcbnew/board_commit.h index 2faa8cf67c..12159bae81 100644 --- a/pcbnew/board_commit.h +++ b/pcbnew/board_commit.h @@ -29,6 +29,8 @@ #include class BOARD_ITEM; +class PCB_SHAPE; +class ZONE; class BOARD; class PICKED_ITEMS_LIST; class PCB_TOOL_BASE; @@ -73,7 +75,8 @@ private: EDA_ITEM* makeImage( EDA_ITEM* aItem ) const override; - void dirtyIntersectingZones( BOARD_ITEM* item, int aChangeType ); + void propagateDamage( BOARD_ITEM* aItem, std::vector* aStaleZones, + std::vector* aStaleHatchedShapes ); private: TOOL_MANAGER* m_toolMgr; diff --git a/pcbnew/dialogs/dialog_shape_properties.cpp b/pcbnew/dialogs/dialog_shape_properties.cpp index a48ee91a31..eed9f8d000 100644 --- a/pcbnew/dialogs/dialog_shape_properties.cpp +++ b/pcbnew/dialogs/dialog_shape_properties.cpp @@ -1029,7 +1029,10 @@ DIALOG_SHAPE_PROPERTIES::DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, } if( m_item->GetShape() == SHAPE_T::ARC || m_item->GetShape() == SHAPE_T::SEGMENT ) - m_filledCtrl->Show( false ); + { + m_fillLabel->Show( false ); + m_fillCtrl->Show( false ); + } SetupStandardButtons(); @@ -1079,7 +1082,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataToWindow() if( m_geomSync ) m_geomSync->SetShape( *m_item ); - m_filledCtrl->SetValue( m_item->IsFilled() ); + m_fillCtrl->SetSelection( m_item->GetFillModeProp() ); m_locked->SetValue( m_item->IsLocked() ); m_thickness.SetValue( m_item->GetStroke().GetWidth() ); @@ -1131,22 +1134,18 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow() bool wasLocked = m_item->IsLocked(); - m_item->SetFilled( m_filledCtrl->GetValue() ); + m_item->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() ); m_item->SetLocked( m_locked->GetValue() ); - STROKE_PARAMS stroke = m_item->GetStroke(); - - stroke.SetWidth( m_thickness.GetIntValue() ); + m_item->SetWidth( m_thickness.GetIntValue() ); auto it = lineTypeNames.begin(); std::advance( it, m_lineStyleCombo->GetSelection() ); if( it == lineTypeNames.end() ) - stroke.SetLineStyle( LINE_STYLE::SOLID ); + m_item->SetLineStyle( LINE_STYLE::SOLID ); else - stroke.SetLineStyle( it->first ); - - m_item->SetStroke( stroke ); + m_item->SetLineStyle( it->first ); m_item->SetLayer( ToLAYER_ID( layer ) ); @@ -1197,19 +1196,19 @@ bool DIALOG_SHAPE_PROPERTIES::Validate() break; case SHAPE_T::CIRCLE: - if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 ) + if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 ) errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) ); break; case SHAPE_T::RECTANGLE: - if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 ) + if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 ) errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) ); break; case SHAPE_T::POLY: - if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 ) + if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 ) errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) ); break; @@ -1221,7 +1220,7 @@ bool DIALOG_SHAPE_PROPERTIES::Validate() break; case SHAPE_T::BEZIER: - if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 ) + if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 ) errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) ); break; diff --git a/pcbnew/dialogs/dialog_shape_properties_base.cpp b/pcbnew/dialogs/dialog_shape_properties_base.cpp index b38e32aa38..7859f27b5e 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.cpp +++ b/pcbnew/dialogs/dialog_shape_properties_base.cpp @@ -209,9 +209,6 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx m_locked = new wxCheckBox( this, wxID_ANY, _("Locked"), wxDefaultPosition, wxDefaultSize, 0 ); gbSizer2->Add( m_locked, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ), wxLEFT, 5 ); - m_filledCtrl = new wxCheckBox( this, wxID_ANY, _("Filled shape"), wxDefaultPosition, wxDefaultSize, 0 ); - gbSizer2->Add( m_filledCtrl, wxGBPosition( 1, 0 ), wxGBSpan( 1, 2 ), wxLEFT, 5 ); - m_thicknessLabel = new wxStaticText( this, wxID_ANY, _("Line width:"), wxDefaultPosition, wxDefaultSize, 0 ); m_thicknessLabel->Wrap( -1 ); gbSizer2->Add( m_thicknessLabel, wxGBPosition( 3, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); @@ -234,16 +231,26 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx gbSizer2->Add( m_lineStyleCombo, wxGBPosition( 4, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + m_fillLabel = new wxStaticText( this, wxID_ANY, _("Fill:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_fillLabel->Wrap( -1 ); + gbSizer2->Add( m_fillLabel, wxGBPosition( 5, 0 ), wxGBSpan( 1, 1 ), wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + wxString m_fillCtrlChoices[] = { _("None"), _("Solid"), _("Hatch"), _("Reverse Hatch"), _("Cross-hatch") }; + int m_fillCtrlNChoices = sizeof( m_fillCtrlChoices ) / sizeof( wxString ); + m_fillCtrl = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_fillCtrlNChoices, m_fillCtrlChoices, 0 ); + m_fillCtrl->SetSelection( 0 ); + gbSizer2->Add( m_fillCtrl, wxGBPosition( 5, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + m_LayerLabel = new wxStaticText( this, wxID_ANY, _("Layer:"), wxDefaultPosition, wxDefaultSize, 0 ); m_LayerLabel->Wrap( -1 ); - gbSizer2->Add( m_LayerLabel, wxGBPosition( 8, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + gbSizer2->Add( m_LayerLabel, wxGBPosition( 9, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); m_LayerSelectionCtrl = new PCB_LAYER_BOX_SELECTOR( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - gbSizer2->Add( m_LayerSelectionCtrl, wxGBPosition( 8, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + gbSizer2->Add( m_LayerSelectionCtrl, wxGBPosition( 9, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); m_techLayersLabel = new wxStaticText( this, wxID_ANY, _("Technical Layers:"), wxDefaultPosition, wxDefaultSize, 0 ); m_techLayersLabel->Wrap( -1 ); - gbSizer2->Add( m_techLayersLabel, wxGBPosition( 9, 0 ), wxGBSpan( 1, 1 ), wxALIGN_BOTTOM|wxTOP|wxLEFT, 5 ); + gbSizer2->Add( m_techLayersLabel, wxGBPosition( 10, 0 ), wxGBSpan( 1, 1 ), wxALIGN_BOTTOM|wxTOP|wxLEFT, 5 ); wxFlexGridSizer* fgSizer2; fgSizer2 = new wxFlexGridSizer( 0, 4, 0, 0 ); @@ -268,14 +275,14 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx fgSizer2->Add( m_solderMaskMarginUnit, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - gbSizer2->Add( fgSizer2, wxGBPosition( 10, 0 ), wxGBSpan( 1, 3 ), wxEXPAND, 5 ); + gbSizer2->Add( fgSizer2, wxGBPosition( 11, 0 ), wxGBSpan( 1, 3 ), wxEXPAND, 5 ); m_netLabel = new wxStaticText( this, wxID_ANY, _("Net:"), wxDefaultPosition, wxDefaultSize, 0 ); m_netLabel->Wrap( -1 ); - gbSizer2->Add( m_netLabel, wxGBPosition( 6, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + gbSizer2->Add( m_netLabel, wxGBPosition( 7, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); m_netSelector = new NET_SELECTOR( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); - gbSizer2->Add( m_netSelector, wxGBPosition( 6, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + gbSizer2->Add( m_netSelector, wxGBPosition( 7, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); gbSizer2->AddGrowableCol( 1 ); diff --git a/pcbnew/dialogs/dialog_shape_properties_base.fbp b/pcbnew/dialogs/dialog_shape_properties_base.fbp index 7082c54e09..415799bc98 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.fbp +++ b/pcbnew/dialogs/dialog_shape_properties_base.fbp @@ -1107,74 +1107,6 @@ - - 5 - 2 - 0 - wxLEFT - 1 - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - Filled shape - - 0 - - - 0 - - 1 - m_filledCtrl - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - 5 1 @@ -1507,12 +1439,145 @@ + + 5 + 1 + 0 + wxALL|wxALIGN_CENTER_VERTICAL + 5 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Fill: + 0 + + 0 + + + 0 + + 1 + m_fillLabel + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + 2 + 1 + wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT + 5 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + "None" "Solid" "Hatch" "Reverse Hatch" "Cross-hatch" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_fillCtrl + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 1 0 wxALIGN_CENTER_VERTICAL|wxLEFT - 8 + 9 1 1 @@ -1577,7 +1642,7 @@ 2 1 wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT - 8 + 9 1 1 @@ -1647,7 +1712,7 @@ 1 0 wxALIGN_BOTTOM|wxTOP|wxLEFT - 9 + 10 1 1 @@ -1712,7 +1777,7 @@ 3 0 wxEXPAND - 10 + 11 1 4 @@ -1988,7 +2053,7 @@ 1 0 wxALIGN_CENTER_VERTICAL|wxLEFT - 6 + 7 1 1 @@ -2053,7 +2118,7 @@ 2 1 wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT - 6 + 7 1 1 diff --git a/pcbnew/dialogs/dialog_shape_properties_base.h b/pcbnew/dialogs/dialog_shape_properties_base.h index 4b6e036921..063a7752c4 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.h +++ b/pcbnew/dialogs/dialog_shape_properties_base.h @@ -29,6 +29,7 @@ class PCB_LAYER_BOX_SELECTOR; #include #include #include +#include #include #include #include @@ -68,12 +69,13 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM wxGridBagSizer* m_gbsBezier; wxBoxSizer* m_upperSizer; wxCheckBox* m_locked; - wxCheckBox* m_filledCtrl; wxStaticText* m_thicknessLabel; wxTextCtrl* m_thicknessCtrl; wxStaticText* m_thicknessUnits; wxStaticText* m_lineStyleLabel; wxBitmapComboBox* m_lineStyleCombo; + wxStaticText* m_fillLabel; + wxChoice* m_fillCtrl; wxStaticText* m_LayerLabel; PCB_LAYER_BOX_SELECTOR* m_LayerSelectionCtrl; wxStaticText* m_techLayersLabel; diff --git a/pcbnew/drc/drc_test_provider_edge_clearance.cpp b/pcbnew/drc/drc_test_provider_edge_clearance.cpp index 510c4cbb40..57ffecce33 100644 --- a/pcbnew/drc/drc_test_provider_edge_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_edge_clearance.cpp @@ -186,7 +186,7 @@ bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::Run() if( item->IsOnLayer( Edge_Cuts ) ) stroke.SetWidth( 0 ); - if( shape->GetShape() == SHAPE_T::RECTANGLE && !shape->IsFilled() ) + if( shape->GetShape() == SHAPE_T::RECTANGLE && !shape->IsSolidFill() ) { // A single rectangle for the board would make the RTree useless, so convert // to 4 edges @@ -211,7 +211,7 @@ bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::Run() edges.back()->SetStroke( stroke ); edges.back()->SetParentGroup( nullptr ); } - else if( shape->GetShape() == SHAPE_T::POLY && !shape->IsFilled() ) + else if( shape->GetShape() == SHAPE_T::POLY && !shape->IsSolidFill() ) { // A single polygon for the board would make the RTree useless, so convert // to n edges. diff --git a/pcbnew/drc/drc_test_provider_library_parity.cpp b/pcbnew/drc/drc_test_provider_library_parity.cpp index 3a8da66edc..012cfec742 100644 --- a/pcbnew/drc/drc_test_provider_library_parity.cpp +++ b/pcbnew/drc/drc_test_provider_library_parity.cpp @@ -186,7 +186,7 @@ bool primitiveNeedsUpdate( const std::shared_ptr& a, } TEST( a->GetStroke(), b->GetStroke(), "" ); - TEST( a->IsFilled(), b->IsFilled(), "" ); + TEST( a->GetFillMode(), b->GetFillMode(), "" ); return diff; } @@ -475,7 +475,7 @@ bool shapeNeedsUpdate( const PCB_SHAPE& curr_shape, const PCB_SHAPE& ref_shape ) if( curr_shape.IsOnCopperLayer() ) TEST( curr_shape.GetStroke(), ref_shape.GetStroke(), "" ); - TEST( curr_shape.IsFilled(), ref_shape.IsFilled(), "" ); + TEST( curr_shape.GetFillMode(), ref_shape.GetFillMode(), "" ); TEST( curr_shape.GetLayer(), ref_shape.GetLayer(), "" ); diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index 968833283c..7b3e257b5a 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -2782,7 +2782,7 @@ double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLEC case SHAPE_T::CIRCLE: case SHAPE_T::POLY: { - if( !shape->IsFilled() ) + if( !shape->IsAnyFill() ) return shape->GetWidth() * shape->GetWidth(); KI_FALLTHROUGH; diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index f84d5ad7bd..439a17e0e9 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -2137,6 +2137,11 @@ std::vector PAD::Recombine( bool aIsDryRun, int maxError ) PCB_SHAPE* primitive = static_cast( fpShape->Duplicate() ); primitive->SetParent( nullptr ); + + // Convert any hatched fills to solid + if( primitive->IsAnyFill() ) + primitive->SetFillMode( FILL_T::FILLED_SHAPE ); + primitive->Move( - ShapePos( layer ) ); primitive->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() ); 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 3e3ad0dc42..83e9afd29f 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -1016,7 +1016,28 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_SHAPE* aShape ) const || ( aShape->GetShape() == SHAPE_T::RECTANGLE ) || ( aShape->GetShape() == SHAPE_T::CIRCLE ) ) { - KICAD_FORMAT::FormatBool( m_out, "fill", aShape->IsFilled() ); + switch( aShape->GetFillMode() ) + { + case FILL_T::HATCH: + m_out->Print( "(fill hatch)" ); + break; + + case FILL_T::REVERSE_HATCH: + m_out->Print( "(fill reverse_hatch)" ); + break; + + case FILL_T::CROSS_HATCH: + m_out->Print( "(fill cross_hatch)" ); + break; + + case FILL_T::FILLED_SHAPE: + KICAD_FORMAT::FormatBool( m_out, "fill", true ); + break; + + default: + KICAD_FORMAT::FormatBool( m_out, "fill", false ); + break; + } } if( aShape->IsLocked() ) @@ -1785,7 +1806,7 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad ) const || ( primitive->GetShape() == SHAPE_T::RECTANGLE ) || ( primitive->GetShape() == SHAPE_T::CIRCLE ) ) { - KICAD_FORMAT::FormatBool( m_out, "fill", primitive->IsFilled() ); + KICAD_FORMAT::FormatBool( m_out, "fill", primitive->IsSolidFill() ); } m_out->Print( ")" ); 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 e7911402f2..f4503e7df2 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -174,7 +174,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl //#define SEXPR_BOARD_FILE_VERSION 20241228 // Convert teardrop curve points to bool //#define SEXPR_BOARD_FILE_VERSION 20241229 // Expand User layers to arbitrary count //----------------- Start of 10.0 development ----------------- -#define SEXPR_BOARD_FILE_VERSION 20250210 // Knockout for textboxes +//#define SEXPR_BOARD_FILE_VERSION 20250210 // Knockout for textboxes +#define SEXPR_BOARD_FILE_VERSION 20250222 // Hatching for PCB shapes #define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag 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 73bb9d417d..381f77c3ac 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 @@ -3121,17 +3121,16 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) // T_yes was used to indicate filling when first introduced, // so treat it like a solid fill since that was the only fill available case T_yes: - case T_solid: - shape->SetFilled( true ); - break; + case T_solid: shape->SetFillMode( FILL_T::FILLED_SHAPE ); break; case T_none: - case T_no: - shape->SetFilled( false ); - break; + case T_no: shape->SetFillMode( FILL_T::NO_FILL ); break; - default: - Expecting( "yes, no, solid, none" ); + case T_hatch: shape->SetFillMode( FILL_T::HATCH ); break; + case T_reverse_hatch: shape->SetFillMode( FILL_T::REVERSE_HATCH ); break; + case T_cross_hatch: shape->SetFillMode( FILL_T::CROSS_HATCH ); break; + + default: Expecting( "yes, no, solid, none, hatch, reverse_hatch or cross_hatch" ); } } @@ -3184,7 +3183,7 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) // Only filled shapes may have a zero line-width. This is not permitted in KiCad but some // external tools can generate invalid files. - if( stroke.GetWidth() <= 0 && !shape->IsFilled() ) + if( stroke.GetWidth() <= 0 && !shape->IsAnyFill() ) { stroke.SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) ); } diff --git a/pcbnew/pcb_io/odbpp/odb_feature.cpp b/pcbnew/pcb_io/odbpp/odb_feature.cpp index 058b9317d5..ac7164236f 100644 --- a/pcbnew/pcb_io/odbpp/odb_feature.cpp +++ b/pcbnew/pcb_io/odbpp/odb_feature.cpp @@ -78,37 +78,39 @@ bool FEATURES_MANAGER::AddContour( const SHAPE_POLY_SET& aPolySet, int aOutline void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) { + int stroke_width = aShape.GetWidth(); + switch( aShape.GetShape() ) { case SHAPE_T::CIRCLE: { int diameter = aShape.GetRadius() * 2; - int width = aShape.GetWidth(); VECTOR2I center = ODB::GetShapePosition( aShape ); - wxString innerDim = ODB::SymDouble2String( ( diameter - width / 2 ) ); - wxString outerDim = ODB::SymDouble2String( ( width + diameter ) ); + wxString innerDim = ODB::SymDouble2String( ( diameter - stroke_width / 2 ) ); + wxString outerDim = ODB::SymDouble2String( ( stroke_width + diameter ) ); - if( aShape.GetFillMode() == FILL_T::NO_FILL ) - { - AddFeature( ODB::AddXY( center ), AddRoundDonutSymbol( outerDim, innerDim ) ); - } - else - { + if( aShape.IsSolidFill() ) AddFeature( ODB::AddXY( center ), AddCircleSymbol( outerDim ) ); - } + else + AddFeature( ODB::AddXY( center ), AddRoundDonutSymbol( outerDim, innerDim ) ); break; } case SHAPE_T::RECTANGLE: { - int stroke_width = aShape.GetWidth(); int width = std::abs( aShape.GetRectangleWidth() ) + stroke_width; int height = std::abs( aShape.GetRectangleHeight() ) + stroke_width; wxString rad = ODB::SymDouble2String( ( stroke_width / 2.0 ) ); VECTOR2I center = ODB::GetShapePosition( aShape ); - if( aShape.GetFillMode() == FILL_T::NO_FILL ) + if( aShape.IsSolidFill() ) + { + AddFeature( ODB::AddXY( center ), + AddRoundRectSymbol( ODB::SymDouble2String( width ), + ODB::SymDouble2String( height ), rad ) ); + } + else { AddFeature( ODB::AddXY( center ), AddRoundRectDonutSymbol( ODB::SymDouble2String( width ), @@ -116,12 +118,6 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) ODB::SymDouble2String( stroke_width ), rad ) ); } - else - { - AddFeature( ODB::AddXY( center ), - AddRoundRectSymbol( ODB::SymDouble2String( width ), - ODB::SymDouble2String( height ), rad ) ); - } break; } @@ -133,7 +129,7 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) // TODO: check if soldermask_min_thickness should be Stroke width if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) ) - soldermask_min_thickness = aShape.GetWidth(); + soldermask_min_thickness = stroke_width; int maxError = m_board->GetDesignSettings().m_MaxError; SHAPE_POLY_SET poly_set; @@ -159,22 +155,20 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) poly_set.Fracture(); } - int strokeWidth = aShape.GetStroke().GetWidth(); - // ODB++ surface features can only represent closed polygons. We add a surface for // the fill of the shape, if present, and add line segments for the outline, if present. - if( aShape.IsFilled() ) + if( aShape.IsSolidFill() ) { for( int ii = 0; ii < poly_set.OutlineCount(); ++ii ) { AddContour( poly_set, ii, FILL_T::FILLED_SHAPE ); - if( strokeWidth != 0 ) + if( stroke_width != 0 ) { for( int jj = 0; jj < poly_set.COutline( ii ).SegmentCount(); ++jj ) { const SEG& seg = poly_set.COutline( ii ).CSegment( jj ); - AddFeatureLine( seg.A, seg.B, strokeWidth ); + AddFeatureLine( seg.A, seg.B, stroke_width ); } } } @@ -186,7 +180,7 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) for( int jj = 0; jj < poly_set.COutline( ii ).SegmentCount(); ++jj ) { const SEG& seg = poly_set.COutline( ii ).CSegment( jj ); - AddFeatureLine( seg.A, seg.B, strokeWidth ); + AddFeatureLine( seg.A, seg.B, stroke_width ); } } } @@ -198,9 +192,7 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) { ODB_DIRECTION dir = !aShape.IsClockwiseArc() ? ODB_DIRECTION::CW : ODB_DIRECTION::CCW; - AddFeatureArc( aShape.GetStart(), aShape.GetEnd(), aShape.GetCenter(), - aShape.GetStroke().GetWidth(), dir ); - + AddFeatureArc( aShape.GetStart(), aShape.GetEnd(), aShape.GetCenter(), stroke_width, dir ); break; } @@ -209,25 +201,24 @@ void FEATURES_MANAGER::AddShape( const PCB_SHAPE& aShape, PCB_LAYER_ID aLayer ) const std::vector& points = aShape.GetBezierPoints(); for( size_t i = 0; i < points.size() - 1; i++ ) - { - AddFeatureLine( points[i], points[i + 1], aShape.GetStroke().GetWidth() ); - } + AddFeatureLine( points[i], points[i + 1], stroke_width ); break; } case SHAPE_T::SEGMENT: - { - AddFeatureLine( aShape.GetStart(), aShape.GetEnd(), aShape.GetStroke().GetWidth() ); - + AddFeatureLine( aShape.GetStart(), aShape.GetEnd(), stroke_width ); break; - } default: - { wxLogError( wxT( "Unknown shape when adding ODB++ layer feature" ) ); break; } + + if( aShape.IsHatchedFill() ) + { + for( int ii = 0; ii < aShape.GetHatching().OutlineCount(); ++ii ) + AddContour( aShape.GetHatching(), ii, FILL_T::FILLED_SHAPE ); } } diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index f3373cec11..3d7126b4a5 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -525,7 +525,7 @@ COLOR4D PCB_RENDER_SETTINGS::GetColor( const BOARD_ITEM* aItem, int aLayer ) con color.a *= m_zoneOpacity; else if( aItem->Type() == PCB_REFERENCE_IMAGE_T ) color.a *= m_imageOpacity; - else if( aItem->Type() == PCB_SHAPE_T && static_cast( aItem )->IsFilled() ) + else if( aItem->Type() == PCB_SHAPE_T && static_cast( aItem )->IsAnyFill() ) color.a *= m_filledShapeOpacity; else if( aItem->Type() == PCB_SHAPE_T && aItem->IsOnCopperLayer() ) color.a *= m_trackOpacity; @@ -1866,7 +1866,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) m_gal->SetFillColor( color ); m_gal->SetStrokeColor( color ); - if( lineStyle == LINE_STYLE::SOLID || aShape->IsFilled() ) + if( lineStyle == LINE_STYLE::SOLID || aShape->IsSolidFill() ) { switch( aShape->GetShape() ) { @@ -1940,7 +1940,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) m_gal->DrawSegment( pts[3], pts[0], thickness ); } - if( aShape->IsFilled() ) + if( aShape->IsSolidFill() ) { SHAPE_POLY_SET poly; poly.NewOutline(); @@ -1991,7 +1991,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) } else { - m_gal->SetIsFill( aShape->IsFilled() ); + m_gal->SetIsFill( aShape->IsSolidFill() ); m_gal->SetIsStroke( lineStyle == LINE_STYLE::SOLID && thickness > 0 ); m_gal->SetLineWidth( thickness ); @@ -2001,7 +2001,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) { m_gal->DrawCircle( aShape->GetStart(), radius ); } - else if( aShape->IsFilled() ) + else if( aShape->IsSolidFill() ) { if( thickness < 0 ) { @@ -2037,7 +2037,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) m_gal->DrawSegmentChain( shape.Outline( ii ), thickness ); } - if( aShape->IsFilled() ) + if( aShape->IsSolidFill() ) { if( thickness < 0 ) { @@ -2081,7 +2081,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) } else { - m_gal->SetIsFill( aShape->IsFilled() ); + m_gal->SetIsFill( aShape->IsSolidFill() ); m_gal->SetIsStroke( lineStyle == LINE_STYLE::SOLID && thickness > 0 ); m_gal->SetLineWidth( thickness ); @@ -2128,6 +2128,9 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) for( SHAPE* shape : shapes ) delete shape; } + + if( aShape->IsHatchedFill() ) + m_gal->DrawPolygon( aShape->GetHatching() ); } @@ -3027,7 +3030,7 @@ void PCB_PAINTER::draw( const PCB_MARKER* aMarker, int aLayer ) for( auto& shape : aLayer == LAYER_DRC_SHAPE1 ? aMarker->GetShapes1() : aMarker->GetShapes2() ) { - m_gal->SetIsFill( shape.IsFilled() ); + m_gal->SetIsFill( shape.IsSolidFill() ); m_gal->SetIsStroke( aLayer == LAYER_DRC_SHAPE1 ? true : false ); m_gal->SetStrokeColor( shape.GetLineColor() ); m_gal->SetFillColor( shape.GetFillColor() ); diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp index 41717c5d8d..45a85d4153 100644 --- a/pcbnew/pcb_shape.cpp +++ b/pcbnew/pcb_shape.cpp @@ -124,6 +124,8 @@ bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer ) // TODO m_hasSolderMask and m_solderMaskMargin + m_hatchingDirty = true; + return true; } @@ -190,7 +192,7 @@ int PCB_SHAPE::GetSolderMaskExpansion() const } // Ensure the resulting mask opening has a non-negative size - if( margin < 0 && !IsFilled() ) + if( margin < 0 && !IsSolidFill() ) margin = std::max( margin, -GetWidth() / 2 ); return margin; @@ -250,7 +252,7 @@ std::vector PCB_SHAPE::GetConnectionPoints() const std::vector ret; // For filled shapes, we may as well use a centroid - if( IsFilled() ) + if( IsSolidFill() ) { ret.emplace_back( GetCenter() ); return ret; @@ -261,16 +263,16 @@ std::vector PCB_SHAPE::GetConnectionPoints() const case SHAPE_T::CIRCLE: { const CIRCLE circle( GetCenter(), GetRadius() ); + for( const TYPED_POINT2I& pt : KIGEOM::GetCircleKeyPoints( circle, false ) ) - { ret.emplace_back( pt.m_point ); - } + break; } + case SHAPE_T::ARC: ret.emplace_back( GetArcMid() ); KI_FALLTHROUGH; - case SHAPE_T::SEGMENT: case SHAPE_T::BEZIER: ret.emplace_back( GetStart() ); @@ -290,7 +292,7 @@ std::vector PCB_SHAPE::GetConnectionPoints() const break; case SHAPE_T::UNDEFINED: - // No default - handle all cases, even if just break + UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break; } @@ -298,6 +300,31 @@ std::vector PCB_SHAPE::GetConnectionPoints() const } +void PCB_SHAPE::updateHatching() const +{ + EDA_SHAPE::updateHatching(); + + if( !m_hatching.IsEmpty() ) + { + PCB_LAYER_ID layer = GetLayer(); + BOX2I bbox = GetBoundingBox(); + SHAPE_POLY_SET holes; + + for( BOARD_ITEM* item : GetBoard()->Drawings() ) + { + if( ( item->Type() == PCB_TEXT_T || item->Type() == PCB_TEXTBOX_T ) + && item->GetLayer() == layer + && item->GetBoundingBox().Intersects( bbox ) ) + { + item->TransformShapeToPolygon( holes, layer, 0, ARC_LOW_DEF, ERROR_OUTSIDE, true ); + } + } + + m_hatching.BooleanSubtract( holes ); + } +} + + int PCB_SHAPE::GetWidth() const { // A stroke width of 0 in PCBNew means no-border, but negative stroke-widths are only used @@ -322,19 +349,19 @@ const VECTOR2I PCB_SHAPE::GetFocusPosition() const switch( m_shape ) { case SHAPE_T::CIRCLE: - if( !IsFilled() ) + if( !IsAnyFill() ) return VECTOR2I( GetCenter().x + GetRadius(), GetCenter().y ); else return GetCenter(); case SHAPE_T::RECTANGLE: - if( !IsFilled() ) + if( !IsAnyFill() ) return GetStart(); else return GetCenter(); case SHAPE_T::POLY: - if( !IsFilled() ) + if( !IsAnyFill() ) { VECTOR2I pos = GetPolyShape().Outline(0).CPoint(0); return VECTOR2I( pos.x, pos.y ); @@ -524,6 +551,8 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); } + + m_hatchingDirty = true; } diff --git a/pcbnew/pcb_shape.h b/pcbnew/pcb_shape.h index e21e6494bf..6db9ca8698 100644 --- a/pcbnew/pcb_shape.h +++ b/pcbnew/pcb_shape.h @@ -194,11 +194,14 @@ public: protected: void swapData( BOARD_ITEM* aImage ) override; + void updateHatching() const override; + struct cmp_drawings { bool operator()( const BOARD_ITEM* aFirst, const BOARD_ITEM* aSecond ) const; }; +protected: bool m_hasSolderMask; std::optional m_solderMaskMargin; }; diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index 9e646e2a5d..634dbb40ad 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -933,7 +933,7 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) break; case SHAPE_T::CIRCLE: - if( aShape->IsFilled() ) + if( aShape->IsSolidFill() ) { int diameter = aShape->GetRadius() * 2 + thickness; @@ -1004,7 +1004,7 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) m_board->GetDesignSettings().m_MaxError ); } - FILL_T fill = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; + FILL_T fill = aShape->IsSolidFill() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj ) { @@ -1055,7 +1055,7 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) m_board->GetDesignSettings().m_MaxError ); } - FILL_T fill_mode = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; + FILL_T fill_mode = aShape->IsSolidFill() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; if( poly.OutlineCount() > 0 ) { @@ -1098,6 +1098,24 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) for( SHAPE* shape : shapes ) delete shape; } + + if( aShape->IsHatchedFill() ) + { + for( int ii = 0; ii < aShape->GetHatching().OutlineCount(); ++ii ) + { + if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER ) + { + GERBER_PLOTTER* gbr_plotter = static_cast( m_plotter ); + gbr_plotter->PlotPolyAsRegion( aShape->GetHatching().Outline( ii ), + FILL_T::FILLED_SHAPE, 0, &gbr_metadata ); + } + else + { + m_plotter->PlotPoly( aShape->GetHatching().Outline( ii ), FILL_T::FILLED_SHAPE, + 0, &gbr_metadata ); + } + } + } } diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp index 54e6cf7117..9d8b35e59b 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -3935,7 +3935,7 @@ int DRAWING_TOOL::DrawVia( const TOOL_EVENT& aEvent ) { if( PCB_SHAPE* shape = findGraphic( via ) ) { - if( shape->IsFilled() ) + if( shape->IsAnyFill() ) { aItem->SetPosition( shape->GetPosition() ); } diff --git a/pcbnew/tools/item_modification_routine.cpp b/pcbnew/tools/item_modification_routine.cpp index c529bfed96..ec526d108c 100644 --- a/pcbnew/tools/item_modification_routine.cpp +++ b/pcbnew/tools/item_modification_routine.cpp @@ -495,7 +495,7 @@ void POLYGON_BOOLEAN_ROUTINE::ProcessShape( PCB_SHAPE& aPcbShape ) { m_width = aPcbShape.GetWidth(); m_layer = aPcbShape.GetLayer(); - m_filled = aPcbShape.IsFilled(); + m_fillMode = aPcbShape.GetFillMode(); m_workingPolygons = std::move( *poly ); m_firstPolygon = false; @@ -544,7 +544,7 @@ void POLYGON_BOOLEAN_ROUTINE::Finalize() // Copy properties from the source polygon new_poly_shape->SetWidth( m_width ); new_poly_shape->SetLayer( m_layer ); - new_poly_shape->SetFilled( m_filled ); + new_poly_shape->SetFillMode( m_fillMode ); handler.AddNewItem( std::move( new_poly_shape ) ); } diff --git a/pcbnew/tools/item_modification_routine.h b/pcbnew/tools/item_modification_routine.h index b9d84071ed..c8600a40db 100644 --- a/pcbnew/tools/item_modification_routine.h +++ b/pcbnew/tools/item_modification_routine.h @@ -360,7 +360,7 @@ private: bool m_firstPolygon = true; int m_width = 0; PCB_LAYER_ID m_layer = PCB_LAYER_ID::UNDEFINED_LAYER; - bool m_filled = false; + FILL_T m_fillMode = FILL_T::NO_FILL; }; class POLYGON_MERGE_ROUTINE : public POLYGON_BOOLEAN_ROUTINE diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index 17f80f6feb..014356dc3d 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -2796,9 +2796,10 @@ int PCB_POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent ) zoneOutline->InsertVertex( nextNearestIdx, nearestPoint ); - // We re-hatch the filled zones but not polygons if( item->Type() == PCB_ZONE_T ) static_cast( item )->HatchBorder(); + else + graphicItem->SetHatchingDirty(); commit.Push( _( "Add Zone Corner" ) ); } @@ -2927,9 +2928,10 @@ int PCB_POINT_EDITOR::removeCorner( const TOOL_EVENT& aEvent ) else commit.Push( _( "Remove Polygon Corner" ) ); - // Refresh zone hatching if( item->Type() == PCB_ZONE_T ) static_cast( item )->HatchBorder(); + else if( item->Type() == PCB_SHAPE_T ) + static_cast( item )->SetHatchingDirty(); updatePoints(); } @@ -3027,9 +3029,10 @@ int PCB_POINT_EDITOR::chamferCorner( const TOOL_EVENT& aEvent ) else commit.Push( _( "Break Polygon Corner" ) ); - // Refresh zone hatching if( item->Type() == PCB_ZONE_T ) static_cast( item )->HatchBorder(); + else if( item->Type() == PCB_SHAPE_T ) + static_cast( item )->SetHatchingDirty(); updatePoints(); diff --git a/pcbnew/zone.cpp b/pcbnew/zone.cpp index 3923d8e5ae..091f134c77 100644 --- a/pcbnew/zone.cpp +++ b/pcbnew/zone.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -1134,14 +1133,6 @@ void ZONE::UnHatchBorder() } -// Creates hatch lines inside the outline of the complex polygon -// sort function used in ::HatchBorder to sort points by descending VECTOR2I.x values -bool sortEndsByDescendingX( const VECTOR2I& ref, const VECTOR2I& tst ) -{ - return tst.x < ref.x; -} - - void ZONE::HatchBorder() { UnHatchBorder(); @@ -1153,27 +1144,6 @@ void ZONE::HatchBorder() return; } - // define range for hatch lines - int min_x = m_Poly->CVertex( 0 ).x; - int max_x = m_Poly->CVertex( 0 ).x; - int min_y = m_Poly->CVertex( 0 ).y; - int max_y = m_Poly->CVertex( 0 ).y; - - for( auto iterator = m_Poly->IterateWithHoles(); iterator; iterator++ ) - { - if( iterator->x < min_x ) - min_x = iterator->x; - - if( iterator->x > max_x ) - max_x = iterator->x; - - if( iterator->y < min_y ) - min_y = iterator->y; - - if( iterator->y > max_y ) - max_y = iterator->y; - } - // Calculate spacing between 2 hatch lines int spacing; @@ -1186,100 +1156,17 @@ void ZONE::HatchBorder() int hatch_line_len = m_borderHatchPitch; // To have a better look, give a slope depending on the layer - int layer = GetFirstLayer(); - std::vector slope_flags; + int layer = GetFirstLayer(); + std::vector slopes; if( IsTeardropArea() ) - slope_flags = { 1, -1 }; + slopes = { 0.7, -0.7 }; else if( layer & 1 ) - slope_flags = { 1 }; + slopes = { 1 }; else - slope_flags = { -1 }; + slopes = { -1 }; - for( int slope_flag : slope_flags ) - { - double slope = 0.707106 * slope_flag; // 45 degrees slope - int64_t max_a, min_a; - - if( slope_flag == 1 ) - { - max_a = KiROUND( max_y - slope * min_x ); - min_a = KiROUND( min_y - slope * max_x ); - } - else - { - max_a = KiROUND( max_y - slope * max_x ); - min_a = KiROUND( min_y - slope * min_x ); - } - - min_a = (min_a / spacing) * spacing; - - // calculate an offset depending on layer number, - // for a better look of hatches on a multilayer board - int offset = (layer * 7) / 8; - min_a += offset; - - // loop through hatch lines - std::vector pointbuffer; - pointbuffer.reserve( 256 ); - - for( int64_t a = min_a; a < max_a; a += spacing ) - { - pointbuffer.clear(); - - // Iterate through all vertices - for( auto iterator = m_Poly->IterateSegmentsWithHoles(); iterator; iterator++ ) - { - const SEG seg = *iterator; - double x, y; - - if( FindLineSegmentIntersection( a, slope, seg.A.x, seg.A.y, seg.B.x, seg.B.y, x, y ) ) - pointbuffer.emplace_back( KiROUND( x ), KiROUND( y ) ); - } - - // sort points in order of descending x (if more than 2) to - // ensure the starting point and the ending point of the same segment - // are stored one just after the other. - if( pointbuffer.size() > 2 ) - sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX ); - - // creates lines or short segments inside the complex polygon - for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip += 2 ) - { - int dx = pointbuffer[ip + 1].x - pointbuffer[ip].x; - - // Push only one line for diagonal hatch, - // or for small lines < twice the line length - // else push 2 small lines - if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL - || std::abs( dx ) < 2 * hatch_line_len ) - { - m_borderHatchLines.emplace_back( SEG( pointbuffer[ip], pointbuffer[ ip + 1] ) ); - } - else - { - double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y; - slope = dy / dx; - - if( dx > 0 ) - dx = hatch_line_len; - else - dx = -hatch_line_len; - - int x1 = KiROUND( pointbuffer[ip].x + dx ); - int x2 = KiROUND( pointbuffer[ip + 1].x - dx ); - int y1 = KiROUND( pointbuffer[ip].y + dx * slope ); - int y2 = KiROUND( pointbuffer[ip + 1].y - dx * slope ); - - m_borderHatchLines.emplace_back( SEG( pointbuffer[ip].x, pointbuffer[ip].y, - x1, y1 ) ); - - m_borderHatchLines.emplace_back( SEG( pointbuffer[ip+1].x, pointbuffer[ip+1].y, - x2, y2 ) ); - } - } - } - } + m_borderHatchLines = m_Poly->GenerateHatchLines( slopes, spacing, hatch_line_len ); } @@ -1588,9 +1475,7 @@ void ZONE::TransformSmoothedOutlineToPolygon( SHAPE_POLY_SET& aBuffer, int aClea std::shared_ptr ZONE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const { - if( GetIsRuleArea() ) - return std::make_shared( *Outline() ); - else if( m_FilledPolysList.find( aLayer ) == m_FilledPolysList.end() ) + if( m_FilledPolysList.find( aLayer ) == m_FilledPolysList.end() ) return std::make_shared(); else return m_FilledPolysList.at( aLayer ); diff --git a/qa/tests/pcbnew/test_graphics_load_save.cpp b/qa/tests/pcbnew/test_graphics_load_save.cpp index 437e4c0a6e..f20f9a57c0 100644 --- a/qa/tests/pcbnew/test_graphics_load_save.cpp +++ b/qa/tests/pcbnew/test_graphics_load_save.cpp @@ -85,20 +85,21 @@ const std::vector GraphicsLoad_testCases{ BOOST_DATA_TEST_CASE( GraphicsLoad, boost::unit_test::data::make( GraphicsLoad_testCases ), testCase ) { - const auto doBoardTest = [&]( const BOARD& aBoard ) - { - for( const GRAPHICS_LOAD_TEST_CASE& testCase : testCase.m_generatorCases ) + const auto doBoardTest = + [&]( const BOARD& aBoard ) { - BOOST_TEST_MESSAGE( - "Checking for graphic with UUID: " << testCase.m_searchUuid.AsString() ); + for( const GRAPHICS_LOAD_TEST_CASE& testCase : testCase.m_generatorCases ) + { + BOOST_TEST_MESSAGE( "Checking for graphic with UUID: " + << testCase.m_searchUuid.AsString() ); - const auto& graphic = - static_cast( KI_TEST::RequireBoardItemWithTypeAndId( - aBoard, PCB_SHAPE_T, testCase.m_searchUuid ) ); + const auto& graphic = + static_cast( KI_TEST::RequireBoardItemWithTypeAndId( + aBoard, PCB_SHAPE_T, testCase.m_searchUuid ) ); - BOOST_CHECK_EQUAL( graphic.IsFilled(), testCase.m_expectedFilled ); - } - }; + BOOST_CHECK_EQUAL( graphic.IsSolidFill(), testCase.m_expectedFilled ); + } + }; KI_TEST::LoadAndTestBoardFile( testCase.m_BoardFileRelativePath, true, doBoardTest, testCase.m_ExpectedBoardVersion );