KiCad PCB EDA Suite
Loading...
Searching...
No Matches
import_fabmaster.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 (C) 2020 BeagleBoard Foundation
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Author: Seth Hillbrand <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 3
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include "import_fabmaster.h"
23
24#include <algorithm>
25#include <array>
26#include <iostream>
27#include <fstream>
28#include <map>
29#include <memory>
30#include <string>
31#include <sstream>
32#include <vector>
33#include <utility>
34
35#include <wx/log.h>
36
37#include <board.h>
39#include <board_item.h>
40#include <footprint.h>
41#include <trace_helpers.h>
42#include <pad.h>
43#include <padstack.h>
44#include <pcb_group.h>
45#include <pcb_shape.h>
46#include <pcb_text.h>
47#include <pcb_track.h>
48#include <zone.h>
49#include <zone_utils.h>
50#include <common.h>
51#include <geometry/shape_arc.h>
53#include <string_utils.h>
54#include <progress_reporter.h>
55#include <math/util.h>
56
57#include <wx/filename.h>
58
59
60
61
63{
64 const unsigned PROGRESS_DELTA = 250;
65
67 {
68 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
69 {
70 m_progressReporter->SetCurrentProgress( ( (double) m_doneCount )
71 / std::max( 1U, m_totalCount ) );
72
73 if( !m_progressReporter->KeepRefreshing() )
74 THROW_IO_ERROR( _( "File import canceled by user." ) );
75
77 }
78 }
79}
80
81
82double FABMASTER::readDouble( const std::string& aStr ) const
83{
84 // This is bad, but at least don't return uninitialized data
85 wxCHECK_MSG( !aStr.empty(), 0.0, "Empty string passed to readDouble" );
86
87 std::istringstream istr( aStr );
88 istr.imbue( std::locale::classic() );
89
90 double doubleValue;
91 istr >> doubleValue;
92 return doubleValue;
93}
94
95
96int FABMASTER::readInt( const std::string& aStr ) const
97{
98 // This is bad, but at least don't return uninitialized data
99 wxCHECK_MSG( !aStr.empty(), 0, "Empty string passed to readInt" );
100
101 std::istringstream istr( aStr );
102 istr.imbue( std::locale::classic() );
103
104 int intValue;
105 istr >> intValue;
106 return intValue;
107}
108
109
110bool FABMASTER::Read( const std::string& aFile )
111{
112 std::ifstream ifs( aFile, std::ios::in | std::ios::binary );
113
114 if( !ifs.is_open() )
115 return false;
116
117 m_filename = aFile;
118
119 // Read/ignore all bytes in the file to find the size and then go back to the beginning
120 ifs.ignore( std::numeric_limits<std::streamsize>::max() );
121 std::streamsize length = ifs.gcount();
122 ifs.clear();
123 ifs.seekg( 0, std::ios_base::beg );
124
125 std::string buffer( std::istreambuf_iterator<char>{ ifs }, {} );
126
127 std::vector < std::string > row;
128
129 // Reserve an estimate of the number of rows to prevent continual re-allocation
130 // crashing (Looking at you MSVC)
131 row.reserve( length / 100 );
132 std::string cell;
133 cell.reserve( 100 );
134
135 bool quoted = false;
136
137 for( auto& ch : buffer )
138 {
139 switch( ch )
140 {
141 case '"':
142
143 if( cell.empty() || cell[0] == '"' )
144 quoted = !quoted;
145
146 cell += ch;
147 break;
148
149 case '!':
150 if( !quoted )
151 {
152 row.push_back( cell );
153 cell.clear();
154 }
155 else
156 cell += ch;
157
158 break;
159
160 case '\n':
161
163 if( !cell.empty() )
164 row.push_back( cell );
165
166 cell.clear();
167 rows.push_back( row );
168 row.clear();
169 quoted = false;
170 break;
171
172 case '\r':
173 break;
174
175 default:
176 cell += std::toupper( ch );
177 }
178 }
179
180 // Handle last line without linebreak
181 if( !cell.empty() || !row.empty() )
182 {
183 row.push_back( cell );
184 cell.clear();
185 rows.push_back( row );
186 row.clear();
187 }
188
189 return true;
190}
191
192
194{
195 single_row row;
196
197 try
198 {
199 row = rows.at( aOffset );
200 }
201 catch( std::out_of_range& )
202 {
203 return UNKNOWN_EXTRACT;
204 }
205
206 if( row.size() < 3 )
207 return UNKNOWN_EXTRACT;
208
209 if( row[0].back() != 'A' )
210 return UNKNOWN_EXTRACT;
211
212 std::string row1 = row[1];
213 std::string row2 = row[2];
214 std::string row3{};
215
217 // some do not
218 std::erase_if( row1, []( char c ){ return c == '_'; } );
219 std::erase_if( row2, []( char c ){ return c == '_'; } );
220
221 if( row.size() > 3 )
222 {
223 row3 = row[3];
224 std::erase_if( row3, []( char c ){ return c == '_'; } );
225 }
226
227 if( row1 == "REFDES" && row2 == "COMPCLASS" )
228 return EXTRACT_REFDES;
229
230 if( row1 == "NETNAME" && row2 == "REFDES" )
231 return EXTRACT_NETS;
232
233 if( row1 == "CLASS" && row2 == "SUBCLASS" && row3.empty() )
235
236 if( row1 == "GRAPHICDATANAME" && row2 == "GRAPHICDATANUMBER" )
237 return EXTRACT_GRAPHICS;
238
239 if( row1 == "CLASS" && row2 == "SUBCLASS" && row3 == "GRAPHICDATANAME" )
240 return EXTRACT_TRACES;
241
242 if( row1 == "SYMNAME" && row2 == "PINNAME" )
244
245 if( row1 == "SYMNAME" && row2 == "SYMMIRROR" && row3 == "PINNAME" )
246 return EXTRACT_PINS;
247
248 if( row1 == "VIAX" && row2 == "VIAY" )
249 return EXTRACT_VIAS;
250
251 if( row1 == "SUBCLASS" && row2 == "PADSHAPENAME" )
252 return EXTRACT_PAD_SHAPES;
253
254 if( row1 == "PADNAME" )
255 return EXTRACT_PADSTACKS;
256
257 if( row1 == "LAYERSORT" )
258 return EXTRACT_FULL_LAYERS;
259
260 reportError( _( "Unknown FABMASTER section %s:%s at row %zu." ),
261 row1.c_str(),
262 row2.c_str(),
263 aOffset );
264 return UNKNOWN_EXTRACT;
265
266}
267
268
269double FABMASTER::processScaleFactor( size_t aRow )
270{
271 double retval = 0.0;
272
273 if( aRow >= rows.size() )
274 return -1.0;
275
276 if( rows[aRow].size() < 11 )
277 {
278 reportError( _( "Invalid row size in J row %zu. Expecting 11 elements but found %zu." ),
279 aRow,
280 rows[aRow].size() );
281 return -1.0;
282 }
283
284 for( int i = 7; i < 10 && retval < 1.0; ++i )
285 {
286 std::string units = rows[aRow][i];
287 std::transform(units.begin(), units.end(),units.begin(), ::toupper);
288
289 if( units == "MILS" )
290 retval = pcbIUScale.IU_PER_MILS;
291 else if( units == "MILLIMETERS" )
292 retval = pcbIUScale.IU_PER_MM;
293 else if( units == "MICRONS" )
294 retval = pcbIUScale.IU_PER_MM * 10.0;
295 else if( units == "INCHES" )
296 retval = pcbIUScale.IU_PER_MILS * 1000.0;
297 }
298
299 if( retval < 1.0 )
300 {
301 reportError( _( "Could not find units value, defaulting to mils." ) );
302 retval = pcbIUScale.IU_PER_MILS;
303 }
304
305 return retval;
306}
307
308
309int FABMASTER::getColFromName( size_t aRow, const std::string& aStr )
310{
311 if( aRow >= rows.size() )
312 return -1;
313
314 std::vector<std::string> header = rows[aRow];
315
316 for( size_t i = 0; i < header.size(); i++ )
317 {
320 std::erase_if( header[i], []( const char c ) { return c == '_'; } );
321
322 if( header[i] == aStr )
323 return i;
324 }
325
326 THROW_IO_ERRORF( _( "Could not find column label %s." ), aStr.c_str() );
327 return -1;
328}
329
330
331PCB_LAYER_ID FABMASTER::getLayer( const std::string& aLayerName )
332{
333 const auto& kicad_layer = layers.find( aLayerName);
334
335 if( kicad_layer == layers.end() )
336 return UNDEFINED_LAYER;
337 else
338 return static_cast<PCB_LAYER_ID>( kicad_layer->second.layerid );
339}
340
341
343{
344 size_t rownum = aRow + 2;
345
346 if( rownum >= rows.size() )
347 return -1;
348
349 const single_row& header = rows[aRow];
350
351 int pad_num_col = getColFromName( aRow, "RECNUMBER" );
352 int pad_lay_col = getColFromName( aRow, "LAYER" );
353
354 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
355 {
356 const single_row& row = rows[rownum];
357
358 if( row.size() != header.size() )
359 {
360 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
361 rownum,
362 header.size(),
363 row.size() );
364 continue;
365 }
366
367 auto& pad_num = row[pad_num_col];
368 auto& pad_layer = row[pad_lay_col];
369
370 // This layer setting seems to be unused
371 if( pad_layer == "INTERNAL_PAD_DEF" || pad_layer == "internal_pad_def" )
372 continue;
373
374 // Skip the technical layers
375 if( pad_layer[0] == '~' )
376 break;
377
378 auto result = layers.emplace( pad_layer, FABMASTER_LAYER{} );
379 FABMASTER_LAYER& layer = result.first->second;
380
382 if( layer.id == 0 )
383 {
384 layer.name = pad_layer;
385 layer.id = readInt( pad_num );
386 layer.conductive = true;
387 }
388 }
389
390 return 0;
391}
392
393
400size_t FABMASTER::processPadStacks( size_t aRow )
401{
402 size_t rownum = aRow + 2;
403
404 if( rownum >= rows.size() )
405 return -1;
406
407 const single_row& header = rows[aRow];
408 double scale_factor = processScaleFactor( aRow + 1 );
409
410 if( scale_factor <= 0.0 )
411 return -1;
412
413 int pad_name_col = getColFromName( aRow, "PADNAME" );
414 int pad_num_col = getColFromName( aRow, "RECNUMBER" );
415 int pad_lay_col = getColFromName( aRow, "LAYER" );
416 int pad_via_col = getColFromName( aRow, "VIAFLAG" );
417 int pad_shape_col = getColFromName( aRow, "PADSHAPE1" );
418 int pad_width_col = getColFromName( aRow, "PADWIDTH" );
419 int pad_height_col = getColFromName( aRow, "PADHGHT" );
420 int pad_xoff_col = getColFromName( aRow, "PADXOFF" );
421 int pad_yoff_col = getColFromName( aRow, "PADYOFF" );
422 int pad_shape_name_col = getColFromName( aRow, "PADSHAPENAME" );
423
424 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
425 {
426 const single_row& row = rows[rownum];
427 FM_PAD* pad;
428
429 if( row.size() != header.size() )
430 {
431 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
432 rownum,
433 header.size(),
434 row.size() );
435 continue;
436 }
437
438 auto& pad_name = row[pad_name_col];
439 auto& pad_num = row[pad_num_col];
440 auto& pad_layer = row[pad_lay_col];
441 auto& pad_is_via = row[pad_via_col];
442 auto& pad_shape = row[pad_shape_col];
443 auto& pad_width = row[pad_width_col];
444 auto& pad_height = row[pad_height_col];
445 auto& pad_xoff = row[pad_xoff_col];
446 auto& pad_yoff = row[pad_yoff_col];
447 auto& pad_shapename = row[pad_shape_name_col];
448
449 // This layer setting seems to be unused
450 if( pad_layer == "INTERNAL_PAD_DEF" || pad_layer == "internal_pad_def" )
451 continue;
452
453 int recnum = KiROUND( readDouble( pad_num ) );
454
455 auto new_pad = pads.find( pad_name );
456
457 if( new_pad != pads.end() )
458 pad = &new_pad->second;
459 else
460 {
461 pads[pad_name] = FM_PAD();
462 pad = &pads[pad_name];
463 pad->name = pad_name;
464 }
465
467 if( pad_layer == "~DRILL" )
468 {
469 int drill_hit;
470 int drill_x;
471 int drill_y;
472
473 try
474 {
475 drill_hit = KiROUND( std::fabs( readDouble( pad_shape ) * scale_factor ) );
476 drill_x = KiROUND( std::fabs( readDouble( pad_width ) * scale_factor ) );
477 drill_y = KiROUND( std::fabs( readDouble( pad_height ) * scale_factor ) );
478 }
479 catch( ... )
480 {
481 reportError( _( "Expecting drill size value but found %s!%s!%s in row %zu." ),
482 pad_shape.c_str(),
483 pad_width.c_str(),
484 pad_height.c_str(),
485 rownum );
486 continue;
487 }
488
489 if( drill_hit == 0 )
490 {
491 pad->drill = false;
492 continue;
493 }
494
495 pad->drill = true;
496
497 // This is to account for broken fabmaster outputs where circle drill hits don't
498 // actually get the drill hit value.
499 if( drill_x == drill_y )
500 {
501 pad->drill_size_x = drill_hit;
502 pad->drill_size_y = drill_hit;
503 }
504 else
505 {
506 pad->drill_size_x = drill_x;
507 pad->drill_size_y = drill_y;
508 }
509
510 if( !pad_shapename.empty() && pad_shapename[0] == 'P' )
511 pad->plated = true;
512
513 continue;
514 }
515
516 if( pad_shape.empty() )
517 continue;
518
519 double w;
520 double h;
521
522 try
523 {
524 w = readDouble( pad_width ) * scale_factor;
525 h = readDouble( pad_height ) * scale_factor;
526 }
527 catch( ... )
528 {
529 reportError( _( "Expecting pad size values but found %s : %s in row %zu." ),
530 pad_width.c_str(),
531 pad_height.c_str(),
532 rownum );
533 continue;
534 }
535
536 auto layer = layers.find( pad_layer );
537
538 if( w > 0.0 && layer != layers.end() && layer->second.conductive )
539 pad->copper_layers.insert( pad_layer );
540
541 if( w <= 0.0 )
542 continue;
543
544 if( layer != layers.end() )
545 {
546 if( layer->second.layerid == F_Cu )
547 pad->top = true;
548 else if( layer->second.layerid == B_Cu )
549 pad->bottom = true;
550 }
551
552 if( w > std::numeric_limits<int>::max() || h > std::numeric_limits<int>::max() )
553 {
554 reportError( _( "Invalid pad size in row %zu." ), rownum );
555 continue;
556 }
557
558 if( pad_layer == "~TSM" || pad_layer == "~BSM" )
559 {
560 if( w > 0.0 && h > 0.0 )
561 {
562 pad->mask_width = KiROUND( w );
563 pad->mask_height = KiROUND( h );
564 }
565 continue;
566 }
567
568 if( pad_layer == "~TSP" || pad_layer == "~BSP" )
569 {
570 if( w > 0.0 && h > 0.0 )
571 {
572 pad->paste_width = KiROUND( w );
573 pad->paste_height = KiROUND( h );
574 }
575 continue;
576 }
577
579 if( pad_layer[0] == '~' )
580 continue;
581
582 int layer_x_offset = 0;
583 int layer_y_offset = 0;
584
585 try
586 {
587 layer_x_offset = KiROUND( readDouble( pad_xoff ) * scale_factor );
588 layer_y_offset = -KiROUND( readDouble( pad_yoff ) * scale_factor );
589 }
590 catch( ... )
591 {
592 reportError( _( "Expecting pad offset values but found %s:%s in row %zu." ),
593 pad_xoff.c_str(),
594 pad_yoff.c_str(),
595 rownum );
596 continue;
597 }
598
599 if( recnum == 1 )
600 {
601 pad->x_offset = layer_x_offset;
602 pad->y_offset = layer_y_offset;
603 }
604
605 if( w > 0.0 && h > 0.0 )
606 {
607 FM_PAD_LAYER layer_data;
608 layer_data.width = KiROUND( w );
609 layer_data.height = KiROUND( h );
610 layer_data.x_offset = layer_x_offset;
611 layer_data.y_offset = layer_y_offset;
612
613 if( pad_shape == "CIRCLE" )
614 {
615 layer_data.height = layer_data.width;
616 layer_data.shape = PAD_SHAPE::CIRCLE;
617 }
618 else if( pad_shape == "RECTANGLE" )
619 {
620 layer_data.shape = PAD_SHAPE::RECTANGLE;
621 }
622 else if( pad_shape == "ROUNDED_RECT" )
623 {
624 layer_data.shape = PAD_SHAPE::ROUNDRECT;
625 }
626 else if( pad_shape == "SQUARE" )
627 {
628 layer_data.shape = PAD_SHAPE::RECTANGLE;
629 layer_data.height = layer_data.width;
630 }
631 else if( pad_shape == "OBLONG" || pad_shape == "OBLONG_X"
632 || pad_shape == "OBLONG_Y" )
633 {
634 layer_data.shape = PAD_SHAPE::OVAL;
635 }
636 else if( pad_shape == "OCTAGON" )
637 {
638 layer_data.shape = PAD_SHAPE::RECTANGLE;
639 layer_data.is_octogon = true;
640 }
641 else if( pad_shape == "SHAPE" )
642 {
643 layer_data.shape = PAD_SHAPE::CUSTOM;
644 layer_data.custom_name = pad_shapename;
645 }
646 else
647 {
648 reportError( _( "Unknown pad shape name '%s' on layer '%s' in row %zu." ),
649 pad_shape.c_str(),
650 pad_layer.c_str(),
651 rownum );
652 continue;
653 }
654
655 pad->layer_shapes[pad_layer] = layer_data;
656
657 if( recnum == 1 )
658 {
659 pad->width = layer_data.width;
660 pad->height = layer_data.height;
661 pad->shape = layer_data.shape;
662 pad->is_octogon = layer_data.is_octogon;
663 pad->via = pad_is_via.empty()
664 || std::toupper( static_cast<unsigned char>( pad_is_via[0] ) ) != 'V';
665
666 if( layer_data.shape == PAD_SHAPE::CUSTOM )
667 pad->custom_name = pad_shapename;
668 }
669 }
670 }
671
672 return rownum - aRow;
673}
674
675
677{
678 size_t rownum = aRow + 2;
679
680 if( rownum >= rows.size() )
681 return -1;
682
683 auto& header = rows[aRow];
684 double scale_factor = processScaleFactor( aRow + 1 );
685
686 if( scale_factor <= 0.0 )
687 return -1;
688
689 int layer_class_col = getColFromName( aRow, "CLASS" );
690 int layer_subclass_col = getColFromName( aRow, "SUBCLASS" );
691
692 if( layer_class_col < 0 || layer_subclass_col < 0 )
693 return -1;
694
695 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
696 {
697 const single_row& row = rows[rownum];
698
699 if( row.size() != header.size() )
700 {
701 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
702 rownum,
703 header.size(),
704 row.size() );
705 continue;
706 }
707
708 auto result = layers.emplace( row[layer_subclass_col], FABMASTER_LAYER{} );
709 FABMASTER_LAYER& layer = result.first->second;
710
711 layer.name = row[layer_subclass_col];
712 layer.positive = true;
713 layer.conductive = false;
714
715 if( row[layer_class_col] == "ANTI ETCH" )
716 {
717 layer.positive = false;
718 layer.conductive = true;
719 }
720 else if( row[layer_class_col] == "ETCH" )
721 {
722 layer.conductive = true;
723 }
724 }
725
726 return rownum - aRow;
727}
728
729
731{
732 std::vector<std::pair<std::string, int>> extra_layers
733 {
734 { "ASSEMBLY_TOP", F_Fab },
735 { "ASSEMBLY_BOTTOM", B_Fab },
736 { "PLACE_BOUND_TOP", F_CrtYd },
737 { "PLACE_BOUND_BOTTOM", B_CrtYd },
738 };
739
740 std::vector<FABMASTER_LAYER*> layer_order;
741
742 int next_user_layer = User_1;
743
744 for( auto& el : layers )
745 {
746 FABMASTER_LAYER& layer = el.second;
748
749 if( layer.conductive )
750 {
751 layer_order.push_back( &layer );
752 }
753 else if( ( layer.name.find( "SILK" ) != std::string::npos
754 && layer.name.find( "AUTOSILK" )
755 == std::string::npos ) // Skip the autosilk layer
756 || layer.name.find( "DISPLAY" ) != std::string::npos )
757 {
758 if( layer.name.find( "B" ) != std::string::npos )
759 layer.layerid = B_SilkS;
760 else
761 layer.layerid = F_SilkS;
762 }
763 else if( layer.name.find( "MASK" ) != std::string::npos ||
764 layer.name.find( "MSK" ) != std::string::npos )
765 {
766 if( layer.name.find( "B" ) != std::string::npos )
767 layer.layerid = B_Mask;
768 else
769 layer.layerid = F_Mask;
770 }
771 else if( layer.name.find( "PAST" ) != std::string::npos )
772 {
773 if( layer.name.find( "B" ) != std::string::npos )
774 layer.layerid = B_Paste;
775 else
776 layer.layerid = F_Paste;
777 }
778 else if( layer.name.find( "NCLEGEND" ) != std::string::npos )
779 {
780 layer.layerid = Dwgs_User;
781 }
782 else
783 {
784 // Try to gather as many other layers into user layers as possible
785
786 // Skip ones that seem like a waste of good layers
787 if( layer.name.find( "AUTOSILK" ) == std::string::npos )
788 {
789 if( next_user_layer <= User_9 )
790 {
791 // Assign the mapping
792 layer.layerid = next_user_layer;
793 next_user_layer += 2;
794 }
795 else
796 {
797 // Out of additional layers
798 // For now, drop it, but maybr we could gather onto some other layer.
799 // Or implement a proper layer remapper.
800 layer.disable = true;
801 reportWarning( _( "No user layer to put layer %s" ), layer.name );
802 }
803 }
804 }
805 }
806
807 std::sort( layer_order.begin(), layer_order.end(), FABMASTER_LAYER::BY_ID() );
808
809 for( size_t layeri = 0; layeri < layer_order.size(); ++layeri )
810 {
811 FABMASTER_LAYER* layer = layer_order[layeri];
812 if( layeri == 0 )
813 layer->layerid = F_Cu;
814 else if( layeri == layer_order.size() - 1 )
815 layer->layerid = B_Cu;
816 else
817 layer->layerid = layeri * 2 + 2;
818 }
819
820 for( auto& new_pair : extra_layers )
821 {
822 FABMASTER_LAYER new_layer;
823
824 new_layer.name = new_pair.first;
825 new_layer.layerid = new_pair.second;
826 new_layer.conductive = false;
827
828 auto result = layers.emplace( new_pair.first, new_layer );
829
830 if( !result.second )
831 {
832 result.first->second.layerid = new_pair.second;
833 result.first->second.disable = false;
834 }
835 }
836
837 for( const auto& [layer_name, fabmaster_layer] : layers )
838 {
839 wxLogTrace( traceFabmaster, wxT( "Layer %s -> KiCad layer %d" ), layer_name,
840 fabmaster_layer.layerid );
841 }
842
843 return true;
844}
845
846
852size_t FABMASTER::processLayers( size_t aRow )
853{
854 size_t rownum = aRow + 2;
855
856 if( rownum >= rows.size() )
857 return -1;
858
859 auto& header = rows[aRow];
860 double scale_factor = processScaleFactor( aRow + 1 );
861
862 if( scale_factor <= 0.0 )
863 return -1;
864
865 int layer_sort_col = getColFromName( aRow, "LAYERSORT" );
866 int layer_subclass_col = getColFromName( aRow, "LAYERSUBCLASS" );
867 int layer_art_col = getColFromName( aRow, "LAYERARTWORK" );
868 int layer_use_col = getColFromName( aRow, "LAYERUSE" );
869 int layer_cond_col = getColFromName( aRow, "LAYERCONDUCTOR" );
870 int layer_er_col = getColFromName( aRow, "LAYERDIELECTRICCONSTANT" );
871 int layer_rho_col = getColFromName( aRow, "LAYERELECTRICALCONDUCTIVITY" );
872 int layer_mat_col = getColFromName( aRow, "LAYERMATERIAL" );
873
874 if( layer_sort_col < 0 || layer_subclass_col < 0 || layer_art_col < 0 || layer_use_col < 0
875 || layer_cond_col < 0 || layer_er_col < 0 || layer_rho_col < 0 || layer_mat_col < 0 )
876 return -1;
877
878 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
879 {
880 const single_row& row = rows[rownum];
881
882 if( row.size() != header.size() )
883 {
884 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
885 rownum,
886 header.size(),
887 row.size() );
888 continue;
889 }
890
891 auto& layer_sort = row[layer_sort_col];
892 auto& layer_subclass = row[layer_subclass_col];
893 auto& layer_art = row[layer_art_col];
894 auto& layer_cond = row[layer_cond_col];
895 auto& layer_mat = row[layer_mat_col];
896
897 if( layer_mat == "AIR" )
898 continue;
899
900 FABMASTER_LAYER layer;
901
902 if( layer_subclass.empty() )
903 {
904 if( layer_cond != "NO" )
905 layer.name = "In.Cu" + layer_sort;
906 else
907 layer.name = "Dielectric" + layer_sort;
908 }
909
910 layer.positive = ( layer_art != "NEGATIVE" );
911
912 layers.emplace( layer.name, layer );
913 }
914
915 return rownum - aRow;
916}
917
918
924size_t FABMASTER::processCustomPads( size_t aRow )
925{
926 size_t rownum = aRow + 2;
927
928 if( rownum >= rows.size() )
929 return -1;
930
931 auto& header = rows[aRow];
932 double scale_factor = processScaleFactor( aRow + 1 );
933
934 if( scale_factor <= 0.0 )
935 return -1;
936
937 int pad_subclass_col = getColFromName( aRow, "SUBCLASS" );
938 int pad_shape_name_col = getColFromName( aRow, "PADSHAPENAME" );
939 int pad_grdata_name_col = getColFromName( aRow, "GRAPHICDATANAME" );
940 int pad_grdata_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" );
941 int pad_record_tag_col = getColFromName( aRow, "RECORDTAG" );
942 int pad_grdata1_col = getColFromName( aRow, "GRAPHICDATA1" );
943 int pad_grdata2_col = getColFromName( aRow, "GRAPHICDATA2" );
944 int pad_grdata3_col = getColFromName( aRow, "GRAPHICDATA3" );
945 int pad_grdata4_col = getColFromName( aRow, "GRAPHICDATA4" );
946 int pad_grdata5_col = getColFromName( aRow, "GRAPHICDATA5" );
947 int pad_grdata6_col = getColFromName( aRow, "GRAPHICDATA6" );
948 int pad_grdata7_col = getColFromName( aRow, "GRAPHICDATA7" );
949 int pad_grdata8_col = getColFromName( aRow, "GRAPHICDATA8" );
950 int pad_grdata9_col = getColFromName( aRow, "GRAPHICDATA9" );
951 int pad_stack_name_col = getColFromName( aRow, "PADSTACKNAME" );
952 int pad_refdes_col = getColFromName( aRow, "REFDES" );
953 int pad_pin_num_col = getColFromName( aRow, "PINNUMBER" );
954
955 if( pad_subclass_col < 0 || pad_shape_name_col < 0 || pad_grdata1_col < 0 || pad_grdata2_col < 0
956 || pad_grdata3_col < 0 || pad_grdata4_col < 0 || pad_grdata5_col < 0
957 || pad_grdata6_col < 0 || pad_grdata7_col < 0 || pad_grdata8_col < 0
958 || pad_grdata9_col < 0 || pad_stack_name_col < 0 || pad_refdes_col < 0
959 || pad_pin_num_col < 0 )
960 return -1;
961
962 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
963 {
964 const single_row& row = rows[rownum];
965
966 if( row.size() != header.size() )
967 {
968 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
969 rownum,
970 header.size(),
971 row.size() );
972
973 continue;
974 }
975
976 auto& pad_layer = row[pad_subclass_col];
977 auto pad_shape_name = row[pad_shape_name_col];
978 auto& pad_record_tag = row[pad_record_tag_col];
979
980 GRAPHIC_DATA gr_data;
981 gr_data.graphic_dataname = row[pad_grdata_name_col];
982 gr_data.graphic_datanum = row[pad_grdata_num_col];
983 gr_data.graphic_data1 = row[pad_grdata1_col];
984 gr_data.graphic_data2 = row[pad_grdata2_col];
985 gr_data.graphic_data3 = row[pad_grdata3_col];
986 gr_data.graphic_data4 = row[pad_grdata4_col];
987 gr_data.graphic_data5 = row[pad_grdata5_col];
988 gr_data.graphic_data6 = row[pad_grdata6_col];
989 gr_data.graphic_data7 = row[pad_grdata7_col];
990 gr_data.graphic_data8 = row[pad_grdata8_col];
991 gr_data.graphic_data9 = row[pad_grdata9_col];
992
993 auto& pad_stack_name = row[pad_stack_name_col];
994 auto& pad_refdes = row[pad_refdes_col];
995 auto& pad_pin_num = row[pad_pin_num_col];
996
997 // N.B. We get the FIGSHAPE records as "FIG_SHAPE name". We only want "name"
998 // and we don't process other pad shape records
999 std::string prefix( "FIG_SHAPE " );
1000
1001 if( pad_shape_name.length() <= prefix.length()
1002 || !std::equal( prefix.begin(), prefix.end(), pad_shape_name.begin() ) )
1003 {
1004 continue;
1005 }
1006
1007 // Custom pads are a series of records with the same record ID but incrementing
1008 // Sequence numbers.
1009 int id = -1;
1010 int seq = -1;
1011
1012 if( std::sscanf( pad_record_tag.c_str(), "%d %d", &id, &seq ) != 2 )
1013 {
1014 reportError( _( "Invalid format for id string '%s' in custom pad row %zu." ),
1015 pad_record_tag.c_str(),
1016 rownum );
1017 continue;
1018 }
1019
1020 auto name = pad_shape_name.substr( prefix.length() );
1021 name += "_" + pad_refdes + "_" + pad_pin_num;
1022 auto ret = pad_shapes.emplace( name, FABMASTER_PAD_SHAPE{} );
1023
1024 auto& custom_pad = ret.first->second;
1025
1026 // If we were able to insert the pad name, then we need to initialize the
1027 // record
1028 if( ret.second )
1029 {
1030 custom_pad.name = name;
1031 custom_pad.padstack = pad_stack_name;
1032 custom_pad.pinnum = pad_pin_num;
1033 custom_pad.refdes = pad_refdes;
1034 }
1035
1036 // At this point we extract the individual graphical elements for processing the complex
1037 // pad. The coordinates are in board origin format, so we'll need to fix the offset later
1038 // when we assign them to the modules.
1039
1040 auto gr_item = std::unique_ptr<GRAPHIC_ITEM>( processGraphic( gr_data, scale_factor ) );
1041
1042 if( gr_item )
1043 {
1044 gr_item->layer = pad_layer;
1045 gr_item->refdes = pad_refdes;
1046 gr_item->seq = seq;
1047 gr_item->subseq = 0;
1048
1049 // emplace may fail here, in which case, it returns the correct position to use for
1050 // the existing map
1051 auto pad_it = custom_pad.elements.emplace( id, graphic_element{} );
1052 auto retval = pad_it.first->second.insert( std::move(gr_item ) );
1053
1054 if( !retval.second )
1055 {
1056 reportError( _( "Could not insert graphical item %d into padstack '%s'." ),
1057 seq,
1058 pad_stack_name.c_str() );
1059 }
1060 }
1061 else
1062 {
1063 reportError( _( "Unrecognized pad shape primitive '%s' in row %zu." ),
1064 gr_data.graphic_dataname,
1065 rownum );
1066 }
1067 }
1068
1069 return rownum - aRow;
1070}
1071
1072
1074 double aScale )
1075{
1076 GRAPHIC_LINE* new_line = new GRAPHIC_LINE ;
1077
1078 new_line->shape = GR_SHAPE_LINE;
1079 new_line->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1080 new_line->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1081 new_line->end_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1082 new_line->end_y = -KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1083 new_line->width = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1084
1085 return new_line;
1086}
1087
1088
1090{
1091 GRAPHIC_ARC* new_arc = new GRAPHIC_ARC ;
1092
1093 new_arc->shape = GR_SHAPE_ARC;
1094 new_arc->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1095 new_arc->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1096 new_arc->end_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1097 new_arc->end_y = -KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1098 new_arc->center_x = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1099 new_arc->center_y = -KiROUND( readDouble( aData.graphic_data6 ) * aScale );
1100 new_arc->radius = KiROUND( readDouble( aData.graphic_data7 ) * aScale );
1101 new_arc->width = KiROUND( readDouble( aData.graphic_data8 ) * aScale );
1102
1103 new_arc->clockwise = ( aData.graphic_data9 != "COUNTERCLOCKWISE" );
1104
1105 EDA_ANGLE startangle( VECTOR2I( new_arc->start_x, new_arc->start_y )
1106 - VECTOR2I( new_arc->center_x, new_arc->center_y ) );
1107 EDA_ANGLE endangle( VECTOR2I( new_arc->end_x, new_arc->end_y )
1108 - VECTOR2I( new_arc->center_x, new_arc->center_y ) );
1109 EDA_ANGLE angle;
1110
1111 startangle.Normalize();
1112 endangle.Normalize();
1113
1114 VECTOR2I center( new_arc->center_x, new_arc->center_y );
1115 VECTOR2I start( new_arc->start_x, new_arc->start_y );
1116 VECTOR2I mid( new_arc->start_x, new_arc->start_y );
1117 VECTOR2I end( new_arc->end_x, new_arc->end_y );
1118
1119 angle = endangle - startangle;
1120
1121 if( new_arc->clockwise && angle < ANGLE_0 )
1122 angle += ANGLE_360;
1123 if( !new_arc->clockwise && angle > ANGLE_0 )
1124 angle -= ANGLE_360;
1125
1126 if( start == end )
1127 angle = -ANGLE_360;
1128
1129 RotatePoint( mid, center, -angle / 2.0 );
1130
1131 if( start == end )
1132 new_arc->shape = GR_SHAPE_CIRCLE;
1133
1134 new_arc->result = SHAPE_ARC( start, mid, end, 0 );
1135
1136 return new_arc;
1137}
1138
1139
1141{
1142 /*
1143 * Example:
1144 * S!DRAWING FORMAT!ASSY!CIRCLE!2!251744 1!-2488.00!1100.00!240.00!240.00!0!!!!!!
1145 *
1146 * Although this is a circle, we treat it as an 360 degree arc.
1147 * This is because files can contain circles in both forms and the arc form
1148 * is more convenient for directly adding to SHAPE_POLY_SET when needed.
1149 *
1150 * It will be identified as a circle based on the 'shape' field, and turned
1151 * back into a circle when needed (or used as an arc if it is part of a polygon).
1152 */
1153
1154 std::unique_ptr<GRAPHIC_ARC> new_circle = std::make_unique<GRAPHIC_ARC>();
1155
1156 new_circle->shape = GR_SHAPE_CIRCLE;
1157
1158 const VECTOR2I center{
1159 KiROUND( readDouble( aData.graphic_data1 ) * aScale ),
1160 -KiROUND( readDouble( aData.graphic_data2 ) * aScale ),
1161 };
1162 const VECTOR2I size = KiROUND( readDouble( aData.graphic_data3 ) * aScale,
1163 readDouble( aData.graphic_data4 ) * aScale );
1164
1165 if( size.x != size.y )
1166 {
1167 reportError( _( "Circle with unequal x and y radii (x=%d, y=%d)" ), size.x, size.y );
1168 return nullptr;
1169 }
1170
1171 new_circle->width = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1172
1173 new_circle->radius = size.x / 2;
1174
1175 // Fake up a 360 degree arc
1176 const VECTOR2I start = center - VECTOR2I{ new_circle->radius, 0 };
1177 const VECTOR2I mid = center + VECTOR2I{ new_circle->radius, 0 };
1178
1179 new_circle->start_x = start.x;
1180 new_circle->start_y = start.y;
1181
1182 new_circle->end_x = start.x;
1183 new_circle->end_y = start.y;
1184
1185 new_circle->center_x = center.x;
1186 new_circle->center_y = center.y;
1187
1188 new_circle->clockwise = true;
1189
1190 new_circle->result = SHAPE_ARC{ start, mid, start, 0 };
1191
1192 return new_circle.release();
1193}
1194
1195
1197 double aScale )
1198{
1199 /*
1200 * Examples:
1201 * S!ROUTE KEEPOUT!BOTTOM!RECTANGLE!259!10076 1!-90.00!-1000.00!-60.00!-990.00!1!!!!!!
1202 */
1203
1204 GRAPHIC_RECTANGLE* new_rect = new GRAPHIC_RECTANGLE;
1205
1206 new_rect->shape = GR_SHAPE_RECTANGLE;
1207 new_rect->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1208 new_rect->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1209 new_rect->end_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1210 new_rect->end_y = -KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1211 new_rect->fill = aData.graphic_data5 == "1";
1212 new_rect->width = 0;
1213
1214 return new_rect;
1215}
1216
1217
1219 double aScale )
1220{
1221 /*
1222 * Examples:
1223 * S!MANUFACTURING!NCLEGEND-1-10!FIG_RECTANGLE!6!8318 1!4891.50!1201.00!35.43!26.57!0!!!!!!
1224 */
1225
1226 auto new_rect = std::make_unique<GRAPHIC_RECTANGLE>();
1227
1228 const int center_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1229 const int center_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1230
1231 const int size_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1232 const int size_y = KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1233
1234 new_rect->shape = GR_SHAPE_RECTANGLE;
1235 new_rect->start_x = center_x - size_x / 2;
1236 new_rect->start_y = center_y + size_y / 2;
1237 new_rect->end_x = center_x + size_x / 2;
1238 new_rect->end_y = center_y - size_y / 2;
1239 new_rect->fill = aData.graphic_data5 == "1";
1240 new_rect->width = 0;
1241
1242 return new_rect.release();
1243}
1244
1245
1247 double aScale )
1248{
1249 /*
1250 * Example:
1251 * S!DRAWING FORMAT!ASSY!SQUARE!5!250496 1!4813.08!2700.00!320.00!320.00!0!!!!!!
1252 */
1253
1254 // This appears to be identical to a FIG_RECTANGLE
1255 return processFigRectangle( aData, aScale );
1256}
1257
1258
1260 double aScale )
1261{
1262 /*
1263 * Examples:
1264 * S!DRAWING FORMAT!ASSY!OBLONG_X!11!250497 1!4449.08!2546.40!240.00!64.00!0!!!!!!
1265 * S!DRAWING FORMAT!ASSY!OBLONG_Y!12!251256 1!15548.68!1900.00!280.00!720.00!0!!!!!!
1266 */
1267 auto new_oblong = std::make_unique<GRAPHIC_OBLONG>();
1268
1269 new_oblong->shape = GR_SHAPE_OBLONG;
1270 new_oblong->oblong_x = aData.graphic_dataname == "OBLONG_X";
1271 new_oblong->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1272 new_oblong->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1273 new_oblong->size_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1274 new_oblong->size_y = KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1275
1276 // Unclear if this is fill or width
1277 new_oblong->width = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1278
1279 return new_oblong.release();
1280}
1281
1282
1284 double aScale )
1285{
1286 /*
1287 * Examples:
1288 * S!MANUFACTURING!NCLEGEND-1-6!TRIANGLE_1!18!252565 1!-965.00!5406.00!125.00!125.00!0!!!!!!
1289 * S!MANUFACTURING!NCLEGEND-1-6!DIAMOND!7!252566 1!-965.00!5656.00!63.00!63.00!0!!!!!!
1290 * S!MANUFACTURING!NCLEGEND-1-6!OCTAGON!3!252567 1!-965.00!5906.00!40.00!40.00!0!!!!!!
1291 * S!MANUFACTURING!NCLEGEND-1-6!HEXAGON_Y!16!252568 1!-965.00!6156.00!35.00!35.00!0!!!!!!
1292 * S!MANUFACTURING!NCLEGEND-1-6!HEXAGON_X!15!252569 1!-965.00!6406.00!12.00!12.00!0!!!!!!
1293 */
1294
1295 const VECTOR2D c{
1296 readDouble( aData.graphic_data1 ) * aScale,
1297 -readDouble( aData.graphic_data2 ) * aScale,
1298 };
1299
1300 const VECTOR2D s{
1301 readDouble( aData.graphic_data3 ) * aScale,
1302 readDouble( aData.graphic_data4 ) * aScale,
1303 };
1304
1305 if( s.x != s.y )
1306 {
1307 }
1308
1309 auto new_poly = std::make_unique<GRAPHIC_POLYGON>();
1310 new_poly->shape = GR_SHAPE_POLYGON;
1311 new_poly->width = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1312
1313 int radius = s.x / 2;
1314 bool across_corners = true;
1315 EDA_ANGLE pt0_angle = ANGLE_90; // /Pointing up
1316 int n_pts = 0;
1317
1318 if( aData.graphic_dataname == "TRIANGLE_1" )
1319 {
1320 // Upright equilateral triangle (pointing upwards, horizontal base)
1321 // The size appears to be (?) the size of the circumscribing circle,
1322 // rather than the width of the base.
1323 n_pts = 3;
1324 }
1325 else if( aData.graphic_dataname == "DIAMOND" )
1326 {
1327 // Square diamond (can it be non-square?)
1328 // Size is point-to-point width/height
1329 n_pts = 4;
1330 }
1331 else if( aData.graphic_dataname == "HEXAGON_X" )
1332 {
1333 // Hexagon with horizontal top/bottom
1334 // Size is the overall width (across corners)
1335 n_pts = 6;
1336 pt0_angle = ANGLE_0;
1337 }
1338 else if( aData.graphic_dataname == "HEXAGON_Y" )
1339 {
1340 // Hexagon with vertical left/right sides
1341 // Size is the height (i.e. across corners)
1342 n_pts = 6;
1343 }
1344 else if( aData.graphic_dataname == "OCTAGON" )
1345 {
1346 // Octagon with horizontal/vertical sides
1347 // Size is the overall width (across flats)
1348 across_corners = false;
1349 pt0_angle = FULL_CIRCLE / 16;
1350 n_pts = 8;
1351 }
1352 else
1353 {
1354 wxCHECK_MSG( false, nullptr,
1355 wxString::Format( "Unhandled polygon type: %s", aData.graphic_dataname ) );
1356 }
1357
1358 new_poly->m_pts =
1359 KIGEOM::MakeRegularPolygonPoints( c, n_pts, radius, across_corners, pt0_angle );
1360 return new_poly.release();
1361}
1362
1363
1365 double aScale )
1366{
1367 /*
1368 * Examples:
1369 * S!MANUFACTURING!NCLEGEND-1-6!CROSS!4!252571 1!-965.00!6906.00!6.00!6.00!0!!!!!!
1370 */
1371 auto new_cross = std::make_unique<GRAPHIC_CROSS>();
1372
1373 new_cross->shape = GR_SHAPE_CROSS;
1374 new_cross->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1375 new_cross->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1376 new_cross->size_x = KiROUND( readDouble( aData.graphic_data3 ) * aScale );
1377 new_cross->size_y = KiROUND( readDouble( aData.graphic_data4 ) * aScale );
1378 new_cross->width = KiROUND( readDouble( aData.graphic_data5 ) * aScale );
1379
1380 return new_cross.release();
1381}
1382
1383
1385 double aScale )
1386{
1387 GRAPHIC_TEXT* new_text = new GRAPHIC_TEXT;
1388
1389 new_text->shape = GR_SHAPE_TEXT;
1390 new_text->start_x = KiROUND( readDouble( aData.graphic_data1 ) * aScale );
1391 new_text->start_y = -KiROUND( readDouble( aData.graphic_data2 ) * aScale );
1392 new_text->rotation = KiROUND( readDouble( aData.graphic_data3 ) );
1393 new_text->mirror = ( aData.graphic_data4 == "YES" );
1394
1395 if( aData.graphic_data5 == "RIGHT" )
1396 new_text->orient = GR_TEXT_H_ALIGN_RIGHT;
1397 else if( aData.graphic_data5 == "CENTER" )
1398 new_text->orient = GR_TEXT_H_ALIGN_CENTER;
1399 else
1400 new_text->orient = GR_TEXT_H_ALIGN_LEFT;
1401
1402 std::vector<std::string> toks = split( aData.graphic_data6, " \t" );
1403
1404 if( toks.size() < 8 )
1405 {
1406 // We log the error here but continue in the case of too few tokens
1407 reportError( _( "Invalid token count. Expected 8 but found %zu." ), toks.size() );
1408 new_text->height = 0;
1409 new_text->width = 0;
1410 new_text->ital = false;
1411 new_text->thickness = 0;
1412 }
1413 else
1414 {
1415 // 0 = size
1416 // 1 = font
1417 new_text->height = KiROUND( readDouble( toks[2] ) * aScale );
1418 new_text->width = KiROUND( readDouble( toks[3] ) * aScale );
1419 new_text->ital = readDouble( toks[4] ) != 0.0;
1420 // 5 = character spacing
1421 // 6 = line spacing
1422 new_text->thickness = KiROUND( readDouble( toks[7] ) * aScale );
1423 }
1424
1425 new_text->text = aData.graphic_data7;
1426 return new_text;
1427}
1428
1429
1431{
1432 GRAPHIC_ITEM* retval = nullptr;
1433
1434 if( aData.graphic_dataname == "LINE" )
1435 retval = processLine( aData, aScale );
1436 else if( aData.graphic_dataname == "ARC" )
1437 retval = processArc( aData, aScale );
1438 else if( aData.graphic_dataname == "CIRCLE" )
1439 retval = processCircle( aData, aScale );
1440 else if( aData.graphic_dataname == "RECTANGLE" )
1441 retval = processRectangle( aData, aScale );
1442 else if( aData.graphic_dataname == "FIG_RECTANGLE" )
1443 retval = processFigRectangle( aData, aScale );
1444 else if( aData.graphic_dataname == "SQUARE" )
1445 retval = processSquare( aData, aScale );
1446 else if( aData.graphic_dataname == "OBLONG_X" || aData.graphic_dataname == "OBLONG_Y" )
1447 retval = processOblong( aData, aScale );
1448 else if( aData.graphic_dataname == "TRIANGLE_1" || aData.graphic_dataname == "DIAMOND"
1449 || aData.graphic_dataname == "HEXAGON_X" || aData.graphic_dataname == "HEXAGON_Y"
1450 || aData.graphic_dataname == "OCTAGON" )
1451 retval = processPolygon( aData, aScale );
1452 else if( aData.graphic_dataname == "CROSS" )
1453 retval = processCross( aData, aScale );
1454 else if( aData.graphic_dataname == "TEXT" )
1455 retval = processText( aData, aScale );
1456
1457 if( retval && !aData.graphic_data10.empty() )
1458 {
1459 if( aData.graphic_data10 == "CONNECT" )
1460 retval->type = GR_TYPE_CONNECT;
1461 else if( aData.graphic_data10 == "NOTCONNECT" )
1462 retval->type = GR_TYPE_NOTCONNECT;
1463 else if( aData.graphic_data10 == "SHAPE" )
1464 retval->type = GR_TYPE_NOTCONNECT;
1465 else if( aData.graphic_data10 == "VOID" )
1466 retval->type = GR_TYPE_NOTCONNECT;
1467 else if( aData.graphic_data10 == "POLYGON" )
1468 retval->type = GR_TYPE_NOTCONNECT;
1469 else
1470 retval->type = GR_TYPE_NONE;
1471 }
1472
1473 return retval;
1474}
1475
1476
1482size_t FABMASTER::processGeometry( size_t aRow )
1483{
1484 size_t rownum = aRow + 2;
1485
1486 if( rownum >= rows.size() )
1487 return -1;
1488
1489 const single_row& header = rows[aRow];
1490 double scale_factor = processScaleFactor( aRow + 1 );
1491
1492 if( scale_factor <= 0.0 )
1493 return -1;
1494
1495 int geo_name_col = getColFromName( aRow, "GRAPHICDATANAME" );
1496 int geo_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" );
1497 int geo_tag_col = getColFromName( aRow, "RECORDTAG" );
1498 int geo_grdata1_col = getColFromName( aRow, "GRAPHICDATA1" );
1499 int geo_grdata2_col = getColFromName( aRow, "GRAPHICDATA2" );
1500 int geo_grdata3_col = getColFromName( aRow, "GRAPHICDATA3" );
1501 int geo_grdata4_col = getColFromName( aRow, "GRAPHICDATA4" );
1502 int geo_grdata5_col = getColFromName( aRow, "GRAPHICDATA5" );
1503 int geo_grdata6_col = getColFromName( aRow, "GRAPHICDATA6" );
1504 int geo_grdata7_col = getColFromName( aRow, "GRAPHICDATA7" );
1505 int geo_grdata8_col = getColFromName( aRow, "GRAPHICDATA8" );
1506 int geo_grdata9_col = getColFromName( aRow, "GRAPHICDATA9" );
1507 int geo_subclass_col = getColFromName( aRow, "SUBCLASS" );
1508 int geo_sym_name_col = getColFromName( aRow, "SYMNAME" );
1509 int geo_refdes_col = getColFromName( aRow, "REFDES" );
1510
1511 if( geo_name_col < 0 || geo_num_col < 0 || geo_grdata1_col < 0 || geo_grdata2_col < 0
1512 || geo_grdata3_col < 0 || geo_grdata4_col < 0 || geo_grdata5_col < 0
1513 || geo_grdata6_col < 0 || geo_grdata7_col < 0 || geo_grdata8_col < 0
1514 || geo_grdata9_col < 0 || geo_subclass_col < 0 || geo_sym_name_col < 0
1515 || geo_refdes_col < 0 )
1516 return -1;
1517
1518 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
1519 {
1520 const single_row& row = rows[rownum];
1521
1522 if( row.size() != header.size() )
1523 {
1524 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
1525 rownum,
1526 header.size(),
1527 row.size() );
1528 continue;
1529 }
1530
1531 auto& geo_tag = row[geo_tag_col];
1532
1533 GRAPHIC_DATA gr_data;
1534 gr_data.graphic_dataname = row[geo_name_col];
1535 gr_data.graphic_datanum = row[geo_num_col];
1536 gr_data.graphic_data1 = row[geo_grdata1_col];
1537 gr_data.graphic_data2 = row[geo_grdata2_col];
1538 gr_data.graphic_data3 = row[geo_grdata3_col];
1539 gr_data.graphic_data4 = row[geo_grdata4_col];
1540 gr_data.graphic_data5 = row[geo_grdata5_col];
1541 gr_data.graphic_data6 = row[geo_grdata6_col];
1542 gr_data.graphic_data7 = row[geo_grdata7_col];
1543 gr_data.graphic_data8 = row[geo_grdata8_col];
1544 gr_data.graphic_data9 = row[geo_grdata9_col];
1545
1546 auto& geo_refdes = row[geo_refdes_col];
1547
1548 // Grouped graphics are a series of records with the same record ID but incrementing
1549 // Sequence numbers.
1550 int id = -1;
1551 int seq = -1;
1552 int subseq = 0;
1553
1554 if( std::sscanf( geo_tag.c_str(), "%d %d %d", &id, &seq, &subseq ) < 2 )
1555 {
1556 reportError( _( "Invalid format for record_tag string '%s' in row %zu." ),
1557 geo_tag.c_str(),
1558 rownum );
1559 continue;
1560 }
1561
1562 auto gr_item = std::unique_ptr<GRAPHIC_ITEM>( processGraphic( gr_data, scale_factor ) );
1563
1564 if( !gr_item )
1565 continue;
1566
1567 gr_item->layer = row[geo_subclass_col];
1568 gr_item->seq = seq;
1569 gr_item->subseq = subseq;
1570
1571 if( geo_refdes.empty() )
1572 {
1573 if( board_graphics.empty() || board_graphics.back().id != id )
1574 {
1575 GEOM_GRAPHIC new_gr;
1576 new_gr.subclass = row[geo_subclass_col];
1577 new_gr.refdes = row[geo_refdes_col];
1578 new_gr.name = row[geo_sym_name_col];
1579 new_gr.id = id;
1580 new_gr.elements = std::make_unique<graphic_element>();
1581 board_graphics.push_back( std::move( new_gr ) );
1582 }
1583
1584 GEOM_GRAPHIC& graphic = board_graphics.back();
1585 graphic.elements->emplace( std::move( gr_item ) );
1586 }
1587 else
1588 {
1589 auto sym_gr_it = comp_graphics.emplace( geo_refdes,
1590 std::map<int, GEOM_GRAPHIC>{} );
1591 auto map_it = sym_gr_it.first->second.emplace( id, GEOM_GRAPHIC{} );
1592 auto& gr = map_it.first;
1593
1594 if( map_it.second )
1595 {
1596 gr->second.subclass = row[geo_subclass_col];
1597 gr->second.refdes = row[geo_refdes_col];
1598 gr->second.name = row[geo_sym_name_col];
1599 gr->second.id = id;
1600 gr->second.elements = std::make_unique<graphic_element>();
1601 }
1602
1603 gr->second.elements->emplace( std::move( gr_item ) );
1604 }
1605 }
1606
1607 return rownum - aRow;
1608}
1609
1610
1614size_t FABMASTER::processVias( size_t aRow )
1615{
1616 size_t rownum = aRow + 2;
1617
1618 if( rownum >= rows.size() )
1619 return -1;
1620
1621 const single_row& header = rows[aRow];
1622 double scale_factor = processScaleFactor( aRow + 1 );
1623
1624 if( scale_factor <= 0.0 )
1625 return -1;
1626
1627 int viax_col = getColFromName( aRow, "VIAX" );
1628 int viay_col = getColFromName( aRow, "VIAY" );
1629 int padstack_name_col = getColFromName( aRow, "PADSTACKNAME" );
1630 int net_name_col = getColFromName( aRow, "NETNAME" );
1631 int test_point_col = getColFromName( aRow, "TESTPOINT" );
1632
1633 if( viax_col < 0 || viay_col < 0 || padstack_name_col < 0 || net_name_col < 0
1634 || test_point_col < 0 )
1635 return -1;
1636
1637 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
1638 {
1639 const single_row& row = rows[rownum];
1640
1641 if( row.size() != header.size() )
1642 {
1643 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
1644 rownum,
1645 header.size(),
1646 row.size() );
1647 continue;
1648 }
1649
1650 vias.emplace_back( std::make_unique<FM_VIA>() );
1651 auto& via = vias.back();
1652
1653 via->x = KiROUND( readDouble( row[viax_col] ) * scale_factor );
1654 via->y = -KiROUND( readDouble( row[viay_col] ) * scale_factor );
1655 via->padstack = row[padstack_name_col];
1656 via->net = row[net_name_col];
1657 via->test_point = ( row[test_point_col] == "YES" );
1658 }
1659
1660 return rownum - aRow;
1661}
1662
1663
1669size_t FABMASTER::processTraces( size_t aRow )
1670{
1671 size_t rownum = aRow + 2;
1672
1673 if( rownum >= rows.size() )
1674 return -1;
1675
1676 const single_row& header = rows[aRow];
1677 double scale_factor = processScaleFactor( aRow + 1 );
1678
1679 if( scale_factor <= 0.0 )
1680 return -1;
1681
1682 int class_col = getColFromName( aRow, "CLASS" );
1683 int layer_col = getColFromName( aRow, "SUBCLASS" );
1684 int grdata_name_col = getColFromName( aRow, "GRAPHICDATANAME" );
1685 int grdata_num_col = getColFromName( aRow, "GRAPHICDATANUMBER" );
1686 int tag_col = getColFromName( aRow, "RECORDTAG" );
1687 int grdata1_col = getColFromName( aRow, "GRAPHICDATA1" );
1688 int grdata2_col = getColFromName( aRow, "GRAPHICDATA2" );
1689 int grdata3_col = getColFromName( aRow, "GRAPHICDATA3" );
1690 int grdata4_col = getColFromName( aRow, "GRAPHICDATA4" );
1691 int grdata5_col = getColFromName( aRow, "GRAPHICDATA5" );
1692 int grdata6_col = getColFromName( aRow, "GRAPHICDATA6" );
1693 int grdata7_col = getColFromName( aRow, "GRAPHICDATA7" );
1694 int grdata8_col = getColFromName( aRow, "GRAPHICDATA8" );
1695 int grdata9_col = getColFromName( aRow, "GRAPHICDATA9" );
1696 int netname_col = getColFromName( aRow, "NETNAME" );
1697
1698 if( class_col < 0 || layer_col < 0 || grdata_name_col < 0 || grdata_num_col < 0
1699 || tag_col < 0 || grdata1_col < 0 || grdata2_col < 0 || grdata3_col < 0
1700 || grdata4_col < 0 || grdata5_col < 0 || grdata6_col < 0 || grdata7_col < 0
1701 || grdata8_col < 0 || grdata9_col < 0 || netname_col < 0 )
1702 return -1;
1703
1704 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
1705 {
1706 const single_row& row = rows[rownum];
1707
1708 if( row.size() != header.size() )
1709 {
1710 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
1711 rownum,
1712 header.size(),
1713 row.size() );
1714 continue;
1715 }
1716
1717 GRAPHIC_DATA gr_data;
1718 gr_data.graphic_dataname = row[grdata_name_col];
1719 gr_data.graphic_datanum = row[grdata_num_col];
1720 gr_data.graphic_data1 = row[grdata1_col];
1721 gr_data.graphic_data2 = row[grdata2_col];
1722 gr_data.graphic_data3 = row[grdata3_col];
1723 gr_data.graphic_data4 = row[grdata4_col];
1724 gr_data.graphic_data5 = row[grdata5_col];
1725 gr_data.graphic_data6 = row[grdata6_col];
1726 gr_data.graphic_data7 = row[grdata7_col];
1727 gr_data.graphic_data8 = row[grdata8_col];
1728 gr_data.graphic_data9 = row[grdata9_col];
1729
1730 const std::string& geo_tag = row[tag_col];
1731 // Grouped graphics are a series of records with the same record ID but incrementing
1732 // Sequence numbers.
1733 int id = -1;
1734 int seq = -1;
1735 int subseq = 0;
1736
1737 if( std::sscanf( geo_tag.c_str(), "%d %d %d", &id, &seq, &subseq ) < 2 )
1738 {
1739 reportError( _( "Invalid format for record_tag string '%s' in row %zu." ),
1740 geo_tag.c_str(),
1741 rownum );
1742 continue;
1743 }
1744
1745 auto gr_item = std::unique_ptr<GRAPHIC_ITEM>( processGraphic( gr_data, scale_factor ) );
1746
1747 if( !gr_item )
1748 {
1749 wxLogTrace( traceFabmaster, wxT( "Unhandled graphic item '%s' in row %zu." ),
1750 gr_data.graphic_dataname.c_str(),
1751 rownum );
1752 continue;
1753 }
1754
1755 auto new_trace = std::make_unique<TRACE>();
1756 new_trace->id = id;
1757 new_trace->layer = row[layer_col];
1758 new_trace->netname = row[netname_col];
1759 new_trace->lclass = row[class_col];
1760
1761 gr_item->layer = row[layer_col];
1762 gr_item->seq = seq;
1763 gr_item->subseq = subseq;
1764
1765 // Collect the reference designator positions for the footprints later
1766 if( new_trace->lclass == "REF DES" )
1767 {
1768 auto result = refdes.emplace( std::move( new_trace ) );
1769 auto& ref = *result.first;
1770 ref->segment.emplace( std::move( gr_item ) );
1771 }
1772 else if( new_trace->lclass == "DEVICE TYPE" || new_trace->lclass == "COMPONENT VALUE"
1773 || new_trace->lclass == "TOLERANCE" )
1774 {
1775 // TODO: This seems like a value field, but it's not immediately clear how to map it
1776 // to the right footprint.
1777 // So these spam the board with huge amount of overlapping text.
1778
1779 // Examples:
1780 // S!DEVICE TYPE!SILKSCREEN_BOTTOM!TEXT!260!255815 1!2725.00!1675.00!270.000!YES!LEFT!45 0 60.00 48.00 0.000 0.00 0.00 0.00!CAP_0.1UF_X5R_6.3V_20% 0201 _40!!!!
1781 // S!DEVICE TYPE!ASSEMBLY_BOTTOM!TEXT!260!255816 1!2725.00!1675.00!270.000!YES!LEFT!45 0 60.00 48.00 0.000 0.00 0.00 0.00!CAP_0.1UF_X5R_6.3V_20% 0201 _40!!!!
1782 // S!COMPONENT VALUE!SILKSCREEN_BOTTOM!TEXT!260!18949 1!361.665!1478.087!270.000!YES!LEFT!31 0 30.000 20.000 0.000 6.000 31.000 6.000!0.01uF!!!!
1783
1784 // For now, just don't do anything with them.
1785 }
1786 else if( gr_item->width == 0 )
1787 {
1788 auto result = zones.emplace( std::move( new_trace ) );
1789 auto& zone = *result.first;
1790 auto gr_result = zone->segment.emplace( std::move( gr_item ) );
1791
1792 if( !gr_result.second )
1793 {
1794 reportError( _( "Duplicate item for ID %d and sequence %d in row %zu." ),
1795 id,
1796 seq,
1797 rownum );
1798 }
1799 }
1800 else
1801 {
1802 auto result = traces.emplace( std::move( new_trace ) );
1803 auto& trace = *result.first;
1804 auto gr_result = trace->segment.emplace( std::move( gr_item ) );
1805
1806 if( !gr_result.second )
1807 {
1808 reportError( _( "Duplicate item for ID %d and sequence %d in row %zu." ),
1809 id,
1810 seq,
1811 rownum );
1812 }
1813 }
1814 }
1815
1816 return rownum - aRow;
1817}
1818
1819
1820FABMASTER::SYMTYPE FABMASTER::parseSymType( const std::string& aSymType )
1821{
1822 if( aSymType == "PACKAGE" )
1823 return SYMTYPE_PACKAGE;
1824 else if( aSymType == "DRAFTING")
1825 return SYMTYPE_DRAFTING;
1826 else if( aSymType == "MECHANICAL" )
1827 return SYMTYPE_MECH;
1828 else if( aSymType == "FORMAT" )
1829 return SYMTYPE_FORMAT;
1830
1831 return SYMTYPE_NONE;
1832}
1833
1834
1836{
1837 if( aCmpClass == "IO" )
1838 return COMPCLASS_IO;
1839 else if( aCmpClass == "IC" )
1840 return COMPCLASS_IC;
1841 else if( aCmpClass == "DISCRETE" )
1842 return COMPCLASS_DISCRETE;
1843
1844 return COMPCLASS_NONE;
1845}
1846
1847
1852size_t FABMASTER::processFootprints( size_t aRow )
1853{
1854 size_t rownum = aRow + 2;
1855
1856 if( rownum >= rows.size() )
1857 return -1;
1858
1859 const single_row& header = rows[aRow];
1860 double scale_factor = processScaleFactor( aRow + 1 );
1861
1862 if( scale_factor <= 0.0 )
1863 return -1;
1864
1865 int refdes_col = getColFromName( aRow, "REFDES" );
1866 int compclass_col = getColFromName( aRow, "COMPCLASS" );
1867 int comppartnum_col = getColFromName( aRow, "COMPPARTNUMBER" );
1868 int compheight_col = getColFromName( aRow, "COMPHEIGHT" );
1869 int compdevlabelcol = getColFromName( aRow, "COMPDEVICELABEL" );
1870 int compinscode_col = getColFromName( aRow, "COMPINSERTIONCODE" );
1871 int symtype_col = getColFromName( aRow, "SYMTYPE" );
1872 int symname_col = getColFromName( aRow, "SYMNAME" );
1873 int symmirror_col = getColFromName( aRow, "SYMMIRROR" );
1874 int symrotate_col = getColFromName( aRow, "SYMROTATE" );
1875 int symx_col = getColFromName( aRow, "SYMX" );
1876 int symy_col = getColFromName( aRow, "SYMY" );
1877 int compvalue_col = getColFromName( aRow, "COMPVALUE" );
1878 int comptol_col = getColFromName( aRow, "COMPTOL" );
1879 int compvolt_col = getColFromName( aRow, "COMPVOLTAGE" );
1880
1881 if( refdes_col < 0 || compclass_col < 0 || comppartnum_col < 0 || compheight_col < 0
1882 || compdevlabelcol < 0 || compinscode_col < 0 || symtype_col < 0 || symname_col < 0
1883 || symmirror_col < 0 || symrotate_col < 0 || symx_col < 0 || symy_col < 0
1884 || compvalue_col < 0 || comptol_col < 0 || compvolt_col < 0 )
1885 return -1;
1886
1887 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
1888 {
1889 const single_row& row = rows[rownum];
1890
1891 if( row.size() != header.size() )
1892 {
1893 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
1894 rownum,
1895 header.size(),
1896 row.size() );
1897 continue;
1898 }
1899
1900 const wxString& comp_refdes = row[refdes_col];
1901
1902 if( row[symx_col].empty() || row[symy_col].empty() || row[symrotate_col].empty() )
1903 {
1904 reportError( _( "Missing X, Y, or rotation data in row %zu for refdes %s. "
1905 "This may be an unplaced component." ),
1906 rownum, comp_refdes );
1907 continue;
1908 }
1909
1910 auto cmp = std::make_unique<COMPONENT>();
1911
1912 cmp->refdes = comp_refdes;
1913 cmp->cclass = parseCompClass( row[compclass_col] );
1914 cmp->pn = row[comppartnum_col];
1915 cmp->height = row[compheight_col];
1916 cmp->dev_label = row[compdevlabelcol];
1917 cmp->insert_code = row[compinscode_col];
1918 cmp->type = parseSymType( row[symtype_col] );
1919 cmp->name = row[symname_col];
1920 cmp->mirror = ( row[symmirror_col] == "YES" );
1921 cmp->rotate = readDouble( row[symrotate_col] );
1922 cmp->x = KiROUND( readDouble( row[symx_col] ) * scale_factor );
1923 cmp->y = -KiROUND( readDouble( row[symy_col] ) * scale_factor );
1924 cmp->value = row[compvalue_col];
1925 cmp->tol = row[comptol_col];
1926 cmp->voltage = row[compvolt_col];
1927
1928 auto vec = components.find( cmp->refdes );
1929
1930 if( vec == components.end() )
1931 {
1932 auto retval = components.insert( std::make_pair( cmp->refdes, std::vector<std::unique_ptr<COMPONENT>>{} ) );
1933
1934 vec = retval.first;
1935 }
1936
1937 vec->second.push_back( std::move( cmp ) );
1938 }
1939
1940 return rownum - aRow;
1941}
1942
1943
1948size_t FABMASTER::processPins( size_t aRow )
1949{
1950 size_t rownum = aRow + 2;
1951
1952 if( rownum >= rows.size() )
1953 return -1;
1954
1955 const single_row& header = rows[aRow];
1956 double scale_factor = processScaleFactor( aRow + 1 );
1957
1958 if( scale_factor <= 0.0 )
1959 return -1;
1960
1961 int symname_col = getColFromName( aRow, "SYMNAME" );
1962 int symmirror_col = getColFromName( aRow, "SYMMIRROR" );
1963 int pinname_col = getColFromName( aRow, "PINNAME" );
1964 int pinnum_col = getColFromName( aRow, "PINNUMBER" );
1965 int pinx_col = getColFromName( aRow, "PINX" );
1966 int piny_col = getColFromName( aRow, "PINY" );
1967 int padstack_col = getColFromName( aRow, "PADSTACKNAME" );
1968 int refdes_col = getColFromName( aRow, "REFDES" );
1969 int pinrot_col = getColFromName( aRow, "PINROTATION" );
1970 int testpoint_col = getColFromName( aRow, "TESTPOINT" );
1971
1972 if( symname_col < 0 ||symmirror_col < 0 || pinname_col < 0 || pinnum_col < 0 || pinx_col < 0
1973 || piny_col < 0 || padstack_col < 0 || refdes_col < 0 || pinrot_col < 0
1974 || testpoint_col < 0 )
1975 return -1;
1976
1977 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
1978 {
1979 const single_row& row = rows[rownum];
1980
1981 if( row.size() != header.size() )
1982 {
1983 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
1984 rownum,
1985 header.size(),
1986 row.size() );
1987 continue;
1988 }
1989
1990 auto pin = std::make_unique<PIN>();
1991
1992 pin->name = row[symname_col];
1993 pin->mirror = ( row[symmirror_col] == "YES" );
1994 pin->pin_name = row[pinname_col];
1995 pin->pin_number = row[pinnum_col];
1996 pin->pin_x = KiROUND( readDouble( row[pinx_col] ) * scale_factor );
1997 pin->pin_y = -KiROUND( readDouble( row[piny_col] ) * scale_factor );
1998 pin->padstack = row[padstack_col];
1999 pin->refdes = row[refdes_col];
2000 pin->rotation = readDouble( row[pinrot_col] );
2001
2002 // Use refdes as the key if available, otherwise fall back to sym_name.
2003 // Some fabmaster exports (e.g., boards with only components and no netlist)
2004 // have empty refdes fields, but the sym_name still links pins to their symbol.
2005 std::string pin_key = pin->refdes.empty() ? pin->name : pin->refdes;
2006
2007 auto map_it = pins.find( pin_key );
2008
2009 if( map_it == pins.end() )
2010 {
2011 auto retval = pins.insert( std::make_pair( pin_key, std::set<std::unique_ptr<PIN>,
2012 PIN::BY_NUM>{} ) );
2013 map_it = retval.first;
2014 }
2015
2016 map_it->second.insert( std::move( pin ) );
2017 }
2018
2019 return rownum - aRow;
2020}
2021
2022
2026size_t FABMASTER::processNets( size_t aRow )
2027{
2028 size_t rownum = aRow + 2;
2029
2030 if( rownum >= rows.size() )
2031 return -1;
2032
2033 const single_row& header = rows[aRow];
2034 double scale_factor = processScaleFactor( aRow + 1 );
2035
2036 if( scale_factor <= 0.0 )
2037 return -1;
2038
2039 int netname_col = getColFromName( aRow, "NETNAME" );
2040 int refdes_col = getColFromName( aRow, "REFDES" );
2041 int pinnum_col = getColFromName( aRow, "PINNUMBER" );
2042 int pinname_col = getColFromName( aRow, "PINNAME" );
2043 int pingnd_col = getColFromName( aRow, "PINGROUND" );
2044 int pinpwr_col = getColFromName( aRow, "PINPOWER" );
2045
2046 if( netname_col < 0 || refdes_col < 0 || pinnum_col < 0 || pinname_col < 0 || pingnd_col < 0
2047 || pinpwr_col < 0 )
2048 return -1;
2049
2050 for( ; rownum < rows.size() && rows[rownum].size() > 0 && rows[rownum][0] == "S"; ++rownum )
2051 {
2052 const single_row& row = rows[rownum];
2053
2054 if( row.size() != header.size() )
2055 {
2056 reportError( _( "Invalid row size in row %zu. Expecting %zu elements but found %zu." ),
2057 rownum,
2058 header.size(),
2059 row.size() );
2060 continue;
2061 }
2062
2063 NETNAME new_net;
2064 new_net.name = row[netname_col];
2065 new_net.refdes = row[refdes_col];
2066 new_net.pin_num = row[pinnum_col];
2067 new_net.pin_name = row[pinname_col];
2068 new_net.pin_gnd = ( row[pingnd_col] == "YES" );
2069 new_net.pin_pwr = ( row[pinpwr_col] == "YES" );
2070
2071 pin_nets.emplace( std::make_pair( new_net.refdes, new_net.pin_num ), new_net );
2072 netnames.insert( row[netname_col] );
2073 }
2074
2075 return rownum - aRow;
2076}
2077
2078
2080{
2081
2082 for( size_t i = 0; i < rows.size(); )
2083 {
2084 auto type = detectType( i );
2085
2086 switch( type )
2087 {
2088 case EXTRACT_PADSTACKS:
2089 {
2093 assignLayers();
2094 int retval = processPadStacks( i );
2095
2096 i += std::max( retval, 1 );
2097 break;
2098 }
2099
2101 {
2102 int retval = processLayers( i );
2103
2104 i += std::max( retval, 1 );
2105 break;
2106 }
2107
2109 {
2110 int retval = processSimpleLayers( i );
2111
2112 i += std::max( retval, 1 );
2113 break;
2114 }
2115
2116 case EXTRACT_VIAS:
2117 {
2118 int retval = processVias( i );
2119
2120 i += std::max( retval, 1 );
2121 break;
2122 }
2123
2124 case EXTRACT_TRACES:
2125 {
2126 int retval = processTraces( i );
2127
2128 i += std::max( retval, 1 );
2129 break;
2130 }
2131
2132 case EXTRACT_REFDES:
2133 {
2134 int retval = processFootprints( i );
2135
2136 i += std::max( retval, 1 );
2137 break;
2138 }
2139
2140 case EXTRACT_NETS:
2141 {
2142 int retval = processNets( i );
2143
2144 i += std::max( retval, 1 );
2145 break;
2146 }
2147
2148 case EXTRACT_GRAPHICS:
2149 {
2150 int retval = processGeometry( i );
2151
2152 i += std::max( retval, 1 );
2153 break;
2154 }
2155
2156 case EXTRACT_PINS:
2157 {
2158 int retval = processPins( i );
2159
2160 i += std::max( retval, 1 );
2161 break;
2162 }
2163
2164 case EXTRACT_PAD_SHAPES:
2165 {
2166 int retval = processCustomPads( i );
2167
2168 i += std::max( retval, 1 );
2169 break;
2170 }
2171
2172 default:
2173 ++i;
2174 break;
2175 }
2176
2177 }
2178
2179 return true;
2180}
2181
2182
2187static bool isRuleAreaClass( const std::string& aClass )
2188{
2189 return aClass == "ROUTE KEEPOUT" || aClass == "VIA KEEPOUT" || aClass == "PACKAGE KEEPOUT"
2190 || aClass == "ROUTE KEEPIN" || aClass == "PACKAGE KEEPIN"
2191 || aClass == "CONSTRAINT REGION";
2192}
2193
2194
2196{
2197 for( auto& zone : zones )
2198 {
2199 checkpoint();
2200
2201 if( isRuleAreaClass( zone->lclass ) || IsCopperLayer( getLayer( zone->layer ) )
2202 || zone->layer == "ALL" )
2203 {
2204 loadZone( aBoard, zone );
2205 }
2206 else
2207 {
2208 if( zone->layer == "OUTLINE" || zone->layer == "DESIGN_OUTLINE" )
2209 {
2210 loadOutline( aBoard, zone );
2211 }
2212 else
2213 {
2214 loadPolygon( aBoard, zone );
2215 }
2216 }
2217 }
2218
2229 std::set<ZONE*> zones_to_delete;
2230 std::set<ZONE*> matched_fills;
2231
2232 for( auto zone : aBoard->Zones() )
2233 {
2234 if( zone->GetNetCode() > 0 )
2235 zones_to_delete.insert( zone );
2236 }
2237
2238 for( auto zone1 : aBoard->Zones() )
2239 {
2240 if( zone1->GetNetCode() > 0 )
2241 continue;
2242
2243 // Rule areas legitimately have no net; they are not orphaned fills awaiting a match.
2244 if( zone1->GetIsRuleArea() )
2245 continue;
2246
2247 SHAPE_LINE_CHAIN& outline1 = zone1->Outline()->Outline( 0 );
2248 std::vector<size_t> overlaps( aBoard->GetNetInfo().GetNetCount() + 1, 0 );
2249 std::map<int, std::vector<ZONE*>> net_to_fills;
2250
2251 for( auto zone2 : aBoard->Zones() )
2252 {
2253 if( zone2->GetNetCode() <= 0 )
2254 continue;
2255
2256 SHAPE_LINE_CHAIN& outline2 = zone2->Outline()->Outline( 0 );
2257
2258 if( zone1->GetLayer() != zone2->GetLayer() )
2259 continue;
2260
2261 if( !outline1.BBox().Intersects( outline2.BBox() ) )
2262 continue;
2263
2264 size_t match_count = 0;
2265
2266 for( auto& pt1 : outline1.CPoints() )
2267 {
2268 if( outline2.PointOnEdge( pt1, 1 ) )
2269 match_count++;
2270 }
2271
2272 for( auto& pt2 : outline2.CPoints() )
2273 {
2274 if( outline1.PointOnEdge( pt2, 1 ) )
2275 match_count++;
2276 }
2277
2278 if( match_count > 0 )
2279 {
2280 overlaps[zone2->GetNetCode()] += match_count;
2281 net_to_fills[zone2->GetNetCode()].push_back( zone2 );
2282 }
2283 }
2284
2285 size_t max_net = 0;
2286 size_t max_net_id = 0;
2287
2288 for( size_t el = 1; el < overlaps.size(); ++el )
2289 {
2290 if( overlaps[el] > max_net )
2291 {
2292 max_net = overlaps[el];
2293 max_net_id = el;
2294 }
2295 }
2296
2297 if( max_net > 0 )
2298 {
2299 zone1->SetNetCode( max_net_id );
2300
2301 for( ZONE* fill : net_to_fills[max_net_id] )
2302 matched_fills.insert( fill );
2303 }
2304 }
2305
2306 for( auto zone : zones_to_delete )
2307 {
2308 if( matched_fills.find( zone ) != matched_fills.end() )
2309 {
2310 aBoard->Remove( zone );
2311 delete zone;
2312 }
2313 }
2314
2315 return true;
2316}
2317
2318
2320 PCB_TEXT& aText, const BOARD& aBoard, const OPT_VECTOR2I& aMirrorPoint )
2321{
2322 aText.SetHorizJustify( aGText.orient );
2323
2324 aText.SetKeepUpright( false );
2325
2326 EDA_ANGLE angle = EDA_ANGLE( aGText.rotation );
2327 angle.Normalize180();
2328
2329 if( aMirrorPoint.has_value() )
2330 {
2331 aText.SetLayer( aBoard.FlipLayer( aLayer ) );
2332 aText.SetTextPos( VECTOR2I(
2333 aGText.start_x, 2 * aMirrorPoint->y - ( aGText.start_y - aGText.height / 2 ) ) );
2334 aText.SetMirrored( !aGText.mirror );
2335
2336 aText.SetTextAngle( -angle + ANGLE_180 );
2337 }
2338 else
2339 {
2340 aText.SetLayer( aLayer );
2341 aText.SetTextPos( VECTOR2I( aGText.start_x, aGText.start_y - aGText.height / 2 ) );
2342 aText.SetMirrored( aGText.mirror );
2343
2344 aText.SetTextAngle( angle );
2345 }
2346
2347 if( std::abs( angle ) >= ANGLE_90 )
2348 {
2350 }
2351
2352 aText.SetText( aGText.text );
2353 aText.SetItalic( aGText.ital );
2354 aText.SetTextThickness( aGText.thickness );
2355 aText.SetTextHeight( aGText.height );
2356 aText.SetTextWidth( aGText.width );
2357}
2358
2359
2361{
2362 for( const auto& [pinKey, pinSet] : pins )
2363 {
2364 if( pinSet.empty() )
2365 continue;
2366
2367 if( components.find( pinKey ) != components.end() )
2368 continue;
2369
2370 const auto& firstPin = *pinSet.begin();
2371
2372 int minX = firstPin->pin_x;
2373 int maxX = firstPin->pin_x;
2374 int minY = firstPin->pin_y;
2375 int maxY = firstPin->pin_y;
2376
2377 for( const auto& pin : pinSet )
2378 {
2379 minX = std::min( minX, pin->pin_x );
2380 maxX = std::max( maxX, pin->pin_x );
2381 minY = std::min( minY, pin->pin_y );
2382 maxY = std::max( maxY, pin->pin_y );
2383 }
2384
2385 auto cmp = std::make_unique<COMPONENT>();
2386 cmp->refdes = pinKey;
2387 cmp->name = firstPin->name;
2388 cmp->mirror = firstPin->mirror;
2389 cmp->rotate = 0.0;
2390 cmp->x = ( minX + maxX ) / 2;
2391 cmp->y = ( minY + maxY ) / 2;
2392 cmp->type = SYMTYPE_PACKAGE;
2393 cmp->cclass = COMPCLASS_IC;
2394
2395 std::vector<std::unique_ptr<COMPONENT>> compVec;
2396 compVec.push_back( std::move( cmp ) );
2397 components.insert( std::make_pair( pinKey, std::move( compVec ) ) );
2398 }
2399}
2400
2401
2403{
2404 const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName();
2405 const auto& ds = aBoard->GetDesignSettings();
2406
2407 for( auto& mod : components )
2408 {
2409 checkpoint();
2410
2411 bool has_multiple = mod.second.size() > 1;
2412
2413 for( int i = 0; i < (int) mod.second.size(); ++i )
2414 {
2415 auto& src = mod.second[i];
2416
2417 FOOTPRINT* fp = new FOOTPRINT( aBoard );
2418
2419 wxString mod_ref = src->name;
2420 wxString lib_ref = m_filename.GetName();
2421
2422 if( has_multiple )
2423 mod_ref.Append( wxString::Format( wxT( "_%d" ), i ) );
2424
2425 ReplaceIllegalFileNameChars( lib_ref, '_' );
2426 ReplaceIllegalFileNameChars( mod_ref, '_' );
2427
2428 wxString key = !lib_ref.empty() ? lib_ref + wxT( ":" ) + mod_ref : mod_ref;
2429
2430 LIB_ID fpID;
2431 fpID.Parse( key, true );
2432 fp->SetFPID( fpID );
2433
2434 fp->SetPosition( VECTOR2I( src->x, src->y ) );
2435 fp->SetOrientationDegrees( -src->rotate );
2436
2437 // KiCad netlisting requires parts to have non-digit + digit annotation.
2438 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source
2439 // designator
2440 wxString reference = src->refdes;
2441
2442 if( !std::isalpha( src->refdes[0] ) )
2443 reference.Prepend( "UNK" );
2444
2445 fp->SetReference( reference );
2446
2447 fp->SetValue( src->value );
2448 fp->Value().SetLayer( F_Fab );
2449 fp->Value().SetVisible( false );
2450
2451 // Set refdes invisible until we find the text for it
2452 // (otherwise we'll plonk a default-sized ref-des on the silkscreen layer
2453 // which wasn't there in the imported file)
2454 fp->Reference().SetVisible( false );
2455
2456 for( auto& ref : refdes )
2457 {
2458 const GRAPHIC_TEXT& lsrc =
2459 static_cast<const GRAPHIC_TEXT&>( **ref->segment.begin() );
2460
2461 if( lsrc.text == src->refdes )
2462 {
2463 PCB_TEXT* txt = nullptr;
2464 PCB_LAYER_ID layer = getLayer( ref->layer );
2465
2466 if( !IsPcbLayer( layer ) )
2467 {
2468 wxLogTrace( traceFabmaster, wxS( "The layer %s is not mapped?" ),
2469 ref->layer.c_str() );
2470 continue;
2471 }
2472
2473 if( layer == F_SilkS || layer == B_SilkS )
2474 txt = &( fp->Reference() );
2475 else
2476 txt = new PCB_TEXT( fp );
2477
2478 OPT_VECTOR2I flip_point = std::nullopt;
2479 if( src->mirror )
2480 flip_point = VECTOR2I( src->x, src->y );
2481
2482 const EDA_ANGLE fp_angle = EDA_ANGLE( lsrc.rotation ).Normalized();
2483 txt->SetTextAngle( fp_angle );
2484
2485 setupText( lsrc, layer, *txt, *aBoard, flip_point );
2486
2487 if( txt != &fp->Reference() )
2488 fp->Add( txt, ADD_MODE::APPEND );
2489 }
2490 }
2491
2494 fp->SetLayer( F_Cu );
2495
2496 auto gr_it = comp_graphics.find( src->refdes );
2497
2498 if( gr_it != comp_graphics.end() )
2499 {
2500 for( auto& gr_ref : gr_it->second )
2501 {
2502 auto& graphic = gr_ref.second;
2503
2504 for( auto& seg : *graphic.elements )
2505 {
2506 PCB_LAYER_ID layer = Dwgs_User;
2507
2508 if( IsPcbLayer( getLayer( seg->layer ) ) )
2509 layer = getLayer( seg->layer );
2510
2511 STROKE_PARAMS defaultStroke( ds.GetLineThickness( layer ) );
2512
2513 switch( seg->shape )
2514 {
2515 case GR_SHAPE_LINE:
2516 {
2517 const GRAPHIC_LINE* lsrc = static_cast<const GRAPHIC_LINE*>( seg.get() );
2518
2519 PCB_SHAPE* line = new PCB_SHAPE( fp, SHAPE_T::SEGMENT );
2520
2521 if( src->mirror )
2522 {
2523 line->SetLayer( aBoard->FlipLayer( layer ) );
2524 line->SetStart( VECTOR2I( lsrc->start_x, 2 * src->y - lsrc->start_y ) );
2525 line->SetEnd( VECTOR2I( lsrc->end_x, 2 * src->y - lsrc->end_y ) );
2526 }
2527 else
2528 {
2529 line->SetLayer( layer );
2530 line->SetStart( VECTOR2I( lsrc->start_x, lsrc->start_y ) );
2531 line->SetEnd( VECTOR2I( lsrc->end_x, lsrc->end_y ) );
2532 }
2533
2535
2536 if( lsrc->width == 0 )
2537 line->SetStroke( defaultStroke );
2538
2539 fp->Add( line, ADD_MODE::APPEND );
2540 break;
2541 }
2542
2543 case GR_SHAPE_CIRCLE:
2544 {
2545 const GRAPHIC_ARC& lsrc = static_cast<const GRAPHIC_ARC&>( *seg );
2546
2548
2549 circle->SetLayer( layer );
2550 circle->SetCenter( VECTOR2I( lsrc.center_x, lsrc.center_y ) );
2551 circle->SetEnd( VECTOR2I( lsrc.end_x, lsrc.end_y ) );
2552 circle->SetWidth( lsrc.width );
2553
2554 if( IsBackLayer( layer ) )
2555 {
2556 // Circles seem to have a flip around the FP origin that lines don't have
2557 const VECTOR2I fp_orig = fp->GetPosition();
2558 circle->Mirror( fp_orig, FLIP_DIRECTION::TOP_BOTTOM );
2559 }
2560
2561 if( lsrc.width == 0 )
2562 {
2563 // It seems that 0-width circles on DISPLAY_T/B layers are filled
2564 // (but not, say, SILKSCREEN_T/B).
2565 // There is an oblique reference to something like this here:
2566 // https://github.com/plusea/EAGLE/blob/master/ulp/fabmaster.ulp
2567 if( lsrc.layer == "DISPLAY_TOP" || lsrc.layer == "DISPLAY_BOTTOM" )
2568 circle->SetFilled( true );
2569 else
2570 circle->SetWidth( ds.GetLineThickness( circle->GetLayer() ) );
2571 }
2572
2573 if( src->mirror )
2574 circle->Flip( circle->GetCenter(), FLIP_DIRECTION::TOP_BOTTOM );
2575
2576 fp->Add( circle, ADD_MODE::APPEND );
2577 break;
2578 }
2579
2580 case GR_SHAPE_ARC:
2581 {
2582 const GRAPHIC_ARC* lsrc = static_cast<const GRAPHIC_ARC*>( seg.get() );
2583
2584 std::unique_ptr<PCB_SHAPE> arc =
2585 std::make_unique<PCB_SHAPE>( fp, SHAPE_T::ARC );
2586
2587 SHAPE_ARC sarc = lsrc->result;
2588
2589 if( IsBackLayer( layer ) )
2590 {
2591 // Arcs seem to have a vertical flip around the FP origin that lines don't have
2592 // and are also flipped around their center (this is a best guess at the transformation)
2593 const VECTOR2I fp_orig = fp->GetPosition();
2594 sarc.Mirror( fp_orig, FLIP_DIRECTION::TOP_BOTTOM );
2596 }
2597
2598 arc->SetLayer( layer );
2599 arc->SetArcGeometry( sarc.GetP0(), sarc.GetArcMid(), sarc.GetP1() );
2600 arc->SetStroke( STROKE_PARAMS( lsrc->width, LINE_STYLE::SOLID ) );
2601
2602 if( lsrc->width == 0 )
2603 arc->SetStroke( defaultStroke );
2604
2605 if( src->mirror )
2606 arc->Flip( arc->GetCenter(), FLIP_DIRECTION::TOP_BOTTOM );
2607
2608 fp->Add( arc.release(), ADD_MODE::APPEND );
2609 break;
2610 }
2611
2612 case GR_SHAPE_RECTANGLE:
2613 {
2614 const GRAPHIC_RECTANGLE *lsrc =
2615 static_cast<const GRAPHIC_RECTANGLE*>( seg.get() );
2616
2617 PCB_SHAPE* rect = new PCB_SHAPE( fp, SHAPE_T::RECTANGLE );
2618
2619 if( src->mirror )
2620 {
2621 rect->SetLayer( aBoard->FlipLayer( layer ) );
2622 rect->SetStart( VECTOR2I( lsrc->start_x, 2 * src->y - lsrc->start_y ) );
2623 rect->SetEnd( VECTOR2I( lsrc->end_x, 2 * src->y - lsrc->end_y ) );
2624 }
2625 else
2626 {
2627 rect->SetLayer( layer );
2628 rect->SetStart( VECTOR2I( lsrc->start_x, lsrc->start_y ) );
2629 rect->SetEnd( VECTOR2I( lsrc->end_x, lsrc->end_y ) );
2630 }
2631
2632 rect->SetStroke( defaultStroke );
2633
2634 fp->Add( rect, ADD_MODE::APPEND );
2635 break;
2636 }
2637
2638 case GR_SHAPE_TEXT:
2639 {
2640 const GRAPHIC_TEXT& lsrc = static_cast<const GRAPHIC_TEXT&>( *seg );
2641
2642 std::unique_ptr<PCB_TEXT> txt = std::make_unique<PCB_TEXT>( fp );
2643
2644 OPT_VECTOR2I flip_point;
2645
2646 if( src->mirror )
2647 flip_point = VECTOR2I( src->x, src->y );
2648
2649 setupText( lsrc, layer, *txt, *aBoard, flip_point );
2650
2651 // FABMASTER doesn't have visibility flags but layers that are not silk
2652 // should be hidden by default to prevent clutter.
2653 if( txt->GetLayer() != F_SilkS && txt->GetLayer() != B_SilkS )
2654 {
2655 PCB_FIELD* field = new PCB_FIELD( *txt, FIELD_T::USER );
2656 field->SetVisible( false );
2657 fp->Add( field, ADD_MODE::APPEND );
2658 }
2659 else
2660 {
2661 fp->Add( txt.release(), ADD_MODE::APPEND );
2662 }
2663
2664 break;
2665 }
2666
2667 default:
2668 continue;
2669 }
2670 }
2671 }
2672 }
2673
2674 auto pin_it = pins.find( src->refdes );
2675
2676 // If no pins found by refdes, try by symbol name (for fabmaster exports without netlists)
2677 if( pin_it == pins.end() )
2678 pin_it = pins.find( src->name );
2679
2680 if( pin_it != pins.end() )
2681 {
2682 for( auto& pin : pin_it->second )
2683 {
2684 auto pin_net_it = pin_nets.find( std::make_pair( pin->refdes,
2685 pin->pin_number ) );
2686 auto padstack = pads.find( pin->padstack );
2687 std::string netname = "";
2688
2689 if( pin_net_it != pin_nets.end() )
2690 netname = pin_net_it->second.name;
2691
2692 auto net_it = netinfo.find( netname );
2693
2694 std::unique_ptr<PAD> newpad = std::make_unique<PAD>( fp );
2695
2696 if( net_it != netinfo.end() )
2697 newpad->SetNet( net_it->second );
2698 else
2699 newpad->SetNetCode( 0 );
2700
2701 newpad->SetX( pin->pin_x );
2702
2703 if( src->mirror )
2704 newpad->SetY( 2 * src->y - pin->pin_y );
2705 else
2706 newpad->SetY( pin->pin_y );
2707
2708 newpad->SetNumber( pin->pin_number );
2709
2710 if( padstack == pads.end() )
2711 {
2712 reportError( _( "Unable to locate padstack %s in file %s\n" ),
2713 pin->padstack.c_str(), aBoard->GetFileName().wc_str() );
2714 continue;
2715 }
2716 else
2717 {
2718 auto& pad = padstack->second;
2719
2720 // Determine if per-layer shapes differ and need
2721 // FRONT_INNER_BACK mode
2722 const FM_PAD_LAYER* front_layer = nullptr;
2723 const FM_PAD_LAYER* back_layer = nullptr;
2724 const FM_PAD_LAYER* inner_layer = nullptr;
2725
2726 for( const auto& [layer_name, layer_data] : pad.layer_shapes )
2727 {
2728 auto layer_it = layers.find( layer_name );
2729
2730 if( layer_it == layers.end() || !layer_it->second.conductive )
2731 continue;
2732
2733 PCB_LAYER_ID kicad_layer =
2734 static_cast<PCB_LAYER_ID>( layer_it->second.layerid );
2735
2736 if( kicad_layer == F_Cu )
2737 front_layer = &layer_data;
2738 else if( kicad_layer == B_Cu )
2739 back_layer = &layer_data;
2740 else if( IsCopperLayer( kicad_layer ) )
2741 inner_layer = &layer_data;
2742 }
2743
2744 auto layersDiffer = []( const FM_PAD_LAYER& aA, const FM_PAD_LAYER& aB )
2745 {
2746 return aA.shape != aB.shape
2747 || aA.width != aB.width
2748 || aA.height != aB.height
2749 || aA.x_offset != aB.x_offset
2750 || aA.y_offset != aB.y_offset
2751 || aA.is_octogon != aB.is_octogon
2752 || aA.custom_name != aB.custom_name;
2753 };
2754
2755 std::vector<const FM_PAD_LAYER*> copper_defs;
2756
2757 for( const FM_PAD_LAYER* def : { front_layer, inner_layer, back_layer } )
2758 {
2759 if( def )
2760 copper_defs.push_back( def );
2761 }
2762
2763 bool needs_padstack = false;
2764
2765 for( size_t ii = 1; ii < copper_defs.size(); ++ii )
2766 {
2767 if( layersDiffer( *copper_defs[0], *copper_defs[ii] ) )
2768 {
2769 needs_padstack = true;
2770 break;
2771 }
2772 }
2773
2774 if( needs_padstack )
2775 newpad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
2776
2777 auto applyLayerShape = [&]( PCB_LAYER_ID aLayer,
2778 const FM_PAD_LAYER& aLayerData )
2779 {
2780 newpad->SetShape( aLayer, aLayerData.shape );
2781
2782 if( aLayerData.shape == PAD_SHAPE::CIRCLE )
2783 {
2784 newpad->SetSize( aLayer,
2785 VECTOR2I( aLayerData.width, aLayerData.width ) );
2786 }
2787 else
2788 {
2789 newpad->SetSize( aLayer,
2790 VECTOR2I( aLayerData.width, aLayerData.height ) );
2791 }
2792
2793 newpad->SetOffset( aLayer,
2794 VECTOR2I( aLayerData.x_offset, aLayerData.y_offset ) );
2795 };
2796
2797 if( needs_padstack )
2798 {
2799 if( front_layer )
2800 applyLayerShape( F_Cu, *front_layer );
2801
2802 if( back_layer )
2803 applyLayerShape( B_Cu, *back_layer );
2804
2805 if( inner_layer )
2806 applyLayerShape( PADSTACK::INNER_LAYERS, *inner_layer );
2807 else if( front_layer )
2808 applyLayerShape( PADSTACK::INNER_LAYERS, *front_layer );
2809
2810 if( pad.shape == PAD_SHAPE::CUSTOM )
2811 {
2812 reportWarning( _( "Pad '%s' has custom shape with per-layer "
2813 "geometry; custom primitives not supported "
2814 "in padstack mode." ),
2815 pad.name );
2816 }
2817 }
2818 else if( pad.shape == PAD_SHAPE::CUSTOM )
2819 {
2820 newpad->SetShape( PADSTACK::ALL_LAYERS, pad.shape );
2821
2822 // Choose the smaller dimension to ensure the base pad
2823 // is fully hidden by the custom pad
2824 int pad_size = std::min( pad.width, pad.height );
2825
2826 newpad->SetSize( PADSTACK::ALL_LAYERS,
2827 VECTOR2I( pad_size / 2, pad_size / 2 ) );
2828
2829 std::string custom_name = pad.custom_name + "_" + pin->refdes + "_" +
2830 pin->pin_number;
2831 auto custom_it = pad_shapes.find( custom_name );
2832
2833 if( custom_it != pad_shapes.end() )
2834 {
2835
2836 SHAPE_POLY_SET poly_outline;
2837 int last_subseq = 0;
2838 int hole_idx = -1;
2839
2840 poly_outline.NewOutline();
2841
2842 // Custom pad shapes have a group of elements
2843 // that are a list of graphical polygons
2844 for( const auto& el : (*custom_it).second.elements )
2845 {
2846 // For now, we are only processing the custom pad for the
2847 // top layer
2848 PCB_LAYER_ID primary_layer = src->mirror ? B_Cu : F_Cu;
2849
2850 if( getLayer( ( *( el.second.begin() ) )->layer ) != primary_layer )
2851 continue;
2852
2853 for( const auto& seg : el.second )
2854 {
2855 if( seg->subseq > 0 || seg->subseq != last_subseq )
2856 {
2857 poly_outline.Polygon(0).back().SetClosed( true );
2858 hole_idx = poly_outline.AddHole( SHAPE_LINE_CHAIN{} );
2859 }
2860
2861 if( seg->shape == GR_SHAPE_LINE )
2862 {
2863 const GRAPHIC_LINE* line_seg = static_cast<const GRAPHIC_LINE*>( seg.get() );
2864
2865 if( poly_outline.VertexCount( 0, hole_idx ) == 0 )
2866 poly_outline.Append( line_seg->start_x, line_seg->start_y,
2867 0, hole_idx );
2868
2869 poly_outline.Append( line_seg->end_x, line_seg->end_y, 0,
2870 hole_idx );
2871 }
2872 else if( seg->shape == GR_SHAPE_ARC )
2873 {
2874 const GRAPHIC_ARC* arc_seg = static_cast<const GRAPHIC_ARC*>( seg.get() );
2875 SHAPE_LINE_CHAIN& chain = poly_outline.Hole( 0, hole_idx );
2876
2877 chain.Append( arc_seg->result );
2878 }
2879 }
2880 }
2881
2882 if( poly_outline.OutlineCount() < 1
2883 || poly_outline.Outline( 0 ).PointCount() < 3 )
2884 {
2885 reportError( _( "Invalid custom pad '%s'. Replacing with "
2886 "circular pad." ),
2887 custom_name.c_str() );
2888 newpad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
2889 }
2890 else
2891 {
2892 poly_outline.Fracture();
2893
2894 poly_outline.Move( -newpad->GetPosition() );
2895
2896 if( src->mirror )
2897 {
2898 poly_outline.Mirror( VECTOR2I( 0, ( pin->pin_y - src->y ) ),
2900 poly_outline.Rotate( EDA_ANGLE( src->rotate - pin->rotation,
2901 DEGREES_T ) );
2902 }
2903 else
2904 {
2905 poly_outline.Rotate( EDA_ANGLE( -src->rotate + pin->rotation,
2906 DEGREES_T ) );
2907 }
2908
2909 newpad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, poly_outline, 0, true );
2910 }
2911
2912 SHAPE_POLY_SET mergedPolygon;
2913 newpad->MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &mergedPolygon );
2914
2915 if( mergedPolygon.OutlineCount() > 1 )
2916 {
2917 reportError( _( "Invalid custom pad '%s'. Replacing with "
2918 "circular pad." ),
2919 custom_name.c_str() );
2920 newpad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
2921 }
2922 }
2923 else
2924 {
2925 reportError( _( "Could not find custom pad '%s'." ),
2926 custom_name.c_str() );
2927 }
2928 }
2929 else
2930 {
2931 newpad->SetShape( PADSTACK::ALL_LAYERS, pad.shape );
2932 newpad->SetSize( PADSTACK::ALL_LAYERS,
2933 VECTOR2I( pad.width, pad.height ) );
2934 }
2935
2936 if( !needs_padstack && ( pad.x_offset || pad.y_offset ) )
2937 {
2938 newpad->SetOffset( PADSTACK::ALL_LAYERS,
2939 VECTOR2I( pad.x_offset, pad.y_offset ) );
2940 }
2941
2942 if( pad.drill )
2943 {
2944 if( pad.plated )
2945 {
2946 newpad->SetAttribute( PAD_ATTRIB::PTH );
2947 newpad->SetLayerSet( PAD::PTHMask() );
2948 }
2949 else
2950 {
2951 newpad->SetAttribute( PAD_ATTRIB::NPTH );
2952 newpad->SetLayerSet( PAD::UnplatedHoleMask() );
2953 }
2954
2955 if( pad.drill_size_x == pad.drill_size_y )
2956 newpad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
2957 else
2958 newpad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
2959
2960 newpad->SetDrillSize( VECTOR2I( pad.drill_size_x, pad.drill_size_y ) );
2961 }
2962 else
2963 {
2964 newpad->SetAttribute( PAD_ATTRIB::SMD );
2965
2966 if( pad.top )
2967 newpad->SetLayerSet( PAD::SMDMask() );
2968 else if( pad.bottom )
2969 newpad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
2970 }
2971 }
2972
2973 if( src->mirror )
2974 newpad->SetOrientation( EDA_ANGLE( -src->rotate + pin->rotation,
2975 DEGREES_T ) );
2976 else
2977 newpad->SetOrientation( EDA_ANGLE( src->rotate - pin->rotation,
2978 DEGREES_T ) );
2979
2980 if( newpad->GetSizeX() > 0 || newpad->GetSizeY() > 0 )
2981 {
2982 fp->Add( newpad.release(), ADD_MODE::APPEND );
2983 }
2984 else
2985 {
2986 reportError( _( "Invalid zero-sized pad ignored in\nfile: %s" ),
2987 aBoard->GetFileName().wc_str() );
2988 }
2989 }
2990 }
2991
2992 if( src->mirror )
2993 {
2994 fp->SetOrientationDegrees( 180.0 - src->rotate );
2996 }
2997
2998 aBoard->Add( fp, ADD_MODE::APPEND );
2999 }
3000 }
3001
3002 return true;
3003}
3004
3005
3007{
3008 LSET layer_set;
3009
3011 layer_set |= LSET::AllTechMask() | LSET::UserMask();
3012
3013 for( auto& layer : layers )
3014 {
3015 checkpoint();
3016
3017 if( layer.second.layerid >= PCBNEW_LAYER_ID_START )
3018 layer_set.set( layer.second.layerid );
3019 }
3020
3021 aBoard->SetEnabledLayers( layer_set );
3022
3023 for( auto& layer : layers )
3024 {
3025 if( layer.second.conductive )
3026 {
3027 aBoard->SetLayerName( static_cast<PCB_LAYER_ID>( layer.second.layerid ),
3028 layer.second.name );
3029 }
3030 }
3031
3032 return true;
3033}
3034
3035
3037{
3038 const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName();
3039 const auto& ds = aBoard->GetDesignSettings();
3040
3041 // Build a sorted list of conductive layers by their layer id for via span determination
3042 std::vector<const FABMASTER_LAYER*> conductiveLayers;
3043
3044 for( const auto& layer : layers )
3045 {
3046 if( layer.second.conductive )
3047 conductiveLayers.push_back( &layer.second );
3048 }
3049
3050 std::sort( conductiveLayers.begin(), conductiveLayers.end(), FABMASTER_LAYER::BY_ID() );
3051
3052 for( auto& via : vias )
3053 {
3054 checkpoint();
3055
3056 auto net_it = netinfo.find( via->net );
3057 auto padstack = pads.find( via->padstack );
3058
3059 PCB_VIA* new_via = new PCB_VIA( aBoard );
3060
3061 new_via->SetPosition( VECTOR2I( via->x, via->y ) );
3062
3063 if( net_it != netinfo.end() )
3064 new_via->SetNet( net_it->second );
3065
3066 if( padstack == pads.end() )
3067 {
3068 new_via->SetDrillDefault();
3069
3070 if( !ds.m_ViasDimensionsList.empty() )
3071 {
3072 new_via->SetWidth( PADSTACK::ALL_LAYERS, ds.m_ViasDimensionsList[0].m_Diameter );
3073 new_via->SetDrill( ds.m_ViasDimensionsList[0].m_Drill );
3074 }
3075 else
3076 {
3077 new_via->SetDrillDefault();
3078 new_via->SetWidth( PADSTACK::ALL_LAYERS, ds.m_ViasMinSize );
3079 }
3080 }
3081 else
3082 {
3083 new_via->SetDrill( padstack->second.drill_size_x );
3084 new_via->SetWidth( PADSTACK::ALL_LAYERS, padstack->second.width );
3085
3086 const std::set<std::string>& viaLayers = padstack->second.copper_layers;
3087
3088 if( viaLayers.size() >= 2 )
3089 {
3090 // Find the first and last conductive layers that have annular rings
3091 const FABMASTER_LAYER* topLayer = nullptr;
3092 const FABMASTER_LAYER* botLayer = nullptr;
3093
3094 for( const FABMASTER_LAYER* layer : conductiveLayers )
3095 {
3096 if( viaLayers.count( layer->name ) )
3097 {
3098 if( !topLayer )
3099 topLayer = layer;
3100
3101 botLayer = layer;
3102 }
3103 }
3104
3105 if( topLayer && botLayer && topLayer != botLayer )
3106 {
3107 PCB_LAYER_ID topLayerId = static_cast<PCB_LAYER_ID>( topLayer->layerid );
3108 PCB_LAYER_ID botLayerId = static_cast<PCB_LAYER_ID>( botLayer->layerid );
3109
3110 // Check if this spans all copper layers
3111 bool isThrough = ( topLayerId == F_Cu && botLayerId == B_Cu );
3112
3113 if( !isThrough )
3114 {
3115 // Blind via connects to an outer layer (F_Cu or B_Cu)
3116 // Buried via connects only to inner layers
3117 if( topLayerId == F_Cu || botLayerId == B_Cu )
3118 new_via->SetViaType( VIATYPE::BLIND );
3119 else
3120 new_via->SetViaType( VIATYPE::BURIED );
3121
3122 new_via->SetLayerPair( topLayerId, botLayerId );
3123 }
3124 }
3125 }
3126 }
3127
3128 aBoard->Add( new_via, ADD_MODE::APPEND );
3129 }
3130
3131 return true;
3132}
3133
3134
3136{
3137 for( auto& net : netnames )
3138 {
3139 checkpoint();
3140
3141 NETINFO_ITEM *newnet = new NETINFO_ITEM( aBoard, net );
3142 aBoard->Add( newnet, ADD_MODE::APPEND );
3143 }
3144
3145 return true;
3146}
3147
3148
3149bool FABMASTER::loadEtch( BOARD* aBoard, const std::unique_ptr<FABMASTER::TRACE>& aLine )
3150{
3151 const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName();
3152 auto net_it = netinfo.find( aLine->netname );
3153
3154 for( const auto& seg : aLine->segment )
3155 {
3156 PCB_LAYER_ID layer = getLayer( seg->layer );
3157
3158 if( IsCopperLayer( layer ) )
3159 {
3160 switch( seg->shape )
3161 {
3162 case GR_SHAPE_LINE:
3163 {
3164 const GRAPHIC_LINE* src = static_cast<const GRAPHIC_LINE*>( seg.get() );
3165
3166 PCB_TRACK* trk = new PCB_TRACK( aBoard );
3167
3168 trk->SetLayer( layer );
3169 trk->SetStart( VECTOR2I( src->start_x, src->start_y ) );
3170 trk->SetEnd( VECTOR2I( src->end_x, src->end_y ) );
3171 trk->SetWidth( src->width );
3172
3173 if( net_it != netinfo.end() )
3174 trk->SetNet( net_it->second );
3175
3176 aBoard->Add( trk, ADD_MODE::APPEND );
3177 break;
3178 }
3179
3180 case GR_SHAPE_ARC:
3181 {
3182 const GRAPHIC_ARC* src = static_cast<const GRAPHIC_ARC*>( seg.get() );
3183
3184 PCB_ARC* trk = new PCB_ARC( aBoard, &src->result );
3185 trk->SetLayer( layer );
3186 trk->SetWidth( src->width );
3187
3188 if( net_it != netinfo.end() )
3189 trk->SetNet( net_it->second );
3190
3191 aBoard->Add( trk, ADD_MODE::APPEND );
3192 break;
3193 }
3194
3195 default:
3196 // Defer to the generic graphics factory
3197 for( std::unique_ptr<BOARD_ITEM>& new_item : createBoardItems( *aBoard, layer, *seg ) )
3198 aBoard->Add( new_item.release(), ADD_MODE::APPEND );
3199
3200 break;
3201 }
3202 }
3203 else
3204 {
3205 reportError( _( "Expecting etch data to be on copper layer. Row found on layer '%s'" ),
3206 seg->layer.c_str() );
3207 }
3208 }
3209
3210 return true;
3211}
3212
3213
3215{
3216 SHAPE_POLY_SET poly_outline;
3217 int last_subseq = 0;
3218 int hole_idx = -1;
3219
3220 poly_outline.NewOutline();
3221
3222 for( const auto& seg : aElement )
3223 {
3224 if( seg->subseq > 0 || seg->subseq != last_subseq )
3225 hole_idx = poly_outline.AddHole( SHAPE_LINE_CHAIN{} );
3226
3227 if( seg->shape == GR_SHAPE_LINE )
3228 {
3229 const GRAPHIC_LINE* src = static_cast<const GRAPHIC_LINE*>( seg.get() );
3230
3231 if( poly_outline.VertexCount( 0, hole_idx ) == 0 )
3232 poly_outline.Append( src->start_x, src->start_y, 0, hole_idx );
3233
3234 poly_outline.Append( src->end_x, src->end_y, 0, hole_idx );
3235 }
3236 else if( seg->shape == GR_SHAPE_ARC || seg->shape == GR_SHAPE_CIRCLE )
3237 {
3238 const GRAPHIC_ARC* src = static_cast<const GRAPHIC_ARC*>( seg.get() );
3239 SHAPE_LINE_CHAIN& chain = poly_outline.Hole( 0, hole_idx );
3240
3241 chain.Append( src->result );
3242 }
3243 }
3244
3245 return poly_outline;
3246}
3247
3248
3249/*
3250 * The format doesn't seem to distinguish between open and closed polygons.
3251 * So the best we can really do is to try to detect an open polyline by looking
3252 * for a closed subsequence 0.
3253 *
3254 * For example three lines like this will be open:
3255 *
3256 * +----
3257 * |
3258 * +----
3259 *
3260 * But four lines will be closed:
3261 *
3262 * +----+
3263 * | |
3264 * +----+
3265 *
3266 * This means that "closed" zones (which can have fill patterns in Allegro)
3267 * and "a bunch of lines, which happen to be closed) are not distinguishable,
3268 * but that just seems to be information thrown away on export to FABMASTER.
3269 */
3271{
3272 if( aLine.segment.size() == 0 )
3273 return true;
3274
3275 // First and last item in the first subsequence
3276 const GRAPHIC_ITEM* first = nullptr;
3277 const GRAPHIC_ITEM* last = nullptr;
3278 int first_subseq = -1;
3279 bool have_multiple_subseqs = false;
3280
3281 for( const std::unique_ptr<GRAPHIC_ITEM>& gr_item : aLine.segment )
3282 {
3283 if( first == nullptr )
3284 {
3285 first = gr_item.get();
3286 first_subseq = gr_item->subseq;
3287 }
3288 else if( gr_item->subseq == first_subseq )
3289 {
3290 last = gr_item.get();
3291 }
3292 else
3293 {
3294 have_multiple_subseqs = true;
3295 break;
3296 }
3297 }
3298
3299 // Should have at least one item
3300 wxCHECK( first, true );
3301
3302 // First subsequence was only one item
3303 if( !last )
3304 {
3305 // It can still be a closed polygon if the outer border is a circle
3306 // and there are inner shapes.
3307 if( first->shape == GR_SHAPE_CIRCLE && have_multiple_subseqs )
3308 return false;
3309
3310 return true;
3311 }
3312
3313 const VECTOR2I start{ first->start_x, first->start_y };
3314
3315 // It's not always possible to find an end
3317
3318 switch( last->shape )
3319 {
3320 case GR_SHAPE_LINE:
3321 {
3322 const GRAPHIC_LINE& line = static_cast<const GRAPHIC_LINE&>( *last );
3323 end = VECTOR2I{ line.end_x, line.end_y };
3324 break;
3325 }
3326
3327 case GR_SHAPE_ARC:
3328 {
3329 const GRAPHIC_ARC& arc = static_cast<const GRAPHIC_ARC&>( *last );
3330 end = VECTOR2I{ arc.end_x, arc.end_y };
3331 break;
3332 }
3333
3334 default:
3335 // These shapes don't have "ends" that make sense for a polyline
3336 break;
3337 }
3338
3339 // This looks like a closed polygon
3340 if( end.has_value() && start == end )
3341 return false;
3342
3343 // Open polyline
3344 return true;
3345}
3346
3347
3348std::vector<std::unique_ptr<BOARD_ITEM>>
3350{
3351 std::vector<std::unique_ptr<BOARD_ITEM>> new_items;
3352
3353 const BOARD_DESIGN_SETTINGS& boardSettings = aBoard.GetDesignSettings();
3354 const STROKE_PARAMS defaultStroke( boardSettings.GetLineThickness( aLayer ) );
3355
3356 const auto setShapeParameters = [&]( PCB_SHAPE& aShape )
3357 {
3358 aShape.SetStroke( STROKE_PARAMS( aGraphic.width, LINE_STYLE::SOLID ) );
3359
3360 if( aShape.GetWidth() == 0 )
3361 aShape.SetStroke( defaultStroke );
3362 };
3363
3364 switch( aGraphic.shape )
3365 {
3366 case GR_SHAPE_TEXT:
3367 {
3368 const GRAPHIC_TEXT& src = static_cast<const GRAPHIC_TEXT&>( aGraphic );
3369
3370 auto new_text = std::make_unique<PCB_TEXT>( &aBoard );
3371
3372 if( IsBackLayer( aLayer ) )
3373 {
3374 new_text->SetMirrored( true );
3375 }
3376
3377 setupText( src, aLayer, *new_text, aBoard, std::nullopt );
3378
3379 new_items.emplace_back( std::move( new_text ) );
3380 break;
3381 }
3382
3383 case GR_SHAPE_CROSS:
3384 {
3385 const GRAPHIC_CROSS& src = static_cast<const GRAPHIC_CROSS&>( aGraphic );
3386
3387 const VECTOR2I c{ src.start_x, src.start_y };
3388 const VECTOR2I s{ src.size_x, src.size_y };
3389
3390 const std::vector<SEG> segs = KIGEOM::MakeCrossSegments( c, s, ANGLE_0 );
3391
3392 for( const SEG& seg : segs )
3393 {
3394 auto line = std::make_unique<PCB_SHAPE>( &aBoard );
3395 line->SetShape( SHAPE_T::SEGMENT );
3396 line->SetStart( seg.A );
3397 line->SetEnd( seg.B );
3398
3399 setShapeParameters( *line );
3400 new_items.emplace_back( std::move( line ) );
3401 }
3402 break;
3403 }
3404
3405 default:
3406 {
3407 // Simple single shape
3408 auto new_shape = std::make_unique<PCB_SHAPE>( &aBoard );
3409
3410 setShapeParameters( *new_shape );
3411
3412 switch( aGraphic.shape )
3413 {
3414 case GR_SHAPE_LINE:
3415 {
3416 const GRAPHIC_LINE& src = static_cast<const GRAPHIC_LINE&>( aGraphic );
3417
3418 new_shape->SetShape( SHAPE_T::SEGMENT );
3419 new_shape->SetStart( VECTOR2I( src.start_x, src.start_y ) );
3420 new_shape->SetEnd( VECTOR2I( src.end_x, src.end_y ) );
3421
3422 break;
3423 }
3424
3425 case GR_SHAPE_ARC:
3426 {
3427 const GRAPHIC_ARC& src = static_cast<const GRAPHIC_ARC&>( aGraphic );
3428
3429 new_shape->SetShape( SHAPE_T::ARC );
3430 new_shape->SetArcGeometry( src.result.GetP0(), src.result.GetArcMid(),
3431 src.result.GetP1() );
3432 break;
3433 }
3434
3435 case GR_SHAPE_CIRCLE:
3436 {
3437 const GRAPHIC_ARC& src = static_cast<const GRAPHIC_ARC&>( aGraphic );
3438
3439 new_shape->SetShape( SHAPE_T::CIRCLE );
3440 new_shape->SetCenter( VECTOR2I( src.center_x, src.center_y ) );
3441 new_shape->SetRadius( src.radius );
3442 break;
3443 }
3444
3445 case GR_SHAPE_RECTANGLE:
3446 {
3447 const GRAPHIC_RECTANGLE& src = static_cast<const GRAPHIC_RECTANGLE&>( aGraphic );
3448
3449 new_shape->SetShape( SHAPE_T::RECTANGLE );
3450 new_shape->SetStart( VECTOR2I( src.start_x, src.start_y ) );
3451 new_shape->SetEnd( VECTOR2I( src.end_x, src.end_y ) );
3452
3453 new_shape->SetFilled( src.fill );
3454 break;
3455 }
3456
3457 case GR_SHAPE_POLYGON:
3458 {
3459 const GRAPHIC_POLYGON& src = static_cast<const GRAPHIC_POLYGON&>( aGraphic );
3460 new_shape->SetShape( SHAPE_T::POLY );
3461 new_shape->SetPolyPoints( src.m_pts );
3462 break;
3463 }
3464
3465 case GR_SHAPE_OBLONG:
3466 {
3467 // Create as a polygon, but we could also make a group of two lines and two arcs
3468 const GRAPHIC_OBLONG& src = static_cast<const GRAPHIC_OBLONG&>( aGraphic );
3469
3470 const VECTOR2I c{ src.start_x, src.start_y };
3471 VECTOR2I s = c;
3472 int w = 0;
3473
3474 if( src.oblong_x )
3475 {
3476 w = src.size_y;
3477 s -= VECTOR2I{ ( src.size_x - w ) / 2, 0 };
3478 }
3479 else
3480 {
3481 w = src.size_x;
3482 s -= VECTOR2I{ 0, ( src.size_y - w ) / 2 };
3483 }
3484
3485 SHAPE_SEGMENT seg( s, c - ( s - c ), w );
3486
3487 SHAPE_POLY_SET poly;
3488 seg.TransformToPolygon( poly, boardSettings.m_MaxError, ERROR_LOC::ERROR_INSIDE );
3489
3490 new_shape->SetShape( SHAPE_T::POLY );
3491 new_shape->SetPolyShape( poly );
3492 break;
3493 }
3494
3495 default:
3496 // Static context, so no reporter is reachable here. This is an internal dispatch
3497 // fallthrough rather than something the user can act on.
3498 wxLogTrace( traceFabmaster,
3499 wxT( "Unhandled shape type %d in polygon on layer %s, seq %d %d" ),
3500 aGraphic.shape, aGraphic.layer, aGraphic.seq, aGraphic.subseq );
3501 }
3502
3503 new_items.emplace_back( std::move( new_shape ) );
3504 }
3505 }
3506
3507 for( std::unique_ptr<BOARD_ITEM>& new_item : new_items )
3508 {
3509 new_item->SetLayer( aLayer );
3510 }
3511
3512 // If there's more than one, group them
3513 if( new_items.size() > 1 )
3514 {
3515 auto new_group = std::make_unique<PCB_GROUP>( &aBoard );
3516 for( std::unique_ptr<BOARD_ITEM>& new_item : new_items )
3517 {
3518 new_group->AddItem( new_item.get() );
3519 }
3520 new_items.emplace_back( std::move( new_group ) );
3521 }
3522
3523 return new_items;
3524}
3525
3526
3527bool FABMASTER::loadPolygon( BOARD* aBoard, const std::unique_ptr<FABMASTER::TRACE>& aLine )
3528{
3529 if( aLine->segment.empty() )
3530 return false;
3531
3532 PCB_LAYER_ID layer = Cmts_User;
3533
3534 const PCB_LAYER_ID new_layer = getLayer( aLine->layer );
3535
3536 if( IsPcbLayer( new_layer ) )
3537 layer = new_layer;
3538
3539 const bool is_open = traceIsOpen( *aLine );
3540
3541 if( is_open )
3542 {
3543 for( const auto& seg : aLine->segment )
3544 {
3545 for( std::unique_ptr<BOARD_ITEM>& new_item : createBoardItems( *aBoard, layer, *seg ) )
3546 {
3547 aBoard->Add( new_item.release(), ADD_MODE::APPEND );
3548 }
3549 }
3550 }
3551 else
3552 {
3553 STROKE_PARAMS defaultStroke( aBoard->GetDesignSettings().GetLineThickness( layer ) );
3554
3555 SHAPE_POLY_SET poly_outline = loadShapePolySet( aLine->segment );
3556
3557 poly_outline.Fracture();
3558
3559 if( poly_outline.OutlineCount() < 1 || poly_outline.COutline( 0 ).PointCount() < 3 )
3560 return false;
3561
3562 PCB_SHAPE* new_poly = new PCB_SHAPE( aBoard );
3563
3564 new_poly->SetShape( SHAPE_T::POLY );
3565 new_poly->SetLayer( layer );
3566
3567 // Polygons on the silk layer are filled but other layers are not/fill doesn't make sense
3568 if( layer == F_SilkS || layer == B_SilkS )
3569 {
3570 new_poly->SetFilled( true );
3571 new_poly->SetStroke( STROKE_PARAMS( 0 ) );
3572 }
3573 else
3574 {
3575 new_poly->SetStroke(
3576 STROKE_PARAMS( ( *( aLine->segment.begin() ) )->width, LINE_STYLE::SOLID ) );
3577
3578 if( new_poly->GetWidth() == 0 )
3579 new_poly->SetStroke( defaultStroke );
3580 }
3581
3582 new_poly->SetPolyShape( poly_outline );
3583 aBoard->Add( new_poly, ADD_MODE::APPEND );
3584 }
3585
3586 return true;
3587}
3588
3589
3590bool FABMASTER::loadZone( BOARD* aBoard, const std::unique_ptr<FABMASTER::TRACE>& aLine )
3591{
3592 if( aLine->segment.size() < 3 )
3593 return false;
3594
3595 SHAPE_POLY_SET* zone_outline = nullptr;
3596 ZONE* zone = nullptr;
3597
3598 const NETNAMES_MAP& netinfo = aBoard->GetNetInfo().NetsByName();
3599 auto net_it = netinfo.find( aLine->netname );
3600 PCB_LAYER_ID layer = Cmts_User;
3601 auto new_layer = getLayer( aLine->layer );
3602
3603 if( IsPcbLayer( new_layer ) )
3604 layer = new_layer;
3605
3606 zone = new ZONE( aBoard );
3607 zone_outline = new SHAPE_POLY_SET;
3608
3609 if( net_it != netinfo.end() )
3610 zone->SetNet( net_it->second );
3611
3612 if( aLine->layer == "ALL" )
3613 zone->SetLayerSet( aBoard->GetLayerSet() & LSET::AllCuMask() );
3614 else if( aLine->layer == "OUTER_LAYERS" )
3615 zone->SetLayerSet( aBoard->GetLayerSet() & LSET::ExternalCuMask() );
3616 else if( aLine->layer == "INNER_PLANE_LAYERS" || aLine->layer == "INNER_SIGNAL_LAYERS" )
3617 zone->SetLayerSet( aBoard->GetLayerSet() & LSET::InternalCuMask() );
3618 else
3619 zone->SetLayer( layer );
3620
3621 zone->SetIsRuleArea( false );
3622 zone->SetDoNotAllowTracks( false );
3623 zone->SetDoNotAllowVias( false );
3624 zone->SetDoNotAllowPads( false );
3625 zone->SetDoNotAllowFootprints( false );
3626 zone->SetDoNotAllowZoneFills( false );
3627
3628 if( aLine->lclass == "ROUTE KEEPOUT" )
3629 {
3630 // A bare Allegro route keepout excludes all routing objects, not just tracks.
3631 zone->SetIsRuleArea( true );
3632 zone->SetDoNotAllowTracks( true );
3633 zone->SetDoNotAllowVias( true );
3634 zone->SetDoNotAllowPads( true );
3635 zone->SetDoNotAllowZoneFills( true );
3636 }
3637 else if( aLine->lclass == "VIA KEEPOUT" )
3638 {
3639 zone->SetIsRuleArea( true );
3640 zone->SetDoNotAllowVias( true );
3641 }
3642 else if( aLine->lclass == "PACKAGE KEEPOUT" )
3643 {
3644 zone->SetIsRuleArea( true );
3645 zone->SetDoNotAllowFootprints( true );
3646 }
3647 else if( aLine->lclass == "ROUTE KEEPIN" || aLine->lclass == "PACKAGE KEEPIN"
3648 || aLine->lclass == "CONSTRAINT REGION" )
3649 {
3650 // KiCad has no keepin or constraint-region concept. Keep the shape as a named rule
3651 // area with no restrictions so it survives import without becoming unconnected copper.
3652 zone->SetIsRuleArea( true );
3653 zone->SetZoneName( wxString::FromUTF8( aLine->lclass.c_str() ) );
3654 }
3655 else
3656 {
3657 zone->SetAssignedPriority( 50 );
3658 }
3659
3660 zone->SetLocalClearance( 0 );
3662
3663 zone_outline->NewOutline();
3664
3665 std::unique_ptr<SHAPE_LINE_CHAIN> pending_hole = nullptr;
3666 SHAPE_LINE_CHAIN* active_chain = &zone_outline->Outline( 0 );
3667
3668 const auto add_hole_if_valid = [&]()
3669 {
3670 if( pending_hole )
3671 {
3672 pending_hole->SetClosed( true );
3673
3674 // If we get junk holes, assert, but don't add them to the zone, as that
3675 // will cause crashes later.
3676 if( !KIGEOM::AddHoleIfValid( *zone_outline, std::move( *pending_hole ) ) )
3677 {
3678 reportInfo( _( "Invalid hole with %d points in zone on layer %s with net %s" ),
3679 pending_hole->PointCount(), zone->GetLayerName(),
3680 zone->GetNetname() );
3681 }
3682
3683 pending_hole.reset();
3684 }
3685 };
3686
3687 int last_subseq = 0;
3688 for( const auto& seg : aLine->segment )
3689 {
3690 if( seg->subseq > 0 && seg->subseq != last_subseq )
3691 {
3692 // Don't knock holes in the BOUNDARY systems. These are the outer layers for
3693 // zone fills.
3694 if( aLine->lclass == "BOUNDARY" )
3695 break;
3696
3697 add_hole_if_valid();
3698 pending_hole = std::make_unique<SHAPE_LINE_CHAIN>();
3699 active_chain = pending_hole.get();
3700 last_subseq = seg->subseq;
3701 }
3702
3703 if( seg->shape == GR_SHAPE_LINE )
3704 {
3705 const GRAPHIC_LINE* src = static_cast<const GRAPHIC_LINE*>( seg.get() );
3706 const VECTOR2I start( src->start_x, src->start_y );
3707 const VECTOR2I end( src->end_x, src->end_y );
3708
3709 if( active_chain->PointCount() == 0 )
3710 {
3711 active_chain->Append( start );
3712 }
3713 else
3714 {
3715 const VECTOR2I& last = active_chain->CLastPoint();
3716
3717 // Not if this can ever happen, or what do if it does (add both points?).
3718 if( last != start )
3719 {
3720 reportError( _( "Outline seems discontinuous: last point was %s, "
3721 "start point of next segment is %s" ),
3722 last.Format(), start.Format() );
3723 }
3724 }
3725
3726 active_chain->Append( end );
3727 }
3728 else if( seg->shape == GR_SHAPE_ARC || seg->shape == GR_SHAPE_CIRCLE )
3729 {
3730 /* Even if it says "circle", it's actually an arc, it's just closed */
3731 const GRAPHIC_ARC* src = static_cast<const GRAPHIC_ARC*>( seg.get() );
3732 active_chain->Append( src->result );
3733 }
3734 else
3735 {
3736 reportError( _( "Invalid shape type %d in zone outline" ), seg->shape );
3737 }
3738 }
3739
3740 // Finalise the last hole, if any
3741 add_hole_if_valid();
3742
3743 if( zone_outline->Outline( 0 ).PointCount() >= 3 )
3744 {
3745 zone->SetOutline( zone_outline );
3746 aBoard->Add( zone, ADD_MODE::APPEND );
3747 }
3748 else
3749 {
3750 delete( zone_outline );
3751 delete( zone );
3752 }
3753
3754 return true;
3755}
3756
3757
3758bool FABMASTER::loadOutline( BOARD* aBoard, const std::unique_ptr<FABMASTER::TRACE>& aLine )
3759{
3760 PCB_LAYER_ID layer;
3761
3762 if( aLine->lclass == "BOARD GEOMETRY" && aLine->layer != "DIMENSION" )
3763 layer = Edge_Cuts;
3764 else if( aLine->lclass == "DRAWING FORMAT" )
3765 layer = Dwgs_User;
3766 else
3767 layer = Cmts_User;
3768
3769 for( auto& seg : aLine->segment )
3770 {
3771 for( std::unique_ptr<BOARD_ITEM>& new_item : createBoardItems( *aBoard, layer, *seg ) )
3772 {
3773 aBoard->Add( new_item.release(), ADD_MODE::APPEND );
3774 }
3775 }
3776
3777 return true;
3778}
3779
3780
3782{
3783
3784 for( auto& geom : board_graphics )
3785 {
3786 checkpoint();
3787
3788 PCB_LAYER_ID layer;
3789
3790 // The pin numbers are not useful for us outside of the footprints
3791 if( geom.subclass == "PIN_NUMBER" )
3792 continue;
3793
3794 layer = getLayer( geom.subclass );
3795
3796 if( !IsPcbLayer( layer ) )
3797 layer = Cmts_User;
3798
3799 if( !geom.elements->empty() )
3800 {
3802 if( ( *( geom.elements->begin() ) )->width == 0 )
3803 {
3804 SHAPE_POLY_SET poly_outline = loadShapePolySet( *( geom.elements ) );
3805
3806 poly_outline.Fracture();
3807
3808 if( poly_outline.OutlineCount() < 1 || poly_outline.COutline( 0 ).PointCount() < 3 )
3809 continue;
3810
3811 PCB_SHAPE* new_poly = new PCB_SHAPE( aBoard, SHAPE_T::POLY );
3812 new_poly->SetLayer( layer );
3813 new_poly->SetPolyShape( poly_outline );
3814 new_poly->SetStroke( STROKE_PARAMS( 0 ) );
3815
3816 if( layer == F_SilkS || layer == B_SilkS )
3817 new_poly->SetFilled( true );
3818
3819 aBoard->Add( new_poly, ADD_MODE::APPEND );
3820 }
3821 }
3822
3823 for( auto& seg : *geom.elements )
3824 {
3825 for( std::unique_ptr<BOARD_ITEM>& new_item : createBoardItems( *aBoard, layer, *seg ) )
3826 {
3827 aBoard->Add( new_item.release(), ADD_MODE::APPEND );
3828 }
3829 }
3830 }
3831
3832 return true;
3833
3834}
3835
3836
3838{
3839 AutoAssignZonePriorities( aBoard );
3840 return true;
3841}
3842
3843
3844bool FABMASTER::LoadBoard( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter )
3845{
3846 aBoard->SetFileName( m_filename.GetFullPath() );
3847 m_progressReporter = aProgressReporter;
3848
3849 m_totalCount = netnames.size()
3850 + layers.size()
3851 + vias.size()
3852 + components.size()
3853 + zones.size()
3854 + board_graphics.size()
3855 + traces.size();
3856 m_doneCount = 0;
3857
3858 loadNets( aBoard );
3859 loadLayers( aBoard );
3860 loadVias( aBoard );
3862 loadFootprints( aBoard );
3863 loadZones( aBoard );
3864 loadGraphics( aBoard );
3865
3866 for( auto& track : traces )
3867 {
3868 checkpoint();
3869
3870 if( track->lclass == "ETCH" )
3871 loadEtch( aBoard, track);
3872 else if( track->layer == "OUTLINE" || track->layer == "DIMENSION" )
3873 loadOutline( aBoard, track );
3874 else
3875 loadPolygon( aBoard, track );
3876 }
3877
3878 orderZones( aBoard );
3879
3880 return true;
3881}
const char * name
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
BASE_SET & set(size_t pos)
Definition base_set.h:116
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Container for design settings for a BOARD object.
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:343
wxString GetLayerName() const
Return the name of the PCB layer on which the item resides.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1098
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1355
void SetFileName(const wxString &aFileName)
Definition board.h:408
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition board.h:782
const ZONES & Zones() const
Definition board.h:425
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition board.cpp:820
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:987
const wxString & GetFileName() const
Definition board.h:410
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1158
void Remove(BOARD_ITEM *aBoardItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
Definition board.cpp:1503
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1063
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
EDA_ANGLE Normalize()
Definition eda_angle.h:229
EDA_ANGLE Normalize180()
Definition eda_angle.h:268
EDA_ANGLE Normalized() const
Definition eda_angle.h:240
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:152
virtual void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:576
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:388
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:412
virtual void SetTextWidth(int aWidth)
Definition eda_text.cpp:554
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual void SetTextHeight(int aHeight)
Definition eda_text.cpp:565
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:420
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:265
void SetItalic(bool aItalic)
Set the text to be italic - this will also update the font if needed.
Definition eda_text.cpp:302
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:404
size_t processFootprints(size_t aRow)
A!REFDES!COMP_CLASS!COMP_PART_NUMBER!COMP_HEIGHT!COMP_DEVICE_LABEL!COMP_INSERTION_CODE!...
unsigned m_doneCount
size_t processPins(size_t aRow)
A!SYM_NAME!SYM_MIRROR!PIN_NAME!PIN_NUMBER!PIN_X!PIN_Y!PAD_STACK_NAME!REFDES!PIN_ROTATION!
int readInt(const std::string &aStr) const
wxFileName m_filename
GRAPHIC_OBLONG * processOblong(const GRAPHIC_DATA &aData, double aScale)
size_t processGeometry(size_t aRow)
A!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!GRAPHIC_DATA_2!GRAPHIC_DATA_3!
static std::vector< std::unique_ptr< BOARD_ITEM > > createBoardItems(BOARD &aBoard, PCB_LAYER_ID aLayer, FABMASTER::GRAPHIC_ITEM &aGraphic)
Convert one Fabmaster graphic item to one or more PCB items.
bool Read(const std::string &aFile)
bool loadNets(BOARD *aBoard)
std::map< std::string, std::map< int, GEOM_GRAPHIC > > comp_graphics
GRAPHIC_CROSS * processCross(const GRAPHIC_DATA &aData, double aScale)
unsigned m_lastProgressCount
SYMTYPE parseSymType(const std::string &aSymType)
GRAPHIC_TEXT * processText(const GRAPHIC_DATA &aData, double aScale)
bool loadLayers(BOARD *aBoard)
static void setupText(const FABMASTER::GRAPHIC_TEXT &aGraphicText, PCB_LAYER_ID aLayer, PCB_TEXT &aText, const BOARD &aBoard, const OPT_VECTOR2I &aMirrorPoint)
Set parameters for graphic text.
PCB_LAYER_ID getLayer(const std::string &aLayerName)
GRAPHIC_RECTANGLE * processSquare(const GRAPHIC_DATA &aData, double aScale)
static bool traceIsOpen(const FABMASTER::TRACE &aLine)
bool loadZones(BOARD *aBoard)
Loads sections of the database into the board.
GRAPHIC_RECTANGLE * processRectangle(const GRAPHIC_DATA &aData, double aScale)
std::vector< std::string > single_row
size_t processSimpleLayers(size_t aRow)
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
bool loadFootprints(BOARD *aBoard)
GRAPHIC_ARC * processCircle(const GRAPHIC_DATA &aData, double aScale)
std::unordered_map< std::string, FM_PAD > pads
int getColFromName(size_t aRow, const std::string &aStr)
bool loadVias(BOARD *aBoard)
size_t processLayers(size_t aRow)
A!LAYER_SORT!LAYER_SUBCLASS!LAYER_ARTWORK!LAYER_USE!LAYER_CONDUCTOR!LAYER_DIELECTRIC_CONSTANT!
void reportError(const wxString &aMsg) const
Report a malformed-input issue the user can act on.
std::map< std::string, FABMASTER_LAYER > layers
COMPCLASS parseCompClass(const std::string &aCompClass)
bool loadEtch(BOARD *aBoard, const std::unique_ptr< TRACE > &aLine)
GRAPHIC_RECTANGLE * processFigRectangle(const GRAPHIC_DATA &aData, double aScale)
std::map< std::string, std::set< std::unique_ptr< PIN >, PIN::BY_NUM > > pins
std::set< std::unique_ptr< GRAPHIC_ITEM >, GRAPHIC_ITEM::SEQ_CMP > graphic_element
std::map< std::pair< std::string, std::string >, NETNAME > pin_nets
void reportInfo(const wxString &aFormat, Args &&... aArgs) const
GRAPHIC_ITEM * processGraphic(const GRAPHIC_DATA &aData, double aScale)
Specialty functions for processing graphical data rows into the internal database.
std::deque< single_row > rows
GRAPHIC_POLYGON * processPolygon(const GRAPHIC_DATA &aData, double aScale)
void createComponentsFromOrphanPins()
Creates synthetic COMPONENT entries from pins that have no matching component.
bool loadOutline(BOARD *aBoard, const std::unique_ptr< TRACE > &aLine)
size_t processPadStacks(size_t aRow)
A!PADNAME!RECNUMBER!LAYER!FIXFLAG!VIAFLAG!PADSHAPE1!PADWIDTH!PADHGHT!
std::vector< GEOM_GRAPHIC > board_graphics
section_type detectType(size_t aOffset)
double readDouble(const std::string &aStr) const
Reads the double/integer value from a std string independent of the user locale.
bool loadZone(BOARD *aBoard, const std::unique_ptr< FABMASTER::TRACE > &aLine)
GRAPHIC_ARC * processArc(const GRAPHIC_DATA &aData, double aScale)
SHAPE_POLY_SET loadShapePolySet(const graphic_element &aLine)
bool loadGraphics(BOARD *aBoard)
std::unordered_map< std::string, FABMASTER_PAD_SHAPE > pad_shapes
bool LoadBoard(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter)
std::vector< std::unique_ptr< FM_VIA > > vias
std::set< std::unique_ptr< TRACE >, TRACE::BY_ID > traces
std::set< std::unique_ptr< TRACE >, TRACE::BY_ID > zones
std::map< std::string, std::vector< std::unique_ptr< COMPONENT > > > components
bool loadPolygon(BOARD *aBoard, const std::unique_ptr< FABMASTER::TRACE > &aLine)
std::set< std::unique_ptr< TRACE >, TRACE::BY_ID > refdes
@ GR_SHAPE_OBLONG
!< Actually 360° arcs (for both arcs where start==end and real circles)
@ GR_SHAPE_CROSS
!< X/Y oblongs
size_t processPadStackLayers(size_t aRow)
void reportWarning(const wxString &aFormat, Args &&... aArgs) const
std::set< std::string > netnames
size_t processTraces(size_t aRow)
A!CLASS!SUBCLASS!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!GRAPHIC_DATA_2!
size_t processNets(size_t aRow)
A!NET_NAME!REFDES!PIN_NUMBER!PIN_NAME!PIN_GROUND!PIN_POWER!
bool orderZones(BOARD *aBoard)
Sets zone priorities based on zone BB size.
double processScaleFactor(size_t aRow)
Processes data from text vectors into internal database for further ordering.
size_t processVias(size_t aRow)
A!VIA_X!VIA_Y!PAD_STACK_NAME!NET_NAME!TEST_POINT!
unsigned m_totalCount
for progress reporting
size_t processCustomPads(size_t aRow)
A!SUBCLASS!PAD_SHAPE_NAME!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!
GRAPHIC_LINE * processLine(const GRAPHIC_DATA &aData, double aScale)
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:445
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:893
void SetOrientationDegrees(double aOrientation)
Definition footprint.h:435
void SetReference(const wxString &aReference)
Definition footprint.h:863
void SetValue(const wxString &aValue)
Definition footprint.h:884
PCB_FIELD & Reference()
Definition footprint.h:894
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
VECTOR2I GetPosition() const override
Definition footprint.h:406
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & UserMask()
Definition lset.cpp:686
static const LSET & ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition lset.cpp:630
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:672
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:573
Handle the data for a net.
Definition netinfo.h:46
unsigned GetNetCount() const
Definition netinfo.h:244
const NETNAMES_MAP & NetsByName() const
Return the name map, at least for python.
Definition netinfo.h:247
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:180
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:579
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:600
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:586
int GetWidth() const override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:200
void SetEnd(const VECTOR2I &aEnd) override
void SetPolyShape(const SHAPE_POLY_SET &aShape) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:496
void SetTextAngle(const EDA_ANGLE &aAngle) override
Definition pcb_text.cpp:553
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
void SetDrillDefault()
Set the drill value for vias to the default value UNDEFINED_DRILL_DIAMETER.
Definition pcb_track.h:774
void SetDrill(int aDrill)
Definition pcb_track.h:752
void SetPosition(const VECTOR2I &aPoint) override
Definition pcb_track.h:562
void SetLayerPair(PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer)
For a via m_layer contains the top layer, the other layer is in m_bottomLayer/.
void SetViaType(VIATYPE aViaType)
Definition pcb_track.h:403
void SetWidth(int aWidth) override
A progress reporter interface for use in multi-threaded environments.
Definition seg.h:38
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
const VECTOR2I & GetCenter() const
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of the line chain.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
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)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both)
int OutlineCount() const
Return the number of outlines in the set.
void Move(const VECTOR2I &aVector) override
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
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aError, ERROR_LOC aErrorLoc) const override
Fills a SHAPE_POLY_SET with a polygon representation of this shape.
Simple container to manage line stroke parameters.
const std::string Format() const
Return the vector formatted as a string.
Definition vector2d.h:419
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:832
void SetLocalClearance(std::optional< int > aClearance)
Definition zone.h:183
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:619
void SetIsRuleArea(bool aEnable)
Definition zone.h:814
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:831
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:644
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:830
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:610
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:833
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:829
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition zone.h:313
void SetZoneName(const wxString &aName)
Definition zone.h:161
void SetOutline(SHAPE_POLY_SET *aOutline)
Definition zone.h:421
The common library.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
const wxChar *const traceFabmaster
static bool isRuleAreaClass(const std::string &aClass)
Allegro area "shapes" that carry no net and must not become copper fills.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
bool IsPcbLayer(int aLayer)
Test whether a layer is a valid layer for Pcbnew.
Definition layer_ids.h:672
constexpr PCB_LAYER_ID PCBNEW_LAYER_ID_START
Definition layer_ids.h:170
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:809
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:683
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ Cmts_User
Definition layer_ids.h:104
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ User_9
Definition layer_ids.h:128
@ UNSELECTED_LAYER
Definition layer_ids.h:58
@ F_Fab
Definition layer_ids.h:115
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ User_1
Definition layer_ids.h:120
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
bool AddHoleIfValid(SHAPE_POLY_SET &aOutline, SHAPE_LINE_CHAIN &&aHole)
Adds a hole to a polygon if it is valid (i.e.
std::vector< VECTOR2I > MakeRegularPolygonPoints(const VECTOR2I &aCenter, size_t aN, const VECTOR2I &aPt0)
Get the corners of a regular polygon from the centre, one point and the number of sides.
std::vector< SEG > MakeCrossSegments(const VECTOR2I &aCenter, const VECTOR2I &aSize, EDA_ANGLE aAngle)
Create the two segments for a cross.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
std::map< wxString, NETINFO_ITEM * > NETNAMES_MAP
Definition netinfo.h:214
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
Class to handle a set of BOARD_ITEMs.
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
Utility functions for working with shapes.
bool ReplaceIllegalFileNameChars(std::string &aName, int aReplaceChar)
Checks aName for illegal file name characters.
static std::vector< std::string > split(const std::string &aStr, const std::string &aDelim)
Split the input string into a vector of output strings.
A!LAYER_SORT!LAYER_SUBCLASS!LAYER_ARTWORK!LAYER_USE!LAYER_CONDUCTOR!LAYER_DIELECTRIC_CONSTANT !...
bool disable
! if true, prevent the layer elements from being used
std::string name
! LAYER_SUBCLASS
int layerid
! pcbnew layer (assigned)
bool conductive
! LAYER_CONDUCTOR
bool positive
! LAYER_ARTWORK (either POSITIVE or NEGATIVE)
A!SUBCLASS!PAD_SHAPE_NAME!GRAPHIC_DATA_NAME!GRAPHIC_DATA_NUMBER!RECORD_TAG!GRAPHIC_DATA_1!
Per-layer pad geometry within a pad stack.
std::string name
! SYM_NAME
std::string refdes
! REFDES
std::string subclass
! SUBCLASS
std::unique_ptr< graphic_element > elements
int end_x
! GRAPHIC_DATA_3
SHAPE_ARC result
! KiCad-style arc representation
int center_x
! GRAPHIC_DATA_5
bool clockwise
! GRAPHIC_DATA_9
int center_y
! GRAPHIC_DATA_6
int end_y
! GRAPHIC_DATA_4
int size_y
! GRAPHIC_DATA_4
int size_x
! GRAPHIC_DATA_3
std::string layer
! SUBCLASS
int subseq
! RECORD_TAG[1]
int width
! Various sections depending on type
GRAPHIC_SHAPE shape
! Shape of the graphic_item
int start_y
! GRAPHIC_DATA_2
int start_x
! GRAPHIC_DATA_1
GRAPHIC_TYPE type
! Type of graphic item
int end_x
! GRAPHIC_DATA_3
bool oblong_x
! OBLONG_X (as opposed to OBLONG_Y)
int size_x
! GRAPHIC_DATA_3
int size_y
! GRAPHIC_DATA_4
std::vector< VECTOR2I > m_pts
double rotation
! GRAPHIC_DATA_3
std::string text
! GRAPHIC_DATA_7
int height
! GRAPHIC_DATA_6[2]
int thickness
! GRAPHIC_DATA_6[6]
GR_TEXT_H_ALIGN_T orient
! GRAPHIC_DATA_5
bool ital
! GRAPHIC_DATA_6[4] != 0.0
bool mirror
! GRAPHIC_DATA_4
std::string refdes
!< NET_NAME
bool pin_pwr
!< PIN_GND
std::string pin_num
!< REFDES
std::string pin_name
!< PIN_NUMBER
bool pin_gnd
!< PIN_NAME
graphic_element segment
! GRAPHIC_DATA (can be either LINE or ARC)
@ USER
The field ID hasn't been set yet; field is invalid.
KIBIS_PIN * pin
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
wxLogTrace helper definitions.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
bool AutoAssignZonePriorities(BOARD *aBoard, PROGRESS_REPORTER *aReporter)
Automatically assign zone priorities based on connectivity analysis of overlapping regions.
@ FULL
pads are covered by copper
Definition zones.h:47