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