31#include <unordered_map>
32#include <unordered_set>
68 all.insert( all.end(), footprint->Constraints().begin(), footprint->Constraints().end() );
82 return std::ranges::any_of( aBoard->
Footprints(),
84 { return !aFootprint->Constraints().empty(); } );
90 std::vector<VECTOR2I> positions;
102 if( !aItems.contains( member.m_item ) )
107 if( pos && std::ranges::find( positions, *pos ) == positions.end() )
108 positions.push_back( *pos );
126 return parent && parent->
IsLocked();
134 if( aOnBoard && !aOnBoard->contains( aId ) )
145static std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>>
147 std::vector<KIID>* aErrored =
nullptr,
const std::unordered_set<KIID>* aOnBoard =
nullptr )
149 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> map;
153 bool errored = constraint->GetMembers().empty();
157 map[member.m_item].push_back( constraint );
165 if( errored && aErrored )
166 aErrored->push_back( constraint->m_Uuid );
176 const std::unordered_map<
KIID, std::vector<PCB_CONSTRAINT*>>& aMap,
const KIID& aSeed,
177 std::unordered_set<KIID>& aClusterShapes, std::vector<PCB_CONSTRAINT*>& aClusterConstraints,
178 std::set<KIID>* aVisited =
nullptr )
181 std::vector<KIID> frontier{ aSeed };
182 aClusterShapes.insert( aSeed );
184 while( !frontier.empty() )
186 KIID shapeId = frontier.back();
190 aVisited->insert( shapeId );
192 auto it = aMap.find( shapeId );
194 if( it == aMap.end() )
199 if( used.insert( constraint->
m_Uuid ).second )
200 aClusterConstraints.push_back( constraint );
204 if( aClusterShapes.insert( member.m_item ).second )
205 frontier.push_back( member.m_item );
217 const std::unordered_set<KIID>* aOnBoard )
219 std::vector<T*> items;
221 for(
const KIID&
id : aIds )
223 if(
T* item =
dynamic_cast<T*
>(
resolveMember( aBoard, aOnBoard,
id ) ) )
224 items.push_back( item );
232 const std::unordered_set<KIID>* aOnBoard =
nullptr )
239 const std::unordered_set<KIID>& aIds,
240 const std::unordered_set<KIID>* aOnBoard =
nullptr )
260 return std::hypot( *aLine.p2.x - *aLine.p1.x, *aLine.p2.y - *aLine.p1.y ) < 1e-9;
266 const double ax[2] = { *aL1.p1.x, *aL1.p2.x };
267 const double ay[2] = { *aL1.p1.y, *aL1.p2.y };
268 const double bx[2] = { *aL2.p1.x, *aL2.p2.x };
269 const double by[2] = { *aL2.p1.y, *aL2.p2.y };
272 double best = std::numeric_limits<double>::max();
274 for(
int i = 0; i < 2; ++i )
276 for(
int j = 0; j < 2; ++j )
278 double dist = std::hypot( ax[i] - bx[j], ay[i] - by[j] );
289 double d1x = ax[1] - ax[0], d1y = ay[1] - ay[0];
290 double d2x = bx[1] - bx[0], d2y = by[1] - by[0];
291 double theta = std::atan2( d1x * d2y - d1y * d2x, d1x * d2x + d1y * d2y );
295 double alpha =
std::abs( std::remainder( aCornerDeg *
M_PI / 180.0, 2.0 *
M_PI ) );
296 double shift = ( vB -
vA ) *
M_PI;
297 double c1 = alpha - shift;
298 double c2 = -alpha - shift;
300 double d1 =
std::abs( std::remainder( theta - c1, 2.0 *
M_PI ) );
301 double d2 =
std::abs( std::remainder( theta - c2, 2.0 *
M_PI ) );
303 return d1 <= d2 ? c1 : c2;
313static double arcSweepTarget(
double aStartAngle,
double aEndAngle,
double aSweepDeg )
315 double current = aEndAngle - aStartAngle;
318 return current - std::remainder( current - alpha, 2.0 *
M_PI );
336 return m_system.AddParameter( aValue );
359 if( aConstraint && !aConstraint->
IsDriving() )
371 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
373 if( members.empty() )
376 auto it =
m_shapeVars.find( members.front().m_item );
381 const PCB_SHAPE* shape = it->second.shape;
382 std::optional<double> value;
387 switch( constraint->GetConstraintType() )
393 if( members.size() == 2 )
395 auto anchorPos = [&](
const CONSTRAINT_MEMBER& aMember ) -> std::optional<VECTOR2I>
406 std::optional<VECTOR2I> pa = anchorPos( members[0] );
407 std::optional<VECTOR2I> pb = anchorPos( members[1] );
420 value = ( *pb - *pa ).EuclideanNorm();
437 if( members.size() != 2 )
440 auto other =
m_shapeVars.find( members[1].m_item );
445 const PCB_SHAPE* shapeB = other->second.shape;
465 std::optional<double> current = constraint->GetValue();
467 if( current &&
std::abs( *value - *current ) <= tol )
471 aBeforeWrite( constraint );
473 constraint->SetValue( value );
512 case 0:
return { leftX, topY };
513 case 1:
return { rightX, topY };
514 case 2:
return { rightX, botY };
515 default:
return { leftX, botY };
553 if( aMembers.size() != 2 || aMembers[0].m_item != aMembers[1].m_item )
578 const std::vector<PCB_CONSTRAINT*>& aConstraints,
579 const std::set<KIID>* aFixedShapes,
580 const std::vector<PCB_DIMENSION_BASE*>& aDimensions )
597 if( aShapes.empty() && aDimensions.empty() )
602 VECTOR2I origin = !aShapes.empty() ? aShapes.front()->GetStart() : aDimensions.front()->GetStart();
609 std::vector<std::pair<int, int>> lockedRanges;
612 std::vector<int> ellipseOffsetParams;
618 int firstParam =
static_cast<int>(
m_params.size() );
633 vars.
startIsLeft = shape->GetStart().x <= shape->GetEnd().x;
634 vars.
startIsTop = shape->GetStart().y <= shape->GetEnd().y;
654 for(
int i = 1; i < outline.
PointCount(); ++i )
709 m_gcs->addConstraintArcRules( arc );
716 double major = shape->GetEllipseMajorRadius();
717 double minor = shape->GetEllipseMinorRadius();
718 double phi = shape->GetEllipseRotation().AsRadians();
721 double focal = major > minor ? std::sqrt( major * major - minor * minor ) : 0.0;
735 ellipseOffsetParams.push_back( offX );
736 ellipseOffsetParams.push_back( offY );
753 GCS::ArcOfEllipse arc;
761 m_gcs->addConstraintArcOfEllipseRules( arc );
772 lockedRanges.emplace_back( firstParam,
static_cast<int>(
m_params.size() ) );
789 switch( dimension->Type() )
807 std::set<int> fixedParams;
809 for(
const auto& [first, last] : lockedRanges )
811 for(
int i = first; i < last; ++i )
812 fixedParams.insert( i );
815 for(
int i : ellipseOffsetParams )
816 fixedParams.insert( i );
818 auto pointAt = [&](
int aXIndex ) -> GCS::Point
825 auto pointFor = [&](
const ANCHOR_PARAMS& aParams ) -> GCS::Point
840 aLine.p1 = pointAt( it->second.startX );
841 aLine.p2 = pointAt( it->second.endX );
847 auto circleFor = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Circle& aCircle ) ->
bool
857 aCircle.center = pointAt( it->second.startX );
858 aCircle.rad = &
m_params[it->second.radius];
864 auto ellipseFor = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Ellipse& aEllipse ) ->
bool
874 aEllipse.center = pointAt( it->second.startX );
875 aEllipse.focus1 = pointAt( it->second.focusX );
876 aEllipse.radmin = &
m_params[it->second.radius];
881 auto pushConstant = [&](
double aIU ) ->
int
884 fixedParams.insert( idx );
892 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
895 switch( constraint->GetConstraintType() )
901 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
903 m_gcs->addConstraintParallel( l1, l2, tag );
914 if( members.size() == 1 )
918 if( lineFor( members[0], l ) )
920 m_gcs->addConstraintHorizontal( l, tag );
924 else if( members.size() == 2 )
931 GCS::Point p1 = pointFor( a );
932 GCS::Point p2 = pointFor( b );
933 m_gcs->addConstraintHorizontal( p1, p2, tag );
943 if( members.size() == 1 )
947 if( lineFor( members[0], l ) )
949 m_gcs->addConstraintVertical( l, tag );
953 else if( members.size() == 2 )
960 GCS::Point p1 = pointFor( a );
961 GCS::Point p2 = pointFor( b );
962 m_gcs->addConstraintVertical( p1, p2, tag );
977 GCS::Point p1 = pointFor( a );
978 GCS::Point p2 = pointFor( b );
979 m_gcs->addConstraintP2PCoincident( p1, p2, tag );
992 fixedParams.insert( a.
x );
993 fixedParams.insert( a.
y );
1004 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
1006 m_gcs->addConstraintPerpendicular( l1, l2, tag );
1017 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
1019 m_gcs->addConstraintEqualLength( l1, l2, tag );
1033 if( p.
IsValid() && lineFor( members[1], l ) )
1035 GCS::Point point = pointFor( p );
1036 m_gcs->addConstraintPointOnLine( point, l, tag );
1039 else if( p.
IsValid() && circleFor( members[1], circ ) )
1042 GCS::Point point = pointFor( p );
1043 m_gcs->addConstraintPointOnCircle( point, circ, tag );
1046 else if( p.
IsValid() && ellipseFor( members[1], ell ) )
1048 GCS::Point point = pointFor( p );
1049 m_gcs->addConstraintPointOnEllipse( point, ell, tag );
1062 if( p.
IsValid() && lineFor( members[1], seg ) )
1064 GCS::Point mid = pointFor( p );
1065 m_gcs->addConstraintP2PSymmetric( seg.p1, seg.p2, mid, tag );
1077 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
1079 m_gcs->addConstraintPointOnLine( l2.p1, l1, tag );
1080 m_gcs->addConstraintPointOnLine( l2.p2, l1, tag );
1095 GCS::Point pa = pointFor( a );
1096 GCS::Point pb = pointFor( b );
1097 m_gcs->addConstraintP2PSymmetric( pa, pb, axis, tag );
1108 if( members.size() == 1 && constraint->HasValue() && lineFor( members[0], l ) )
1110 int len = pushConstant( *constraint->GetValue() );
1111 m_gcs->addConstraintP2PDistance( l.p1, l.p2, &
m_params[len], tag, constraint->IsDriving() );
1114 if( constraint->IsDriving() )
1117 it->second.fixedLengthParam = len;
1124 else if( members.size() == 2 && constraint->HasValue() )
1138 int aAxis = horiz ? a.
x : a.
y;
1139 int bAxis = horiz ? b.
x : b.
y;
1141 double sign = gap >= 0.0 ? 1.0 : -1.0;
1142 int len = pushConstant(
sign * *constraint->GetValue() );
1145 &
m_params[len], tag, constraint->IsDriving() );
1149 GCS::Point pa = pointFor( a );
1150 GCS::Point pb = pointFor( b );
1151 int len = pushConstant( *constraint->GetValue() );
1152 m_gcs->addConstraintP2PDistance( pa, pb, &
m_params[len], tag,
1153 constraint->IsDriving() );
1168 if( members.size() == 1 && constraint->HasValue() && circleFor( members[0], c ) )
1170 int rad = pushConstant( *constraint->GetValue() );
1171 m_gcs->addConstraintCircleRadius( c, &
m_params[rad], tag, constraint->IsDriving() );
1183 if( members.size() == 2 && circleFor( members[0], c1 ) && circleFor( members[1], c2 ) )
1185 m_gcs->addConstraintEqualRadius( c1, c2, tag );
1195 auto centerOf = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Point& aOut ) ->
bool
1200 if( circleFor( aMember, c ) )
1206 if( ellipseFor( aMember, e ) )
1217 if( members.size() == 2 && centerOf( members[0], p1 ) && centerOf( members[1], p2 ) )
1219 m_gcs->addConstraintP2PCoincident( p1, p2, tag );
1230 if( members.size() == 2 && constraint->HasValue() && lineFor( members[0], l1 )
1234 fixedParams.insert( angle );
1235 m_gcs->addConstraintL2LAngle( l1, l2, &
m_params[angle], tag, constraint->IsDriving() );
1249 bool validSweep = constraint->HasValue() && *constraint->GetValue() > 0.0
1250 && *constraint->GetValue() < 360.0;
1256 *constraint->GetValue() );
1258 fixedParams.insert( tgt );
1260 &
m_params[tgt], tag, constraint->IsDriving() );
1270 if( members.size() != 2 )
1278 int lineIdx = lineFor( members[0], l ) ? 0 : ( lineFor( members[1], l ) ? 1 : -1 );
1284 if( circleFor( other, c1 ) )
1287 double dx = *l.p2.x - *l.p1.x;
1288 double dy = *l.p2.y - *l.p1.y;
1289 double cross = dx * ( *c1.center.y - *l.p1.y ) - dy * ( *c1.center.x - *l.p1.x );
1291 m_gcs->addConstraintTangent( l, c1, cross > 0.0, tag );
1294 else if( ellipseFor( other, ell ) )
1296 m_gcs->addConstraintTangent( l, ell, tag );
1300 else if( circleFor( members[0], c1 ) && circleFor( members[1], c2 ) )
1302 m_gcs->addConstraintTangent( c1, c2, tag );
1326 if( !constraint->IsDriving() )
1334 switch( constraint->GetConstraintType() )
1341 if( constraint->GetMembers().size() != 1 )
1372 std::set<int> referencedDimParams;
1376 if( unmappedConstraints.contains( constraint->m_Uuid ) )
1386 referencedDimParams.insert(
anchor.x );
1398 for(
int pointX : { vars.startX, vars.endX } )
1400 if( pointX >= 0 && (
locked || !referencedDimParams.contains( pointX ) ) )
1402 fixedParams.insert( pointX );
1403 fixedParams.insert( pointX + 1 );
1409 GCS::VEC_pD unknowns;
1411 for(
int i = 0; i < static_cast<int>(
m_params.size() ); ++i )
1413 if( !fixedParams.contains( i ) )
1414 unknowns.push_back( &
m_params[i] );
1418 m_gcs->declareUnknowns( unknowns );
1419 m_gcs->initSolution();
1450 m_gcs->initSolution();
1451 int ret =
m_gcs->solve();
1452 m_gcs->applySolution();
1456 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1477 if( vars.radius < 0 )
1482 c.center = GCS::Point{ &
m_params[vars.startX], &
m_params[vars.startX + 1] };
1485 if( kiid == aResizedShape )
1493 m_gcs->addConstraintCoordinateX( c.center, &
m_params[cx], GCS::DefaultTemporaryConstraint );
1494 m_gcs->addConstraintCoordinateY( c.center, &
m_params[cy], GCS::DefaultTemporaryConstraint );
1499 m_gcs->addConstraintCircleRadius( c, &
m_params[target], GCS::DefaultTemporaryConstraint );
1511 m_gcs->initSolution();
1512 int ret =
m_gcs->solve();
1513 m_gcs->applySolution();
1517 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1525 const std::set<KIID>& aEdited,
1526 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged,
1527 bool aHoldDraggedRigid )
1554 double len = std::hypot( dx, dy );
1566 && it->second.fixedLengthParam >= 0
1572 double dx = targetX -
m_params[farX];
1573 double dy = targetY -
m_params[farX + 1];
1574 double len = std::hypot( dx, dy );
1576 if( len > 1e-9 && segLen > 1e-9 )
1578 targetX =
m_params[farX] + dx * segLen / len;
1579 targetY =
m_params[farX + 1] + dy * segLen / len;
1603 coDragged = &aCoDragged->first;
1627 if( aHoldDraggedRigid )
1633 std::set<KIID> editedShapes = aEdited;
1634 editedShapes.insert( aDragged.
m_item );
1637 editedShapes.insert( coDragged->
m_item );
1643 std::set<KIID> heldPolygons = editedShapes;
1646 heldPolygons.erase( aDragged.
m_item );
1650 m_gcs->initSolution();
1651 int ret =
m_gcs->solve();
1652 m_gcs->applySolution();
1656 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1663 const std::vector<SNAP_CANDIDATE>& aCandidates,
1667 bool addedRelation =
false;
1669 const auto addCoordinateX = [&](
double aCoordinate )
1672 m_gcs->addConstraintCoordinateX(
anchor, &
m_params[target], GCS::DefaultTemporaryConstraint );
1673 addedRelation =
true;
1675 const auto addCoordinateY = [&](
double aCoordinate )
1678 m_gcs->addConstraintCoordinateY(
anchor, &
m_params[target], GCS::DefaultTemporaryConstraint );
1679 addedRelation =
true;
1683 if( aCandidate.direction.SquaredEuclideanNorm() <= 1e-12 )
1686 VECTOR2D direction = aCandidate.direction * ( 1000000.0 / aCandidate.direction.EuclideanNorm() );
1695 m_gcs->addConstraintPointOnLine(
anchor, line, GCS::DefaultTemporaryConstraint );
1696 addedRelation =
true;
1701 switch( candidate.relation )
1706 addCoordinateX( candidate.origin.x );
1707 addCoordinateY( candidate.origin.y );
1718 if( candidate.direction.x != 0.0 )
1719 addCoordinateX( candidate.origin.x );
1720 else if( candidate.direction.y != 0.0 )
1721 addCoordinateY( candidate.origin.y );
1733 if( !candidate.manifold )
1739 if(
const CIRCLE*
circle = std::get_if<CIRCLE>( &*candidate.manifold ) )
1744 else if(
const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &*candidate.manifold ) )
1746 center = arc->GetCenter();
1747 radius = arc->GetRadius();
1761 m_gcs->addConstraintPointOnCircle(
anchor,
circle, GCS::DefaultTemporaryConstraint );
1762 addedRelation =
true;
1768 return addedRelation;
1777 const auto addPoint = [&](
int aPointX )
1780 state.
points.insert( aPointX );
1783 for(
const KIID&
id : aEditedShapes )
1799 addPoint( vars.
endX );
1830 addPoint( vars.
startX + 2 * i );
1854 const std::vector<SNAP_CANDIDATE>& aCandidates,
1874 int xPin =
m_gcs->addConstraintCoordinateX(
anchor, &
m_params[cursorX], GCS::DefaultTemporaryConstraint );
1875 int yPin =
m_gcs->addConstraintCoordinateY(
anchor, &
m_params[cursorY], GCS::DefaultTemporaryConstraint );
1882 m_gcs->initSolution();
1883 int ret =
m_gcs->solve();
1884 m_gcs->applySolution();
1887 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1895 [&](
const auto& aEntry )
1897 return aEditedShapes.contains( aEntry.first )
1907 if( !
m_built || aEditedShapes.empty() )
1912 if( state.
points.empty() )
1915 const bool movesGroundedPoint =
1916 std::ranges::any_of( state.
points,
1919 return m_fixedParams.contains( aPointX ) || m_fixedParams.contains( aPointX + 1 );
1923 if( movesGroundedPoint && aTranslation !=
VECTOR2I() )
1928 std::map<int, VECTOR2I> exactTargets;
1930 for(
int pointX : state.
points )
1944 for(
int pointX : state.
points )
1945 softPinPoint( pointX, GCS::DefaultTemporaryConstraint );
1950 m_gcs->initSolution();
1951 int ret =
m_gcs->solve();
1952 m_gcs->applySolution();
1955 for(
const auto& [pointX, target] : exactTargets )
1959 solved = solved && resolved == target;
1962 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1968 const std::vector<SNAP_CANDIDATE>& aCandidates,
1971 if( !
m_built || aEditedShapes.empty() )
1976 if( state.
points.empty() )
1980 const int anchorX = *state.
points.begin();
1983 const VECTOR2I cursorToAnchor = anchorAtBaseline - aReference;
1984 std::map<int, VECTOR2I> baselinePoints;
1986 for(
int pointX : state.
points )
1991 if( pointX == anchorX )
1997 GCS::DefaultTemporaryConstraint );
1999 GCS::DefaultTemporaryConstraint );
2002 bool addedRelation =
addSnapRelations( { anchorX, anchorX + 1 }, aCandidates, cursorToAnchor );
2008 int xPin =
m_gcs->addConstraintCoordinateX(
anchor, &
m_params[cursorX], GCS::DefaultTemporaryConstraint );
2009 int yPin =
m_gcs->addConstraintCoordinateY(
anchor, &
m_params[cursorY], GCS::DefaultTemporaryConstraint );
2013 m_gcs->initSolution();
2014 int ret =
m_gcs->solve();
2015 m_gcs->applySolution();
2016 bool solved =
solveSucceeded( ret ) && ( addedRelation || aCandidates.empty() );
2020 aResolvedCursor = resolvedAnchor - cursorToAnchor;
2021 VECTOR2I translation = aResolvedCursor - aReference;
2023 for(
const auto& [pointX, baseline] : baselinePoints )
2027 solved = solved && resolved == baseline + translation;
2030 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
2046 if( !verified.
Accepted( candidate.id ) )
2050 return verified.
position == aResolvedCursor;
2059 return std::nullopt;
2068 if( aSolveResult == GCS::Success || aSolveResult == GCS::Converged )
2080 const double residualTol = 1e-3;
2084 double structuralErr =
m_gcs->calculateConstraintErrorByTag( 0 );
2086 if( std::isfinite( structuralErr ) &&
std::abs( structuralErr ) > residualTol )
2095 double err =
m_gcs->calculateConstraintErrorByTag( tag );
2097 if( !std::isfinite( err ) ||
std::abs( err ) > residualTol )
2108 std::set<int> xParams;
2109 std::set<int> yParams;
2110 std::set<int> radiusParams;
2112 const auto addPoint =
2117 xParams.insert( aX );
2118 yParams.insert( aX + 1 );
2124 addPoint( vars.startX );
2125 addPoint( vars.endX );
2126 addPoint( vars.arcStartX );
2127 addPoint( vars.arcEndX );
2128 addPoint( vars.focusX );
2132 for(
int i = 0; i < vars.vertexCount; ++i )
2133 addPoint( vars.startX + 2 * i );
2136 if( vars.radius >= 0 )
2137 radiusParams.insert( vars.radius );
2140 for(
int index : xParams )
2143 for(
int index : yParams )
2146 for(
int index : radiusParams )
2158 wxASSERT( !aWeight || *aWeight > 0 );
2167 int cx =
m_gcs->addConstraintCoordinateX( point, &
m_params[pinX], aTag );
2168 int cy =
m_gcs->addConstraintCoordinateY( point, &
m_params[pinY], aTag );
2172 m_gcs->rescaleConstraint( cx, *aWeight );
2173 m_gcs->rescaleConstraint( cy, *aWeight );
2226 ( aDragged.
m_index + 2 ) % 4 ) ),
2253 auto holdRadius = [&](
const SHAPE_VARS& aVars )
2265 if( aEdited.contains( kiid ) )
2304 for(
int i = 0; i < vars.vertexCount; ++i )
2320 if( !aShapes.contains( kiid ) )
2323 for(
int i = 0; i < vars.vertexCount; ++i )
2337 if( !aShapes.contains( kiid ) )
2345 m_gcs->addConstraintP2PDistance( p1, p2, &
m_params[len], aTag );
2370 if( !aShapes.contains( kiid ) )
2388 if( state.
points.empty() )
2392 const int anchorX = *state.
points.begin();
2394 for(
int pointX : state.
points )
2396 if( pointX == anchorX )
2413 std::vector<PCB_SHAPE*> changed;
2419 auto pointAt = [&](
int aX )
2428 VECTOR2I start = pointAt( vars.startX );
2431 VECTOR2I end = vars.endX >= 0 ? pointAt( vars.endX ) : vars.dimension->GetEnd();
2433 if( start == vars.dimension->GetStart() &&
end == vars.dimension->GetEnd() )
2437 aBeforeWrite( vars.dimension );
2444 vars.dimension->SetStart( start );
2445 vars.dimension->SetEnd(
end );
2450 vars.dimension->Update();
2459 if(
center == vars.shape->GetCenter() &&
radius == vars.shape->GetRadius() )
2463 aBeforeWrite( vars.shape );
2465 vars.shape->SetCenter(
center );
2466 vars.shape->SetRadius(
radius );
2467 changed.push_back( vars.shape );
2479 VECTOR2I start = pointAt( vars.arcStartX );
2482 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd()
2483 &&
center == vars.shape->GetCenter() )
2489 aBeforeWrite( vars.shape );
2494 double sa = std::atan2( start.
y -
center.y, start.
x -
center.x );
2497 if( vars.shape->IsClockwiseArc() )
2507 double midAngle = 0.5 * ( sa + ea );
2511 vars.shape->SetArcGeometry( start, mid,
end );
2512 changed.push_back( vars.shape );
2523 double focal = std::hypot( fx, fy );
2525 double major = std::sqrt( focal * focal + minor * minor );
2530 focal > 1.0 ?
EDA_ANGLE( std::atan2( fy, fx ),
RADIANS_T ) : vars.shape->GetEllipseRotation();
2532 EDA_ANGLE startAngle = vars.shape->GetEllipseStartAngle();
2533 EDA_ANGLE endAngle = vars.shape->GetEllipseEndAngle();
2543 return std::abs( ( aA - aB ).Normalize180().AsDegrees() ) < 1e-6;
2546 if(
center == vars.shape->GetEllipseCenter() &&
KiROUND( major ) == vars.shape->GetEllipseMajorRadius()
2547 &&
KiROUND( minor ) == vars.shape->GetEllipseMinorRadius()
2548 && sameAngle( rotation, vars.shape->GetEllipseRotation() )
2549 && sameAngle( startAngle, vars.shape->GetEllipseStartAngle() )
2550 && sameAngle( endAngle, vars.shape->GetEllipseEndAngle() ) )
2556 aBeforeWrite( vars.shape );
2558 vars.shape->SetEllipseCenter(
center );
2559 vars.shape->SetEllipseRotation( rotation );
2560 vars.shape->SetEllipseMajorRadius(
KiROUND( major ) );
2561 vars.shape->SetEllipseMinorRadius(
KiROUND( minor ) );
2565 vars.shape->SetEllipseStartAngle( startAngle );
2566 vars.shape->SetEllipseEndAngle( endAngle );
2569 changed.push_back( vars.shape );
2575 VECTOR2I start = pointAt( vars.startX );
2578 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2582 aBeforeWrite( vars.shape );
2586 VECTOR2I startDelta = start - vars.shape->GetStart();
2589 vars.shape->SetBezierC1( vars.shape->GetBezierC1() + startDelta );
2590 vars.shape->SetBezierC2( vars.shape->GetBezierC2() + endDelta );
2591 vars.shape->SetStart( start );
2592 vars.shape->SetEnd(
end );
2593 vars.shape->RebuildBezierToSegmentsPointsList();
2594 changed.push_back( vars.shape );
2610 std::vector<VECTOR2I> points;
2611 points.reserve( vars.vertexCount );
2613 for(
int i = 0; i < vars.vertexCount; ++i )
2614 points.push_back( pointAt( vars.startX + 2 * i ) );
2619 for(
int i = 0; i < vars.vertexCount; ++i )
2621 if( points[i] != outline.
CPoint( i ) )
2632 aBeforeWrite( vars.shape );
2636 vars.shape->SetPolyPoints( points );
2637 changed.push_back( vars.shape );
2643 VECTOR2I start = pointAt( vars.startX );
2646 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2651 const int collapseFloor = 1000;
2654 int curWidth =
std::abs( vars.shape->GetEnd().x - vars.shape->GetStart().x );
2655 int curHeight =
std::abs( vars.shape->GetEnd().y - vars.shape->GetStart().y );
2657 if( ( newWidth < collapseFloor && curWidth >= collapseFloor )
2658 || ( newHeight < collapseFloor && curHeight >= collapseFloor ) )
2664 aBeforeWrite( vars.shape );
2668 int cornerRadius = vars.shape->GetCornerRadius();
2670 vars.shape->SetStart( start );
2671 vars.shape->SetEnd(
end );
2673 if( cornerRadius > 0 )
2674 vars.shape->SetCornerRadius( cornerRadius );
2676 changed.push_back( vars.shape );
2680 VECTOR2I start = pointAt( vars.startX );
2683 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2688 const double collapseFloor = 1000.0;
2689 double newLen = (
end - start ).EuclideanNorm();
2690 double curLen = ( vars.shape->GetEnd() - vars.shape->GetStart() ).EuclideanNorm();
2692 if( newLen < collapseFloor && curLen >= collapseFloor )
2696 aBeforeWrite( vars.shape );
2698 vars.shape->SetStart( start );
2699 vars.shape->SetEnd(
end );
2700 changed.push_back( vars.shape );
2717 GCS::VEC_I conflictingTags;
2718 GCS::VEC_I redundantTags;
2719 m_gcs->getConflicting( conflictingTags );
2720 m_gcs->getRedundant( redundantTags );
2722 auto tagsToKiids = [&](
const GCS::VEC_I& aTags, std::vector<KIID>& aOut )
2724 for(
int t : aTags )
2729 aOut.push_back( it->second );
2734 tagsToKiids( redundantTags, diag.
redundant );
2736 auto flagConflict = [&](
const KIID& aKiid )
2743 const double residualTol = 1e-3;
2752 double err =
m_gcs->calculateConstraintErrorByTag( tag );
2755 if( !std::isfinite( err ) ||
std::abs( err ) <= residualTol )
2758 flagConflict( kiid );
2763 const double normFloor = 1e-3;
2764 std::set<KIID> collapsedShapes;
2771 double solvedLen = std::hypot(
m_params[vars.endX] -
m_params[vars.startX],
2773 double origLen = ( vars.shape->GetEnd() - vars.shape->GetStart() ).EuclideanNorm() *
m_invScale;
2775 if( origLen > normFloor && solvedLen < normFloor )
2776 collapsedShapes.insert( k );
2779 if( !collapsedShapes.empty() )
2781 bool attributed =
false;
2794 bool incident = std::any_of( members->second.begin(), members->second.end(),
2795 [&](
const KIID& aMember )
2797 return collapsedShapes.contains( aMember );
2802 flagConflict( kiid );
2814 flagConflict( kiid );
2833 const std::unordered_set<KIID>& aClusterShapes,
2834 const std::vector<PCB_CONSTRAINT*>& aConstraints )
2842 if( ( shapes.empty() && dimensions.empty() ) || aConstraints.empty() )
2845 auto adapter = std::make_unique<BOARD_CONSTRAINT_ADAPTER>();
2847 if( !adapter->Build( shapes, aConstraints,
nullptr, dimensions ) )
2880 const std::vector<PCB_SHAPE*>& aEditedShapes,
2887 if( !aBoard || aEditedShapes.empty() )
2891 std::set<KIID> visited;
2892 std::unordered_set<KIID> sessionShapes;
2893 std::vector<PCB_CONSTRAINT*> sessionConstraints;
2899 if( !seed || visited.contains( seed->m_Uuid )
2900 || !shapeToConstraints.contains( seed->m_Uuid ) )
2905 std::unordered_set<KIID> clusterShapes;
2906 std::vector<PCB_CONSTRAINT*> clusterConstraints;
2908 clusterConstraints, &visited );
2910 for(
PCB_SHAPE* edited : aEditedShapes )
2912 if( edited && clusterShapes.contains( edited->m_Uuid ) )
2916 sessionShapes.insert( clusterShapes.begin(), clusterShapes.end() );
2917 sessionConstraints.insert( sessionConstraints.end(), clusterConstraints.begin(),
2918 clusterConstraints.end() );
2924 return buildCluster( aBoard, sessionShapes, sessionConstraints );
2962 allAccepted = allAccepted && referenceResult.
Accepted( candidate.id );
2965 return referenceResult;
2992 result.position = projected;
3003 const VECTOR2I& aTarget, std::vector<PCB_SHAPE*>* aModified,
3004 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3017 std::vector<PCB_SHAPE*> changed =
m_adapter->Apply( aBeforeModify );
3018 m_adapter->ApplyReferenceValues( aBeforeModify );
3021 aModified->insert( aModified->end(), changed.begin(), changed.end() );
3038 std::unordered_set<KIID> clusterShapes;
3039 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3063 std::optional<VECTOR2I> resolved = solved ?
m_adapter->AnchorPosition(
m_dragged ) : std::nullopt;
3064 bool quantizedValid = resolved && *resolved == aTarget &&
m_adapter->QuantizedRelationsSatisfied();
3066 return quantizedValid;
3100 result.position = *resolved;
3102 const auto candidateResidual =
3107 switch( aCandidate.relation )
3112 return ( point - aCandidate.origin ).EuclideanNorm();
3116 return std::abs( point.
x - aCandidate.origin.x );
3120 return std::abs( point.
y - aCandidate.origin.y );
3124 if( aCandidate.direction.x != 0.0 )
3125 return std::abs( point.
x - aCandidate.origin.x );
3127 if( aCandidate.direction.y != 0.0 )
3128 return std::abs( point.
y - aCandidate.origin.y );
3130 return std::nullopt;
3137 double divisor = aCandidate.direction.SquaredEuclideanNorm();
3139 if( divisor <= 1e-12 )
3140 return std::nullopt;
3142 VECTOR2D offset = point - aCandidate.origin;
3143 double parameter = offset.
Dot( aCandidate.direction ) / divisor;
3146 return std::nullopt;
3149 && ( parameter < 0.0 || parameter > 1.0 ) )
3151 return std::nullopt;
3154 return std::abs( offset.
x * aCandidate.direction.y
3155 - offset.
y * aCandidate.direction.x )
3156 / std::sqrt( divisor );
3162 if( !aCandidate.manifold )
3163 return std::nullopt;
3165 if(
const CIRCLE*
circle = std::get_if<CIRCLE>( &*aCandidate.manifold ) )
3171 if(
const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &*aCandidate.manifold ) )
3173 if( !arc->Collide(
result.position, 2 ) )
3174 return std::nullopt;
3177 - arc->GetRadius() );
3180 return std::nullopt;
3184 return std::nullopt;
3189 std::optional<double> residual = candidateResidual( candidate );
3200 if( !residual || ( discrete ? *residual != 0.0 : *residual > 2.0 ) )
3210 if( !
m_adapter->QuantizedRelationsSatisfied() )
3219 const VECTOR2I& aCursor, std::vector<PCB_SHAPE*>* aModified,
3220 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
bool aIncludeDragged,
3221 bool aStabilize,
const std::set<KIID>& aEdited,
3222 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged )
3229 const auto notifyModify = [&](
BOARD_ITEM* aItem )
3231 if( ( aIncludeDragged || aItem !=
m_draggedShape ) && aBeforeModify )
3232 aBeforeModify( aItem );
3248 std::vector<PCB_SHAPE*> changed =
m_adapter->Apply( notifyModify );
3250 m_adapter->ApplyReferenceValues( aBeforeModify );
3257 std::ranges::copy_if( changed, std::back_inserter( *aModified ),
3269 std::vector<PCB_SHAPE*>* aModified,
3270 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
bool aIncludeDragged,
3271 bool aStabilize,
const std::set<KIID>& aEdited,
3272 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged,
3273 const std::set<KIID>& aFixedShapes,
bool aHoldDraggedRigid )
3284 std::unordered_set<KIID> clusterShapes;
3285 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3291 if( ( shapes.empty() && dimensions.empty() ) || clusterConstraints.empty() )
3296 if( !adapter.
Build( shapes, clusterConstraints, &aFixedShapes, dimensions ) )
3299 bool solved = adapter.
Solve( aDragged, aCursor, aStabilize, aEdited, aCoDragged, aHoldDraggedRigid );
3308 std::vector<PCB_SHAPE*> changed = adapter.
Apply(
3311 if( ( aIncludeDragged || aItem != draggedShape ) && aBeforeModify )
3312 aBeforeModify( aItem );
3322 std::ranges::copy_if( changed, std::back_inserter( *aModified ),
3324 {
return aIncludeDragged || aShape != draggedShape; } );
3333 if( !aBoard || !aConstraint )
3341 const std::vector<CONSTRAINT_MEMBER>& members = aConstraint->
GetMembers();
3343 if( members.size() != 2 || members.front().m_item == members.back().m_item )
3360 && aOther->GetMembers().front() == members.front();
3366 return { members.back().m_item };
3371 std::vector<PCB_SHAPE*>* aModified,
3372 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
3373 const std::set<KIID>& aFixedShapes )
3375 if( !aBoard || !aConstraint || aConstraint->
GetMembers().empty() )
3392 if( anchors.empty() )
3395 pin.m_anchor = anchors.front().anchor;
3396 pin.m_index = anchors.front().index;
3406 return SolveCluster( aBoard,
pin, *pos, aModified, aBeforeModify,
true,
3407 true, {}, std::nullopt, aFixedShapes,
3408 !aFixedShapes.empty() );
3413 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3421 std::set<KIID> visited;
3425 if( !shape || visited.contains( shape->m_Uuid ) || !shapeToConstraints.contains( shape->m_Uuid ) )
3428 std::unordered_set<KIID> clusterShapes;
3429 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3434 if( anchors.empty() )
3439 std::set<KIID> edited;
3443 if( other && clusterShapes.contains( other->m_Uuid ) )
3444 edited.insert( other->m_Uuid );
3447 SolveCluster( aBoard, { shape->m_Uuid, anchors.front().anchor, anchors.front().index }, anchors.front().pos,
3448 aModified, aBeforeModify,
3449 true,
false, edited );
3455 std::vector<PCB_SHAPE*>* aModified,
3456 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3462 std::set<KIID> visited;
3463 std::vector<std::unique_ptr<BOARD_CONSTRAINT_ADAPTER>> solvedClusters;
3467 if( !seed || visited.contains( seed->m_Uuid ) || !shapeToConstraints.contains( seed->m_Uuid ) )
3470 std::unordered_set<KIID> clusterShapes;
3471 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3477 if( shapes.empty() || clusterConstraints.empty() )
3482 std::set<KIID> fixed;
3484 for(
PCB_SHAPE* edited : aEditedShapes )
3486 if( edited && clusterShapes.contains( edited->m_Uuid ) )
3487 fixed.insert( edited->m_Uuid );
3490 auto adapter = std::make_unique<BOARD_CONSTRAINT_ADAPTER>();
3492 if( !adapter->Build( shapes, clusterConstraints, &fixed, dimensions )
3493 || !adapter->Solve(
true ) || !adapter->CurrentRelationsSatisfied() )
3498 solvedClusters.push_back( std::move( adapter ) );
3501 for(
const std::unique_ptr<BOARD_CONSTRAINT_ADAPTER>& adapter : solvedClusters )
3503 std::vector<PCB_SHAPE*> changed = adapter->Apply( aBeforeModify );
3507 adapter->ApplyReferenceValues( aBeforeModify );
3510 aModified->insert( aModified->end(), changed.begin(), changed.end() );
3518 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3520 if( !aBoard || !aShape )
3525 if( !shapeToConstraints.contains( aShape->
m_Uuid ) )
3528 std::unordered_set<KIID> clusterShapes;
3529 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3535 if( shapes.empty() || clusterConstraints.empty() )
3540 if( !adapter.
Build( shapes, clusterConstraints,
nullptr, dimensions )
3546 std::vector<PCB_SHAPE*> changed = adapter.
Apply(
3550 aBeforeModify( aChanged );
3556 aModified->insert( aModified->end(), changed.begin(), changed.end() );
3563 const std::unordered_set<KIID>& aClusterShapes,
3564 const std::vector<PCB_CONSTRAINT*>& aClusterConstraints,
3565 const std::unordered_set<KIID>& aOnBoard )
3574 if( ( shapes.empty() && dimensions.empty() ) || aClusterConstraints.empty()
3575 || !adapter.
Build( shapes, aClusterConstraints,
nullptr, dimensions ) )
3586 adapter.
Solve(
true );
3598 result.shapeIds.push_back( shape->m_Uuid );
3603 result.dimensionIds.push_back( dimension->m_Uuid );
3655 if( boardConstraints.empty() )
3660 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> shapeToConstraints =
3663 std::set<KIID> visitedShapes;
3665 for(
const auto& [seedShape, seedConstraints] : shapeToConstraints )
3667 if( visitedShapes.contains( seedShape ) )
3670 std::unordered_set<KIID> clusterShapes;
3671 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3688 aSeed ^= aValue + 0x9e3779b97f4a7c15ULL + ( aSeed << 6 ) + ( aSeed >> 2 );
3692static void hashInt( std::size_t& aSeed,
long long aValue )
3694 hashCombine( aSeed,
static_cast<std::size_t
>( aValue ) );
3702 std::uint64_t bits = 0;
3703 static_assert(
sizeof( bits ) ==
sizeof( aValue ) );
3704 std::memcpy( &bits, &aValue,
sizeof( bits ) );
3705 hashCombine( aSeed,
static_cast<std::size_t
>( bits & 0xFFFFFFFFULL ) );
3706 hashCombine( aSeed,
static_cast<std::size_t
>( bits >> 32 ) );
3769 for(
int i = 0; i < outline.
PointCount(); ++i )
3811 hashInt( aSeed,
static_cast<int>( aDimension->
Type() ) );
3819 hashInt( aSeed,
static_cast<int>(
3838 hashInt( aSeed,
static_cast<int>( member.m_anchor ) );
3839 hashInt( aSeed, member.m_index );
3847 const std::vector<PCB_CONSTRAINT*>& aClusterConstraints,
3848 const std::unordered_set<KIID>& aOnBoard )
3850 std::size_t seed = 0;
3852 std::vector<KIID> ids( aClusterShapes.begin(), aClusterShapes.end() );
3853 std::sort( ids.begin(), ids.end() );
3855 for(
const KIID&
id : ids )
3865 std::vector<PCB_CONSTRAINT*> constraints = aClusterConstraints;
3866 std::sort( constraints.begin(), constraints.end(),
3868 { return aLhs->m_Uuid < aRhs->m_Uuid; } );
3895 if( boardConstraints.empty() )
3905 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> shapeToConstraints =
3908 std::set<KIID> visitedShapes;
3909 std::set<std::vector<KIID>> seenKeys;
3911 for(
const auto& [seedShape, seedConstraints] : shapeToConstraints )
3913 if( visitedShapes.contains( seedShape ) )
3916 std::unordered_set<KIID> clusterShapes;
3917 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3923 std::vector<KIID> key;
3926 key.push_back( constraint->m_Uuid );
3928 std::sort( key.begin(), key.end() );
3930 std::size_t hash =
hashCluster( aBoard, clusterShapes, clusterConstraints, onBoard );
3931 auto it =
m_cache.find( key );
3933 if( it ==
m_cache.end() || it->second.hash != hash )
3938 it =
m_cache.insert_or_assign( key,
CACHE_ENTRY{ hash, std::move( cluster ) } ).first;
3942 seenKeys.insert( key );
3947 std::erase_if(
m_cache, [&seenKeys](
const auto& entry ) {
return !seenKeys.contains( entry.first ); } );
std::set< KIID > ConstraintReferenceShapes(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint)
The shapes a just-authored constraint should treat as an immovable reference, for the caller to pass ...
static std::vector< PCB_CONSTRAINT * > collectAllConstraints(BOARD *aBoard)
static void hashCombine(std::size_t &aSeed, std::size_t aValue)
static void hashConstraint(std::size_t &aSeed, const PCB_CONSTRAINT *aConstraint)
static constexpr double STAY_PUT_WEIGHT
static void hashKiid(std::size_t &aSeed, const KIID &aId)
static void hashShape(std::size_t &aSeed, const PCB_SHAPE *aShape)
std::vector< VECTOR2I > ConstraintPinnedPositions(BOARD *aBoard, const std::set< KIID > &aItems)
The distinct board positions that fixed positions pin on aItems.
void ReSolveAfterShapeResize(BOARD *aBoard, PCB_SHAPE *aShape, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve after a resize, e.g. a circle radius edit. Holds aShape fixed so its neighbors adjust.
static std::size_t hashCluster(BOARD *aBoard, const std::unordered_set< KIID > &aClusterShapes, const std::vector< PCB_CONSTRAINT * > &aClusterConstraints, const std::unordered_set< KIID > &aOnBoard)
bool BoardHasConstraints(BOARD *aBoard)
True if the board or any of its footprints carries at least one geometric constraint.
static void assembleClusterInto(BOARD_CONSTRAINT_DIAGNOSTICS &aResult, const CLUSTER_DIAGNOSIS &aCluster)
static void collectConstraintCluster(const std::unordered_map< KIID, std::vector< PCB_CONSTRAINT * > > &aMap, const KIID &aSeed, std::unordered_set< KIID > &aClusterShapes, std::vector< PCB_CONSTRAINT * > &aClusterConstraints, std::set< KIID > *aVisited=nullptr)
static constexpr double IU_PER_NORM_UNIT
CONSTRAINT_DIAGNOSIS ApplyConstraintImmediately(BOARD *aBoard, const PCB_CONSTRAINT *aConstraint, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, const std::set< KIID > &aFixedShapes)
Solve a just-created constraint's cluster so the geometry snaps to satisfy it (SolidWorks-style),...
static void hashPoint(std::size_t &aSeed, const VECTOR2I &aPoint)
CONSTRAINT_DIAGNOSIS SolveCluster(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged, const std::set< KIID > &aFixedShapes, bool aHoldDraggedRigid)
Gather the cluster of shapes transitively constrained with the dragged shape, solve with the dragged ...
static std::vector< PCB_SHAPE * > resolveClusterShapes(BOARD *aBoard, const std::unordered_set< KIID > &aIds, const std::unordered_set< KIID > *aOnBoard=nullptr)
static void hashDimension(std::size_t &aSeed, const PCB_DIMENSION_BASE *aDimension)
static double arcSweepTarget(double aStartAngle, double aEndAngle, double aSweepDeg)
static CLUSTER_DIAGNOSIS diagnoseSingleCluster(BOARD *aBoard, const std::unordered_set< KIID > &aClusterShapes, const std::vector< PCB_CONSTRAINT * > &aClusterConstraints, const std::unordered_set< KIID > &aOnBoard)
static double directedAngleForCorner(const GCS::Line &aL1, const GCS::Line &aL2, double aCornerDeg)
static constexpr double CURSOR_WEIGHT
void ReSolveShapeClusters(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve the clusters of shapes edited outside the solver, e.g.
static void hashInt(std::size_t &aSeed, long long aValue)
bool ReSolveShapeClustersHoldingEdited(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve clusters whose new geometry is authoritative holding every edited shape fully fixed so only ...
bool ConstraintItemIsLocked(const BOARD_ITEM *aItem)
True when the solver must treat aItem as immovable, either locked itself or living inside a locked fo...
static void dedupErrored(BOARD_CONSTRAINT_DIAGNOSTICS &aResult)
static std::unordered_map< KIID, std::vector< PCB_CONSTRAINT * > > buildShapeConstraintMap(BOARD *aBoard, const std::vector< PCB_CONSTRAINT * > &aConstraints, std::vector< KIID > *aErrored=nullptr, const std::unordered_set< KIID > *aOnBoard=nullptr)
static std::vector< PCB_DIMENSION_BASE * > resolveClusterDimensions(BOARD *aBoard, const std::unordered_set< KIID > &aIds, const std::unordered_set< KIID > *aOnBoard=nullptr)
BOARD_CONSTRAINT_DIAGNOSTICS DiagnoseBoardConstraints(BOARD *aBoard)
Diagnose every constraint cluster on the board (validate only – geometry is not changed) and return t...
static constexpr int MAX_SOLVE_ITERATIONS
static BOARD_ITEM * resolveMember(BOARD *aBoard, const std::unordered_set< KIID > *aOnBoard, const KIID &aId)
static std::vector< T * > resolveClusterItems(BOARD *aBoard, const std::unordered_set< KIID > &aIds, const std::unordered_set< KIID > *aOnBoard)
static bool isDegenerateLine(const GCS::Line &aLine)
static void hashDouble(std::size_t &aSeed, double aValue)
@ OVER_CONSTRAINED
In a cluster the solver reports as conflicting.
@ UNDER_CONSTRAINED
In a cluster with remaining free degrees of freedom.
@ WELL_CONSTRAINED
In a fully-determined cluster (zero free DOF).
bool ConstraintItemIsLocked(const BOARD_ITEM *aItem)
True when the solver must treat aItem as immovable, either locked itself or living inside a locked fo...
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Translates KiCad board geometry to and from the planegcs solver (issue #2329).
SNAPSHOT Snapshot() const
void recordReferenceValue(PCB_CONSTRAINT *aConstraint)
Note a non-driving valued constraint so its measured value can be read back after a solve.
void holdShapesRigid(int aTag, const std::set< KIID > &aShapes)
Hold the shapes in aShapes rigid, tagged aTag, so a shape the solve moves translates instead of stret...
double normalizeY(int aIU) const
CONSTRAINT_SYSTEM_2D m_system
int m_coDragTargetY
Backing slot for the co-dragged pin y target.
PCB_DIM_ORTHOGONAL * orthogonalDimensionForMembers(const std::vector< CONSTRAINT_MEMBER > &aMembers) const
The orthogonal dimension a two-point length constraint drives or nullptr requires both members to be ...
std::vector< KIID > m_unmapped
Constraints Build() could not map (not enforced).
double denormalizeX(double aNorm) const
std::set< int > m_fixedParams
Parameter indices the solver may not move, so a rigid translation may not either.
void softPinPoint(const ANCHOR_PARAMS &aPoint, int aTag, std::optional< double > aWeight=std::nullopt)
Pin the point at aPoint where it sits tagged aTag with aWeight rescaling the tier or the default weig...
bool addSnapRelations(const ANCHOR_PARAMS &aAnchor, const std::vector< SNAP_CANDIDATE > &aCandidates, const VECTOR2I &aOffset)
BOARD_CONSTRAINT_ADAPTER()
void pinUneditedShapes(const std::set< KIID > &aEdited, int aTag)
Soft-pin every cluster shape not in aEdited at its current geometry tagged aTag for a minimal-movemen...
bool Solve(const CONSTRAINT_MEMBER &aDragged, const VECTOR2I &aCursor, bool aStabilize=false, const std::set< KIID > &aEdited={}, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged=std::nullopt, bool aHoldDraggedRigid=false)
Solve the system, pinning a dragged anchor to a cursor position.
RIGID_STATE collectRigidState(const std::set< KIID > &aEditedShapes) const
int m_dragTargetX
Stable backing slot for the drag pin's x target (-1 = unset).
void holdFreeSegmentLengths(int aTag, const std::set< KIID > &aShapes)
Length hold on the free segments in aShapes tagged aTag so only those shapes are protected while a me...
std::vector< PCB_CONSTRAINT * > m_referenceConstraints
Non-driving valued, read back after a solve.
bool solveSucceeded(int aSolveResult)
Decide whether a solve reached a usable result a raw Success or Converged always qualifies while a Fa...
double m_invScale
1 / m_scale.
void holdRigidRadii(const std::vector< RIGID_RADIUS_HOLD > &aRadii, int aTag)
const std::vector< KIID > & UnmappedConstraints() const
Constraints from the last Build() that could not be mapped onto a solver primitive (wrong member coun...
ANCHOR_PARAMS anchorParams(const CONSTRAINT_MEMBER &aMember) const
Indices into m_params of the coordinates an anchor maps to invalid if the shape has no such anchor fo...
std::map< KIID, SHAPE_VARS > m_shapeVars
std::deque< double > & m_params
bool SolveAfterResize(const KIID &aResizedShape)
Solve after a resize.
int m_coDragTargetX
Backing slot for the co-dragged pin x target.
CONSTRAINT_SYSTEM_2D::SNAPSHOT SNAPSHOT
double denormalizeY(double aNorm) const
void pinDraggedShapeRest(const CONSTRAINT_MEMBER &aDragged, int aTag, const CONSTRAINT_MEMBER *aCoDragged=nullptr)
Hold the parts of the dragged shape meant to stay put tagged aTag a segment holds its far endpoint an...
std::map< int, std::vector< KIID > > m_tagMembers
Member items per tag, for collapse attribution.
int m_dragTargetY
Stable backing slot for the drag pin's y target.
double m_scale
IU per normalized unit.
std::optional< VECTOR2I > AnchorPosition(const CONSTRAINT_MEMBER &aMember) const
std::vector< int > m_temporaryParams
bool Restore(const SNAPSHOT &aSnapshot)
bool SolveRigidTranslation(const std::set< KIID > &aEditedShapes, const VECTOR2I &aTranslation)
std::map< int, KIID > m_tagToConstraint
std::set< KIID > m_angleConstrainedShapes
Shapes a direction or angle constraint could collapse to a point only these get a stabilize length or...
void holdFreeArcRadii(int aTag, const std::set< KIID > &aShapes)
Radius hold on the free arcs in aShapes tagged aTag so an angle change rotates an endpoint instead of...
bool SolveRigidSnapRelations(const std::set< KIID > &aEditedShapes, const VECTOR2I &aReference, const std::vector< SNAP_CANDIDATE > &aCandidates, const VECTOR2I &aCursor, VECTOR2I &aResolvedCursor)
bool hardRelationsSatisfied() const
size_t m_nextTemporaryParam
bool Build(const std::vector< PCB_SHAPE * > &aShapes, const std::vector< PCB_CONSTRAINT * > &aConstraints, const std::set< KIID > *aFixedShapes=nullptr, const std::vector< PCB_DIMENSION_BASE * > &aDimensions={})
Translate a cluster into a planegcs system.
void holdPolygonVertices(const std::set< KIID > &aShapes, int aTag)
Soft-pin every vertex of each POLYGON in aShapes tagged aTag for edited shapes pinUneditedShapes excl...
std::vector< PCB_SHAPE * > Apply(const std::function< void(BOARD_ITEM *)> &aBeforeWrite={})
Write the solved coordinates back into the shapes, de-normalized to IU.
bool TravelsWhole(const std::set< KIID > &aEditedShapes) const
True when every shape in the cluster is in aEditedShapes and free to travel with it.
int pushParam(double aValue)
Append a normalized coordinate to the backing store, returning its stable index.
CONSTRAINT_DIAGNOSIS Diagnose()
Report degrees of freedom and conflicting/redundant constraints.
void beginTemporaryParameters()
int temporaryParam(double aValue)
std::set< int > m_nonDrivingTags
Measurement-only; excluded from conflict residuals.
double normalizeX(int aIU) const
IU <-> normalized (millimetre, cluster-centred) frame, per axis.
~BOARD_CONSTRAINT_ADAPTER()
@ RECTANGLE
An axis-aligned rectangle whose four corners alias the two stored corners params so rectness holds by...
@ POINT_PAIR
A dimension's two feature points (start + end); no line/curve geometry.
@ POLYGON
A single hole-free outline with one free param pair per vertex since write-back rebuilds one outline ...
@ BEZIER
A cubic bezier only its start and end endpoints are exposed as free points.
bool QuantizedRelationsSatisfied()
bool SolveSnapRelations(const CONSTRAINT_MEMBER &aDragged, const std::vector< SNAP_CANDIDATE > &aCandidates, const VECTOR2I &aCursor)
void ApplyReferenceValues(const std::function< void(BOARD_ITEM *)> &aBeforeWrite={})
Propagate solved reference (non-driving) constraint values back into their m_value so a reference dim...
void holdArcRadius(const SHAPE_VARS &aVars, int aTag)
Hold aVars's arc at its current radius (tagged aTag).
BOARD_CONSTRAINT_DIAGNOSTICS Diagnose(BOARD *aBoard)
Diagnose every cluster reusing cached per-cluster results whose solve inputs are unchanged.
std::map< std::vector< KIID >, CACHE_ENTRY > m_cache
void Clear()
Drop the cache call when the board or view reloads and item-identity assumptions break.
bool IsExactFeasible(const VECTOR2I &aTarget)
bool Build(BOARD *aBoard, const CONSTRAINT_MEMBER &aDragged)
CONSTRAINT_DIAGNOSIS Solve(const VECTOR2I &aCursor, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify, bool aIncludeDragged, bool aStabilize, const std::set< KIID > &aEdited={}, const std::optional< std::pair< CONSTRAINT_MEMBER, VECTOR2I > > &aCoDragged=std::nullopt)
SNAP_RESULT ResolveCandidates(const SNAP_SOURCE_CONTEXT &aContext, const std::vector< SNAP_CANDIDATE > &aCandidates)
bool Matches(const CONSTRAINT_MEMBER &aDragged) const
PCB_SHAPE * m_draggedShape
CONSTRAINT_MEMBER m_dragged
std::set< KIID > m_edited
bool feasibleAt(const VECTOR2I &aTarget)
bool Solve(const VECTOR2I &aTarget, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
bool Build(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, const VECTOR2I &aReference)
SNAP_RESULT ResolveCandidates(const SNAP_SOURCE_CONTEXT &aContext, const std::vector< SNAP_CANDIDATE > &aCandidates)
BOARD_CONSTRAINT_ADAPTER::SNAPSHOT m_baseline
bool rewind()
Rewind to the state the next speculative solve starts from.
SNAP_RESULT startResult(const SNAP_SOURCE_CONTEXT &aContext) const
Seed a snap result at the cursor, rejecting a cluster that was already broken.
void reset()
Drop the built cluster, leaving the session unusable until the next successful build.
bool buildCluster(BOARD *aBoard, const std::unordered_set< KIID > &aClusterShapes, const std::vector< PCB_CONSTRAINT * > &aConstraints)
Assemble the adapter for one cluster and record its baseline.
std::unique_ptr< BOARD_CONSTRAINT_ADAPTER > m_adapter
bool HasBaseConflict() const
static void accept(SNAP_RESULT &aResult, const SNAP_CANDIDATE &aCandidate, double aResidual)
Record one candidate the session honoured.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
bool IsLocked() const override
FOOTPRINT * GetParentFootprint() const
Information pertinent to a Pcbnew printed circuit board.
const FOOTPRINTS & Footprints() const
const CONSTRAINTS & Constraints() const
Geometric constraints (#2329) owned by this board.
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Represent basic circle geometry with utility geometry functions.
KICAD_T Type() const
Returns the type of object.
EDA_ANGLE GetArcAngle() const
int GetEllipseMinorRadius() const
const VECTOR2I & GetBezierC2() const
const VECTOR2I & GetEllipseCenter() const
EDA_ANGLE GetEllipseEndAngle() const
int GetEllipseMajorRadius() const
SHAPE_POLY_SET & GetPolyShape()
EDA_ANGLE GetEllipseRotation() const
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
EDA_ANGLE GetEllipseStartAngle() const
const VECTOR2I & GetBezierC1() const
virtual void SetTextPos(const VECTOR2I &aPoint)
A geometric constraint between board items (issue #2329).
const std::vector< CONSTRAINT_MEMBER > & GetMembers() const
std::optional< double > GetValue() const
bool IsDriving() const
A driving constraint forces its value; a reference (non-driving) one only measures it.
PCB_CONSTRAINT_TYPE GetConstraintType() const
virtual VECTOR2I GetEnd() const
virtual VECTOR2I GetStart() const
The dimension's origin is the first feature point for the dimension.
An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the X or ...
A radial dimension indicates either the radius or diameter of an arc or circle.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
VECTOR2I GetTextPos() const override
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
SNAP_RESULT Resolve(const SNAP_SOURCE_CONTEXT &aContext) const
void AddCandidate(SNAP_CANDIDATE aCandidate)
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
bool IsConstrainableItem(const BOARD_ITEM *aItem)
True when aItem is one ResolveConstrainableItem() accepts: a PCB_SHAPE or a dimension.
EDA_ANGLE MeasureCornerAngle(const SEG &aA, const SEG &aB)
The corner angle between two segments, in the closed range [0, 180] degrees.
std::vector< CONSTRAINT_ANCHOR_POINT > ConstraintShapeAnchors(const PCB_SHAPE *aShape)
Enumerate a shape constraint anchors with positions segment and arc endpoints arc centre circle centr...
std::unordered_set< KIID > CollectMemberCandidateIds(const BOARD *aBoard)
The ids of every item on aBoard a constraint member can resolve to (shapes, dimensions and table cell...
bool ConstraintPolygonIsModelable(const PCB_SHAPE *aShape)
True when polygon has one non empty hole free arc free outline making it solver eligible Shared by ad...
std::optional< VECTOR2I > ConstraintAnchorPosition(const BOARD *aBoard, const CONSTRAINT_MEMBER &aMember)
Current location of a constraint member's anchor (its shape's START/END/CENTER, or a dimension's feat...
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
CONSTRAINT_ANCHOR
Which feature of a referenced board item participates in a constraint.
@ VERTEX
An indexed rectangle corner or polygon outline vertex; pairs with CONSTRAINT_MEMBER::m_index.
@ WHOLE
The item as a whole (a segment as a line, a circle).
@ START
First endpoint of a segment or arc.
@ END
Second endpoint of a segment or arc.
@ CENTER
Center of an arc or circle.
PCB_CONSTRAINT_TYPE
The geometric relationship a PCB_CONSTRAINT enforces between its members.
@ CONCENTRIC
Two arcs/circles share a center.
@ SYMMETRIC
Two points are mirror images about an axis.
@ FIXED_POSITION
A point is locked at its current location.
@ VERTICAL
A segment (or two points) is vertical.
@ TANGENT
A line and a curve, or two curves, touch tangentially.
@ COINCIDENT
Two points are made to coincide.
@ PERPENDICULAR
Two segments are perpendicular.
@ FIXED_RADIUS
An arc/circle has a driving radius value.
@ HORIZONTAL
A segment (or two points) is horizontal.
@ EQUAL_RADIUS
Two arcs/circles have equal radius.
@ MIDPOINT
A point is the midpoint of a segment.
@ POINT_ON_LINE
A point lies on a segment's supporting line.
@ FIXED_LENGTH
A segment has a driving length value.
@ ANGULAR_DIMENSION
An angle between members (driving or reference).
@ COLLINEAR
Two segments lie on the same line.
@ ARC_ANGLE
An arc has a driving or reference swept-angle value.
@ PARALLEL
Two segments are parallel.
@ EQUAL_LENGTH
Two segments have equal length.
Param indices of an anchor coordinates a rect corner aliases mixed start end params so y is not alway...
std::vector< RIGID_RADIUS_HOLD > radii
Per-shape indices into m_params.
int radius
radius scalar (circle, arc) / minor radius (ellipse).
bool startIsLeft
Rect corner roles frozen at Build so VERTEX 0 to 3 as TL TR BR BL bind the same physical corners what...
int arcStartX
arc start-point.x.
int fixedLengthParam
param index of a driving fixed-length target, or -1.
int focusX
first focus.x (ellipse kinds only).
int startX
start.x (segment) / center.x (circle, arc, ellipse).
int endAngle
arc end angle (radians).
int startAngle
arc start angle (radians).
int arcEndX
arc end-point.x.
int endX
end.x (segment only).
PCB_DIMENSION_BASE * dimension
set instead of shape for a POINT_PAIR
int vertexCount
Outline-0 vertex count of a POLYGON vertex i x param is startX plus 2 times i.
Board-wide diagnostics for the constraint overlay and info bar.
std::map< KIID, CONSTRAINT_STATE > shapeStates
std::vector< KIID > errored
Invalid constraints (member missing, deleted, or of a kind incompatible with the type).
std::vector< KIID > redundant
std::vector< KIID > conflicting
One cluster's diagnosis, the unit DiagnoseBoardConstraints assembles the board-wide result from and B...
int freeDof
Remaining free DOF folded into the board total.
std::vector< KIID > dimensionIds
Cluster dimensions.
std::vector< KIID > conflicting
std::vector< KIID > erroredUnmapped
Constraints Build could not map and so not enforced.
std::vector< KIID > shapeIds
Cluster shapes in the order the state is written.
std::vector< KIID > redundant
The outcome of a constraint solve, in plain data so callers need not know planegcs.
bool solved
Solver reached Success or Converged.
bool IsOverConstrained() const
std::vector< KIID > conflicting
Constraints the solver reports as over-constraining.
bool IsUnderConstrained() const
std::vector< KIID > redundant
Constraints the solver reports as redundant.
int freeDof
Remaining degrees of freedom (-1 if not diagnosed).
One participant in a constraint: a referenced board item plus the feature of that item that participa...
KIID m_item
Referenced board item, usually a PCB_SHAPE.
CONSTRAINT_ANCHOR m_anchor
Which feature of that item participates.
int m_index
Vertex ordinal; only meaningful for the VERTEX anchor.
std::vector< double > quantizedResiduals
bool Accepted(const SNAP_STABLE_ID &aId) const
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
constexpr int sign(T val)
VECTOR2< int32_t > VECTOR2I
VECTOR2< double > VECTOR2D