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