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