KiCad PCB EDA Suite
Loading...
Searching...
No Matches
board_downgrade.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
21
22#include <downgrade_scan.h>
25
26#include <algorithm>
27#include <cctype>
28#include <functional>
29#include <memory>
30#include <set>
31#include <string>
32#include <vector>
33
34#include <wx/ffile.h>
35#include <wx/filefn.h>
36
37#include <base_units.h>
38#include <board.h>
40#include <footprint.h>
42#include <pad.h>
43#include <pcb_barcode.h>
44#include <pcb_shape.h>
45#include <pcb_table.h>
46#include <pcb_tablecell.h>
47#include <pcb_textbox.h>
48#include <pcb_track.h>
49#include <pcb_point.h>
50#include <pcb_reference_image.h>
51#include <zone.h>
52#include <zone_settings.h>
53#include <netinfo.h>
54#include <eda_group.h>
55#include <eda_shape.h>
56#include <pcb_group.h>
57#include <pcb_dimension.h>
58#include <pcb_generator.h>
59#include <pcb_text.h>
60#include <project.h>
61#include <common.h>
64#include <layer_ids.h>
65#include <lset.h>
72
73
74// Board format date each feature landed. A rule fires only when the target predates this.
75static constexpr int VER_USER_LAYERS = 20241229;
76static constexpr int VER_TEXTBOX_KNOCKOUT = 20250210;
77static constexpr int VER_SHAPE_HATCH = 20250222;
78static constexpr int VER_VIA_PROTECTION = 20250228;
79static constexpr int VER_ZONE_DEFAULTS = 20250302;
80static constexpr int VER_JUMPER_PADS = 20250324;
81static constexpr int VER_VIA_SPLIT = 20250926;
82static constexpr int VER_SKIP_VIA = 20250801;
83static constexpr int VER_PRESSFIT = 20250811;
84static constexpr int VER_POST_MACHINING = 20251101;
85static constexpr int VER_PCB_POINTS = 20250901;
86static constexpr int VER_ROUNDED_RECT = 20250829;
87static constexpr int VER_FP_UNITS = 20250909;
88static constexpr int VER_BARCODE = 20250914;
89static constexpr int VER_PAD_DIE_DELAY = 20250401;
90static constexpr int VER_TIME_DOMAIN_TUNING = 20250401;
91// Group placement arrived without a format bump while 20250401 was current.
92static constexpr int VER_GROUP_PLACEMENT = 20250401;
93static constexpr int VER_CELL_KNOCKOUT = 20260603;
94static constexpr int VER_BACKDRILL = 20251101;
95static constexpr int VER_EXTRUDED_BODY = 20260410;
96static constexpr int VER_ELLIPSE = 20260508;
97static constexpr int VER_THIEVING = 20260513;
98static constexpr int VER_NET_CHAINS = 20260512;
99static constexpr int VER_PAD_SIM = 20260521;
100static constexpr int VER_DIELECTRIC = 20260511;
101static constexpr int VER_GROUP_LIB_ID = 20250513;
102static constexpr int VER_IMAGE_PPI = 20260623;
103static constexpr int VER_CONSTRAINTS = 20260624;
104static constexpr int VER_GRIDS = 20260728;
105static constexpr int VER_VIA_STITCH = 20260816;
106static constexpr int VER_PROPERTY_VARIABLES = 20260817;
107static constexpr int VER_LINE_ENDINGS = 20260818;
108static constexpr int VER_SIM_EXCLUSION = 20260828;
109static constexpr int VER_VIA_STACK = 20260830;
110static constexpr int VER_CUSTOM_PROPERTIES = 20260831;
111static constexpr int VER_DRILL_DRAWINGS = 20260901;
112
113// The board format this feature was built for. If SEXPR_BOARD_FILE_VERSION moves past this, a new
114// feature landed and the rules or denylist may be stale. Review pcb.keywords, then update this.
115// Bumps whose data lives in the project file, not the board file, need no rule here. That covers
116// 20250309 component class assignment rules and the tuning profile part of 20250401.
117static constexpr int BOARD_DOWNGRADE_COVERED = 20260901;
118
119
120static bool isEllipse( const BOARD_ITEM* aItem )
121{
122 if( aItem->Type() != PCB_SHAPE_T )
123 return false;
124
125 SHAPE_T shape = static_cast<const PCB_SHAPE*>( aItem )->GetShape();
126 return shape == SHAPE_T::ELLIPSE || shape == SHAPE_T::ELLIPSE_ARC;
127}
128
129
130static bool isHatchedShape( const BOARD_ITEM* aItem )
131{
132 return aItem->Type() == PCB_SHAPE_T && static_cast<const PCB_SHAPE*>( aItem )->IsHatchedFill();
133}
134
135
136// A hatched fill on copper is copper, so losing it changes the board electrically. Elsewhere it is
137// ink. The two cases are reported and transformed separately.
138static bool isHatchedCopperShape( const BOARD_ITEM* aItem )
139{
140 return isHatchedShape( aItem ) && IsCopperLayer( aItem->GetLayer() );
141}
142
143
144static bool isHatchedInkShape( const BOARD_ITEM* aItem )
145{
146 return isHatchedShape( aItem ) && !IsCopperLayer( aItem->GetLayer() );
147}
148
149
150// FOOTPRINT::Remove does not detach group membership the way BOARD::Remove does, so items
151// deleted from a footprint must leave their group first or the writer walks a dangling pointer.
152static void detachFromGroup( BOARD_ITEM* aItem )
153{
154 if( EDA_GROUP* group = aItem->GetParentGroup() )
155 group->RemoveItem( aItem );
156}
157
158
159static void ellipseToPolygon( PCB_SHAPE* aShape, int aMaxError )
160{
161 // A board polygon is always closed, so an elliptical arc becomes a filled polygon of
162 // its stroked ink instead of an open outline.
163 if( aShape->GetShape() == SHAPE_T::ELLIPSE_ARC )
164 {
165 aShape->SetWidth( std::max( aShape->GetWidth(), 2 * aMaxError ) );
166
167 SHAPE_POLY_SET poly;
168 aShape->EDA_SHAPE::TransformShapeToPolygon( poly, 0, aMaxError, ERROR_INSIDE, false );
169 poly.Simplify();
170
171 // The writers save one outline per shape, so holes must become cuts.
172 poly.Fracture();
173
174 aShape->SetShape( SHAPE_T::POLY );
175 aShape->SetPolyShape( poly );
177 aShape->SetWidth( 0 );
178 return;
179 }
180
181 SHAPE_ELLIPSE ellipse( aShape->GetEllipseCenter(), aShape->GetEllipseMajorRadius(), aShape->GetEllipseMinorRadius(),
182 aShape->GetEllipseRotation() );
183
184 SHAPE_LINE_CHAIN chain = ellipse.ConvertToPolyline( aMaxError );
185 chain.SetClosed( true );
186
187 SHAPE_POLY_SET poly;
188 poly.AddOutline( chain );
189
190 aShape->SetShape( SHAPE_T::POLY );
191 aShape->SetPolyShape( poly );
192}
193
194
195// Per-footprint rule scopes. The board apply functions delegate to these, and
196// DowngradeFootprintInPlace runs them from the same rule table, so the standalone
197// .kicad_mod path cannot drift from the board path.
198static int footprintMaxError( const FOOTPRINT* aFootprint )
199{
200 return aFootprint->GetBoard() ? aFootprint->GetBoard()->GetDesignSettings().m_MaxError : pcbIUScale.mmToIU( 0.005 );
201}
202
203
204// Defined below with the rounded-rectangle helpers. Ellipses can also live in a custom pad.
205static void forEachPadPrimitive( FOOTPRINT* aFootprint, const std::function<void( PCB_SHAPE* )>& aFunc );
206
207
208static void lowerEllipsesFp( FOOTPRINT* aFootprint )
209{
210 int maxError = footprintMaxError( aFootprint );
211
212 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
213 {
214 if( isEllipse( item ) )
215 ellipseToPolygon( static_cast<PCB_SHAPE*>( item ), maxError );
216 }
217
218 forEachPadPrimitive( aFootprint,
219 [&]( PCB_SHAPE* aPrimitive )
220 {
221 if( isEllipse( aPrimitive ) )
222 ellipseToPolygon( aPrimitive, maxError );
223 } );
224}
225
226
227static void setHatchedFillsFp( FOOTPRINT* aFootprint, const std::function<bool( const BOARD_ITEM* )>& aMatches,
228 FILL_T aFill )
229{
230 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
231 {
232 if( aMatches( item ) )
233 static_cast<PCB_SHAPE*>( item )->SetFillMode( aFill );
234 }
235}
236
237
238// The sexpr writer only saves outline 0 of a polygon shape, so a barcode's ink must be
239// split into one shape per outline or all but one module would be lost on save.
240static std::vector<PCB_SHAPE*> barcodeToShapes( PCB_BARCODE* aBarcode, BOARD_ITEM_CONTAINER* aParent )
241{
242 SHAPE_POLY_SET poly;
243 aBarcode->TransformShapeToPolygon( poly, aBarcode->GetLayer(), 0, 0, ERROR_INSIDE );
244 poly.Simplify();
245
246 // Each shape below carries a single outline, so holes must become cuts first or a
247 // finder ring would print as a solid square.
248 poly.Fracture();
249
250 std::vector<PCB_SHAPE*> shapes;
251
252 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
253 {
254 PCB_SHAPE* shape = new PCB_SHAPE( aParent, SHAPE_T::POLY );
255
256 SHAPE_POLY_SET single;
257 single.AddOutline( poly.Outline( ii ) );
258
259 shape->SetPolyShape( single );
261 shape->SetWidth( 0 );
262 shape->SetLayer( aBarcode->GetLayer() );
263 shape->SetLocked( aBarcode->IsLocked() );
264 shapes.push_back( shape );
265 }
266
267 return shapes;
268}
269
270
271static void lowerBarcodesFp( FOOTPRINT* aFootprint )
272{
273 std::vector<PCB_BARCODE*> barcodes;
274
275 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
276 {
277 if( item->Type() == PCB_BARCODE_T )
278 barcodes.push_back( static_cast<PCB_BARCODE*>( item ) );
279 }
280
281 for( PCB_BARCODE* barcode : barcodes )
282 {
283 EDA_GROUP* group = barcode->GetParentGroup();
284
285 for( PCB_SHAPE* shape : barcodeToShapes( barcode, aFootprint ) )
286 {
287 aFootprint->Add( shape );
288
289 if( group )
290 group->AddItem( shape );
291 }
292
293 detachFromGroup( barcode );
294 aFootprint->Remove( barcode );
295 delete barcode;
296 }
297}
298
299
300static void dropPcbPointsFp( FOOTPRINT* aFootprint )
301{
302 for( PCB_POINT* point : aFootprint->Points() )
303 {
304 detachFromGroup( point );
305 delete point;
306 }
307
308 aFootprint->Points().clear();
309}
310
311
312static void dropExtrudedBodiesFp( FOOTPRINT* aFootprint )
313{
314 aFootprint->ClearExtrudedBody();
315}
316
317
318static void dropPadSimTypesFp( FOOTPRINT* aFootprint )
319{
320 for( PAD* pad : aFootprint->Pads() )
321 pad->SetSimElectricalType( PAD_SIM_ELECTRICAL_TYPE::NONE );
322}
323
324
325static void dropUnitInfoFp( FOOTPRINT* aFootprint )
326{
327 aFootprint->SetUnitInfo( {} );
328}
329
330
331static void dropJumperPadsFp( FOOTPRINT* aFootprint )
332{
333 aFootprint->JumperPadGroups().Clear();
334 aFootprint->SetDuplicatePadNumbersAreJumpers( false );
335}
336
337
338static void dropPcbVariantsFp( FOOTPRINT* aFootprint )
339{
340 std::vector<wxString> names;
341
342 for( const auto& [name, variant] : aFootprint->GetVariants() )
343 names.push_back( name );
344
345 for( const wxString& name : names )
346 aFootprint->DeleteVariant( name );
347}
348
349
350static void dropPostMachiningFp( FOOTPRINT* aFootprint )
351{
352 for( PAD* pad : aFootprint->Pads() )
353 {
354 pad->Padstack().FrontPostMachining().mode.reset();
355 pad->Padstack().BackPostMachining().mode.reset();
356 }
357}
358
359
360static void dropPressFitPadsFp( FOOTPRINT* aFootprint )
361{
362 for( PAD* pad : aFootprint->Pads() )
363 {
364 if( pad->GetProperty() == PAD_PROP::PRESSFIT )
365 pad->SetProperty( PAD_PROP::NONE );
366 }
367}
368
369
370static void dropDieDelaysFp( FOOTPRINT* aFootprint )
371{
372 for( PAD* pad : aFootprint->Pads() )
373 pad->SetPadToDieDelay( 0 );
374}
375
376
377static void dropGroupDesignBlocksFp( FOOTPRINT* aFootprint )
378{
379 for( PCB_GROUP* group : aFootprint->Groups() )
380 group->SetDesignBlockLibId( LIB_ID() );
381}
382
383
384static int countNetChains( const BOARD* aBoard )
385{
386 int count = 0;
387
388 // Match what the writer emits: a named chain, or a net with terminal pads but no chain name.
389 for( NETINFO_ITEM* net : aBoard->GetNetInfo() )
390 {
391 if( !net->GetNetChain().IsEmpty() || net->GetTerminalPad( 0 ) || net->GetTerminalPad( 1 ) )
392 count++;
393 }
394
395 return count;
396}
397
398
399static void dropNetChains( BOARD* aBoard )
400{
401 for( NETINFO_ITEM* net : aBoard->GetNetInfo() )
402 {
403 net->SetNetChain( wxEmptyString );
404 net->ClearTerminalPad( 0 ); // terminal_pad is part of the net-chain feature
405 net->ClearTerminalPad( 1 );
406 }
407
408 std::vector<wxString> chains;
409
410 for( const auto& [chain, color] : aBoard->GetNetChainColors() )
411 chains.push_back( chain );
412
413 for( const wxString& chain : chains )
415}
416
417
418static int countEllipses( const BOARD* aBoard )
419{
420 int count = 0;
421
422 for( BOARD_ITEM* item : aBoard->Drawings() )
423 {
424 if( isEllipse( item ) )
425 count++;
426 }
427
428 for( FOOTPRINT* fp : aBoard->Footprints() )
429 {
430 for( BOARD_ITEM* item : fp->GraphicalItems() )
431 {
432 if( isEllipse( item ) )
433 count++;
434 }
435
437 [&]( PCB_SHAPE* aPrimitive )
438 {
439 if( isEllipse( aPrimitive ) )
440 count++;
441 } );
442 }
443
444 return count;
445}
446
447
448static void lowerEllipses( BOARD* aBoard )
449{
450 int maxError = aBoard->GetDesignSettings().m_MaxError;
451
452 for( BOARD_ITEM* item : aBoard->Drawings() )
453 {
454 if( isEllipse( item ) )
455 ellipseToPolygon( static_cast<PCB_SHAPE*>( item ), maxError );
456 }
457
458 for( FOOTPRINT* fp : aBoard->Footprints() )
459 lowerEllipsesFp( fp );
460}
461
462
463static int countExtrudedBodies( const BOARD* aBoard )
464{
465 int count = 0;
466
467 for( FOOTPRINT* fp : aBoard->Footprints() )
468 {
469 if( fp->HasExtrudedBody() )
470 count++;
471 }
472
473 return count;
474}
475
476
477static void dropExtrudedBodies( BOARD* aBoard )
478{
479 for( FOOTPRINT* fp : aBoard->Footprints() )
481}
482
483
484static int countThievingZones( const BOARD* aBoard )
485{
486 int count = 0;
487
488 for( ZONE* zone : aBoard->Zones() )
489 {
490 if( zone->IsCopperThieving() )
491 count++;
492 }
493
494 for( FOOTPRINT* fp : aBoard->Footprints() )
495 {
496 for( ZONE* zone : fp->Zones() )
497 {
498 if( zone->IsCopperThieving() )
499 count++;
500 }
501 }
502
503 return count;
504}
505
506
507static int countPadSimTypes( const BOARD* aBoard )
508{
509 int count = 0;
510
511 for( PAD* pad : aBoard->GetPads() )
512 {
513 if( pad->GetSimElectricalType() != PAD_SIM_ELECTRICAL_TYPE::NONE )
514 count++;
515 }
516
517 return count;
518}
519
520
521static void dropPadSimTypes( BOARD* aBoard )
522{
523 for( FOOTPRINT* fp : aBoard->Footprints() )
524 dropPadSimTypesFp( fp );
525}
526
527
528static int countUnitInfo( const BOARD* aBoard )
529{
530 int count = 0;
531
532 for( FOOTPRINT* fp : aBoard->Footprints() )
533 {
534 // Every footprint carries a default single unit. Only more than one unit holds real
535 // gate-swap information, so only that is a reportable loss.
536 if( fp->GetUnitInfo().size() > 1 )
537 count++;
538 }
539
540 return count;
541}
542
543
544static void dropUnitInfo( BOARD* aBoard )
545{
546 for( FOOTPRINT* fp : aBoard->Footprints() )
547 dropUnitInfoFp( fp );
548}
549
550
551static int countBarcodes( const BOARD* aBoard )
552{
553 int count = 0;
554
555 for( BOARD_ITEM* item : aBoard->Drawings() )
556 {
557 if( item->Type() == PCB_BARCODE_T )
558 count++;
559 }
560
561 for( FOOTPRINT* fp : aBoard->Footprints() )
562 {
563 for( BOARD_ITEM* item : fp->GraphicalItems() )
564 {
565 if( item->Type() == PCB_BARCODE_T )
566 count++;
567 }
568 }
569
570 return count;
571}
572
573
574static void lowerBarcodes( BOARD* aBoard )
575{
576 std::vector<PCB_BARCODE*> barcodes;
577
578 for( BOARD_ITEM* item : aBoard->Drawings() )
579 {
580 if( item->Type() == PCB_BARCODE_T )
581 barcodes.push_back( static_cast<PCB_BARCODE*>( item ) );
582 }
583
584 for( PCB_BARCODE* barcode : barcodes )
585 {
586 EDA_GROUP* group = barcode->GetParentGroup();
587
588 for( PCB_SHAPE* shape : barcodeToShapes( barcode, aBoard ) )
589 {
590 aBoard->Add( shape );
591
592 if( group )
593 group->AddItem( shape );
594 }
595
596 aBoard->Remove( barcode );
597 delete barcode;
598 }
599
600 for( FOOTPRINT* fp : aBoard->Footprints() )
601 lowerBarcodesFp( fp );
602}
603
604
605static int countPcbVariants( const BOARD* aBoard )
606{
607 int count = aBoard->GetVariantNames().empty() ? 0 : 1;
608
609 for( FOOTPRINT* fp : aBoard->Footprints() )
610 {
611 if( !fp->GetVariants().empty() )
612 count++;
613 }
614
615 return count;
616}
617
618
619static void dropPcbVariants( BOARD* aBoard )
620{
621 aBoard->SetVariantNames( {} );
622 aBoard->SetCurrentVariant( wxEmptyString );
623
624 for( FOOTPRINT* fp : aBoard->Footprints() )
625 dropPcbVariantsFp( fp );
626}
627
628
629static int countJumperPads( const BOARD* aBoard )
630{
631 int count = 0;
632
633 for( FOOTPRINT* fp : aBoard->Footprints() )
634 {
635 if( !fp->JumperPadGroups().IsEmpty() || fp->GetDuplicatePadNumbersAreJumpers() )
636 count++;
637 }
638
639 return count;
640}
641
642
643static void dropJumperPads( BOARD* aBoard )
644{
645 for( FOOTPRINT* fp : aBoard->Footprints() )
646 dropJumperPadsFp( fp );
647}
648
649
650static bool hasBackdrill( const PADSTACK& aPadstack )
651{
652 return aPadstack.SecondaryDrill().size.x > 0 || aPadstack.TertiaryDrill().size.x > 0;
653}
654
655
656static int countBackdrills( const BOARD* aBoard )
657{
658 int count = 0;
659
660 for( PAD* pad : aBoard->GetPads() )
661 {
662 if( hasBackdrill( pad->Padstack() ) )
663 count++;
664 }
665
666 for( PCB_TRACK* track : aBoard->Tracks() )
667 {
668 if( track->Type() == PCB_VIA_T && hasBackdrill( static_cast<PCB_VIA*>( track )->Padstack() ) )
669 count++;
670 }
671
672 return count;
673}
674
675
676static bool hasPostMachining( const PADSTACK& aPadstack )
677{
678 auto set = []( const PADSTACK::POST_MACHINING_PROPS& aProps )
679 {
680 return aProps.mode.has_value() && aProps.mode != PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED;
681 };
682
683 return set( aPadstack.FrontPostMachining() ) || set( aPadstack.BackPostMachining() );
684}
685
686
687static int countPostMachining( const BOARD* aBoard )
688{
689 int count = 0;
690
691 for( PAD* pad : aBoard->GetPads() )
692 {
693 if( hasPostMachining( pad->Padstack() ) )
694 count++;
695 }
696
697 for( PCB_TRACK* track : aBoard->Tracks() )
698 {
699 if( track->Type() == PCB_VIA_T && hasPostMachining( static_cast<PCB_VIA*>( track )->Padstack() ) )
700 count++;
701 }
702
703 return count;
704}
705
706
707static void dropPostMachining( BOARD* aBoard )
708{
709 auto clear = []( PADSTACK& aPadstack )
710 {
711 aPadstack.FrontPostMachining().mode.reset();
712 aPadstack.BackPostMachining().mode.reset();
713 };
714
715 for( FOOTPRINT* fp : aBoard->Footprints() )
717
718 for( PCB_TRACK* track : aBoard->Tracks() )
719 {
720 if( track->Type() == PCB_VIA_T )
721 clear( static_cast<PCB_VIA*>( track )->Padstack() );
722 }
723}
724
725
726static int countPressFitPads( const BOARD* aBoard )
727{
728 int count = 0;
729
730 for( PAD* pad : aBoard->GetPads() )
731 {
732 if( pad->GetProperty() == PAD_PROP::PRESSFIT )
733 count++;
734 }
735
736 return count;
737}
738
739
740static void dropPressFitPads( BOARD* aBoard )
741{
742 for( FOOTPRINT* fp : aBoard->Footprints() )
743 dropPressFitPadsFp( fp );
744}
745
746
747static int countBuriedVias( const BOARD* aBoard )
748{
749 int count = 0;
750
751 for( PCB_TRACK* track : aBoard->Tracks() )
752 {
753 if( track->Type() == PCB_VIA_T && static_cast<PCB_VIA*>( track )->GetViaType() == VIATYPE::BURIED )
754 count++;
755 }
756
757 return count;
758}
759
760
761// The old format had one blind type covering both, so a buried via becomes blind.
762static void lowerBuriedVias( BOARD* aBoard )
763{
764 for( PCB_TRACK* track : aBoard->Tracks() )
765 {
766 if( track->Type() == PCB_VIA_T )
767 {
768 PCB_VIA* via = static_cast<PCB_VIA*>( track );
769
770 if( via->GetViaType() == VIATYPE::BURIED )
771 via->SetViaType( VIATYPE::BLIND );
772 }
773 }
774}
775
776
777static int countZoneDefaults( const BOARD* aBoard )
778{
779 return aBoard->GetDesignSettings().m_ZoneLayerProperties.empty() ? 0 : 1;
780}
781
782
783static void dropZoneDefaults( BOARD* aBoard )
784{
786}
787
788
789static int countSkipVias( const BOARD* aBoard )
790{
791 int count = 0;
792
793 for( PCB_TRACK* track : aBoard->Tracks() )
794 {
795 if( track->Type() == PCB_VIA_T
796 && static_cast<PCB_VIA*>( track )->Padstack().UnconnectedLayerMode()
798 {
799 count++;
800 }
801 }
802
803 return count;
804}
805
806
807// Skip vias keep copper only on the start and end layers. The nearest older mode keeps the outer
808// rings and drops the inner ones, so use that.
809static void lowerSkipVias( BOARD* aBoard )
810{
811 for( PCB_TRACK* track : aBoard->Tracks() )
812 {
813 if( track->Type() != PCB_VIA_T )
814 continue;
815
816 PADSTACK& padstack = static_cast<PCB_VIA*>( track )->Padstack();
817
820 }
821}
822
823
824static int countDieDelays( const BOARD* aBoard )
825{
826 int count = 0;
827
828 for( PAD* pad : aBoard->GetPads() )
829 {
830 if( pad->GetPadToDieDelay() != 0 )
831 count++;
832 }
833
834 return count;
835}
836
837
838static void dropDieDelays( BOARD* aBoard )
839{
840 for( FOOTPRINT* fp : aBoard->Footprints() )
841 dropDieDelaysFp( fp );
842}
843
844
845static int countHatchedFills( const BOARD* aBoard, const std::function<bool( const BOARD_ITEM* )>& aMatches )
846{
847 int count = 0;
848
849 for( BOARD_ITEM* item : aBoard->Drawings() )
850 {
851 if( aMatches( item ) )
852 count++;
853 }
854
855 for( FOOTPRINT* fp : aBoard->Footprints() )
856 {
857 for( BOARD_ITEM* item : fp->GraphicalItems() )
858 {
859 if( aMatches( item ) )
860 count++;
861 }
862 }
863
864 return count;
865}
866
867
868static void setHatchedFills( BOARD* aBoard, const std::function<bool( const BOARD_ITEM* )>& aMatches, FILL_T aFill )
869{
870 for( BOARD_ITEM* item : aBoard->Drawings() )
871 {
872 if( aMatches( item ) )
873 static_cast<PCB_SHAPE*>( item )->SetFillMode( aFill );
874 }
875
876 for( FOOTPRINT* fp : aBoard->Footprints() )
877 setHatchedFillsFp( fp, aMatches, aFill );
878}
879
880
881static int countPcbPoints( const BOARD* aBoard )
882{
883 int count = static_cast<int>( aBoard->Points().size() );
884
885 for( FOOTPRINT* fp : aBoard->Footprints() )
886 count += static_cast<int>( fp->Points().size() );
887
888 return count;
889}
890
891
892static void dropPcbPoints( BOARD* aBoard )
893{
894 std::vector<BOARD_ITEM*> points( aBoard->Points().begin(), aBoard->Points().end() );
895
896 for( BOARD_ITEM* item : points )
897 {
898 aBoard->Remove( item );
899 delete item;
900 }
901
902 for( FOOTPRINT* fp : aBoard->Footprints() )
903 dropPcbPointsFp( fp );
904}
905
906
907static int countHighUserLayers( const BOARD* aBoard )
908{
909 const LSET& enabled = aBoard->GetEnabledLayers();
910 int count = 0;
911
912 // User_10 and up only exist since 20241229. Odd ids in this range are the user layers.
913 for( int layer = User_10; layer <= User_45; layer += 2 )
914 {
915 if( enabled.Contains( PCB_LAYER_ID( layer ) ) )
916 count++;
917 }
918
919 return count;
920}
921
922
923static bool hasViaProtection( const PCB_VIA* aVia )
924{
925 const PADSTACK& padstack = aVia->Padstack();
926
927 return padstack.Drill().is_filled.has_value() || padstack.Drill().is_capped.has_value()
928 || padstack.FrontOuterLayers().has_covering.has_value()
929 || padstack.BackOuterLayers().has_covering.has_value()
930 || padstack.FrontOuterLayers().has_plugging.has_value()
931 || padstack.BackOuterLayers().has_plugging.has_value();
932}
933
934
935static int countViaProtection( const BOARD* aBoard )
936{
937 int count = 0;
938
939 for( PCB_TRACK* track : aBoard->Tracks() )
940 {
941 if( track->Type() == PCB_VIA_T && hasViaProtection( static_cast<PCB_VIA*>( track ) ) )
942 count++;
943 }
944
945 const BOARD_DESIGN_SETTINGS& bds = aBoard->GetDesignSettings();
946
948 || bds.m_FillVias )
949 {
950 count++;
951 }
952
953 return count;
954}
955
956
957static void dropViaProtection( BOARD* aBoard )
958{
959 for( PCB_TRACK* track : aBoard->Tracks() )
960 {
961 if( track->Type() != PCB_VIA_T )
962 continue;
963
964 PADSTACK& padstack = static_cast<PCB_VIA*>( track )->Padstack();
965
966 padstack.Drill().is_filled.reset();
967 padstack.Drill().is_capped.reset();
968 padstack.FrontOuterLayers().has_covering.reset();
969 padstack.BackOuterLayers().has_covering.reset();
970 padstack.FrontOuterLayers().has_plugging.reset();
971 padstack.BackOuterLayers().has_plugging.reset();
972 }
973
975
976 bds.m_CoverViasFront = false;
977 bds.m_CoverViasBack = false;
978 bds.m_PlugViasFront = false;
979 bds.m_PlugViasBack = false;
980 bds.m_CapVias = false;
981 bds.m_FillVias = false;
982}
983
984
985static int countDielectricModels( const BOARD* aBoard )
986{
987 const BOARD_STACKUP& stackup = aBoard->GetDesignSettings().GetStackupDescriptor();
988
989 int count = 0;
990
991 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
992 {
993 if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
994 continue;
995
996 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
997 {
998 if( item->GetDielectricModel( idx ) != DIELECTRIC_MODEL::CONSTANT )
999 count++;
1000 }
1001 }
1002
1003 return count;
1004}
1005
1006
1007static void dropDielectricModels( BOARD* aBoard )
1008{
1010
1011 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
1012 {
1013 if( item->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1014 continue;
1015
1016 for( int idx = 0; idx < item->GetSublayersCount(); idx++ )
1017 item->SetDielectricModel( DIELECTRIC_MODEL::CONSTANT, idx );
1018 }
1019}
1020
1021
1022static int countGroupDesignBlocks( const BOARD* aBoard )
1023{
1024 int count = 0;
1025
1026 for( PCB_GROUP* group : aBoard->Groups() )
1027 {
1028 if( group->HasDesignBlockLink() )
1029 count++;
1030 }
1031
1032 for( FOOTPRINT* fp : aBoard->Footprints() )
1033 {
1034 for( PCB_GROUP* group : fp->Groups() )
1035 {
1036 if( group->HasDesignBlockLink() )
1037 count++;
1038 }
1039 }
1040
1041 return count;
1042}
1043
1044
1045static void dropGroupDesignBlocks( BOARD* aBoard )
1046{
1047 for( PCB_GROUP* group : aBoard->Groups() )
1048 group->SetDesignBlockLibId( LIB_ID() );
1049
1050 for( FOOTPRINT* fp : aBoard->Footprints() )
1052}
1053
1054
1055static int countScaledFootprints( const BOARD* aBoard )
1056{
1057 int count = 0;
1058
1059 for( FOOTPRINT* fp : aBoard->Footprints() )
1060 {
1061 if( fp->GetTransform().GetScaleX() != 1.0 || fp->GetTransform().GetScaleY() != 1.0 )
1062 count++;
1063 }
1064
1065 return count;
1066}
1067
1068
1069static bool isRoundedRect( const BOARD_ITEM* aItem )
1070{
1071 if( aItem->Type() != PCB_SHAPE_T )
1072 return false;
1073
1074 const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
1075 return shape->GetShape() == SHAPE_T::RECTANGLE && shape->GetCornerRadius() > 0;
1076}
1077
1078
1079static void roundedRectToPolygon( PCB_SHAPE* aShape, int aMaxError )
1080{
1081 int radius = aShape->GetCornerRadius();
1082
1083 SHAPE_POLY_SET poly;
1084 poly.NewOutline();
1085
1086 for( const VECTOR2I& corner : aShape->GetRectCorners() )
1087 poly.Append( corner );
1088
1089 poly = poly.Fillet( radius, aMaxError );
1090
1091 aShape->SetCornerRadius( 0 );
1092 aShape->SetShape( SHAPE_T::POLY );
1093 aShape->SetPolyShape( poly );
1094}
1095
1096
1097// Rounded rectangles can also live in custom pad primitives.
1098static void forEachPadPrimitive( FOOTPRINT* aFootprint, const std::function<void( PCB_SHAPE* )>& aFunc )
1099{
1100 for( PAD* pad : aFootprint->Pads() )
1101 {
1102 pad->Padstack().ForEachUniqueLayer(
1103 [&]( PCB_LAYER_ID aLayer )
1104 {
1105 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
1106 aFunc( primitive.get() );
1107 } );
1108 }
1109}
1110
1111
1112static int countRoundedRectsFp( const FOOTPRINT* aFootprint )
1113{
1114 int count = 0;
1115
1116 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1117 {
1118 if( isRoundedRect( item ) )
1119 count++;
1120 }
1121
1122 forEachPadPrimitive( const_cast<FOOTPRINT*>( aFootprint ),
1123 [&]( PCB_SHAPE* aPrimitive )
1124 {
1125 if( isRoundedRect( aPrimitive ) )
1126 count++;
1127 } );
1128
1129 return count;
1130}
1131
1132
1133static void lowerRoundedRectsFp( FOOTPRINT* aFootprint )
1134{
1135 int maxError = footprintMaxError( aFootprint );
1136
1137 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1138 {
1139 if( isRoundedRect( item ) )
1140 roundedRectToPolygon( static_cast<PCB_SHAPE*>( item ), maxError );
1141 }
1142
1143 forEachPadPrimitive( aFootprint,
1144 [&]( PCB_SHAPE* aPrimitive )
1145 {
1146 if( isRoundedRect( aPrimitive ) )
1147 roundedRectToPolygon( aPrimitive, maxError );
1148 } );
1149}
1150
1151
1152static int countRoundedRects( const BOARD* aBoard )
1153{
1154 int count = 0;
1155
1156 for( BOARD_ITEM* item : aBoard->Drawings() )
1157 {
1158 if( isRoundedRect( item ) )
1159 count++;
1160 }
1161
1162 for( FOOTPRINT* fp : aBoard->Footprints() )
1163 count += countRoundedRectsFp( fp );
1164
1165 return count;
1166}
1167
1168
1169static void lowerRoundedRects( BOARD* aBoard )
1170{
1171 int maxError = aBoard->GetDesignSettings().m_MaxError;
1172
1173 for( BOARD_ITEM* item : aBoard->Drawings() )
1174 {
1175 if( isRoundedRect( item ) )
1176 roundedRectToPolygon( static_cast<PCB_SHAPE*>( item ), maxError );
1177 }
1178
1179 for( FOOTPRINT* fp : aBoard->Footprints() )
1180 lowerRoundedRectsFp( fp );
1181}
1182
1183
1184static int countKnockoutTextBoxesFp( const FOOTPRINT* aFootprint )
1185{
1186 int count = 0;
1187
1188 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1189 {
1190 if( item->Type() == PCB_TEXTBOX_T && item->IsKnockout() )
1191 count++;
1192 }
1193
1194 return count;
1195}
1196
1197
1198static void dropKnockoutTextBoxesFp( FOOTPRINT* aFootprint )
1199{
1200 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1201 {
1202 if( item->Type() == PCB_TEXTBOX_T && item->IsKnockout() )
1203 item->SetIsKnockout( false );
1204 }
1205}
1206
1207
1208static int countKnockoutTextBoxes( const BOARD* aBoard )
1209{
1210 int count = 0;
1211
1212 for( BOARD_ITEM* item : aBoard->Drawings() )
1213 {
1214 if( item->Type() == PCB_TEXTBOX_T && item->IsKnockout() )
1215 count++;
1216 }
1217
1218 for( FOOTPRINT* fp : aBoard->Footprints() )
1219 count += countKnockoutTextBoxesFp( fp );
1220
1221 return count;
1222}
1223
1224
1225static void dropKnockoutTextBoxes( BOARD* aBoard )
1226{
1227 for( BOARD_ITEM* item : aBoard->Drawings() )
1228 {
1229 if( item->Type() == PCB_TEXTBOX_T && item->IsKnockout() )
1230 item->SetIsKnockout( false );
1231 }
1232
1233 for( FOOTPRINT* fp : aBoard->Footprints() )
1235}
1236
1237
1238static int countKnockoutCellsIn( const BOARD_ITEM* aItem )
1239{
1240 if( aItem->Type() != PCB_TABLE_T )
1241 return 0;
1242
1243 int count = 0;
1244
1245 for( PCB_TABLECELL* cell : static_cast<const PCB_TABLE*>( aItem )->GetCells() )
1246 {
1247 if( cell->IsKnockout() )
1248 count++;
1249 }
1250
1251 return count;
1252}
1253
1254
1255static int countKnockoutTableCells( const BOARD* aBoard )
1256{
1257 int count = 0;
1258
1259 for( BOARD_ITEM* item : aBoard->Drawings() )
1260 count += countKnockoutCellsIn( item );
1261
1262 for( FOOTPRINT* fp : aBoard->Footprints() )
1263 {
1264 for( BOARD_ITEM* item : fp->GraphicalItems() )
1265 count += countKnockoutCellsIn( item );
1266 }
1267
1268 return count;
1269}
1270
1271
1272static void dropKnockoutCellsIn( BOARD_ITEM* aItem )
1273{
1274 if( aItem->Type() != PCB_TABLE_T )
1275 return;
1276
1277 for( PCB_TABLECELL* cell : static_cast<PCB_TABLE*>( aItem )->GetCells() )
1278 {
1279 if( cell->IsKnockout() )
1280 cell->SetIsKnockout( false );
1281 }
1282}
1283
1284
1285static void dropKnockoutTableCellsFp( FOOTPRINT* aFootprint )
1286{
1287 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1288 dropKnockoutCellsIn( item );
1289}
1290
1291
1292static void dropKnockoutTableCells( BOARD* aBoard )
1293{
1294 for( BOARD_ITEM* item : aBoard->Drawings() )
1295 dropKnockoutCellsIn( item );
1296
1297 for( FOOTPRINT* fp : aBoard->Footprints() )
1299}
1300
1301
1302static int countZoneLayerProperties( const BOARD* aBoard )
1303{
1304 int count = 0;
1305
1306 for( ZONE* zone : aBoard->Zones() )
1307 {
1308 if( !zone->LayerProperties().empty() )
1309 count++;
1310 }
1311
1312 for( FOOTPRINT* fp : aBoard->Footprints() )
1313 {
1314 for( ZONE* zone : fp->Zones() )
1315 {
1316 if( !zone->LayerProperties().empty() )
1317 count++;
1318 }
1319 }
1320
1321 return count;
1322}
1323
1324
1325static void dropZoneLayerPropertiesFp( FOOTPRINT* aFootprint )
1326{
1327 for( ZONE* zone : aFootprint->Zones() )
1328 zone->SetLayerProperties( {} );
1329}
1330
1331
1332static void dropZoneLayerProperties( BOARD* aBoard )
1333{
1334 for( ZONE* zone : aBoard->Zones() )
1335 zone->SetLayerProperties( {} );
1336
1337 for( FOOTPRINT* fp : aBoard->Footprints() )
1339}
1340
1341
1342static bool hasGroupPlacementLinkage( const ZONE* aZone )
1343{
1345 && ( aZone->GetPlacementAreaEnabled() || !aZone->GetPlacementAreaSource().IsEmpty() );
1346}
1347
1348
1349static int countGroupPlacementAreas( const BOARD* aBoard )
1350{
1351 int count = std::count_if( aBoard->Zones().begin(), aBoard->Zones().end(), hasGroupPlacementLinkage );
1352
1353 for( const FOOTPRINT* footprint : aBoard->Footprints() )
1354 count += std::count_if( footprint->Zones().begin(), footprint->Zones().end(), hasGroupPlacementLinkage );
1355
1356 return count;
1357}
1358
1359
1360static void dropGroupPlacementLinkage( ZONE* aZone )
1361{
1363 {
1364 aZone->SetPlacementAreaEnabled( false );
1365 aZone->SetPlacementAreaSource( wxEmptyString );
1367 }
1368}
1369
1370
1371static void dropGroupPlacementAreasFp( FOOTPRINT* aFootprint )
1372{
1373 for( ZONE* zone : aFootprint->Zones() )
1375}
1376
1377
1378static void dropGroupPlacementAreas( BOARD* aBoard )
1379{
1380 for( ZONE* zone : aBoard->Zones() )
1382
1383 for( FOOTPRINT* footprint : aBoard->Footprints() )
1384 dropGroupPlacementAreasFp( footprint );
1385}
1386
1387
1388static int countCustomFpStackups( const BOARD* aBoard )
1389{
1390 int count = 0;
1391
1392 for( FOOTPRINT* fp : aBoard->Footprints() )
1393 {
1394 if( fp->GetStackupMode() != FOOTPRINT_STACKUP::EXPAND_INNER_LAYERS )
1395 count++;
1396 }
1397
1398 return count;
1399}
1400
1401
1402// Omission is distinct from lowering: do not leave replacement polygons, plain knockout
1403// text, or electrically different vias behind when the user declines approximations.
1404static void omitGraphicsFp( FOOTPRINT* aFootprint, const std::function<bool( const BOARD_ITEM* )>& aMatches )
1405{
1406 std::vector<BOARD_ITEM*> remove;
1407
1408 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1409 {
1410 if( aMatches( item ) )
1411 remove.push_back( item );
1412 }
1413
1414 for( BOARD_ITEM* item : remove )
1415 {
1416 detachFromGroup( item );
1417 aFootprint->Remove( item );
1418 delete item;
1419 }
1420}
1421
1422
1423static void omitGraphics( BOARD* aBoard, const std::function<bool( const BOARD_ITEM* )>& aMatches )
1424{
1425 std::vector<BOARD_ITEM*> remove;
1426
1427 for( BOARD_ITEM* item : aBoard->Drawings() )
1428 {
1429 if( aMatches( item ) )
1430 remove.push_back( item );
1431 }
1432
1433 for( BOARD_ITEM* item : remove )
1434 {
1435 aBoard->Remove( item );
1436 delete item;
1437 }
1438
1439 for( FOOTPRINT* fp : aBoard->Footprints() )
1440 omitGraphicsFp( fp, aMatches );
1441}
1442
1443
1444static void omitPadPrimitives( FOOTPRINT* aFootprint, const std::function<bool( const BOARD_ITEM* )>& aMatches )
1445{
1446 for( PAD* pad : aFootprint->Pads() )
1447 {
1448 pad->Padstack().ForEachUniqueLayer(
1449 [&]( PCB_LAYER_ID aLayer )
1450 {
1451 auto& primitives = pad->Padstack().Primitives( aLayer );
1452 primitives.erase( std::remove_if( primitives.begin(), primitives.end(),
1453 [&]( const std::shared_ptr<PCB_SHAPE>& aPrimitive )
1454 {
1455 return aMatches( aPrimitive.get() );
1456 } ),
1457 primitives.end() );
1458 } );
1459 pad->SetDirty();
1460 }
1461}
1462
1463
1464static void omitEllipsesFp( FOOTPRINT* aFootprint )
1465{
1466 omitGraphicsFp( aFootprint, isEllipse );
1467 omitPadPrimitives( aFootprint, isEllipse );
1468}
1469static void omitEllipses( BOARD* aBoard )
1470{
1471 omitGraphics( aBoard, isEllipse );
1472
1473 for( FOOTPRINT* fp : aBoard->Footprints() )
1475}
1476
1477static bool isBarcode( const BOARD_ITEM* aItem )
1478{
1479 return aItem->Type() == PCB_BARCODE_T;
1480}
1481static void omitBarcodesFp( FOOTPRINT* aFootprint )
1482{
1483 omitGraphicsFp( aFootprint, isBarcode );
1484}
1485static void omitBarcodes( BOARD* aBoard )
1486{
1487 omitGraphics( aBoard, isBarcode );
1488}
1489
1490static bool isKnockoutTextBox( const BOARD_ITEM* aItem )
1491{
1492 return aItem->Type() == PCB_TEXTBOX_T && aItem->IsKnockout();
1493}
1494
1495static void omitKnockoutTextBoxesFp( FOOTPRINT* aFootprint )
1496{
1497 omitGraphicsFp( aFootprint, isKnockoutTextBox );
1498}
1499static void omitKnockoutTextBoxes( BOARD* aBoard )
1500{
1502}
1503
1504
1505static void omitRoundedRectsFp( FOOTPRINT* aFootprint )
1506{
1507 omitGraphicsFp( aFootprint, isRoundedRect );
1508 omitPadPrimitives( aFootprint, isRoundedRect );
1509}
1510
1511
1512static void omitRoundedRects( BOARD* aBoard )
1513{
1514 omitGraphics( aBoard, isRoundedRect );
1515
1516 for( FOOTPRINT* fp : aBoard->Footprints() )
1518}
1519
1520
1522{
1523 if( aItem->Type() != PCB_TABLE_T )
1524 return;
1525
1526 for( PCB_TABLECELL* cell : static_cast<PCB_TABLE*>( aItem )->GetCells() )
1527 {
1528 if( cell->IsKnockout() )
1529 {
1530 // Keep the supported grid structure, but not a plain-text approximation of the ink.
1531 cell->SetText( wxEmptyString );
1532 cell->SetIsKnockout( false );
1533 }
1534 }
1535}
1536
1537
1538static void omitKnockoutTableCellsFp( FOOTPRINT* aFootprint )
1539{
1540 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1542}
1543
1544
1545static void omitKnockoutTableCells( BOARD* aBoard )
1546{
1547 for( BOARD_ITEM* item : aBoard->Drawings() )
1549
1550 for( FOOTPRINT* fp : aBoard->Footprints() )
1552}
1553
1554
1555static int countConstraints( const BOARD* aBoard )
1556{
1557 int count = aBoard->Constraints().size();
1558
1559 for( const FOOTPRINT* fp : aBoard->Footprints() )
1560 count += fp->Constraints().size();
1561
1562 return count;
1563}
1564
1565
1566static void dropConstraintsFp( FOOTPRINT* aFootprint )
1567{
1568 const auto constraints = aFootprint->Constraints();
1569
1570 for( PCB_CONSTRAINT* constraint : constraints )
1571 {
1572 detachFromGroup( constraint );
1573 aFootprint->Remove( constraint );
1574 delete constraint;
1575 }
1576}
1577
1578
1579static void dropConstraints( BOARD* aBoard )
1580{
1581 const auto constraints = aBoard->Constraints();
1582
1583 for( PCB_CONSTRAINT* constraint : constraints )
1584 {
1585 aBoard->Remove( constraint );
1586 delete constraint;
1587 }
1588
1589 for( FOOTPRINT* fp : aBoard->Footprints() )
1590 dropConstraintsFp( fp );
1591}
1592
1593
1594static bool isGrid( const BOARD_ITEM* aItem )
1595{
1596 return aItem->Type() == PCB_GRID_ITEM_T;
1597}
1598
1599
1600static bool isDrillDrawing( const BOARD_ITEM* aItem )
1601{
1602 return aItem->Type() == PCB_DRILL_CHART_T || aItem->Type() == PCB_DRILL_MAP_T;
1603}
1604
1605
1606static int countDrawings( const BOARD* aBoard, const std::function<bool( const BOARD_ITEM* )>& aMatches )
1607{
1608 return std::count_if( aBoard->Drawings().begin(), aBoard->Drawings().end(), aMatches );
1609}
1610
1611
1612static int countGrids( const BOARD* aBoard )
1613{
1614 return countDrawings( aBoard, isGrid );
1615}
1616
1617
1618static void dropGrids( BOARD* aBoard )
1619{
1620 omitGraphics( aBoard, isGrid );
1621}
1622
1623
1624static int countDrillDrawings( const BOARD* aBoard )
1625{
1626 return countDrawings( aBoard, isDrillDrawing );
1627}
1628
1629
1630static void dropDrillDrawings( BOARD* aBoard )
1631{
1632 omitGraphics( aBoard, isDrillDrawing );
1633}
1634
1635
1636static int countDrillSymbolConfiguration( const BOARD* aBoard )
1637{
1638 return aBoard->GetDesignSettings().GetDrillSymbolProfile() == DRILL_SYMBOL_PROFILE() ? 0 : 1;
1639}
1640
1641
1646
1647
1648static int countGenerators( const BOARD* aBoard, const wxString& aType )
1649{
1650 return std::count_if( aBoard->Generators().begin(), aBoard->Generators().end(),
1651 [&]( const PCB_GENERATOR* aGenerator )
1652 {
1653 return aGenerator->GetGeneratorType() == aType;
1654 } );
1655}
1656
1657
1658static void dropGenerators( BOARD* aBoard, const std::function<bool( const PCB_GENERATOR* )>& aMatches )
1659{
1660 const auto generators = aBoard->Generators();
1661
1662 for( PCB_GENERATOR* generator : generators )
1663 {
1664 if( !aMatches( generator ) )
1665 continue;
1666
1667 EDA_GROUP* outer = generator->GetParentGroup();
1668 const auto members = generator->GetItems();
1669 generator->RemoveAll();
1670
1671 if( outer )
1672 {
1673 for( EDA_ITEM* member : members )
1674 outer->AddItem( member );
1675 }
1676
1677 aBoard->Remove( generator );
1678 delete generator;
1679 }
1680}
1681
1682
1683static void dropGenerators( BOARD* aBoard, const wxString& aType )
1684{
1685 dropGenerators( aBoard,
1686 [&]( const PCB_GENERATOR* aGenerator )
1687 {
1688 return aGenerator->GetGeneratorType() == aType;
1689 } );
1690}
1691
1692
1693static bool isTimeDomainTuning( const PCB_GENERATOR* aGenerator )
1694{
1695 const auto pattern = dynamic_cast<const PCB_TUNING_PATTERN*>( aGenerator );
1696 return pattern && !pattern->GetItems().empty() && pattern->GetSettings().m_isTimeDomain;
1697}
1698
1699
1700static int countTimeDomainTuning( const BOARD* aBoard )
1701{
1702 return std::count_if( aBoard->Generators().begin(), aBoard->Generators().end(), isTimeDomainTuning );
1703}
1704
1705
1706static void dropTimeDomainTuning( BOARD* aBoard )
1707{
1709}
1710
1711
1712static int countUnreviewedTuningProperties( const BOARD* aBoard )
1713{
1714 return std::count_if( aBoard->Generators().begin(), aBoard->Generators().end(),
1715 []( const PCB_GENERATOR* aGenerator )
1716 {
1717 if( aGenerator->GetGeneratorType() != wxT( "tuning_pattern" ) )
1718 return false;
1719
1720 return !dynamic_cast<const PCB_TUNING_PATTERN*>( aGenerator )
1721 || !KICAD_FORMAT::LEGACY::AreTuningPropertiesReviewed(
1722 aGenerator->GetProperties() );
1723 } );
1724}
1725
1726
1727static int countUnknownGenerators( const BOARD* aBoard )
1728{
1729 int count = 0;
1730
1731 for( PCB_GENERATOR* generator : aBoard->Generators() )
1732 {
1733 const wxString type = generator->GetGeneratorType();
1734
1735 if( type != wxT( "tuning_pattern" ) && type != wxT( "via_stitch" ) && type != wxT( "via_stack" ) )
1736 ++count;
1737
1738 for( EDA_ITEM* member : generator->GetItems() )
1739 {
1740 // Detached children would disappear when ownership metadata is removed.
1741 if( !dynamic_cast<BOARD_ITEM*>( member )
1742 || !aBoard->IsItemIndexedById( static_cast<BOARD_ITEM*>( member ) ) )
1743 {
1744 ++count;
1745 break;
1746 }
1747 }
1748 }
1749
1750 return count;
1751}
1752
1753
1754static bool isUnreviewedDrawing( const BOARD_ITEM* aItem )
1755{
1756 switch( aItem->Type() )
1757 {
1758 case PCB_SHAPE_T:
1759 case PCB_TEXT_T:
1760 case PCB_FIELD_T:
1761 case PCB_TEXTBOX_T:
1762 case PCB_TABLE_T:
1763 case PCB_DIM_ALIGNED_T:
1764 case PCB_DIM_CENTER_T:
1765 case PCB_DIM_RADIAL_T:
1767 case PCB_DIM_LEADER_T:
1769 case PCB_TARGET_T:
1770 case PCB_BARCODE_T:
1771 case PCB_GRID_ITEM_T:
1772 case PCB_DRILL_CHART_T:
1773 case PCB_DRILL_MAP_T: return false;
1774
1775 default: return true;
1776 }
1777}
1778
1779
1780static int countUnreviewedDrawings( const BOARD* aBoard )
1781{
1782 int count = countDrawings( aBoard, isUnreviewedDrawing );
1783
1784 for( FOOTPRINT* fp : aBoard->Footprints() )
1785 {
1786 count += std::count_if( fp->GraphicalItems().begin(), fp->GraphicalItems().end(), isUnreviewedDrawing );
1787 }
1788
1789 return count;
1790}
1791
1792
1793static void visitFootprintItems( const FOOTPRINT* aFootprint, const std::function<void( BOARD_ITEM* )>& aVisit )
1794{
1795 aVisit( const_cast<FOOTPRINT*>( aFootprint ) );
1796 aFootprint->RunOnChildren( aVisit, RECURSE_MODE::RECURSE );
1797}
1798
1799
1800static void visitBoardItems( const BOARD* aBoard, const std::function<void( BOARD_ITEM* )>& aVisit )
1801{
1802 aBoard->RunOnChildren( aVisit, RECURSE_MODE::RECURSE );
1803
1804 // Generators are omitted by BOARD::RunOnChildren.
1805 for( PCB_GENERATOR* generator : aBoard->Generators() )
1806 {
1807 aVisit( generator );
1808
1809 for( const auto& [name, item] : generator->GetTemplateItems() )
1810 {
1811 if( item )
1812 aVisit( const_cast<BOARD_ITEM*>( item ) );
1813 }
1814 }
1815}
1816
1817
1818static int countCustomProperties( const BOARD* aBoard )
1819{
1820 int count = 0;
1821 visitBoardItems( aBoard,
1822 [&]( BOARD_ITEM* aItem )
1823 {
1824 if( aItem && aItem->HasCustomProperties() )
1825 ++count;
1826 } );
1827 return count;
1828}
1829
1830
1831static void dropCustomPropertiesFp( FOOTPRINT* aFootprint )
1832{
1833 visitFootprintItems( aFootprint,
1834 []( BOARD_ITEM* aItem )
1835 {
1836 if( aItem )
1837 aItem->ClearCustomProperties();
1838 } );
1839}
1840
1841
1842static void dropCustomProperties( BOARD* aBoard )
1843{
1844 visitBoardItems( aBoard,
1845 []( BOARD_ITEM* aItem )
1846 {
1847 if( aItem )
1848 aItem->ClearCustomProperties();
1849 } );
1850}
1851
1852
1853static int countSimulationExclusions( const BOARD* aBoard )
1854{
1855 int count = 0;
1856
1857 for( const FOOTPRINT* fp : aBoard->Footprints() )
1858 {
1859 bool excluded = fp->IsExcludedFromSim();
1860
1861 for( const auto& [name, variant] : fp->GetVariants() )
1862 excluded = excluded || variant.GetExcludedFromSim();
1863
1864 if( excluded )
1865 ++count;
1866 }
1867
1868 return count;
1869}
1870
1871
1872static void dropSimulationExclusionsFp( FOOTPRINT* aFootprint )
1873{
1874 aFootprint->SetExcludedFromSim( false );
1875
1876 for( const auto& [name, variant] : aFootprint->GetVariants() )
1877 aFootprint->GetVariant( name )->SetExcludedFromSim( false );
1878}
1879
1880
1881static void dropSimulationExclusions( BOARD* aBoard )
1882{
1883 for( FOOTPRINT* fp : aBoard->Footprints() )
1885}
1886
1887
1888static bool hasLineEndings( const BOARD_ITEM* aItem )
1889{
1890 if( !aItem || aItem->Type() != PCB_SHAPE_T )
1891 return false;
1892
1893 const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
1896}
1897
1898
1899static bool unsafeLineEndings( const PCB_SHAPE* aShape )
1900{
1901 VECTOR2I start, end;
1902
1903 if( !aShape->GetLineEndingEndpoints( start, end ) )
1904 return false;
1905
1906 const auto unsafeStroke = []( const STROKE_PARAMS& aStroke )
1907 {
1908 return ( aStroke.GetLineStyle() != LINE_STYLE::DEFAULT && aStroke.GetLineStyle() != LINE_STYLE::SOLID )
1909 || aStroke.GetColor() != KIGFX::COLOR4D::UNSPECIFIED;
1910 };
1911
1912 // The native polygon helper does not preserve dash patterns or ending colors.
1913 return aShape->GetEffectiveWidth() <= 0
1914 || ( aShape->GetLineStyle() != LINE_STYLE::DEFAULT && aShape->GetLineStyle() != LINE_STYLE::SOLID )
1915 || unsafeStroke( aShape->GetStartEnding().GetStroke() )
1916 || unsafeStroke( aShape->GetEndEnding().GetStroke() );
1917}
1918
1919
1920static int countLineEndings( const BOARD* aBoard, bool aUnsafeOnly = false )
1921{
1922 int count = 0;
1923 visitBoardItems( aBoard,
1924 [&]( BOARD_ITEM* aItem )
1925 {
1926 if( hasLineEndings( aItem )
1927 && ( !aUnsafeOnly || unsafeLineEndings( static_cast<PCB_SHAPE*>( aItem ) ) ) )
1928 ++count;
1929 } );
1930 return count;
1931}
1932
1933
1934static void clearLineEndings( PCB_SHAPE* aShape )
1935{
1936 aShape->SetStartEnding( LINE_ENDING() );
1937 aShape->SetEndEnding( LINE_ENDING() );
1938}
1939
1940
1941static void lowerLineEndingShape( PCB_SHAPE* aShape, int aMaxError )
1942{
1943 VECTOR2I start, end;
1944
1945 if( !aShape->GetLineEndingEndpoints( start, end ) )
1946 {
1947 clearLineEndings( aShape );
1948 return;
1949 }
1950
1951 SHAPE_POLY_SET ink;
1952 aShape->TransformWithLineEndingsToPolygon( ink, 0, aMaxError, ERROR_INSIDE );
1953 ink.Simplify();
1954 ink.Fracture();
1955 EDA_GROUP* group = aShape->GetParentGroup();
1956 BOARD_ITEM_CONTAINER* parent = aShape->GetParent();
1957
1958 for( int outline = 0; outline < ink.OutlineCount(); ++outline )
1959 {
1960 PCB_SHAPE* polygon = outline == 0 ? aShape : new PCB_SHAPE( *aShape );
1961 SHAPE_POLY_SET poly;
1962 poly.AddOutline( ink.COutline( outline ) );
1963 polygon->SetShape( SHAPE_T::POLY );
1964 polygon->SetPolyShape( poly );
1965 polygon->SetWidth( 0 );
1967 polygon->SetLineStyle( LINE_STYLE::SOLID );
1968 clearLineEndings( polygon );
1969
1970 if( outline > 0 )
1971 {
1972 const_cast<KIID&>( polygon->m_Uuid ) = KIID();
1973 polygon->SetParentGroup( nullptr );
1974 parent->Add( polygon );
1975
1976 if( group )
1977 group->AddItem( polygon );
1978 }
1979 }
1980
1981 if( ink.OutlineCount() == 0 )
1982 clearLineEndings( aShape );
1983}
1984
1985
1986static void transformLineEndingsFp( FOOTPRINT* aFootprint, bool aOmit )
1987{
1988 std::vector<PCB_SHAPE*> shapes;
1989 visitFootprintItems( aFootprint,
1990 [&]( BOARD_ITEM* aItem )
1991 {
1992 if( hasLineEndings( aItem ) )
1993 shapes.push_back( static_cast<PCB_SHAPE*>( aItem ) );
1994 } );
1995
1996 for( PCB_SHAPE* shape : shapes )
1997 {
1998 if( aOmit )
1999 clearLineEndings( shape );
2000 else if( !unsafeLineEndings( shape ) )
2001 lowerLineEndingShape( shape, footprintMaxError( aFootprint ) );
2002 }
2003}
2004
2005
2006static void transformLineEndings( BOARD* aBoard, bool aOmit )
2007{
2008 std::vector<PCB_SHAPE*> shapes;
2009 visitBoardItems( aBoard,
2010 [&]( BOARD_ITEM* aItem )
2011 {
2012 if( hasLineEndings( aItem ) )
2013 shapes.push_back( static_cast<PCB_SHAPE*>( aItem ) );
2014 } );
2015
2016 for( PCB_SHAPE* shape : shapes )
2017 {
2018 if( aOmit )
2019 clearLineEndings( shape );
2020 else if( !unsafeLineEndings( shape ) )
2022 }
2023}
2024
2025
2026static int titleFieldIndex( const wxString& aName )
2027{
2028 if( aName == wxT( "TITLE" ) )
2029 return 0;
2030 if( aName == wxT( "ISSUE_DATE" ) )
2031 return 1;
2032 if( aName == wxT( "REVISION" ) )
2033 return 2;
2034 if( aName == wxT( "COMPANY" ) )
2035 return 3;
2036 if( aName.length() == 8 && aName.StartsWith( wxT( "COMMENT" ) ) && aName[7] >= '1' && aName[7] <= '9' )
2037 return 4 + aName[7].GetValue() - '1';
2038 return -1;
2039}
2040
2041
2042static const wxString& titleFieldText( const TITLE_BLOCK& aTitle, int aIndex )
2043{
2044 switch( aIndex )
2045 {
2046 case 0: return aTitle.GetTitle();
2047 case 1: return aTitle.GetDate();
2048 case 2: return aTitle.GetRevision();
2049 case 3: return aTitle.GetCompany();
2050 default: return aTitle.GetComment( aIndex - 4 );
2051 }
2052}
2053
2054
2055static bool postTargetText( const wxString& aText, const BOARD* aBoard, int aTargetVersion,
2056 std::set<wxString>& aDependencies, int aDepth = 0 )
2057{
2058 if( aDepth > 32 )
2059 return true;
2060
2061 for( size_t pos = 0; pos + 1 < aText.length(); ++pos )
2062 {
2063 if( aText[pos] == '\\' && pos + 2 < aText.length() && ( aText[pos + 1] == '$' || aText[pos + 1] == '@' )
2064 && aText[pos + 2] == '{' )
2065 {
2066 if( aTargetVersion <= PCB_WRITER_V9::FORMAT_VERSION )
2067 return true;
2068
2069 int braces = 1;
2070 pos += 3;
2071
2072 for( ; pos < aText.length() && braces; ++pos )
2073 braces += aText[pos] == '{' ? 1 : ( aText[pos] == '}' ? -1 : 0 );
2074
2075 --pos;
2076 continue;
2077 }
2078
2079 if( aText[pos] == '@' && aText[pos + 1] == '{'
2080 && ( aTargetVersion <= PCB_WRITER_V9::FORMAT_VERSION || aText.Mid( pos ).Contains( wxT( "mils" ) ) ) )
2081 return true;
2082
2083 if( aText[pos] == '$' && aText[pos + 1] == '{'
2084 && aText.Mid( pos + 2 ).BeforeFirst( '}' ).AfterFirst( ':' ).StartsWith( wxT( "PROPERTY." ) ) )
2085 return true;
2086 }
2087
2088 for( const auto& reference : ExtractTextVarReferences( aText ) )
2089 {
2090 const wxString& name = reference.primary;
2091
2092 if( name.StartsWith( wxT( "PROPERTY." ) ) || reference.secondary.StartsWith( wxT( "PROPERTY." ) )
2093 || name == wxT( "DRILL_OPERATIONS" ) || name == wxT( "DRILL_SITES" ) || name == wxT( "DRILL_GROUPS" ) )
2094 return true;
2095
2096 if( aBoard && titleFieldIndex( name ) >= 0 )
2097 {
2098 wxString resolved = name;
2099
2100 if( aBoard->GetTitleBlock().TextVarResolver( &resolved, aBoard->GetProject(), INTERNAL ) )
2101 {
2103
2104 if( aDependencies.insert( name ).second
2105 && postTargetText( resolved, aBoard, aTargetVersion, aDependencies, aDepth + 1 ) )
2106 return true;
2107
2108 continue;
2109 }
2110 }
2111
2112 if( aBoard )
2113 {
2114 const wxString* value = nullptr;
2115
2116 if( aBoard->GetProject() )
2117 {
2118 const auto& variables = aBoard->GetProject()->GetTextVars();
2119 auto variable = variables.find( name );
2120
2121 if( variable != variables.end() )
2122 value = &variable->second;
2123 }
2124
2125 if( !value )
2126 {
2127 const auto& properties = aBoard->GetProperties();
2128 auto property = properties.find( name );
2129
2130 if( property != properties.end() )
2131 value = &property->second;
2132 }
2133
2134 if( value && aDependencies.insert( name ).second
2135 && postTargetText( *value, aBoard, aTargetVersion, aDependencies, aDepth + 1 ) )
2136 return true;
2137 }
2138 }
2139
2140 return false;
2141}
2142
2143
2144static bool postTargetText( const wxString& aText, const BOARD* aBoard, int aTargetVersion )
2145{
2146 std::set<wxString> dependencies;
2147 return postTargetText( aText, aBoard, aTargetVersion, dependencies );
2148}
2149
2150
2152{
2153 std::vector<std::pair<BOARD_ITEM*, wxString>> m_replacements;
2154 std::vector<std::pair<int, wxString>> m_titleReplacements;
2155 BOARD* m_board = nullptr;
2156 int m_blocked = 0;
2157};
2158
2159
2160static bool hasActiveExpression( const wxString& aText )
2161{
2162 for( size_t pos = 0; pos + 1 < aText.length(); ++pos )
2163 {
2164 if( aText[pos] == '\\' && pos + 2 < aText.length() && ( aText[pos + 1] == '$' || aText[pos + 1] == '@' )
2165 && aText[pos + 2] == '{' )
2166 {
2167 int braces = 1;
2168 pos += 3;
2169
2170 for( ; pos < aText.length() && braces; ++pos )
2171 braces += aText[pos] == '{' ? 1 : ( aText[pos] == '}' ? -1 : 0 );
2172
2173 --pos;
2174 }
2175 else if( aText[pos] == '@' && aText[pos + 1] == '{' )
2176 {
2177 return true;
2178 }
2179 }
2180
2181 return false;
2182}
2183
2184
2185static void planTextItem( BOARD_ITEM* aItem, int aTargetVersion, TEXT_DOWNGRADE_PLAN& aPlan,
2186 const std::function<bool( wxString* )>* aResolver = nullptr )
2187{
2188 if( !aItem )
2189 return;
2190
2191 EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( aItem );
2192 PCB_BARCODE* barcode = dynamic_cast<PCB_BARCODE*>( aItem );
2193 PCB_DIMENSION_BASE* dimension = dynamic_cast<PCB_DIMENSION_BASE*>( aItem );
2194
2195 if( !text && !barcode )
2196 return;
2197
2198 if( dimension && !dimension->GetOverrideTextEnabled() )
2199 return;
2200
2201 const wxString raw = dimension ? dimension->GetOverrideText() : ( text ? text->GetText() : barcode->GetText() );
2202 BOARD* board = aItem->GetBoard();
2203
2204 if( !postTargetText( raw, board, aTargetVersion ) )
2205 return;
2206
2207 std::function<bool( wxString* )> resolver = [&]( wxString* aToken )
2208 {
2209 if( aResolver )
2210 return ( *aResolver )( aToken );
2211
2212 if( *aToken == wxT( "LAYER" ) && board )
2213 {
2214 *aToken = board->GetLayerName( aItem->GetLayer() );
2215 return true;
2216 }
2217
2218 FOOTPRINT* fp = aItem->GetParentFootprint();
2219 return ( fp && fp->ResolveTextVar( aToken, 0 ) ) || ( board && board->ResolveTextVar( aToken, 0 ) );
2220 };
2221
2222 wxString result;
2223 bool blocked = false;
2224
2225 for( size_t pos = 0; pos < raw.length(); )
2226 {
2227 size_t start = pos;
2228 bool escaped = raw[pos] == '\\' && pos + 2 < raw.length() && ( raw[pos + 1] == '$' || raw[pos + 1] == '@' )
2229 && raw[pos + 2] == '{';
2230 size_t marker = escaped ? pos + 1 : pos;
2231
2232 if( marker + 1 >= raw.length() || ( raw[marker] != '$' && raw[marker] != '@' ) || raw[marker + 1] != '{' )
2233 {
2234 result += raw[pos++];
2235 continue;
2236 }
2237
2238 int braces = 1;
2239 pos = marker + 2;
2240
2241 while( pos < raw.length() && braces )
2242 {
2243 braces += raw[pos] == '{' ? 1 : ( raw[pos] == '}' ? -1 : 0 );
2244 ++pos;
2245 }
2246
2247 wxString block = raw.Mid( start, pos - start );
2248
2249 if( escaped || !postTargetText( block, board, aTargetVersion ) )
2250 {
2251 result += block;
2252 continue;
2253 }
2254
2255 if( braces )
2256 {
2257 blocked = true;
2258 break;
2259 }
2260
2261 if( !aResolver )
2262 {
2263 // Resolve only the affected token using the native item-context semantics.
2264 if( auto cell = dynamic_cast<PCB_TABLECELL*>( aItem ) )
2265 {
2266 if( cell->GetRow() < 0 || cell->GetColumn() < 0 )
2267 {
2268 blocked = true;
2269 break;
2270 }
2271
2272 PCB_TABLE table( *static_cast<PCB_TABLE*>( cell->GetParent() ) );
2273 PCB_TABLECELL* temporary = table.GetCell( cell->GetRow(), cell->GetColumn() );
2274
2275 if( !temporary )
2276 {
2277 blocked = true;
2278 break;
2279 }
2280
2281 temporary->SetText( block );
2282 block = temporary->GetUnwrappedShownText( INTERNAL );
2283 }
2284 else
2285 {
2286 PCB_TEXT temporary( aItem->GetParent() );
2287 temporary.SetLayer( aItem->GetLayer() );
2288 temporary.SetText( block );
2289 block = temporary.GetShownText( INTERNAL );
2290 }
2291
2292 if( hasActiveExpression( block ) )
2293 blocked = true;
2294 }
2295 else
2296 {
2297 for( int depth = 0; depth < 32; ++depth )
2298 {
2299 wxString expanded = ExpandTextVars( block, &resolver, INTERNAL );
2300
2301 if( hasActiveExpression( expanded ) )
2302 {
2303 EXPRESSION_EVALUATOR evaluator;
2304 expanded = evaluator.Evaluate( expanded );
2305
2306 if( evaluator.HasErrors() )
2307 {
2308 blocked = true;
2309 break;
2310 }
2311 }
2312
2313 if( expanded == block )
2314 break;
2315
2316 block = expanded;
2317 }
2318
2319 if( hasActiveExpression( block ) )
2320 blocked = true;
2321 }
2322
2324
2325 if( postTargetText( block, board, aTargetVersion ) || block.Contains( wxT( "<Unresolved:" ) )
2326 || block.Contains( wxT( "<Unknown reference:" ) ) )
2327 blocked = true;
2328
2329 result += block;
2330 }
2331
2332 if( aTargetVersion <= PCB_WRITER_V9::FORMAT_VERSION
2333 && ( result.Contains( wxT( "\\${" ) ) || result.Contains( wxT( "\\@{" ) ) ) )
2334 blocked = true;
2335
2336 if( blocked )
2337 ++aPlan.m_blocked;
2338 else if( result != raw )
2339 aPlan.m_replacements.emplace_back( aItem, result );
2340}
2341
2342
2343static TEXT_DOWNGRADE_PLAN planBoardText( const BOARD* aBoard, int aTargetVersion )
2344{
2346 plan.m_board = const_cast<BOARD*>( aBoard );
2347 visitBoardItems( aBoard,
2348 [&]( BOARD_ITEM* aItem )
2349 {
2350 planTextItem( aItem, aTargetVersion, plan );
2351 } );
2352
2353 std::function<bool( wxString* )> resolver = [&]( wxString* aToken )
2354 {
2355 if( *aToken == wxT( "FILENAME" ) || *aToken == wxT( "FILEPATH" ) || *aToken == wxT( "VARIANT" )
2356 || *aToken == wxT( "VARIANT_DESC" ) )
2357 return aBoard->ResolveTextVar( aToken, 0 );
2358
2359 return aBoard->GetTitleBlock().TextVarResolver( aToken, aBoard->GetProject(), INTERNAL )
2360 || ( aBoard->GetProject() && aBoard->GetProject()->TextVarResolver( aToken ) );
2361 };
2362
2363 // Worksheet metadata has no footprint context or fixed plotted layer.
2364 for( int field = 0; field < 13; ++field )
2365 {
2366 PCB_TEXT temporary( const_cast<BOARD*>( aBoard ) );
2367 temporary.SetText( titleFieldText( aBoard->GetTitleBlock(), field ) );
2368 TEXT_DOWNGRADE_PLAN title;
2369 planTextItem( &temporary, aTargetVersion, title, &resolver );
2370 plan.m_blocked += title.m_blocked;
2371
2372 if( !title.m_replacements.empty() )
2373 plan.m_titleReplacements.emplace_back( field, title.m_replacements.front().second );
2374 }
2375
2376 return plan;
2377}
2378
2379
2380static void applyTextPlan( const TEXT_DOWNGRADE_PLAN& aPlan )
2381{
2382 for( const auto& [item, value] : aPlan.m_replacements )
2383 {
2384 if( auto dimension = dynamic_cast<PCB_DIMENSION_BASE*>( item ) )
2385 dimension->ChangeOverrideText( value );
2386 else if( auto text = dynamic_cast<EDA_TEXT*>( item ) )
2387 text->SetText( value );
2388 else if( auto barcode = dynamic_cast<PCB_BARCODE*>( item ) )
2389 barcode->SetBarcodeText( value );
2390 }
2391
2392 for( const auto& [field, value] : aPlan.m_titleReplacements )
2393 {
2394 TITLE_BLOCK& title = aPlan.m_board->GetTitleBlock();
2395
2396 switch( field )
2397 {
2398 case 0: title.SetTitle( value ); break;
2399 case 1: title.SetDate( value ); break;
2400 case 2: title.SetRevision( value ); break;
2401 case 3: title.SetCompany( value ); break;
2402 default: title.SetComment( field - 4, value ); break;
2403 }
2404 }
2405}
2406
2407
2408// One row per feature. The report and the transform both walk this list, so they cannot drift.
2409// To add a feature, add a row. A null apply means the feature blocks the export.
2411{
2414 wxString m_feature;
2415 wxString m_detail;
2416 std::function<int( const BOARD* )> m_count;
2417 std::function<void( BOARD* )> m_apply;
2418 std::function<void( FOOTPRINT* )> m_applyFp = {};
2419 std::function<void( BOARD* )> m_omit = {};
2420 std::function<void( FOOTPRINT* )> m_omitFp = {};
2421 wxString m_omitDetail = {};
2422
2426};
2427
2428
2429static std::vector<BOARD_RULE> boardRules()
2430{
2431 return {
2432 { VER_CONSTRAINTS, DOWNGRADE_BUCKET::BLOCK, _( "Unreviewed generated objects" ),
2433 _( "Unknown generator kinds or detached generated children cannot be safely exported." ),
2434 countUnknownGenerators, nullptr },
2435 { VER_CONSTRAINTS, DOWNGRADE_BUCKET::BLOCK, _( "Unreviewed tuning properties" ),
2436 _( "The tuning generator contains properties outside the reviewed historical schemas." ),
2438 { VER_TIME_DOMAIN_TUNING, DOWNGRADE_BUCKET::DROP, _( "Time-domain tuning patterns" ),
2439 _( "Generator editability is removed. Existing generated copper and group membership are kept." ),
2441 { VER_GROUP_PLACEMENT, DOWNGRADE_BUCKET::DROP, _( "Group placement areas" ),
2442 _( "Group placement linkage is removed. Zone geometry, keepout settings, groups, and copper are kept." ),
2444 { VER_CONSTRAINTS, DOWNGRADE_BUCKET::BLOCK, _( "Unreviewed board graphics" ),
2445 _( "An unrecognized graphic item cannot be safely represented by the target writer." ),
2446 countUnreviewedDrawings, nullptr },
2447 { VER_CONSTRAINTS, DOWNGRADE_BUCKET::DROP, _( "Geometric constraints" ),
2448 _( "Solver relationships are removed. Existing geometry is kept in its current position." ), countConstraints,
2450 { VER_GRIDS, DOWNGRADE_BUCKET::DROP, _( "Local grids" ),
2451 _( "Placement, snapping, and routing grid configuration is removed. Copper is unchanged." ), countGrids,
2452 dropGrids },
2453 { VER_VIA_STITCH, DOWNGRADE_BUCKET::DROP, _( "Via stitching and guarding" ),
2454 _( "Generator editability is removed. Existing generated copper and group membership are kept." ),
2455 []( const BOARD* b )
2456 {
2457 return countGenerators( b, wxT( "via_stitch" ) );
2458 },
2459 []( BOARD* b )
2460 {
2461 dropGenerators( b, wxT( "via_stitch" ) );
2462 } },
2463 { VER_VIA_STACK, DOWNGRADE_BUCKET::DROP, _( "Microvia stack generators" ),
2464 _( "Generator editability is removed. Existing generated copper and group membership are kept." ),
2465 []( const BOARD* b )
2466 {
2467 return countGenerators( b, wxT( "via_stack" ) );
2468 },
2469 []( BOARD* b )
2470 {
2471 dropGenerators( b, wxT( "via_stack" ) );
2472 } },
2473 { VER_LINE_ENDINGS, DOWNGRADE_BUCKET::LOWER, _( "Graphic line endings" ),
2474 _( "Converted to printed polygons with the shortened line body. Ending editability is lost." ),
2475 []( const BOARD* b )
2476 {
2477 return countLineEndings( b );
2478 },
2479 []( BOARD* b )
2480 {
2481 transformLineEndings( b, false );
2482 },
2483 []( FOOTPRINT* f )
2484 {
2485 transformLineEndingsFp( f, false );
2486 },
2487 []( BOARD* b )
2488 {
2489 transformLineEndings( b, true );
2490 },
2491 []( FOOTPRINT* f )
2492 {
2493 transformLineEndingsFp( f, true );
2494 },
2495 _( "Endpoint embellishments are removed. The supported line body and its style are kept." ) },
2496 { VER_SIM_EXCLUSION, DOWNGRADE_BUCKET::DROP, _( "Footprint simulation exclusions" ),
2497 _( "Simulation exclusions are removed. The target may include these footprints in simulation." ),
2499 { VER_CUSTOM_PROPERTIES, DOWNGRADE_BUCKET::DROP, _( "Custom properties" ),
2500 _( "Custom item metadata is removed after affected displayed text has been resolved." ),
2502 { VER_DRILL_DRAWINGS, DOWNGRADE_BUCKET::DROP, _( "Drill charts and maps" ),
2503 _( "Drill chart tables and drill map symbols are removed from the fabrication drawings. Holes are kept." ),
2505 { VER_DRILL_DRAWINGS, DOWNGRADE_BUCKET::DROP, _( "Drill symbol configuration" ),
2506 _( "Drill symbol assignments and display settings are removed. Holes are unchanged." ),
2508 { VER_NET_CHAINS, DOWNGRADE_BUCKET::DROP, _( "Net chains" ),
2509 _( "No equivalent in the target. Connectivity will change." ), countNetChains, dropNetChains },
2510 { VER_ELLIPSE, DOWNGRADE_BUCKET::LOWER, _( "Ellipse graphics" ), _( "Approximated by a polygon." ),
2512 _( "Ellipse graphics are omitted instead of approximated." ) },
2513 { VER_EXTRUDED_BODY, DOWNGRADE_BUCKET::DROP, _( "Extruded 3D bodies" ),
2514 _( "No equivalent in the target. The 3D body is left out." ), countExtrudedBodies, dropExtrudedBodies,
2516 { VER_THIEVING, DOWNGRADE_BUCKET::BLOCK, _( "Copper-thieving zones" ),
2517 _( "A refill in the target would turn the pattern into solid copper." ), countThievingZones, nullptr },
2518 { VER_PAD_SIM, DOWNGRADE_BUCKET::DROP, _( "Pad simulation types" ),
2519 _( "The simulation electrical type is left out." ), countPadSimTypes, dropPadSimTypes, dropPadSimTypesFp },
2520 { VER_FP_UNITS, DOWNGRADE_BUCKET::DROP, _( "Footprint unit metadata" ),
2521 _( "Pin-to-unit assignments used for gate swapping are removed." ), countUnitInfo, dropUnitInfo,
2523 { VER_BARCODE, DOWNGRADE_BUCKET::LOWER, _( "Barcodes" ),
2524 _( "Converted to the printed polygons. No longer editable as a barcode." ), countBarcodes, lowerBarcodes,
2526 _( "Barcodes are omitted instead of converted to polygons." ) },
2527 { FIRST_PCB_VARIANTS, DOWNGRADE_BUCKET::DROP, _( "PCB variants" ),
2528 _( "Variant registry and per-footprint overrides are removed." ), countPcbVariants, dropPcbVariants,
2530 { VER_JUMPER_PADS, DOWNGRADE_BUCKET::DROP, _( "Jumper pad groups" ), _( "Jumper pad groupings are removed." ),
2532 { VER_BACKDRILL, DOWNGRADE_BUCKET::BLOCK, _( "Backdrill / tertiary drill" ),
2533 _( "Backdrilling changes copper connectivity and cannot be represented." ), countBackdrills, nullptr },
2534 { VER_POST_MACHINING, DOWNGRADE_BUCKET::DROP, _( "Counterbore / countersink" ),
2535 _( "Drill post-machining is removed." ), countPostMachining, dropPostMachining, dropPostMachiningFp },
2536 { VER_PRESSFIT, DOWNGRADE_BUCKET::DROP, _( "Press-fit pads" ),
2537 _( "The press-fit fabrication property is removed." ), countPressFitPads, dropPressFitPads,
2539 { VER_SKIP_VIA,
2541 _( "Skip vias" ),
2542 _( "Approximated by keeping only the start and end layers." ),
2545 {},
2546 {},
2547 {},
2548 {},
2549 true },
2550 { VER_VIA_SPLIT,
2552 _( "Buried vias" ),
2553 _( "Written with the older combined blind type." ),
2556 {},
2557 {},
2558 {},
2559 {},
2560 true },
2561 { VER_ZONE_DEFAULTS, DOWNGRADE_BUCKET::DROP, _( "Zone layer defaults" ),
2562 _( "Per-layer zone defaults are removed." ), countZoneDefaults, dropZoneDefaults },
2563 { VER_PAD_DIE_DELAY, DOWNGRADE_BUCKET::DROP, _( "Pad-to-die delays" ), _( "Pad-to-die delay is removed." ),
2565 { VER_SHAPE_HATCH, DOWNGRADE_BUCKET::LOWER, _( "Hatched shape fills" ), _( "Converted to a solid fill." ),
2566 []( const BOARD* b )
2567 {
2569 },
2570 []( BOARD* b )
2571 {
2573 },
2574 []( FOOTPRINT* f )
2575 {
2577 },
2578 []( BOARD* b )
2579 {
2581 },
2582 []( FOOTPRINT* f )
2583 {
2585 },
2586 _( "The hatch fill is omitted. The supported outline is kept." ) },
2589 _( "Hatched copper fills" ),
2590 _( "Converted to a solid fill. Copper geometry may change." ),
2591 []( const BOARD* b )
2592 {
2594 },
2595 []( BOARD* b )
2596 {
2598 },
2599 []( FOOTPRINT* f )
2600 {
2602 },
2603 {},
2604 {},
2605 {},
2606 true },
2607 { VER_PCB_POINTS, DOWNGRADE_BUCKET::DROP, _( "PCB points" ),
2608 _( "No equivalent in the target. Points are removed." ), countPcbPoints, dropPcbPoints, dropPcbPointsFp },
2609 { VER_VIA_PROTECTION, DOWNGRADE_BUCKET::DROP, _( "Via protection" ),
2610 _( "IPC-4761 covering, plugging, capping, and filling are removed." ), countViaProtection,
2612 { VER_DIELECTRIC, DOWNGRADE_BUCKET::DROP, _( "Dielectric frequency models" ),
2613 _( "The frequency-dependent dielectric model is removed." ), countDielectricModels, dropDielectricModels },
2614 { VER_GROUP_LIB_ID, DOWNGRADE_BUCKET::DROP, _( "Group design block links" ),
2615 _( "The link from a group to its design block is removed." ), countGroupDesignBlocks, dropGroupDesignBlocks,
2617 { VER_TEXTBOX_KNOCKOUT, DOWNGRADE_BUCKET::LOWER, _( "Knockout text boxes" ), _( "Drawn as plain text boxes." ),
2619 omitKnockoutTextBoxesFp, _( "Knockout text boxes are omitted instead of drawn as plain text." ) },
2620 { VER_ROUNDED_RECT, DOWNGRADE_BUCKET::LOWER, _( "Rounded rectangles" ), _( "Approximated by a polygon." ),
2622 _( "Rounded rectangles and rounded custom pad primitives are omitted. Copper geometry may change." ) },
2623 { VER_CELL_KNOCKOUT, DOWNGRADE_BUCKET::LOWER, _( "Knockout table cells" ), _( "Drawn as plain cells." ),
2626 _( "Knockout cell contents are omitted; empty cells and the table grid are kept." ) },
2627 { VER_ZONE_DEFAULTS, DOWNGRADE_BUCKET::DROP, _( "Per-zone layer properties" ),
2628 _( "The per-layer overrides are removed." ), countZoneLayerProperties, dropZoneLayerProperties,
2630 { VER_USER_LAYERS, DOWNGRADE_BUCKET::BLOCK, _( "User layers past User_9" ),
2631 _( "The target supports only 9 user layers." ), countHighUserLayers, nullptr },
2632 { FIRST_FP_CUSTOM_STACKUP, DOWNGRADE_BUCKET::BLOCK, _( "Custom footprint stackups" ),
2633 _( "The target would expand the footprint's inner layers and change its connectivity." ),
2634 countCustomFpStackups, nullptr },
2635 { FIRST_FP_AFFINE_TRANSFORM, DOWNGRADE_BUCKET::BLOCK, _( "Scaled footprints" ),
2636 _( "The target cannot represent a scaled footprint." ), countScaledFootprints, nullptr },
2637 };
2638}
2639
2640
2642 bool aDropInsteadOfApproximate )
2643{
2644 // The rules were written against a specific format. Warn developers when the format has
2645 // moved on, since a frozen writer silently omits features that have no rule yet.
2647 wxT( "Board format changed. Review the downgrade rules and denylist, then update "
2648 "BOARD_DOWNGRADE_COVERED." ) );
2649
2650 COMPATIBILITY_REPORT report;
2651
2653 report.Add( DOWNGRADE_BUCKET::BLOCK, _( "Unreviewed board format" ),
2654 _( "The current board format has changed since the downgrade rules were reviewed." ), 1 );
2655
2656 if( aBoard->GetPlotOptions().GetFormat() == PLOT_FORMAT::PNG )
2657 report.Add( DOWNGRADE_BUCKET::DROP, _( "PNG plot selection" ),
2658 _( "The unsupported plot format is reset to Gerber." ), 1 );
2659
2661 {
2662 const TEXT_DOWNGRADE_PLAN text = planBoardText( aBoard, aTarget.m_boardVersion );
2663
2664 if( text.m_blocked )
2665 report.Add( DOWNGRADE_BUCKET::BLOCK, _( "Unresolved post-target text" ),
2666 _( "Affected property variables or expressions could not be safely resolved." ),
2667 text.m_blocked );
2668
2669 if( !text.m_replacements.empty() || !text.m_titleReplacements.empty() )
2670 report.Add( DOWNGRADE_BUCKET::DROP, _( "Post-target text variables" ),
2671 _( "Affected displayed text is resolved to its current value and loses dynamic evaluation." ),
2672 text.m_replacements.size() + text.m_titleReplacements.size() );
2673 }
2674
2675 if( aTarget.m_boardVersion < VER_LINE_ENDINGS && !aDropInsteadOfApproximate )
2676 {
2677 if( int unsafe = countLineEndings( aBoard, true ); unsafe > 0 )
2678 report.Add( DOWNGRADE_BUCKET::BLOCK, _( "Styled graphic line endings" ),
2679 _( "Dashed, colored, or unresolved-width line endings cannot be faithfully converted. "
2680 "The omission policy can remove just the endpoint embellishments." ),
2681 unsafe );
2682 }
2683
2684 for( const BOARD_RULE& rule : boardRules() )
2685 {
2686 if( aTarget.m_boardVersion >= rule.m_introducedIn )
2687 continue;
2688
2689 if( int count = rule.m_count( aBoard ); count > 0 )
2690 {
2691 bool omit = aDropInsteadOfApproximate && rule.m_bucket == DOWNGRADE_BUCKET::LOWER
2692 && !rule.m_keepUnderOmitPolicy;
2693 report.Add( omit ? DOWNGRADE_BUCKET::DROP : rule.m_bucket, rule.m_feature,
2694 omit ? rule.m_omitDetail : rule.m_detail, count );
2695 }
2696 }
2697
2698 return report;
2699}
2700
2701
2702// The stored scale compensates for the truncated pixels/cm PPI that targets before 20260623
2703// compute, so it must be rewound. The catalog keeps images with a silent correction, so this
2704// is not a table rule and adds no report row.
2706{
2707 REFERENCE_IMAGE& ref = aImage->GetReferenceImage();
2708 int legacyPPI = ref.GetImage().GetLegacyPPI();
2709
2710 if( legacyPPI > 0 && ref.GetImage().GetPPI() != legacyPPI )
2711 ref.SetImageScale( ref.GetImageScale() * legacyPPI / ref.GetImage().GetPPI() );
2712}
2713
2714
2716{
2717 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
2718 {
2719 if( item->Type() == PCB_REFERENCE_IMAGE_T )
2720 compensateImageScale( static_cast<PCB_REFERENCE_IMAGE*>( item ) );
2721 }
2722}
2723
2724
2726{
2727 for( BOARD_ITEM* item : aBoard->Drawings() )
2728 {
2729 if( item->Type() == PCB_REFERENCE_IMAGE_T )
2730 compensateImageScale( static_cast<PCB_REFERENCE_IMAGE*>( item ) );
2731 }
2732
2733 for( FOOTPRINT* fp : aBoard->Footprints() )
2735}
2736
2737
2738void DowngradeBoardInPlace( BOARD* aBoard, const DOWNGRADE_TARGET& aTarget, bool aDropInsteadOfApproximate )
2739{
2740 if( aBoard->GetPlotOptions().GetFormat() == PLOT_FORMAT::PNG )
2741 {
2742 PCB_PLOT_PARAMS options = aBoard->GetPlotOptions();
2743 options.SetFormat( PLOT_FORMAT::GERBER );
2744 aBoard->SetPlotOptions( options );
2745 }
2746
2748 applyTextPlan( planBoardText( aBoard, aTarget.m_boardVersion ) );
2749
2750 if( aTarget.m_boardVersion < VER_IMAGE_PPI )
2752
2753 for( const BOARD_RULE& rule : boardRules() )
2754 {
2755 if( aTarget.m_boardVersion >= rule.m_introducedIn )
2756 continue;
2757
2758 bool omit = aDropInsteadOfApproximate && rule.m_bucket == DOWNGRADE_BUCKET::LOWER
2759 && !rule.m_keepUnderOmitPolicy;
2760 const auto& apply = omit ? rule.m_omit : rule.m_apply;
2761 wxASSERT_MSG( !omit || apply, wxT( "A LOWER rule needs an omission transform." ) );
2762
2763 if( apply )
2764 apply( aBoard );
2765 }
2766}
2767
2768
2769// A library footprint carries the same post-target features as one on a board, but it lives in its
2770// own .kicad_mod, so the board rules never see it. This applies the footprint-scoped subset.
2771void DowngradeFootprintInPlace( FOOTPRINT* aFootprint, const DOWNGRADE_TARGET& aTarget, bool aDropInsteadOfApproximate )
2772{
2774 {
2776 visitFootprintItems( aFootprint,
2777 [&]( BOARD_ITEM* item )
2778 {
2779 planTextItem( item, aTarget.m_boardVersion, text );
2780 } );
2782 }
2783
2784 if( aTarget.m_boardVersion < VER_IMAGE_PPI )
2786
2787 for( const BOARD_RULE& rule : boardRules() )
2788 {
2789 if( aTarget.m_boardVersion >= rule.m_introducedIn )
2790 continue;
2791
2792 bool omit = aDropInsteadOfApproximate && rule.m_bucket == DOWNGRADE_BUCKET::LOWER
2793 && !rule.m_keepUnderOmitPolicy;
2794 const auto& apply = omit ? rule.m_omitFp : rule.m_applyFp;
2795 wxASSERT_MSG( !omit || !rule.m_applyFp || apply, wxT( "A footprint LOWER rule needs an omission transform." ) );
2796
2797 if( apply )
2798 apply( aFootprint );
2799 }
2800}
2801
2802
2803// Node heads dated by the format that introduced them. The versioned writers cannot emit these,
2804// so this gate is a safety net against a wrong or miswired writer. To add a token, add a row
2805// with its format version.
2806static constexpr DATED_TOKEN BOARD_TOKENS[] = {
2807 { "is_time_domain", VER_TIME_DOMAIN_TUNING },
2808 { "target_delay", VER_TIME_DOMAIN_TUNING },
2809 { "target_delay_min", VER_TIME_DOMAIN_TUNING },
2810 { "target_delay_max", VER_TIME_DOMAIN_TUNING },
2811 { "last_tuning_length", VER_TIME_DOMAIN_TUNING },
2812 { "constraint", VER_CONSTRAINTS },
2813 { "grid_item", VER_GRIDS },
2814 { "start_shape", VER_LINE_ENDINGS },
2815 { "end_shape", VER_LINE_ENDINGS },
2816 { "custom_property", VER_CUSTOM_PROPERTIES },
2817 { "exclude_from_sim", VER_SIM_EXCLUSION },
2818 { "drill_chart", VER_DRILL_DRAWINGS },
2819 { "drill_map", VER_DRILL_DRAWINGS },
2820 { "drill_symbol_profile", VER_DRILL_DRAWINGS },
2821 { "templates", VER_VIA_STITCH },
2822 { "hatch_position", VER_ZONE_DEFAULTS },
2823 { "covering", VER_VIA_PROTECTION },
2824 { "plugging", VER_VIA_PROTECTION },
2825 { "capping", VER_VIA_PROTECTION },
2826 { "filling", VER_VIA_PROTECTION },
2827 { "depth", VER_VIA_PROTECTION },
2828 { "zone_defaults", VER_ZONE_DEFAULTS },
2829 { "jumper_pad_groups", VER_JUMPER_PADS },
2830 { "lib_id", VER_GROUP_LIB_ID },
2831 { "start_end_only", VER_SKIP_VIA },
2832 { "point", VER_PCB_POINTS },
2833 { "unit", VER_FP_UNITS },
2834 { "pins", VER_FP_UNITS },
2835 { "barcode", VER_BARCODE },
2836 { "ecc_level", VER_BARCODE },
2837 { "text", VER_BARCODE },
2838 { "text_height", VER_BARCODE },
2839 { "die_delay", VER_PAD_DIE_DELAY },
2840 { "backdrill", VER_BACKDRILL },
2841 { "tertiary_drill", VER_BACKDRILL },
2842 { "front_post_machining", VER_POST_MACHINING },
2843 { "back_post_machining", VER_POST_MACHINING },
2844 { "variant", FIRST_PCB_VARIANTS },
2845 { "variants", FIRST_PCB_VARIANTS },
2846 { "field", FIRST_PCB_VARIANTS },
2847 { "description", FIRST_PCB_VARIANTS },
2848 { "body_pcb_gap", VER_EXTRUDED_BODY },
2849 { "overall_height", VER_EXTRUDED_BODY },
2850 { "gr_ellipse", VER_ELLIPSE },
2851 { "gr_ellipse_arc", VER_ELLIPSE },
2852 { "fp_ellipse", VER_ELLIPSE },
2853 { "fp_ellipse_arc", VER_ELLIPSE },
2854 { "dielectric_model", VER_DIELECTRIC },
2855 { "spec_frequency", VER_DIELECTRIC },
2856 { "constant", VER_DIELECTRIC },
2857 { "net_chain", VER_NET_CHAINS },
2858 { "net_chains", VER_NET_CHAINS },
2859 { "terminal_pad", VER_NET_CHAINS },
2860 { "thieving", VER_THIEVING },
2861 { "rotation_angle", VER_ELLIPSE },
2862 { "stagger", VER_THIEVING },
2863 { "sim_electrical_type", VER_PAD_SIM },
2864 { "transform", FIRST_FP_AFFINE_TRANSFORM },
2865};
2866
2867// Bareword values the target cannot parse. The node scan cannot see these.
2868// Hatch fill values cannot be listed here, unlike the schematic gate. Zones legitimately emit
2869// hatch tokens in every target format, so a hatched shape fill leak is invisible to this gate.
2870static constexpr DATED_TOKEN BOARD_VALUES[] = {
2871 { "exclude_from_sim", VER_SIM_EXCLUSION }, { "via_stitch", VER_VIA_STITCH }, { "via_stack", VER_VIA_STACK },
2872 { "pad_prop_pressfit", VER_PRESSFIT }, { "buried", VER_VIA_SPLIT },
2873};
2874
2875
2880
2881
2882// Each target has its own writer, extracted from that release. A target without a writer writes
2883// nothing, and the fail-closed check downstream refuses the export.
2884void SaveBoardForTarget( BOARD* aBoard, const wxString& aPath, const DOWNGRADE_TARGET& aTarget )
2885{
2887 PCB_WRITER_V10().SaveBoard( aPath, aBoard );
2888 else if( aTarget.m_boardVersion == PCB_WRITER_V9::FORMAT_VERSION )
2889 PCB_WRITER_V9().SaveBoard( aPath, aBoard );
2890 else
2891 wxFAIL_MSG( wxT( "No writer for this downgrade target." ) );
2892}
2893
2894
2895void SaveFootprintForTarget( FOOTPRINT* aFootprint, const wxString& aPath, const DOWNGRADE_TARGET& aTarget )
2896{
2898 PCB_WRITER_V10().SaveFootprintFile( aPath, aFootprint );
2899 else if( aTarget.m_boardVersion == PCB_WRITER_V9::FORMAT_VERSION )
2900 PCB_WRITER_V9().SaveFootprintFile( aPath, aFootprint );
2901 else
2902 wxFAIL_MSG( wxT( "No writer for this downgrade target." ) );
2903}
2904
2905
2906static bool containsGroupPlacementSource( const std::string& aText )
2907{
2908 const std::string text = StripSexprStrings( aText );
2909 std::vector<std::string> path;
2910
2911 for( size_t position = 0; position < text.size(); ++position )
2912 {
2913 if( text[position] == '(' )
2914 {
2915 size_t begin = position + 1;
2916
2917 while( begin < text.size() && std::isspace( static_cast<unsigned char>( text[begin] ) ) )
2918 ++begin;
2919
2920 size_t end = begin;
2921
2922 while( end < text.size()
2923 && ( std::isalnum( static_cast<unsigned char>( text[end] ) ) || text[end] == '_' ) )
2924 ++end;
2925
2926 const std::string head = text.substr( begin, end - begin );
2927
2928 if( head == "group" && path.size() >= 2 && path.back() == "placement" && path[path.size() - 2] == "zone" )
2929 return true;
2930
2931 path.push_back( head );
2932 position = end > begin ? end - 1 : position;
2933 }
2934 else if( text[position] == ')' && !path.empty() )
2935 {
2936 path.pop_back();
2937 }
2938 }
2939
2940 return false;
2941}
2942
2943
2944wxString FindUnsupportedBoardToken( const wxString& aSerialized, const DOWNGRADE_TARGET& aTarget )
2945{
2947 return wxEmptyString;
2948
2949 if( aTarget.m_boardVersion < VER_GROUP_PLACEMENT && containsGroupPlacementSource( aSerialized.ToStdString() ) )
2950 return wxT( "placement/group" );
2951
2952 return FindUnsupportedToken( aSerialized, BOARD_TOKENS, std::size( BOARD_TOKENS ), BOARD_VALUES,
2953 std::size( BOARD_VALUES ), aTarget.m_boardVersion );
2954}
2955
2956
2957void FlattenBoardVariant( BOARD* aBoard, const wxString& aVariantName )
2958{
2959 for( FOOTPRINT* fp : aBoard->Footprints() )
2960 {
2961 fp->SetDNP( fp->GetDNPForVariant( aVariantName ) );
2962 fp->SetExcludedFromBOM( fp->GetExcludedFromBOMForVariant( aVariantName ) );
2963 fp->SetExcludedFromPosFiles( fp->GetExcludedFromPosFilesForVariant( aVariantName ) );
2964 fp->SetExcludedFromSim( fp->GetExcludedFromSimForVariant( aVariantName ) );
2965
2966 if( const FOOTPRINT_VARIANT* variant = fp->GetVariant( aVariantName ) )
2967 {
2968 for( const auto& [name, value] : variant->GetFields() )
2969 {
2970 if( PCB_FIELD* field = fp->GetField( name ) )
2971 field->SetText( value );
2972 }
2973 }
2974 }
2975}
2976
2977
2978DOWNGRADE_FILE_RESULT DowngradeFootprintFileToTemp( const wxString& aSrcFile, const wxString& aTmpFile,
2979 const DOWNGRADE_TARGET& aTarget, bool aDropInsteadOfApproximate )
2980{
2981 PCB_IO_KICAD_SEXPR loader;
2982 wxString nameOut;
2983 std::unique_ptr<FOOTPRINT> owned = loader.ImportFootprint( aSrcFile, nameOut );
2984
2985 if( !owned )
2987
2988 // The scratch board lets the block rules decide, so this stays in step with the rule
2989 // table instead of repeating a subset of it.
2990 BOARD scratch;
2991 FOOTPRINT* fp = owned.get();
2992 scratch.Add( owned.release() );
2993
2994 if( ClassifyBoardForDowngrade( &scratch, aTarget, aDropInsteadOfApproximate ).IsBlocked() )
2996
2997 // Detached before writing, like a real library save. The writers clip a parented
2998 // footprint's zone layers and padstacks to the board's layer set. Remove does not clear
2999 // the parent pointer, so that is explicit.
3000 scratch.Remove( fp );
3001 owned.reset( fp );
3002 fp->SetParent( nullptr );
3003
3004 DowngradeFootprintInPlace( fp, aTarget, aDropInsteadOfApproximate );
3005 SaveFootprintForTarget( fp, aTmpFile, aTarget );
3007}
3008
3009
3010bool ExportBoardToOlderVersion( const wxString& aSrcFile, const wxString& aDestFile, const DOWNGRADE_TARGET& aTarget,
3011 COMPATIBILITY_REPORT& aReport, wxString* aUnsupportedToken, const wxString& aVariant,
3012 bool aDropInsteadOfApproximate )
3013{
3014 if( aUnsupportedToken )
3015 aUnsupportedToken->clear();
3016
3017 SETTINGS_MANAGER projectSettings;
3018 wxFileName projectFile( aSrcFile );
3019 projectFile.SetExt( FILEEXT::ProjectFileExtension );
3020 projectFile.MakeAbsolute();
3021 projectSettings.LoadProject( projectFile.GetFullPath(), false );
3022
3024 std::unique_ptr<BOARD> board( pi.LoadBoard( aSrcFile, nullptr ) );
3025
3026 if( !board )
3027 return false;
3028
3029 // Resolve project variables before replacing dynamic items with fixed geometry.
3030 board->SetProject( projectSettings.GetProject( projectFile.GetFullPath() ), true );
3031
3032 if( !aVariant.IsEmpty() )
3033 FlattenBoardVariant( board.get(), aVariant );
3034
3035 aReport = ClassifyBoardForDowngrade( board.get(), aTarget, aDropInsteadOfApproximate );
3036
3037 if( aReport.IsBlocked() )
3038 return false;
3039
3040 DowngradeBoardInPlace( board.get(), aTarget, aDropInsteadOfApproximate );
3041
3042 // Write to a sibling temp and verify that before it replaces the destination, so a failed
3043 // export never destroys a pre-existing output file. The writer resolves symlinks on purpose,
3044 // so reserve the name exclusively rather than spelling out a guessable one.
3045 const wxString tmpFile = wxFileName::CreateTempFileName( aDestFile + wxT( ".downgrade_tmp." ) );
3046
3047 if( tmpFile.IsEmpty() )
3048 return false;
3049
3050 // Release the reservation so the writer's own exclusive create still sees a new target and
3051 // keeps the permissions an ordinary save would give it.
3052 wxRemoveFile( tmpFile );
3053
3054 try
3055 {
3056 SaveBoardForTarget( board.get(), tmpFile, aTarget );
3057 }
3058 catch( ... )
3059 {
3060 wxRemoveFile( tmpFile );
3061 throw;
3062 }
3063
3064 // Fail closed. Discard the temp if it still uses a token the target cannot parse, or if it
3065 // cannot be read back to verify.
3066 wxString bad;
3067
3068 bool good = VerifyDowngradedFile(
3069 tmpFile, aTarget.m_boardVersion,
3070 [&]( const wxString& aText )
3071 {
3072 return FindUnsupportedBoardToken( aText, aTarget );
3073 },
3074 &bad );
3075
3076 if( !good )
3077 {
3078 wxRemoveFile( tmpFile );
3079
3080 if( aUnsupportedToken )
3081 *aUnsupportedToken = bad;
3082
3083 return false;
3084 }
3085
3086 if( !wxRenameFile( tmpFile, aDestFile, true ) )
3087 {
3088 wxRemoveFile( tmpFile );
3089 return false;
3090 }
3091
3092 return true;
3093}
const char * name
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
static void dropDielectricModels(BOARD *aBoard)
static void dropUnitInfoFp(FOOTPRINT *aFootprint)
COMPATIBILITY_REPORT ClassifyBoardForDowngrade(const BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a board would lose if exported to an older KiCad.
static void dropUnitInfo(BOARD *aBoard)
static void transformLineEndingsFp(FOOTPRINT *aFootprint, bool aOmit)
static constexpr int VER_IMAGE_PPI
static constexpr int VER_USER_LAYERS
static constexpr int VER_JUMPER_PADS
static void dropKnockoutTableCells(BOARD *aBoard)
static void lowerBarcodes(BOARD *aBoard)
static void dropPressFitPadsFp(FOOTPRINT *aFootprint)
static int countDieDelays(const BOARD *aBoard)
static bool hasViaProtection(const PCB_VIA *aVia)
static void detachFromGroup(BOARD_ITEM *aItem)
static void dropTimeDomainTuning(BOARD *aBoard)
static void dropJumperPadsFp(FOOTPRINT *aFootprint)
static void dropCustomProperties(BOARD *aBoard)
static int countCustomFpStackups(const BOARD *aBoard)
static constexpr int VER_DRILL_DRAWINGS
static int countHatchedFills(const BOARD *aBoard, const std::function< bool(const BOARD_ITEM *)> &aMatches)
static constexpr int VER_THIEVING
static bool isDrillDrawing(const BOARD_ITEM *aItem)
static void lowerRoundedRects(BOARD *aBoard)
void SaveFootprintForTarget(FOOTPRINT *aFootprint, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a single .kicad_mod footprint file for the target.
static constexpr int VER_DIELECTRIC
static constexpr int BOARD_DOWNGRADE_COVERED
static int countKnockoutTextBoxesFp(const FOOTPRINT *aFootprint)
static void dropKnockoutCellsIn(BOARD_ITEM *aItem)
static void omitBarcodesFp(FOOTPRINT *aFootprint)
static int countLineEndings(const BOARD *aBoard, bool aUnsafeOnly=false)
static void lowerBuriedVias(BOARD *aBoard)
static void dropPostMachiningFp(FOOTPRINT *aFootprint)
static int countPcbPoints(const BOARD *aBoard)
static int countGrids(const BOARD *aBoard)
static void omitGraphicsFp(FOOTPRINT *aFootprint, const std::function< bool(const BOARD_ITEM *)> &aMatches)
static constexpr int VER_PRESSFIT
static void dropKnockoutTextBoxes(BOARD *aBoard)
int BoardDowngradeCoveredVersion()
The format the denylist was generated against. A format bump forces a review.
static void forEachPadPrimitive(FOOTPRINT *aFootprint, const std::function< void(PCB_SHAPE *)> &aFunc)
static bool isBarcode(const BOARD_ITEM *aItem)
static bool hasActiveExpression(const wxString &aText)
static int countZoneDefaults(const BOARD *aBoard)
static bool isUnreviewedDrawing(const BOARD_ITEM *aItem)
static void dropViaProtection(BOARD *aBoard)
static void omitKnockoutTextBoxesFp(FOOTPRINT *aFootprint)
static void dropJumperPads(BOARD *aBoard)
static void dropGrids(BOARD *aBoard)
static constexpr int VER_SIM_EXCLUSION
static int countJumperPads(const BOARD *aBoard)
static int countPostMachining(const BOARD *aBoard)
static int countSkipVias(const BOARD *aBoard)
static int countPadSimTypes(const BOARD *aBoard)
static void planTextItem(BOARD_ITEM *aItem, int aTargetVersion, TEXT_DOWNGRADE_PLAN &aPlan, const std::function< bool(wxString *)> *aResolver=nullptr)
static const wxString & titleFieldText(const TITLE_BLOCK &aTitle, int aIndex)
static void roundedRectToPolygon(PCB_SHAPE *aShape, int aMaxError)
static int countHighUserLayers(const BOARD *aBoard)
static bool hasBackdrill(const PADSTACK &aPadstack)
static void dropZoneLayerProperties(BOARD *aBoard)
static int countBackdrills(const BOARD *aBoard)
static int countNetChains(const BOARD *aBoard)
static void setHatchedFills(BOARD *aBoard, const std::function< bool(const BOARD_ITEM *)> &aMatches, FILL_T aFill)
static std::vector< BOARD_RULE > boardRules()
static void dropConstraints(BOARD *aBoard)
static int countKnockoutCellsIn(const BOARD_ITEM *aItem)
static constexpr int VER_ZONE_DEFAULTS
static constexpr int VER_SHAPE_HATCH
static int countRoundedRectsFp(const FOOTPRINT *aFootprint)
static void omitGraphics(BOARD *aBoard, const std::function< bool(const BOARD_ITEM *)> &aMatches)
void SaveBoardForTarget(BOARD *aBoard, const wxString &aPath, const DOWNGRADE_TARGET &aTarget)
Save a board stamped for the target, using the writer for that version.
static int countGroupDesignBlocks(const BOARD *aBoard)
static void omitEllipses(BOARD *aBoard)
static int countScaledFootprints(const BOARD *aBoard)
static int countSimulationExclusions(const BOARD *aBoard)
static int footprintMaxError(const FOOTPRINT *aFootprint)
static bool isGrid(const BOARD_ITEM *aItem)
void FlattenBoardVariant(BOARD *aBoard, const wxString &aVariantName)
Bake a variant's overrides into the base footprint attributes.
static void dropPcbVariantsFp(FOOTPRINT *aFootprint)
static int countZoneLayerProperties(const BOARD *aBoard)
static void dropConstraintsFp(FOOTPRINT *aFootprint)
static void omitKnockoutTableCells(BOARD *aBoard)
static void dropExtrudedBodiesFp(FOOTPRINT *aFootprint)
static void setHatchedFillsFp(FOOTPRINT *aFootprint, const std::function< bool(const BOARD_ITEM *)> &aMatches, FILL_T aFill)
static int countGroupPlacementAreas(const BOARD *aBoard)
static void transformLineEndings(BOARD *aBoard, bool aOmit)
static constexpr int VER_PAD_DIE_DELAY
static constexpr int VER_VIA_SPLIT
static int countUnreviewedDrawings(const BOARD *aBoard)
static constexpr int VER_VIA_STITCH
static void omitEllipsesFp(FOOTPRINT *aFootprint)
static void dropGroupPlacementLinkage(ZONE *aZone)
static void dropDieDelaysFp(FOOTPRINT *aFootprint)
static constexpr int VER_NET_CHAINS
static void dropExtrudedBodies(BOARD *aBoard)
static bool isHatchedShape(const BOARD_ITEM *aItem)
static constexpr DATED_TOKEN BOARD_VALUES[]
static int countBuriedVias(const BOARD *aBoard)
static void dropNetChains(BOARD *aBoard)
static constexpr int VER_TIME_DOMAIN_TUNING
static bool isKnockoutTextBox(const BOARD_ITEM *aItem)
static int countUnknownGenerators(const BOARD *aBoard)
static constexpr int VER_VIA_STACK
static constexpr int VER_ELLIPSE
static int countDrillSymbolConfiguration(const BOARD *aBoard)
static void omitRoundedRectsFp(FOOTPRINT *aFootprint)
static void dropDrillDrawings(BOARD *aBoard)
static bool isTimeDomainTuning(const PCB_GENERATOR *aGenerator)
static constexpr int VER_CELL_KNOCKOUT
static constexpr int VER_GRIDS
static constexpr int VER_PROPERTY_VARIABLES
static void dropZoneDefaults(BOARD *aBoard)
static int countTimeDomainTuning(const BOARD *aBoard)
static int countCustomProperties(const BOARD *aBoard)
static void dropPadSimTypesFp(FOOTPRINT *aFootprint)
static bool isHatchedCopperShape(const BOARD_ITEM *aItem)
static constexpr int VER_TEXTBOX_KNOCKOUT
bool ExportBoardToOlderVersion(const wxString &aSrcFile, const wxString &aDestFile, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, wxString *aUnsupportedToken, const wxString &aVariant, bool aDropInsteadOfApproximate)
Load a board, downgrade the copy, and write it stamped for the target.
static void dropPcbVariants(BOARD *aBoard)
static void dropGroupPlacementAreas(BOARD *aBoard)
static void dropPressFitPads(BOARD *aBoard)
static void dropPostMachining(BOARD *aBoard)
static TEXT_DOWNGRADE_PLAN planBoardText(const BOARD *aBoard, int aTargetVersion)
static constexpr int VER_FP_UNITS
static int countBarcodes(const BOARD *aBoard)
static void lowerLineEndingShape(PCB_SHAPE *aShape, int aMaxError)
static void dropSimulationExclusions(BOARD *aBoard)
static bool hasLineEndings(const BOARD_ITEM *aItem)
static int countGenerators(const BOARD *aBoard, const wxString &aType)
static constexpr int VER_GROUP_LIB_ID
static int countEllipses(const BOARD *aBoard)
static void ellipseToPolygon(PCB_SHAPE *aShape, int aMaxError)
static void dropSimulationExclusionsFp(FOOTPRINT *aFootprint)
static int countExtrudedBodies(const BOARD *aBoard)
static void lowerSkipVias(BOARD *aBoard)
static void dropPcbPoints(BOARD *aBoard)
static void visitBoardItems(const BOARD *aBoard, const std::function< void(BOARD_ITEM *)> &aVisit)
static constexpr int VER_LINE_ENDINGS
static int countViaProtection(const BOARD *aBoard)
static int countUnitInfo(const BOARD *aBoard)
static constexpr int VER_CONSTRAINTS
static int countDrawings(const BOARD *aBoard, const std::function< bool(const BOARD_ITEM *)> &aMatches)
static bool postTargetText(const wxString &aText, const BOARD *aBoard, int aTargetVersion, std::set< wxString > &aDependencies, int aDepth=0)
static void compensateImageScale(PCB_REFERENCE_IMAGE *aImage)
static void omitKnockoutTextBoxes(BOARD *aBoard)
static void dropKnockoutTextBoxesFp(FOOTPRINT *aFootprint)
static void clearLineEndings(PCB_SHAPE *aShape)
static void lowerEllipses(BOARD *aBoard)
static void dropDieDelays(BOARD *aBoard)
static constexpr int VER_PCB_POINTS
static void omitKnockoutTableCellsFp(FOOTPRINT *aFootprint)
static bool unsafeLineEndings(const PCB_SHAPE *aShape)
static std::vector< PCB_SHAPE * > barcodeToShapes(PCB_BARCODE *aBarcode, BOARD_ITEM_CONTAINER *aParent)
static constexpr int VER_BACKDRILL
static void omitBarcodes(BOARD *aBoard)
static void dropGroupDesignBlocks(BOARD *aBoard)
static int countDrillDrawings(const BOARD *aBoard)
static bool hasPostMachining(const PADSTACK &aPadstack)
static void lowerEllipsesFp(FOOTPRINT *aFootprint)
static void dropPcbPointsFp(FOOTPRINT *aFootprint)
static void lowerRoundedRectsFp(FOOTPRINT *aFootprint)
static bool isEllipse(const BOARD_ITEM *aItem)
static void lowerBarcodesFp(FOOTPRINT *aFootprint)
static void compensateReferenceImageScales(BOARD *aBoard)
static void visitFootprintItems(const FOOTPRINT *aFootprint, const std::function< void(BOARD_ITEM *)> &aVisit)
static constexpr int VER_VIA_PROTECTION
static bool isRoundedRect(const BOARD_ITEM *aItem)
DOWNGRADE_FILE_RESULT DowngradeFootprintFileToTemp(const wxString &aSrcFile, const wxString &aTmpFile, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Load one .kicad_mod, classify it against the rule table, and write the downgraded copy to aTmpFile.
static int countPcbVariants(const BOARD *aBoard)
static bool containsGroupPlacementSource(const std::string &aText)
static void applyTextPlan(const TEXT_DOWNGRADE_PLAN &aPlan)
void DowngradeBoardInPlace(BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Approximate or remove, in place, what the target cannot represent.
static int countConstraints(const BOARD *aBoard)
static void dropGroupDesignBlocksFp(FOOTPRINT *aFootprint)
static int countKnockoutTableCells(const BOARD *aBoard)
static constexpr int VER_POST_MACHINING
static int countUnreviewedTuningProperties(const BOARD *aBoard)
void DowngradeFootprintInPlace(FOOTPRINT *aFootprint, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Footprint-scoped downgrade for standalone .kicad_mod files the board pass never sees.
static int countPressFitPads(const BOARD *aBoard)
static constexpr int VER_PAD_SIM
static constexpr int VER_BARCODE
static void omitKnockoutCellContents(BOARD_ITEM *aItem)
static void dropZoneLayerPropertiesFp(FOOTPRINT *aFootprint)
static bool hasGroupPlacementLinkage(const ZONE *aZone)
static int countThievingZones(const BOARD *aBoard)
static int countKnockoutTextBoxes(const BOARD *aBoard)
static void omitPadPrimitives(FOOTPRINT *aFootprint, const std::function< bool(const BOARD_ITEM *)> &aMatches)
static constexpr int VER_CUSTOM_PROPERTIES
static void dropCustomPropertiesFp(FOOTPRINT *aFootprint)
static constexpr int VER_EXTRUDED_BODY
static int countDielectricModels(const BOARD *aBoard)
static int countRoundedRects(const BOARD *aBoard)
static void dropPadSimTypes(BOARD *aBoard)
static constexpr int VER_ROUNDED_RECT
static void omitRoundedRects(BOARD *aBoard)
static int titleFieldIndex(const wxString &aName)
static void dropGroupPlacementAreasFp(FOOTPRINT *aFootprint)
static constexpr int VER_SKIP_VIA
static constexpr DATED_TOKEN BOARD_TOKENS[]
static bool isHatchedInkShape(const BOARD_ITEM *aItem)
static void compensateReferenceImageScalesFp(FOOTPRINT *aFootprint)
static void dropDrillSymbolConfiguration(BOARD *aBoard)
static void dropGenerators(BOARD *aBoard, const std::function< bool(const PCB_GENERATOR *)> &aMatches)
wxString FindUnsupportedBoardToken(const wxString &aSerialized, const DOWNGRADE_TARGET &aTarget)
Scan serialized board text for tokens the target cannot parse.
static constexpr int VER_GROUP_PLACEMENT
static void dropKnockoutTableCellsFp(FOOTPRINT *aFootprint)
constexpr int FIRST_FP_CUSTOM_STACKUP
First board format that stores custom footprint stackups.
constexpr int FIRST_PCB_VARIANTS
First board format that stores variants.
@ BS_ITEM_TYPE_DIELECTRIC
int GetPPI() const
int GetLegacyPPI() const
Recompute the PPI the way it was computed before the pixels/cm truncation fix.
Container for design settings for a BOARD object.
std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > m_ZoneLayerProperties
DRILL_SYMBOL_PROFILE & GetDrillSymbolProfile()
BOARD_STACKUP & GetStackupDescriptor()
Abstract interface for BOARD_ITEMs capable of storing other items inside.
virtual void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false)=0
Adds an item to the container.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
void SetLocked(bool aLocked) override
Definition board_item.h:418
virtual bool IsKnockout() const
Definition board_item.h:414
bool IsLocked() const override
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
FOOTPRINT * GetParentFootprint() const
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void SetPlotOptions(const PCB_PLOT_PARAMS &aOptions)
Definition board.h:1022
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1215
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:3170
void SetVariantNames(const std::vector< wxString > &aNames)
Definition board.h:533
const std::vector< wxString > & GetVariantNames() const
Definition board.h:532
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1521
void SetNetChainColor(const wxString &aChain, const KIGFX::COLOR4D &aColor)
Per-net-chain colour override (empty COLOR4D::UNSPECIFIED = no override).
Definition board.h:1334
const GENERATORS & Generators() const
Definition board.h:484
const PCB_POINTS & Points() const
Definition board.h:498
const std::map< wxString, KIGFX::COLOR4D > & GetNetChainColors() const
Definition board.h:1348
const ZONES & Zones() const
Definition board.h:468
const GROUPS & Groups() const
The groups must maintain the following invariants.
Definition board.h:517
bool ResolveTextVar(wxString *token, int aDepth) const
Definition board.cpp:684
const std::vector< PAD * > GetPads() const
Return a reference to a list of all the pads.
Definition board.cpp:3906
TITLE_BLOCK & GetTitleBlock()
Definition board.h:1024
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:525
const FOOTPRINTS & Footprints() const
Definition board.h:464
const TRACKS & Tracks() const
Definition board.h:462
const CONSTRAINTS & Constraints() const
Geometric constraints (#2329) owned by this board.
Definition board.h:521
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1021
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:940
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
Definition board.cpp:845
PROJECT * GetProject() const
Definition board.h:775
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1298
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1182
void Remove(BOARD_ITEM *aBoardItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
Definition board.cpp:1692
bool IsItemIndexedById(const BOARD_ITEM *aItem) const
Definition board.h:1703
const DRAWINGS & Drawings() const
Definition board.h:466
What an export to a given target will keep, approximate, drop, or block.
void Add(DOWNGRADE_BUCKET aBucket, const wxString &aFeature, const wxString &aDetail=wxEmptyString, int aCount=1)
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
Grouping rules and symbol assignments, shared by reference so a chart and its map can never disagree ...
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:78
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
void ClearCustomProperties()
Definition eda_item.h:270
const KIID m_Uuid
Definition eda_item.h:599
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:115
bool HasCustomProperties() const
Definition eda_item.h:269
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
int GetEllipseMinorRadius() const
Definition eda_shape.h:395
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
void SetEndEnding(const LINE_ENDING &aEnding)
Definition eda_shape.h:181
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
virtual int GetEffectiveWidth() const
Definition eda_shape.h:164
void TransformWithLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape body shortened for line endings plus line-ending geometry to polygons.
void SetLineStyle(const LINE_STYLE aStyle)
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:404
SHAPE_T GetShape() const
Definition eda_shape.h:175
void SetStartEnding(const LINE_ENDING &aEnding)
Definition eda_shape.h:178
bool GetLineEndingEndpoints(VECTOR2I &aStartPoint, VECTOR2I &aEndPoint) const
Return the source endpoints used to place line endings.
LINE_ENDING_STYLE GetStartEndingStyle() const
Definition eda_shape.h:183
LINE_STYLE GetLineStyle() const
std::vector< VECTOR2I > GetRectCorners() const
const LINE_ENDING & GetStartEnding() const
Definition eda_shape.h:177
const LINE_ENDING & GetEndEnding() const
Definition eda_shape.h:180
LINE_ENDING_STYLE GetEndEndingStyle() const
Definition eda_shape.h:186
int GetCornerRadius() const
void SetFillMode(FILL_T aFill)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
High-level wrapper for evaluating mathematical and string expressions in wxString format.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all '@{}' expressions.
bool HasErrors() const
Check if the last evaluation had errors.
Variant information for a footprint.
Definition footprint.h:228
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:249
const CASE_INSENSITIVE_MAP< FOOTPRINT_VARIANT > & GetVariants() const
Get all variants.
Definition footprint.h:1127
ZONES & Zones()
Definition footprint.h:411
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
PCB_POINTS & Points()
Definition footprint.h:420
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:1055
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1244
CONSTRAINTS & Constraints()
Definition footprint.h:417
void ClearExtrudedBody()
Definition footprint.h:433
std::deque< PAD * > & Pads()
Definition footprint.h:405
bool ResolveTextVar(wxString *aToken, int aDepth=0) const
Resolve any references to system tokens supported by the component.
void DeleteVariant(const wxString &aVariantName)
Delete a variant by name.
JUMPER_GROUP_SET & JumperPadGroups()
Each jumper pad group is a set of pad numbers that should be treated as internally connected.
Definition footprint.h:1250
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
void SetUnitInfo(const std::vector< FP_UNIT_INFO > &aUnits)
Definition footprint.h:1024
GROUPS & Groups()
Definition footprint.h:414
DRAWINGS & GraphicalItems()
Definition footprint.h:408
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:400
Definition kiid.h:46
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
Decorative shape (arrowhead, circle, square) at the start or end of a graphic line,...
Definition line_ending.h:62
const STROKE_PARAMS & GetStroke() const
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Handle the data for a net.
Definition netinfo.h:50
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:156
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:379
void SetUnconnectedLayerMode(UNCONNECTED_LAYER_MODE aMode)
Definition padstack.h:374
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:365
UNCONNECTED_LAYER_MODE UnconnectedLayerMode() const
Definition padstack.h:373
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:362
DRILL_PROPS & Drill()
Definition padstack.h:356
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:359
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:368
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:382
Definition pad.h:61
wxString GetText() const
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE, bool ignoreLineWidth=false) const override
Convert the barcode (text + symbol shapes) to polygonal geometry suitable for filling/collision tests...
A geometric constraint between board items (issue #2329).
Abstract dimension API.
wxString GetOverrideText() const
bool GetOverrideTextEnabled() const
virtual wxString GetGeneratorType() const
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
std::unique_ptr< FOOTPRINT > ImportFootprint(const wxString &aFootprintPath, wxString &aFootprintNameOut, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a single footprint from aFootprintPath and put its name in aFootprintNameOut.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
Parameters and options when plotting/printing a board.
PLOT_FORMAT GetFormat() const
void SetFormat(PLOT_FORMAT aFormat)
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:39
Object to handle a bitmap image that can be inserted in a PCB.
REFERENCE_IMAGE & GetReferenceImage()
void SetCornerRadius(int aCornerRadius) override
void SetWidth(int aWidth) override
int GetWidth() const override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:209
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
wxString GetUnwrappedShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const
GetShownText() before it is line-broken to the cell's width, i.e.
wxString GetShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const override
Return the string actually shown after processing of the base text.
Definition pcb_text.cpp:178
const PADSTACK & Padstack() const
Definition pcb_track.h:427
VIATYPE GetViaType() const
Definition pcb_track.h:418
Writes a board or footprint in the KiCad 10.0 file format.
static constexpr int FORMAT_VERSION
void SaveFootprintFile(const wxString &aFileName, FOOTPRINT *aFootprint)
void SaveBoard(const wxString &aFileName, BOARD *aBoard)
Writes a board or footprint in the KiCad 9.0 file format.
static constexpr int FORMAT_VERSION
void SaveFootprintFile(const wxString &aFileName, FOOTPRINT *aFootprint)
void SaveBoard(const wxString &aFileName, BOARD *aBoard)
virtual bool TextVarResolver(wxString *aToken) const
Definition project.cpp:81
virtual std::map< wxString, wxString > & GetTextVars() const
Definition project.cpp:126
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
const BITMAP_BASE & GetImage() const
Get the underlying image.
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Simple container to manage line stroke parameters.
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
const wxString & GetCompany() const
Definition title_block.h:93
void SetRevision(const wxString &aRevision)
Definition title_block.h:78
void SetComment(int aIdx, const wxString &aComment)
Definition title_block.h:98
bool TextVarResolver(wxString *aToken, const PROJECT *aProject, RESOLUTION_CONTEXT aContext) const
const wxString & GetRevision() const
Definition title_block.h:83
void SetTitle(const wxString &aTitle)
Definition title_block.h:55
const wxString & GetDate() const
Definition title_block.h:73
const wxString & GetComment(int aIdx) const
void SetCompany(const wxString &aCompany)
Definition title_block.h:88
const wxString & GetTitle() const
Definition title_block.h:60
void SetDate(const wxString &aDate)
Set the date field, and defaults to the current time and date.
Definition title_block.h:68
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
wxString GetPlacementAreaSource() const
Definition zone.h:837
void SetPlacementAreaSource(const wxString &aSource)
Definition zone.h:838
PLACEMENT_SOURCE_T GetPlacementAreaSourceType() const
Definition zone.h:839
void SetPlacementAreaSourceType(PLACEMENT_SOURCE_T aType)
Definition zone.h:840
bool GetPlacementAreaEnabled() const
Definition zone.h:834
void SetPlacementAreaEnabled(bool aEnabled)
Definition zone.h:835
void FinalizeTextVarExpansion(wxString &aText, RESOLUTION_CONTEXT aContext)
Definition common.cpp:88
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
std::vector< TEXT_VAR_REF_KEY > ExtractTextVarReferences(const wxString &aSource)
Lex-scan aSource and return every ${...} reference that appears, without resolving.
Definition common.cpp:468
@ INTERNAL
Definition common.h:94
DOWNGRADE_BUCKET
What happens to a feature when we export to an older KiCad.
@ BLOCK
Cannot be represented at all, export to this target is not possible.
@ LOWER
Approximated down to a simpler equivalent.
@ DROP
No equivalent, left out.
wxString FindUnsupportedToken(const wxString &aSerialized, const DATED_TOKEN *aNodes, size_t aNodeCount, const DATED_TOKEN *aValues, size_t aValueCount, int aTargetVersion)
Scan serialized s-expression text against dated node and value denylists.
std::string StripSexprStrings(const std::string &aText)
Drop quoted strings so a token inside a text value cannot cause a false refusal.
DOWNGRADE_FILE_RESULT
What a per-file converter did with one library file.
@ SKIPPED
Not a convertible file, leave it alone.
@ REFUSED
The target cannot represent this file.
@ CONVERTED
The temp file holds the downgraded copy.
bool VerifyDowngradedFile(const wxString &aPath, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, wxString *aForbidden=nullptr)
Read back a downgraded output and require the target stamp plus a clean token scan.
#define _(s)
FILL_T
Definition eda_fill.h:29
@ NO_FILL
Definition eda_fill.h:30
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
@ RECURSE
Definition eda_item.h:51
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ EXPAND_INNER_LAYERS
The 'normal' stackup handling, where there is a single inner layer (In1) and rule areas using it expa...
Definition footprint.h:161
static const std::string ProjectFileExtension
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ User_45
Definition layer_ids.h:164
@ User_10
Definition layer_ids.h:129
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
@ PRESSFIT
a PTH with a hole diameter with tight tolerances for press fit pin
Definition padstack.h:122
@ NONE
no special fabrication property
Definition padstack.h:114
BARCODE class definition.
Class to handle a set of BOARD_ITEMs.
#define FIRST_FP_AFFINE_TRANSFORM
First version that stores footprint children in library frame.
#define SEXPR_BOARD_FILE_VERSION
Current s-expression file format version. 2 was the last legacy format version.
static int countCustomProperties(const SCH_SCREEN *aScreen)
static void lowerEllipses(SCH_SCREEN *aScreen)
static void omitEllipses(SCH_SCREEN *aScreen)
static bool isEllipse(const SCH_ITEM *aItem)
static void lowerRoundedRects(SCH_SCREEN *aScreen)
static bool isRoundedRect(const SCH_ITEM *aItem)
static int countRoundedRects(const SCH_SCREEN *aScreen)
static void omitRoundedRects(SCH_SCREEN *aScreen)
static void dropCustomProperties(SCH_SCREEN *aScreen)
static int countEllipses(const SCH_SCREEN *aScreen)
std::function< void(BOARD *)> m_omit
std::function< void(BOARD *)> m_apply
DOWNGRADE_BUCKET m_bucket
std::function< void(FOOTPRINT *)> m_applyFp
Footprint-scoped part, for .kicad_mod files.
std::function< void(FOOTPRINT *)> m_omitFp
wxString m_omitDetail
std::function< int(const BOARD *)> m_count
bool m_keepUnderOmitPolicy
An approximation the omission preference must not remove, because removing it would change the board ...
A token dated by the format that introduced it, for the fail-closed downgrade gates.
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:273
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:279
std::optional< bool > is_filled
True if the drill hole should be filled completely.
Definition padstack.h:278
std::optional< bool > has_covering
True if the pad on this side should have covering.
Definition padstack.h:261
std::optional< bool > has_plugging
True if the drill hole should be plugged on this side.
Definition padstack.h:262
std::vector< std::pair< int, wxString > > m_titleReplacements
std::map< EDA_TEXT *, wxString > m_replacements
netlist clear()
std::string path
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DRILL_MAP_T
class PCB_DRILL_MAP, drill symbols drawn at the holes
Definition typeinfo.h:240
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:99
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97
@ PCB_DRILL_CHART_T
class PCB_DRILL_CHART, a live drill chart derived from PCB_TABLE
Definition typeinfo.h:239
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
Definition of file extensions used in Kicad.
Class ZONE_SETTINGS used to handle zones parameters in dialogs.