1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017-2019 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  */
23 
24 #include <core/spinlock.h>
25 #include <connectivity/connectivity_data.h>
26 #include <tools/zone_create_helper.h>
27 #include <tool/tool_manager.h>
28 #include <zone.h>
29 #include <pcb_shape.h>
30 #include <footprint.h>
31 #include <fp_shape.h>
32 #include <board_commit.h>
33 #include <board_design_settings.h>
34 #include <pcb_painter.h>
35 #include <pcbnew_settings.h>
36 #include <tools/pcb_actions.h>
37 #include <tools/pcb_selection_tool.h>
38 #include <zone_filler.h>
39 
ZONE_CREATE_HELPER(DRAWING_TOOL & aTool,PARAMS & aParams)40 ZONE_CREATE_HELPER::ZONE_CREATE_HELPER( DRAWING_TOOL& aTool, PARAMS& aParams ):
41         m_tool( aTool ),
42         m_params( aParams ),
43         m_parentView( *aTool.getView() )
44 {
45     m_parentView.Add( &m_previewItem );
46 }
47 
48 
~ZONE_CREATE_HELPER()49 ZONE_CREATE_HELPER::~ZONE_CREATE_HELPER()
50 {
51     // remove the preview from the view
52     m_parentView.SetVisible( &m_previewItem, false );
53     m_parentView.Remove( &m_previewItem );
54 }
55 
56 
createNewZone(bool aKeepout)57 std::unique_ptr<ZONE> ZONE_CREATE_HELPER::createNewZone( bool aKeepout )
58 {
59     PCB_BASE_EDIT_FRAME*  frame = m_tool.getEditFrame<PCB_BASE_EDIT_FRAME>();
60     BOARD*                board = frame->GetBoard();
61     BOARD_ITEM_CONTAINER* parent = m_tool.m_frame->GetModel();
62     KIGFX::VIEW_CONTROLS* controls = m_tool.GetManager()->GetViewControls();
63     std::set<int>         highlightedNets = board->GetHighLightNetCodes();
64 
65     // Get the current default settings for zones
66     ZONE_SETTINGS         zoneInfo = frame->GetZoneSettings();
67     zoneInfo.m_Layers.reset().set( m_params.m_layer );  // TODO(JE) multilayer defaults?
68     zoneInfo.m_NetcodeSelection = highlightedNets.empty() ? -1 : *highlightedNets.begin();
69     zoneInfo.SetIsRuleArea( m_params.m_keepout );
70     zoneInfo.m_Zone_45_Only = ( m_params.m_leaderMode == POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 );
71 
72     // If we don't have a net from highlighting, maybe we can get one from the selection
73     PCB_SELECTION_TOOL* selectionTool = m_tool.GetManager()->GetTool<PCB_SELECTION_TOOL>();
74 
75     if( selectionTool && !selectionTool->GetSelection().Empty()
76             && zoneInfo.m_NetcodeSelection == -1 )
77     {
78         EDA_ITEM* item = *selectionTool->GetSelection().GetItems().begin();
79 
80         if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
81             zoneInfo.m_NetcodeSelection = bci->GetNetCode();
82     }
83 
84     if( m_params.m_mode != ZONE_MODE::GRAPHIC_POLYGON )
85     {
86         // Get the current default settings for zones
87 
88         // Show options dialog
89         int dialogResult;
90 
91         if( m_params.m_keepout )
92             dialogResult = InvokeRuleAreaEditor( frame, &zoneInfo );
93         else
94         {
95             // TODO(JE) combine these dialogs?
96             if( ( zoneInfo.m_Layers & LSET::AllCuMask() ).any() )
97                 dialogResult = InvokeCopperZonesEditor( frame, &zoneInfo );
98             else
99                 dialogResult = InvokeNonCopperZonesEditor( frame, &zoneInfo );
100         }
101 
102         if( dialogResult == wxID_CANCEL )
103             return nullptr;
104 
105         controls->WarpCursor( controls->GetCursorPosition(), true );
106     }
107 
108     // The new zone is a ZONE if created in the board editor and a FP_ZONE if created in the
109     // footprint editor
110     wxASSERT( !m_tool.m_isFootprintEditor || ( parent->Type() == PCB_FOOTPRINT_T ) );
111 
112     std::unique_ptr<ZONE> newZone = m_tool.m_isFootprintEditor ?
113                                                 std::make_unique<FP_ZONE>( parent ) :
114                                                 std::make_unique<ZONE>( parent );
115 
116     // Apply the selected settings
117     zoneInfo.ExportSetting( *newZone );
118 
119     return newZone;
120 }
121 
122 
createZoneFromExisting(const ZONE & aSrcZone)123 std::unique_ptr<ZONE> ZONE_CREATE_HELPER::createZoneFromExisting( const ZONE& aSrcZone )
124 {
125     BOARD* board = m_tool.getModel<BOARD>();
126 
127     std::unique_ptr<ZONE> newZone = std::make_unique<ZONE>( board );
128 
129     ZONE_SETTINGS zoneSettings;
130     zoneSettings << aSrcZone;
131 
132     zoneSettings.ExportSetting( *newZone );
133 
134     return newZone;
135 }
136 
137 
performZoneCutout(ZONE & aZone,const ZONE & aCutout)138 void ZONE_CREATE_HELPER::performZoneCutout( ZONE& aZone, const ZONE& aCutout )
139 {
140     BOARD_COMMIT commit( &m_tool );
141     std::vector<ZONE*> newZones;
142 
143     // Clear the selection before removing the old zone
144     auto toolMgr = m_tool.GetManager();
145     toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
146 
147     SHAPE_POLY_SET originalOutline( *aZone.Outline() );
148     originalOutline.BooleanSubtract( *aCutout.Outline(), SHAPE_POLY_SET::PM_FAST );
149 
150     // After substracting the hole, originalOutline can have more than one
151     // main outline.
152     // But a zone can have only one main outline, so create as many zones as
153     // originalOutline contains main outlines:
154     for( int outline = 0; outline < originalOutline.OutlineCount(); outline++ )
155     {
156         auto newZoneOutline = new SHAPE_POLY_SET;
157         newZoneOutline->AddOutline( originalOutline.Outline( outline ) );
158 
159         // Add holes (if any) to thez new zone outline:
160         for (int hole = 0; hole < originalOutline.HoleCount( outline ) ; hole++ )
161             newZoneOutline->AddHole( originalOutline.CHole( outline, hole ) );
162 
163         auto newZone = new ZONE( aZone );
164         newZone->SetOutline( newZoneOutline );
165         newZone->SetLocalFlags( 1 );
166         newZone->HatchBorder();
167         newZone->UnFill();
168         newZones.push_back( newZone );
169         commit.Add( newZone );
170     }
171 
172     commit.Remove( &aZone );
173 
174     // TODO Refill zones when KiCad supports auto re-fill
175 
176     commit.Push( _( "Add a zone cutout" ) );
177 
178     // Select the new zone and set it as the source for the next cutout
179     if( newZones.empty() )
180     {
181         m_params.m_sourceZone = nullptr;
182     }
183     else
184     {
185         m_params.m_sourceZone = newZones[0];
186         toolMgr->RunAction( PCB_ACTIONS::selectItem, true, newZones[0] );
187     }
188 
189 }
190 
191 
commitZone(std::unique_ptr<ZONE> aZone)192 void ZONE_CREATE_HELPER::commitZone( std::unique_ptr<ZONE> aZone )
193 {
194     switch ( m_params.m_mode )
195     {
196         case ZONE_MODE::CUTOUT:
197             // For cutouts, subtract from the source
198             performZoneCutout( *m_params.m_sourceZone, *aZone );
199             break;
200 
201         case ZONE_MODE::ADD:
202         case ZONE_MODE::SIMILAR:
203         {
204             BOARD_COMMIT commit( &m_tool );
205             BOARD*       board = m_tool.getModel<BOARD>();
206 
207             aZone->HatchBorder();
208 
209             // TODO Refill zones when KiCad supports auto re-fill
210 
211             commit.Add( aZone.get() );
212 
213             std::lock_guard<KISPINLOCK> lock( board->GetConnectivity()->GetLock() );
214 
215             commit.Push( _( "Add a zone" ) );
216             m_tool.GetManager()->RunAction( PCB_ACTIONS::selectItem, true, aZone.release() );
217             break;
218         }
219 
220         case ZONE_MODE::GRAPHIC_POLYGON:
221         {
222             BOARD_COMMIT          commit( &m_tool );
223             BOARD*                board = m_tool.getModel<BOARD>();
224             PCB_LAYER_ID          layer = m_params.m_layer;
225             PCB_SHAPE*            poly;
226 
227             if( m_tool.m_isFootprintEditor )
228                 poly = new FP_SHAPE( static_cast<FOOTPRINT*>( m_tool.m_frame->GetModel() ) );
229             else
230                 poly = new PCB_SHAPE();
231 
232             poly->SetShape( SHAPE_T::POLY );
233 
234             if( layer == Edge_Cuts || layer == F_CrtYd || layer == B_CrtYd )
235                 poly->SetFilled( false );
236             else
237                 poly->SetFilled( true );
238 
239             poly->SetWidth( board->GetDesignSettings().GetLineThickness( m_params.m_layer ) );
240             poly->SetLayer( layer );
241             poly->SetPolyShape( *aZone->Outline() );
242 
243             commit.Add( poly );
244             m_tool.GetManager()->RunAction( PCB_ACTIONS::selectItem, true, poly );
245 
246             commit.Push( _( "Add a graphical polygon" ) );
247 
248             break;
249         }
250     }
251 }
252 
253 
OnFirstPoint(POLYGON_GEOM_MANAGER & aMgr)254 bool ZONE_CREATE_HELPER::OnFirstPoint( POLYGON_GEOM_MANAGER& aMgr )
255 {
256     // if we don't have a zone, create one
257     // the user's choice here can affect things like the colour of the preview
258     if( !m_zone )
259     {
260         if( m_params.m_sourceZone )
261             m_zone = createZoneFromExisting( *m_params.m_sourceZone );
262         else
263             m_zone = createNewZone( m_params.m_keepout );
264 
265         if( m_zone )
266         {
267             m_tool.GetManager()->RunAction( PCB_ACTIONS::selectionClear, true );
268 
269             // set up properties from zone
270             const auto& settings = *m_parentView.GetPainter()->GetSettings();
271             COLOR4D color = settings.GetColor( nullptr, m_zone->GetLayer() );
272 
273             m_previewItem.SetStrokeColor( COLOR4D::WHITE );
274             m_previewItem.SetFillColor( color.WithAlpha( 0.2 ) );
275 
276             m_parentView.SetVisible( &m_previewItem, true );
277 
278             aMgr.SetLeaderMode( m_zone->GetHV45() ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
279                                                   : POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
280         }
281     }
282 
283     return m_zone != nullptr;
284 }
285 
286 
OnGeometryChange(const POLYGON_GEOM_MANAGER & aMgr)287 void ZONE_CREATE_HELPER::OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr )
288 {
289     // send the points to the preview item
290     m_previewItem.SetPoints( aMgr.GetLockedInPoints(), aMgr.GetLeaderLinePoints() );
291     m_parentView.Update( &m_previewItem, KIGFX::GEOMETRY );
292 }
293 
294 
OnComplete(const POLYGON_GEOM_MANAGER & aMgr)295 void ZONE_CREATE_HELPER::OnComplete( const POLYGON_GEOM_MANAGER& aMgr )
296 {
297     auto& finalPoints = aMgr.GetLockedInPoints();
298 
299     if( finalPoints.PointCount() < 3 )
300     {
301         // just scrap the zone in progress
302         m_zone = nullptr;
303     }
304     else
305     {
306         // if m_params.m_mode == DRAWING_TOOL::ZONE_MODE::CUTOUT, m_zone
307         // will be merged to the existing zone as a new hole.
308         m_zone->Outline()->NewOutline();
309         auto* outline = m_zone->Outline();
310 
311         for( int i = 0; i < finalPoints.PointCount(); ++i )
312             outline->Append( finalPoints.CPoint( i ) );
313 
314         // In DEG45 mode, we may have intermediate points in the leader that should be
315         // included as they are shown in the preview.  These typically maintain the
316         // 45 constraint
317         if( aMgr.GetLeaderMode() == POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 )
318         {
319             const auto& pts = aMgr.GetLeaderLinePoints();
320             for( int i = 1; i < pts.PointCount(); i++ )
321                 outline->Append( pts.CPoint( i ) );
322         }
323 
324         outline->Outline( 0 ).SetClosed( true );
325         outline->RemoveNullSegments();
326         outline->Simplify( SHAPE_POLY_SET::PM_FAST );
327 
328         // hand the zone over to the committer
329         commitZone( std::move( m_zone ) );
330         m_zone = nullptr;
331     }
332 
333     m_parentView.SetVisible( &m_previewItem, false );
334 }
335