389 std::unique_ptr<FOOTPRINT> footprintPtr = std::make_unique<FOOTPRINT>(
m_board );
390 FOOTPRINT* footprint = footprintPtr.get();
393 const int defaultTextThickness(
pcbIUScale.mmToIU( 0.15 ) );
398 field->SetTextSize( defaultTextSize );
399 field->SetTextThickness( defaultTextThickness );
404 if( row.
type == wxS(
"CANVAS" ) )
409 else if( row.
type == wxS(
"POLY" ) )
415 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
417 std::vector<std::unique_ptr<PCB_SHAPE>> results =
m_v2Parser.ParsePoly( footprint, polyData,
false,
false );
419 for(
auto& shape : results )
421 shape->SetLayer( klayer );
426 else if( row.
type == wxS(
"PAD" ) )
431 else if( row.
type == wxS(
"FILL" ) )
436 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
438 if( !polyDataList.is_null() && !polyDataList.empty() )
440 if( !polyDataList.at( 0 ).is_array() )
441 polyDataList = nlohmann::json::array( { polyDataList } );
443 std::vector<SHAPE_LINE_CHAIN> contours;
445 for( nlohmann::json& polyData : polyDataList )
449 contours.push_back( contour );
459 std::unique_ptr<PCB_GROUP>
group;
462 group = std::make_unique<PCB_GROUP>( footprint );
466 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::POLY );
468 shape->SetFilled(
true );
469 shape->SetPolyShape( poly );
470 shape->SetLayer( klayer );
471 shape->SetWidth( 0 );
474 group->AddItem( shape.get() );
483 else if( row.
type == wxS(
"ATTR" ) )
488 if( key == wxS(
"Designator" ) )
491 else if( row.
type == wxS(
"REGION" ) )
496 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
497 nlohmann::json prohibitTypes = row.
inner.value(
"prohibitType", nlohmann::json::array() );
501 if( prohibitTypes.is_array() )
503 for(
const auto& pt : prohibitTypes )
508 ptStr = pt.get<std::string>();
510 if( ptStr == wxS(
"COMPONENT" ) )
512 else if( ptStr == wxS(
"TRACK" ) )
514 else if( ptStr == wxS(
"COPPER" ) )
516 else if( ptStr == wxS(
"VIA" ) )
518 else if( ptStr == wxS(
"FILL" ) || ptStr == wxS(
"PLANE" ) )
523 for( nlohmann::json& polyData : polyDataList )
527 std::vector<std::unique_ptr<PCB_SHAPE>> results =
528 m_v2Parser.ParsePoly(
nullptr, polyData,
true,
false );
530 for(
auto& shape : results )
532 shape->SetFilled(
true );
538 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( footprint );
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 ) );
547 zone->SetLayer( klayer );
548 zone->Outline()->Append( polySet );
553 else if( row.
type == wxS(
"IMAGE" ) )
568 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
571 std::vector<SHAPE_LINE_CHAIN> contours;
572 for( nlohmann::json& polyData : polyDataList )
577 contours.push_back( contour );
592 for(
int i = 0; i < contour.PointCount(); i++ )
603 std::unique_ptr<PCB_GROUP>
group;
606 group = std::make_unique<PCB_GROUP>( footprint );
612 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::POLY );
614 shape->SetFilled(
true );
615 shape->SetPolyShape( poly );
616 shape->SetLayer( klayer );
617 shape->SetWidth( 0 );
629 group->AddItem( shape.get() );
637 else if( row.
type == wxS(
"OBJ" ) )
654 if( imageUrl.empty() )
657 wxString mimeType, base64Data;
659 if( imageUrl.BeforeFirst(
':' ) == wxS(
"blob" ) )
661 wxString objectId = imageUrl.AfterLast(
':' );
663 if(
auto blob =
get_opt( aBlobMap, objectId ) )
665 wxString blobUrl = blob->url;
667 if( blobUrl.BeforeFirst(
':' ) == wxS(
"data" ) )
669 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst(
':' ).BeforeFirst(
',' ),
';',
'\0' );
671 base64Data = blobUrl.AfterFirst(
',' );
673 if( paramsArr.size() > 0 )
674 mimeType = paramsArr[0];
682 if( mimeType.empty() || base64Data.empty() )
685 wxMemoryBuffer buf = wxBase64Decode( base64Data );
687 if( mimeType == wxS(
"image/svg+xml" ) )
693 VECTOR2D kcenter = kstart + ksize / 2;
695 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
696 std::make_unique<PCB_REFERENCE_IMAGE>( footprint, kcenter, klayer );
699 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
711 int x = bitmap->GetPosition().x;
722 else if( row.
type == wxS(
"STRING" ) )
733 std::vector<PCB_SHAPE*> edgeShapes;
738 edgeShapes.push_back(
static_cast<PCB_SHAPE*
>( item ) );
749 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( footprint,
SHAPE_T::RECTANGLE );
754 shape->SetEnd( bbox.
GetEnd() );
760 bool hasFabRef =
false;
766 if(
static_cast<PCB_TEXT*
>( item )->GetText() == wxT(
"${REFERENCE}" ) )
777 int c_refTextThickness =
pcbIUScale.mmToIU( 0.1 );
778 std::unique_ptr<PCB_TEXT> refText = std::make_unique<PCB_TEXT>( footprint );
780 refText->SetLayer(
F_Fab );
781 refText->SetTextSize(
VECTOR2I( c_refTextSize, c_refTextSize ) );
782 refText->SetTextThickness( c_refTextThickness );
783 refText->SetText( wxT(
"${REFERENCE}" ) );
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 )
875 std::map<wxString, wxString> attrs;
887 bool keyVisible =
false;
888 bool valVisible =
false;
890 double fontSize = 45.0;
891 double strokeWidth = 6.0;
905 std::map<wxString, COMP_DATA> componentMap;
906 std::multimap<wxString, ATTR_DATA> attrMap;
907 std::multimap<wxString, PAD_NET_DATA> padNetMap;
909 std::multimap<wxString, EASYEDAPRO::POURED> boardPouredMap = aPouredMap;
910 std::map<wxString, ZONE*> poursToFill;
922 if( !idArr.is_array() || idArr.size() < 3 ||
V3JsonToString( idArr[1] ) != wxS(
"SAFE" ) )
925 nlohmann::json safeSpacing =
926 row.
inner.value(
"ruleContext", nlohmann::json() ).value(
"safeSpacing", nlohmann::json::array() );
928 if( safeSpacing.is_array() && !safeSpacing.empty() )
931 safeSpacing[0].value(
"content", nlohmann::json::array() ) );
956 if( row.
type == wxS(
"LAYER" ) )
964 if(
id.is_array() &&
id.size() > 1 )
969 if( layerId.ToLong( &parsedLayer ) )
985 blayers.
set( klayer );
988 if( !layerName.IsEmpty() )
992 else if( row.
type == wxS(
"NET" ) )
997 if( idArr.is_array() && idArr.size() > 1 )
1000 if( !netname.IsEmpty() )
1005 else if( row.
type == wxS(
"RULE" ) )
1009 if( !idArr.is_array() || idArr.size() < 3 )
1013 bool isDefault =
V3GetString( row.
inner,
"ruleState" ) == wxS(
"DEFAULT" );
1015 nlohmann::json context = row.
inner.value(
"ruleContext", nlohmann::json() );
1017 if( ruleType == wxS(
"TRACK" ) && isDefault )
1019 nlohmann::json track = context.value(
"track", nlohmann::json() );
1020 nlohmann::json content = track.value(
"content", nlohmann::json::array() );
1022 if( content.is_array() && !content.empty() )
1024 double minVal =
V3GetDouble( content[0],
"stroMin" );
1029 else if( ruleType == wxS(
"SAFE" ) && isDefault )
1031 nlohmann::json safeSpacing = context.value(
"safeSpacing", nlohmann::json::array() );
1033 if( safeSpacing.is_array() && !safeSpacing.empty() )
1036 safeSpacing[0].value(
"content", nlohmann::json::array() ) );
1045 if( content.is_array() && !content.empty() && content.at( 0 ).is_array() && !content.at( 0 ).empty()
1046 && content.at( 0 ).at( 0 ).is_number() )
1048 double trackClearance = content.at( 0 ).at( 0 );
1050 if( trackClearance > 0 )
1059 double minEdgeClearance = 0;
1061 if( content.size() > 11 && content.at( 11 ).is_array() )
1063 for(
const nlohmann::json& value : content.at( 11 ) )
1065 if( value.is_number() && value.get<
double>() > 0
1066 && ( minEdgeClearance == 0 || value.get<
double>() < minEdgeClearance ) )
1068 minEdgeClearance = value;
1073 if( minEdgeClearance > 0 )
1081 else if( row.
type == wxS(
"VIA" ) )
1091 std::unique_ptr<PCB_VIA>
via = std::make_unique<PCB_VIA>( aBoard );
1101 else if( row.
type == wxS(
"LINE" ) )
1117 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( aBoard );
1119 track->SetLayer( klayer );
1123 track->SetNet( aBoard->
FindNet( netname ) );
1127 else if( row.
type == wxS(
"ARC" ) )
1147 double ha = angle / 2;
1148 double hd =
delta.EuclideanNorm() / 2;
1149 double cdist = hd / tan(
DEG2RAD( ha ) );
1157 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( aBoard, &sarc );
1159 arc->SetLayer( klayer );
1160 arc->SetNet( aBoard->
FindNet( netname ) );
1164 else if( row.
type == wxS(
"POLY" ) )
1170 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
1172 std::vector<std::unique_ptr<PCB_SHAPE>> results =
m_v2Parser.ParsePoly( aBoard, polyData,
false,
false );
1174 for(
auto& shape : results )
1176 shape->SetLayer( klayer );
1182 else if( row.
type == wxS(
"FILL" ) )
1189 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1191 if( polyDataList.is_null() || polyDataList.empty() )
1194 if( !polyDataList.at( 0 ).is_array() )
1195 polyDataList = nlohmann::json::array( { polyDataList } );
1197 std::vector<SHAPE_LINE_CHAIN> contours;
1199 for( nlohmann::json& polyData : polyDataList )
1203 contours.push_back( contour );
1214 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1216 zone->SetNet( aBoard->
FindNet( netname ) );
1217 zone->SetLayer( klayer );
1219 zone->SetFilledPolysList( klayer, zoneFillPoly );
1220 zone->SetAssignedPriority( 500 );
1221 zone->SetIsFilled(
true );
1222 zone->SetNeedRefill(
false );
1224 zone->SetLocalClearance( zoneClearance() );
1225 zone->SetMinThickness( zoneMinThickness );
1229 else if( row.
type == wxS(
"POUR" ) )
1237 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1239 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
1241 zone->SetNet( aBoard->
FindNet( netname ) );
1242 zone->SetLayer( klayer );
1243 zone->SetAssignedPriority( 500 - fillOrder );
1244 zone->SetLocalClearance( zoneClearance() );
1245 zone->SetMinThickness( zoneMinThickness );
1247 for( nlohmann::json& polyData : polyDataList )
1251 zone->Outline()->Append( contour );
1254 wxASSERT( zone->Outline()->OutlineCount() == 1 );
1256 poursToFill.emplace( row.
id, zone.get() );
1260 else if( row.
type == wxS(
"TEARDROP" ) )
1266 nlohmann::json polyData = row.
inner.value(
"path", nlohmann::json::array() );
1271 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
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 );
1286 else if( row.
type == wxS(
"POURED" ) )
1291 if( idArr.is_array() && idArr.size() > 1 )
1294 nlohmann::json fills = row.
inner.value(
"pourFill", nlohmann::json::array() );
1296 if( !fills.is_array() )
1299 for(
const nlohmann::json& fill : fills )
1306 poured.
polyData = fill.value(
"path", nlohmann::json::array() );
1308 boardPouredMap.emplace( poured.
parentId, poured );
1311 else if( row.
type == wxS(
"REGION" ) )
1316 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1317 nlohmann::json prohibitTypes = row.
inner.value(
"prohibitType", nlohmann::json::array() );
1319 std::set<int> flags;
1321 if( prohibitTypes.is_array() )
1323 for(
const auto& pt : prohibitTypes )
1327 if( pt.is_string() )
1328 ptStr = pt.get<std::string>();
1330 if( ptStr == wxS(
"COMPONENT" ) )
1332 else if( ptStr == wxS(
"TRACK" ) )
1334 else if( ptStr == wxS(
"COPPER" ) )
1336 else if( ptStr == wxS(
"VIA" ) )
1338 else if( ptStr == wxS(
"FILL" ) || ptStr == wxS(
"PLANE" ) )
1343 for( nlohmann::json& polyData : polyDataList )
1348 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aBoard );
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 ) );
1357 zone->SetLayer( klayer );
1358 zone->Outline()->Append( contour );
1363 else if( row.
type == wxS(
"STRING" ) )
1367 else if( row.
type == wxS(
"COMPONENT" ) )
1375 nlohmann::json attrsObj = row.
inner.value(
"attrs", nlohmann::json::object() );
1377 if( attrsObj.is_object() )
1379 for(
auto& [k, v] : attrsObj.items() )
1383 componentMap[row.
id] =
comp;
1385 else if( row.
type == wxS(
"ATTR" ) )
1410 attrMap.emplace( attr.parentId, attr );
1412 else if( row.
type == wxS(
"PAD_NET" ) )
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();
1421 if( !pn.compId.IsEmpty() )
1422 padNetMap.emplace( pn.compId, pn );
1424 else if( row.
type == wxS(
"PAD" ) )
1428 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( aBoard );
1440 footprint->SetPosition( pos );
1441 footprint->SetOrientation( orient );
1443 wxString fpName = wxS(
"Pad_" ) + row.
id;
1446 footprint->SetFPID( fpID );
1447 footprint->Reference().SetVisible(
true );
1448 footprint->Value().SetVisible(
true );
1449 footprint->AutoPositionFields();
1453 else if( row.
type == wxS(
"IMAGE" ) )
1468 nlohmann::json polyDataList = row.
inner.value(
"path", nlohmann::json::array() );
1471 std::vector<SHAPE_LINE_CHAIN> contours;
1472 for( nlohmann::json& polyData : polyDataList )
1477 contours.push_back( contour );
1492 for(
int i = 0; i < contour.PointCount(); i++ )
1503 std::unique_ptr<PCB_GROUP>
group;
1506 group = std::make_unique<PCB_GROUP>( aBoard );
1512 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aBoard,
SHAPE_T::POLY );
1514 shape->SetFilled(
true );
1515 shape->SetPolyShape( poly );
1516 shape->SetLayer( klayer );
1517 shape->SetWidth( 0 );
1529 group->AddItem( shape.get() );
1537 else if( row.
type == wxS(
"OBJ" ) )
1554 if( imageUrl.empty() )
1557 wxString mimeType, base64Data;
1559 if( imageUrl.BeforeFirst(
':' ) == wxS(
"blob" ) )
1561 wxString objectId = imageUrl.AfterLast(
':' );
1563 if(
auto blob =
get_opt( aBlobMap, objectId ) )
1565 wxString blobUrl = blob->url;
1567 if( blobUrl.BeforeFirst(
':' ) == wxS(
"data" ) )
1569 wxArrayString paramsArr = wxSplit( blobUrl.AfterFirst(
':' ).BeforeFirst(
',' ),
';',
'\0' );
1571 base64Data = blobUrl.AfterFirst(
',' );
1573 if( paramsArr.size() > 0 )
1574 mimeType = paramsArr[0];
1582 if( mimeType.empty() || base64Data.empty() )
1585 wxMemoryBuffer buf = wxBase64Decode( base64Data );
1587 if( mimeType == wxS(
"image/svg+xml" ) )
1593 VECTOR2D kcenter = kstart + ksize / 2;
1595 std::unique_ptr<PCB_REFERENCE_IMAGE> bitmap =
1596 std::make_unique<PCB_REFERENCE_IMAGE>( aBoard, kcenter, klayer );
1599 wxImage::SetDefaultLoadFlags( wxImage::GetDefaultLoadFlags() & ~wxImage::Load_Verbose );
1611 int x = bitmap->GetPosition().x;
1625 for(
auto const& [compId,
comp] : componentMap )
1628 wxString fpIdOverride;
1629 wxString fpDesignator;
1632 auto attrRange = attrMap.equal_range( compId );
1634 for(
auto it = attrRange.first; it != attrRange.second; ++it )
1636 const ATTR_DATA& attr = it->second;
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;
1648 const std::map<wxString, wxString>& deviceAttrs = deviceData.
attributes;
1650 wxString fpId = fpIdOverride;
1653 fpId =
get_def( deviceAttrs, wxS(
"Footprint" ), wxEmptyString );
1657 wxLogTrace(
traceEasyEdaIo, wxT(
"EasyEDA Pro v3 component '%s' (%s): no footprint mapping, skipping." ),
1658 compId, fpDesignator );
1662 auto it = aFootprintMap.find( fpId );
1664 if( it == aFootprintMap.end() )
1666 wxLogTrace(
traceEasyEdaIo,
"Footprint of '%s' with uuid '%s' not found.", fpDesignator, fpId );
1670 std::unique_ptr<FOOTPRINT>& footprintOrig = it->second;
1671 std::unique_ptr<FOOTPRINT> footprint(
static_cast<FOOTPRINT*
>( footprintOrig->Duplicate(
false ) ) );
1673 footprint->SetParent( aBoard );
1677 wxString libItemName = footprint->GetFPID().GetLibItemName();
1679 if( libItemName.empty() )
1682 footprint->SetFPID(
ToKiCadLibID( aFpLibName, libItemName ) );
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 );
1688 m_v2Parser.FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
1692 if( !valueText.empty() )
1693 footprint->SetValue( valueText );
1697 if( description.empty() )
1698 description =
get_def( deviceAttrs, wxS(
"Description" ), wxEmptyString );
1700 if( !description.empty() )
1707 [&](
const wxString& attrKey,
const wxString& value )
1711 if( attrKey == wxS(
"Datasheet" ) )
1714 field = footprint->GetField( attrKey );
1729 if( klayer ==
B_Cu )
1732 footprint->SetOrientationDegrees(
comp.angle );
1736 for(
auto attrIt = attrRange.first; attrIt != attrRange.second; ++attrIt )
1738 const ATTR_DATA& attr = attrIt->second;
1740 if( attr.key == wxS(
"Designator" ) )
1744 if( attr.fontName != wxS(
"default" ) )
1747 if( attr.valVisible && attr.keyVisible )
1748 field->
SetText( attr.key +
':' + attr.value );
1749 else if( attr.keyVisible )
1754 field->
SetVisible( attr.keyVisible || attr.valVisible );
1777 auto padNetRange = padNetMap.equal_range( compId );
1779 for(
auto pnIt = padNetRange.first; pnIt != padNetRange.second; ++pnIt )
1781 const PAD_NET_DATA& pn = pnIt->second;
1783 for(
PAD*
pad : footprint->Pads() )
1785 if(
pad->GetNumber() == pn.padNumber )
1796 for(
auto& [uuid, zone] : poursToFill )
1801 auto range = boardPouredMap.equal_range( uuid );
1803 for(
auto& pIt = range.first; pIt != range.second; ++pIt )
1809 for(
int dataId = 0; dataId < static_cast<int>( poured.
polyData.size() ); dataId++ )
1811 const nlohmann::json& fillData = poured.
polyData[dataId];
1812 const double ptScale = 10;
1817 for(
int i = 0; i < contour.
PointCount(); i++ )
1830 thisPoly.
Append( simple );
1839 const int thermalWidth =
pcbIUScale.mmToIU( 0.2 );
1841 for(
int segId = 0; segId < contour.
SegmentCount(); segId++ )
1851 fillPolySet.
Append( thisPoly );
1858 const int strokeWidth =
pcbIUScale.MilsToIU( 8 );
1865 zone->SetFilledPolysList( zone->GetFirstLayer(), fillPolySet );
1866 zone->SetNeedRefill(
false );
1867 zone->SetIsFilled(
true );
1873 std::vector<PCB_SHAPE*> shapes;
1881 shapes.push_back(
static_cast<PCB_SHAPE*
>( item ) );
1896 offset.
x =
KiROUND( offset.
x / alignGrid ) * alignGrid;
1897 offset.
y =
KiROUND( offset.
y / alignGrid ) * alignGrid;
1899 aBoard->
Move( offset );