KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_easyedapro_parser.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) 2023 Alex Shvartzkop <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
24
25
26#include <algorithm>
27#include <cmath>
28#include <memory>
29
30
31#include <json_common.h>
33#include <core/map_helpers.h>
34#include <string_utils.h>
35
36#include <wx/wfstream.h>
37#include <wx/stdstream.h>
38#include <wx/base64.h>
39#include <wx/log.h>
40#include <glm/glm.hpp>
41
42#include <progress_reporter.h>
43#include <footprint.h>
44#include <board.h>
47#include <netclass.h>
49
50#include <bezier_curves.h>
51#include <pcb_group.h>
52#include <pcb_track.h>
53#include <pcb_shape.h>
54#include <pcb_text.h>
55#include <font/font.h>
56#include <geometry/shape_arc.h>
59#include <geometry/shape_rect.h>
61#include <zone.h>
62#include <pad.h>
64#include <project.h>
65#include <fix_board_shape.h>
66#include <pcb_reference_image.h>
67#include <core/mirror.h>
68#include <trace_helpers.h>
69
70
71static const wxString QUERY_MODEL_UUID_KEY = wxS( "JLC_3DModel_Q" );
72static const wxString MODEL_SIZE_KEY = wxS( "JLC_3D_Size" );
73
74static const int SHAPE_JOIN_DISTANCE = pcbIUScale.mmToIU( 1.5 );
75
76
77double PCB_IO_EASYEDAPRO_PARSER::Convert( wxString aValue )
78{
79 double value = 0;
80
81 if( !aValue.ToCDouble( &value ) )
82 THROW_IO_ERRORF( _( "Failed to parse value: '%s'" ), aValue );
83
84 return value;
85}
86
87
88nlohmann::json PCB_IO_EASYEDAPRO_PARSER::NormalizeSafeSpacing( const nlohmann::json& aTable )
89{
90 if( !aTable.is_array() || ( aTable.size() != 8 && aTable.size() != 10 ) )
91 return aTable;
92
93 // Eight-row tables omit Copper / Plane Zone and finish with Board Outline. Ten-row
94 // tables add Copper / Plane Zone and Hole. Both predate v3's inserted Slot, Line,
95 // and Text / Image rows.
96 static constexpr size_t legacy8ToV3[] = { 0, 1, 2, 3, 4, 5, 6, 11 };
97 static constexpr size_t legacy10ToV3[] = { 0, 1, 2, 3, 4, 5, 6, 7, 11, 12 };
98 const size_t* legacyToV3 = aTable.size() == 8 ? legacy8ToV3 : legacy10ToV3;
99
100 nlohmann::json normalized = nlohmann::json::array();
101
102 for( size_t i = 0; i < 13; ++i )
103 normalized.push_back( nlohmann::json::array() );
104
105 for( size_t sourceRow = 0; sourceRow < aTable.size(); ++sourceRow )
106 {
107 if( !aTable.at( sourceRow ).is_array() )
108 continue;
109
110 for( size_t sourceCol = 0; sourceCol < aTable.at( sourceRow ).size() && sourceCol < aTable.size(); ++sourceCol )
111 {
112 normalized[legacyToV3[sourceRow]][legacyToV3[sourceCol]] = aTable.at( sourceRow ).at( sourceCol );
113 }
114 }
115
116 return normalized;
117}
118
119
121{
122 m_board = aBoard;
123}
124
125
129
130
132{
133 switch( aLayer )
134 {
135 case 1: return F_Cu;
136 case 2: return B_Cu;
137 case 3: return F_SilkS;
138 case 4: return B_SilkS;
139 case 5: return F_Mask;
140 case 6: return B_Mask;
141 case 7: return F_Paste;
142 case 8: return B_Paste;
143 case 9: return F_Fab;
144 case 10: return B_Fab;
145 case 11: return Edge_Cuts;
146 case 12: return Edge_Cuts; // Multi
147 case 13: return Dwgs_User;
148 case 14: return Eco2_User;
149
150 case 15: return In1_Cu;
151 case 16: return In2_Cu;
152 case 17: return In3_Cu;
153 case 18: return In4_Cu;
154 case 19: return In5_Cu;
155 case 20: return In6_Cu;
156 case 21: return In7_Cu;
157 case 22: return In8_Cu;
158 case 23: return In9_Cu;
159 case 24: return In10_Cu;
160 case 25: return In11_Cu;
161 case 26: return In12_Cu;
162 case 27: return In13_Cu;
163 case 28: return In14_Cu;
164 case 29: return In15_Cu;
165 case 30: return In16_Cu;
166 case 31: return In17_Cu;
167 case 32: return In18_Cu;
168 case 33: return In19_Cu;
169 case 34: return In20_Cu;
170 case 35: return In21_Cu;
171 case 36: return In22_Cu;
172 case 37: return In23_Cu;
173 case 38: return In24_Cu;
174 case 39: return In25_Cu;
175 case 40: return In26_Cu;
176 case 41: return In27_Cu;
177 case 42: return In28_Cu;
178 case 43: return In29_Cu;
179 case 44: return In30_Cu;
180
181 case 48: return F_Fab; // Component shape layer
182 case 49: return F_Fab; // Component marking
183
184 case 53: return User_4; // 3D shell outline
185 case 54: return User_5; // 3D shell top
186 case 55: return User_6; // 3D shell bot
187 case 56: return User_7; // Drill drawing
188
189 default: break;
190 }
191
192 return User_1;
193}
194
195
196static void AlignText( EDA_TEXT* text, int align )
197{
198 switch( align )
199 {
200 case 1:
201 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
202 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
203 break;
204 case 2:
205 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
206 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
207 break;
208 case 3:
209 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
210 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
211 break;
212
213 case 4:
214 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
215 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
216 break;
217 case 5:
218 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
219 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
220 break;
221 case 6:
222 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
223 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
224 break;
225
226 case 7:
227 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
228 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
229 break;
230 case 8:
231 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
232 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
233 break;
234 case 9:
235 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
236 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
237 break;
238 }
239}
240
241
242void PCB_IO_EASYEDAPRO_PARSER::FillFootprintModelInfo( FOOTPRINT* footprint, const wxString& modelUuid,
243 const wxString& modelTitle,
244 const wxString& modelTransform ) const
245{
246 // TODO: make this path configurable?
247 const wxString easyedaModelDir = wxS( "EASYEDA_MODELS" );
248 const wxString kicadModelPrefix = wxS( "${KIPRJMOD}/" ) + easyedaModelDir + wxS( "/" );
249
250 VECTOR3D kmodelOffset;
251 VECTOR3D kmodelRotation;
252
253 if( !modelUuid.IsEmpty() && !footprint->GetField( QUERY_MODEL_UUID_KEY ) )
254 {
255 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, QUERY_MODEL_UUID_KEY );
256 field->SetLayer( Cmts_User );
257 field->SetVisible( false );
258 field->SetText( modelUuid );
259 footprint->Add( field );
260 }
261
262 if( !modelTransform.IsEmpty() && !footprint->GetField( MODEL_SIZE_KEY ) )
263 {
264 wxArrayString arr = wxSplit( modelTransform, ',', '\0' );
265
266 double fitXmm = pcbIUScale.IUTomm( ScaleSize( Convert( arr[0] ) ) );
267 double fitYmm = pcbIUScale.IUTomm( ScaleSize( Convert( arr[1] ) ) );
268
269 if( fitXmm > 0.0 && fitYmm > 0.0 )
270 {
271 PCB_FIELD* field = new PCB_FIELD( footprint, FIELD_T::USER, MODEL_SIZE_KEY );
272 field->SetLayer( Cmts_User );
273 field->SetVisible( false );
274 field->SetText( wxString::FromCDouble( fitXmm ) + wxS( " " ) + wxString::FromCDouble( fitYmm ) );
275 footprint->Add( field );
276 }
277
278 kmodelRotation.z = -Convert( arr[3] );
279 kmodelRotation.x = -Convert( arr[4] );
280 kmodelRotation.y = -Convert( arr[5] );
281
282 kmodelOffset.x = pcbIUScale.IUTomm( ScaleSize( Convert( arr[6] ) ) );
283 kmodelOffset.y = pcbIUScale.IUTomm( ScaleSize( Convert( arr[7] ) ) );
284 kmodelOffset.z = pcbIUScale.IUTomm( ScaleSize( Convert( arr[8] ) ) );
285 }
286
287 if( !modelTitle.IsEmpty() && footprint->Models().empty() )
288 {
290 model.m_Filename = kicadModelPrefix + EscapeString( modelTitle, ESCAPE_CONTEXT::CTX_FILENAME ) + wxS( ".step" );
291 model.m_Offset = kmodelOffset;
292 model.m_Rotation = kmodelRotation;
293 footprint->Models().push_back( model );
294 }
295}
296
297
298std::vector<std::unique_ptr<PCB_SHAPE>> PCB_IO_EASYEDAPRO_PARSER::ParsePoly( BOARD_ITEM_CONTAINER* aContainer,
299 nlohmann::json polyData, bool aClosed,
300 bool aInFill ) const
301{
302 std::vector<std::unique_ptr<PCB_SHAPE>> results;
303
304 VECTOR2D prevPt;
305 for( int i = 0; i < (int) polyData.size(); i++ )
306 {
307 nlohmann::json val = polyData.at( i );
308
309 if( val.is_string() )
310 {
311 wxString str = val;
312 if( str == wxS( "CIRCLE" ) )
313 {
315 center.x = ( polyData.at( ++i ) );
316 center.y = ( polyData.at( ++i ) );
317 double r = ( polyData.at( ++i ) );
318
319 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::CIRCLE );
320
321 shape->SetCenter( ScalePos( center ) );
322 shape->SetEnd( ScalePos( center + VECTOR2D( r, 0 ) ) );
323 shape->SetFilled( aClosed );
324
325 results.emplace_back( std::move( shape ) );
326 }
327 else if( str == wxS( "R" ) )
328 {
329 VECTOR2D start, size;
330 start.x = ( polyData.at( ++i ) );
331 start.y = ( polyData.at( ++i ) );
332 size.x = ( polyData.at( ++i ) );
333 size.y = -( polyData.at( ++i ).get<double>() );
334 double angle = polyData.at( ++i );
335 double cr = ( i + 1 ) < (int) polyData.size() ? polyData.at( ++i ).get<double>() : 0;
336
337 if( cr == 0 )
338 {
339 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::RECTANGLE );
340
341 shape->SetStart( ScalePos( start ) );
342 shape->SetEnd( ScalePos( start + size ) );
343 shape->SetFilled( aClosed );
344 shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
345
346 results.emplace_back( std::move( shape ) );
347 }
348 else
349 {
350 VECTOR2D end = start + size;
351
352 auto addSegment = [&]( VECTOR2D aStart, VECTOR2D aEnd )
353 {
354 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
355
356 shape->SetStart( ScalePos( aStart ) );
357 shape->SetEnd( ScalePos( aEnd ) );
358 shape->SetFilled( aClosed );
359 shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
360
361 results.emplace_back( std::move( shape ) );
362 };
363
364 auto addArc = [&]( VECTOR2D aStart, VECTOR2D aEnd, VECTOR2D center )
365 {
366 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::ARC );
367
368 shape->SetStart( ScalePos( aStart ) );
369 shape->SetEnd( ScalePos( aEnd ) );
370 shape->SetCenter( ScalePos( center ) );
371 shape->SetFilled( aClosed );
372 shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
373
374 results.emplace_back( std::move( shape ) );
375 };
376
377 addSegment( { start.x + cr, start.y }, { end.x - cr, start.y } );
378 addSegment( { end.x, start.y - cr }, { end.x, end.y + cr } );
379 addSegment( { start.x + cr, end.y }, { end.x - cr, end.y } );
380 addSegment( { start.x, start.y - cr }, { start.x, end.y + cr } );
381
382 addArc( { end.x - cr, start.y }, { end.x, start.y - cr }, { end.x - cr, start.y - cr } );
383
384 addArc( { end.x, end.y + cr }, { end.x - cr, end.y }, { end.x - cr, end.y + cr } );
385
386 addArc( { start.x + cr, end.y }, { start.x, end.y + cr }, { start.x + cr, end.y + cr } );
387
388 addArc( { start.x, start.y - cr }, { start.x + cr, start.y }, { start.x + cr, start.y - cr } );
389 }
390 }
391 else if( str == wxS( "ARC" ) || str == wxS( "CARC" ) )
392 {
394 double angle = polyData.at( ++i ).get<double>() / ( aInFill ? 10 : 1 );
395 end.x = ( polyData.at( ++i ) );
396 end.y = ( polyData.at( ++i ) );
397
398 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::ARC );
399
400 if( angle < 0 )
401 {
402 shape->SetStart( ScalePos( prevPt ) );
403 shape->SetEnd( ScalePos( end ) );
404 }
405 else
406 {
407 shape->SetStart( ScalePos( end ) );
408 shape->SetEnd( ScalePos( prevPt ) );
409 }
410
411 VECTOR2D delta = end - prevPt;
412 VECTOR2D mid = ( prevPt + delta / 2 );
413
414 double ha = angle / 2;
415 double hd = delta.EuclideanNorm() / 2;
416 double cdist = hd / tan( DEG2RAD( ha ) );
417 VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
418 shape->SetCenter( ScalePos( center ) );
419
420 shape->SetFilled( aClosed );
421
422 results.emplace_back( std::move( shape ) );
423
424 prevPt = end;
425 }
426 else if( str == wxS( "L" ) )
427 {
429 chain.Append( ScalePos( prevPt ) );
430
431 while( i < (int) polyData.size() - 2 && polyData.at( i + 1 ).is_number() )
432 {
433 VECTOR2D pt;
434 pt.x = ( polyData.at( ++i ) );
435 pt.y = ( polyData.at( ++i ) );
436
437 chain.Append( ScalePos( pt ) );
438
439 prevPt = pt;
440 }
441
442 if( aClosed )
443 {
444 if( chain.PointCount() > 2 )
445 {
446 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::POLY );
447
448 chain.SetClosed( true );
449 shape->SetFilled( true );
450 shape->SetPolyShape( chain );
451
452 results.emplace_back( std::move( shape ) );
453 }
454 }
455 else
456 {
457 for( int s = 0; s < chain.SegmentCount(); s++ )
458 {
459 SEG seg = chain.Segment( s );
460
461 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aContainer, SHAPE_T::SEGMENT );
462
463 shape->SetStart( seg.A );
464 shape->SetEnd( seg.B );
465
466 results.emplace_back( std::move( shape ) );
467 }
468 }
469 }
470 }
471 else if( val.is_number() )
472 {
473 prevPt.x = ( polyData.at( i ) );
474 prevPt.y = ( polyData.at( ++i ) );
475 }
476 }
477
478 return results;
479}
480
481
483PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill, int aMaxError ) const
484{
486 VECTOR2D prevPt;
487
488 for( int i = 0; i < (int) polyData.size(); i++ )
489 {
490 nlohmann::json val = polyData.at( i );
491
492 if( val.is_string() )
493 {
494 wxString str = val;
495 if( str == wxS( "CIRCLE" ) )
496 {
498 center.x = ( polyData.at( ++i ) );
499 center.y = ( polyData.at( ++i ) );
500 double r = ( polyData.at( ++i ) );
501
503 }
504 else if( str == wxS( "R" ) )
505 {
506 VECTOR2D start, size;
507 start.x = ( polyData.at( ++i ) );
508 start.y = ( polyData.at( ++i ) );
509 size.x = ( polyData.at( ++i ) );
510 size.y = ( polyData.at( ++i ).get<double>() );
511 double angle = polyData.at( ++i );
512 double cr = ( i + 1 ) < (int) polyData.size() ? polyData.at( ++i ).get<double>() : 0;
513
514 SHAPE_POLY_SET poly;
515
516 VECTOR2D kstart = ScalePos( start );
517 VECTOR2D ksize = ScaleSize( size );
518 VECTOR2D kcenter = kstart + ksize / 2;
519 RotatePoint( kcenter, kstart, EDA_ANGLE( angle, DEGREES_T ) );
520
521 double maxRadius = std::min( std::abs( ksize.x ), std::abs( ksize.y ) ) / 2.0;
522 double radius = std::min( ScaleSize( cr ), maxRadius );
523
524 TransformRoundChamferedRectToPolygon( poly, kcenter, ksize, EDA_ANGLE( angle, DEGREES_T ), radius, 0, 0,
525 0, aMaxError, ERROR_INSIDE );
526
527 result.Append( poly.Outline( 0 ) );
528 }
529 else if( str == wxS( "ARC" ) || str == wxS( "CARC" ) )
530 {
532 double angle = polyData.at( ++i ).get<double>();
533
534 if( aInFill ) // In .epcb fills, the angle is 10x for some reason
535 angle /= 10;
536
537 end.x = ( polyData.at( ++i ) );
538 end.y = ( polyData.at( ++i ) );
539
540 SHAPE_ARC sarc;
541 sarc.ConstructFromStartEndAngle( ScalePos( prevPt ), ScalePos( end ), -EDA_ANGLE( angle, DEGREES_T ) );
542
543 result.Append( sarc, aMaxError );
544
545 prevPt = end;
546 }
547 else if( str == wxS( "C" ) )
548 {
549 VECTOR2D pt1;
550 pt1.x = ( polyData.at( ++i ) );
551 pt1.y = ( polyData.at( ++i ) );
552
553 VECTOR2D pt2;
554 pt2.x = ( polyData.at( ++i ) );
555 pt2.y = ( polyData.at( ++i ) );
556
557 VECTOR2D pt3;
558 pt3.x = ( polyData.at( ++i ) );
559 pt3.y = ( polyData.at( ++i ) );
560
561 std::vector<VECTOR2I> ctrlPoints = { ScalePos( prevPt ), ScalePos( pt1 ), ScalePos( pt2 ),
562 ScalePos( pt3 ) };
563 BEZIER_POLY converter( ctrlPoints );
564
565 std::vector<VECTOR2I> bezierPoints;
566 converter.GetPoly( bezierPoints, aMaxError );
567
568 result.Append( bezierPoints );
569
570 prevPt = pt3;
571 }
572 else if( str == wxS( "L" ) )
573 {
574 result.Append( ScalePos( prevPt ) );
575
576 while( i < (int) polyData.size() - 2 && polyData.at( i + 1 ).is_number() )
577 {
578 VECTOR2D pt;
579 pt.x = ( polyData.at( ++i ) );
580 pt.y = ( polyData.at( ++i ) );
581
582 result.Append( ScalePos( pt ) );
583
584 prevPt = pt;
585 }
586 }
587 }
588 else if( val.is_number() )
589 {
590 prevPt.x = ( polyData.at( i ) );
591 prevPt.y = ( polyData.at( ++i ) );
592 }
593 }
594
595 return result;
596}
597
598
599NETINFO_ITEM* PCB_IO_EASYEDAPRO_PARSER::getNet( BOARD* aBoard, const wxString& aNetName )
600{
601 if( !aBoard || aNetName.empty() )
602 return nullptr;
603
604 NETINFO_ITEM* net = aBoard->FindNet( aNetName );
605
606 if( !net )
607 {
608 net = new NETINFO_ITEM( aBoard, aNetName, static_cast<int>( aBoard->GetNetCount() ) + 1 );
609 aBoard->Add( net );
610 }
611
612 return net;
613}
614
615
616std::unique_ptr<PAD> PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* aFootprint, const nlohmann::json& line )
617{
618 wxString uuid = line.at( 1 );
619
620 // if( line.at( 2 ).is_number() )
621 // int unk = line.at( 2 ).get<int>();
622 // else if( line.at( 2 ).is_string() )
623 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
624
625 wxString netname = line.at( 3 );
626 int layer = line.at( 4 ).get<int>();
627 PCB_LAYER_ID klayer = LayerToKi( layer );
628
629 wxString padNumber = line.at( 5 );
630
632 center.x = line.at( 6 );
633 center.y = line.at( 7 );
634
635 double orientation = line.at( 8 );
636
637 nlohmann::json padHole = line.at( 9 );
638 nlohmann::json padShape = line.at( 10 );
639
640 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
641
642 pad->SetNumber( padNumber );
643 pad->SetPosition( ScalePos( center ) );
644 pad->SetOrientationDegrees( orientation );
645 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
646
647 // Check if this pad has a real drill hole
648 // JLCEDA may use ["ROUND",0,0] to indicate SMD pads
649 bool hasHole = false;
650
651 if( !padHole.is_null() && !padHole.empty() )
652 {
653 wxString holeShape = padHole.at( 0 );
654
655 if( holeShape == wxS( "ROUND" ) || holeShape == wxS( "SLOT" ) )
656 {
657 VECTOR2D drill;
658 drill.x = padHole.at( 1 );
659 drill.y = padHole.at( 2 );
660
661 // Only treat as PTH if hole size is non-zero
662 if( drill.x > 0 || drill.y > 0 )
663 {
664 hasHole = true;
665
666 double drill_dir = 0;
667
668 if( line.at( 14 ).is_number() )
669 drill_dir = line.at( 14 );
670
671 double deg = EDA_ANGLE( drill_dir, DEGREES_T ).Normalize90().AsDegrees();
672
673 if( std::abs( deg ) >= 45 )
674 std::swap( drill.x, drill.y ); // KiCad doesn't support arbitrary hole direction
675
676 if( holeShape == wxS( "SLOT" ) )
677 {
678 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
679 }
680
681 pad->SetDrillSize( ScaleSize( drill ) );
682 pad->SetLayerSet( PAD::PTHMask() );
683 pad->SetAttribute( PAD_ATTRIB::PTH );
684 }
685 }
686 }
687
688 // If no valid hole, this is an SMD pad
689 if( !hasHole )
690 {
691 if( klayer == F_Cu )
692 {
693 pad->SetLayerSet( PAD::SMDMask() );
694 }
695 else if( klayer == B_Cu )
696 {
697 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
698 }
699
700 pad->SetAttribute( PAD_ATTRIB::SMD );
701 }
702
703 wxString padSh = padShape.at( 0 );
704
705 if( padSh == wxS( "RECT" ) )
706 {
707 VECTOR2D size;
708 size.x = padShape.at( 1 );
709 size.y = padShape.at( 2 );
710 double cr_p = padShape.size() > 3 ? padShape.at( 3 ).get<double>() : 0;
711
712 pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) );
713
714 if( cr_p == 0 )
715 {
717 }
718 else
719 {
721 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, cr_p / 100 );
722 }
723 }
724 else if( padSh == wxS( "ELLIPSE" ) )
725 {
726 VECTOR2D size;
727 size.x = padShape.at( 1 );
728 size.y = padShape.at( 2 );
729
730 pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) );
732 }
733 else if( padSh == wxS( "OVAL" ) )
734 {
735 VECTOR2D size;
736 size.x = padShape.at( 1 );
737 size.y = padShape.at( 2 );
738
739 pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) );
741 }
742 else if( padSh == wxS( "POLY" ) || padSh == wxS( "POLYGON" ) )
743 {
745 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
746 pad->SetSize( PADSTACK::ALL_LAYERS, { 1, 1 } );
747
748 nlohmann::json polyData = padShape.at( 1 );
749
750 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
751 wxASSERT( contour.PointCount() > 2 );
752
753 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
754
755 if( contour.PointCount() > 2 )
756 {
757 contour.SetClosed( true );
758 shape->SetFilled( true );
759 shape->SetPolyShape( contour );
760 shape->SetLayer( klayer );
761 shape->SetWidth( 0 );
762 shape->Move( -pad->GetPosition() );
763
764 pad->AddPrimitive( PADSTACK::ALL_LAYERS, shape.release() );
765 }
766 }
767
768 pad->SetThermalSpokeAngle( ANGLE_90 );
769
770 return pad;
771}
772
773
774std::unique_ptr<FOOTPRINT> PCB_IO_EASYEDAPRO_PARSER::ParseFootprint( const nlohmann::json& aProject,
775 const wxString& aFpUuid,
776 const std::vector<nlohmann::json>& aLines )
777{
778 std::unique_ptr<FOOTPRINT> footprintPtr = std::make_unique<FOOTPRINT>( m_board );
779 FOOTPRINT* footprint = footprintPtr.get();
780
781 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
782 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
783 VECTOR2I canvasOrigin( 0, 0 );
784
785 for( PCB_FIELD* field : footprint->GetFields() )
786 {
787 field->SetTextSize( defaultTextSize );
788 field->SetTextThickness( defaultTextThickness );
789 }
790
791 for( const nlohmann::json& line : aLines )
792 {
793 if( line.size() == 0 )
794 continue;
795
796 wxString type = line.at( 0 );
797
798 if( type == wxS( "CANVAS" ) )
799 {
800 if( line.size() > 2 && line.at( 1 ).is_number() && line.at( 2 ).is_number() )
801 {
802 canvasOrigin = ScalePos( VECTOR2D( line.at( 1 ), line.at( 2 ) ) );
803 }
804 }
805 else if( type == wxS( "POLY" ) || type == wxS( "PAD" ) || type == wxS( "FILL" ) || type == wxS( "ATTR" ) )
806 {
807 wxString uuid = line.at( 1 );
808
809 // if( line.at( 2 ).is_number() )
810 // int unk = line.at( 2 ).get<int>();
811 // else if( line.at( 2 ).is_string() )
812 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
813
814 wxString netname = line.at( 3 );
815 int layer = line.at( 4 ).get<int>();
816 PCB_LAYER_ID klayer = LayerToKi( layer );
817
818 if( type == wxS( "POLY" ) )
819 {
820 double thickness = ( line.at( 5 ) );
821 nlohmann::json polyData = line.at( 6 );
822
823 std::vector<std::unique_ptr<PCB_SHAPE>> results = ParsePoly( footprint, polyData, false, false );
824
825 for( auto& shape : results )
826 {
827 shape->SetLayer( klayer );
828 shape->SetWidth( ScaleSize( thickness ) );
829
830 footprint->Add( shape.release(), ADD_MODE::APPEND );
831 }
832 }
833 else if( type == wxS( "PAD" ) )
834 {
835 std::unique_ptr<PAD> pad = createPAD( footprint, line );
836
837 footprint->Add( pad.release(), ADD_MODE::APPEND );
838 }
839 else if( type == wxS( "FILL" ) )
840 {
841 int fillLayer = line.at( 4 ).get<int>();
842 PCB_LAYER_ID fillKlayer = LayerToKi( fillLayer );
843
844 nlohmann::json polyDataList = line.at( 7 );
845
846 if( !polyDataList.is_null() && !polyDataList.empty() )
847 {
848 if( !polyDataList.at( 0 ).is_array() )
849 polyDataList = nlohmann::json::array( { polyDataList } );
850
851 std::vector<SHAPE_LINE_CHAIN> contours;
852 for( nlohmann::json& polyData : polyDataList )
853 {
854 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
855 contour.SetClosed( true );
856
857 contours.push_back( contour );
858 }
859
860 SHAPE_POLY_SET polySet;
861
862 for( SHAPE_LINE_CHAIN& contour : contours )
863 polySet.AddOutline( contour );
864
865 polySet.RebuildHolesFromContours();
866
867 std::unique_ptr<PCB_GROUP> group;
868
869 if( polySet.OutlineCount() > 1 )
870 group = std::make_unique<PCB_GROUP>( footprint );
871
872 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
873 {
874 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::POLY );
875
876 shape->SetFilled( true );
877 shape->SetPolyShape( poly );
878 shape->SetLayer( fillKlayer );
879 shape->SetWidth( 0 );
880
881 if( group )
882 group->AddItem( shape.get() );
883
884 footprint->Add( shape.release(), ADD_MODE::APPEND );
885 }
886
887 if( group )
888 footprint->Add( group.release(), ADD_MODE::APPEND );
889 }
890 }
891 else if( type == wxS( "ATTR" ) )
892 {
893 EASYEDAPRO::PCB_ATTR attr = line;
894
895 if( attr.key == wxS( "Designator" ) )
896 footprint->GetField( FIELD_T::REFERENCE )->SetText( attr.value );
897 }
898 }
899 else if( type == wxS( "REGION" ) )
900 {
901 wxString uuid = line.at( 1 );
902
903 // if( line.at( 2 ).is_number() )
904 // int unk = line.at( 2 ).get<int>();
905 // else if( line.at( 2 ).is_string() )
906 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
907
908 int layer = line.at( 3 ).get<int>();
909 PCB_LAYER_ID klayer = LayerToKi( layer );
910
911 std::set<int> flags = line.at( 5 );
912 nlohmann::json polyDataList = line.at( 6 );
913
914 for( nlohmann::json& polyData : polyDataList )
915 {
916 SHAPE_POLY_SET polySet;
917
918 std::vector<std::unique_ptr<PCB_SHAPE>> results = ParsePoly( nullptr, polyData, true, false );
919
920 for( auto& shape : results )
921 {
922 shape->SetFilled( true );
923 shape->TransformShapeToPolygon( polySet, klayer, 0, ARC_HIGH_DEF, ERROR_INSIDE, true );
924 }
925
926 polySet.Simplify();
927
928 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( footprint );
929
930 zone->SetIsRuleArea( true );
931 zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
932 zone->SetDoNotAllowZoneFills( !!flags.count( 7 ) || !!flags.count( 6 ) || !!flags.count( 8 ) );
933 zone->SetDoNotAllowPads( !!flags.count( 7 ) );
934 zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
935 zone->SetDoNotAllowVias( !!flags.count( 7 ) );
936
937 zone->SetLayer( klayer );
938 zone->Outline()->Append( polySet );
939
940 footprint->Add( zone.release(), ADD_MODE::APPEND );
941 }
942 }
943 }
944
945 if( aProject.is_object() && aProject.contains( "devices" ) )
946 {
947 std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devicesMap = aProject.at( "devices" );
948 std::map<wxString, wxString> compAttrs;
949
950 for( auto& [devUuid, devData] : devicesMap )
951 {
952 if( auto fp = get_opt( devData.attributes, "Footprint" ) )
953 {
954 if( *fp == aFpUuid )
955 {
956 compAttrs = devData.attributes;
957 break;
958 }
959 }
960 }
961
962 wxString modelUuid, modelTitle, modelTransform;
963
964 modelUuid = get_def( compAttrs, "3D Model", "" );
965 modelTitle = get_def( compAttrs, "3D Model Title", modelUuid );
966 modelTransform = get_def( compAttrs, "3D Model Transform", "" );
967
968 FillFootprintModelInfo( footprint, modelUuid, modelTitle, modelTransform );
969 }
970
971 // Heal board outlines
972 std::vector<PCB_SHAPE*> edgeShapes;
973
974 for( BOARD_ITEM* item : footprint->GraphicalItems() )
975 {
976 if( item->IsOnLayer( Edge_Cuts ) && item->Type() == PCB_SHAPE_T )
977 edgeShapes.push_back( static_cast<PCB_SHAPE*>( item ) );
978 }
979
981
982 // EasyEDA footprints don't have courtyard, so build a box ourselves
983 if( !footprint->IsOnLayer( F_CrtYd ) )
984 {
985 BOX2I bbox = footprint->GetLayerBoundingBox( { F_Cu, F_Fab, F_Paste, F_Mask, Edge_Cuts } );
986 bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Default courtyard clearance
987
988 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::RECTANGLE );
989
990 shape->SetWidth( pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ) );
991 shape->SetLayer( F_CrtYd );
992 shape->SetStart( bbox.GetOrigin() );
993 shape->SetEnd( bbox.GetEnd() );
994
995 footprint->Add( shape.release(), ADD_MODE::APPEND );
996 }
997
998 bool hasFabRef = false;
999
1000 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1001 {
1002 if( item->Type() == PCB_TEXT_T && item->IsOnLayer( F_Fab ) )
1003 {
1004 if( static_cast<PCB_TEXT*>( item )->GetText() == wxT( "${REFERENCE}" ) )
1005 {
1006 hasFabRef = true;
1007 break;
1008 }
1009 }
1010 }
1011
1012 if( !hasFabRef )
1013 {
1014 // Add reference text field on F_Fab
1015 int c_refTextSize = pcbIUScale.mmToIU( 0.5 ); // KLC min Fab text size
1016 int c_refTextThickness = pcbIUScale.mmToIU( 0.1 ); // Decent text thickness
1017 std::unique_ptr<PCB_TEXT> refText = std::make_unique<PCB_TEXT>( footprint );
1018
1019 refText->SetLayer( F_Fab );
1020 refText->SetTextSize( VECTOR2I( c_refTextSize, c_refTextSize ) );
1021 refText->SetTextThickness( c_refTextThickness );
1022 refText->SetText( wxT( "${REFERENCE}" ) );
1023
1024 footprint->Add( refText.release(), ADD_MODE::APPEND );
1025 }
1026
1027 footprint->MoveAnchorPosition( -canvasOrigin );
1028
1029 return footprintPtr;
1030}
1031
1032
1033void PCB_IO_EASYEDAPRO_PARSER::ParseBoard( BOARD* aBoard, const nlohmann::json& aProject,
1034 std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
1035 const std::map<wxString, EASYEDAPRO::BLOB>& aBlobMap,
1036 const std::multimap<wxString, EASYEDAPRO::POURED>& aPouredMap,
1037 const std::vector<nlohmann::json>& aLines, const wxString& aFpLibName )
1038{
1039 std::map<wxString, std::vector<nlohmann::json>> componentLines;
1040 std::map<wxString, std::vector<nlohmann::json>> ruleLines;
1041
1042 std::multimap<wxString, EASYEDAPRO::POURED> boardPouredMap = aPouredMap;
1043 std::map<wxString, ZONE*> poursToFill;
1044
1045 m_safeSpacing = nlohmann::json();
1046
1048
1049 auto zoneClearance =
1050 [&]() -> int
1051 {
1052 if( m_safeSpacing.is_array()
1053 && m_safeSpacing.size() > 7
1054 && m_safeSpacing.at( 7 ).is_array()
1055 && !m_safeSpacing.at( 7 ).empty()
1056 && m_safeSpacing.at( 7 ).at( 0 ).is_number() )
1057 {
1058 const int clearance = ScaleSize( m_safeSpacing.at( 7 ).at( 0 ).get<double>() );
1059
1060 if( clearance > 0 )
1061 return clearance;
1062 }
1063
1064 return bds.m_MinClearance;
1065 };
1066
1067 const int zoneMinThickness = bds.GetDefaultZoneSettings().m_ZoneMinThickness;
1068
1069 std::map<wxString, nlohmann::json> safeSpacingProfiles;
1070 std::map<wxString, wxString> pourSafeSpacingProfiles;
1071
1072 for( const nlohmann::json& line : aLines )
1073 {
1074 if( line.empty() || !line.at( 0 ).is_string() )
1075 continue;
1076
1077 wxString type = line.at( 0 );
1078
1079 if( type == wxS( "RULE" ) && line.size() > 4 && line.at( 1 ).get<wxString>() == wxS( "1" )
1080 && line.at( 3 ).get<int>() != 2 && line.at( 4 ).is_array() && line.at( 4 ).size() > 1
1081 && line.at( 4 ).at( 1 ).is_array() )
1082 {
1083 safeSpacingProfiles[line.at( 2 ).get<wxString>()] = NormalizeSafeSpacing( line.at( 4 ).at( 1 ) );
1084 }
1085 else if( type == wxS( "RULE_SELECTOR" ) && line.size() > 3 && line.at( 1 ).is_array() && line.at( 1 ).size() > 1
1086 && line.at( 1 ).at( 0 ).is_string() && line.at( 1 ).at( 0 ).get<wxString>() == wxS( "POUR" )
1087 && line.at( 1 ).at( 1 ).is_string() && line.at( 3 ).is_object() && line.at( 3 ).contains( "1" )
1088 && line.at( 3 ).at( "1" ).is_string() )
1089 {
1090 pourSafeSpacingProfiles[line.at( 1 ).at( 1 ).get<wxString>()] = line.at( 3 ).at( "1" ).get<wxString>();
1091 }
1092 }
1093
1094
1095 for( const nlohmann::json& line : aLines )
1096 {
1097 if( line.size() == 0 )
1098 continue;
1099
1100 wxString type = line.at( 0 );
1101
1102 if( type == wxS( "LAYER" ) )
1103 {
1104 int layer = line.at( 1 );
1105 PCB_LAYER_ID klayer = LayerToKi( layer );
1106
1107 wxString layerType = line.at( 2 );
1108 wxString layerName = line.at( 3 );
1109 int layerFlag = line.at( 4 );
1110
1111 if( layerFlag != 0 )
1112 {
1113 LSET blayers = aBoard->GetEnabledLayers();
1114 blayers.set( klayer );
1115 aBoard->SetEnabledLayers( blayers );
1116 aBoard->SetLayerName( klayer, layerName );
1117 }
1118 }
1119 else if( type == wxS( "NET" ) )
1120 {
1121 wxString netname = line.at( 1 );
1122
1123 aBoard->Add( new NETINFO_ITEM( aBoard, netname, aBoard->GetNetCount() + 1 ), ADD_MODE::APPEND );
1124 }
1125 else if( type == wxS( "RULE" ) )
1126 {
1127 wxString ruleType = line.at( 1 );
1128 wxString ruleName = line.at( 2 );
1129 int isDefault = line.at( 3 );
1130 nlohmann::json ruleData = line.at( 4 );
1131
1132 if( ruleType == wxS( "3" ) && isDefault ) // Track width
1133 {
1134 wxString units = ruleData.at( 0 );
1135
1136 if( ruleData.at( 1 ).is_number() )
1137 {
1138 // ["RULE","3","trackWidth",1,["mil",5,10,100]]
1139 double minVal = ruleData.at( 1 );
1140 // double optVal = ruleData.at( 2 );
1141 // double maxVal = ruleData.at( 3 );
1142
1143 bds.m_TrackMinWidth = ScaleSize( minVal );
1144 aBoard->m_LegacyDesignSettingsLoaded = true;
1145 }
1146 else
1147 {
1148 // ["RULE","3","trackWidth",1,["mil",{"1":[10,10,150]}]]
1149 nlohmann::json table = ruleData.at( 1 );
1150
1151 for( auto& [key, arr] : table.items() )
1152 {
1153 double minVal = arr.at( 0 );
1154 // double optVal = arr.at( 1 );
1155 // double maxVal = arr.at( 2 );
1156
1157 bds.m_TrackMinWidth = ScaleSize( minVal );
1158 aBoard->m_LegacyDesignSettingsLoaded = true;
1159 }
1160 }
1161 }
1162 else if( ruleType == wxS( "1" ) && isDefault )
1163 {
1164 wxString units = ruleData.at( 0 );
1165 nlohmann::json table = ruleData.at( 1 );
1166
1167 if( table.is_array() )
1168 {
1170 }
1171
1172 // EasyEDA Pro labels this array "mm", but its values are always mils. The
1173 // lower-triangular matrix starts with the track-to-track clearance, which maps
1174 // to KiCad's global minimum clearance; the remaining object-specific values
1175 // cannot be represented by that single board setting.
1176 const nlohmann::json& safeSpacing = m_safeSpacing;
1177
1178 if( safeSpacing.is_array() && !safeSpacing.empty() && safeSpacing.at( 0 ).is_array()
1179 && !safeSpacing.at( 0 ).empty() && safeSpacing.at( 0 ).at( 0 ).is_number() )
1180 {
1181 double trackClearance = safeSpacing.at( 0 ).at( 0 );
1182
1183 if( trackClearance > 0 )
1184 {
1185 int clearance = ScaleSize( trackClearance );
1188 aBoard->m_LegacyDesignSettingsLoaded = true;
1189 aBoard->m_LegacyNetclassesLoaded = true;
1190 }
1191 }
1192 double minEdgeClearance = 0;
1193
1194 if( safeSpacing.is_array() && safeSpacing.size() > 11 && safeSpacing.at( 11 ).is_array() )
1195 {
1196 for( const nlohmann::json& value : safeSpacing.at( 11 ) )
1197 {
1198 if( value.is_number() && value.get<double>() > 0
1199 && ( minEdgeClearance == 0 || value.get<double>() < minEdgeClearance ) )
1200 {
1201 minEdgeClearance = value;
1202 }
1203 }
1204 }
1205
1206 if( minEdgeClearance > 0 )
1207 {
1208 bds.m_CopperEdgeClearance = ScaleSize( minEdgeClearance );
1209 aBoard->m_LegacyDesignSettingsLoaded = true;
1210 }
1211 }
1212
1213 ruleLines[ruleType].push_back( line );
1214 }
1215 else if( type == wxS( "POURED" ) )
1216 {
1217 if( !line.at( 2 ).is_string() )
1218 continue; // Unknown type of POURED
1219
1220 EASYEDAPRO::POURED poured = line;
1221 boardPouredMap.emplace( poured.parentId, poured );
1222 }
1223 else if( type == wxS( "VIA" ) || type == wxS( "LINE" ) || type == wxS( "ARC" ) || type == wxS( "POLY" )
1224 || type == wxS( "FILL" ) || type == wxS( "POUR" ) )
1225 {
1226 wxString uuid = line.at( 1 );
1227
1228 // if( line.at( 2 ).is_number() )
1229 // int unk = line.at( 2 ).get<int>();
1230 // else if( line.at( 2 ).is_string() )
1231 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1232
1233 wxString netname = line.at( 3 );
1234
1235 if( type == wxS( "VIA" ) )
1236 {
1238 center.x = line.at( 5 );
1239 center.y = line.at( 6 );
1240
1241 double drill = line.at( 7 );
1242 double dia = line.at( 8 );
1243
1244 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( aBoard );
1245
1246 via->SetPosition( ScalePos( center ) );
1247 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
1248 via->SetDrill( ScaleSize( drill ) );
1249 via->SetWidth( PADSTACK::ALL_LAYERS, ScaleSize( dia ) );
1250
1251 via->SetNet( getNet( aBoard, netname ) );
1252
1253 aBoard->Add( via.release(), ADD_MODE::APPEND );
1254 }
1255 else if( type == wxS( "LINE" ) )
1256 {
1257 int layer = line.at( 4 ).get<int>();
1258 PCB_LAYER_ID klayer = LayerToKi( layer );
1259
1260 VECTOR2D start;
1261 start.x = line.at( 5 );
1262 start.y = line.at( 6 );
1263
1264 VECTOR2D end;
1265 end.x = line.at( 7 );
1266 end.y = line.at( 8 );
1267
1268 double width = line.at( 9 );
1269
1270 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
1271
1272 track->SetLayer( klayer );
1273 track->SetStart( ScalePos( start ) );
1274 track->SetEnd( ScalePos( end ) );
1275 track->SetWidth( ScaleSize( width ) );
1276
1277 track->SetNet( getNet( aBoard, netname ) );
1278
1279 aBoard->Add( track.release(), ADD_MODE::APPEND );
1280 }
1281 else if( type == wxS( "ARC" ) )
1282 {
1283 int layer = line.at( 4 ).get<int>();
1284 PCB_LAYER_ID klayer = LayerToKi( layer );
1285
1286 VECTOR2D start;
1287 start.x = line.at( 5 );
1288 start.y = line.at( 6 );
1289
1290 VECTOR2D end;
1291 end.x = line.at( 7 );
1292 end.y = line.at( 8 );
1293
1294 double angle = line.at( 9 );
1295 double width = line.at( 10 );
1296
1297 VECTOR2D delta = end - start;
1298 VECTOR2D mid = ( start + delta / 2 );
1299
1300 double ha = angle / 2;
1301 double hd = delta.EuclideanNorm() / 2;
1302 double cdist = hd / tan( DEG2RAD( ha ) );
1303 VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
1304
1305 SHAPE_ARC sarc;
1306 sarc.ConstructFromStartEndCenter( ScalePos( start ), ScalePos( end ), ScalePos( center ), angle >= 0,
1307 width );
1308
1309 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
1310 arc->SetWidth( ScaleSize( width ) );
1311
1312 arc->SetLayer( klayer );
1313 arc->SetNet( getNet( aBoard, netname ) );
1314
1315 aBoard->Add( arc.release(), ADD_MODE::APPEND );
1316 }
1317 else if( type == wxS( "FILL" ) )
1318 {
1319 int layer = line.at( 4 ).get<int>();
1320 PCB_LAYER_ID klayer = LayerToKi( layer );
1321
1322 nlohmann::json polyDataList = line.at( 7 );
1323
1324 if( !polyDataList.at( 0 ).is_array() )
1325 polyDataList = nlohmann::json::array( { polyDataList } );
1326
1327 std::vector<SHAPE_LINE_CHAIN> contours;
1328 for( nlohmann::json& polyData : polyDataList )
1329 {
1330 SHAPE_LINE_CHAIN contour = ParseContour( polyData, true );
1331 contour.SetClosed( true );
1332
1333 contours.push_back( contour );
1334 }
1335
1336 SHAPE_POLY_SET zoneFillPoly;
1337
1338 for( SHAPE_LINE_CHAIN& contour : contours )
1339 zoneFillPoly.AddOutline( contour );
1340
1341 zoneFillPoly.RebuildHolesFromContours();
1342 zoneFillPoly.Fracture();
1343
1344 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1345
1346 zone->SetNet( getNet( aBoard, netname ) );
1347 zone->SetLayer( klayer );
1348 zone->Outline()->Append( SHAPE_RECT( zoneFillPoly.BBox() ).Outline() );
1349 zone->SetFilledPolysList( klayer, zoneFillPoly );
1350 zone->SetAssignedPriority( 500 );
1351 zone->SetIsFilled( true );
1352 zone->SetNeedRefill( false );
1353
1354 zone->SetLocalClearance( zoneClearance() );
1355 zone->SetMinThickness( zoneMinThickness );
1356
1357 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1358 }
1359 else if( type == wxS( "POLY" ) )
1360 {
1361 int layer = line.at( 4 );
1362 PCB_LAYER_ID klayer = LayerToKi( layer );
1363
1364 double thickness = line.at( 5 );
1365 nlohmann::json polyData = line.at( 6 );
1366
1367 std::vector<std::unique_ptr<PCB_SHAPE>> results = ParsePoly( aBoard, polyData, false, false );
1368
1369 for( std::unique_ptr<PCB_SHAPE>& shape : results )
1370 {
1371 shape->SetLayer( klayer );
1372 shape->SetWidth( ScaleSize( thickness ) );
1373
1374 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1375 }
1376 }
1377 else if( type == wxS( "POUR" ) )
1378 {
1379 int layer = line.at( 4 ).get<int>();
1380 PCB_LAYER_ID klayer = LayerToKi( layer );
1381
1382 wxString pourname = line.at( 6 );
1383 int fillOrder = line.at( 7 ).get<int>();
1384 nlohmann::json polyDataList = line.at( 8 );
1385
1386 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1387
1388 zone->SetNet( getNet( aBoard, netname ) );
1389 zone->SetLayer( klayer );
1390 zone->SetAssignedPriority( 500 - fillOrder );
1391 int localClearance = zoneClearance();
1392
1393 if( auto selector = pourSafeSpacingProfiles.find( uuid ); selector != pourSafeSpacingProfiles.end() )
1394 {
1395 auto profile = safeSpacingProfiles.find( selector->second );
1396
1397 if( profile != safeSpacingProfiles.end() && profile->second.size() > 7
1398 && profile->second.at( 7 ).is_array() && !profile->second.at( 7 ).empty()
1399 && profile->second.at( 7 ).at( 0 ).is_number() )
1400 {
1401 const int profileClearance = ScaleSize( profile->second.at( 7 ).at( 0 ).get<double>() );
1402
1403 if( profileClearance > 0 )
1404 localClearance = profileClearance;
1405 }
1406 }
1407
1408 zone->SetLocalClearance( localClearance );
1409 zone->SetMinThickness( zoneMinThickness );
1410
1411 for( nlohmann::json& polyData : polyDataList )
1412 {
1413 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1414 contour.SetClosed( true );
1415
1416 zone->Outline()->Append( contour );
1417 }
1418
1419 wxASSERT( zone->Outline()->OutlineCount() == 1 );
1420
1421 poursToFill.emplace( uuid, zone.get() );
1422
1423 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1424 }
1425 }
1426 else if( type == wxS( "TEARDROP" ) )
1427 {
1428 wxString uuid = line.at( 1 );
1429 wxString netname = line.at( 2 );
1430 int layer = line.at( 3 ).get<int>();
1431 PCB_LAYER_ID klayer = LayerToKi( layer );
1432
1433 nlohmann::json polyData = line.at( 4 );
1434
1435 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1436 contour.SetClosed( true );
1437
1438 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1439
1440 zone->SetNet( getNet( aBoard, netname ) );
1441 zone->SetLayer( klayer );
1442 zone->Outline()->Append( contour );
1443 zone->SetFilledPolysList( klayer, contour );
1444 zone->SetNeedRefill( false );
1445 zone->SetIsFilled( true );
1446
1447 zone->SetAssignedPriority( 600 );
1448 zone->SetLocalClearance( 0 );
1449 zone->SetMinThickness( 0 );
1450 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
1451
1452 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1453 }
1454 else if( type == wxS( "REGION" ) )
1455 {
1456 wxString uuid = line.at( 1 );
1457
1458 // if( line.at( 2 ).is_number() )
1459 // int unk = line.at( 2 ).get<int>();
1460 // else if( line.at( 2 ).is_string() )
1461 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1462
1463 int layer = line.at( 3 ).get<int>();
1464 PCB_LAYER_ID klayer = LayerToKi( layer );
1465
1466 std::set<int> flags = line.at( 5 );
1467 nlohmann::json polyDataList = line.at( 6 );
1468
1469 for( nlohmann::json& polyData : polyDataList )
1470 {
1471 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1472 contour.SetClosed( true );
1473
1474 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1475
1476 zone->SetIsRuleArea( true );
1477 zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
1478 zone->SetDoNotAllowZoneFills( !!flags.count( 7 ) || !!flags.count( 6 ) || !!flags.count( 8 ) );
1479 zone->SetDoNotAllowPads( !!flags.count( 7 ) );
1480 zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
1481 zone->SetDoNotAllowVias( !!flags.count( 7 ) );
1482
1483 zone->SetLayer( klayer );
1484 zone->Outline()->Append( contour );
1485
1486 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1487 }
1488 }
1489 else if( type == wxS( "PAD" ) )
1490 {
1491 wxString netname = line.at( 3 );
1492
1493 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
1494 std::unique_ptr<PAD> pad = createPAD( footprint.get(), line );
1495
1496 pad->SetNet( getNet( aBoard, netname ) );
1497
1498 VECTOR2I pos = pad->GetPosition();
1499 EDA_ANGLE orient = pad->GetOrientation();
1500
1501 pad->SetPosition( VECTOR2I() );
1502 pad->SetOrientation( ANGLE_0 );
1503
1504 footprint->Add( pad.release(), ADD_MODE::APPEND );
1505 footprint->SetPosition( pos );
1506 footprint->SetOrientation( orient );
1507
1508 wxString fpName = wxS( "Pad_" ) + line.at( 1 ).get<wxString>();
1509 LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, fpName );
1510
1511 footprint->SetFPID( fpID );
1512 footprint->Reference().SetVisible( true );
1513 footprint->Value().SetVisible( true );
1514 footprint->AutoPositionFields();
1515
1516 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1517 }
1518 else if( type == wxS( "IMAGE" ) )
1519 {
1520 wxString uuid = line.at( 1 );
1521
1522 // if( line.at( 2 ).is_number() )
1523 // int unk = line.at( 2 ).get<int>();
1524 // else if( line.at( 2 ).is_string() )
1525 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1526
1527 int layer = line.at( 3 ).get<int>();
1528 PCB_LAYER_ID klayer = LayerToKi( layer );
1529
1530 VECTOR2D start( line.at( 4 ), line.at( 5 ) );
1531 VECTOR2D size( line.at( 6 ), line.at( 7 ) );
1532
1533 double angle = line.at( 8 ); // from top left corner
1534 int mirror = line.at( 9 );
1535 nlohmann::json polyDataList = line.at( 10 );
1536
1537 BOX2I bbox;
1538 std::vector<SHAPE_LINE_CHAIN> contours;
1539 for( nlohmann::json& polyData : polyDataList )
1540 {
1541 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1542 contour.SetClosed( true );
1543
1544 contours.push_back( contour );
1545
1546 bbox.Merge( contour.BBox() );
1547 }
1548
1549 VECTOR2D scale( ScaleSize( size.x ) / bbox.GetSize().x, ScaleSize( size.y ) / bbox.GetSize().y );
1550
1551 SHAPE_POLY_SET polySet;
1552
1553 for( SHAPE_LINE_CHAIN& contour : contours )
1554 {
1555 for( int i = 0; i < contour.PointCount(); i++ )
1556 {
1557 VECTOR2I pt = contour.CPoint( i );
1558 contour.SetPoint( i, VECTOR2I( pt.x * scale.x, pt.y * scale.y ) );
1559 }
1560
1561 polySet.AddOutline( contour );
1562 }
1563
1564 polySet.RebuildHolesFromContours();
1565
1566 std::unique_ptr<PCB_GROUP> group;
1567
1568 if( polySet.OutlineCount() > 1 )
1569 group = std::make_unique<PCB_GROUP>( aBoard );
1570
1571 BOX2I polyBBox = polySet.BBox();
1572
1573 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
1574 {
1575 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aBoard, SHAPE_T::POLY );
1576
1577 shape->SetFilled( true );
1578 shape->SetPolyShape( poly );
1579 shape->SetLayer( klayer );
1580 shape->SetWidth( 0 );
1581
1582 shape->Move( ScalePos( start ) - polyBBox.GetOrigin() );
1583 shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
1584
1585 if( IsBackLayer( klayer ) ^ !!mirror )
1586 {
1587 FLIP_DIRECTION flipDirection =
1589 shape->Mirror( ScalePos( start ), flipDirection );
1590 }
1591
1592 if( group )
1593 group->AddItem( shape.get() );
1594
1595 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1596 }
1597
1598 if( group )
1599 aBoard->Add( group.release(), ADD_MODE::APPEND );
1600 }
1601 else if( type == wxS( "OBJ" ) )
1602 {
1603 VECTOR2D start, size;
1604 wxString mimeType, base64Data;
1605 double angle = 0;
1606 int flipped = 0;
1607
1608 if( !line.at( 3 ).is_number() )
1609 continue;
1610
1611 int layer = line.at( 3 ).get<int>();
1612 PCB_LAYER_ID klayer = LayerToKi( layer );
1613
1614 start = VECTOR2D( line.at( 5 ), line.at( 6 ) );
1615 size = VECTOR2D( line.at( 7 ), line.at( 8 ) );
1616 angle = line.at( 9 );
1617 flipped = line.at( 10 );
1618
1619 wxString imageUrl = line.at( 11 );
1620
1621 if( imageUrl.BeforeFirst( ':' ) == wxS( "blob" ) )
1622 {
1623 wxString objectId = imageUrl.AfterLast( ':' );
1624
1625 if( auto blob = get_opt( aBlobMap, objectId ) )
1626 {
1627 wxString blobUrl = blob->url;
1628
1629 if( blobUrl.BeforeFirst( ':' ) == wxS( "data" ) )
1630 {
1631 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
1632
1633 base64Data = blobUrl.AfterFirst( ',' );
1634
1635 if( paramsArr.size() > 0 )
1636 mimeType = paramsArr[0];
1637 }
1638 }
1639 }
1640
1641 VECTOR2D kstart = ScalePos( start );
1642 VECTOR2D ksize = ScaleSize( size );
1643
1644 if( mimeType.empty() || base64Data.empty() )
1645 continue;
1646
1647 wxMemoryBuffer buf = wxBase64Decode( base64Data );
1648
1649 if( mimeType == wxS( "image/svg+xml" ) )
1650 {
1651 // Not yet supported by EasyEDA
1652 }
1653 else
1654 {
1655 VECTOR2D kcenter = kstart + ksize / 2;
1656
1657 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
1658 std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
1659 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
1660
1661 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
1662
1663 if( refImage.ReadImageFile( buf ) )
1664 {
1665 double scaleFactor = ScaleSize( size.x ) / refImage.GetSize().x;
1666 refImage.SetImageScale( scaleFactor );
1667
1668 // TODO: support non-90-deg angles
1669 bitmap->Rotate( kstart, EDA_ANGLE( angle, DEGREES_T ) );
1670
1671 if( flipped )
1672 {
1673 int x = bitmap->GetPosition().x;
1674 MIRROR( x, KiROUND( kstart.x ) );
1675 bitmap->SetX( x );
1676
1678 }
1679
1680 aBoard->Add( bitmap.release(), ADD_MODE::APPEND );
1681 }
1682 }
1683 }
1684 else if( type == wxS( "STRING" ) )
1685 {
1686 wxString uuid = line.at( 1 );
1687
1688 // if( line.at( 2 ).is_number() )
1689 // int unk = line.at( 2 ).get<int>();
1690 // else if( line.at( 2 ).is_string() )
1691 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1692
1693 int layer = line.at( 3 ).get<int>();
1694 PCB_LAYER_ID klayer = LayerToKi( layer );
1695
1696 VECTOR2D location( line.at( 4 ), line.at( 5 ) );
1697 wxString string = line.at( 6 );
1698 wxString font = line.at( 7 );
1699
1700 double height = line.at( 8 );
1701 double strokew = line.at( 9 );
1702
1703 int align = line.at( 12 );
1704 double angle = line.at( 13 );
1705 int inverted = line.at( 14 );
1706 int mirror = line.at( 16 );
1707
1708 PCB_TEXT* text = new PCB_TEXT( aBoard );
1709
1710 text->SetText( string );
1711 text->SetLayer( klayer );
1712 text->SetPosition( ScalePos( location ) );
1713 text->SetIsKnockout( inverted );
1714 text->SetTextThickness( ScaleSize( strokew ) );
1715 text->SetTextSize( VECTOR2D( ScaleSize( height * 0.6 ), ScaleSize( height * 0.7 ) ) );
1716
1717 if( font != wxS( "default" ) )
1718 {
1719 text->SetFont( KIFONT::FONT::GetFont( font ) );
1720 //text->SetupRenderCache( text->GetShownText(), text->GetFont(), EDA_ANGLE( angle, DEGREES_T ) );
1721
1722 //text->AddRenderCacheGlyph();
1723 // TODO: import geometry cache
1724 }
1725
1726 AlignText( text, align );
1727
1728 if( IsBackLayer( klayer ) ^ !!mirror )
1729 {
1730 text->SetMirrored( true );
1731 text->SetTextAngleDegrees( -angle );
1732 }
1733 else
1734 {
1735 text->SetTextAngleDegrees( angle );
1736 }
1737
1738 aBoard->Add( text, ADD_MODE::APPEND );
1739 }
1740 else if( type == wxS( "COMPONENT" ) )
1741 {
1742 wxString compId = line.at( 1 );
1743 componentLines[compId].push_back( line );
1744 }
1745 else if( type == wxS( "ATTR" ) )
1746 {
1747 wxString compId = line.at( 3 );
1748 componentLines[compId].push_back( line );
1749 }
1750 else if( type == wxS( "PAD_NET" ) )
1751 {
1752 wxString compId = line.at( 1 );
1753 componentLines[compId].push_back( line );
1754 }
1755 }
1756
1757 for( auto const& [compId, lines] : componentLines )
1758 {
1759 wxString deviceId;
1760 wxString fpIdOverride;
1761 wxString fpDesignator;
1762 std::map<wxString, wxString> localCompAttribs;
1763
1764 for( auto& line : lines )
1765 {
1766 if( line.size() == 0 )
1767 continue;
1768
1769 wxString type = line.at( 0 );
1770
1771 if( type == wxS( "COMPONENT" ) )
1772 {
1773 localCompAttribs = line.at( 7 );
1774 }
1775 else if( type == wxS( "ATTR" ) )
1776 {
1777 EASYEDAPRO::PCB_ATTR attr = line;
1778
1779 if( attr.key == wxS( "Device" ) )
1780 deviceId = attr.value;
1781
1782 else if( attr.key == wxS( "Footprint" ) )
1783 fpIdOverride = attr.value;
1784
1785 else if( attr.key == wxS( "Designator" ) )
1786 fpDesignator = attr.value;
1787 }
1788 }
1789
1790 if( deviceId.empty() )
1791 continue;
1792
1793 nlohmann::json compAttrs = aProject.at( "devices" ).at( deviceId ).at( "attributes" );
1794
1795 wxString fpId;
1796
1797 if( !fpIdOverride.IsEmpty() )
1798 fpId = fpIdOverride;
1799 else
1800 fpId = compAttrs.at( "Footprint" ).get<wxString>();
1801
1802 auto it = aFootprintMap.find( fpId );
1803 if( it == aFootprintMap.end() )
1804 {
1805 wxLogTrace( traceEasyEdaIo, "Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
1806 continue;
1807 }
1808
1809 std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
1810 std::unique_ptr<FOOTPRINT> footprint( static_cast<FOOTPRINT*>( footprintOrig->Duplicate( false ) ) );
1811
1812 wxString modelUuid, modelTitle, modelTransform;
1813
1814 if( auto val = get_opt( localCompAttribs, "3D Model" ) )
1815 modelUuid = *val;
1816 else
1817 modelUuid = compAttrs.value<wxString>( "3D Model", "" );
1818
1819 if( auto val = get_opt( localCompAttribs, "3D Model Title" ) )
1820 modelTitle = val->Trim();
1821 else
1822 modelTitle = compAttrs.value<wxString>( "3D Model Title", modelUuid ).Trim();
1823
1824 if( auto val = get_opt( localCompAttribs, "3D Model Transform" ) )
1825 modelTransform = *val;
1826 else
1827 modelTransform = compAttrs.value<wxString>( "3D Model Transform", "" );
1828
1829 FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
1830
1831 footprint->SetParent( aBoard );
1832
1833 for( auto& line : lines )
1834 {
1835 if( line.size() == 0 )
1836 continue;
1837
1838 wxString type = line.at( 0 );
1839
1840 if( type == wxS( "COMPONENT" ) )
1841 {
1842 int layer = line.at( 3 );
1843 PCB_LAYER_ID klayer = LayerToKi( layer );
1844
1845 VECTOR2D center( line.at( 4 ), line.at( 5 ) );
1846
1847 double orient = line.at( 6 );
1848 //std::map<wxString, wxString> props = line.at( 7 );
1849
1850 if( klayer == B_Cu )
1851 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
1852
1853 footprint->SetOrientationDegrees( orient );
1854 footprint->SetPosition( ScalePos( center ) );
1855 }
1856 else if( type == wxS( "ATTR" ) )
1857 {
1858 EASYEDAPRO::PCB_ATTR attr = line;
1859
1860 PCB_LAYER_ID klayer = LayerToKi( attr.layer );
1861
1862 PCB_FIELD* field = nullptr;
1863
1864 if( attr.key == wxS( "Designator" ) )
1865 {
1866 if( attr.key == wxS( "Designator" ) )
1867 {
1868 field = footprint->GetField( FIELD_T::REFERENCE );
1869 }
1870 else
1871 {
1872 field = new PCB_FIELD( footprint.get(), FIELD_T::USER, attr.key );
1873 footprint->Add( field, ADD_MODE::APPEND );
1874 }
1875
1876 if( attr.fontName != wxS( "default" ) )
1877 field->SetFont( KIFONT::FONT::GetFont( attr.fontName ) );
1878
1879 if( attr.valVisible && attr.keyVisible )
1880 {
1881 field->SetText( attr.key + ':' + attr.value );
1882 }
1883 else if( attr.keyVisible )
1884 {
1885 field->SetText( attr.key );
1886 }
1887 else
1888 {
1889 field->SetText( attr.value );
1890 }
1891
1892 field->SetVisible( attr.keyVisible || attr.valVisible );
1893 field->SetLayer( klayer );
1894 field->SetPosition( ScalePos( attr.position ) );
1895 field->SetTextAngleDegrees( footprint->IsFlipped() ? -attr.rotation : attr.rotation );
1896 field->SetIsKnockout( attr.inverted );
1897 field->SetTextThickness( ScaleSize( attr.strokeWidth ) );
1898 field->SetTextSize( VECTOR2D( ScaleSize( attr.height * 0.55 ), ScaleSize( attr.height * 0.6 ) ) );
1899
1900 AlignText( field, attr.textOrigin );
1901 }
1902 }
1903 else if( type == wxS( "PAD_NET" ) )
1904 {
1905 wxString padNumber = line.at( 2 );
1906 wxString padNet = line.at( 3 );
1907
1908 for( PAD* pad : footprint->Pads() )
1909 {
1910 if( pad->GetNumber() == padNumber )
1911 pad->SetNet( getNet( aBoard, padNet ) );
1912 }
1913 }
1914 }
1915
1916 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1917 }
1918
1919 // Set zone fills
1921 {
1922 for( auto& [uuid, zone] : poursToFill )
1923 {
1924 SHAPE_POLY_SET fillPolySet;
1925 SHAPE_POLY_SET thermalSpokes;
1926
1927 auto range = boardPouredMap.equal_range( uuid );
1928 for( auto& it = range.first; it != range.second; ++it )
1929 {
1930 const EASYEDAPRO::POURED& poured = it->second;
1931
1932 SHAPE_POLY_SET thisPoly;
1933
1934 for( int dataId = 0; dataId < (int) poured.polyData.size(); dataId++ )
1935 {
1936 const nlohmann::json& fillData = poured.polyData[dataId];
1937 const double ptScale = 10;
1938
1939 SHAPE_LINE_CHAIN contour = ParseContour( fillData, false, ARC_HIGH_DEF / ptScale );
1940
1941 // Scale the fill
1942 for( int i = 0; i < contour.PointCount(); i++ )
1943 contour.SetPoint( i, contour.GetPoint( i ) * ptScale );
1944
1945 if( poured.isPoly )
1946 {
1947 contour.SetClosed( true );
1948
1949 // The contour can be self-intersecting
1950 SHAPE_POLY_SET simple( contour );
1951 simple.Simplify();
1952
1953 if( dataId == 0 )
1954 {
1955 thisPoly.Append( simple );
1956 }
1957 else
1958 {
1959 thisPoly.BooleanSubtract( simple );
1960 }
1961 }
1962 else
1963 {
1964 const int thermalWidth = pcbIUScale.mmToIU( 0.2 ); // Generic
1965
1966 for( int segId = 0; segId < contour.SegmentCount(); segId++ )
1967 {
1968 const SEG& seg = contour.CSegment( segId );
1969
1970 TransformOvalToPolygon( thermalSpokes, seg.A, seg.B, thermalWidth, ARC_HIGH_DEF,
1971 ERROR_INSIDE );
1972 }
1973 }
1974 }
1975
1976 fillPolySet.Append( thisPoly );
1977 }
1978
1979 if( !fillPolySet.IsEmpty() )
1980 {
1981 fillPolySet.Simplify();
1982
1983 const int strokeWidth = pcbIUScale.MilsToIU( 8 ); // Seems to be 8 mils
1984
1985 fillPolySet.Inflate( strokeWidth / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF, false );
1986
1987 fillPolySet.BooleanAdd( thermalSpokes );
1988
1989 fillPolySet.Fracture();
1990
1991 zone->SetFilledPolysList( zone->GetFirstLayer(), fillPolySet );
1992 zone->SetNeedRefill( false );
1993 zone->SetIsFilled( true );
1994 }
1995 }
1996 }
1997
1998 // Heal board outlines
1999 std::vector<PCB_SHAPE*> shapes;
2000
2001 for( BOARD_ITEM* item : aBoard->Drawings() )
2002 {
2003 if( !item->IsOnLayer( Edge_Cuts ) )
2004 continue;
2005
2006 if( item->Type() == PCB_SHAPE_T )
2007 shapes.push_back( static_cast<PCB_SHAPE*>( item ) );
2008 }
2009
2011
2012 // Center the board
2013 BOX2I outlineBbox = aBoard->ComputeBoundingBox( true, true );
2014 PAGE_INFO pageInfo = aBoard->GetPageSettings();
2015
2016 VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ),
2017 pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) );
2018
2019 VECTOR2D offset = pageCenter - outlineBbox.GetCenter();
2020
2021
2022 int alignGrid = pcbIUScale.mmToIU( 10 );
2023 offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
2024 offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
2025
2026 aBoard->Move( offset );
2027 bds.SetAuxOrigin( offset );
2028}
@ ERROR_INSIDE
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
constexpr int ARC_HIGH_DEF
Definition base_units.h:144
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
#define DEFAULT_COURTYARD_WIDTH
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BASE_SET & set(size_t pos)
Definition base_set.h:126
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
void Mirror(FLIP_DIRECTION aFlipDirection)
Mirror image vertically (i.e.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetAuxOrigin(const VECTOR2I &aOrigin)
ZONE_SETTINGS & GetDefaultZoneSettings()
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:424
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:383
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
bool m_LegacyDesignSettingsLoaded
True if the legacy board design settings were loaded from a file.
Definition board.h:565
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
const PAGE_INFO & GetPageSettings() const
Definition board.h:1020
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2797
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition board.cpp:950
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition board.cpp:816
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition board.h:569
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2538
unsigned GetNetCount() const
Definition board.h:1252
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1194
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
const DRAWINGS & Drawings() const
Definition board.h:467
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr const SizeVec & GetSize() const
Definition box2.h:203
EDA_ANGLE Normalize90()
Definition eda_angle.h:260
double AsDegrees() const
Definition eda_angle.h:115
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
void SetTextAngleDegrees(double aOrientation)
Definition eda_text.h:181
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:471
const BOX2I GetLayerBoundingBox(const LSET &aLayers) const
Return the bounding box of the footprint on a given set of layers.
void MoveAnchorPosition(const VECTOR2I &aMoveVector)
Move the reference point of the footprint.
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:375
DRAWINGS & GraphicalItems()
Definition footprint.h:358
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:144
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
void SetClearance(int aClearance)
Definition netclass.h:144
Handle the data for a net.
Definition netinfo.h:50
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:615
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:622
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
double GetHeightMils() const
Definition page_info.h:142
double GetWidthMils() const
Definition page_info.h:137
static nlohmann::json NormalizeSafeSpacing(const nlohmann::json &aTable)
Map legacy eight- and ten-row safe-spacing matrices to the v3 category layout.
static VECTOR2< T > ScalePos(VECTOR2< T > aValue)
PCB_IO_EASYEDAPRO_PARSER(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter)
std::unique_ptr< FOOTPRINT > ParseFootprint(const nlohmann::json &aProject, const wxString &aFpUuid, const std::vector< nlohmann::json > &aLines)
std::vector< std::unique_ptr< PCB_SHAPE > > ParsePoly(BOARD_ITEM_CONTAINER *aContainer, nlohmann::json polyData, bool aClosed, bool aInFill) const
SHAPE_LINE_CHAIN ParseContour(nlohmann::json polyData, bool aInFill, int aMaxError=SHAPE_ARC::DefaultAccuracyForPCB()) const
NETINFO_ITEM * getNet(BOARD *aBoard, const wxString &aNetName)
void FillFootprintModelInfo(FOOTPRINT *footprint, const wxString &modelUuid, const wxString &modelTitle, const wxString &modelTransform) const
void ParseBoard(BOARD *aBoard, const nlohmann::json &aProject, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, const std::map< wxString, EASYEDAPRO::BLOB > &aBlobMap, const std::multimap< wxString, EASYEDAPRO::POURED > &aPouredMap, const std::vector< nlohmann::json > &aLines, const wxString &aFpLibName)
std::unique_ptr< PAD > createPAD(FOOTPRINT *aFootprint, const nlohmann::json &line)
PCB_LAYER_ID LayerToKi(int aLayer)
static double Convert(wxString aValue)
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:512
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
Definition pcb_text.cpp:484
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:103
A progress reporter interface for use in multi-threaded environments.
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
BITMAP_BASE & MutableImage() const
Only use this if you really need to modify the underlying image.
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
VECTOR2I GetSize() const
void SetImageScale(double aScale)
Set the image "zoom" value.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
SHAPE_ARC & ConstructFromStartEndAngle(const VECTOR2I &aStart, const VECTOR2I &aEnd, const EDA_ANGLE &aAngle, double aWidth=0)
Construct this arc from the given start, end and angle.
SHAPE_ARC & ConstructFromStartEndCenter(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aClockwise=false, double aWidth=0)
Constructs this arc from the given start, end and center.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
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 BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
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)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
void RebuildHolesFromContours()
Extract all contours from this polygon set, then recreate polygons with holes.
int OutlineCount() const
Return the number of outlines in the set.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const std::vector< POLYGON > & CPolygons() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
const SHAPE_LINE_CHAIN Outline() const
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ DEGREES_T
Definition eda_angle.h:30
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
void ConnectBoardShapes(std::vector< PCB_SHAPE * > &aShapeList, int aChainingEpsilon)
Connects shapes to each other, making continious contours (adjacent shapes will have a common vertex)...
#define THROW_IO_ERRORF(msg,...)
const wxChar *const traceEasyEdaIo
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:867
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ In22_Cu
Definition layer_ids.h:83
@ In11_Cu
Definition layer_ids.h:72
@ In29_Cu
Definition layer_ids.h:90
@ In30_Cu
Definition layer_ids.h:91
@ F_CrtYd
Definition layer_ids.h:112
@ In17_Cu
Definition layer_ids.h:78
@ Edge_Cuts
Definition layer_ids.h:108
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ In9_Cu
Definition layer_ids.h:70
@ Cmts_User
Definition layer_ids.h:104
@ User_6
Definition layer_ids.h:125
@ User_7
Definition layer_ids.h:126
@ In19_Cu
Definition layer_ids.h:80
@ In7_Cu
Definition layer_ids.h:68
@ In28_Cu
Definition layer_ids.h:89
@ In26_Cu
Definition layer_ids.h:87
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ User_5
Definition layer_ids.h:124
@ F_Mask
Definition layer_ids.h:93
@ In21_Cu
Definition layer_ids.h:82
@ In23_Cu
Definition layer_ids.h:84
@ B_Paste
Definition layer_ids.h:101
@ In15_Cu
Definition layer_ids.h:76
@ In2_Cu
Definition layer_ids.h:63
@ F_Fab
Definition layer_ids.h:115
@ In10_Cu
Definition layer_ids.h:71
@ F_SilkS
Definition layer_ids.h:96
@ In4_Cu
Definition layer_ids.h:65
@ Eco2_User
Definition layer_ids.h:106
@ In16_Cu
Definition layer_ids.h:77
@ In24_Cu
Definition layer_ids.h:85
@ In1_Cu
Definition layer_ids.h:62
@ User_1
Definition layer_ids.h:120
@ B_SilkS
Definition layer_ids.h:97
@ In13_Cu
Definition layer_ids.h:74
@ User_4
Definition layer_ids.h:123
@ In8_Cu
Definition layer_ids.h:69
@ In14_Cu
Definition layer_ids.h:75
@ In12_Cu
Definition layer_ids.h:73
@ In27_Cu
Definition layer_ids.h:88
@ In6_Cu
Definition layer_ids.h:67
@ In5_Cu
Definition layer_ids.h:66
@ In3_Cu
Definition layer_ids.h:64
@ In20_Cu
Definition layer_ids.h:81
@ F_Cu
Definition layer_ids.h:60
@ In18_Cu
Definition layer_ids.h:79
@ In25_Cu
Definition layer_ids.h:86
@ B_Fab
Definition layer_ids.h:114
wxString get_def(const std::map< wxString, wxString > &aMap, const char *aKey, const char *aDefval="")
Definition map_helpers.h:60
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition map_helpers.h:30
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:41
FLIP_DIRECTION
Definition mirror.h:23
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
LIB_ID ToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
static const bool IMPORT_POURED
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ ROUNDRECT
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:53
Class to handle a set of BOARD_ITEMs.
static const wxString MODEL_SIZE_KEY
static const int SHAPE_JOIN_DISTANCE
static void AlignText(EDA_TEXT *text, int align)
static const wxString QUERY_MODEL_UUID_KEY
static bool addSegment(VRML_LAYER &model, IDF_SEGMENT *seg, int icont, int iseg)
const int scale
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_FILENAME
nlohmann::json polyData
@ USER
The field ID hasn't been set yet; field is invalid.
@ REFERENCE
Field Reference of part, i.e. "IC21".
KIBIS_MODEL * model
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
int clearance
VECTOR2I location
wxString result
Test unit parsing edge cases and error handling.
int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
wxLogTrace helper definitions.
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:227
double DEG2RAD(double deg)
Definition trigo.h:178
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
VECTOR3< double > VECTOR3D
Definition vector3.h:230