KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_board_commit.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21#include <tool/tool_manager.h>
23#include <board.h>
24#include <board_commit.h>
25#include <footprint.h>
26#include <pad.h>
27#include <pcb_shape.h>
28#include <pcb_text.h>
29#include <pcb_group.h>
30#include <lset.h>
32#include <pcb_view.h>
34
35BOOST_AUTO_TEST_SUITE( BoardCommit )
36
37BOOST_AUTO_TEST_CASE( RecursesThroughGroups )
38{
39 BOARD board;
40 TOOL_MANAGER mgr;
41 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
43 mgr.RegisterTool( dummyTool );
44 BOARD_COMMIT commit( dummyTool );
45
46 PCB_SHAPE s1( nullptr, SHAPE_T::SEGMENT );
47 PCB_SHAPE s2( nullptr, SHAPE_T::SEGMENT );
48 PCB_GROUP group( nullptr );
49 group.AddItem( &s1 );
50 group.AddItem( &s2 );
51
52 commit.Stage( &group, CHT_MODIFY, nullptr, RECURSE_MODE::RECURSE );
53
54 BOOST_CHECK_EQUAL( commit.GetStatus( &s1 ), CHT_MODIFY );
55 BOOST_CHECK_EQUAL( commit.GetStatus( &s2 ), CHT_MODIFY );
56}
57
58BOOST_AUTO_TEST_CASE( MakeImageCreatesTransientCopy )
59{
60 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
62
64 BOOST_CHECK( copy != &shape );
65 BOOST_CHECK( copy->HasFlag( UR_TRANSIENT ) );
66
67 delete copy;
68}
69
70BOOST_AUTO_TEST_CASE( ReturnsBoardFromManager )
71{
72 BOARD board;
73 TOOL_MANAGER mgr;
74 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
76 mgr.RegisterTool( dummyTool );
77
78 BOARD_COMMIT commit( dummyTool );
79
80 BOOST_CHECK_EQUAL( commit.GetBoard(), &board );
81}
82
83BOOST_AUTO_TEST_CASE( RemoveFootprintTextFromBoardEditor )
84{
85 BOARD board;
86 TOOL_MANAGER mgr;
87 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
89 mgr.RegisterTool( dummyTool );
90
91 FOOTPRINT* fp = new FOOTPRINT( &board );
92 PCB_TEXT* text = new PCB_TEXT( fp );
93 text->SetText( wxT( "${REFERENCE}" ) );
94 text->SetLayer( F_Fab );
95 fp->Add( text );
96 board.Add( fp );
97
98 BOOST_REQUIRE_EQUAL( fp->GraphicalItems().size(), 1 );
99
100 {
101 BOARD_COMMIT commit( &mgr, true, false );
102 commit.Remove( text );
103 commit.Push( wxT( "Delete" ), SKIP_UNDO );
104 }
105
106 BOOST_CHECK_EQUAL( fp->GraphicalItems().size(), 0 );
107}
108
109// A COMMIT object reused across Push() calls (such as the group tool's persistent commit)
110// must not carry m_addedItems from one commit into the next. If it does, modifying a
111// previously-added item in a later commit is silently dropped and no undo entry is created.
112// This is the root cause of nested-group undo corruption (work item 24146).
113BOOST_AUTO_TEST_CASE( ReusedCommitModifyAfterAdd )
114{
115 BOARD board;
116 TOOL_MANAGER mgr;
117 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
118 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
119 mgr.RegisterTool( dummyTool );
120
121 BOARD_COMMIT commit( &mgr, true, false );
122
123 PCB_SHAPE* shape = new PCB_SHAPE( &board, SHAPE_T::SEGMENT );
124
125 // First commit adds the shape. After Push the commit is reused.
126 commit.Add( shape );
127 commit.Push( wxT( "Add" ), SKIP_UNDO );
128
129 // Modifying the already-added shape in the next commit must record a change.
130 commit.Modify( shape );
131 BOOST_CHECK_EQUAL( commit.GetStatus( shape ), CHT_MODIFY );
132}
133
134// Removing a footprint frees its pads, fields and other owned children with it. A child that
135// sits in the selection on its own (the footprint itself unselected) must be pruned as well, or
136// PCB_SELECTION::updateDrawList() dereferences the freed child on the next repaint.
137BOOST_AUTO_TEST_CASE( RemoveFootprintPrunesSelectedChildren )
138{
139 BOARD board;
140 KIGFX::PCB_VIEW view;
141 TOOL_MANAGER mgr;
142 mgr.SetEnvironment( &board, &view, nullptr, nullptr, nullptr );
143
145 mgr.RegisterTool( selTool );
146
147 FOOTPRINT* fp = new FOOTPRINT( &board );
148 PAD* pad = new PAD( fp );
149 fp->Add( pad );
150 board.Add( fp );
151
152 selTool->AddItemToSel( pad, true );
153
154 BOOST_REQUIRE( selTool->GetSelection().Contains( pad ) );
155 BOOST_REQUIRE( !fp->IsSelected() );
156
157 BOARD_COMMIT commit( &mgr, true, false );
158 commit.Remove( fp );
159 commit.Push( wxT( "Delete footprint" ), SKIP_UNDO | SKIP_TEARDROPS );
160
161 BOOST_CHECK( !selTool->GetSelection().Contains( pad ) );
162
163 // With SKIP_UNDO the removed footprint is ours to free
164 delete fp;
165}
166
167// Undo after a drag must put the hops back with the stack.
168BOOST_AUTO_TEST_CASE( RevertAfterDraggingAViaStackRestoresItsHops )
169{
170 BOARD board;
171 TOOL_MANAGER mgr;
172
173 board.SetCopperLayerCount( 4 );
175 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
176
177 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
178 mgr.RegisterTool( dummyTool );
179
180 VECTOR2I origin( 10000000, 10000000 );
181
182 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
183 stack->SetStartLayer( F_Cu );
184 stack->SetEndLayer( In2_Cu );
185 stack->SetViaSize( 300000 );
186 stack->SetViaDrill( 150000 );
187 stack->SetPosition( origin );
188 board.Add( stack );
189 stack->Regenerate( &board, nullptr );
190
191 std::map<BOARD_ITEM*, VECTOR2I> before;
192
193 for( BOARD_ITEM* item : stack->GetBoardItems() )
194 before[item] = item->GetPosition();
195
196 BOOST_REQUIRE_EQUAL( before.size(), 2u );
197
198 BOARD_COMMIT commit( &mgr, true, false );
199
200 stack->EditStart( nullptr, &board, &commit );
201
202 // A drag is a stream of motion events.
203 for( const VECTOR2I& step : { VECTOR2I( 500000, 0 ), VECTOR2I( 500000, 250000 ) } )
204 {
205 stack->Move( step );
206 stack->Update( nullptr, &board, &commit );
207 }
208
209 stack->EditFinish( nullptr, &board, &commit );
210
211 commit.Revert();
212
213 BOOST_CHECK_EQUAL( stack->GetPosition(), origin );
214
215 for( const auto& [item, pos] : before )
216 {
217 BOOST_CHECK_MESSAGE( item->GetPosition() == pos,
218 "hop left behind at " + item->GetPosition().Format() + " instead of " + pos.Format() );
219 }
220}
221
222
223// Editing a stack stages its members, then rebuilds them. A member must not carry both a
224// modify and a remove line, or redo trips over the pair.
225BOOST_AUTO_TEST_CASE( RegeneratingAViaStackDoesNotDoubleStageItsHops )
226{
227 BOARD board;
228 TOOL_MANAGER mgr;
229
230 board.SetCopperLayerCount( 6 );
232 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
233
234 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
235 mgr.RegisterTool( dummyTool );
236
237 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
238 stack->SetStartLayer( F_Cu );
239 stack->SetEndLayer( In2_Cu );
240 stack->SetViaSize( 300000 );
241 stack->SetViaDrill( 150000 );
242 stack->SetPosition( VECTOR2I( 10000000, 10000000 ) );
243 board.Add( stack );
244 stack->Regenerate( &board, nullptr );
245
246 std::vector<BOARD_ITEM*> original( stack->GetBoardItems().begin(), stack->GetBoardItems().end() );
247 BOOST_REQUIRE_EQUAL( original.size(), 2u );
248
249 BOARD_COMMIT commit( &mgr, true, false );
250
251 stack->EditStart( nullptr, &board, &commit );
252
253 // Widening the span changes the hop set, so the members are replaced rather than reused.
254 stack->SetEndLayer( In3_Cu );
255 stack->Update( nullptr, &board, &commit );
256 stack->EditFinish( nullptr, &board, &commit );
257
258 for( BOARD_ITEM* item : original )
259 {
260 BOOST_CHECK_MESSAGE( commit.GetStatus( item ) == CHT_REMOVE,
261 "replaced hop is staged as " << commit.GetStatus( item ) << ", expected CHT_REMOVE only" );
262 }
263
264 commit.Revert();
265}
266
267
268// Update outside an edit must do nothing. Regenerating there would delete and rebuild the
269// hops behind the back of whatever holds them.
270BOOST_AUTO_TEST_CASE( UpdateOutsideAnEditIsInert )
271{
272 BOARD board;
273 TOOL_MANAGER mgr;
274
275 board.SetCopperLayerCount( 4 );
277 mgr.SetEnvironment( &board, nullptr, nullptr, nullptr, nullptr );
278
279 KI_TEST::DUMMY_TOOL* dummyTool = new KI_TEST::DUMMY_TOOL();
280 mgr.RegisterTool( dummyTool );
281
282 PCB_VIA_STACK* stack = new PCB_VIA_STACK( &board, F_Cu );
283 stack->SetStartLayer( F_Cu );
284 stack->SetEndLayer( In2_Cu );
285 stack->SetViaSize( 300000 );
286 stack->SetViaDrill( 150000 );
287 stack->SetPosition( VECTOR2I( 10000000, 10000000 ) );
288 board.Add( stack );
289 stack->Regenerate( &board, nullptr );
290
291 std::vector<BOARD_ITEM*> before( stack->GetBoardItems().begin(), stack->GetBoardItems().end() );
292 BOOST_REQUIRE_EQUAL( before.size(), 2u );
293
294 BOARD_COMMIT commit( &mgr, true, false );
295
296 // No EditStart, so IN_EDIT is not set.
297 BOOST_CHECK( !stack->Update( nullptr, &board, &commit ) );
298
299 std::vector<BOARD_ITEM*> after( stack->GetBoardItems().begin(), stack->GetBoardItems().end() );
300
301 BOOST_REQUIRE_EQUAL( after.size(), before.size() );
302 BOOST_CHECK_MESSAGE( std::set<BOARD_ITEM*>( before.begin(), before.end() )
303 == std::set<BOARD_ITEM*>( after.begin(), after.end() ),
304 "the hops were rebuilt outside an edit" );
305}
306
307
309
#define SKIP_TEARDROPS
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
COMMIT & Stage(EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE) override
Add a change of the item aItem of type aChangeType to the change list.
BOARD * GetBoard() const
virtual void Revert() override
Revert the commit by restoring the modified items state.
static EDA_ITEM * MakeImage(EDA_ITEM *aItem)
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
void SetCopperLayerCount(int aCount)
Definition board.cpp:1137
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1203
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
int GetStatus(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Returns status of an item.
Definition commit.cpp:232
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
bool IsSelected() const
Definition eda_item.h:134
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
DRAWINGS & GraphicalItems()
Definition footprint.h:407
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:672
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
Definition pad.h:61
void SetPosition(const VECTOR2I &aPos) override
VECTOR2I GetPosition() const override
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
A microvia stack: several single hop microvias, plus connecting traces for staggered stacks,...
bool Update(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void EditStart(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void EditFinish(GENERATOR_TOOL *aTool, BOARD *aBoard, BOARD_COMMIT *aCommit) override
void Regenerate(BOARD *aBoard, BOARD_COMMIT *aCommit)
Rebuild the member vias (and, for a staggered stack, the connecting traces) from the current stack se...
void SetStartLayer(PCB_LAYER_ID aLayer)
void SetViaSize(int aSize)
void SetViaDrill(int aDrill)
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void SetEndLayer(PCB_LAYER_ID aLayer)
int AddItemToSel(const TOOL_EVENT &aEvent)
bool Contains(EDA_ITEM *aItem) const
Definition selection.cpp:84
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
@ CHT_MODIFY
Definition commit.h:40
@ CHT_REMOVE
Definition commit.h:39
@ RECURSE
Definition eda_item.h:51
#define UR_TRANSIENT
indicates the item is owned by the undo/redo stack
@ SEGMENT
Definition eda_shape.h:56
@ In2_Cu
Definition layer_ids.h:63
@ F_Fab
Definition layer_ids.h:115
@ In3_Cu
Definition layer_ids.h:64
@ F_Cu
Definition layer_ids.h:60
Class to handle a set of BOARD_ITEMs.
#define SKIP_UNDO
Definition sch_commit.h:38
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(RecursesThroughGroups)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683