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