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