/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include ZONE_CREATE_HELPER::ZONE_CREATE_HELPER( DRAWING_TOOL& aTool, PARAMS& aParams ): m_tool( aTool ), m_params( aParams ), m_parentView( *aTool.getView() ) { m_parentView.Add( &m_previewItem ); } ZONE_CREATE_HELPER::~ZONE_CREATE_HELPER() { // remove the preview from the view m_parentView.SetVisible( &m_previewItem, false ); m_parentView.Remove( &m_previewItem ); } void ZONE_CREATE_HELPER::setUniquePriority( ZONE_SETTINGS& aZoneInfo ) { PCB_BASE_EDIT_FRAME* frame = m_tool.getEditFrame(); BOARD* board = frame->GetBoard(); // By default, new zones get the first unused priority std::set priorities; for( ZONE* zone : board->Zones() ) { if( zone->GetTeardropAreaType() == TEARDROP_TYPE::TD_NONE && ( zone->GetLayerSet() & LSET::AllCuMask() ).any() && !zone->GetIsRuleArea() ) { priorities.insert( zone->GetAssignedPriority() ); } } unsigned priority = 0; for( unsigned exist_priority : priorities ) { if( priority != exist_priority ) break; ++priority; } aZoneInfo.m_ZonePriority = priority; } std::unique_ptr ZONE_CREATE_HELPER::createNewZone( bool aKeepout ) { PCB_BASE_EDIT_FRAME* frame = m_tool.getEditFrame(); BOARD* board = frame->GetBoard(); BOARD_ITEM_CONTAINER* parent = m_tool.m_frame->GetModel(); KIGFX::VIEW_CONTROLS* controls = m_tool.GetManager()->GetViewControls(); std::set highlightedNets = board->GetHighLightNetCodes(); // Get the current default settings for zones ZONE_SETTINGS zoneInfo = frame->GetZoneSettings(); zoneInfo.m_Layers.reset().set( m_params.m_layer ); // TODO(JE) multilayer defaults? zoneInfo.m_NetcodeSelection = highlightedNets.empty() ? -1 : *highlightedNets.begin(); zoneInfo.SetIsRuleArea( m_params.m_keepout ); if( m_params.m_mode != ZONE_MODE::GRAPHIC_POLYGON && ( zoneInfo.m_Layers & LSET::AllCuMask() ).any() ) { setUniquePriority( zoneInfo ); } // If we don't have a net from highlighting, maybe we can get one from the selection PCB_SELECTION_TOOL* selectionTool = m_tool.GetManager()->GetTool(); if( selectionTool && !selectionTool->GetSelection().Empty() && zoneInfo.m_NetcodeSelection == -1 ) { EDA_ITEM* item = *selectionTool->GetSelection().GetItems().begin(); if( BOARD_CONNECTED_ITEM* bci = dynamic_cast( item ) ) zoneInfo.m_NetcodeSelection = bci->GetNetCode(); } if( m_params.m_mode != ZONE_MODE::GRAPHIC_POLYGON ) { // Show options dialog int dialogResult; if( m_params.m_keepout ) dialogResult = InvokeRuleAreaEditor( frame, &zoneInfo ); else if( ( zoneInfo.m_Layers & LSET::AllCuMask() ).any() ) dialogResult = InvokeCopperZonesEditor( frame, &zoneInfo ); else dialogResult = InvokeNonCopperZonesEditor( frame, &zoneInfo ); if( dialogResult == wxID_CANCEL ) return nullptr; controls->WarpMouseCursor( controls->GetCursorPosition(), true ); frame->GetCanvas()->SetFocus(); } wxASSERT( !m_tool.m_isFootprintEditor || ( parent->Type() == PCB_FOOTPRINT_T ) ); std::unique_ptr newZone = std::make_unique( parent ); // Apply the selected settings zoneInfo.ExportSetting( *newZone ); return newZone; } std::unique_ptr ZONE_CREATE_HELPER::createZoneFromExisting( const ZONE& aSrcZone ) { BOARD* board = m_tool.getModel(); std::unique_ptr newZone = std::make_unique( board ); ZONE_SETTINGS zoneSettings; zoneSettings << aSrcZone; zoneSettings.ExportSetting( *newZone ); return newZone; } void ZONE_CREATE_HELPER::performZoneCutout( ZONE& aZone, const ZONE& aCutout ) { BOARD_COMMIT commit( &m_tool ); std::vector newZones; // Clear the selection before removing the old zone auto toolMgr = m_tool.GetManager(); toolMgr->RunAction( PCB_ACTIONS::selectionClear ); SHAPE_POLY_SET originalOutline( *aZone.Outline() ); originalOutline.BooleanSubtract( *aCutout.Outline(), SHAPE_POLY_SET::PM_FAST ); // After substracting the hole, originalOutline can have more than one main outline. // But a zone can have only one main outline, so create as many zones as originalOutline // contains main outlines: for( int outline = 0; outline < originalOutline.OutlineCount(); outline++ ) { auto newZoneOutline = new SHAPE_POLY_SET; newZoneOutline->AddOutline( originalOutline.Outline( outline ) ); // Add holes (if any) to the new zone outline: for (int hole = 0; hole < originalOutline.HoleCount( outline ) ; hole++ ) newZoneOutline->AddHole( originalOutline.CHole( outline, hole ) ); auto newZone = new ZONE( aZone ); newZone->SetOutline( newZoneOutline ); newZone->SetLocalFlags( 1 ); newZone->HatchBorder(); newZone->UnFill(); newZones.push_back( newZone ); commit.Add( newZone ); } commit.Remove( &aZone ); commit.Push( _( "Add Zone Cutout" ) ); // Select the new zone and set it as the source for the next cutout if( newZones.empty() ) { m_params.m_sourceZone = nullptr; } else { m_params.m_sourceZone = newZones[0]; toolMgr->RunAction( PCB_ACTIONS::selectItem, newZones[0] ); } } void ZONE_CREATE_HELPER::commitZone( std::unique_ptr aZone ) { switch ( m_params.m_mode ) { case ZONE_MODE::CUTOUT: // For cutouts, subtract from the source performZoneCutout( *m_params.m_sourceZone, *aZone ); break; case ZONE_MODE::ADD: case ZONE_MODE::SIMILAR: { BOARD_COMMIT commit( &m_tool ); aZone->HatchBorder(); commit.Add( aZone.get() ); commit.Push( _( "Add a zone" ) ); m_tool.GetManager()->RunAction( PCB_ACTIONS::selectItem, aZone.release() ); break; } case ZONE_MODE::GRAPHIC_POLYGON: { BOARD_COMMIT commit( &m_tool ); BOARD* board = m_tool.getModel(); PCB_LAYER_ID layer = m_params.m_layer; PCB_SHAPE* poly = new PCB_SHAPE( m_tool.m_frame->GetModel() ); poly->SetShape( SHAPE_T::POLY ); poly->SetFilled( layer != Edge_Cuts && layer != F_CrtYd && layer != B_CrtYd ); poly->SetStroke( STROKE_PARAMS( board->GetDesignSettings().GetLineThickness( layer ), LINE_STYLE::SOLID ) ); poly->SetLayer( layer ); poly->SetPolyShape( *aZone->Outline() ); commit.Add( poly ); commit.Push( _( "Add Polygon" ) ); m_tool.GetManager()->RunAction( PCB_ACTIONS::selectItem, poly ); break; } } } bool ZONE_CREATE_HELPER::OnFirstPoint( POLYGON_GEOM_MANAGER& aMgr ) { // if we don't have a zone, create one if( !m_zone ) { if( m_params.m_sourceZone ) m_zone = createZoneFromExisting( *m_params.m_sourceZone ); else m_zone = createNewZone( m_params.m_keepout ); if( m_zone ) { m_tool.GetManager()->RunAction( PCB_ACTIONS::selectionClear ); // set up properties from zone const auto& settings = *m_parentView.GetPainter()->GetSettings(); COLOR4D color = settings.GetColor( nullptr, m_zone->GetFirstLayer() ); m_previewItem.SetStrokeColor( COLOR4D::WHITE ); m_previewItem.SetFillColor( color.WithAlpha( 0.2 ) ); m_parentView.SetVisible( &m_previewItem, true ); aMgr.SetLeaderMode( m_tool.Is45Limited() ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 : POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT ); } } return m_zone != nullptr; } void ZONE_CREATE_HELPER::OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr ) { // Handle a cancel-interactive if( m_zone && !aMgr.IsPolygonInProgress() ) { m_zone = nullptr; m_parentView.SetVisible( &m_previewItem, false ); return; } // send the points to the preview item m_previewItem.SetPoints( aMgr.GetLockedInPoints(), aMgr.GetLeaderLinePoints(), aMgr.GetLoopLinePoints() ); m_parentView.Update( &m_previewItem, KIGFX::GEOMETRY ); } void ZONE_CREATE_HELPER::OnComplete( const POLYGON_GEOM_MANAGER& aMgr ) { auto& finalPoints = aMgr.GetLockedInPoints(); if( finalPoints.PointCount() < 3 ) { // just scrap the zone in progress m_zone = nullptr; } else { // if m_params.m_mode == DRAWING_TOOL::ZONE_MODE::CUTOUT, m_zone will be merged to the // existing zone as a new hole. m_zone->Outline()->NewOutline(); auto* outline = m_zone->Outline(); for( int i = 0; i < finalPoints.PointCount(); ++i ) outline->Append( finalPoints.CPoint( i ) ); // In DEG45 mode, we may have intermediate points in the leader that should be included // as they are shown in the preview. These typically maintain the 45 constraint if( aMgr.GetLeaderMode() == POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 ) { const SHAPE_LINE_CHAIN leaderPts = aMgr.GetLeaderLinePoints(); for( int i = 1; i < leaderPts.PointCount(); i++ ) outline->Append( leaderPts.CPoint( i ) ); const SHAPE_LINE_CHAIN loopPts = aMgr.GetLoopLinePoints(); for( int i = 1; i < loopPts.PointCount() - 1; i++ ) outline->Append( loopPts.CPoint( i ) ); } SHAPE_LINE_CHAIN& chain = outline->Outline( 0 ); chain.SetClosed( true ); chain.Simplify( true ); // Remove the start point if it lies on the line between neighbouring points. // Simplify doesn't handle that currently. if( chain.PointCount() >= 3 ) { SEG seg( chain.CPoint( -1 ), chain.CPoint( 1 ) ); if( seg.LineDistance( chain.CPoint( 0 ) ) <= 1 ) chain.Remove( 0 ); } // hand the zone over to the committer commitZone( std::move( m_zone ) ); m_zone = nullptr; } m_parentView.SetVisible( &m_previewItem, false ); }