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 auto zoneClearance = [&]() -> int
1043 {
1044 if( m_safeSpacing.is_array() && m_safeSpacing.size() > 7 && m_safeSpacing.at( 7 ).is_array()
1045 && !m_safeSpacing.at( 7 ).empty() && m_safeSpacing.at( 7 ).at( 0 ).is_number() )
1046 {
1047 const int clearance = ScaleSize( m_safeSpacing.at( 7 ).at( 0 ).get<double>() );
1048
1049 if( clearance > 0 )
1050 return clearance;
1051 }
1052
1053 return bds.m_MinClearance;
1054 };
1055 const int zoneMinThickness = bds.GetDefaultZoneSettings().m_ZoneMinThickness;
1056
1057 std::map<wxString, nlohmann::json> safeSpacingProfiles;
1058 std::map<wxString, wxString> pourSafeSpacingProfiles;
1059
1060 for( const nlohmann::json& line : aLines )
1061 {
1062 if( line.empty() || !line.at( 0 ).is_string() )
1063 continue;
1064
1065 wxString type = line.at( 0 );
1066
1067 if( type == wxS( "RULE" ) && line.size() > 4 && line.at( 1 ).get<wxString>() == wxS( "1" )
1068 && line.at( 3 ).get<int>() != 2 && line.at( 4 ).is_array() && line.at( 4 ).size() > 1
1069 && line.at( 4 ).at( 1 ).is_array() )
1070 {
1071 safeSpacingProfiles[line.at( 2 ).get<wxString>()] = NormalizeSafeSpacing( line.at( 4 ).at( 1 ) );
1072 }
1073 else if( type == wxS( "RULE_SELECTOR" ) && line.size() > 3 && line.at( 1 ).is_array() && line.at( 1 ).size() > 1
1074 && line.at( 1 ).at( 0 ).is_string() && line.at( 1 ).at( 0 ).get<wxString>() == wxS( "POUR" )
1075 && line.at( 1 ).at( 1 ).is_string() && line.at( 3 ).is_object() && line.at( 3 ).contains( "1" )
1076 && line.at( 3 ).at( "1" ).is_string() )
1077 {
1078 pourSafeSpacingProfiles[line.at( 1 ).at( 1 ).get<wxString>()] = line.at( 3 ).at( "1" ).get<wxString>();
1079 }
1080 }
1081
1082
1083 for( const nlohmann::json& line : aLines )
1084 {
1085 if( line.size() == 0 )
1086 continue;
1087
1088 wxString type = line.at( 0 );
1089
1090 if( type == wxS( "LAYER" ) )
1091 {
1092 int layer = line.at( 1 );
1093 PCB_LAYER_ID klayer = LayerToKi( layer );
1094
1095 wxString layerType = line.at( 2 );
1096 wxString layerName = line.at( 3 );
1097 int layerFlag = line.at( 4 );
1098
1099 if( layerFlag != 0 )
1100 {
1101 LSET blayers = aBoard->GetEnabledLayers();
1102 blayers.set( klayer );
1103 aBoard->SetEnabledLayers( blayers );
1104 aBoard->SetLayerName( klayer, layerName );
1105 }
1106 }
1107 else if( type == wxS( "NET" ) )
1108 {
1109 wxString netname = line.at( 1 );
1110
1111 aBoard->Add( new NETINFO_ITEM( aBoard, netname, aBoard->GetNetCount() + 1 ), ADD_MODE::APPEND );
1112 }
1113 else if( type == wxS( "RULE" ) )
1114 {
1115 wxString ruleType = line.at( 1 );
1116 wxString ruleName = line.at( 2 );
1117 int isDefault = line.at( 3 );
1118 nlohmann::json ruleData = line.at( 4 );
1119
1120 if( ruleType == wxS( "3" ) && isDefault ) // Track width
1121 {
1122 wxString units = ruleData.at( 0 );
1123
1124 if( ruleData.at( 1 ).is_number() )
1125 {
1126 // ["RULE","3","trackWidth",1,["mil",5,10,100]]
1127 double minVal = ruleData.at( 1 );
1128 // double optVal = ruleData.at( 2 );
1129 // double maxVal = ruleData.at( 3 );
1130
1131 bds.m_TrackMinWidth = ScaleSize( minVal );
1132 aBoard->m_LegacyDesignSettingsLoaded = true;
1133 }
1134 else
1135 {
1136 // ["RULE","3","trackWidth",1,["mil",{"1":[10,10,150]}]]
1137 nlohmann::json table = ruleData.at( 1 );
1138
1139 for( auto& [key, arr] : table.items() )
1140 {
1141 double minVal = arr.at( 0 );
1142 // double optVal = arr.at( 1 );
1143 // double maxVal = arr.at( 2 );
1144
1145 bds.m_TrackMinWidth = ScaleSize( minVal );
1146 aBoard->m_LegacyDesignSettingsLoaded = true;
1147 }
1148 }
1149 }
1150 else if( ruleType == wxS( "1" ) && isDefault )
1151 {
1152 wxString units = ruleData.at( 0 );
1153 nlohmann::json table = ruleData.at( 1 );
1154
1155 if( table.is_array() )
1156 {
1158 }
1159
1160 // EasyEDA Pro labels this array "mm", but its values are always mils. The
1161 // lower-triangular matrix starts with the track-to-track clearance, which maps
1162 // to KiCad's global minimum clearance; the remaining object-specific values
1163 // cannot be represented by that single board setting.
1164 const nlohmann::json& safeSpacing = m_safeSpacing;
1165
1166 if( safeSpacing.is_array() && !safeSpacing.empty() && safeSpacing.at( 0 ).is_array()
1167 && !safeSpacing.at( 0 ).empty() && safeSpacing.at( 0 ).at( 0 ).is_number() )
1168 {
1169 double trackClearance = safeSpacing.at( 0 ).at( 0 );
1170
1171 if( trackClearance > 0 )
1172 {
1173 int clearance = ScaleSize( trackClearance );
1176 aBoard->m_LegacyDesignSettingsLoaded = true;
1177 aBoard->m_LegacyNetclassesLoaded = true;
1178 }
1179 }
1180 double minEdgeClearance = 0;
1181
1182 if( safeSpacing.is_array() && safeSpacing.size() > 11 && safeSpacing.at( 11 ).is_array() )
1183 {
1184 for( const nlohmann::json& value : safeSpacing.at( 11 ) )
1185 {
1186 if( value.is_number() && value.get<double>() > 0
1187 && ( minEdgeClearance == 0 || value.get<double>() < minEdgeClearance ) )
1188 {
1189 minEdgeClearance = value;
1190 }
1191 }
1192 }
1193
1194 if( minEdgeClearance > 0 )
1195 {
1196 bds.m_CopperEdgeClearance = ScaleSize( minEdgeClearance );
1197 aBoard->m_LegacyDesignSettingsLoaded = true;
1198 }
1199 }
1200
1201 ruleLines[ruleType].push_back( line );
1202 }
1203 else if( type == wxS( "POURED" ) )
1204 {
1205 if( !line.at( 2 ).is_string() )
1206 continue; // Unknown type of POURED
1207
1208 EASYEDAPRO::POURED poured = line;
1209 boardPouredMap.emplace( poured.parentId, poured );
1210 }
1211 else if( type == wxS( "VIA" ) || type == wxS( "LINE" ) || type == wxS( "ARC" ) || type == wxS( "POLY" )
1212 || type == wxS( "FILL" ) || type == wxS( "POUR" ) )
1213 {
1214 wxString uuid = line.at( 1 );
1215
1216 // if( line.at( 2 ).is_number() )
1217 // int unk = line.at( 2 ).get<int>();
1218 // else if( line.at( 2 ).is_string() )
1219 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1220
1221 wxString netname = line.at( 3 );
1222
1223 if( type == wxS( "VIA" ) )
1224 {
1226 center.x = line.at( 5 );
1227 center.y = line.at( 6 );
1228
1229 double drill = line.at( 7 );
1230 double dia = line.at( 8 );
1231
1232 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( aBoard );
1233
1234 via->SetPosition( ScalePos( center ) );
1235 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
1236 via->SetDrill( ScaleSize( drill ) );
1237 via->SetWidth( PADSTACK::ALL_LAYERS, ScaleSize( dia ) );
1238
1239 via->SetNet( getNet( aBoard, netname ) );
1240
1241 aBoard->Add( via.release(), ADD_MODE::APPEND );
1242 }
1243 else if( type == wxS( "LINE" ) )
1244 {
1245 int layer = line.at( 4 ).get<int>();
1246 PCB_LAYER_ID klayer = LayerToKi( layer );
1247
1248 VECTOR2D start;
1249 start.x = line.at( 5 );
1250 start.y = line.at( 6 );
1251
1252 VECTOR2D end;
1253 end.x = line.at( 7 );
1254 end.y = line.at( 8 );
1255
1256 double width = line.at( 9 );
1257
1258 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
1259
1260 track->SetLayer( klayer );
1261 track->SetStart( ScalePos( start ) );
1262 track->SetEnd( ScalePos( end ) );
1263 track->SetWidth( ScaleSize( width ) );
1264
1265 track->SetNet( getNet( aBoard, netname ) );
1266
1267 aBoard->Add( track.release(), ADD_MODE::APPEND );
1268 }
1269 else if( type == wxS( "ARC" ) )
1270 {
1271 int layer = line.at( 4 ).get<int>();
1272 PCB_LAYER_ID klayer = LayerToKi( layer );
1273
1274 VECTOR2D start;
1275 start.x = line.at( 5 );
1276 start.y = line.at( 6 );
1277
1278 VECTOR2D end;
1279 end.x = line.at( 7 );
1280 end.y = line.at( 8 );
1281
1282 double angle = line.at( 9 );
1283 double width = line.at( 10 );
1284
1285 VECTOR2D delta = end - start;
1286 VECTOR2D mid = ( start + delta / 2 );
1287
1288 double ha = angle / 2;
1289 double hd = delta.EuclideanNorm() / 2;
1290 double cdist = hd / tan( DEG2RAD( ha ) );
1291 VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
1292
1293 SHAPE_ARC sarc;
1294 sarc.ConstructFromStartEndCenter( ScalePos( start ), ScalePos( end ), ScalePos( center ), angle >= 0,
1295 width );
1296
1297 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
1298 arc->SetWidth( ScaleSize( width ) );
1299
1300 arc->SetLayer( klayer );
1301 arc->SetNet( getNet( aBoard, netname ) );
1302
1303 aBoard->Add( arc.release(), ADD_MODE::APPEND );
1304 }
1305 else if( type == wxS( "FILL" ) )
1306 {
1307 int layer = line.at( 4 ).get<int>();
1308 PCB_LAYER_ID klayer = LayerToKi( layer );
1309
1310 nlohmann::json polyDataList = line.at( 7 );
1311
1312 if( !polyDataList.at( 0 ).is_array() )
1313 polyDataList = nlohmann::json::array( { polyDataList } );
1314
1315 std::vector<SHAPE_LINE_CHAIN> contours;
1316 for( nlohmann::json& polyData : polyDataList )
1317 {
1318 SHAPE_LINE_CHAIN contour = ParseContour( polyData, true );
1319 contour.SetClosed( true );
1320
1321 contours.push_back( contour );
1322 }
1323
1324 SHAPE_POLY_SET zoneFillPoly;
1325
1326 for( SHAPE_LINE_CHAIN& contour : contours )
1327 zoneFillPoly.AddOutline( contour );
1328
1329 zoneFillPoly.RebuildHolesFromContours();
1330 zoneFillPoly.Fracture();
1331
1332 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1333
1334 zone->SetNet( getNet( aBoard, netname ) );
1335 zone->SetLayer( klayer );
1336 zone->Outline()->Append( SHAPE_RECT( zoneFillPoly.BBox() ).Outline() );
1337 zone->SetFilledPolysList( klayer, zoneFillPoly );
1338 zone->SetAssignedPriority( 500 );
1339 zone->SetIsFilled( true );
1340 zone->SetNeedRefill( false );
1341
1342 zone->SetLocalClearance( zoneClearance() );
1343 zone->SetMinThickness( zoneMinThickness );
1344
1345 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1346 }
1347 else if( type == wxS( "POLY" ) )
1348 {
1349 int layer = line.at( 4 );
1350 PCB_LAYER_ID klayer = LayerToKi( layer );
1351
1352 double thickness = line.at( 5 );
1353 nlohmann::json polyData = line.at( 6 );
1354
1355 std::vector<std::unique_ptr<PCB_SHAPE>> results = ParsePoly( aBoard, polyData, false, false );
1356
1357 for( auto& shape : results )
1358 {
1359 shape->SetLayer( klayer );
1360 shape->SetWidth( ScaleSize( thickness ) );
1361
1362 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1363 }
1364 }
1365 else if( type == wxS( "POUR" ) )
1366 {
1367 int layer = line.at( 4 ).get<int>();
1368 PCB_LAYER_ID klayer = LayerToKi( layer );
1369
1370 wxString pourname = line.at( 6 );
1371 int fillOrder = line.at( 7 ).get<int>();
1372 nlohmann::json polyDataList = line.at( 8 );
1373
1374 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1375
1376 zone->SetNet( getNet( aBoard, netname ) );
1377 zone->SetLayer( klayer );
1378 zone->SetAssignedPriority( 500 - fillOrder );
1379 int localClearance = zoneClearance();
1380
1381 if( auto selector = pourSafeSpacingProfiles.find( uuid ); selector != pourSafeSpacingProfiles.end() )
1382 {
1383 auto profile = safeSpacingProfiles.find( selector->second );
1384
1385 if( profile != safeSpacingProfiles.end() && profile->second.size() > 7
1386 && profile->second.at( 7 ).is_array() && !profile->second.at( 7 ).empty()
1387 && profile->second.at( 7 ).at( 0 ).is_number() )
1388 {
1389 const int profileClearance = ScaleSize( profile->second.at( 7 ).at( 0 ).get<double>() );
1390
1391 if( profileClearance > 0 )
1392 localClearance = profileClearance;
1393 }
1394 }
1395
1396 zone->SetLocalClearance( localClearance );
1397 zone->SetMinThickness( zoneMinThickness );
1398
1399 for( nlohmann::json& polyData : polyDataList )
1400 {
1401 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1402 contour.SetClosed( true );
1403
1404 zone->Outline()->Append( contour );
1405 }
1406
1407 wxASSERT( zone->Outline()->OutlineCount() == 1 );
1408
1409 poursToFill.emplace( uuid, zone.get() );
1410
1411 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1412 }
1413 }
1414 else if( type == wxS( "TEARDROP" ) )
1415 {
1416 wxString uuid = line.at( 1 );
1417 wxString netname = line.at( 2 );
1418 int layer = line.at( 3 ).get<int>();
1419 PCB_LAYER_ID klayer = LayerToKi( layer );
1420
1421 nlohmann::json polyData = line.at( 4 );
1422
1423 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1424 contour.SetClosed( true );
1425
1426 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1427
1428 zone->SetNet( getNet( aBoard, netname ) );
1429 zone->SetLayer( klayer );
1430 zone->Outline()->Append( contour );
1431 zone->SetFilledPolysList( klayer, contour );
1432 zone->SetNeedRefill( false );
1433 zone->SetIsFilled( true );
1434
1435 zone->SetAssignedPriority( 600 );
1436 zone->SetLocalClearance( 0 );
1437 zone->SetMinThickness( 0 );
1438 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
1439
1440 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1441 }
1442 else if( type == wxS( "REGION" ) )
1443 {
1444 wxString uuid = line.at( 1 );
1445
1446 // if( line.at( 2 ).is_number() )
1447 // int unk = line.at( 2 ).get<int>();
1448 // else if( line.at( 2 ).is_string() )
1449 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1450
1451 int layer = line.at( 3 ).get<int>();
1452 PCB_LAYER_ID klayer = LayerToKi( layer );
1453
1454 std::set<int> flags = line.at( 5 );
1455 nlohmann::json polyDataList = line.at( 6 );
1456
1457 for( nlohmann::json& polyData : polyDataList )
1458 {
1459 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1460 contour.SetClosed( true );
1461
1462 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1463
1464 zone->SetIsRuleArea( true );
1465 zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
1466 zone->SetDoNotAllowZoneFills( !!flags.count( 7 ) || !!flags.count( 6 ) || !!flags.count( 8 ) );
1467 zone->SetDoNotAllowPads( !!flags.count( 7 ) );
1468 zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
1469 zone->SetDoNotAllowVias( !!flags.count( 7 ) );
1470
1471 zone->SetLayer( klayer );
1472 zone->Outline()->Append( contour );
1473
1474 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1475 }
1476 }
1477 else if( type == wxS( "PAD" ) )
1478 {
1479 wxString netname = line.at( 3 );
1480
1481 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
1482 std::unique_ptr<PAD> pad = createPAD( footprint.get(), line );
1483
1484 pad->SetNet( getNet( aBoard, netname ) );
1485
1486 VECTOR2I pos = pad->GetPosition();
1487 EDA_ANGLE orient = pad->GetOrientation();
1488
1489 pad->SetPosition( VECTOR2I() );
1490 pad->SetOrientation( ANGLE_0 );
1491
1492 footprint->Add( pad.release(), ADD_MODE::APPEND );
1493 footprint->SetPosition( pos );
1494 footprint->SetOrientation( orient );
1495
1496 wxString fpName = wxS( "Pad_" ) + line.at( 1 ).get<wxString>();
1497 LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, fpName );
1498
1499 footprint->SetFPID( fpID );
1500 footprint->Reference().SetVisible( true );
1501 footprint->Value().SetVisible( true );
1502 footprint->AutoPositionFields();
1503
1504 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1505 }
1506 else if( type == wxS( "IMAGE" ) )
1507 {
1508 wxString uuid = line.at( 1 );
1509
1510 // if( line.at( 2 ).is_number() )
1511 // int unk = line.at( 2 ).get<int>();
1512 // else if( line.at( 2 ).is_string() )
1513 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1514
1515 int layer = line.at( 3 ).get<int>();
1516 PCB_LAYER_ID klayer = LayerToKi( layer );
1517
1518 VECTOR2D start( line.at( 4 ), line.at( 5 ) );
1519 VECTOR2D size( line.at( 6 ), line.at( 7 ) );
1520
1521 double angle = line.at( 8 ); // from top left corner
1522 int mirror = line.at( 9 );
1523 nlohmann::json polyDataList = line.at( 10 );
1524
1525 BOX2I bbox;
1526 std::vector<SHAPE_LINE_CHAIN> contours;
1527 for( nlohmann::json& polyData : polyDataList )
1528 {
1529 SHAPE_LINE_CHAIN contour = ParseContour( polyData, false );
1530 contour.SetClosed( true );
1531
1532 contours.push_back( contour );
1533
1534 bbox.Merge( contour.BBox() );
1535 }
1536
1537 VECTOR2D scale( ScaleSize( size.x ) / bbox.GetSize().x, ScaleSize( size.y ) / bbox.GetSize().y );
1538
1539 SHAPE_POLY_SET polySet;
1540
1541 for( SHAPE_LINE_CHAIN& contour : contours )
1542 {
1543 for( int i = 0; i < contour.PointCount(); i++ )
1544 {
1545 VECTOR2I pt = contour.CPoint( i );
1546 contour.SetPoint( i, VECTOR2I( pt.x * scale.x, pt.y * scale.y ) );
1547 }
1548
1549 polySet.AddOutline( contour );
1550 }
1551
1552 polySet.RebuildHolesFromContours();
1553
1554 std::unique_ptr<PCB_GROUP> group;
1555
1556 if( polySet.OutlineCount() > 1 )
1557 group = std::make_unique<PCB_GROUP>( aBoard );
1558
1559 BOX2I polyBBox = polySet.BBox();
1560
1561 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
1562 {
1563 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aBoard, SHAPE_T::POLY );
1564
1565 shape->SetFilled( true );
1566 shape->SetPolyShape( poly );
1567 shape->SetLayer( klayer );
1568 shape->SetWidth( 0 );
1569
1570 shape->Move( ScalePos( start ) - polyBBox.GetOrigin() );
1571 shape->Rotate( ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
1572
1573 if( IsBackLayer( klayer ) ^ !!mirror )
1574 {
1575 FLIP_DIRECTION flipDirection =
1577 shape->Mirror( ScalePos( start ), flipDirection );
1578 }
1579
1580 if( group )
1581 group->AddItem( shape.get() );
1582
1583 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1584 }
1585
1586 if( group )
1587 aBoard->Add( group.release(), ADD_MODE::APPEND );
1588 }
1589 else if( type == wxS( "OBJ" ) )
1590 {
1591 VECTOR2D start, size;
1592 wxString mimeType, base64Data;
1593 double angle = 0;
1594 int flipped = 0;
1595
1596 if( !line.at( 3 ).is_number() )
1597 continue;
1598
1599 int layer = line.at( 3 ).get<int>();
1600 PCB_LAYER_ID klayer = LayerToKi( layer );
1601
1602 start = VECTOR2D( line.at( 5 ), line.at( 6 ) );
1603 size = VECTOR2D( line.at( 7 ), line.at( 8 ) );
1604 angle = line.at( 9 );
1605 flipped = line.at( 10 );
1606
1607 wxString imageUrl = line.at( 11 );
1608
1609 if( imageUrl.BeforeFirst( ':' ) == wxS( "blob" ) )
1610 {
1611 wxString objectId = imageUrl.AfterLast( ':' );
1612
1613 if( auto blob = get_opt( aBlobMap, objectId ) )
1614 {
1615 wxString blobUrl = blob->url;
1616
1617 if( blobUrl.BeforeFirst( ':' ) == wxS( "data" ) )
1618 {
1619 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
1620
1621 base64Data = blobUrl.AfterFirst( ',' );
1622
1623 if( paramsArr.size() > 0 )
1624 mimeType = paramsArr[0];
1625 }
1626 }
1627 }
1628
1629 VECTOR2D kstart = ScalePos( start );
1630 VECTOR2D ksize = ScaleSize( size );
1631
1632 if( mimeType.empty() || base64Data.empty() )
1633 continue;
1634
1635 wxMemoryBuffer buf = wxBase64Decode( base64Data );
1636
1637 if( mimeType == wxS( "image/svg+xml" ) )
1638 {
1639 // Not yet supported by EasyEDA
1640 }
1641 else
1642 {
1643 VECTOR2D kcenter = kstart + ksize / 2;
1644
1645 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
1646 std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
1647 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
1648
1649 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
1650
1651 if( refImage.ReadImageFile( buf ) )
1652 {
1653 double scaleFactor = ScaleSize( size.x ) / refImage.GetSize().x;
1654 refImage.SetImageScale( scaleFactor );
1655
1656 // TODO: support non-90-deg angles
1657 bitmap->Rotate( kstart, EDA_ANGLE( angle, DEGREES_T ) );
1658
1659 if( flipped )
1660 {
1661 int x = bitmap->GetPosition().x;
1662 MIRROR( x, KiROUND( kstart.x ) );
1663 bitmap->SetX( x );
1664
1666 }
1667
1668 aBoard->Add( bitmap.release(), ADD_MODE::APPEND );
1669 }
1670 }
1671 }
1672 else if( type == wxS( "STRING" ) )
1673 {
1674 wxString uuid = line.at( 1 );
1675
1676 // if( line.at( 2 ).is_number() )
1677 // int unk = line.at( 2 ).get<int>();
1678 // else if( line.at( 2 ).is_string() )
1679 // int unk = wxAtoi( line.at( 2 ).get<wxString>() );
1680
1681 int layer = line.at( 3 ).get<int>();
1682 PCB_LAYER_ID klayer = LayerToKi( layer );
1683
1684 VECTOR2D location( line.at( 4 ), line.at( 5 ) );
1685 wxString string = line.at( 6 );
1686 wxString font = line.at( 7 );
1687
1688 double height = line.at( 8 );
1689 double strokew = line.at( 9 );
1690
1691 int align = line.at( 12 );
1692 double angle = line.at( 13 );
1693 int inverted = line.at( 14 );
1694 int mirror = line.at( 16 );
1695
1696 PCB_TEXT* text = new PCB_TEXT( aBoard );
1697
1698 text->SetText( string );
1699 text->SetLayer( klayer );
1700 text->SetPosition( ScalePos( location ) );
1701 text->SetIsKnockout( inverted );
1702 text->SetTextThickness( ScaleSize( strokew ) );
1703 text->SetTextSize( VECTOR2D( ScaleSize( height * 0.6 ), ScaleSize( height * 0.7 ) ) );
1704
1705 if( font != wxS( "default" ) )
1706 {
1707 text->SetFont( KIFONT::FONT::GetFont( font ) );
1708 //text->SetupRenderCache( text->GetShownText(), text->GetFont(), EDA_ANGLE( angle, DEGREES_T ) );
1709
1710 //text->AddRenderCacheGlyph();
1711 // TODO: import geometry cache
1712 }
1713
1714 AlignText( text, align );
1715
1716 if( IsBackLayer( klayer ) ^ !!mirror )
1717 {
1718 text->SetMirrored( true );
1719 text->SetTextAngleDegrees( -angle );
1720 }
1721 else
1722 {
1723 text->SetTextAngleDegrees( angle );
1724 }
1725
1726 aBoard->Add( text, ADD_MODE::APPEND );
1727 }
1728 else if( type == wxS( "COMPONENT" ) )
1729 {
1730 wxString compId = line.at( 1 );
1731 componentLines[compId].push_back( line );
1732 }
1733 else if( type == wxS( "ATTR" ) )
1734 {
1735 wxString compId = line.at( 3 );
1736 componentLines[compId].push_back( line );
1737 }
1738 else if( type == wxS( "PAD_NET" ) )
1739 {
1740 wxString compId = line.at( 1 );
1741 componentLines[compId].push_back( line );
1742 }
1743 }
1744
1745 for( auto const& [compId, lines] : componentLines )
1746 {
1747 wxString deviceId;
1748 wxString fpIdOverride;
1749 wxString fpDesignator;
1750 std::map<wxString, wxString> localCompAttribs;
1751
1752 for( auto& line : lines )
1753 {
1754 if( line.size() == 0 )
1755 continue;
1756
1757 wxString type = line.at( 0 );
1758
1759 if( type == wxS( "COMPONENT" ) )
1760 {
1761 localCompAttribs = line.at( 7 );
1762 }
1763 else if( type == wxS( "ATTR" ) )
1764 {
1765 EASYEDAPRO::PCB_ATTR attr = line;
1766
1767 if( attr.key == wxS( "Device" ) )
1768 deviceId = attr.value;
1769
1770 else if( attr.key == wxS( "Footprint" ) )
1771 fpIdOverride = attr.value;
1772
1773 else if( attr.key == wxS( "Designator" ) )
1774 fpDesignator = attr.value;
1775 }
1776 }
1777
1778 if( deviceId.empty() )
1779 continue;
1780
1781 nlohmann::json compAttrs = aProject.at( "devices" ).at( deviceId ).at( "attributes" );
1782
1783 wxString fpId;
1784
1785 if( !fpIdOverride.IsEmpty() )
1786 fpId = fpIdOverride;
1787 else
1788 fpId = compAttrs.at( "Footprint" ).get<wxString>();
1789
1790 auto it = aFootprintMap.find( fpId );
1791 if( it == aFootprintMap.end() )
1792 {
1793 wxLogTrace( traceEasyEdaIo, "Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
1794 continue;
1795 }
1796
1797 std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
1798 std::unique_ptr<FOOTPRINT> footprint( static_cast<FOOTPRINT*>( footprintOrig->Duplicate( false ) ) );
1799
1800 wxString modelUuid, modelTitle, modelTransform;
1801
1802 if( auto val = get_opt( localCompAttribs, "3D Model" ) )
1803 modelUuid = *val;
1804 else
1805 modelUuid = compAttrs.value<wxString>( "3D Model", "" );
1806
1807 if( auto val = get_opt( localCompAttribs, "3D Model Title" ) )
1808 modelTitle = val->Trim();
1809 else
1810 modelTitle = compAttrs.value<wxString>( "3D Model Title", modelUuid ).Trim();
1811
1812 if( auto val = get_opt( localCompAttribs, "3D Model Transform" ) )
1813 modelTransform = *val;
1814 else
1815 modelTransform = compAttrs.value<wxString>( "3D Model Transform", "" );
1816
1817 FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
1818
1819 footprint->SetParent( aBoard );
1820
1821 for( auto& line : lines )
1822 {
1823 if( line.size() == 0 )
1824 continue;
1825
1826 wxString type = line.at( 0 );
1827
1828 if( type == wxS( "COMPONENT" ) )
1829 {
1830 int layer = line.at( 3 );
1831 PCB_LAYER_ID klayer = LayerToKi( layer );
1832
1833 VECTOR2D center( line.at( 4 ), line.at( 5 ) );
1834
1835 double orient = line.at( 6 );
1836 //std::map<wxString, wxString> props = line.at( 7 );
1837
1838 if( klayer == B_Cu )
1839 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
1840
1841 footprint->SetOrientationDegrees( orient );
1842 footprint->SetPosition( ScalePos( center ) );
1843 }
1844 else if( type == wxS( "ATTR" ) )
1845 {
1846 EASYEDAPRO::PCB_ATTR attr = line;
1847
1848 PCB_LAYER_ID klayer = LayerToKi( attr.layer );
1849
1850 PCB_FIELD* field = nullptr;
1851
1852 if( attr.key == wxS( "Designator" ) )
1853 {
1854 if( attr.key == wxS( "Designator" ) )
1855 {
1856 field = footprint->GetField( FIELD_T::REFERENCE );
1857 }
1858 else
1859 {
1860 field = new PCB_FIELD( footprint.get(), FIELD_T::USER, attr.key );
1861 footprint->Add( field, ADD_MODE::APPEND );
1862 }
1863
1864 if( attr.fontName != wxS( "default" ) )
1865 field->SetFont( KIFONT::FONT::GetFont( attr.fontName ) );
1866
1867 if( attr.valVisible && attr.keyVisible )
1868 {
1869 field->SetText( attr.key + ':' + attr.value );
1870 }
1871 else if( attr.keyVisible )
1872 {
1873 field->SetText( attr.key );
1874 }
1875 else
1876 {
1877 field->SetText( attr.value );
1878 }
1879
1880 field->SetVisible( attr.keyVisible || attr.valVisible );
1881 field->SetLayer( klayer );
1882 field->SetPosition( ScalePos( attr.position ) );
1883 field->SetTextAngleDegrees( footprint->IsFlipped() ? -attr.rotation : attr.rotation );
1884 field->SetIsKnockout( attr.inverted );
1885 field->SetTextThickness( ScaleSize( attr.strokeWidth ) );
1886 field->SetTextSize( VECTOR2D( ScaleSize( attr.height * 0.55 ), ScaleSize( attr.height * 0.6 ) ) );
1887
1888 AlignText( field, attr.textOrigin );
1889 }
1890 }
1891 else if( type == wxS( "PAD_NET" ) )
1892 {
1893 wxString padNumber = line.at( 2 );
1894 wxString padNet = line.at( 3 );
1895
1896 for( PAD* pad : footprint->Pads() )
1897 {
1898 if( pad->GetNumber() == padNumber )
1899 pad->SetNet( getNet( aBoard, padNet ) );
1900 }
1901 }
1902 }
1903
1904 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1905 }
1906
1907 // Set zone fills
1909 {
1910 for( auto& [uuid, zone] : poursToFill )
1911 {
1912 SHAPE_POLY_SET fillPolySet;
1913 SHAPE_POLY_SET thermalSpokes;
1914
1915 auto range = boardPouredMap.equal_range( uuid );
1916 for( auto& it = range.first; it != range.second; ++it )
1917 {
1918 const EASYEDAPRO::POURED& poured = it->second;
1919
1920 SHAPE_POLY_SET thisPoly;
1921
1922 for( int dataId = 0; dataId < (int) poured.polyData.size(); dataId++ )
1923 {
1924 const nlohmann::json& fillData = poured.polyData[dataId];
1925 const double ptScale = 10;
1926
1927 SHAPE_LINE_CHAIN contour = ParseContour( fillData, false, ARC_HIGH_DEF / ptScale );
1928
1929 // Scale the fill
1930 for( int i = 0; i < contour.PointCount(); i++ )
1931 contour.SetPoint( i, contour.GetPoint( i ) * ptScale );
1932
1933 if( poured.isPoly )
1934 {
1935 contour.SetClosed( true );
1936
1937 // The contour can be self-intersecting
1938 SHAPE_POLY_SET simple( contour );
1939 simple.Simplify();
1940
1941 if( dataId == 0 )
1942 {
1943 thisPoly.Append( simple );
1944 }
1945 else
1946 {
1947 thisPoly.BooleanSubtract( simple );
1948 }
1949 }
1950 else
1951 {
1952 const int thermalWidth = pcbIUScale.mmToIU( 0.2 ); // Generic
1953
1954 for( int segId = 0; segId < contour.SegmentCount(); segId++ )
1955 {
1956 const SEG& seg = contour.CSegment( segId );
1957
1958 TransformOvalToPolygon( thermalSpokes, seg.A, seg.B, thermalWidth, ARC_HIGH_DEF,
1959 ERROR_INSIDE );
1960 }
1961 }
1962 }
1963
1964 fillPolySet.Append( thisPoly );
1965 }
1966
1967 if( !fillPolySet.IsEmpty() )
1968 {
1969 fillPolySet.Simplify();
1970
1971 const int strokeWidth = pcbIUScale.MilsToIU( 8 ); // Seems to be 8 mils
1972
1973 fillPolySet.Inflate( strokeWidth / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF, false );
1974
1975 fillPolySet.BooleanAdd( thermalSpokes );
1976
1977 fillPolySet.Fracture();
1978
1979 zone->SetFilledPolysList( zone->GetFirstLayer(), fillPolySet );
1980 zone->SetNeedRefill( false );
1981 zone->SetIsFilled( true );
1982 }
1983 }
1984 }
1985
1986 // Heal board outlines
1987 std::vector<PCB_SHAPE*> shapes;
1988
1989 for( BOARD_ITEM* item : aBoard->Drawings() )
1990 {
1991 if( !item->IsOnLayer( Edge_Cuts ) )
1992 continue;
1993
1994 if( item->Type() == PCB_SHAPE_T )
1995 shapes.push_back( static_cast<PCB_SHAPE*>( item ) );
1996 }
1997
1999
2000 // Center the board
2001 BOX2I outlineBbox = aBoard->ComputeBoundingBox( true, true );
2002 PAGE_INFO pageInfo = aBoard->GetPageSettings();
2003
2004 VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ),
2005 pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) );
2006
2007 VECTOR2D offset = pageCenter - outlineBbox.GetCenter();
2008
2009
2010 int alignGrid = pcbIUScale.mmToIU( 10 );
2011 offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
2012 offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
2013
2014 aBoard->Move( offset );
2015 bds.SetAuxOrigin( offset );
2016}
@ 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:415
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
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:1524
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:3001
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition board.cpp:961
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition board.cpp:827
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition board.h:568
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2742
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:1205
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