diff --git a/api/proto/board/board_types.proto b/api/proto/board/board_types.proto index 716a671a6a..bb36a95a17 100644 --- a/api/proto/board/board_types.proto +++ b/api/proto/board/board_types.proto @@ -647,6 +647,14 @@ message ZoneFilledPolygons kiapi.common.types.PolySet shapes = 2; } +message ZoneLayerProperties +{ + BoardLayer layer = 1; + + kiapi.common.types.Vector2 hatching_offset = 2; +} + + message Zone { kiapi.common.types.KIID id = 1; @@ -673,6 +681,8 @@ message Zone ZoneBorderSettings border = 11; kiapi.common.types.LockedState locked = 12; + + repeated ZoneLayerProperties layer_properties = 13; } // An aligned dimension is drawn parallel to the line segment between the start and end points diff --git a/common/pcb.keywords b/common/pcb.keywords index 2a46d65e73..b17909d6c8 100644 --- a/common/pcb.keywords +++ b/common/pcb.keywords @@ -171,6 +171,7 @@ hatch hatch_thickness hatch_gap hatch_orientation +hatch_position hatch_smoothing_level hatch_smoothing_value hatch_border_algorithm @@ -398,4 +399,5 @@ zone_clearance zone_connect zone_layer_connections zone_type +zone_defaults zones diff --git a/common/project/project_local_settings.cpp b/common/project/project_local_settings.cpp index a833020810..d517732e5e 100644 --- a/common/project/project_local_settings.cpp +++ b/common/project/project_local_settings.cpp @@ -33,11 +33,13 @@ PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxStrin JSON_SETTINGS( aFilename, SETTINGS_LOC::PROJECT, projectLocalSettingsVersion, /* aCreateIfMissing = */ true, /* aCreateIfDefault = */ false, /* aWriteFile = */ true ), + // clang-format off: suggestion is less readable. m_ActiveLayer( UNDEFINED_LAYER ), m_ContrastModeDisplay( HIGH_CONTRAST_MODE::NORMAL ), m_NetColorMode( NET_COLOR_MODE::RATSNEST ), m_AutoTrackWidth( true ), m_ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FILLED ), + m_PrototypeZoneFill( false ), m_TrackOpacity( 1.0 ), m_ViaOpacity( 1.0 ), m_PadOpacity( 1.0 ), @@ -47,6 +49,7 @@ PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxStrin m_PcbSelectionFilter(), m_project( aProject ), m_wasMigrated( false ) +// clang-format on: suggestion is less readable. { // Keep old files around m_deleteLegacyAfterMigration = false; @@ -201,6 +204,9 @@ PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxStrin ZONE_DISPLAY_MODE::SHOW_FILLED, ZONE_DISPLAY_MODE::SHOW_FILLED, ZONE_DISPLAY_MODE::SHOW_TRIANGULATION ) ); + m_params.emplace_back( + new PARAM( "board.prototype_zone_fills", &m_PrototypeZoneFill, false ) ); + m_params.emplace_back( new PARAM( "git.repo_username", &m_GitRepoUsername, "" ) ); m_params.emplace_back( new PARAM( "git.repo_type", &m_GitRepoType, "" ) ); diff --git a/include/project/project_local_settings.h b/include/project/project_local_settings.h index ec50f43a77..192c9d68a5 100644 --- a/include/project/project_local_settings.h +++ b/include/project/project_local_settings.h @@ -130,6 +130,9 @@ public: /// How zones are drawn ZONE_DISPLAY_MODE m_ZoneDisplayMode; + /// Whether Zone fill should always be solid for performance with large boards. + bool m_PrototypeZoneFill; + double m_TrackOpacity; ///< Opacity override for all tracks double m_ViaOpacity; ///< Opacity override for all types of via double m_PadOpacity; ///< Opacity override for SMD pads and PTH diff --git a/pcbnew/edit_zone_helpers.cpp b/pcbnew/edit_zone_helpers.cpp index bcb9eb2ec9..f87c00d41c 100644 --- a/pcbnew/edit_zone_helpers.cpp +++ b/pcbnew/edit_zone_helpers.cpp @@ -44,6 +44,12 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( ZONE* aZone ) ZONE_SETTINGS zoneInfo = m_pcb->GetDesignSettings().GetDefaultZoneSettings(); BOARD_COMMIT commit( this ); + // store default layer properties + std::map layer_properties; + + std::ranges::copy( zoneInfo.m_layerProperties, + std::inserter( layer_properties, std::end( layer_properties ) ) ); + if( aZone->GetIsRuleArea() ) { // edit a rule area on a copper layer @@ -80,6 +86,11 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( ZONE* aZone ) if( net ) // net == NULL should not occur aZone->SetNetCode( net->GetNetCode() ); + // restore default layer properties + zoneInfo.m_layerProperties.clear(); + std::ranges::copy( layer_properties, std::inserter( zoneInfo.m_layerProperties, + std::end( zoneInfo.m_layerProperties ) ) ); + m_pcb->GetDesignSettings().SetDefaultZoneSettings( zoneInfo ); commit.Push( _( "Edit Zone Properties" ), SKIP_CONNECTIVITY ); 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 200986d19f..8d5aad8ad2 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -606,6 +606,19 @@ void PCB_IO_KICAD_SEXPR::formatSetup( const BOARD* aBoard ) const KICAD_FORMAT::FormatBool( m_out, "filling", dsnSettings.m_FillVias ); + if( !dsnSettings.GetDefaultZoneSettings().m_layerProperties.empty() ) + { + m_out->Print( 0, " (zone_defaults" ); + + for( const auto& [layer, properties] : + dsnSettings.GetDefaultZoneSettings().m_layerProperties ) + { + format( properties, 0, layer ); + } + + m_out->Print( 0, ")\n" ); + } + VECTOR2I origin = dsnSettings.GetAuxOrigin(); if( origin != VECTOR2I( 0, 0 ) ) @@ -2658,6 +2671,11 @@ void PCB_IO_KICAD_SEXPR::format( const ZONE* aZone ) const m_out->Print( ")" ); + for( const auto& [layer, properties] : aZone->LayerProperties() ) + { + format( properties, 0, layer ); + } + if( aZone->GetNumCorners() ) { SHAPE_POLY_SET::POLYGON poly = aZone->Outline()->Polygon(0); @@ -2694,6 +2712,26 @@ void PCB_IO_KICAD_SEXPR::format( const ZONE* aZone ) const } +void PCB_IO_KICAD_SEXPR::format( const ZONE_LAYER_PROPERTIES& aZoneLayerProperties, int aNestLevel, + PCB_LAYER_ID aLayer ) const +{ + // Do not store the layer properties if no value is actually set. + if( !aZoneLayerProperties.hatching_offset.has_value() ) + return; + + m_out->Print( aNestLevel, "(property\n" ); + m_out->Print( aNestLevel, "(layer %s)\n", m_out->Quotew( LSET::Name( aLayer ) ).c_str() ); + + if( aZoneLayerProperties.hatching_offset.has_value() ) + { + m_out->Print( aNestLevel, "(hatch_position (xy %s))", + formatInternalUnits( aZoneLayerProperties.hatching_offset.value() ).c_str() ); + } + + m_out->Print( aNestLevel, ")\n" ); +} + + PCB_IO_KICAD_SEXPR::PCB_IO_KICAD_SEXPR( int aControlFlags ) : PCB_IO( wxS( "KiCad" ) ), m_cache( nullptr ), m_ctl( aControlFlags ), 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 27f2445dee..6ee2b78315 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -177,7 +178,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl //----------------- Start of 10.0 development ----------------- //#define SEXPR_BOARD_FILE_VERSION 20250210 // Knockout for textboxes //#define SEXPR_BOARD_FILE_VERSION 20250222 // Hatching for PCB shapes -#define SEXPR_BOARD_FILE_VERSION 20250228 // ipc-4761 via protection features +//#define SEXPR_BOARD_FILE_VERSION 20250228 // ipc-4761 via protection features +#define SEXPR_BOARD_FILE_VERSION 20250302 // Zone Hatching Offsets #define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag @@ -453,6 +455,9 @@ private: void format( const ZONE* aZone ) const; + void format( const ZONE_LAYER_PROPERTIES& aZoneLayerProperties, int aNestLevel, + PCB_LAYER_ID aLayer ) const; + void formatPolyPts( const SHAPE_LINE_CHAIN& outline, const FOOTPRINT* aParentFP = nullptr ) const; 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 bc57754095..1849d4be12 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 @@ -27,6 +27,7 @@ * @brief Pcbnew s-expression file format parser implementation. */ +#include "layer_ids.h" #include #include #include @@ -2548,6 +2549,9 @@ void PCB_IO_KICAD_SEXPR_PARSER::parseSetup() break; } + case T_zone_defaults: + parseZoneDefaults( bds.GetDefaultZoneSettings() ); + break; default: Unexpected( CurText() ); @@ -2565,6 +2569,70 @@ void PCB_IO_KICAD_SEXPR_PARSER::parseSetup() } +void PCB_IO_KICAD_SEXPR_PARSER::parseZoneDefaults( ZONE_SETTINGS& aZoneSettings ) +{ + T token; + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + { + Expecting( T_LEFT ); + } + + token = NextTok(); + + switch( token ) + { + case T_property: + parseZoneLayerProperty( aZoneSettings.m_layerProperties ); + break; + default: + Unexpected( CurText() ); + } + } +} + + +void PCB_IO_KICAD_SEXPR_PARSER::parseZoneLayerProperty( + std::map& aProperties ) +{ + T token; + + PCB_LAYER_ID layer = UNDEFINED_LAYER; + ZONE_LAYER_PROPERTIES properties; + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + { + Expecting( T_LEFT ); + } + + token = NextTok(); + + switch( token ) + { + case T_layer: + layer = parseBoardItemLayer(); + NeedRIGHT(); + break; + case T_hatch_position: + { + properties.hatching_offset = parseXY(); + NeedRIGHT(); + break; + } + default: + Unexpected( CurText() ); + break; + } + } + + aProperties.emplace( layer, properties ); +} + + void PCB_IO_KICAD_SEXPR_PARSER::parseDefaults( BOARD_DESIGN_SETTINGS& designSettings ) { T token; @@ -6837,6 +6905,10 @@ ZONE* PCB_IO_KICAD_SEXPR_PARSER::parseZONE( BOARD_ITEM_CONTAINER* aParent ) zone->SetLayerSet( parseBoardItemLayersAsMask() ); break; + case T_property: + parseZoneLayerProperty( zone->LayerProperties() ); + break; + case T_tstamp: case T_uuid: NextTok(); diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h index e1cf10b848..0400dc18be 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h @@ -303,6 +303,10 @@ private: void parseMargins( int& aLeft, int& aTop, int& aRight, int& aBottom ); + void parseZoneDefaults( ZONE_SETTINGS& aZoneSettings ); + + void parseZoneLayerProperty( std::map& aProperties ); + std::pair parseBoardProperty(); /** diff --git a/pcbnew/tools/zone_create_helper.cpp b/pcbnew/tools/zone_create_helper.cpp index 387b0e72dc..dfbb96740b 100644 --- a/pcbnew/tools/zone_create_helper.cpp +++ b/pcbnew/tools/zone_create_helper.cpp @@ -95,6 +95,7 @@ std::unique_ptr ZONE_CREATE_HELPER::createNewZone( bool aKeepout ) // Get the current default settings for zones ZONE_SETTINGS zoneInfo = board->GetDesignSettings().GetDefaultZoneSettings(); zoneInfo.m_Layers.reset().set( m_params.m_layer ); // TODO(JE) multilayer defaults? + zoneInfo.m_layerProperties.clear(); // Do not copy over layer properties zoneInfo.m_NetcodeSelection = highlightedNets.empty() ? -1 : *highlightedNets.begin(); zoneInfo.SetIsRuleArea( m_params.m_keepout ); diff --git a/pcbnew/zone.cpp b/pcbnew/zone.cpp index 091f134c77..fe10671573 100644 --- a/pcbnew/zone.cpp +++ b/pcbnew/zone.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -84,7 +85,8 @@ ZONE::ZONE( BOARD_ITEM_CONTAINER* aParent ) : SetIsRuleArea( true ); // Zones living in footprints have the rule area option if( aParent->GetBoard() ) - aParent->GetBoard()->GetDesignSettings().GetDefaultZoneSettings().ExportSetting( *this ); + aParent->GetBoard()->GetDesignSettings().GetDefaultZoneSettings().ExportSetting( *this, + false ); else ZONE_SETTINGS().ExportSetting( *this ); @@ -191,6 +193,11 @@ void ZONE::InitDataFromSrcInCopyCtor( const ZONE& aZone ) m_insulatedIslands[layer] = aZone.m_insulatedIslands.at( layer ); } ); + m_layerProperties.clear(); + + std::ranges::copy( aZone.LayerProperties(), + std::inserter( m_layerProperties, std::end( m_layerProperties ) ) ); + m_borderStyle = aZone.m_borderStyle; m_borderHatchPitch = aZone.m_borderHatchPitch; m_borderHatchLines = aZone.m_borderHatchLines; @@ -213,6 +220,7 @@ void ZONE::Serialize( google::protobuf::Any& aContainer ) const { using namespace kiapi::board; types::Zone zone; + using kiapi::common::PackVector2; zone.mutable_id()->set_value( m_Uuid.AsStdString() ); PackLayerSet( *zone.mutable_layers(), GetLayerSet() ); @@ -292,6 +300,18 @@ void ZONE::Serialize( google::protobuf::Any& aContainer ) const kiapi::common::PackPolySet( *filledLayer->mutable_shapes(), *shape ); } + for( const auto& [layer, properties] : m_layerProperties ) + { + types::ZoneLayerProperties* layerProperties = zone.add_layer_properties(); + layerProperties->set_layer( ToProtoEnum( layer ) ); + + if( properties.hatching_offset.has_value() ) + { + PackVector2( *layerProperties->mutable_hatching_offset(), + properties.hatching_offset.value() ); + } + } + zone.mutable_border()->set_style( ToProtoEnum( m_borderStyle ) ); zone.mutable_border()->mutable_pitch()->set_value_nm( m_borderHatchPitch ); @@ -304,6 +324,7 @@ bool ZONE::Deserialize( const google::protobuf::Any& aContainer ) { using namespace kiapi::board; types::Zone zone; + using kiapi::common::UnpackVector2; if( !aContainer.UnpackTo( &zone ) ) return false; @@ -366,6 +387,20 @@ bool ZONE::Deserialize( const google::protobuf::Any& aContainer ) SetNetCode( cu.net().code().value() ); m_teardropType = FromProtoEnum( cu.teardrop().type() ); + + for( const auto& properties : zone.layer_properties() ) + { + PCB_LAYER_ID layer = FromProtoEnum( properties.layer() ); + + ZONE_LAYER_PROPERTIES layerProperties; + + if( properties.has_hatching_offset() ) + { + layerProperties.hatching_offset = UnpackVector2( properties.hatching_offset() ); + } + + m_layerProperties[layer] = layerProperties; + } } m_borderStyle = FromProtoEnum( zone.border().style() ); @@ -510,12 +545,49 @@ void ZONE::SetLayerSet( const LSET& aLayerSet ) m_filledPolysHash[layer] = {}; m_insulatedIslands[layer] = {}; } ); + + std::erase_if( m_layerProperties, + [&]( const auto& item ) + { + return !aLayerSet.Contains( item.first ); + } ); } m_layerSet = aLayerSet; } +const ZONE_LAYER_PROPERTIES& ZONE::LayerProperties( PCB_LAYER_ID aLayer ) const +{ + wxCHECK_MSG( m_layerProperties.contains( aLayer ), m_layerProperties.at( GetFirstLayer() ), + "Attempt to retrieve properties for layer " + + std::string( magic_enum::enum_name( aLayer ) ) + + " from a " + "zone that does not contain it" ); + return m_layerProperties.at( aLayer ); +} + + +void ZONE::SetLayerProperties( const std::map& aOther ) +{ + m_layerProperties.clear(); + + std::ranges::copy( aOther, std::inserter( m_layerProperties, std::end( m_layerProperties ) ) ); +} + + +const std::optional& ZONE::HatchingOffset( PCB_LAYER_ID aLayer ) const +{ + wxCHECK_MSG( m_layerProperties.contains( aLayer ), + m_layerProperties.at( GetFirstLayer() ).hatching_offset, + "Attempt to retrieve properties for layer " + + std::string( magic_enum::enum_name( aLayer ) ) + + " from a " + "zone that does not contain it" ); + return m_layerProperties.at( aLayer ).hatching_offset; +} + + std::vector ZONE::ViewGetLayers() const { std::vector layers; @@ -963,8 +1035,19 @@ void ZONE::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) fillsCopy[oldLayer] = *shapePtr; } + std::map layerPropertiesCopy; + + std::ranges::copy( m_layerProperties, + std::inserter( layerPropertiesCopy, std::end( layerPropertiesCopy ) ) ); + SetLayerSet( GetLayerSet().Flip( GetBoard()->GetCopperLayerCount() ) ); + for( auto& [oldLayer, properties] : layerPropertiesCopy ) + { + PCB_LAYER_ID newLayer = GetBoard()->FlipLayer( oldLayer ); + m_layerProperties[newLayer] = properties; + } + for( auto& [oldLayer, shape] : fillsCopy ) { PCB_LAYER_ID newLayer = GetBoard()->FlipLayer( oldLayer ); diff --git a/pcbnew/zone.h b/pcbnew/zone.h index 7cbacb04ed..09f48ce219 100644 --- a/pcbnew/zone.h +++ b/pcbnew/zone.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -139,6 +140,24 @@ public: */ void SetLayerSetAndRemoveUnusedFills( const LSET& aLayerSet ); + ZONE_LAYER_PROPERTIES& LayerProperties( PCB_LAYER_ID aLayer ) + { + return m_layerProperties[aLayer]; + } + + const ZONE_LAYER_PROPERTIES& LayerProperties( PCB_LAYER_ID aLayer ) const; + + std::map& LayerProperties() { return m_layerProperties; } + + const std::map& LayerProperties() const + { + return m_layerProperties; + } + + void SetLayerProperties( const std::map& aOther ); + + const std::optional& HatchingOffset( PCB_LAYER_ID aLayer ) const; + const wxString& GetZoneName() const { return m_zoneName; } void SetZoneName( const wxString& aName ) { m_zoneName = aName; } @@ -849,6 +868,8 @@ protected: LSET m_layerSet; + std::map m_layerProperties; + /* Priority: when a zone outline is inside and other zone, if its priority is higher * the other zone priority, it will be created inside. * if priorities are equal, a DRC error is set diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 790854bf7d..b9131b7db8 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -50,6 +50,8 @@ #include #include // for KiROUND #include "zone_filler.h" +#include "project.h" +#include "project/project_local_settings.h" // Helper classes for connect_nearby_polys class RESULTS @@ -881,6 +883,24 @@ bool ZONE_FILLER::Fill( const std::vector& aZones, bool aCheck, wxWindow* } } + if( ( m_board->GetProject() + && m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) ) + { + KIDIALOG dlg( aParent, _( "Prototype zone fill enabled. Disable setting and refill?" ), + _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING ); + dlg.SetOKCancelLabels( _( "Disable and refill" ), _( "Continue without Refill" ) ); + dlg.DoNotShowCheckbox( __FILE__, __LINE__ ); + + if( dlg.ShowModal() == wxID_OK ) + { + m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill = false; + } + else if( !outOfDate ) + { + return false; + } + } + if( outOfDate ) { KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ), @@ -1812,7 +1832,9 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA * Process the hatch pattern (note that we do this while deflated) */ - if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN ) + if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN + && ( !m_board->GetProject() + || !m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) ) { if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) ) return false; @@ -2379,31 +2401,53 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer // Build holes SHAPE_POLY_SET holes; - for( int xx = 0; ; xx++ ) + VECTOR2I offset_opt = VECTOR2I(); + bool zone_has_offset = false; + + if( aZone->LayerProperties().contains( aLayer ) ) { - int xpos = xx * gridsize; + zone_has_offset = aZone->HatchingOffset( aLayer ).has_value(); - if( xpos > bbox.GetWidth() ) - break; + offset_opt = aZone->HatchingOffset( aLayer ).value_or( VECTOR2I( 0, 0 ) ); + } - for( int yy = 0; ; yy++ ) + if( !zone_has_offset ) + { + if( m_board->GetDesignSettings().GetDefaultZoneSettings().m_layerProperties.contains( + aLayer ) ) { - int ypos = yy * gridsize; + const ZONE_LAYER_PROPERTIES& properties = + m_board->GetDesignSettings().GetDefaultZoneSettings().m_layerProperties.at( + aLayer ); - if( ypos > bbox.GetHeight() ) - break; + offset_opt = properties.hatching_offset.value_or( VECTOR2I( 0, 0 ) ); + } + } + + int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize; + int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize; + + + for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize ) + { + for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize ) + { // Generate hole SHAPE_LINE_CHAIN hole( hole_base ); - hole.Move( VECTOR2I( xpos, ypos ) ); + hole.Move( VECTOR2I( xx, yy ) ); + + if( !aZone->GetHatchOrientation().IsZero() ) + { + hole.Rotate( aZone->GetHatchOrientation() ); + } + + hole.Move( VECTOR2I( offset_opt.x % gridsize, offset_opt.y % gridsize ) ); + holes.AddOutline( hole ); } } - holes.Move( bbox.GetPosition() ); - - if( !aZone->GetHatchOrientation().IsZero() ) - holes.Rotate( aZone->GetHatchOrientation() ); DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) ); diff --git a/pcbnew/zone_manager/dialog_zone_manager.cpp b/pcbnew/zone_manager/dialog_zone_manager.cpp index e19549d05a..c09174a712 100644 --- a/pcbnew/zone_manager/dialog_zone_manager.cpp +++ b/pcbnew/zone_manager/dialog_zone_manager.cpp @@ -260,7 +260,7 @@ void DIALOG_ZONE_MANAGER::OnOk( wxCommandEvent& aEvt ) if( m_zoneInfo ) { if( std::shared_ptr zone = m_panelZoneProperties->GetZoneSettings() ) - *m_zoneInfo = *zone; + m_zoneInfo->CopyFrom( *zone, false ); } aEvt.Skip(); diff --git a/pcbnew/zone_settings.cpp b/pcbnew/zone_settings.cpp index 201af428cb..5fde50c8d8 100644 --- a/pcbnew/zone_settings.cpp +++ b/pcbnew/zone_settings.cpp @@ -35,6 +35,11 @@ #include #include +bool ZONE_LAYER_PROPERTIES::operator==( const ZONE_LAYER_PROPERTIES& aOther ) const +{ + return hatching_offset == aOther.hatching_offset; +} + ZONE_SETTINGS::ZONE_SETTINGS() { m_ZonePriority = 0; @@ -124,6 +129,10 @@ bool ZONE_SETTINGS::operator==( const ZONE_SETTINGS& aOther ) const if( m_removeIslands != aOther.m_removeIslands ) return false; if( m_minIslandArea != aOther.m_minIslandArea ) return false; + if( !std::equal( std::begin( m_layerProperties ), std::end( m_layerProperties ), + std::begin( aOther.m_layerProperties ) ) ) + return false; + // Currently, the teardrop area type is not really a ZONE_SETTINGS parameter, // but a ZONE parameter only. // However it can be used in dialogs @@ -170,6 +179,11 @@ ZONE_SETTINGS& ZONE_SETTINGS::operator << ( const ZONE& aSource ) m_removeIslands = aSource.GetIslandRemovalMode(); m_minIslandArea = aSource.GetMinIslandArea(); + m_layerProperties.clear(); + + std::ranges::copy( aSource.LayerProperties(), + std::inserter( m_layerProperties, std::end( m_layerProperties ) ) ); + // Currently, the teardrop area type is not really a ZONE_SETTINGS parameter, // but a ZONE parameter only. // However it can be used in dialogs @@ -220,7 +234,10 @@ void ZONE_SETTINGS::ExportSetting( ZONE& aTarget, bool aFullExport ) const if( aFullExport ) { aTarget.SetAssignedPriority( m_ZonePriority ); + + aTarget.SetLayerProperties( m_layerProperties ); aTarget.SetLayerSet( m_Layers ); + aTarget.SetZoneName( m_Name ); if( !m_isRuleArea ) @@ -233,6 +250,56 @@ void ZONE_SETTINGS::ExportSetting( ZONE& aTarget, bool aFullExport ) const m_BorderHatchPitch, true ); } +void ZONE_SETTINGS::CopyFrom( const ZONE_SETTINGS& aOther, bool aCopyFull ) +{ + // clang-format off + m_ZonePriority = aOther.m_ZonePriority; + m_FillMode = aOther.m_FillMode; + m_ZoneClearance = aOther.m_ZoneClearance; + m_ZoneMinThickness = aOther.m_ZoneMinThickness; + m_HatchThickness = aOther.m_HatchThickness; + m_HatchGap = aOther.m_HatchGap; + m_HatchOrientation = aOther.m_HatchOrientation; + m_HatchSmoothingLevel = aOther.m_HatchSmoothingLevel; + m_HatchSmoothingValue = aOther.m_HatchSmoothingValue; + m_HatchBorderAlgorithm = aOther.m_HatchBorderAlgorithm; + m_HatchHoleMinArea = aOther.m_HatchHoleMinArea; + m_NetcodeSelection = aOther.m_NetcodeSelection; + m_Name = aOther.m_Name; + m_ZoneBorderDisplayStyle = aOther.m_ZoneBorderDisplayStyle; + m_BorderHatchPitch = aOther.m_BorderHatchPitch; + m_ThermalReliefGap = aOther.m_ThermalReliefGap; + m_ThermalReliefSpokeWidth = aOther.m_ThermalReliefSpokeWidth; + m_padConnection = aOther.m_padConnection; + m_cornerSmoothingType = aOther.m_cornerSmoothingType; + m_cornerRadius = aOther.m_cornerRadius; + m_isRuleArea = aOther.m_isRuleArea; + m_ruleAreaPlacementEnabled = aOther.m_ruleAreaPlacementEnabled; + m_ruleAreaPlacementSourceType = aOther.m_ruleAreaPlacementSourceType; + m_ruleAreaPlacementSource = aOther.m_ruleAreaPlacementSource; + m_keepoutDoNotAllowCopperPour = aOther.m_keepoutDoNotAllowCopperPour; + m_keepoutDoNotAllowVias = aOther.m_keepoutDoNotAllowVias; + m_keepoutDoNotAllowTracks = aOther.m_keepoutDoNotAllowTracks; + m_keepoutDoNotAllowPads = aOther.m_keepoutDoNotAllowPads; + m_keepoutDoNotAllowFootprints = aOther.m_keepoutDoNotAllowFootprints; + m_Locked = aOther.m_Locked; + m_removeIslands = aOther.m_removeIslands; + m_minIslandArea = aOther.m_minIslandArea; + // clang-format on + + if( aCopyFull ) + { + m_layerProperties.clear(); + + std::ranges::copy( aOther.m_layerProperties, + std::inserter( m_layerProperties, std::end( m_layerProperties ) ) ); + + m_TeardropType = aOther.m_TeardropType; + + m_Layers = aOther.m_Layers; + } +} + void ZONE_SETTINGS::SetCornerRadius( int aRadius ) { diff --git a/pcbnew/zone_settings.h b/pcbnew/zone_settings.h index 0f3c042569..b6abf01fe3 100644 --- a/pcbnew/zone_settings.h +++ b/pcbnew/zone_settings.h @@ -30,6 +30,8 @@ #ifndef ZONE_SETTINGS_H_ #define ZONE_SETTINGS_H_ +#include +#include #include #include #include @@ -44,6 +46,13 @@ enum class ZONE_FILL_MODE HATCH_PATTERN = 1 // fill zone using a grid pattern }; +struct ZONE_LAYER_PROPERTIES +{ + std::optional hatching_offset; + + bool operator==( const ZONE_LAYER_PROPERTIES& aOther ) const; +}; + /// Zone border styles enum class ZONE_BORDER_DISPLAY_STYLE @@ -120,6 +129,8 @@ public: */ TEARDROP_TYPE m_TeardropType; + std::map m_layerProperties; + private: int m_cornerSmoothingType; // Corner smoothing type unsigned int m_cornerRadius; // Corner chamfer distance / fillet radius @@ -187,6 +198,19 @@ public: */ void ExportSetting( ZONE& aTarget, bool aFullExport = true ) const; + /** + * Function CopyFrom + * copy settings from a different ZONE_SETTINGS object + * + * @param aOther the other ZONE_SETTINGS + * @param aCopyFull if false: some parameters are not copied. + * This option is used specifically to copy zone settings from + * a zone to the default zone settings. + * There, the layer information is not needed, plus layer specific + * properties should not be overridden in the zone default settings. + */ + void CopyFrom( const ZONE_SETTINGS& aOther, bool aCopyFull = true ); + void SetCornerSmoothingType( int aType) { m_cornerSmoothingType = aType; } int GetCornerSmoothingType() const { return m_cornerSmoothingType; } diff --git a/qa/data/pcbnew/api_kitchen_sink.kicad_pcb b/qa/data/pcbnew/api_kitchen_sink.kicad_pcb index 9069a921d3..1057b99f55 100644 --- a/qa/data/pcbnew/api_kitchen_sink.kicad_pcb +++ b/qa/data/pcbnew/api_kitchen_sink.kicad_pcb @@ -1022,6 +1022,12 @@ (thermal_gap 0.762) (thermal_bridge_width 0.508) ) + (property + (layer "B.Cu") + (hatch_position + (xy 1 1) + ) + ) (polygon (pts (xy 232.833 116.922) (xy 232.833 38.436) (xy 133.9 38.436) (xy 133.9 116.922) diff --git a/qa/data/pcbnew/plugins/kicad_sexpr/Issue19775_ZoneLayers/LayerEnumerate.kicad_pcb b/qa/data/pcbnew/plugins/kicad_sexpr/Issue19775_ZoneLayers/LayerEnumerate.kicad_pcb index 45cca24259..a586b535d8 100644 --- a/qa/data/pcbnew/plugins/kicad_sexpr/Issue19775_ZoneLayers/LayerEnumerate.kicad_pcb +++ b/qa/data/pcbnew/plugins/kicad_sexpr/Issue19775_ZoneLayers/LayerEnumerate.kicad_pcb @@ -148,6 +148,12 @@ (island_removal_mode 1) (island_area_min 10) ) + (property + (layer "F.Cu") + (hatch_position + (xy 1 1) + ) + ) (polygon (pts (xy 134 70.5) (xy 159.5 70.5) (xy 159.5 86.5) (xy 137 86.5) diff --git a/qa/tests/pcbnew/pcb_io/kicad_sexpr/test_kicad_sexpr.cpp b/qa/tests/pcbnew/pcb_io/kicad_sexpr/test_kicad_sexpr.cpp index 1d03172fde..ea06f67ba8 100644 --- a/qa/tests/pcbnew/pcb_io/kicad_sexpr/test_kicad_sexpr.cpp +++ b/qa/tests/pcbnew/pcb_io/kicad_sexpr/test_kicad_sexpr.cpp @@ -96,6 +96,7 @@ BOOST_AUTO_TEST_CASE( Issue19775_ZoneLayerWildcards ) BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) ); BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 ); BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 ); + BOOST_CHECK( z->LayerProperties().contains( F_Cu ) ); } }