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