KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_graphic_trim.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
22#include <board.h>
23#include <geometry/shape_arc.h>
24#include <layer_ids.h>
25#include <pcb_shape.h>
26#include <pcb_track.h>
27#include <tools/graphic_trim.h>
28
29#include <limits>
30
31
32BOOST_AUTO_TEST_SUITE( GraphicTrim )
33
34
35static PCB_SHAPE makeLine( const VECTOR2I& aStart, const VECTOR2I& aEnd, PCB_LAYER_ID aLayer = Dwgs_User )
36{
37 PCB_SHAPE line( nullptr, SHAPE_T::SEGMENT );
38 line.SetStart( aStart );
39 line.SetEnd( aEnd );
40 line.SetLayer( aLayer );
41 return line;
42}
43
44
45static PCB_SHAPE makeArc( const VECTOR2I& aCenter, const VECTOR2I& aStart, double aAngle,
46 PCB_LAYER_ID aLayer = Dwgs_User )
47{
48 SHAPE_ARC geometry( aCenter, aStart, EDA_ANGLE( aAngle, DEGREES_T ) );
49 PCB_SHAPE arc( nullptr, SHAPE_T::ARC );
50 arc.SetArcGeometry( geometry.GetP0(), geometry.GetArcMid(), geometry.GetP1() );
51 arc.SetLayer( aLayer );
52 return arc;
53}
54
55
56// One cut keeps the side the pointer is not on.
57BOOST_AUTO_TEST_CASE( OneBoundaryRemovesThePointerSide )
58{
59 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
60 PCB_SHAPE boundary = makeLine( { 70, -10 }, { 70, 10 } );
61
62 GRAPHIC_EDIT_RESULT afterCut = GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 2 }, { &boundary } );
63
64 BOOST_REQUIRE( afterCut );
65 BOOST_REQUIRE_EQUAL( afterCut.m_Geometry.size(), 1 );
66 BOOST_CHECK_EQUAL( afterCut.m_Geometry[0].m_Start, VECTOR2I( 0, 0 ) );
67 BOOST_CHECK_EQUAL( afterCut.m_Geometry[0].m_End, VECTOR2I( 70, 0 ) );
68
69 GRAPHIC_EDIT_RESULT beforeCut = GRAPHIC_TRIM_PLANNER::Plan( source, { 10, -2 }, { &boundary } );
70
71 BOOST_REQUIRE( beforeCut );
72 BOOST_REQUIRE_EQUAL( beforeCut.m_Geometry.size(), 1 );
73 BOOST_CHECK_EQUAL( beforeCut.m_Geometry[0].m_Start, VECTOR2I( 70, 0 ) );
74 BOOST_CHECK_EQUAL( beforeCut.m_Geometry[0].m_End, VECTOR2I( 100, 0 ) );
75}
76
77
78BOOST_AUTO_TEST_CASE( TwoBoundariesRemoveMiddleAndRetainExteriorPieces )
79{
80 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
81 PCB_SHAPE left = makeLine( { 30, -10 }, { 30, 10 } );
82 PCB_SHAPE right = makeLine( { 70, -10 }, { 70, 10 } );
83
84 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 50, 3 }, { &right, &left } );
85
87 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 2 );
88 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_Start, VECTOR2I( 0, 0 ) );
89 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 30, 0 ) );
90 BOOST_CHECK_EQUAL( result.m_Geometry[1].m_Start, VECTOR2I( 70, 0 ) );
91 BOOST_CHECK_EQUAL( result.m_Geometry[1].m_End, VECTOR2I( 100, 0 ) );
92}
93
94
95BOOST_AUTO_TEST_CASE( ChoosesNearestIntersectionsAroundClickedInterval )
96{
97 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
98 PCB_SHAPE cut10 = makeLine( { 10, -10 }, { 10, 10 } );
99 PCB_SHAPE cut30 = makeLine( { 30, -10 }, { 30, 10 } );
100 PCB_SHAPE cut70 = makeLine( { 70, -10 }, { 70, 10 } );
101 PCB_SHAPE cut90 = makeLine( { 90, -10 }, { 90, 10 } );
102
103 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 50, 0 }, { &cut90, &cut10, &cut70, &cut30 } );
104
106 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 2 );
107 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 30, 0 ) );
108 BOOST_CHECK_EQUAL( result.m_Geometry[1].m_Start, VECTOR2I( 70, 0 ) );
109}
110
111
112BOOST_AUTO_TEST_CASE( TrimsLineAtFiniteArcBoundary )
113{
114 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
115 PCB_SHAPE boundary = makeArc( { 70, 0 }, { 60, 0 }, 180.0 );
116
117 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 95, 0 }, { &boundary } );
118
120 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
121 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 80, 0 ) );
122}
123
124
125// Board scale. At a 100 IU radius the rounding is percent-level and swamps the cut.
126BOOST_AUTO_TEST_CASE( TrimsArcAtFiniteLineBoundaries )
127{
128 PCB_SHAPE source = makeArc( { 0, 0 }, { 100000, 0 }, 180.0 );
129 VECTOR2I firstCut = makeArc( { 0, 0 }, { 100000, 0 }, 45.0 ).GetEnd();
130 VECTOR2I secondCut = makeArc( { 0, 0 }, { 100000, 0 }, 135.0 ).GetEnd();
131 PCB_SHAPE firstBoundary = makeLine( { 0, 0 }, firstCut * 2 );
132 PCB_SHAPE secondBoundary = makeLine( { 0, 0 }, secondCut * 2 );
133
135 GRAPHIC_TRIM_PLANNER::Plan( source, source.GetArcMid(), { &secondBoundary, &firstBoundary } );
136
138 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 2 );
139 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_Start, source.GetStart() );
140 BOOST_CHECK_LE( result.m_Geometry[0].m_End.Distance( firstCut ), 2 );
141 BOOST_CHECK_LE( result.m_Geometry[1].m_Start.Distance( secondCut ), 2 );
142 BOOST_CHECK_EQUAL( result.m_Geometry[1].m_End, source.GetEnd() );
143}
144
145
146BOOST_AUTO_TEST_CASE( TrimsArcAtFiniteArcBoundaryAndPreservesWinding )
147{
148 // Board scale again. At 100 IU the rounding says nothing about the winding.
149 PCB_SHAPE source = makeArc( { 0, 0 }, { 100000, 0 }, -180.0 );
150 SHAPE_ARC sourceGeometry( source.GetStart(), source.GetArcMid(), source.GetEnd(), 0 );
151 VECTOR2I cut = sourceGeometry.GetArcMid();
152 VECTOR2I radial = cut - sourceGeometry.GetCenter();
153 VECTOR2I tangent( -radial.y / 5, radial.x / 5 );
154 PCB_SHAPE boundary = makeArc( cut + tangent, cut, 270.0 );
155
156 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, source.GetEnd(), { &boundary } );
157
159 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
160 SHAPE_ARC retained( result.m_Geometry[0].m_Start, result.m_Geometry[0].m_Mid, result.m_Geometry[0].m_End, 0 );
161 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_Start, source.GetStart() );
162 BOOST_CHECK_NE( result.m_Geometry[0].m_End, source.GetEnd() );
163 BOOST_CHECK_EQUAL( retained.IsClockwise(), sourceGeometry.IsClockwise() );
164 BOOST_CHECK_CLOSE( retained.GetRadius(), sourceGeometry.GetRadius(), 0.01 );
165}
166
167
168BOOST_AUTO_TEST_CASE( RejectsTrackSourceAndIgnoresTracksAndWrongLayerBoundaries )
169{
170 PCB_TRACK trackSource( nullptr );
171 trackSource.SetStart( { 0, 0 } );
172 trackSource.SetEnd( { 100, 0 } );
173 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
174 PCB_TRACK trackBoundary( nullptr );
175 trackBoundary.SetStart( { 50, -10 } );
176 trackBoundary.SetEnd( { 50, 10 } );
177 PCB_SHAPE wrongLayer = makeLine( { 70, -10 }, { 70, 10 }, Cmts_User );
178
179 BOOST_CHECK( !GRAPHIC_TRIM_PLANNER::Plan( trackSource, { 50, 0 }, { &source } ) );
180 BOOST_CHECK( !GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 0 }, { &trackBoundary, &wrongLayer } ) );
181}
182
183
184BOOST_AUTO_TEST_CASE( LockedSourceIsRejectedButLockedBoundaryIsEligible )
185{
186 BOARD board;
187 PCB_SHAPE source( &board, SHAPE_T::SEGMENT );
188 source.SetStart( { 0, 0 } );
189 source.SetEnd( { 100, 0 } );
190 source.SetLayer( Dwgs_User );
191 PCB_SHAPE boundary( &board, SHAPE_T::SEGMENT );
192 boundary.SetStart( { 70, -10 } );
193 boundary.SetEnd( { 70, 10 } );
194 boundary.SetLayer( Dwgs_User );
195 boundary.SetLocked( true );
196 BOOST_CHECK( GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 0 }, { &boundary } ) );
197
198 source.SetLocked( true );
199 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 0 }, { &boundary } );
200 BOOST_CHECK( !result );
201 BOOST_CHECK( result.m_Refusal == GRAPHIC_EDIT_REFUSAL::LOCKED_SOURCE );
202}
203
204
205BOOST_AUTO_TEST_CASE( RefusesCollinearOverlapAndPointerOnCut )
206{
207 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
208 PCB_SHAPE collinear = makeLine( { 20, 0 }, { 80, 0 } );
209 PCB_SHAPE middle = makeLine( { 50, -10 }, { 50, 10 } );
210
211 // A shared run has no one point to cut at.
212 GRAPHIC_EDIT_RESULT overlap = GRAPHIC_TRIM_PLANNER::Plan( source, { 50, 0 }, { &collinear } );
213 BOOST_CHECK( !overlap );
214 BOOST_CHECK( overlap.m_Refusal == GRAPHIC_EDIT_REFUSAL::AMBIGUOUS );
215
216 // The pointer is on the cut, so neither side is the one meant.
217 GRAPHIC_EDIT_RESULT onCut = GRAPHIC_TRIM_PLANNER::Plan( source, { 50, 0 }, { &middle } );
218 BOOST_CHECK( !onCut );
219 BOOST_CHECK( onCut.m_Refusal == GRAPHIC_EDIT_REFUSAL::AMBIGUOUS );
220}
221
222
223// A graze meets the source at one point, which is all a cut needs.
224BOOST_AUTO_TEST_CASE( TangentBoundaryStillCuts )
225{
226 PCB_SHAPE source = makeLine( { 0, 0 }, { 100000, 0 } );
227 PCB_SHAPE tangent = makeArc( { 50000, 10000 }, { 50000, 0 }, 180.0 );
228
229 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90000, 0 }, { &tangent } );
230
232 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
233 BOOST_CHECK_LE( result.m_Geometry[0].m_End.Distance( VECTOR2I( 50000, 0 ) ), 2 );
234}
235
236
237// The stem of a T is bounded at one end and free at the other. Trimming it removes all of it.
238BOOST_AUTO_TEST_CASE( ShapeBoundedOnlyAtAnEndIsRemovedWhole )
239{
240 PCB_SHAPE stem = makeLine( { 50000, 0 }, { 50000, 100000 } );
241 PCB_SHAPE crossbar = makeLine( { 0, 0 }, { 100000, 0 } );
242
243 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( stem, { 50000, 50000 }, { &crossbar } );
244
246 BOOST_CHECK( result.m_Geometry.empty() );
247 BOOST_REQUIRE_EQUAL( result.m_Preview.size(), 1 );
248 BOOST_CHECK_EQUAL( result.m_Preview[0].m_Start, VECTOR2I( 50000, 0 ) );
249 BOOST_CHECK_EQUAL( result.m_Preview[0].m_End, VECTOR2I( 50000, 100000 ) );
250}
251
252
253// The other two arms of the same T cut normally, either side of the stem.
254BOOST_AUTO_TEST_CASE( EitherArmOfATeeTrimsAtTheStem )
255{
256 PCB_SHAPE crossbar = makeLine( { 0, 0 }, { 100000, 0 } );
257 PCB_SHAPE stem = makeLine( { 50000, 0 }, { 50000, 100000 } );
258
259 GRAPHIC_EDIT_RESULT left = GRAPHIC_TRIM_PLANNER::Plan( crossbar, { 10000, 0 }, { &stem } );
260
262 BOOST_REQUIRE_EQUAL( left.m_Geometry.size(), 1 );
263 BOOST_CHECK_EQUAL( left.m_Geometry[0].m_Start, VECTOR2I( 50000, 0 ) );
264 BOOST_CHECK_EQUAL( left.m_Geometry[0].m_End, VECTOR2I( 100000, 0 ) );
265
266 GRAPHIC_EDIT_RESULT right = GRAPHIC_TRIM_PLANNER::Plan( crossbar, { 90000, 0 }, { &stem } );
267
269 BOOST_REQUIRE_EQUAL( right.m_Geometry.size(), 1 );
270 BOOST_CHECK_EQUAL( right.m_Geometry[0].m_Start, VECTOR2I( 0, 0 ) );
271 BOOST_CHECK_EQUAL( right.m_Geometry[0].m_End, VECTOR2I( 50000, 0 ) );
272}
273
274
275// The preview is the span that goes, not what is left.
276BOOST_AUTO_TEST_CASE( PreviewCarriesTheRemovedSpan )
277{
278 PCB_SHAPE source = makeLine( { 0, 0 }, { 100000, 0 } );
279 PCB_SHAPE left = makeLine( { 30000, -10000 }, { 30000, 10000 } );
280 PCB_SHAPE right = makeLine( { 70000, -10000 }, { 70000, 10000 } );
281
282 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 50000, 0 }, { &left, &right } );
283
285 BOOST_REQUIRE_EQUAL( result.m_Preview.size(), 1 );
286 BOOST_CHECK_EQUAL( result.m_Preview[0].m_Start, VECTOR2I( 30000, 0 ) );
287 BOOST_CHECK_EQUAL( result.m_Preview[0].m_End, VECTOR2I( 70000, 0 ) );
288}
289
290
291BOOST_AUTO_TEST_CASE( IgnoresUnrelatedCollinearAndTangentGeometry )
292{
293 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
294 PCB_SHAPE remoteCollinear = makeLine( { 200, 0 }, { 300, 0 } );
295 PCB_SHAPE remoteTangent = makeArc( { 200, 10 }, { 200, 0 }, 180.0 );
296 PCB_SHAPE validCut = makeLine( { 70, -10 }, { 70, 10 } );
297
299 GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 0 }, { &remoteCollinear, &remoteTangent, &validCut } );
300
302 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 70, 0 ) );
303}
304
305
306// An overlap contributes no cut of its own, but it does not spoil one that another shape gives.
307BOOST_AUTO_TEST_CASE( OverlapDoesNotSpoilAUsableCut )
308{
309 PCB_SHAPE source = makeLine( { 0, 0 }, { 100, 0 } );
310 PCB_SHAPE overlap = makeLine( { 20, 0 }, { 80, 0 } );
311 PCB_SHAPE validCut = makeLine( { 70, -10 }, { 70, 10 } );
312
313 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90, 0 }, { &overlap, &validCut } );
314
316 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
317 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 70, 0 ) );
318}
319
320
321// SEG::TCoef() wraps on a span wider than the coordinate range, so a metre is the most the
322// parameter can describe.
323BOOST_AUTO_TEST_CASE( HandlesBoardScaleParameter )
324{
325 constexpr int reach = 1000000000;
326 PCB_SHAPE source = makeLine( { -reach, -100 }, { reach, 100 } );
327 PCB_SHAPE boundary = makeLine( { 0, -1000 }, { 0, 1000 } );
328
329 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { reach / 2, 50 }, { &boundary } );
330
332 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
333 BOOST_CHECK_SMALL( result.m_Geometry[0].m_End.x, 2 );
334}
335
336
337BOOST_AUTO_TEST_CASE( DegenerateSourcesReportWhyTheyWereRefused )
338{
339 PCB_SHAPE zeroLength = makeLine( { 1000, 1000 }, { 1000, 1000 } );
340 PCB_SHAPE boundary = makeLine( { 1000, 0 }, { 1000, 2000 } );
341
342 BOOST_CHECK( GRAPHIC_TRIM_PLANNER::Plan( zeroLength, { 1000, 1000 }, { &boundary } ).m_Refusal
344}
345
346
347// Two shapes meeting on the source cross it once. Not two cuts.
348BOOST_AUTO_TEST_CASE( BoundariesCrossingAtOnePointYieldOneCut )
349{
350 PCB_SHAPE source = makeLine( { 0, 0 }, { 100000, 0 } );
351 PCB_SHAPE first = makeLine( { 50000, -10000 }, { 50000, 10000 } );
352 PCB_SHAPE second = makeLine( { 40000, -10000 }, { 60000, 10000 } );
353
354 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90000, 0 }, { &first, &second } );
355
357 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
358 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 50000, 0 ) );
359 BOOST_CHECK_EQUAL( result.m_Boundaries.size(), 1 );
360}
361
362
363static PCB_SHAPE makeCircle( const VECTOR2I& aCenter, int aRadius, PCB_LAYER_ID aLayer = Dwgs_User )
364{
365 PCB_SHAPE circle( nullptr, SHAPE_T::CIRCLE );
366 circle.SetCenter( aCenter );
367 circle.SetEnd( aCenter + VECTOR2I( aRadius, 0 ) );
368 circle.SetLayer( aLayer );
369 return circle;
370}
371
372
373static PCB_SHAPE makeRectangle( const VECTOR2I& aCorner, const VECTOR2I& aOpposite,
374 PCB_LAYER_ID aLayer = Dwgs_User )
375{
376 PCB_SHAPE rectangle( nullptr, SHAPE_T::RECTANGLE );
377 rectangle.SetStart( aCorner );
378 rectangle.SetEnd( aOpposite );
379 rectangle.SetLayer( aLayer );
380 return rectangle;
381}
382
383
384// Two cuts open a circle up. What is left is one arc, the long way round from the pointer.
385BOOST_AUTO_TEST_CASE( CircleTrimsIntoAnArc )
386{
387 PCB_SHAPE source = makeCircle( { 0, 0 }, 100000 );
388 PCB_SHAPE boundary = makeLine( { 0, -200000 }, { 0, 200000 } );
389
390 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 100000, 0 }, { &boundary } );
391
393 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 1 );
394 BOOST_CHECK( result.m_Geometry[0].m_Shape == SHAPE_T::ARC );
395
396 SHAPE_ARC kept( result.m_Geometry[0].m_Start, result.m_Geometry[0].m_Mid, result.m_Geometry[0].m_End, 0 );
397
398 BOOST_CHECK_CLOSE( kept.GetRadius(), 100000.0, 0.01 );
399
400 // The half the pointer was on has gone, so the middle of what is left is the other side.
401 BOOST_CHECK_LE( kept.GetArcMid().Distance( VECTOR2I( -100000, 0 ) ), 2 );
402}
403
404
405BOOST_AUTO_TEST_CASE( CircleNeedsTwoCutsToOpen )
406{
407 PCB_SHAPE source = makeCircle( { 0, 0 }, 100000 );
408
409 // Ends on the circle, so it meets it once and no more.
410 PCB_SHAPE boundary = makeLine( { 100000, 0 }, { 300000, 0 } );
411
412 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 0, 100000 }, { &boundary } );
413
414 BOOST_CHECK( !result );
415 BOOST_CHECK( result.m_Refusal == GRAPHIC_EDIT_REFUSAL::NO_INTERSECTION );
416}
417
418
419// The side under the pointer is the one that gets cut. The other three come through as lines.
420BOOST_AUTO_TEST_CASE( RectangleTrimsIntoLines )
421{
422 PCB_SHAPE source = makeRectangle( { 0, 0 }, { 100000, 100000 } );
423 PCB_SHAPE boundary = makeLine( { 50000, -10000 }, { 50000, 10000 } );
424
425 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90000, 0 }, { &boundary } );
426
428 BOOST_REQUIRE_EQUAL( result.m_Geometry.size(), 4 );
429
430 for( const GRAPHIC_EDIT_GEOMETRY& piece : result.m_Geometry )
431 BOOST_CHECK( piece.m_Shape == SHAPE_T::SEGMENT );
432
433 // The remnant of the trimmed side runs from the far corner to the cut.
434 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_Start, VECTOR2I( 0, 0 ) );
435 BOOST_CHECK_EQUAL( result.m_Geometry[0].m_End, VECTOR2I( 50000, 0 ) );
436
437 BOOST_REQUIRE_EQUAL( result.m_Preview.size(), 1 );
438 BOOST_CHECK_EQUAL( result.m_Preview[0].m_Start, VECTOR2I( 50000, 0 ) );
439 BOOST_CHECK_EQUAL( result.m_Preview[0].m_End, VECTOR2I( 100000, 0 ) );
440}
441
442
443// Nothing crosses the side the pointer is on, so the rectangle is left alone.
444// GetRectCorners() answers for a flat rectangle too, with two zero-length sides and two that
445// run opposite ways. Trimming one put zero-length segments on the board.
446BOOST_AUTO_TEST_CASE( FlatRectangleIsRefused )
447{
448 PCB_SHAPE source = makeRectangle( { 0, 0 }, { 0, 100000 } );
449 PCB_SHAPE boundary = makeLine( { -10000, 50000 }, { 10000, 50000 } );
450
451 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 0, 40000 }, { &boundary } );
452
453 BOOST_CHECK( !result );
454 BOOST_CHECK( result.m_Refusal == GRAPHIC_EDIT_REFUSAL::DEGENERATE );
455}
456
457
458BOOST_AUTO_TEST_CASE( RectangleSideWithNoCrossingIsRefused )
459{
460 PCB_SHAPE source = makeRectangle( { 0, 0 }, { 100000, 100000 } );
461 PCB_SHAPE boundary = makeLine( { 50000, -10000 }, { 50000, 10000 } );
462
463 GRAPHIC_EDIT_RESULT result = GRAPHIC_TRIM_PLANNER::Plan( source, { 90000, 100000 }, { &boundary } );
464
465 BOOST_CHECK( !result );
466 BOOST_CHECK( result.m_Refusal == GRAPHIC_EDIT_REFUSAL::NO_INTERSECTION );
467}
468
469
470BOOST_AUTO_TEST_CASE( CirclesAndRectanglesAreBoundariesToo )
471{
472 PCB_SHAPE source = makeLine( { -200000, 0 }, { 200000, 0 } );
473 PCB_SHAPE circle = makeCircle( { 0, 0 }, 100000 );
474
475 GRAPHIC_EDIT_RESULT byCircle = GRAPHIC_TRIM_PLANNER::Plan( source, { 150000, 0 }, { &circle } );
476
477 BOOST_REQUIRE( byCircle );
478 BOOST_REQUIRE_EQUAL( byCircle.m_Geometry.size(), 1 );
479 BOOST_CHECK_LE( byCircle.m_Geometry[0].m_End.Distance( VECTOR2I( 100000, 0 ) ), 2 );
480
481 PCB_SHAPE rectangle = makeRectangle( { -50000, -50000 }, { 50000, 50000 } );
482
483 GRAPHIC_EDIT_RESULT byRectangle = GRAPHIC_TRIM_PLANNER::Plan( source, { 150000, 0 }, { &rectangle } );
484
485 BOOST_REQUIRE( byRectangle );
486 BOOST_REQUIRE_EQUAL( byRectangle.m_Geometry.size(), 1 );
487 BOOST_CHECK_LE( byRectangle.m_Geometry[0].m_End.Distance( VECTOR2I( 50000, 0 ) ), 2 );
488}
489
490
void SetLocked(bool aLocked) override
Definition board_item.h:417
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
VECTOR2I GetArcMid() const
void SetEnd(const VECTOR2I &aEnd) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
bool IsClockwise() const
Definition shape_arc.h:319
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
double GetRadius() const
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Dwgs_User
Definition layer_ids.h:103
@ Cmts_User
Definition layer_ids.h:104
GRAPHIC_EDIT_RESULT Plan(const BOARD_ITEM &aSource, const VECTOR2I &aPointer, const std::vector< const BOARD_ITEM * > &aBoundaries)
Plan removal of the part of aSource under aPointer.
SHAPE_T m_Shape
A result need not be the same kind as the source.
std::vector< GRAPHIC_EDIT_GEOMETRY > m_Geometry
What the source becomes. Empty means the whole source goes.
GRAPHIC_EDIT_REFUSAL m_Refusal
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static PCB_SHAPE makeLine(const VECTOR2I &aStart, const VECTOR2I &aEnd, PCB_LAYER_ID aLayer=Dwgs_User)
static PCB_SHAPE makeArc(const VECTOR2I &aCenter, const VECTOR2I &aStart, double aAngle, PCB_LAYER_ID aLayer=Dwgs_User)
static PCB_SHAPE makeCircle(const VECTOR2I &aCenter, int aRadius, PCB_LAYER_ID aLayer=Dwgs_User)
static PCB_SHAPE makeRectangle(const VECTOR2I &aCorner, const VECTOR2I &aOpposite, PCB_LAYER_ID aLayer=Dwgs_User)
static PCB_SHAPE makeLine(const VECTOR2I &aStart, const VECTOR2I &aEnd, PCB_LAYER_ID aLayer=Dwgs_User)
BOOST_AUTO_TEST_CASE(OneBoundaryRemovesThePointerSide)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683