KiCad PCB EDA Suite
Loading...
Searching...
No Matches
idf_common.cpp
Go to the documentation of this file.
1/*
2 * file: idf_common.cpp
3 *
4 * This program source code file is part of KiCad, a free EDA CAD application.
5 *
6 * Copyright (C) 2013-2017 Cirilo Bernardo
7 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23
24#include <algorithm>
25#include <list>
26#include <string>
27#include <iostream>
28#include <sstream>
29#include <iomanip>
30#include <cerrno>
31#include <cstdio>
32#include <cmath>
33#include <io/idf/idf_common.h>
34#include <io/idf/idf_helpers.h>
35
36using namespace IDF3;
37using namespace std;
38
39
40std::string source;
41std::string message;
42
43IDF_ERROR::IDF_ERROR( const char* aSourceFile, const char* aSourceMethod, int aSourceLine,
44 const std::string& aMessage ) noexcept
45{
46 ostringstream ostr;
47
48 if( aSourceFile )
49 ostr << "* " << aSourceFile << ":";
50 else
51 ostr << "* [BUG: No Source File]:";
52
53 ostr << aSourceLine << ":";
54
55 if( aSourceMethod )
56 ostr << aSourceMethod << "(): ";
57 else
58 ostr << "[BUG: No Source Method]:\n* ";
59
60 ostr << aMessage;
61 message = ostr.str();
62}
63
64
66{
67}
68
69
70const char* IDF_ERROR::what() const noexcept
71{
72 return message.c_str();
73}
74
75
77 xpos( 0.0 ),
78 ypos( 0.0 ),
79 height( 0.0 ),
80 length( 0.0 )
81{
82}
83
84
85bool IDF_NOTE::readNote( std::istream& aBoardFile, IDF3::FILE_STATE& aBoardState,
86 IDF3::IDF_UNIT aBoardUnit )
87{
88 std::string iline; // the input line
89 bool isComment; // true if a line just read in is a comment line
90 std::streampos pos;
91 int idx = 0;
92 bool quoted = false;
93 std::string token;
94
95 // RECORD 2: X, Y, text Height, text Length, "TEXT"
96 while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );
97
98 if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() )
99 {
100 aBoardState = FILE_STATE::FILE_INVALID;
101 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, "problems reading board notes" ) );
102 }
103
104 if( isComment )
105 {
106 aBoardState = FILE_STATE::FILE_INVALID;
107 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
108 "invalid IDFv3 file\n"
109 "* Violation of specification: comment within a section (NOTES)" ) );
110 }
111
112 idx = 0;
113 GetIDFString( iline, token, quoted, idx );
114
115 if( quoted )
116 {
117 aBoardState = FILE_STATE::FILE_INVALID;
118 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
119 "invalid IDFv3 file\n"
120 "* Violation of specification: X position in NOTES section must not be "
121 "in quotes" ) );
122 }
123
124 if( CompareToken( ".END_NOTES", token ) )
125 return false;
126
127 istringstream istr;
128 istr.str( token );
129
130 istr >> xpos;
131
132 if( istr.fail() )
133 {
134 aBoardState = FILE_STATE::FILE_INVALID;
135 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
136 "invalid IDFv3 file\n"
137 "* Violation of specification: X position in NOTES section is not "
138 "numeric" ) );
139 }
140
141 if( !GetIDFString( iline, token, quoted, idx ) )
142 {
143 aBoardState = FILE_STATE::FILE_INVALID;
144 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
145 "invalid IDFv3 file\n"
146 "* Violation of specification: Y position in NOTES section is "
147 "missing" ) );
148 }
149
150 if( quoted )
151 {
152 aBoardState = FILE_STATE::FILE_INVALID;
153 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
154 "invalid IDFv3 file\n"
155 "* Violation of specification: Y position in NOTES section must not be "
156 "in quotes" ) );
157 }
158
159 istr.clear();
160 istr.str( token );
161
162 istr >> ypos;
163
164 if( istr.fail() )
165 {
166 aBoardState = FILE_STATE::FILE_INVALID;
167 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
168 "invalid IDFv3 file\n"
169 "* Violation of specification: Y position in NOTES section is not "
170 "numeric" ) );
171 }
172
173 if( !GetIDFString( iline, token, quoted, idx ) )
174 {
175 aBoardState = FILE_STATE::FILE_INVALID;
176 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
177 "invalid IDFv3 file\n"
178 "* Violation of specification: text height in NOTES section is "
179 "missing" ) );
180 }
181
182 if( quoted )
183 {
184 aBoardState = FILE_STATE::FILE_INVALID;
185 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
186 "invalid IDFv3 file\n"
187 "* Violation of specification: text height in NOTES section must not "
188 "be in quotes" ) );
189 }
190
191 istr.clear();
192 istr.str( token );
193
194 istr >> height;
195
196 if( istr.fail() )
197 {
198 aBoardState = FILE_STATE::FILE_INVALID;
199 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
200 "invalid IDFv3 file\n"
201 "* Violation of specification: text height in NOTES section is not "
202 "numeric" ) );
203 }
204
205 if( !GetIDFString( iline, token, quoted, idx ) )
206 {
207 aBoardState = FILE_STATE::FILE_INVALID;
208 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
209 "invalid IDFv3 file\n"
210 "* Violation of specification: text length in NOTES section is "
211 "missing" ) );
212 }
213
214 if( quoted )
215 {
216 aBoardState = FILE_STATE::FILE_INVALID;
217 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
218 "invalid IDFv3 file\n"
219 "* Violation of specification: text length in NOTES section must not "
220 "be in quotes" ) );
221 }
222
223 istr.clear();
224 istr.str( token );
225
226 istr >> length;
227
228 if( istr.fail() )
229 {
230 aBoardState = FILE_STATE::FILE_INVALID;
231 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
232 "invalid IDFv3 file\n"
233 "* Violation of specification: text length in NOTES section is not "
234 "numeric" ) );
235 }
236
237 if( !GetIDFString( iline, token, quoted, idx ) )
238 {
239 aBoardState = FILE_STATE::FILE_INVALID;
240 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
241 "invalid IDFv3 file\n"
242 "* Violation of specification: text value in NOTES section is "
243 "missing" ) );
244 }
245
246 text = token;
247
248 if( aBoardUnit == IDF_UNIT::UNIT_THOU )
249 {
254 }
255
256 return true;
257}
258
259
260bool IDF_NOTE::writeNote( std::ostream& aBoardFile, IDF3::IDF_UNIT aBoardUnit )
261{
262 if( aBoardUnit == IDF_UNIT::UNIT_THOU )
263 {
264 aBoardFile << setiosflags( ios::fixed ) << setprecision( 1 ) << ( xpos / IDF_THOU_TO_MM )
265 << " " << ( ypos / IDF_THOU_TO_MM ) << " " << ( height / IDF_THOU_TO_MM ) << " "
266 << ( length / IDF_THOU_TO_MM ) << " ";
267 }
268 else
269 {
270 aBoardFile << setiosflags( ios::fixed ) << setprecision( 5 ) << xpos << " " << ypos << " "
271 << height << " " << length << " ";
272 }
273
274 aBoardFile << "\"" << text << "\"\n";
275
276 return !aBoardFile.bad();
277}
278
279
280void IDF_NOTE::SetText( const std::string& aText )
281{
282 text = aText;
283}
284
285
286void IDF_NOTE::SetPosition( double aXpos, double aYpos )
287{
288 xpos = aXpos;
289 ypos = aYpos;
290}
291
292
293void IDF_NOTE::SetSize( double aHeight, double aLength )
294{
295 height = aHeight;
296 length = aLength;
297}
298
299
300const std::string& IDF_NOTE::GetText()
301{
302 return text;
303}
304
305
306void IDF_NOTE::GetPosition( double& aXpos, double& aYpos )
307{
308 aXpos = xpos;
309 aYpos = ypos;
310}
311
312
313void IDF_NOTE::GetSize( double& aHeight, double& aLength )
314{
315 aHeight = height;
316 aLength = length;
317}
318
319
321 dia( 0.0 ),
322 x( 0.0 ),
323 y( 0.0 ),
328{
329}
330
331
332IDF_DRILL_DATA::IDF_DRILL_DATA( double aDrillDia, double aPosX, double aPosY,
333 IDF3::KEY_PLATING aPlating, const std::string& aRefDes,
334 const std::string& aHoleType, IDF3::KEY_OWNER aOwner )
335{
336 if( aDrillDia < 0.3 )
337 dia = 0.3;
338 else
339 dia = aDrillDia;
340
341 x = aPosX;
342 y = aPosY;
343 plating = aPlating;
344
345 if( !aRefDes.compare( "BOARD" ) )
346 {
348 }
349 else if( aRefDes.empty() || !aRefDes.compare( "NOREFDES" ) )
350 {
352 }
353 else if( !aRefDes.compare( "PANEL" ) )
354 {
356 }
357 else
358 {
360 refdes = aRefDes;
361 }
362
363 if( !aHoleType.compare( "PIN" ) )
364 {
366 }
367 else if( !aHoleType.compare( "VIA" ) )
368 {
370 }
371 else if( aHoleType.empty() || !aHoleType.compare( "MTG" ) )
372 {
374 }
375 else if( !aHoleType.compare( "TOOL" ) )
376 {
378 }
379 else
380 {
382 holetype = aHoleType;
383 }
384
385 owner = aOwner;
386}
387
388
389bool IDF_DRILL_DATA::Matches( double aDrillDia, double aPosX, double aPosY ) const
390{
391 double ddia = aDrillDia - dia;
392 IDF_POINT p1, p2;
393
394 p1.x = x;
395 p1.y = y;
396 p2.x = aPosX;
397 p2.y = aPosY;
398
399 if( ddia > -0.00001 && ddia < 0.00001 && p1.Matches( p2, 0.00001 ) )
400 return true;
401
402 return false;
403}
404
405
406bool IDF_DRILL_DATA::read( std::istream& aBoardFile, IDF3::IDF_UNIT aBoardUnit,
407 IDF3::FILE_STATE aBoardState, IDF3::IDF_VERSION aIdfVersion )
408{
409 std::string iline; // the input line
410 bool isComment; // true if a line just read in is a comment line
411 std::streampos pos;
412 int idx = 0;
413 bool quoted = false;
414 std::string token;
415
416 // RECORD 2: DIA, X, Y, Plating Style, REFDES, HOLE TYPE, HOLE OWNER
417 while( !FetchIDFLine( aBoardFile, iline, isComment, pos ) && aBoardFile.good() );
418
419 if( ( !aBoardFile.good() && !aBoardFile.eof() ) || iline.empty() )
420 {
421 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
422 "problems reading board drilled holes" ) );
423 }
424
425 if( isComment )
426 {
427 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
428 "invalid IDF file\n"
429 "* Violation of specification: comment within a section (DRILLED "
430 "HOLES)" ) );
431 }
432
433 idx = 0;
434 GetIDFString( iline, token, quoted, idx );
435
436 if( quoted )
437 {
438 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
439 "invalid IDF file\n"
440 "* Violation of specification: drill diameter must not be in quotes" ) );
441 }
442
443 if( CompareToken( ".END_DRILLED_HOLES", token ) )
444 return false;
445
446 istringstream istr;
447 istr.str( token );
448
449 istr >> dia;
450
451 if( istr.fail() )
452 {
453 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
454 "invalid IDF file\n"
455 "* Violation of specification: drill diameter is not numeric" ) );
456 }
457
458 if( ( aBoardUnit == IDF_UNIT::UNIT_MM && dia < IDF_MIN_DIA_MM )
459 || ( aBoardUnit == IDF_UNIT::UNIT_THOU && dia < IDF_MIN_DIA_THOU )
460 || ( aBoardUnit == IDF_UNIT::UNIT_TNM && dia < IDF_MIN_DIA_TNM ) )
461 {
462 ostringstream ostr;
463 ostr << "invalid IDF file\n";
464 ostr << "* Invalid drill diameter (too small): '" << token << "'";
465
466 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
467 }
468
469 if( !GetIDFString( iline, token, quoted, idx ) )
470 {
471 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
472 "invalid IDF file\n"
473 "* Violation of specification: missing X position for drilled hole" ) );
474 }
475
476 if( quoted )
477 {
478 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
479 "invalid IDF file\n"
480 "* Violation of specification: X position in DRILLED HOLES section "
481 "must not be in quotes" ) );
482 }
483
484 istr.clear();
485 istr.str( token );
486
487 istr >> x;
488
489 if( istr.fail() )
490 {
491 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
492 "invalid IDF file\n"
493 "* Violation of specification: X position in DRILLED HOLES section is "
494 "not numeric" ) );
495 }
496
497 if( !GetIDFString( iline, token, quoted, idx ) )
498 {
499 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
500 "invalid IDF file\n"
501 "* Violation of specification: missing Y position for drilled hole" ) );
502 }
503
504 if( quoted )
505 {
506 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
507 "invalid IDF file\n"
508 "* Violation of specification: Y position in DRILLED HOLES section "
509 "must not be in quotes" ) );
510 }
511
512 istr.clear();
513 istr.str( token );
514
515 istr >> y;
516
517 if( istr.fail() )
518 {
519 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
520 "invalid IDF file\n"
521 "* Violation of specification: Y position in DRILLED HOLES section is "
522 "not numeric" ) );
523 }
524
525 if( aIdfVersion > IDF_VERSION::IDF_V2 )
526 {
527 if( !GetIDFString( iline, token, quoted, idx ) )
528 {
529 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
530 "invalid IDFv3 file\n"
531 "* Violation of specification: missing PLATING for drilled hole" ) );
532 }
533
534 if( CompareToken( "PTH", token ) )
535 {
537 }
538 else if( CompareToken( "NPTH", token ) )
539 {
541 }
542 else
543 {
544 ostringstream ostr;
545 ostr << "invalid IDFv3 file\n";
546 ostr << "* Violation of specification: invalid PLATING type ('" << token << "')";
547
548 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
549 }
550 }
551 else
552 {
554 }
555
556 if( !GetIDFString( iline, token, quoted, idx ) )
557 {
558 if( aIdfVersion > IDF_VERSION::IDF_V2 )
559 {
560 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
561 "invalid IDFv3 file\n"
562 "* Violation of specification: missing REFDES for drilled hole" ) );
563 }
564 else
565 {
566 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
567 "invalid IDFv2 file\n"
568 "* Violation of specification: missing HOLE TYPE for drilled "
569 "hole" ) );
570 }
571 }
572
573 std::string tok1 = token;
574
575 if( !GetIDFString( iline, token, quoted, idx ) )
576 {
577 if( aIdfVersion > IDF_VERSION::IDF_V2 )
578 {
579 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
580 "invalid IDFv3 file\n"
581 "* Violation of specification: missing HOLE TYPE for drilled "
582 "hole" ) );
583 }
584 else
585 {
586 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
587 "invalid IDFv2 file\n"
588 "* Violation of specification: missing REFDES for drilled hole" ) );
589 }
590 }
591
592 std::string tok2 = token;
593
594 if( aIdfVersion > IDF_VERSION::IDF_V2 )
595 token = tok1;
596
597 if( CompareToken( "BOARD", token ) )
598 {
600 }
601 else if( CompareToken( "NOREFDES", token ) )
602 {
604 }
605 else if( CompareToken( "PANEL", token ) )
606 {
608 }
609 else
610 {
612 refdes = token;
613 }
614
615 if( aIdfVersion > IDF_VERSION::IDF_V2 )
616 token = tok2;
617 else
618 token = tok1;
619
620 if( CompareToken( "PIN", token ) )
621 {
623 }
624 else if( CompareToken( "VIA", token ) )
625 {
627 }
628 else if( CompareToken( "MTG", token ) )
629 {
631 }
632 else if( CompareToken( "TOOL", token ) )
633 {
635 }
636 else
637 {
639 holetype = token;
640 }
641
642 if( aIdfVersion > IDF_VERSION::IDF_V2 )
643 {
644 if( !GetIDFString( iline, token, quoted, idx ) )
645 {
646 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__,
647 "invalid IDFv3 file\n"
648 "* Violation of specification: missing OWNER for drilled hole" ) );
649 }
650
651 if( !ParseOwner( token, owner ) )
652 {
653 ostringstream ostr;
654 ostr << "invalid IDFv3 file\n";
655 ostr << "* Violation of specification: invalid OWNER for drilled hole ('" << token
656 << "')";
657
658 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
659 }
660 }
661 else
662 {
664 }
665
666 if( aBoardUnit == IDF_UNIT::UNIT_THOU )
667 {
669 x *= IDF_THOU_TO_MM;
670 y *= IDF_THOU_TO_MM;
671 }
672 else if( ( aIdfVersion == IDF_VERSION::IDF_V2 ) && ( aBoardUnit == IDF_UNIT::UNIT_TNM ) )
673 {
675 x *= IDF_TNM_TO_MM;
676 y *= IDF_TNM_TO_MM;
677 }
678 else if( aBoardUnit != IDF_UNIT::UNIT_MM )
679 {
680 ostringstream ostr;
681 ostr << "\n* BUG: invalid UNIT type: " << static_cast<int>( aBoardUnit );
682
683 throw( IDF_ERROR( __FILE__, __FUNCTION__, __LINE__, ostr.str() ) );
684 }
685
686 return true;
687}
688
689
690void IDF_DRILL_DATA::write( std::ostream& aBoardFile, IDF3::IDF_UNIT aBoardUnit )
691{
692 std::string holestr;
693 std::string refstr;
694 std::string ownstr;
695 std::string pltstr;
696
697 switch( khole )
698 {
699 case KEY_HOLETYPE::PIN: holestr = "PIN"; break;
700 case KEY_HOLETYPE::VIA: holestr = "VIA"; break;
701 case KEY_HOLETYPE::TOOL: holestr = "TOOL"; break;
702 case KEY_HOLETYPE::OTHER: holestr = "\"" + holetype + "\""; break;
703 default: holestr = "MTG"; break;
704 }
705
706 switch( kref )
707 {
708 case KEY_REFDES::BOARD: refstr = "BOARD"; break;
709 case KEY_REFDES::PANEL: refstr = "PANEL"; break;
710 case KEY_REFDES::REFDES: refstr = "\"" + refdes + "\""; break;
711 default: refstr = "NOREFDES"; break;
712 }
713
715 pltstr = "PTH";
716 else
717 pltstr = "NPTH";
718
719 switch( owner )
720 {
721 case KEY_OWNER::MCAD: ownstr = "MCAD"; break;
722 case KEY_OWNER::ECAD: ownstr = "ECAD"; break;
723 default: ownstr = "UNOWNED"; break;
724 }
725
726 if( aBoardUnit == IDF_UNIT::UNIT_MM )
727 {
728 aBoardFile << std::setiosflags( std::ios::fixed ) << std::setprecision( 3 ) << dia << " "
729 << std::setprecision( 5 ) << x << " " << y << " " << pltstr.c_str() << " "
730 << refstr.c_str() << " " << holestr.c_str() << " " << ownstr.c_str() << "\n";
731 }
732 else
733 {
734 aBoardFile << std::setiosflags( std::ios::fixed ) << std::setprecision( 1 )
735 << ( dia / IDF_THOU_TO_MM ) << " " << std::setprecision( 1 )
736 << ( x / IDF_THOU_TO_MM ) << " " << ( y / IDF_THOU_TO_MM ) << " "
737 << pltstr.c_str() << " " << refstr.c_str() << " " << holestr.c_str() << " "
738 << ownstr.c_str() << "\n";
739 }
740}
741
742
744{
745 return dia;
746}
747
748
750{
751 return x;
752}
753
754
756{
757 return y;
758}
759
760
765
766
768{
769 switch( kref )
770 {
771 case KEY_REFDES::BOARD: refdes = "BOARD"; break;
772 case KEY_REFDES::PANEL: refdes = "PANEL"; break;
773 case KEY_REFDES::REFDES: break;
774 default: refdes = "NOREFDES"; break;
775 }
776
777 return refdes;
778}
779
780
782{
783 switch( khole )
784 {
785 case KEY_HOLETYPE::PIN: holetype = "PIN"; break;
786 case KEY_HOLETYPE::VIA: holetype = "VIA"; break;
787 case KEY_HOLETYPE::TOOL: holetype = "TOOL"; break;
788 case KEY_HOLETYPE::OTHER: break;
789 default: holetype = "MTG"; break;
790 }
791
792 return holetype;
793}
794
795
796bool IDF_POINT::Matches( const IDF_POINT& aPoint, double aRadius ) const
797{
798 double dx = x - aPoint.x;
799 double dy = y - aPoint.y;
800
801 double d2 = dx * dx + dy * dy;
802
803 if( d2 <= aRadius * aRadius )
804 return true;
805
806 return false;
807}
808
809
810double IDF_POINT::CalcDistance( const IDF_POINT& aPoint ) const
811{
812 double dx = aPoint.x - x;
813 double dy = aPoint.y - y;
814 double dist = sqrt( dx * dx + dy * dy );
815
816 return dist;
817}
818
819
820double IDF3::CalcAngleRad( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint )
821{
822 return atan2( aEndPoint.y - aStartPoint.y, aEndPoint.x - aStartPoint.x );
823}
824
825
826double IDF3::CalcAngleDeg( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint )
827{
828 double ang = CalcAngleRad( aStartPoint, aEndPoint );
829
830 // round to thousandths of a degree
831 int iang = int (ang / M_PI * 1800000.0);
832
833 ang = iang / 10000.0;
834
835 return ang;
836}
837
838
839void IDF3::GetOutline( std::list<IDF_SEGMENT*>& aLines, IDF_OUTLINE& aOutline )
840{
841 aOutline.Clear();
842
843 // NOTE: To tell if the point order is CCW or CW,
844 // sum all: (endPoint.X[n] - startPoint.X[n])*(endPoint[n] + startPoint.Y[n])
845 // If the result is >0, the direction is CW, otherwise
846 // it is CCW. Note that the result cannot be 0 unless
847 // we have a bounded area of 0.
848
849 // First we find the segment with the leftmost point
850 std::list<IDF_SEGMENT*>::iterator bl = aLines.begin();
851 std::list<IDF_SEGMENT*>::iterator el = aLines.end();
852 std::list<IDF_SEGMENT*>::iterator idx = bl++; // iterator for the object with minX
853
854 double minx = (*idx)->GetMinX();
855 double curx;
856
857 while( bl != el )
858 {
859 curx = (*bl)->GetMinX();
860
861 if( curx < minx )
862 {
863 minx = curx;
864 idx = bl;
865 }
866
867 ++bl;
868 }
869
870 aOutline.push( *idx );
871
872#ifdef DEBUG_IDF
873 PrintSeg( *idx );
874#endif
875
876 aLines.erase( idx );
877
878 // If the item is a circle then we're done
879 if( aOutline.front()->IsCircle() )
880 return;
881
882 // Assemble the loop
883 bool complete = false; // set if loop is complete
884 bool matched; // set if a segment's end point was matched
885
886 while( !complete )
887 {
888 matched = false;
889 bl = aLines.begin();
890 el = aLines.end();
891
892 while( bl != el && !matched )
893 {
894 if( (*bl)->MatchesStart( aOutline.back()->endPoint ) )
895 {
896 if( (*bl)->IsCircle() )
897 {
898 // a circle on the perimeter is pathological but we just ignore it
899 ++bl;
900 }
901 else
902 {
903 matched = true;
904
905#ifdef DEBUG_IDF
906 PrintSeg( *bl );
907#endif
908
909 aOutline.push( *bl );
910 bl = aLines.erase( bl );
911 }
912
913 continue;
914 }
915
916 ++bl;
917 }
918
919 if( !matched )
920 {
921 // attempt to match the end points
922 bl = aLines.begin();
923 el = aLines.end();
924
925 while( bl != el && !matched )
926 {
927 if( (*bl)->MatchesEnd( aOutline.back()->endPoint ) )
928 {
929 if( (*bl)->IsCircle() )
930 {
931 // a circle on the perimeter is pathological but we just ignore it
932 ++bl;
933 }
934 else
935 {
936 matched = true;
937 (*bl)->SwapEnds();
938
939#ifdef DEBUG_IDF
940 printSeg( *bl );
941#endif
942
943 aOutline.push( *bl );
944 bl = aLines.erase( bl );
945 }
946
947 continue;
948 }
949
950 ++bl;
951 }
952 }
953
954 if( !matched )
955 {
956 // still no match - attempt to close the loop
957 if( (aOutline.size() > 1) || ( aOutline.front()->angle < -MIN_ANG )
958 || ( aOutline.front()->angle > MIN_ANG ) )
959 {
960 // close the loop
961 IDF_SEGMENT* seg = new IDF_SEGMENT( aOutline.back()->endPoint,
962 aOutline.front()->startPoint );
963
964 if( seg )
965 {
966 complete = true;
967
968#ifdef DEBUG_IDF
969 printSeg( seg );
970#endif
971
972 aOutline.push( seg );
973 break;
974 }
975 }
976
977 // the outline is bad; drop the segments
978 aOutline.Clear();
979
980 return;
981 }
982
983 // check if the loop is complete
984 if( aOutline.front()->MatchesStart( aOutline.back()->endPoint ) )
985 {
986 complete = true;
987 break;
988 }
989 }
990}
991
992
994 angle( 0.0 ),
995 offsetAngle( 0.0 ),
996 radius( 0.0 )
997{
998}
999
1000
1001IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint ) :
1002 startPoint( aStartPoint ),
1003 endPoint( aEndPoint ),
1004 angle( 0.0 ),
1005 offsetAngle( 0.0),
1006 radius( 0.0 )
1007{
1008}
1009
1010
1011IDF_SEGMENT::IDF_SEGMENT( const IDF_POINT& aStartPoint, const IDF_POINT& aEndPoint, double aAngle,
1012 bool aFromKicad ) :
1013 angle( 0.0 ),
1014 offsetAngle( 0.0 ),
1015 radius( 0.0 )
1016{
1017 double diff = abs( aAngle ) - 360.0;
1018
1019 if( ( diff < MIN_ANG && diff > -MIN_ANG ) || ( aAngle < MIN_ANG && aAngle > -MIN_ANG )
1020 || ( !aFromKicad ) )
1021 {
1022 angle = 0.0;
1023 startPoint = aStartPoint;
1024 endPoint = aEndPoint;
1025
1026 if( diff < MIN_ANG && diff > -MIN_ANG )
1027 {
1028 angle = 360.0;
1029 center = aStartPoint;
1030 offsetAngle = 0.0;
1031 radius = aStartPoint.CalcDistance( aEndPoint );
1032 }
1033 else if( aAngle > MIN_ANG || aAngle < -MIN_ANG )
1034 {
1035 angle = aAngle;
1037 }
1038
1039 return;
1040 }
1041
1042 // we need to convert from the KiCad arc convention
1043 angle = aAngle;
1044
1045 center = aStartPoint;
1046
1047 offsetAngle = IDF3::CalcAngleDeg( aStartPoint, aEndPoint );
1048
1049 radius = aStartPoint.CalcDistance( aEndPoint );
1050
1051 startPoint = aEndPoint;
1052
1053 double ang = offsetAngle + aAngle;
1054 ang = (ang / 180.0) * M_PI;
1055
1056 endPoint.x = ( radius * cos( ang ) ) + center.x;
1057 endPoint.y = ( radius * sin( ang ) ) + center.y;
1058}
1059
1060
1061bool IDF_SEGMENT::MatchesStart( const IDF_POINT& aPoint, double aRadius )
1062{
1063 return startPoint.Matches( aPoint, aRadius );
1064}
1065
1066
1067bool IDF_SEGMENT::MatchesEnd( const IDF_POINT& aPoint, double aRadius )
1068{
1069 return endPoint.Matches( aPoint, aRadius );
1070}
1071
1072
1074{
1075 // NOTE: this routine does not check if the points are the same
1076 // or too close to be sensible in a production setting.
1077
1078 double offAng = IDF3::CalcAngleRad( startPoint, endPoint );
1079 double d = startPoint.CalcDistance( endPoint ) / 2.0;
1080 double xm = ( startPoint.x + endPoint.x ) * 0.5;
1081 double ym = ( startPoint.y + endPoint.y ) * 0.5;
1082
1083 radius = d / sin( angle * M_PI / 360.0 );
1084
1085 if( radius < 0.0 )
1086 radius = -radius;
1087
1088 // calculate the height of the triangle with base d and hypotenuse r
1089 double dh2 = radius * radius - d * d;
1090
1091 // this should only ever happen due to rounding errors when r == d
1092 if( dh2 < 0 )
1093 dh2 = 0;
1094
1095 double h = sqrt( dh2 );
1096
1097 if( angle > 0.0 )
1098 offAng += M_PI_2;
1099 else
1100 offAng -= M_PI_2;
1101
1102 if( angle < -180.0 )
1103 offAng += M_PI;
1104 else if( angle > 180 )
1105 offAng -= M_PI;
1106
1107 center.x = h * cos( offAng ) + xm;
1108 center.y = h * sin( offAng ) + ym;
1109
1111}
1112
1113
1115{
1116 double diff = abs( angle ) - 360.0;
1117
1118 if( ( diff < MIN_ANG ) && ( diff > -MIN_ANG ) )
1119 return true;
1120
1121 return false;
1122}
1123
1124
1126{
1127 if( angle == 0.0 )
1128 return std::min( startPoint.x, endPoint.x );
1129
1130 // Calculate the leftmost point of the circle or arc
1131
1132 // if only everything were this easy
1133 if( IsCircle() )
1134 return center.x - radius;
1135
1136 // cases:
1137 // 1. CCW arc: if offset + included angle >= 180 deg then
1138 // MinX = center.x - radius, otherwise MinX is the
1139 // same as for the case of a line.
1140 // 2. CW arc: if offset + included angle <= -180 deg then
1141 // MinX = center.x - radius, otherwise MinX is the
1142 // same as for the case of a line.
1143
1144 if( angle > 0 )
1145 {
1146 // CCW case
1147 if( ( offsetAngle + angle ) >= 180.0 )
1148 return center.x - radius;
1149 else
1150 return std::min( startPoint.x, endPoint.x );
1151 }
1152
1153 // CW case
1154 if( ( offsetAngle + angle ) <= -180.0 )
1155 return center.x - radius;
1156
1157 return std::min( startPoint.x, endPoint.x );
1158}
1159
1160
1162{
1163 if( IsCircle() )
1164 {
1165 // reverse the direction
1166 angle = -angle;
1167 return;
1168 }
1169
1170 IDF_POINT tmp = startPoint;
1172 endPoint = tmp;
1173
1174 if( ( angle < MIN_ANG ) && ( angle > -MIN_ANG ) )
1175 return; // nothing more to do
1176
1177 // change the direction of the arc
1178 angle = -angle;
1179
1180 // calculate the new offset angle
1182}
1183
1184
1186{
1187 // note: when outlines are not valid, 'false' is returned
1188 switch( outline.size() )
1189 {
1190 case 0:
1191 // no outline
1192 return false;
1193 break;
1194
1195 case 1:
1196 // circles are always reported as CCW
1197 if( outline.front()->IsCircle() )
1198 return true;
1199 else
1200 return false;
1201
1202 break;
1203
1204 case 2:
1205 // we may have a closed outline consisting of:
1206 // 1. arc and line, winding depends on the arc
1207 // 2. 2 arcs, winding depends on larger arc
1208 {
1209 double a1 = outline.front()->angle;
1210 double a2 = outline.back()->angle;
1211
1212 if( ( a1 < -MIN_ANG || a1 > MIN_ANG ) && ( a2 < -MIN_ANG || a2 > MIN_ANG ) )
1213 {
1214 // we have 2 arcs; the winding is determined by
1215 // the longer cord. although the angles are in
1216 // degrees, there is no need to convert to radians
1217 // to determine the longer cord.
1218 if( abs( a1 * outline.front()->radius ) >= abs( a2 * outline.back()->radius ) )
1219 {
1220 // winding depends on a1
1221 if( a1 < 0.0 )
1222 return false;
1223 else
1224 return true;
1225 }
1226 else
1227 {
1228 if( a2 < 0.0 )
1229 return false;
1230 else
1231 return true;
1232 }
1233 }
1234
1235 // we may have a line + arc (or 2 lines)
1236 if( a1 < -MIN_ANG )
1237 return false;
1238
1239 if( a1 > MIN_ANG )
1240 return true;
1241
1242 if( a2 < -MIN_ANG )
1243 return false;
1244
1245 if( a2 > MIN_ANG )
1246 return true;
1247
1248 // we have 2 lines (invalid outline)
1249 return false;
1250 }
1251 break;
1252
1253 default:
1254 break;
1255 }
1256
1257 double winding = dir + ( outline.front()->startPoint.x - outline.back()->endPoint.x )
1258 * ( outline.front()->startPoint.y + outline.back()->endPoint.y );
1259
1260 if( winding > 0.0 )
1261 return false;
1262
1263 return true;
1264}
1265
1266
1268{
1269 if( outline.front()->IsCircle() )
1270 return true;
1271
1272 return false;
1273}
1274
1275
1277{
1278 if( !outline.empty() )
1279 {
1280 if( item->IsCircle() )
1281 {
1282 // not allowed
1283 ERROR_IDF << "INVALID GEOMETRY\n";
1284 cerr << "* a circle is being added to a non-empty outline\n";
1285 return false;
1286 }
1287 else
1288 {
1289 if( outline.back()->IsCircle() )
1290 {
1291 // we can't add lines to a circle
1292 ERROR_IDF << "INVALID GEOMETRY\n";
1293 cerr << "* a line is being added to a circular outline\n";
1294 return false;
1295 }
1296 else if( !item->MatchesStart( outline.back()->endPoint ) )
1297 {
1298 // startPoint[N] != endPoint[N -1]
1299 ERROR_IDF << "INVALID GEOMETRY\n";
1300 cerr << "* disjoint segments (current start point != last end point)\n";
1301 cerr << "* start point: " << item->startPoint.x << ", " << item->startPoint.y
1302 << "\n";
1303 cerr << "* end point: " << outline.back()->endPoint.x << ", "
1304 << outline.back()->endPoint.y << "\n";
1305 return false;
1306 }
1307 }
1308 }
1309
1310 outline.push_back( item );
1311
1312 double ang = outline.back()->angle;
1313 double oang = outline.back()->offsetAngle;
1314 double radius = outline.back()->radius;
1315
1316 if( ang < -MIN_ANG || ang > MIN_ANG )
1317 {
1318 // arcs require special consideration since the winding depends on
1319 // the arc length; the arc length is adequately represented by
1320 // taking 2 cords from the endpoints to the midpoint of the arc.
1321 oang = ( oang + ang / 2.0 ) * M_PI / 180.0;
1322 double midx = outline.back()->center.x + radius * cos( oang );
1323 double midy = outline.back()->center.y + radius * sin( oang );
1324
1325 dir += ( outline.back()->endPoint.x - midx ) * ( outline.back()->endPoint.y + midy );
1326
1327 dir += ( midx - outline.back()->startPoint.x ) * ( midy + outline.back()->startPoint.y );
1328 }
1329 else
1330 {
1331 dir += ( outline.back()->endPoint.x - outline.back()->startPoint.x )
1332 * ( outline.back()->endPoint.y + outline.back()->startPoint.y );
1333 }
1334
1335 return true;
1336}
void write(std::ostream &aBoardFile, IDF3::IDF_UNIT aBoardUnit)
Write a single line representing a hole within a .DRILLED_HOLES section.
double GetDrillXPos() const
IDF3::KEY_PLATING GetDrillPlating()
double GetDrillYPos() const
std::string refdes
Definition idf_common.h:416
IDF3::KEY_REFDES kref
Definition idf_common.h:414
bool Matches(double aDrillDia, double aPosX, double aPosY) const
Return true if the given drill diameter and location matches the diameter and location of this IDF_DR...
double GetDrillDia() const
const std::string & GetDrillRefDes()
const std::string & GetDrillHoleType()
IDF_DRILL_DATA()
Create an empty drill entry which can be populated by the read() function.
std::string holetype
Definition idf_common.h:417
bool read(std::istream &aBoardFile, IDF3::IDF_UNIT aBoardUnit, IDF3::FILE_STATE aBoardState, IDF3::IDF_VERSION aIdfVersion)
Read a drill entry from an IDFv3 file.
IDF3::KEY_OWNER owner
Definition idf_common.h:418
IDF3::KEY_PLATING plating
Definition idf_common.h:413
IDF3::KEY_HOLETYPE khole
Definition idf_common.h:415
const std::string & GetText()
void SetSize(double aHeight, double aLength)
Set the height and length (mm) of the NOTE entry.
bool readNote(std::istream &aBoardFile, IDF3::FILE_STATE &aBoardState, IDF3::IDF_UNIT aBoardUnit)
Read a note entry from an IDFv3 file.
std::string text
Definition idf_common.h:298
double length
Definition idf_common.h:302
void GetPosition(double &aXpos, double &aYpos)
bool writeNote(std::ostream &aBoardFile, IDF3::IDF_UNIT aBoardUnit)
Write a note entry to an IDFv3 file.
void SetPosition(double aXpos, double aYpos)
Set the position (mm) of the NOTE entry.
double height
Definition idf_common.h:301
void GetSize(double &aHeight, double &aLength)
void SetText(const std::string &aText)
Set the text to be stored as a NOTE entry.
double xpos
Definition idf_common.h:299
double ypos
Definition idf_common.h:300
A segment and winding information for an IDF outline.
Definition idf_common.h:551
size_t size()
Definition idf_common.h:583
IDF_SEGMENT *& back()
Definition idf_common.h:607
IDF_SEGMENT *& front()
Definition idf_common.h:599
std::list< IDF_SEGMENT * > outline
Definition idf_common.h:641
void Clear()
Clear the internal list of outline segments.
Definition idf_common.h:569
bool push(IDF_SEGMENT *item)
Add a segment to the internal segment list.
A point as used by the various IDF related classes.
Definition idf_common.h:426
bool Matches(const IDF_POINT &aPoint, double aRadius=1e-5) const
Return true if the given coordinate point is within the given radius of the point.
double x
Definition idf_common.h:457
double y
Definition idf_common.h:458
double CalcDistance(const IDF_POINT &aPoint) const
Return the Euclidean distance between this point and the given point.
A segment as used in IDFv3 outlines.
Definition idf_common.h:468
double radius
radius of the arc or circle; internally calculated
Definition idf_common.h:543
void CalcCenterAndRadius()
IDF_POINT endPoint
end point coordinates in mm
Definition idf_common.h:537
bool MatchesEnd(const IDF_POINT &aPoint, double aRadius=1e-3)
Return true if the given coordinate is within a radius 'rad' of the end point.
IDF_POINT startPoint
starting point coordinates in mm
Definition idf_common.h:536
void SwapEnds()
Swap the start and end points and alters internal variables as necessary for arcs.
double angle
included angle (degrees) according to IDFv3 specification
Definition idf_common.h:541
IDF_SEGMENT()
Initialize the internal variables.
double GetMinX()
bool MatchesStart(const IDF_POINT &aPoint, double aRadius=1e-3)
Return true if the given coordinate is within a radius 'rad' of the start point.
double offsetAngle
angle between center and start of arc; internally calculated
Definition idf_common.h:542
IDF_POINT center
center of an arc or circle; internally calculated and not to be set by the user.
Definition idf_common.h:540
std::string source
std::string message
#define MIN_ANG
Definition idf_common.h:35
#define IDF_THOU_TO_MM
Definition idf_helpers.h:63
#define IDF_MIN_DIA_MM
Definition idf_helpers.h:58
#define IDF_TNM_TO_MM
Definition idf_helpers.h:65
#define IDF_MIN_DIA_TNM
Definition idf_helpers.h:60
#define IDF_MIN_DIA_THOU
Definition idf_helpers.h:59
#define ERROR_IDF
Definition idf_helpers.h:55
FILE_STATE
State values for the IDF parser's input.
Definition idf_common.h:63
KEY_REFDES
A component's Reference Designator.
Definition idf_common.h:116
KEY_PLATING
The plating condition of a hole.
Definition idf_common.h:107
bool GetIDFString(const std::string &aLine, std::string &aIDFString, bool &hasQuotes, int &aIndex)
Parse a line retrieved via FetchIDFLine() and returns the first IDF string found from the starting po...
bool FetchIDFLine(std::istream &aModel, std::string &aLine, bool &isComment, std::streampos &aFilePos)
Retrieve a single line from an IDF file and performs minimal processing.
IDF_VERSION
The supported IDF versions (3.0 and 2.0 ONLY).
Definition idf_common.h:76
IDF_UNIT
The native unit of the board and of component outlines.
Definition idf_common.h:177
bool CompareToken(const char *aTokenString, const std::string &aInputString)
Perform a case-insensitive comparison of a token string and an input string.
KEY_OWNER
The type of CAD which has ownership an object.
Definition idf_common.h:85
void GetOutline(std::list< IDF_SEGMENT * > &aLines, IDF_OUTLINE &aOutline)
Take contiguous elements from 'aLines' and stuffs them into 'aOutline'; elements put into the outline...
KEY_HOLETYPE
The purpose of an IDF hole.
Definition idf_common.h:95
double CalcAngleDeg(const IDF_POINT &aStartPoint, const IDF_POINT &aEndPoint)
Calculate the angle (degrees) between the horizon and the segment aStartPoint to aEndPoint.
bool ParseOwner(const std::string &aToken, IDF3::KEY_OWNER &aOwner)
Parse the input string for a valid IDF Owner type.
double CalcAngleRad(const IDF_POINT &aStartPoint, const IDF_POINT &aEndPoint)
Calculate the angle (radians) between the horizon and the segment aStartPoint to aEndPoint.
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
std::string message
Definition idf_common.h:46
virtual const char * what() const noexcept override
IDF_ERROR(const char *aSourceFile, const char *aSourceMethod, int aSourceLine, const std::string &aMessage) noexcept
virtual ~IDF_ERROR() noexcept
int radius
#define M_PI