KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_easyedapro_v3_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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
26
27#include <memory>
28
29
30#include <json_common.h>
32#include <core/map_helpers.h>
33#include <string_utils.h>
34
35#include <wx/log.h>
36#include <wx/base64.h>
37#include <wx/mstream.h>
38
39#include <footprint.h>
40#include <board.h>
41#include <pcb_group.h>
42#include <pcb_shape.h>
43#include <pcb_text.h>
44#include <pcb_track.h>
45#include <pcb_reference_image.h>
46#include <geometry/shape_arc.h>
47#include <geometry/shape_rect.h>
48#include <zone.h>
49#include <pad.h>
50#include <fix_board_shape.h>
53#include <netclass.h>
55
56#include <font/font.h>
57#include <core/mirror.h>
59#include <trace_helpers.h>
60
61using namespace EASYEDAPRO;
62
63static const wxString safeSpacingNames[] = { wxS( "Track" ),
64 wxS( "SMD Pad" ),
65 wxS( "TH Pad" ),
66 wxS( "SMD Test Point" ),
67 wxS( "TH Test Point" ),
68 wxS( "Via" ),
69 wxS( "Fill Region / Teardrop" ),
70 wxS( "Copper / Plane Zone" ),
71 wxS( "Slot Region" ),
72 wxS( "Line" ),
73 wxS( "Text / Image" ),
74 wxS( "Board Outline" ),
75 wxS( "Hole" ) };
76
77// Empty entries are source categories that the current item conversion flattens into another
78// KiCad type. Their values are retained as comments in the generated .kicad_dru file.
79static const wxString safeSpacingConditions[] = { wxS( "A.Type == 'Track'" ),
80 wxS( "A.Type == 'Pad' && A.Pad_Type == 'SMD'" ),
81 wxS( "A.Type == 'Pad' && A.Pad_Type == 'Through-hole'" ),
82 wxEmptyString,
83 wxEmptyString,
84 wxS( "A.Type == 'Via'" ),
85 wxEmptyString,
86 wxEmptyString,
87 wxEmptyString,
88 wxEmptyString,
89 wxEmptyString,
90 wxEmptyString,
91 wxEmptyString };
92
93
94static wxString safeSpacingToMm( double aMil )
95{
96 return wxString::FromUTF8( FormatDouble2Str( pcbIUScale.IUTomm( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( aMil ) ) ) );
97}
98
99
104
105
106wxString PCB_IO_EASYEDAPRO_V3_PARSER::GenerateSafeSpacingRules( const nlohmann::json& aSafeSpacing )
107{
108 if( !aSafeSpacing.is_array() || aSafeSpacing.empty() )
109 return wxEmptyString;
110
111 wxString rules = wxS( "(version 1)\n\n"
112 "# EasyEDA Pro safeSpacing values. Values are mils even when the\n"
113 "# source rule context reports unit \"mm\". Entries without a\n"
114 "# generated rule are source categories that KiCad's imported item\n"
115 "# model cannot distinguish; they remain here for lossless reference.\n" );
116
117 for( size_t row = 0; row < sizeof( safeSpacingNames ) / sizeof( safeSpacingNames[0] ) && row < aSafeSpacing.size();
118 ++row )
119 {
120 const nlohmann::json& values = aSafeSpacing.at( row );
121
122 if( !values.is_array() )
123 continue;
124
125 for( size_t col = 0; col <= row && col < values.size(); ++col )
126 {
127 if( !values.at( col ).is_number() )
128 continue;
129
130 double value = values.at( col );
131 wxString mil = wxString::FromUTF8( FormatDouble2Str( value ) );
132 wxString mm = safeSpacingToMm( value );
133
134 rules += wxString::Format( wxS( "# %s ↔ %s: %s mil (%s mm)\n" ), safeSpacingNames[row],
135 safeSpacingNames[col], mil, mm );
136
137 if( value <= 0 || safeSpacingConditions[row].empty() || safeSpacingConditions[col].empty()
138 || ( row == 0 && col == 0 ) )
139 {
140 continue;
141 }
142 wxString conditionA = safeSpacingConditions[row];
143 wxString conditionB = safeSpacingConditions[col];
144 conditionB.Replace( wxS( "A." ), wxS( "B." ) );
145
146 rules += wxString::Format( wxS( "\n(rule \"EasyEDA safe spacing: %s ↔ %s\"\n"
147 " (condition \"(%s) && (%s)\")\n"
148 " (constraint clearance (min %smm)))\n" ),
149 safeSpacingNames[row], safeSpacingNames[col], conditionA, conditionB, mm );
150 }
151 }
152
153 constexpr size_t boardOutline = 11;
154
155 if( boardOutline < aSafeSpacing.size() && aSafeSpacing.at( boardOutline ).is_array() )
156 {
157 const nlohmann::json& values = aSafeSpacing.at( boardOutline );
158
159 for( size_t index : { 0u, 1u, 2u, 5u } )
160 {
161 if( index >= values.size() || !values.at( index ).is_number() || values.at( index ).get<double>() <= 0 )
162 {
163 continue;
164 }
165
166 wxString mm = safeSpacingToMm( values.at( index ) );
167
168 rules += wxString::Format( wxS( "\n(rule \"EasyEDA safe spacing: %s ↔ Board Outline\"\n"
169 " (condition \"(%s)\")\n"
170 " (constraint edge_clearance (min %smm)))\n" ),
172 }
173 }
174
175 return rules;
176}
177
178
179static const int SHAPE_JOIN_DISTANCE = pcbIUScale.mmToIU( 1.5 );
180
181
183 m_board( aBoard ),
184 m_v2Parser( aBoard, aProgressReporter )
185{
186}
187
188
189static void V3AlignText( EDA_TEXT* text, int align );
190static int V3AlignToOriginCode( const wxString& aAlign );
191
192
193std::unique_ptr<PAD> PCB_IO_EASYEDAPRO_V3_PARSER::createV3PAD( FOOTPRINT* aFootprint, const V3_ROW& aRow )
194{
195 int layer = V3GetInt( aRow.inner, "layerId", 1 );
196 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
197
198 wxString padNumber = V3GetString( aRow.inner, "num" );
199
201 center.x = V3GetDouble( aRow.inner, "centerX" );
202 center.y = V3GetDouble( aRow.inner, "centerY" );
203
204 double orientation = V3GetDouble( aRow.inner, "padAngle" );
205
206 nlohmann::json holeObj = aRow.inner.value( "hole", nlohmann::json() );
207 nlohmann::json padDef = aRow.inner.value( "defaultPad", nlohmann::json() );
208
209 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
210
211 pad->SetNumber( padNumber );
213 pad->SetOrientationDegrees( orientation );
214 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
215
216 // Process hole
217 bool hasHole = false;
218
219 if( holeObj.is_object() )
220 {
221 wxString holeType = V3GetString( holeObj, "holeType", wxS( "ROUND" ) );
222
223 if( holeType == wxS( "RECT" ) )
224 holeType = wxS( "SLOT" );
225
226 if( holeType != wxS( "ROUND" ) && holeType != wxS( "SLOT" ) )
227 holeType = wxS( "ROUND" );
228
229 VECTOR2D drill;
230 drill.x = V3GetDouble( holeObj, "width" );
231 drill.y = V3GetDouble( holeObj, "height" );
232
233 if( drill.x > 0 || drill.y > 0 )
234 {
235 hasHole = true;
236
237 double drill_dir = V3GetDouble( aRow.inner, "relativeAngle" );
238
239 double deg = EDA_ANGLE( drill_dir, DEGREES_T ).Normalize90().AsDegrees();
240
241 if( std::abs( deg ) >= 45 )
242 std::swap( drill.x, drill.y );
243
244 if( holeType == wxS( "SLOT" ) )
245 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
246
247 pad->SetDrillSize( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( drill ) );
248 pad->SetLayerSet( PAD::PTHMask() );
249 pad->SetAttribute( PAD_ATTRIB::PTH );
250 }
251 }
252
253 if( !hasHole )
254 {
255 if( klayer == F_Cu )
256 pad->SetLayerSet( PAD::SMDMask() );
257 else if( klayer == B_Cu )
258 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
259
260 pad->SetAttribute( PAD_ATTRIB::SMD );
261 }
262
263 // Process pad shape
264 if( padDef.is_object() )
265 {
266 wxString padType = V3GetString( padDef, "padType", wxS( "RECT" ) );
267
268 if( padType == wxS( "RECT" ) )
269 {
270 VECTOR2D size;
271 size.x = V3GetDouble( padDef, "width", 1.0 );
272 size.y = V3GetDouble( padDef, "height", 1.0 );
273 double radius = V3GetDouble( padDef, "radius" );
274 double radiusRatio = std::clamp( radius, 0.0, 100.0 ) / 100 / 2;
275
277
278 if( radiusRatio == 0 )
280 else
281 {
283 pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, radiusRatio );
284 }
285 }
286 else if( padType == wxS( "ELLIPSE" ) )
287 {
288 VECTOR2D size;
289 size.x = V3GetDouble( padDef, "width", 1.0 );
290 size.y = V3GetDouble( padDef, "height", 1.0 );
291
293 pad->SetShape( PADSTACK::ALL_LAYERS, size.x == size.y ? PAD_SHAPE::CIRCLE : PAD_SHAPE::OVAL );
294 }
295 else if( padType == wxS( "OVAL" ) )
296 {
297 VECTOR2D size;
298 size.x = V3GetDouble( padDef, "width", 1.0 );
299 size.y = V3GetDouble( padDef, "height", 1.0 );
300
303 }
304 else if( padType == wxS( "POLY" ) || padType == wxS( "POLYGON" ) )
305 {
307 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
308 pad->SetSize( PADSTACK::ALL_LAYERS, { 1, 1 } );
309
310 nlohmann::json polyData = padDef.value( "path", nlohmann::json() );
311
312 std::vector<std::unique_ptr<PCB_SHAPE>> results = m_v2Parser.ParsePoly( aFootprint, polyData, true, false );
313
314 for( auto& shape : results )
315 {
316 shape->SetLayer( klayer );
317 shape->SetWidth( 0 );
318 shape->Move( -pad->GetPosition() );
319 pad->AddPrimitive( PADSTACK::ALL_LAYERS, shape.release() );
320 }
321 }
322 }
323 else
324 {
327 }
328
329 pad->SetThermalSpokeAngle( ANGLE_90 );
330
331 return pad;
332}
333
334
336{
337 int layer = V3GetInt( aRow.inner, "layerId", 3 );
338 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
339
341 location.x = V3GetDouble( aRow.inner, "x" );
342 location.y = V3GetDouble( aRow.inner, "y" );
343
344 wxString string = V3GetString( aRow.inner, "text" );
345 wxString font = V3GetString( aRow.inner, "fontFamily", wxS( "default" ) );
346
347 double height = V3GetDouble( aRow.inner, "fontSize", 45.0 );
348 double strokew = V3GetDouble( aRow.inner, "strokeWidth", 6.0 );
349
350 wxString originStr = V3GetString( aRow.inner, "origin", wxS( "LEFT_BOTTOM" ) );
351 int align = V3AlignToOriginCode( originStr );
352 double angle = V3GetDouble( aRow.inner, "angle" );
353 int inverted = V3GetBool( aRow.inner, "reverse" ) ? 1 : 0;
354 int mirror = V3GetBool( aRow.inner, "mirror" ) ? 1 : 0;
355
356 PCB_TEXT* text = new PCB_TEXT( aContainer );
357
358 text->SetText( string );
359 text->SetLayer( klayer );
361 text->SetIsKnockout( inverted );
362 text->SetTextThickness( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( strokew ) );
363 text->SetTextSize( VECTOR2D( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( height * 0.6 ),
364 PCB_IO_EASYEDAPRO_PARSER::ScaleSize( height * 0.7 ) ) );
365
366 if( font != wxS( "default" ) )
367 text->SetFont( KIFONT::FONT::GetFont( font ) );
368
369 V3AlignText( text, align );
370
371 if( IsBackLayer( klayer ) ^ !!mirror )
372 {
373 text->SetMirrored( true );
374 text->SetTextAngleDegrees( -angle );
375 }
376 else
377 {
378 text->SetTextAngleDegrees( angle );
379 }
380
381 return text;
382}
383
384
385std::unique_ptr<FOOTPRINT>
386PCB_IO_EASYEDAPRO_V3_PARSER::ParseFootprint( const std::map<wxString, EASYEDAPRO::BLOB>& aBlobMap,
387 const V3_DOC_RAW& aDoc )
388{
389 std::unique_ptr<FOOTPRINT> footprintPtr = std::make_unique<FOOTPRINT>( m_board );
390 FOOTPRINT* footprint = footprintPtr.get();
391
392 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
393 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
394 VECTOR2I canvasOrigin( 0, 0 );
395
396 for( PCB_FIELD* field : footprint->GetFields() )
397 {
398 field->SetTextSize( defaultTextSize );
399 field->SetTextThickness( defaultTextThickness );
400 }
401
402 for( const V3_ROW& row : aDoc.rows )
403 {
404 if( row.type == wxS( "CANVAS" ) )
405 {
407 VECTOR2D( V3GetDouble( row.inner, "originX" ), V3GetDouble( row.inner, "originY" ) ) );
408 }
409 else if( row.type == wxS( "POLY" ) )
410 {
411 int layer = V3GetInt( row.inner, "layerId", 1 );
412 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
413 double thickness = V3GetDouble( row.inner, "width" );
414
415 nlohmann::json polyData = row.inner.value( "path", nlohmann::json::array() );
416
417 std::vector<std::unique_ptr<PCB_SHAPE>> results = m_v2Parser.ParsePoly( footprint, polyData, false, false );
418
419 for( auto& shape : results )
420 {
421 shape->SetLayer( klayer );
422 shape->SetWidth( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( thickness ) );
423 footprint->Add( shape.release(), ADD_MODE::APPEND );
424 }
425 }
426 else if( row.type == wxS( "PAD" ) )
427 {
428 std::unique_ptr<PAD> pad = createV3PAD( footprint, row );
429 footprint->Add( pad.release(), ADD_MODE::APPEND );
430 }
431 else if( row.type == wxS( "FILL" ) )
432 {
433 int layer = V3GetInt( row.inner, "layerId", 1 );
434 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
435
436 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
437
438 if( !polyDataList.is_null() && !polyDataList.empty() )
439 {
440 if( !polyDataList.at( 0 ).is_array() )
441 polyDataList = nlohmann::json::array( { polyDataList } );
442
443 std::vector<SHAPE_LINE_CHAIN> contours;
444
445 for( nlohmann::json& polyData : polyDataList )
446 {
447 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
448 contour.SetClosed( true );
449 contours.push_back( contour );
450 }
451
452 SHAPE_POLY_SET polySet;
453
454 for( SHAPE_LINE_CHAIN& contour : contours )
455 polySet.AddOutline( contour );
456
457 polySet.RebuildHolesFromContours();
458
459 std::unique_ptr<PCB_GROUP> group;
460
461 if( polySet.OutlineCount() > 1 )
462 group = std::make_unique<PCB_GROUP>( footprint );
463
464 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
465 {
466 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::POLY );
467
468 shape->SetFilled( true );
469 shape->SetPolyShape( poly );
470 shape->SetLayer( klayer );
471 shape->SetWidth( 0 );
472
473 if( group )
474 group->AddItem( shape.get() );
475
476 footprint->Add( shape.release(), ADD_MODE::APPEND );
477 }
478
479 if( group )
480 footprint->Add( group.release(), ADD_MODE::APPEND );
481 }
482 }
483 else if( row.type == wxS( "ATTR" ) )
484 {
485 wxString key = V3GetString( row.inner, "key" );
486 wxString value = V3JsonToString( row.inner.value( "value", nlohmann::json() ) );
487
488 if( key == wxS( "Designator" ) )
489 footprint->GetField( FIELD_T::REFERENCE )->SetText( value );
490 }
491 else if( row.type == wxS( "REGION" ) )
492 {
493 int layer = V3GetInt( row.inner, "layerId", 1 );
494 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
495
496 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
497 nlohmann::json prohibitTypes = row.inner.value( "prohibitType", nlohmann::json::array() );
498
499 std::set<int> flags;
500
501 if( prohibitTypes.is_array() )
502 {
503 for( const auto& pt : prohibitTypes )
504 {
505 wxString ptStr;
506
507 if( pt.is_string() )
508 ptStr = pt.get<std::string>();
509
510 if( ptStr == wxS( "COMPONENT" ) )
511 flags.insert( 2 );
512 else if( ptStr == wxS( "TRACK" ) )
513 flags.insert( 5 );
514 else if( ptStr == wxS( "COPPER" ) )
515 flags.insert( 6 );
516 else if( ptStr == wxS( "VIA" ) )
517 flags.insert( 7 );
518 else if( ptStr == wxS( "FILL" ) || ptStr == wxS( "PLANE" ) )
519 flags.insert( 8 );
520 }
521 }
522
523 for( nlohmann::json& polyData : polyDataList )
524 {
525 SHAPE_POLY_SET polySet;
526
527 std::vector<std::unique_ptr<PCB_SHAPE>> results =
528 m_v2Parser.ParsePoly( nullptr, polyData, true, false );
529
530 for( auto& shape : results )
531 {
532 shape->SetFilled( true );
533 shape->TransformShapeToPolygon( polySet, klayer, 0, ARC_HIGH_DEF, ERROR_INSIDE, true );
534 }
535
536 polySet.Simplify();
537
538 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( footprint );
539
540 zone->SetIsRuleArea( true );
541 zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
542 zone->SetDoNotAllowZoneFills( !!flags.count( 7 ) || !!flags.count( 6 ) || !!flags.count( 8 ) );
543 zone->SetDoNotAllowPads( !!flags.count( 7 ) );
544 zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
545 zone->SetDoNotAllowVias( !!flags.count( 7 ) );
546
547 zone->SetLayer( klayer );
548 zone->Outline()->Append( polySet );
549
550 footprint->Add( zone.release(), ADD_MODE::APPEND );
551 }
552 }
553 else if( row.type == wxS( "IMAGE" ) )
554 {
555 int layer = V3GetInt( row.inner, "layerId", 1 );
556 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
557
558 VECTOR2D start;
559 start.x = V3GetDouble( row.inner, "startX" );
560 start.y = V3GetDouble( row.inner, "startY" );
561
562 VECTOR2D size;
563 size.x = V3GetDouble( row.inner, "width" );
564 size.y = V3GetDouble( row.inner, "height" );
565
566 double angle = V3GetDouble( row.inner, "angle" );
567 bool mirror = V3GetBool( row.inner, "mirror" );
568 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
569
570 BOX2I bbox;
571 std::vector<SHAPE_LINE_CHAIN> contours;
572 for( nlohmann::json& polyData : polyDataList )
573 {
574 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
575 contour.SetClosed( true );
576
577 contours.push_back( contour );
578
579 bbox.Merge( contour.BBox() );
580 }
581
582 if( bbox.GetSize().x == 0 || bbox.GetSize().y == 0 )
583 continue;
584
587
588 SHAPE_POLY_SET polySet;
589
590 for( SHAPE_LINE_CHAIN& contour : contours )
591 {
592 for( int i = 0; i < contour.PointCount(); i++ )
593 {
594 VECTOR2I pt = contour.CPoint( i );
595 contour.SetPoint( i, VECTOR2I( pt.x * scale.x, pt.y * scale.y ) );
596 }
597
598 polySet.AddOutline( contour );
599 }
600
601 polySet.RebuildHolesFromContours();
602
603 std::unique_ptr<PCB_GROUP> group;
604
605 if( polySet.OutlineCount() > 1 )
606 group = std::make_unique<PCB_GROUP>( footprint );
607
608 BOX2I polyBBox = polySet.BBox();
609
610 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
611 {
612 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::POLY );
613
614 shape->SetFilled( true );
615 shape->SetPolyShape( poly );
616 shape->SetLayer( klayer );
617 shape->SetWidth( 0 );
618
619 shape->Move( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ) - polyBBox.GetOrigin() );
620 shape->Rotate( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
621
622 if( IsBackLayer( klayer ) ^ mirror )
623 {
625 shape->Mirror( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ), flipDirection );
626 }
627
628 if( group )
629 group->AddItem( shape.get() );
630
631 footprint->Add( shape.release(), ADD_MODE::APPEND );
632 }
633
634 if( group )
635 footprint->Add( group.release(), ADD_MODE::APPEND );
636 }
637 else if( row.type == wxS( "OBJ" ) )
638 {
639 int layer = V3GetInt( row.inner, "layerId", 1 );
640 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
641
642 VECTOR2D start;
643 start.x = V3GetDouble( row.inner, "startX" );
644 start.y = V3GetDouble( row.inner, "startY" );
645
646 VECTOR2D size;
647 size.x = V3GetDouble( row.inner, "width" );
648 size.y = V3GetDouble( row.inner, "height" );
649
650 double angle = V3GetDouble( row.inner, "angle" );
651 bool mirror = V3GetBool( row.inner, "mirror" );
652 wxString imageUrl = V3GetString( row.inner, "path" );
653
654 if( imageUrl.empty() )
655 continue;
656
657 wxString mimeType, base64Data;
658
659 if( imageUrl.BeforeFirst( ':' ) == wxS( "blob" ) )
660 {
661 wxString objectId = imageUrl.AfterLast( ':' );
662
663 if( auto blob = get_opt( aBlobMap, objectId ) )
664 {
665 wxString blobUrl = blob->url;
666
667 if( blobUrl.BeforeFirst( ':' ) == wxS( "data" ) )
668 {
669 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
670
671 base64Data = blobUrl.AfterFirst( ',' );
672
673 if( paramsArr.size() > 0 )
674 mimeType = paramsArr[0];
675 }
676 }
677 }
678
681
682 if( mimeType.empty() || base64Data.empty() )
683 continue;
684
685 wxMemoryBuffer buf = wxBase64Decode( base64Data );
686
687 if( mimeType == wxS( "image/svg+xml" ) )
688 {
689 // Not yet supported by EasyEDA
690 }
691 else
692 {
693 VECTOR2D kcenter = kstart + ksize / 2;
694
695 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
696 std::make_unique<PCB_REFERENCE_IMAGE>( footprint, kcenter, klayer );
697 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
698
699 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
700
701 if( refImage.ReadImageFile( buf ) )
702 {
703 double scaleFactor = PCB_IO_EASYEDAPRO_PARSER::ScaleSize( size.x ) / refImage.GetSize().x;
704 refImage.SetImageScale( scaleFactor );
705
706 // TODO: support non-90-deg angles
707 bitmap->Rotate( kstart, EDA_ANGLE( angle, DEGREES_T ) );
708
709 if( mirror )
710 {
711 int x = bitmap->GetPosition().x;
712 MIRROR( x, KiROUND( kstart.x ) );
713 bitmap->SetX( x );
714
716 }
717
718 footprint->Add( bitmap.release(), ADD_MODE::APPEND );
719 }
720 }
721 }
722 else if( row.type == wxS( "STRING" ) )
723 {
724 footprint->Add( createV3Text( footprint, row ), ADD_MODE::APPEND );
725 }
726 }
727
728 // 3D models are applied by the caller from the component's Device ATTR (board import)
729 // or a matching library device (FootprintLoad). Do not pick an arbitrary device here:
730 // footprints are often shared by devices with different models.
731
732 // Heal board outlines
733 std::vector<PCB_SHAPE*> edgeShapes;
734
735 for( BOARD_ITEM* item : footprint->GraphicalItems() )
736 {
737 if( item->IsOnLayer( Edge_Cuts ) && item->Type() == PCB_SHAPE_T )
738 edgeShapes.push_back( static_cast<PCB_SHAPE*>( item ) );
739 }
740
742
743 // Build courtyard if missing
744 if( !footprint->IsOnLayer( F_CrtYd ) )
745 {
746 BOX2I bbox = footprint->GetLayerBoundingBox( { F_Cu, F_Fab, F_Paste, F_Mask, Edge_Cuts } );
747 bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) );
748
749 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint, SHAPE_T::RECTANGLE );
750
751 shape->SetWidth( pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ) );
752 shape->SetLayer( F_CrtYd );
753 shape->SetStart( bbox.GetOrigin() );
754 shape->SetEnd( bbox.GetEnd() );
755
756 footprint->Add( shape.release(), ADD_MODE::APPEND );
757 }
758
759 // Add F_Fab reference text if missing
760 bool hasFabRef = false;
761
762 for( BOARD_ITEM* item : footprint->GraphicalItems() )
763 {
764 if( item->Type() == PCB_TEXT_T && item->IsOnLayer( F_Fab ) )
765 {
766 if( static_cast<PCB_TEXT*>( item )->GetText() == wxT( "${REFERENCE}" ) )
767 {
768 hasFabRef = true;
769 break;
770 }
771 }
772 }
773
774 if( !hasFabRef )
775 {
776 int c_refTextSize = pcbIUScale.mmToIU( 0.5 );
777 int c_refTextThickness = pcbIUScale.mmToIU( 0.1 );
778 std::unique_ptr<PCB_TEXT> refText = std::make_unique<PCB_TEXT>( footprint );
779
780 refText->SetLayer( F_Fab );
781 refText->SetTextSize( VECTOR2I( c_refTextSize, c_refTextSize ) );
782 refText->SetTextThickness( c_refTextThickness );
783 refText->SetText( wxT( "${REFERENCE}" ) );
784
785 footprint->Add( refText.release(), ADD_MODE::APPEND );
786 }
787
788 footprint->MoveAnchorPosition( -canvasOrigin );
789
790 return footprintPtr;
791}
792
793
794static void V3AlignText( EDA_TEXT* text, int align )
795{
796 switch( align )
797 {
798 case 1:
799 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
800 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
801 break;
802 case 2:
803 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
804 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
805 break;
806 case 3:
807 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
808 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
809 break;
810 case 4:
811 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
812 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
813 break;
814 case 5:
815 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
816 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
817 break;
818 case 6:
819 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
820 text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
821 break;
822 case 7:
823 text->SetVertJustify( GR_TEXT_V_ALIGN_TOP );
824 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
825 break;
826 case 8:
827 text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
828 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
829 break;
830 case 9:
831 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
832 text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
833 break;
834 }
835}
836
837
838static int V3AlignToOriginCode( const wxString& aAlign )
839{
840 if( aAlign == wxS( "LEFT_TOP" ) )
841 return 1;
842 if( aAlign == wxS( "LEFT_CENTER" ) || aAlign == wxS( "LEFT_MIDDLE" ) )
843 return 2;
844 if( aAlign == wxS( "LEFT_BOTTOM" ) )
845 return 3;
846 if( aAlign == wxS( "CENTER_TOP" ) )
847 return 4;
848 if( aAlign == wxS( "CENTER_MIDDLE" ) || aAlign == wxS( "CENTER_CENTER" ) )
849 return 5;
850 if( aAlign == wxS( "CENTER_BOTTOM" ) )
851 return 6;
852 if( aAlign == wxS( "RIGHT_TOP" ) )
853 return 7;
854 if( aAlign == wxS( "RIGHT_CENTER" ) || aAlign == wxS( "RIGHT_MIDDLE" ) )
855 return 8;
856 if( aAlign == wxS( "RIGHT_BOTTOM" ) )
857 return 9;
858
859 return 3; // default: LEFT_BOTTOM
860}
861
862
863void PCB_IO_EASYEDAPRO_V3_PARSER::ParseBoard( BOARD* aBoard, const nlohmann::json& aProject,
864 std::map<wxString, std::unique_ptr<FOOTPRINT>>& aFootprintMap,
865 const std::map<wxString, EASYEDAPRO::BLOB>& aBlobMap,
866 const std::multimap<wxString, EASYEDAPRO::POURED>& aPouredMap,
867 const V3_DOC_RAW& aDoc, const wxString& aFpLibName )
868{
869 // Structures to collect component-related rows for second-pass placement
870 struct COMP_DATA
871 {
872 int layer = 1;
873 VECTOR2D pos;
874 double angle = 0;
875 std::map<wxString, wxString> attrs;
876 };
877
878 struct ATTR_DATA
879 {
880 wxString parentId;
881 int layer = 3;
882 double x = 0;
883 double y = 0;
884 bool hasPos = false;
885 wxString key;
886 wxString value;
887 bool keyVisible = false;
888 bool valVisible = false;
889 wxString fontName;
890 double fontSize = 45.0;
891 double strokeWidth = 6.0;
892 wxString origin;
893 double angle = 0;
894 int inverted = 0;
895 int mirror = 0;
896 };
897
898 struct PAD_NET_DATA
899 {
900 wxString compId;
901 wxString padNumber;
902 wxString padNet;
903 };
904
905 std::map<wxString, COMP_DATA> componentMap;
906 std::multimap<wxString, ATTR_DATA> attrMap;
907 std::multimap<wxString, PAD_NET_DATA> padNetMap;
908
909 std::multimap<wxString, EASYEDAPRO::POURED> boardPouredMap = aPouredMap;
910 std::map<wxString, ZONE*> poursToFill;
912
913 m_safeSpacing = nlohmann::json();
914
915 for( const V3_ROW& row : aDoc.rows )
916 {
917 if( row.type != wxS( "RULE" ) || V3GetString( row.inner, "ruleState" ) != wxS( "DEFAULT" ) )
918 continue;
919
920 nlohmann::json idArr = V3ParseIdArray( row.id );
921
922 if( !idArr.is_array() || idArr.size() < 3 || V3JsonToString( idArr[1] ) != wxS( "SAFE" ) )
923 continue;
924
925 nlohmann::json safeSpacing =
926 row.inner.value( "ruleContext", nlohmann::json() ).value( "safeSpacing", nlohmann::json::array() );
927
928 if( safeSpacing.is_array() && !safeSpacing.empty() )
929 {
931 safeSpacing[0].value( "content", nlohmann::json::array() ) );
932 break;
933 }
934 }
935
936 auto zoneClearance =
937 [&]() -> int
938 {
939 if( m_safeSpacing.is_array() && m_safeSpacing.size() > 7 && m_safeSpacing.at( 7 ).is_array()
940 && !m_safeSpacing.at( 7 ).empty() && m_safeSpacing.at( 7 ).at( 0 ).is_number() )
941 {
942 const int clearance = PCB_IO_EASYEDAPRO_PARSER::ScaleSize( m_safeSpacing.at( 7 ).at( 0 ).get<double>() );
943
944 if( clearance > 0 )
945 return clearance;
946 }
947
948 return bds.m_MinClearance;
949 };
950
951 const int zoneMinThickness = bds.GetDefaultZoneSettings().m_ZoneMinThickness;
952
953
954 for( const V3_ROW& row : aDoc.rows )
955 {
956 if( row.type == wxS( "LAYER" ) )
957 {
958 int layer = V3GetInt( row.inner, "layerId", -1 );
959
960 if( layer < 0 )
961 {
962 nlohmann::json id = V3ParseIdArray( row.id );
963
964 if( id.is_array() && id.size() > 1 )
965 {
966 wxString layerId = V3JsonToString( id.at( 1 ) );
967 long parsedLayer;
968
969 if( layerId.ToLong( &parsedLayer ) )
970 layer = parsedLayer;
971 }
972 }
973
974 if( layer < 0 )
975 continue;
976
977 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
978
979 bool use = V3GetBool( row.inner, "use", true );
980 wxString layerName = V3GetString( row.inner, "layerName" );
981
982 if( use )
983 {
984 LSET blayers = aBoard->GetEnabledLayers();
985 blayers.set( klayer );
986 aBoard->SetEnabledLayers( blayers );
987
988 if( !layerName.IsEmpty() )
989 aBoard->SetLayerName( klayer, layerName );
990 }
991 }
992 else if( row.type == wxS( "NET" ) )
993 {
994 nlohmann::json idArr = V3ParseIdArray( row.id );
995 wxString netname;
996
997 if( idArr.is_array() && idArr.size() > 1 )
998 netname = V3JsonToString( idArr[1] );
999
1000 if( !netname.IsEmpty() )
1001 {
1002 aBoard->Add( new NETINFO_ITEM( aBoard, netname, aBoard->GetNetCount() + 1 ), ADD_MODE::APPEND );
1003 }
1004 }
1005 else if( row.type == wxS( "RULE" ) )
1006 {
1007 nlohmann::json idArr = V3ParseIdArray( row.id );
1008
1009 if( !idArr.is_array() || idArr.size() < 3 )
1010 continue;
1011
1012 wxString ruleType = V3JsonToString( idArr[1] );
1013 bool isDefault = V3GetString( row.inner, "ruleState" ) == wxS( "DEFAULT" );
1014
1015 nlohmann::json context = row.inner.value( "ruleContext", nlohmann::json() );
1016
1017 if( ruleType == wxS( "TRACK" ) && isDefault )
1018 {
1019 nlohmann::json track = context.value( "track", nlohmann::json() );
1020 nlohmann::json content = track.value( "content", nlohmann::json::array() );
1021
1022 if( content.is_array() && !content.empty() )
1023 {
1024 double minVal = V3GetDouble( content[0], "stroMin" );
1026 aBoard->m_LegacyDesignSettingsLoaded = true;
1027 }
1028 }
1029 else if( ruleType == wxS( "SAFE" ) && isDefault )
1030 {
1031 nlohmann::json safeSpacing = context.value( "safeSpacing", nlohmann::json::array() );
1032
1033 if( safeSpacing.is_array() && !safeSpacing.empty() )
1034 {
1035 nlohmann::json content = PCB_IO_EASYEDAPRO_PARSER::NormalizeSafeSpacing(
1036 safeSpacing[0].value( "content", nlohmann::json::array() ) );
1037
1038 if( m_safeSpacing.is_null() )
1039 m_safeSpacing = content;
1040
1041 // EasyEDA Pro labels this array "mm", but its values are always mils. The
1042 // lower-triangular matrix starts with the track-to-track clearance, which maps
1043 // to KiCad's global minimum clearance; the remaining object-specific values
1044 // cannot be represented by that single board setting.
1045 if( content.is_array() && !content.empty() && content.at( 0 ).is_array() && !content.at( 0 ).empty()
1046 && content.at( 0 ).at( 0 ).is_number() )
1047 {
1048 double trackClearance = content.at( 0 ).at( 0 );
1049
1050 if( trackClearance > 0 )
1051 {
1052 int clearance = PCB_IO_EASYEDAPRO_PARSER::ScaleSize( trackClearance );
1055 aBoard->m_LegacyDesignSettingsLoaded = true;
1056 aBoard->m_LegacyNetclassesLoaded = true;
1057 }
1058 }
1059 double minEdgeClearance = 0;
1060
1061 if( content.size() > 11 && content.at( 11 ).is_array() )
1062 {
1063 for( const nlohmann::json& value : content.at( 11 ) )
1064 {
1065 if( value.is_number() && value.get<double>() > 0
1066 && ( minEdgeClearance == 0 || value.get<double>() < minEdgeClearance ) )
1067 {
1068 minEdgeClearance = value;
1069 }
1070 }
1071 }
1072
1073 if( minEdgeClearance > 0 )
1074 {
1076 aBoard->m_LegacyDesignSettingsLoaded = true;
1077 }
1078 }
1079 }
1080 }
1081 else if( row.type == wxS( "VIA" ) )
1082 {
1084 center.x = V3GetDouble( row.inner, "centerX" );
1085 center.y = V3GetDouble( row.inner, "centerY" );
1086
1087 double drill = V3GetDouble( row.inner, "holeDiameter" );
1088 double dia = V3GetDouble( row.inner, "viaDiameter" );
1089 wxString netname = V3GetString( row.inner, "netName" );
1090
1091 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( aBoard );
1092
1093 via->SetPadstackMode( PADSTACK::MODE::NORMAL );
1095 via->SetDrill( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( drill ) );
1097 via->SetNet( aBoard->FindNet( netname ) );
1098
1099 aBoard->Add( via.release(), ADD_MODE::APPEND );
1100 }
1101 else if( row.type == wxS( "LINE" ) )
1102 {
1103 int layer = V3GetInt( row.inner, "layerId", 1 );
1104 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1105
1106 VECTOR2D start;
1107 start.x = V3GetDouble( row.inner, "startX" );
1108 start.y = V3GetDouble( row.inner, "startY" );
1109
1110 VECTOR2D end;
1111 end.x = V3GetDouble( row.inner, "endX" );
1112 end.y = V3GetDouble( row.inner, "endY" );
1113
1114 double width = V3GetDouble( row.inner, "width" );
1115 wxString netname = V3GetString( row.inner, "netName" );
1116
1117 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
1118
1119 track->SetLayer( klayer );
1120 track->SetStart( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ) );
1121 track->SetEnd( PCB_IO_EASYEDAPRO_PARSER::ScalePos( end ) );
1122 track->SetWidth( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( width ) );
1123 track->SetNet( aBoard->FindNet( netname ) );
1124
1125 aBoard->Add( track.release(), ADD_MODE::APPEND );
1126 }
1127 else if( row.type == wxS( "ARC" ) )
1128 {
1129 int layer = V3GetInt( row.inner, "layerId", 1 );
1130 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1131
1132 VECTOR2D start;
1133 start.x = V3GetDouble( row.inner, "startX" );
1134 start.y = V3GetDouble( row.inner, "startY" );
1135
1136 VECTOR2D end;
1137 end.x = V3GetDouble( row.inner, "endX" );
1138 end.y = V3GetDouble( row.inner, "endY" );
1139
1140 double angle = V3GetDouble( row.inner, "angle" );
1141 double width = V3GetDouble( row.inner, "width" );
1142 wxString netname = V3GetString( row.inner, "netName" );
1143
1144 VECTOR2D delta = end - start;
1145 VECTOR2D mid = ( start + delta / 2 );
1146
1147 double ha = angle / 2;
1148 double hd = delta.EuclideanNorm() / 2;
1149 double cdist = hd / tan( DEG2RAD( ha ) );
1150 VECTOR2D center = mid + delta.Perpendicular().Resize( cdist );
1151
1152 SHAPE_ARC sarc;
1155 PCB_IO_EASYEDAPRO_PARSER::ScalePos( center ), angle >= 0, width );
1156
1157 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
1158 arc->SetWidth( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( width ) );
1159 arc->SetLayer( klayer );
1160 arc->SetNet( aBoard->FindNet( netname ) );
1161
1162 aBoard->Add( arc.release(), ADD_MODE::APPEND );
1163 }
1164 else if( row.type == wxS( "POLY" ) )
1165 {
1166 int layer = V3GetInt( row.inner, "layerId", 1 );
1167 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1168
1169 double thickness = V3GetDouble( row.inner, "width" );
1170 nlohmann::json polyData = row.inner.value( "path", nlohmann::json::array() );
1171
1172 std::vector<std::unique_ptr<PCB_SHAPE>> results = m_v2Parser.ParsePoly( aBoard, polyData, false, false );
1173
1174 for( auto& shape : results )
1175 {
1176 shape->SetLayer( klayer );
1177 shape->SetWidth( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( thickness ) );
1178
1179 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1180 }
1181 }
1182 else if( row.type == wxS( "FILL" ) )
1183 {
1184 int layer = V3GetInt( row.inner, "layerId", 1 );
1185 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1186
1187 wxString netname = V3GetString( row.inner, "netName" );
1188
1189 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
1190
1191 if( polyDataList.is_null() || polyDataList.empty() )
1192 continue;
1193
1194 if( !polyDataList.at( 0 ).is_array() )
1195 polyDataList = nlohmann::json::array( { polyDataList } );
1196
1197 std::vector<SHAPE_LINE_CHAIN> contours;
1198
1199 for( nlohmann::json& polyData : polyDataList )
1200 {
1201 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, true );
1202 contour.SetClosed( true );
1203 contours.push_back( contour );
1204 }
1205
1206 SHAPE_POLY_SET zoneFillPoly;
1207
1208 for( SHAPE_LINE_CHAIN& contour : contours )
1209 zoneFillPoly.AddOutline( contour );
1210
1211 zoneFillPoly.RebuildHolesFromContours();
1212 zoneFillPoly.Fracture();
1213
1214 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1215
1216 zone->SetNet( aBoard->FindNet( netname ) );
1217 zone->SetLayer( klayer );
1218 zone->Outline()->Append( SHAPE_RECT( zoneFillPoly.BBox() ).Outline() );
1219 zone->SetFilledPolysList( klayer, zoneFillPoly );
1220 zone->SetAssignedPriority( 500 );
1221 zone->SetIsFilled( true );
1222 zone->SetNeedRefill( false );
1223
1224 zone->SetLocalClearance( zoneClearance() );
1225 zone->SetMinThickness( zoneMinThickness );
1226
1227 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1228 }
1229 else if( row.type == wxS( "POUR" ) )
1230 {
1231 int layer = V3GetInt( row.inner, "layerId", 1 );
1232 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1233
1234 wxString netname = V3GetString( row.inner, "netName" );
1235 int fillOrder = V3GetInt( row.inner, "order" );
1236
1237 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
1238
1239 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1240
1241 zone->SetNet( aBoard->FindNet( netname ) );
1242 zone->SetLayer( klayer );
1243 zone->SetAssignedPriority( 500 - fillOrder );
1244 zone->SetLocalClearance( zoneClearance() );
1245 zone->SetMinThickness( zoneMinThickness );
1246
1247 for( nlohmann::json& polyData : polyDataList )
1248 {
1249 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
1250 contour.SetClosed( true );
1251 zone->Outline()->Append( contour );
1252 }
1253
1254 wxASSERT( zone->Outline()->OutlineCount() == 1 );
1255
1256 poursToFill.emplace( row.id, zone.get() );
1257
1258 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1259 }
1260 else if( row.type == wxS( "TEARDROP" ) )
1261 {
1262 int layer = V3GetInt( row.inner, "layerId", 1 );
1263 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1264 wxString netname = V3GetString( row.inner, "netName" );
1265
1266 nlohmann::json polyData = row.inner.value( "path", nlohmann::json::array() );
1267
1268 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
1269 contour.SetClosed( true );
1270
1271 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1272
1273 zone->SetNet( aBoard->FindNet( netname ) );
1274 zone->SetLayer( klayer );
1275 zone->Outline()->Append( contour );
1276 zone->SetFilledPolysList( klayer, contour );
1277 zone->SetNeedRefill( false );
1278 zone->SetIsFilled( true );
1279 zone->SetAssignedPriority( 600 );
1280 zone->SetLocalClearance( 0 );
1281 zone->SetMinThickness( 0 );
1282 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_VIAPAD );
1283
1284 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1285 }
1286 else if( row.type == wxS( "POURED" ) )
1287 {
1288 nlohmann::json idArr = V3ParseIdArray( row.id );
1289 wxString parentId;
1290
1291 if( idArr.is_array() && idArr.size() > 1 )
1292 parentId = V3JsonToString( idArr[1] );
1293
1294 nlohmann::json fills = row.inner.value( "pourFill", nlohmann::json::array() );
1295
1296 if( !fills.is_array() )
1297 continue;
1298
1299 for( const nlohmann::json& fill : fills )
1300 {
1301 EASYEDAPRO::POURED poured;
1302 poured.pouredId = V3JsonToString( fill.value( "id", nlohmann::json( row.id ) ) );
1303 poured.parentId = parentId;
1304 poured.unki = V3GetInt( fill, "strokeWidth" );
1305 poured.isPoly = V3GetBool( fill, "fill" );
1306 poured.polyData = fill.value( "path", nlohmann::json::array() );
1307
1308 boardPouredMap.emplace( poured.parentId, poured );
1309 }
1310 }
1311 else if( row.type == wxS( "REGION" ) )
1312 {
1313 int layer = V3GetInt( row.inner, "layerId", 1 );
1314 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1315
1316 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
1317 nlohmann::json prohibitTypes = row.inner.value( "prohibitType", nlohmann::json::array() );
1318
1319 std::set<int> flags;
1320
1321 if( prohibitTypes.is_array() )
1322 {
1323 for( const auto& pt : prohibitTypes )
1324 {
1325 wxString ptStr;
1326
1327 if( pt.is_string() )
1328 ptStr = pt.get<std::string>();
1329
1330 if( ptStr == wxS( "COMPONENT" ) )
1331 flags.insert( 2 );
1332 else if( ptStr == wxS( "TRACK" ) )
1333 flags.insert( 5 );
1334 else if( ptStr == wxS( "COPPER" ) )
1335 flags.insert( 6 );
1336 else if( ptStr == wxS( "VIA" ) )
1337 flags.insert( 7 );
1338 else if( ptStr == wxS( "FILL" ) || ptStr == wxS( "PLANE" ) )
1339 flags.insert( 8 );
1340 }
1341 }
1342
1343 for( nlohmann::json& polyData : polyDataList )
1344 {
1345 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
1346 contour.SetClosed( true );
1347
1348 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1349
1350 zone->SetIsRuleArea( true );
1351 zone->SetDoNotAllowFootprints( !!flags.count( 2 ) );
1352 zone->SetDoNotAllowZoneFills( !!flags.count( 7 ) || !!flags.count( 6 ) || !!flags.count( 8 ) );
1353 zone->SetDoNotAllowPads( !!flags.count( 7 ) );
1354 zone->SetDoNotAllowTracks( !!flags.count( 7 ) || !!flags.count( 5 ) );
1355 zone->SetDoNotAllowVias( !!flags.count( 7 ) );
1356
1357 zone->SetLayer( klayer );
1358 zone->Outline()->Append( contour );
1359
1360 aBoard->Add( zone.release(), ADD_MODE::APPEND );
1361 }
1362 }
1363 else if( row.type == wxS( "STRING" ) )
1364 {
1365 aBoard->Add( createV3Text( aBoard, row ), ADD_MODE::APPEND );
1366 }
1367 else if( row.type == wxS( "COMPONENT" ) )
1368 {
1369 COMP_DATA comp;
1370 comp.layer = V3GetInt( row.inner, "layerId", 1 );
1371 comp.pos.x = V3GetDouble( row.inner, "x" );
1372 comp.pos.y = V3GetDouble( row.inner, "y" );
1373 comp.angle = V3GetDouble( row.inner, "angle" );
1374
1375 nlohmann::json attrsObj = row.inner.value( "attrs", nlohmann::json::object() );
1376
1377 if( attrsObj.is_object() )
1378 {
1379 for( auto& [k, v] : attrsObj.items() )
1380 comp.attrs[wxString( k )] = V3JsonToString( v );
1381 }
1382
1383 componentMap[row.id] = comp;
1384 }
1385 else if( row.type == wxS( "ATTR" ) )
1386 {
1387 ATTR_DATA attr;
1388 attr.parentId = V3GetString( row.inner, "parentId" );
1389 attr.layer = V3GetInt( row.inner, "layerId", 3 );
1390 attr.hasPos = !V3IsNullOrMissing( row.inner, "x" );
1391
1392 if( attr.hasPos )
1393 {
1394 attr.x = V3GetDouble( row.inner, "x" );
1395 attr.y = V3GetDouble( row.inner, "y" );
1396 }
1397
1398 attr.key = V3GetString( row.inner, "key" );
1399 attr.value = V3JsonToString( row.inner.value( "value", nlohmann::json() ) );
1400 attr.keyVisible = V3GetBool( row.inner, "keyVisible" );
1401 attr.valVisible = V3GetBool( row.inner, "valueVisible" );
1402 attr.fontName = V3GetString( row.inner, "fontFamily", wxS( "default" ) );
1403 attr.fontSize = V3GetDouble( row.inner, "fontSize", 45.0 );
1404 attr.strokeWidth = V3GetDouble( row.inner, "strokeWidth", 6.0 );
1405 attr.origin = V3GetString( row.inner, "origin", wxS( "LEFT_BOTTOM" ) );
1406 attr.angle = V3GetDouble( row.inner, "angle" );
1407 attr.inverted = V3GetBool( row.inner, "reverse" ) ? 1 : 0;
1408 attr.mirror = V3GetBool( row.inner, "mirror" ) ? 1 : 0;
1409
1410 attrMap.emplace( attr.parentId, attr );
1411 }
1412 else if( row.type == wxS( "PAD_NET" ) )
1413 {
1414 nlohmann::json idArr = V3ParseIdArray( row.id );
1415
1416 PAD_NET_DATA pn;
1417 pn.compId = idArr.is_array() && idArr.size() > 1 ? V3JsonToString( idArr[1] ) : wxString();
1418 pn.padNumber = idArr.is_array() && idArr.size() > 2 ? V3JsonToString( idArr[2] ) : wxString();
1419 pn.padNet = V3GetString( row.inner, "padNet" );
1420
1421 if( !pn.compId.IsEmpty() )
1422 padNetMap.emplace( pn.compId, pn );
1423 }
1424 else if( row.type == wxS( "PAD" ) )
1425 {
1426 wxString netname = V3GetString( row.inner, "netName" );
1427
1428 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
1429 std::unique_ptr<PAD> pad = createV3PAD( footprint.get(), row );
1430
1431 pad->SetNet( aBoard->FindNet( netname ) );
1432
1433 VECTOR2I pos = pad->GetPosition();
1434 EDA_ANGLE orient = pad->GetOrientation();
1435
1436 pad->SetPosition( VECTOR2I() );
1437 pad->SetOrientation( ANGLE_0 );
1438
1439 footprint->Add( pad.release(), ADD_MODE::APPEND );
1440 footprint->SetPosition( pos );
1441 footprint->SetOrientation( orient );
1442
1443 wxString fpName = wxS( "Pad_" ) + row.id;
1444 LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, fpName );
1445
1446 footprint->SetFPID( fpID );
1447 footprint->Reference().SetVisible( true );
1448 footprint->Value().SetVisible( true );
1449 footprint->AutoPositionFields();
1450
1451 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1452 }
1453 else if( row.type == wxS( "IMAGE" ) )
1454 {
1455 int layer = V3GetInt( row.inner, "layerId", 1 );
1456 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1457
1458 VECTOR2D start;
1459 start.x = V3GetDouble( row.inner, "startX" );
1460 start.y = V3GetDouble( row.inner, "startY" );
1461
1462 VECTOR2D size;
1463 size.x = V3GetDouble( row.inner, "width" );
1464 size.y = V3GetDouble( row.inner, "height" );
1465
1466 double angle = V3GetDouble( row.inner, "angle" );
1467 bool mirror = V3GetBool( row.inner, "mirror" );
1468 nlohmann::json polyDataList = row.inner.value( "path", nlohmann::json::array() );
1469
1470 BOX2I bbox;
1471 std::vector<SHAPE_LINE_CHAIN> contours;
1472 for( nlohmann::json& polyData : polyDataList )
1473 {
1474 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( polyData, false );
1475 contour.SetClosed( true );
1476
1477 contours.push_back( contour );
1478
1479 bbox.Merge( contour.BBox() );
1480 }
1481
1482 if( bbox.GetSize().x == 0 || bbox.GetSize().y == 0 )
1483 continue;
1484
1487
1488 SHAPE_POLY_SET polySet;
1489
1490 for( SHAPE_LINE_CHAIN& contour : contours )
1491 {
1492 for( int i = 0; i < contour.PointCount(); i++ )
1493 {
1494 VECTOR2I pt = contour.CPoint( i );
1495 contour.SetPoint( i, VECTOR2I( pt.x * scale.x, pt.y * scale.y ) );
1496 }
1497
1498 polySet.AddOutline( contour );
1499 }
1500
1501 polySet.RebuildHolesFromContours();
1502
1503 std::unique_ptr<PCB_GROUP> group;
1504
1505 if( polySet.OutlineCount() > 1 )
1506 group = std::make_unique<PCB_GROUP>( aBoard );
1507
1508 BOX2I polyBBox = polySet.BBox();
1509
1510 for( const SHAPE_POLY_SET::POLYGON& poly : polySet.CPolygons() )
1511 {
1512 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aBoard, SHAPE_T::POLY );
1513
1514 shape->SetFilled( true );
1515 shape->SetPolyShape( poly );
1516 shape->SetLayer( klayer );
1517 shape->SetWidth( 0 );
1518
1519 shape->Move( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ) - polyBBox.GetOrigin() );
1520 shape->Rotate( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ), EDA_ANGLE( angle, DEGREES_T ) );
1521
1522 if( IsBackLayer( klayer ) ^ mirror )
1523 {
1525 shape->Mirror( PCB_IO_EASYEDAPRO_PARSER::ScalePos( start ), flipDirection );
1526 }
1527
1528 if( group )
1529 group->AddItem( shape.get() );
1530
1531 aBoard->Add( shape.release(), ADD_MODE::APPEND );
1532 }
1533
1534 if( group )
1535 aBoard->Add( group.release(), ADD_MODE::APPEND );
1536 }
1537 else if( row.type == wxS( "OBJ" ) )
1538 {
1539 int layer = V3GetInt( row.inner, "layerId", 1 );
1540 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( layer );
1541
1542 VECTOR2D start;
1543 start.x = V3GetDouble( row.inner, "startX" );
1544 start.y = V3GetDouble( row.inner, "startY" );
1545
1546 VECTOR2D size;
1547 size.x = V3GetDouble( row.inner, "width" );
1548 size.y = V3GetDouble( row.inner, "height" );
1549
1550 double angle = V3GetDouble( row.inner, "angle" );
1551 bool mirror = V3GetBool( row.inner, "mirror" );
1552 wxString imageUrl = V3GetString( row.inner, "path" );
1553
1554 if( imageUrl.empty() )
1555 continue;
1556
1557 wxString mimeType, base64Data;
1558
1559 if( imageUrl.BeforeFirst( ':' ) == wxS( "blob" ) )
1560 {
1561 wxString objectId = imageUrl.AfterLast( ':' );
1562
1563 if( auto blob = get_opt( aBlobMap, objectId ) )
1564 {
1565 wxString blobUrl = blob->url;
1566
1567 if( blobUrl.BeforeFirst( ':' ) == wxS( "data" ) )
1568 {
1569 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst( ':' ).BeforeFirst( ',' ), ';', '\0' );
1570
1571 base64Data = blobUrl.AfterFirst( ',' );
1572
1573 if( paramsArr.size() > 0 )
1574 mimeType = paramsArr[0];
1575 }
1576 }
1577 }
1578
1581
1582 if( mimeType.empty() || base64Data.empty() )
1583 continue;
1584
1585 wxMemoryBuffer buf = wxBase64Decode( base64Data );
1586
1587 if( mimeType == wxS( "image/svg+xml" ) )
1588 {
1589 // Not yet supported by EasyEDA
1590 }
1591 else
1592 {
1593 VECTOR2D kcenter = kstart + ksize / 2;
1594
1595 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
1596 std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
1597 REFERENCE_IMAGE& refImage = bitmap->GetReferenceImage();
1598
1599 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
1600
1601 if( refImage.ReadImageFile( buf ) )
1602 {
1603 double scaleFactor = PCB_IO_EASYEDAPRO_PARSER::ScaleSize( size.x ) / refImage.GetSize().x;
1604 refImage.SetImageScale( scaleFactor );
1605
1606 // TODO: support non-90-deg angles
1607 bitmap->Rotate( kstart, EDA_ANGLE( angle, DEGREES_T ) );
1608
1609 if( mirror )
1610 {
1611 int x = bitmap->GetPosition().x;
1612 MIRROR( x, KiROUND( kstart.x ) );
1613 bitmap->SetX( x );
1614
1616 }
1617
1618 aBoard->Add( bitmap.release(), ADD_MODE::APPEND );
1619 }
1620 }
1621 }
1622 } // end first pass
1623
1624 // Second pass: place components
1625 for( auto const& [compId, comp] : componentMap )
1626 {
1627 wxString deviceId;
1628 wxString fpIdOverride;
1629 wxString fpDesignator;
1630
1631 // Collect attrs for this component
1632 auto attrRange = attrMap.equal_range( compId );
1633
1634 for( auto it = attrRange.first; it != attrRange.second; ++it )
1635 {
1636 const ATTR_DATA& attr = it->second;
1637
1638 if( attr.key == wxS( "Device" ) )
1639 deviceId = attr.value;
1640 else if( attr.key == wxS( "Footprint" ) )
1641 fpIdOverride = attr.value;
1642 else if( attr.key == wxS( "Designator" ) )
1643 fpDesignator = attr.value;
1644 }
1645
1646 // BOM / identity / 3D fields come from the component's Device ATTR.
1647 EASYEDAPRO::V3_DEVICE_DATA deviceData = GetV3DeviceData( aProject, deviceId );
1648 const std::map<wxString, wxString>& deviceAttrs = deviceData.attributes;
1649
1650 wxString fpId = fpIdOverride;
1651
1652 if( fpId.empty() )
1653 fpId = get_def( deviceAttrs, wxS( "Footprint" ), wxEmptyString );
1654
1655 if( fpId.empty() )
1656 {
1657 wxLogTrace( traceEasyEdaIo, wxT( "EasyEDA Pro v3 component '%s' (%s): no footprint mapping, skipping." ),
1658 compId, fpDesignator );
1659 continue;
1660 }
1661
1662 auto it = aFootprintMap.find( fpId );
1663
1664 if( it == aFootprintMap.end() )
1665 {
1666 wxLogTrace( traceEasyEdaIo, "Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
1667 continue;
1668 }
1669
1670 std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
1671 std::unique_ptr<FOOTPRINT> footprint( static_cast<FOOTPRINT*>( footprintOrig->Duplicate( false ) ) );
1672
1673 footprint->SetParent( aBoard );
1674
1675 // Footprint LIB_ID uses package geometry name (e.g. C0402), matching schematic
1676 // Footprint fields. Device/BOM identity stays in fields, not the FPID.
1677 wxString libItemName = footprint->GetFPID().GetLibItemName();
1678
1679 if( libItemName.empty() )
1680 libItemName = fpId;
1681
1682 footprint->SetFPID( ToKiCadLibID( aFpLibName, libItemName ) );
1683
1684 wxString modelUuid = get_def( deviceAttrs, wxS( "3D Model" ), wxEmptyString );
1685 wxString modelTitle = get_def( deviceAttrs, wxS( "3D Model Title" ), modelUuid ).Trim();
1686 wxString modelTransform = get_def( deviceAttrs, wxS( "3D Model Transform" ), wxEmptyString );
1687
1688 m_v2Parser.FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
1689
1690 wxString valueText = ResolveV3DeviceValueText( deviceAttrs );
1691
1692 if( !valueText.empty() )
1693 footprint->SetValue( valueText );
1694
1695 wxString description = deviceData.description;
1696
1697 if( description.empty() )
1698 description = get_def( deviceAttrs, wxS( "Description" ), wxEmptyString );
1699
1700 if( !description.empty() )
1701 {
1702 footprint->GetField( FIELD_T::DESCRIPTION )
1703 ->SetText( NormalizeEasyEDAText( ResolveDeviceFieldVariables( description, deviceAttrs ) ) );
1704 }
1705
1706 ForEachImportedDeviceField( deviceAttrs, false,
1707 [&]( const wxString& attrKey, const wxString& value )
1708 {
1709 PCB_FIELD* field = nullptr;
1710
1711 if( attrKey == wxS( "Datasheet" ) )
1712 field = footprint->GetField( FIELD_T::DATASHEET );
1713 else
1714 field = footprint->GetField( attrKey );
1715
1716 if( !field )
1717 {
1718 field = new PCB_FIELD( footprint.get(), FIELD_T::USER, attrKey );
1719 footprint->Add( field, ADD_MODE::APPEND );
1720 }
1721
1722 field->SetText( value );
1723 field->SetVisible( false );
1724 } );
1725
1726 // Apply position, rotation, flip
1727 PCB_LAYER_ID klayer = m_v2Parser.LayerToKi( comp.layer );
1728
1729 if( klayer == B_Cu )
1730 footprint->Flip( footprint->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
1731
1732 footprint->SetOrientationDegrees( comp.angle );
1733 footprint->SetPosition( PCB_IO_EASYEDAPRO_PARSER::ScalePos( comp.pos ) );
1734
1735 // Apply attributes (designator, etc.)
1736 for( auto attrIt = attrRange.first; attrIt != attrRange.second; ++attrIt )
1737 {
1738 const ATTR_DATA& attr = attrIt->second;
1739
1740 if( attr.key == wxS( "Designator" ) )
1741 {
1742 PCB_FIELD* field = footprint->GetField( FIELD_T::REFERENCE );
1743
1744 if( attr.fontName != wxS( "default" ) )
1745 field->SetFont( KIFONT::FONT::GetFont( attr.fontName ) );
1746
1747 if( attr.valVisible && attr.keyVisible )
1748 field->SetText( attr.key + ':' + attr.value );
1749 else if( attr.keyVisible )
1750 field->SetText( attr.key );
1751 else
1752 field->SetText( attr.value );
1753
1754 field->SetVisible( attr.keyVisible || attr.valVisible );
1755
1756 PCB_LAYER_ID attrLayer = m_v2Parser.LayerToKi( attr.layer );
1757 field->SetLayer( attrLayer );
1758
1759 if( attr.hasPos )
1760 {
1761 field->SetPosition( PCB_IO_EASYEDAPRO_PARSER::ScalePos( VECTOR2D( attr.x, attr.y ) ) );
1762 }
1763
1764 field->SetKeepUpright( false );
1765 field->SetTextAngleDegrees( footprint->IsFlipped() ? -attr.angle : attr.angle );
1766 field->SetIsKnockout( attr.inverted );
1767 field->SetTextThickness( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( attr.strokeWidth ) );
1768 field->SetTextSize( VECTOR2D( PCB_IO_EASYEDAPRO_PARSER::ScaleSize( attr.fontSize * 0.55 ),
1769 PCB_IO_EASYEDAPRO_PARSER::ScaleSize( attr.fontSize * 0.6 ) ) );
1770
1771 int alignCode = V3AlignToOriginCode( attr.origin );
1772 V3AlignText( field, alignCode );
1773 }
1774 }
1775
1776 // Apply pad nets
1777 auto padNetRange = padNetMap.equal_range( compId );
1778
1779 for( auto pnIt = padNetRange.first; pnIt != padNetRange.second; ++pnIt )
1780 {
1781 const PAD_NET_DATA& pn = pnIt->second;
1782
1783 for( PAD* pad : footprint->Pads() )
1784 {
1785 if( pad->GetNumber() == pn.padNumber )
1786 pad->SetNet( aBoard->FindNet( pn.padNet ) );
1787 }
1788 }
1789
1790 aBoard->Add( footprint.release(), ADD_MODE::APPEND );
1791 }
1792
1793 // Set zone fills from POURED data
1795 {
1796 for( auto& [uuid, zone] : poursToFill )
1797 {
1798 SHAPE_POLY_SET fillPolySet;
1799 SHAPE_POLY_SET thermalSpokes;
1800
1801 auto range = boardPouredMap.equal_range( uuid );
1802
1803 for( auto& pIt = range.first; pIt != range.second; ++pIt )
1804 {
1805 const EASYEDAPRO::POURED& poured = pIt->second;
1806
1807 SHAPE_POLY_SET thisPoly;
1808
1809 for( int dataId = 0; dataId < static_cast<int>( poured.polyData.size() ); dataId++ )
1810 {
1811 const nlohmann::json& fillData = poured.polyData[dataId];
1812 const double ptScale = 10;
1813
1814 SHAPE_LINE_CHAIN contour = m_v2Parser.ParseContour( fillData, false, ARC_HIGH_DEF / ptScale );
1815
1816 // Scale the fill
1817 for( int i = 0; i < contour.PointCount(); i++ )
1818 contour.SetPoint( i, contour.GetPoint( i ) * ptScale );
1819
1820 if( poured.isPoly )
1821 {
1822 contour.SetClosed( true );
1823
1824 // The contour can be self-intersecting
1825 SHAPE_POLY_SET simple( contour );
1826 simple.Simplify();
1827
1828 if( dataId == 0 )
1829 {
1830 thisPoly.Append( simple );
1831 }
1832 else
1833 {
1834 thisPoly.BooleanSubtract( simple );
1835 }
1836 }
1837 else
1838 {
1839 const int thermalWidth = pcbIUScale.mmToIU( 0.2 );
1840
1841 for( int segId = 0; segId < contour.SegmentCount(); segId++ )
1842 {
1843 const SEG& seg = contour.CSegment( segId );
1844
1845 TransformOvalToPolygon( thermalSpokes, seg.A, seg.B, thermalWidth, ARC_HIGH_DEF,
1846 ERROR_INSIDE );
1847 }
1848 }
1849 }
1850
1851 fillPolySet.Append( thisPoly );
1852 }
1853
1854 if( !fillPolySet.IsEmpty() )
1855 {
1856 fillPolySet.Simplify();
1857
1858 const int strokeWidth = pcbIUScale.MilsToIU( 8 );
1859
1860 fillPolySet.Inflate( strokeWidth / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF, false );
1861
1862 fillPolySet.BooleanAdd( thermalSpokes );
1863 fillPolySet.Fracture();
1864
1865 zone->SetFilledPolysList( zone->GetFirstLayer(), fillPolySet );
1866 zone->SetNeedRefill( false );
1867 zone->SetIsFilled( true );
1868 }
1869 }
1870 }
1871
1872 // Heal board outlines
1873 std::vector<PCB_SHAPE*> shapes;
1874
1875 for( BOARD_ITEM* item : aBoard->Drawings() )
1876 {
1877 if( !item->IsOnLayer( Edge_Cuts ) )
1878 continue;
1879
1880 if( item->Type() == PCB_SHAPE_T )
1881 shapes.push_back( static_cast<PCB_SHAPE*>( item ) );
1882 }
1883
1885
1886 // Center the board
1887 BOX2I outlineBbox = aBoard->ComputeBoundingBox( true );
1888 PAGE_INFO pageInfo = aBoard->GetPageSettings();
1889
1890 VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ),
1891 pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) );
1892
1893 VECTOR2D offset = pageCenter - outlineBbox.GetCenter();
1894
1895 int alignGrid = pcbIUScale.mmToIU( 10 );
1896 offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
1897 offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
1898
1899 aBoard->Move( offset );
1900 bds.SetAuxOrigin( offset );
1901}
int index
@ ERROR_INSIDE
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
void Mirror(FLIP_DIRECTION aFlipDirection)
Mirror image vertically (i.e.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetAuxOrigin(const VECTOR2I &aOrigin)
ZONE_SETTINGS & GetDefaultZoneSettings()
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:424
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:383
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
bool m_LegacyDesignSettingsLoaded
True if the legacy board design settings were loaded from a file.
Definition board.h:565
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
const PAGE_INFO & GetPageSettings() const
Definition board.h:1020
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2797
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition board.cpp:950
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition board.cpp:816
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition board.h:569
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2538
unsigned GetNetCount() const
Definition board.h:1252
void SetEnabledLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1194
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
const DRAWINGS & Drawings() const
Definition board.h:467
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr const SizeVec & GetSize() const
Definition box2.h:203
EDA_ANGLE Normalize90()
Definition eda_angle.h:260
double AsDegrees() const
Definition eda_angle.h:115
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
void SetTextAngleDegrees(double aOrientation)
Definition eda_text.h:181
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:386
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.
DRAWINGS & GraphicalItems()
Definition footprint.h:358
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:144
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
void SetClearance(int aClearance)
Definition netclass.h:144
Handle the data for a net.
Definition netinfo.h:50
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:615
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:622
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
double GetHeightMils() const
Definition page_info.h:142
double GetWidthMils() const
Definition page_info.h:137
static nlohmann::json NormalizeSafeSpacing(const nlohmann::json &aTable)
Map legacy eight- and ten-row safe-spacing matrices to the v3 category layout.
static VECTOR2< T > ScalePos(VECTOR2< T > aValue)
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 EASYEDAPRO::V3_DOC_RAW &aDoc, const wxString &aFpLibName)
PCB_IO_EASYEDAPRO_V3_PARSER(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter)
std::unique_ptr< PAD > createV3PAD(FOOTPRINT *aFootprint, const EASYEDAPRO::V3_ROW &aRow)
std::unique_ptr< FOOTPRINT > ParseFootprint(const std::map< wxString, EASYEDAPRO::BLOB > &aBlobMap, const EASYEDAPRO::V3_DOC_RAW &aDoc)
PCB_TEXT * createV3Text(BOARD_ITEM_CONTAINER *aContainer, const EASYEDAPRO::V3_ROW &aRow)
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:512
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
Definition pcb_text.cpp:484
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:103
A progress reporter interface for use in multi-threaded environments.
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
BITMAP_BASE & MutableImage() const
Only use this if you really need to modify the underlying image.
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
VECTOR2I GetSize() const
void SetImageScale(double aScale)
Set the image "zoom" value.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
SHAPE_ARC & 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.
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 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.
static bool empty(const wxTextEntryBase *aCtrl)
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
@ 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)...
const wxChar *const traceEasyEdaIo
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:867
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ F_Fab
Definition layer_ids.h:115
@ F_Cu
Definition layer_ids.h:60
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)
bool V3GetBool(const nlohmann::json &aObj, const char *aKey, bool aDefault)
void ForEachImportedDeviceField(const std::map< wxString, wxString > &aDeviceAttributes, bool aIncludeValue, const std::function< void(const wxString &aKey, const wxString &aValue)> &aCallback)
Invoke aCallback for each non-empty whitelisted Device field (resolved + normalized).
wxString ResolveDeviceFieldVariables(const wxString &aInput, const std::map< wxString, wxString > &aDeviceAttributes)
Resolve EasyEDA ={Var} / ={A}text{B} field expressions against device attributes.
wxString NormalizeEasyEDAText(wxString aText)
Replace EasyEDA temperature glyph (℃) with °C.
V3_DEVICE_DATA GetV3DeviceData(const nlohmann::json &aProject, const wxString &aDeviceUuid)
static const bool IMPORT_POURED
nlohmann::json V3ParseIdArray(const wxString &aId)
bool V3IsNullOrMissing(const nlohmann::json &aObj, const char *aKey)
double V3GetDouble(const nlohmann::json &aObj, const char *aKey, double aDefault)
wxString V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
wxString ResolveV3DeviceValueText(const std::map< wxString, wxString > &aDeviceAttributes)
Preferred Value text: Value attribute, else Name, with variables resolved.
int V3GetInt(const nlohmann::json &aObj, const char *aKey, int aDefault)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
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 int SHAPE_JOIN_DISTANCE
static wxString safeSpacingToMm(double aMil)
static int V3AlignToOriginCode(const wxString &aAlign)
static void V3AlignText(EDA_TEXT *text, int align)
static const wxString safeSpacingNames[]
static const wxString safeSpacingConditions[]
const int scale
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
nlohmann::json polyData
std::map< wxString, wxString > attributes
Raw parsed document from an EasyEDA Pro v3 .epru stream.
std::vector< V3_ROW > rows
One parsed row from an EasyEDA Pro v3 .epru document stream.
@ USER
The field ID hasn't been set yet; field is invalid.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
KIBIS_COMPONENT * comp
VECTOR2I center
int radius
VECTOR2I end
int clearance
VECTOR2I location
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.
double DEG2RAD(double deg)
Definition trigo.h:178
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707