388 std::unique_ptr<FOOTPRINT> footprintPtr = std::make_unique<FOOTPRINT>(
m_board );
389 FOOTPRINT* footprint = footprintPtr.get();
392 const int defaultTextThickness(
pcbIUScale.mmToIU( 0.15 ) );
397 field->SetTextSize( defaultTextSize );
398 field->SetTextThickness( defaultTextThickness );
403 if( row.
type == wxS(
"CANVAS" ) )
408 else if( row.
type == wxS(
"POLY" ) )
414 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
416 std::vector<std::unique_ptr<PCB_SHAPE>> results =
m_v2Parser.ParsePoly( footprint, polyData,
false,
false );
418 for(
auto& shape : results )
420 shape->SetLayer( klayer );
425 else if( row.
type == wxS(
"PAD" ) )
430 else if( row.
type == wxS(
"FILL" ) )
435 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
437 if( !polyDataList.is_null() && !polyDataList.empty() )
439 if( !polyDataList.at( 0 ).is_array() )
440 polyDataList = nlohmann::json::array( { polyDataList } );
442 std::vector<SHAPE_LINE_CHAIN> contours;
444 for( nlohmann::json& polyData : polyDataList )
448 contours.push_back( contour );
458 std::unique_ptr<PCB_GROUP>
group;
461 group = std::make_unique<PCB_GROUP>( footprint );
465 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::POLY );
467 shape->SetFilled(
true );
468 shape->SetPolyShape( poly );
469 shape->SetLayer( klayer );
470 shape->SetWidth( 0 );
473 group->AddItem( shape.get() );
482 else if( row.
type == wxS(
"ATTR" ) )
487 if( key == wxS(
"Designator" ) )
490 else if( row.
type == wxS(
"REGION" ) )
495 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
496 nlohmann::json prohibitTypes = row.
inner.value(
"prohibitType", nlohmann::json::array() );
500 if( prohibitTypes.is_array() )
502 for(
const auto& pt : prohibitTypes )
507 ptStr = pt.get<std::string>();
509 if( ptStr == wxS(
"COMPONENT" ) )
511 else if( ptStr == wxS(
"TRACK" ) )
513 else if( ptStr == wxS(
"COPPER" ) )
515 else if( ptStr == wxS(
"VIA" ) )
517 else if( ptStr == wxS(
"FILL" ) || ptStr == wxS(
"PLANE" ) )
522 for( nlohmann::json& polyData : polyDataList )
526 std::vector<std::unique_ptr<PCB_SHAPE>> results =
527 m_v2Parser.ParsePoly(
nullptr, polyData,
true,
false );
529 for(
auto& shape : results )
531 shape->SetFilled(
true );
537 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( footprint );
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 ) );
546 zone->SetLayer( klayer );
547 zone->Outline()->Append( polySet );
552 else if( row.
type == wxS(
"IMAGE" ) )
567 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
570 std::vector<SHAPE_LINE_CHAIN> contours;
571 for( nlohmann::json& polyData : polyDataList )
576 contours.push_back( contour );
591 for(
int i = 0; i < contour.PointCount(); i++ )
602 std::unique_ptr<PCB_GROUP>
group;
605 group = std::make_unique<PCB_GROUP>( footprint );
611 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::POLY );
613 shape->SetFilled(
true );
614 shape->SetPolyShape( poly );
615 shape->SetLayer( klayer );
616 shape->SetWidth( 0 );
628 group->AddItem( shape.get() );
636 else if( row.
type == wxS(
"OBJ" ) )
653 if( imageUrl.empty() )
656 wxString mimeType, base64Data;
658 if( imageUrl.BeforeFirst(
':' ) == wxS(
"blob" ) )
660 wxString objectId = imageUrl.AfterLast(
':' );
662 if(
auto blob =
get_opt( aBlobMap, objectId ) )
664 wxString blobUrl = blob->url;
666 if( blobUrl.BeforeFirst(
':' ) == wxS(
"data" ) )
668 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst(
':' ).BeforeFirst(
',' ),
';',
'\0' );
670 base64Data = blobUrl.AfterFirst(
',' );
672 if( paramsArr.size() > 0 )
673 mimeType = paramsArr[0];
681 if( mimeType.empty() || base64Data.empty() )
684 wxMemoryBuffer buf = wxBase64Decode( base64Data );
686 if( mimeType == wxS(
"image/svg+xml" ) )
692 VECTOR2D kcenter = kstart + ksize / 2;
694 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
695 std::make_unique<PCB_REFERENCE_IMAGE>( footprint, kcenter, klayer );
698 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
710 int x = bitmap->GetPosition().x;
721 else if( row.
type == wxS(
"STRING" ) )
732 std::vector<PCB_SHAPE*> edgeShapes;
737 edgeShapes.push_back(
static_cast<PCB_SHAPE*
>( item ) );
748 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::RECTANGLE );
753 shape->SetEnd( bbox.
GetEnd() );
759 bool hasFabRef =
false;
765 if(
static_cast<PCB_TEXT*
>( item )->GetText() == wxT(
"${REFERENCE}" ) )
776 int c_refTextThickness =
pcbIUScale.mmToIU( 0.1 );
777 std::unique_ptr<PCB_TEXT> refText = std::make_unique<PCB_TEXT>( footprint );
779 refText->SetLayer(
F_Fab );
780 refText->SetTextSize(
VECTOR2I( c_refTextSize, c_refTextSize ) );
781 refText->SetTextThickness( c_refTextThickness );
782 refText->SetText( wxT(
"${REFERENCE}" ) );
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 )
874 std::map<wxString, wxString> attrs;
886 bool keyVisible =
false;
887 bool valVisible =
false;
889 double fontSize = 45.0;
890 double strokeWidth = 6.0;
904 std::map<wxString, COMP_DATA> componentMap;
905 std::multimap<wxString, ATTR_DATA> attrMap;
906 std::multimap<wxString, PAD_NET_DATA> padNetMap;
908 std::multimap<wxString, EASYEDAPRO::POURED> boardPouredMap = aPouredMap;
909 std::map<wxString, ZONE*> poursToFill;
922 if( !idArr.is_array() || idArr.size() < 3 ||
V3JsonToString( idArr[1] ) != wxS(
"SAFE" ) )
927 nlohmann::json safeSpacing =
928 row.
inner.value(
"ruleContext", nlohmann::json() ).value(
"safeSpacing", nlohmann::json::array() );
930 if( safeSpacing.is_array() && !safeSpacing.empty() )
933 safeSpacing[0].value(
"content", nlohmann::json::array() ) );
939 auto zoneClearance = [&]() ->
int
957 if( row.
type == wxS(
"LAYER" ) )
965 if(
id.is_array() &&
id.size() > 1 )
970 if( layerId.ToLong( &parsedLayer ) )
986 blayers.
set( klayer );
989 if( !layerName.IsEmpty() )
993 else if( row.
type == wxS(
"NET" ) )
998 if( idArr.is_array() && idArr.size() > 1 )
1001 if( !netname.IsEmpty() )
1006 else if( row.
type == wxS(
"RULE" ) )
1010 if( !idArr.is_array() || idArr.size() < 3 )
1014 bool isDefault =
V3GetString( row.
inner,
"ruleState" ) == wxS(
"DEFAULT" );
1016 nlohmann::json context = row.
inner.value(
"ruleContext", nlohmann::json() );
1018 if( ruleType == wxS(
"TRACK" ) && isDefault )
1020 nlohmann::json track = context.value(
"track", nlohmann::json() );
1021 nlohmann::json content = track.value(
"content", nlohmann::json::array() );
1023 if( content.is_array() && !content.empty() )
1025 double minVal =
V3GetDouble( content[0],
"stroMin" );
1030 else if( ruleType == wxS(
"SAFE" ) && isDefault )
1032 nlohmann::json safeSpacing = context.value(
"safeSpacing", nlohmann::json::array() );
1034 if( safeSpacing.is_array() && !safeSpacing.empty() )
1037 safeSpacing[0].value(
"content", nlohmann::json::array() ) );
1046 if( content.is_array() && !content.empty() && content.at( 0 ).is_array() && !content.at( 0 ).empty()
1047 && content.at( 0 ).at( 0 ).is_number() )
1049 double trackClearance = content.at( 0 ).at( 0 );
1051 if( trackClearance > 0 )
1060 double minEdgeClearance = 0;
1062 if( content.size() > 11 && content.at( 11 ).is_array() )
1064 for(
const nlohmann::json& value : content.at( 11 ) )
1066 if( value.is_number() && value.get<
double>() > 0
1067 && ( minEdgeClearance == 0 || value.get<
double>() < minEdgeClearance ) )
1069 minEdgeClearance = value;
1074 if( minEdgeClearance > 0 )
1082 else if( row.
type == wxS(
"VIA" ) )
1092 std::unique_ptr<PCB_VIA>
via = std::make_unique<PCB_VIA>( aBoard );
1102 else if( row.
type == wxS(
"LINE" ) )
1118 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
1120 track->SetLayer( klayer );
1124 track->SetNet( aBoard->
FindNet( netname ) );
1128 else if( row.
type == wxS(
"ARC" ) )
1148 double ha = angle / 2;
1149 double hd =
delta.EuclideanNorm() / 2;
1150 double cdist = hd / tan(
DEG2RAD( ha ) );
1158 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
1160 arc->SetLayer( klayer );
1161 arc->SetNet( aBoard->
FindNet( netname ) );
1165 else if( row.
type == wxS(
"POLY" ) )
1171 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
1173 std::vector<std::unique_ptr<PCB_SHAPE>> results =
m_v2Parser.ParsePoly( aBoard, polyData,
false,
false );
1175 for(
auto& shape : results )
1177 shape->SetLayer( klayer );
1183 else if( row.
type == wxS(
"FILL" ) )
1190 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1192 if( polyDataList.is_null() || polyDataList.empty() )
1195 if( !polyDataList.at( 0 ).is_array() )
1196 polyDataList = nlohmann::json::array( { polyDataList } );
1198 std::vector<SHAPE_LINE_CHAIN> contours;
1200 for( nlohmann::json& polyData : polyDataList )
1204 contours.push_back( contour );
1215 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1217 zone->SetNet( aBoard->
FindNet( netname ) );
1218 zone->SetLayer( klayer );
1220 zone->SetFilledPolysList( klayer, zoneFillPoly );
1221 zone->SetAssignedPriority( 500 );
1222 zone->SetIsFilled(
true );
1223 zone->SetNeedRefill(
false );
1225 zone->SetLocalClearance( zoneClearance() );
1226 zone->SetMinThickness( zoneMinThickness );
1230 else if( row.
type == wxS(
"POUR" ) )
1238 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1240 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1242 zone->SetNet( aBoard->
FindNet( netname ) );
1243 zone->SetLayer( klayer );
1244 zone->SetAssignedPriority( 500 - fillOrder );
1245 zone->SetLocalClearance( zoneClearance() );
1246 zone->SetMinThickness( zoneMinThickness );
1248 for( nlohmann::json& polyData : polyDataList )
1252 zone->Outline()->Append( contour );
1255 wxASSERT( zone->Outline()->OutlineCount() == 1 );
1257 poursToFill.emplace( row.
id, zone.get() );
1261 else if( row.
type == wxS(
"TEARDROP" ) )
1267 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
1272 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
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 );
1287 else if( row.
type == wxS(
"POURED" ) )
1292 if( idArr.is_array() && idArr.size() > 1 )
1295 nlohmann::json fills = row.
inner.value(
"pourFill", nlohmann::json::array() );
1297 if( !fills.is_array() )
1300 for(
const nlohmann::json& fill : fills )
1307 poured.
polyData = fill.value(
"path", nlohmann::json::array() );
1309 boardPouredMap.emplace( poured.
parentId, poured );
1312 else if( row.
type == wxS(
"REGION" ) )
1317 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1318 nlohmann::json prohibitTypes = row.
inner.value(
"prohibitType", nlohmann::json::array() );
1320 std::set<int> flags;
1322 if( prohibitTypes.is_array() )
1324 for(
const auto& pt : prohibitTypes )
1328 if( pt.is_string() )
1329 ptStr = pt.get<std::string>();
1331 if( ptStr == wxS(
"COMPONENT" ) )
1333 else if( ptStr == wxS(
"TRACK" ) )
1335 else if( ptStr == wxS(
"COPPER" ) )
1337 else if( ptStr == wxS(
"VIA" ) )
1339 else if( ptStr == wxS(
"FILL" ) || ptStr == wxS(
"PLANE" ) )
1344 for( nlohmann::json& polyData : polyDataList )
1349 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
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 ) );
1358 zone->SetLayer( klayer );
1359 zone->Outline()->Append( contour );
1364 else if( row.
type == wxS(
"STRING" ) )
1368 else if( row.
type == wxS(
"COMPONENT" ) )
1376 nlohmann::json attrsObj = row.
inner.value(
"attrs", nlohmann::json::object() );
1378 if( attrsObj.is_object() )
1380 for(
auto& [k, v] : attrsObj.items() )
1384 componentMap[row.
id] =
comp;
1386 else if( row.
type == wxS(
"ATTR" ) )
1411 attrMap.emplace( attr.parentId, attr );
1413 else if( row.
type == wxS(
"PAD_NET" ) )
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();
1422 if( !pn.compId.IsEmpty() )
1423 padNetMap.emplace( pn.compId, pn );
1425 else if( row.
type == wxS(
"PAD" ) )
1429 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
1441 footprint->SetPosition( pos );
1442 footprint->SetOrientation( orient );
1444 wxString fpName = wxS(
"Pad_" ) + row.
id;
1447 footprint->SetFPID( fpID );
1448 footprint->Reference().SetVisible(
true );
1449 footprint->Value().SetVisible(
true );
1450 footprint->AutoPositionFields();
1454 else if( row.
type == wxS(
"IMAGE" ) )
1469 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1472 std::vector<SHAPE_LINE_CHAIN> contours;
1473 for( nlohmann::json& polyData : polyDataList )
1478 contours.push_back( contour );
1493 for(
int i = 0; i < contour.PointCount(); i++ )
1504 std::unique_ptr<PCB_GROUP>
group;
1507 group = std::make_unique<PCB_GROUP>( aBoard );
1513 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aBoard,
SHAPE_T::POLY );
1515 shape->SetFilled(
true );
1516 shape->SetPolyShape( poly );
1517 shape->SetLayer( klayer );
1518 shape->SetWidth( 0 );
1530 group->AddItem( shape.get() );
1538 else if( row.
type == wxS(
"OBJ" ) )
1555 if( imageUrl.empty() )
1558 wxString mimeType, base64Data;
1560 if( imageUrl.BeforeFirst(
':' ) == wxS(
"blob" ) )
1562 wxString objectId = imageUrl.AfterLast(
':' );
1564 if(
auto blob =
get_opt( aBlobMap, objectId ) )
1566 wxString blobUrl = blob->url;
1568 if( blobUrl.BeforeFirst(
':' ) == wxS(
"data" ) )
1570 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst(
':' ).BeforeFirst(
',' ),
';',
'\0' );
1572 base64Data = blobUrl.AfterFirst(
',' );
1574 if( paramsArr.size() > 0 )
1575 mimeType = paramsArr[0];
1583 if( mimeType.empty() || base64Data.empty() )
1586 wxMemoryBuffer buf = wxBase64Decode( base64Data );
1588 if( mimeType == wxS(
"image/svg+xml" ) )
1594 VECTOR2D kcenter = kstart + ksize / 2;
1596 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
1597 std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
1600 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
1612 int x = bitmap->GetPosition().x;
1626 for(
auto const& [compId,
comp] : componentMap )
1629 wxString fpIdOverride;
1630 wxString fpDesignator;
1633 auto attrRange = attrMap.equal_range( compId );
1635 for(
auto it = attrRange.first; it != attrRange.second; ++it )
1637 const ATTR_DATA& attr = it->second;
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;
1649 const std::map<wxString, wxString>& deviceAttrs = deviceData.
attributes;
1651 wxString fpId = fpIdOverride;
1654 fpId =
get_def( deviceAttrs, wxS(
"Footprint" ), wxEmptyString );
1658 wxLogTrace(
traceEasyEdaIo, wxT(
"EasyEDA Pro v3 component '%s' (%s): no footprint mapping, skipping." ),
1659 compId, fpDesignator );
1663 auto it = aFootprintMap.find( fpId );
1665 if( it == aFootprintMap.end() )
1667 wxLogTrace(
traceEasyEdaIo,
"Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
1671 std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
1672 std::unique_ptr<FOOTPRINT> footprint(
static_cast<FOOTPRINT*
>( footprintOrig->Duplicate(
false ) ) );
1674 footprint->SetParent( aBoard );
1678 wxString libItemName = footprint->GetFPID().GetLibItemName();
1680 if( libItemName.empty() )
1683 footprint->SetFPID(
ToKiCadLibID( aFpLibName, libItemName ) );
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 );
1689 m_v2Parser.FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
1693 if( !valueText.empty() )
1694 footprint->SetValue( valueText );
1698 if( description.empty() )
1699 description =
get_def( deviceAttrs, wxS(
"Description" ), wxEmptyString );
1701 if( !description.empty() )
1708 [&](
const wxString& attrKey,
const wxString& value )
1712 if( attrKey == wxS(
"Datasheet" ) )
1715 field = footprint->GetField( attrKey );
1730 if( klayer ==
B_Cu )
1733 footprint->SetOrientationDegrees(
comp.angle );
1737 for(
auto attrIt = attrRange.first; attrIt != attrRange.second; ++attrIt )
1739 const ATTR_DATA& attr = attrIt->second;
1741 if( attr.key == wxS(
"Designator" ) )
1745 if( attr.fontName != wxS(
"default" ) )
1748 if( attr.valVisible && attr.keyVisible )
1749 field->
SetText( attr.key +
':' + attr.value );
1750 else if( attr.keyVisible )
1755 field->
SetVisible( attr.keyVisible || attr.valVisible );
1778 auto padNetRange = padNetMap.equal_range( compId );
1780 for(
auto pnIt = padNetRange.first; pnIt != padNetRange.second; ++pnIt )
1782 const PAD_NET_DATA& pn = pnIt->second;
1784 for(
PAD*
pad : footprint->Pads() )
1786 if(
pad->GetNumber() == pn.padNumber )
1797 for(
auto& [uuid, zone] : poursToFill )
1802 auto range = boardPouredMap.equal_range( uuid );
1804 for(
auto& pIt = range.first; pIt != range.second; ++pIt )
1810 for(
int dataId = 0; dataId < static_cast<int>( poured.
polyData.size() ); dataId++ )
1812 const nlohmann::json& fillData = poured.
polyData[dataId];
1813 const double ptScale = 10;
1818 for(
int i = 0; i < contour.
PointCount(); i++ )
1831 thisPoly.
Append( simple );
1840 const int thermalWidth =
pcbIUScale.mmToIU( 0.2 );
1842 for(
int segId = 0; segId < contour.
SegmentCount(); segId++ )
1852 fillPolySet.
Append( thisPoly );
1859 const int strokeWidth =
pcbIUScale.MilsToIU( 8 );
1866 zone->SetFilledPolysList( zone->GetFirstLayer(), fillPolySet );
1867 zone->SetNeedRefill(
false );
1868 zone->SetIsFilled(
true );
1874 std::vector<PCB_SHAPE*> shapes;
1882 shapes.push_back(
static_cast<PCB_SHAPE*
>( item ) );
1897 offset.
x =
KiROUND( offset.
x / alignGrid ) * alignGrid;
1898 offset.
y =
KiROUND( offset.
y / alignGrid ) * alignGrid;
1900 aBoard->
Move( offset );