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