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(); } );
105 return parent && parent->
IsLocked();
113static std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>>
115 std::vector<KIID>* aErrored =
nullptr )
117 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> map;
121 bool errored = constraint->GetMembers().empty();
125 map[member.m_item].push_back( constraint );
133 if( errored && aErrored )
134 aErrored->push_back( constraint->m_Uuid );
144 const std::unordered_map<
KIID, std::vector<PCB_CONSTRAINT*>>& aMap,
const KIID& aSeed,
145 std::unordered_set<KIID>& aClusterShapes, std::vector<PCB_CONSTRAINT*>& aClusterConstraints,
146 std::set<KIID>* aVisited =
nullptr )
149 std::vector<KIID> frontier{ aSeed };
150 aClusterShapes.insert( aSeed );
152 while( !frontier.empty() )
154 KIID shapeId = frontier.back();
158 aVisited->insert( shapeId );
160 auto it = aMap.find( shapeId );
162 if( it == aMap.end() )
167 if( used.insert( constraint->
m_Uuid ).second )
168 aClusterConstraints.push_back( constraint );
172 if( aClusterShapes.insert( member.m_item ).second )
173 frontier.push_back( member.m_item );
186 std::vector<T*> items;
188 for(
const KIID&
id : aIds )
190 if(
T* item =
dynamic_cast<T*
>( aBoard->
ResolveItem(
id,
true ) ) )
191 items.push_back( item );
205 const std::unordered_set<KIID>& aIds )
225 return std::hypot( *aLine.p2.x - *aLine.p1.x, *aLine.p2.y - *aLine.p1.y ) < 1e-9;
231 const double ax[2] = { *aL1.p1.x, *aL1.p2.x };
232 const double ay[2] = { *aL1.p1.y, *aL1.p2.y };
233 const double bx[2] = { *aL2.p1.x, *aL2.p2.x };
234 const double by[2] = { *aL2.p1.y, *aL2.p2.y };
237 double best = std::numeric_limits<double>::max();
239 for(
int i = 0; i < 2; ++i )
241 for(
int j = 0; j < 2; ++j )
243 double dist = std::hypot( ax[i] - bx[j], ay[i] - by[j] );
254 double d1x = ax[1] - ax[0], d1y = ay[1] - ay[0];
255 double d2x = bx[1] - bx[0], d2y = by[1] - by[0];
256 double theta = std::atan2( d1x * d2y - d1y * d2x, d1x * d2x + d1y * d2y );
260 double alpha =
std::abs( std::remainder( aCornerDeg *
M_PI / 180.0, 2.0 *
M_PI ) );
261 double shift = ( vB -
vA ) *
M_PI;
262 double c1 = alpha - shift;
263 double c2 = -alpha - shift;
265 double d1 =
std::abs( std::remainder( theta - c1, 2.0 *
M_PI ) );
266 double d2 =
std::abs( std::remainder( theta - c2, 2.0 *
M_PI ) );
268 return d1 <= d2 ? c1 : c2;
278static double arcSweepTarget(
double aStartAngle,
double aEndAngle,
double aSweepDeg )
280 double current = aEndAngle - aStartAngle;
283 return current - std::remainder( current - alpha, 2.0 *
M_PI );
301 return m_system.AddParameter( aValue );
324 if( aConstraint && !aConstraint->
IsDriving() )
336 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
338 if( members.empty() )
341 auto it =
m_shapeVars.find( members.front().m_item );
346 const PCB_SHAPE* shape = it->second.shape;
347 std::optional<double> value;
352 switch( constraint->GetConstraintType() )
358 if( members.size() == 2 )
360 auto anchorPos = [&](
const CONSTRAINT_MEMBER& aMember ) -> std::optional<VECTOR2I>
371 std::optional<VECTOR2I> pa = anchorPos( members[0] );
372 std::optional<VECTOR2I> pb = anchorPos( members[1] );
385 value = ( *pb - *pa ).EuclideanNorm();
402 if( members.size() != 2 )
405 auto other =
m_shapeVars.find( members[1].m_item );
410 const PCB_SHAPE* shapeB = other->second.shape;
430 std::optional<double> current = constraint->GetValue();
432 if( current &&
std::abs( *value - *current ) <= tol )
436 aBeforeWrite( constraint );
438 constraint->SetValue( value );
477 case 0:
return { leftX, topY };
478 case 1:
return { rightX, topY };
479 case 2:
return { rightX, botY };
480 default:
return { leftX, botY };
518 if( aMembers.size() != 2 || aMembers[0].m_item != aMembers[1].m_item )
543 const std::vector<PCB_CONSTRAINT*>& aConstraints,
544 const std::set<KIID>* aFixedShapes,
545 const std::vector<PCB_DIMENSION_BASE*>& aDimensions )
562 if( aShapes.empty() && aDimensions.empty() )
567 VECTOR2I origin = !aShapes.empty() ? aShapes.front()->GetStart() : aDimensions.front()->GetStart();
574 std::vector<std::pair<int, int>> lockedRanges;
577 std::vector<int> ellipseOffsetParams;
583 int firstParam =
static_cast<int>(
m_params.size() );
598 vars.
startIsLeft = shape->GetStart().x <= shape->GetEnd().x;
599 vars.
startIsTop = shape->GetStart().y <= shape->GetEnd().y;
619 for(
int i = 1; i < outline.
PointCount(); ++i )
674 m_gcs->addConstraintArcRules( arc );
681 double major = shape->GetEllipseMajorRadius();
682 double minor = shape->GetEllipseMinorRadius();
683 double phi = shape->GetEllipseRotation().AsRadians();
686 double focal = major > minor ? std::sqrt( major * major - minor * minor ) : 0.0;
700 ellipseOffsetParams.push_back( offX );
701 ellipseOffsetParams.push_back( offY );
718 GCS::ArcOfEllipse arc;
726 m_gcs->addConstraintArcOfEllipseRules( arc );
737 lockedRanges.emplace_back( firstParam,
static_cast<int>(
m_params.size() ) );
754 switch( dimension->Type() )
772 std::set<int> fixedParams;
774 for(
const auto& [first, last] : lockedRanges )
776 for(
int i = first; i < last; ++i )
777 fixedParams.insert( i );
780 for(
int i : ellipseOffsetParams )
781 fixedParams.insert( i );
783 auto pointAt = [&](
int aXIndex ) -> GCS::Point
790 auto pointFor = [&](
const ANCHOR_PARAMS& aParams ) -> GCS::Point
805 aLine.p1 = pointAt( it->second.startX );
806 aLine.p2 = pointAt( it->second.endX );
812 auto circleFor = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Circle& aCircle ) ->
bool
822 aCircle.center = pointAt( it->second.startX );
823 aCircle.rad = &
m_params[it->second.radius];
829 auto ellipseFor = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Ellipse& aEllipse ) ->
bool
839 aEllipse.center = pointAt( it->second.startX );
840 aEllipse.focus1 = pointAt( it->second.focusX );
841 aEllipse.radmin = &
m_params[it->second.radius];
846 auto pushConstant = [&](
double aIU ) ->
int
849 fixedParams.insert( idx );
857 const std::vector<CONSTRAINT_MEMBER>& members = constraint->GetMembers();
860 switch( constraint->GetConstraintType() )
866 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
868 m_gcs->addConstraintParallel( l1, l2, tag );
879 if( members.size() == 1 )
883 if( lineFor( members[0], l ) )
885 m_gcs->addConstraintHorizontal( l, tag );
889 else if( members.size() == 2 )
896 GCS::Point p1 = pointFor( a );
897 GCS::Point p2 = pointFor( b );
898 m_gcs->addConstraintHorizontal( p1, p2, tag );
908 if( members.size() == 1 )
912 if( lineFor( members[0], l ) )
914 m_gcs->addConstraintVertical( l, tag );
918 else if( members.size() == 2 )
925 GCS::Point p1 = pointFor( a );
926 GCS::Point p2 = pointFor( b );
927 m_gcs->addConstraintVertical( p1, p2, tag );
942 GCS::Point p1 = pointFor( a );
943 GCS::Point p2 = pointFor( b );
944 m_gcs->addConstraintP2PCoincident( p1, p2, tag );
957 fixedParams.insert( a.
x );
958 fixedParams.insert( a.
y );
969 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
971 m_gcs->addConstraintPerpendicular( l1, l2, tag );
982 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
984 m_gcs->addConstraintEqualLength( l1, l2, tag );
998 if( p.
IsValid() && lineFor( members[1], l ) )
1000 GCS::Point point = pointFor( p );
1001 m_gcs->addConstraintPointOnLine( point, l, tag );
1004 else if( p.
IsValid() && circleFor( members[1], circ ) )
1007 GCS::Point point = pointFor( p );
1008 m_gcs->addConstraintPointOnCircle( point, circ, tag );
1011 else if( p.
IsValid() && ellipseFor( members[1], ell ) )
1013 GCS::Point point = pointFor( p );
1014 m_gcs->addConstraintPointOnEllipse( point, ell, tag );
1027 if( p.
IsValid() && lineFor( members[1], seg ) )
1029 GCS::Point mid = pointFor( p );
1030 m_gcs->addConstraintP2PSymmetric( seg.p1, seg.p2, mid, tag );
1042 if( members.size() == 2 && lineFor( members[0], l1 ) && lineFor( members[1], l2 ) )
1044 m_gcs->addConstraintPointOnLine( l2.p1, l1, tag );
1045 m_gcs->addConstraintPointOnLine( l2.p2, l1, tag );
1060 GCS::Point pa = pointFor( a );
1061 GCS::Point pb = pointFor( b );
1062 m_gcs->addConstraintP2PSymmetric( pa, pb, axis, tag );
1073 if( members.size() == 1 && constraint->HasValue() && lineFor( members[0], l ) )
1075 int len = pushConstant( *constraint->GetValue() );
1076 m_gcs->addConstraintP2PDistance( l.p1, l.p2, &
m_params[len], tag, constraint->IsDriving() );
1079 if( constraint->IsDriving() )
1082 it->second.fixedLengthParam = len;
1089 else if( members.size() == 2 && constraint->HasValue() )
1103 int aAxis = horiz ? a.
x : a.
y;
1104 int bAxis = horiz ? b.
x : b.
y;
1106 double sign = gap >= 0.0 ? 1.0 : -1.0;
1107 int len = pushConstant(
sign * *constraint->GetValue() );
1110 &
m_params[len], tag, constraint->IsDriving() );
1114 GCS::Point pa = pointFor( a );
1115 GCS::Point pb = pointFor( b );
1116 int len = pushConstant( *constraint->GetValue() );
1117 m_gcs->addConstraintP2PDistance( pa, pb, &
m_params[len], tag,
1118 constraint->IsDriving() );
1133 if( members.size() == 1 && constraint->HasValue() && circleFor( members[0], c ) )
1135 int rad = pushConstant( *constraint->GetValue() );
1136 m_gcs->addConstraintCircleRadius( c, &
m_params[rad], tag, constraint->IsDriving() );
1148 if( members.size() == 2 && circleFor( members[0], c1 ) && circleFor( members[1], c2 ) )
1150 m_gcs->addConstraintEqualRadius( c1, c2, tag );
1160 auto centerOf = [&](
const CONSTRAINT_MEMBER& aMember, GCS::Point& aOut ) ->
bool
1165 if( circleFor( aMember, c ) )
1171 if( ellipseFor( aMember, e ) )
1182 if( members.size() == 2 && centerOf( members[0], p1 ) && centerOf( members[1], p2 ) )
1184 m_gcs->addConstraintP2PCoincident( p1, p2, tag );
1195 if( members.size() == 2 && constraint->HasValue() && lineFor( members[0], l1 )
1199 fixedParams.insert( angle );
1200 m_gcs->addConstraintL2LAngle( l1, l2, &
m_params[angle], tag, constraint->IsDriving() );
1214 bool validSweep = constraint->HasValue() && *constraint->GetValue() > 0.0
1215 && *constraint->GetValue() < 360.0;
1221 *constraint->GetValue() );
1223 fixedParams.insert( tgt );
1225 &
m_params[tgt], tag, constraint->IsDriving() );
1235 if( members.size() != 2 )
1243 int lineIdx = lineFor( members[0], l ) ? 0 : ( lineFor( members[1], l ) ? 1 : -1 );
1249 if( circleFor( other, c1 ) )
1252 double dx = *l.p2.x - *l.p1.x;
1253 double dy = *l.p2.y - *l.p1.y;
1254 double cross = dx * ( *c1.center.y - *l.p1.y ) - dy * ( *c1.center.x - *l.p1.x );
1256 m_gcs->addConstraintTangent( l, c1, cross > 0.0, tag );
1259 else if( ellipseFor( other, ell ) )
1261 m_gcs->addConstraintTangent( l, ell, tag );
1265 else if( circleFor( members[0], c1 ) && circleFor( members[1], c2 ) )
1267 m_gcs->addConstraintTangent( c1, c2, tag );
1289 if( !constraint->IsDriving() )
1297 switch( constraint->GetConstraintType() )
1304 if( constraint->GetMembers().size() != 1 )
1335 std::set<int> referencedDimParams;
1339 if( unmappedConstraints.contains( constraint->m_Uuid ) )
1349 referencedDimParams.insert(
anchor.x );
1361 for(
int pointX : { vars.startX, vars.endX } )
1363 if( pointX >= 0 && (
locked || !referencedDimParams.contains( pointX ) ) )
1365 fixedParams.insert( pointX );
1366 fixedParams.insert( pointX + 1 );
1372 GCS::VEC_pD unknowns;
1374 for(
int i = 0; i < static_cast<int>(
m_params.size() ); ++i )
1376 if( !fixedParams.contains( i ) )
1377 unknowns.push_back( &
m_params[i] );
1380 m_gcs->declareUnknowns( unknowns );
1381 m_gcs->initSolution();
1412 m_gcs->initSolution();
1413 int ret =
m_gcs->solve();
1414 m_gcs->applySolution();
1418 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1439 if( vars.radius < 0 )
1444 c.center = GCS::Point{ &
m_params[vars.startX], &
m_params[vars.startX + 1] };
1447 if( kiid == aResizedShape )
1455 m_gcs->addConstraintCoordinateX( c.center, &
m_params[cx], GCS::DefaultTemporaryConstraint );
1456 m_gcs->addConstraintCoordinateY( c.center, &
m_params[cy], GCS::DefaultTemporaryConstraint );
1461 m_gcs->addConstraintCircleRadius( c, &
m_params[target], GCS::DefaultTemporaryConstraint );
1473 m_gcs->initSolution();
1474 int ret =
m_gcs->solve();
1475 m_gcs->applySolution();
1479 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1487 const std::set<KIID>& aEdited,
1488 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged,
1489 bool aHoldDraggedRigid )
1516 double len = std::hypot( dx, dy );
1528 && it->second.fixedLengthParam >= 0
1534 double dx = targetX -
m_params[farX];
1535 double dy = targetY -
m_params[farX + 1];
1536 double len = std::hypot( dx, dy );
1538 if( len > 1e-9 && segLen > 1e-9 )
1540 targetX =
m_params[farX] + dx * segLen / len;
1541 targetY =
m_params[farX + 1] + dy * segLen / len;
1565 coDragged = &aCoDragged->first;
1589 if( aHoldDraggedRigid )
1595 std::set<KIID> editedShapes = aEdited;
1596 editedShapes.insert( aDragged.
m_item );
1599 editedShapes.insert( coDragged->
m_item );
1605 std::set<KIID> heldPolygons = editedShapes;
1608 heldPolygons.erase( aDragged.
m_item );
1612 m_gcs->initSolution();
1613 int ret =
m_gcs->solve();
1614 m_gcs->applySolution();
1618 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1625 const std::vector<SNAP_CANDIDATE>& aCandidates,
1629 bool addedRelation =
false;
1631 const auto addCoordinateX = [&](
double aCoordinate )
1634 m_gcs->addConstraintCoordinateX(
anchor, &
m_params[target], GCS::DefaultTemporaryConstraint );
1635 addedRelation =
true;
1637 const auto addCoordinateY = [&](
double aCoordinate )
1640 m_gcs->addConstraintCoordinateY(
anchor, &
m_params[target], GCS::DefaultTemporaryConstraint );
1641 addedRelation =
true;
1645 if( aCandidate.direction.SquaredEuclideanNorm() <= 1e-12 )
1648 VECTOR2D direction = aCandidate.direction * ( 1000000.0 / aCandidate.direction.EuclideanNorm() );
1657 m_gcs->addConstraintPointOnLine(
anchor, line, GCS::DefaultTemporaryConstraint );
1658 addedRelation =
true;
1663 switch( candidate.relation )
1668 addCoordinateX( candidate.origin.x );
1669 addCoordinateY( candidate.origin.y );
1680 if( candidate.direction.x != 0.0 )
1681 addCoordinateX( candidate.origin.x );
1682 else if( candidate.direction.y != 0.0 )
1683 addCoordinateY( candidate.origin.y );
1695 if( !candidate.manifold )
1701 if(
const CIRCLE*
circle = std::get_if<CIRCLE>( &*candidate.manifold ) )
1706 else if(
const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &*candidate.manifold ) )
1708 center = arc->GetCenter();
1709 radius = arc->GetRadius();
1723 m_gcs->addConstraintPointOnCircle(
anchor,
circle, GCS::DefaultTemporaryConstraint );
1724 addedRelation =
true;
1730 return addedRelation;
1739 const auto addPoint = [&](
int aPointX )
1742 state.
points.insert( aPointX );
1745 for(
const KIID&
id : aEditedShapes )
1761 addPoint( vars.
endX );
1792 addPoint( vars.
startX + 2 * i );
1816 const std::vector<SNAP_CANDIDATE>& aCandidates,
1836 int xPin =
m_gcs->addConstraintCoordinateX(
anchor, &
m_params[cursorX], GCS::DefaultTemporaryConstraint );
1837 int yPin =
m_gcs->addConstraintCoordinateY(
anchor, &
m_params[cursorY], GCS::DefaultTemporaryConstraint );
1844 m_gcs->initSolution();
1845 int ret =
m_gcs->solve();
1846 m_gcs->applySolution();
1849 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1857 if( !
m_built || aEditedShapes.empty() )
1862 if( state.
points.empty() )
1867 std::map<int, VECTOR2I> exactTargets;
1869 for(
int pointX : state.
points )
1879 for(
int pointX : state.
points )
1880 softPinPoint( pointX, GCS::DefaultTemporaryConstraint );
1885 m_gcs->initSolution();
1886 int ret =
m_gcs->solve();
1887 m_gcs->applySolution();
1890 for(
const auto& [pointX, target] : exactTargets )
1894 solved = solved && resolved == target;
1897 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1903 const std::vector<SNAP_CANDIDATE>& aCandidates,
1906 if( !
m_built || aEditedShapes.empty() )
1911 if( state.
points.empty() )
1915 const int anchorX = *state.
points.begin();
1918 const VECTOR2I cursorToAnchor = anchorAtBaseline - aReference;
1919 std::map<int, VECTOR2I> baselinePoints;
1921 for(
int pointX : state.
points )
1926 if( pointX == anchorX )
1932 GCS::DefaultTemporaryConstraint );
1934 GCS::DefaultTemporaryConstraint );
1937 bool addedRelation =
addSnapRelations( { anchorX, anchorX + 1 }, aCandidates, cursorToAnchor );
1943 int xPin =
m_gcs->addConstraintCoordinateX(
anchor, &
m_params[cursorX], GCS::DefaultTemporaryConstraint );
1944 int yPin =
m_gcs->addConstraintCoordinateY(
anchor, &
m_params[cursorY], GCS::DefaultTemporaryConstraint );
1948 m_gcs->initSolution();
1949 int ret =
m_gcs->solve();
1950 m_gcs->applySolution();
1951 bool solved =
solveSucceeded( ret ) && ( addedRelation || aCandidates.empty() );
1955 aResolvedCursor = resolvedAnchor - cursorToAnchor;
1956 VECTOR2I translation = aResolvedCursor - aReference;
1958 for(
const auto& [pointX, baseline] : baselinePoints )
1962 solved = solved && resolved == baseline + translation;
1965 m_gcs->clearByTag( GCS::DefaultTemporaryConstraint );
1981 if( !verified.
Accepted( candidate.id ) )
1985 return verified.
position == aResolvedCursor;
1994 return std::nullopt;
2003 if( aSolveResult == GCS::Success || aSolveResult == GCS::Converged )
2015 const double residualTol = 1e-3;
2019 double structuralErr =
m_gcs->calculateConstraintErrorByTag( 0 );
2021 if( std::isfinite( structuralErr ) &&
std::abs( structuralErr ) > residualTol )
2030 double err =
m_gcs->calculateConstraintErrorByTag( tag );
2032 if( !std::isfinite( err ) ||
std::abs( err ) > residualTol )
2043 std::set<int> xParams;
2044 std::set<int> yParams;
2045 std::set<int> radiusParams;
2047 const auto addPoint =
2052 xParams.insert( aX );
2053 yParams.insert( aX + 1 );
2059 addPoint( vars.startX );
2060 addPoint( vars.endX );
2061 addPoint( vars.arcStartX );
2062 addPoint( vars.arcEndX );
2063 addPoint( vars.focusX );
2067 for(
int i = 0; i < vars.vertexCount; ++i )
2068 addPoint( vars.startX + 2 * i );
2071 if( vars.radius >= 0 )
2072 radiusParams.insert( vars.radius );
2075 for(
int index : xParams )
2078 for(
int index : yParams )
2081 for(
int index : radiusParams )
2093 wxASSERT( !aWeight || *aWeight > 0 );
2102 int cx =
m_gcs->addConstraintCoordinateX( point, &
m_params[pinX], aTag );
2103 int cy =
m_gcs->addConstraintCoordinateY( point, &
m_params[pinY], aTag );
2107 m_gcs->rescaleConstraint( cx, *aWeight );
2108 m_gcs->rescaleConstraint( cy, *aWeight );
2161 ( aDragged.
m_index + 2 ) % 4 ) ),
2188 auto holdRadius = [&](
const SHAPE_VARS& aVars )
2200 if( aEdited.contains( kiid ) )
2239 for(
int i = 0; i < vars.vertexCount; ++i )
2255 if( !aShapes.contains( kiid ) )
2258 for(
int i = 0; i < vars.vertexCount; ++i )
2272 if( !aShapes.contains( kiid ) )
2280 m_gcs->addConstraintP2PDistance( p1, p2, &
m_params[len], aTag );
2305 if( !aShapes.contains( kiid ) )
2323 if( state.
points.empty() )
2327 const int anchorX = *state.
points.begin();
2329 for(
int pointX : state.
points )
2331 if( pointX == anchorX )
2348 std::vector<PCB_SHAPE*> changed;
2354 auto pointAt = [&](
int aX )
2363 VECTOR2I start = pointAt( vars.startX );
2366 VECTOR2I end = vars.endX >= 0 ? pointAt( vars.endX ) : vars.dimension->GetEnd();
2368 if( start == vars.dimension->GetStart() &&
end == vars.dimension->GetEnd() )
2372 aBeforeWrite( vars.dimension );
2379 vars.dimension->SetStart( start );
2380 vars.dimension->SetEnd(
end );
2385 vars.dimension->Update();
2394 if(
center == vars.shape->GetCenter() &&
radius == vars.shape->GetRadius() )
2398 aBeforeWrite( vars.shape );
2400 vars.shape->SetCenter(
center );
2401 vars.shape->SetRadius(
radius );
2402 changed.push_back( vars.shape );
2414 VECTOR2I start = pointAt( vars.arcStartX );
2417 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd()
2418 &&
center == vars.shape->GetCenter() )
2424 aBeforeWrite( vars.shape );
2429 double sa = std::atan2( start.
y -
center.y, start.
x -
center.x );
2432 if( vars.shape->IsClockwiseArc() )
2442 double midAngle = 0.5 * ( sa + ea );
2446 vars.shape->SetArcGeometry( start, mid,
end );
2447 changed.push_back( vars.shape );
2458 double focal = std::hypot( fx, fy );
2460 double major = std::sqrt( focal * focal + minor * minor );
2465 focal > 1.0 ?
EDA_ANGLE( std::atan2( fy, fx ),
RADIANS_T ) : vars.shape->GetEllipseRotation();
2467 EDA_ANGLE startAngle = vars.shape->GetEllipseStartAngle();
2468 EDA_ANGLE endAngle = vars.shape->GetEllipseEndAngle();
2478 return std::abs( ( aA - aB ).Normalize180().AsDegrees() ) < 1e-6;
2481 if(
center == vars.shape->GetEllipseCenter() &&
KiROUND( major ) == vars.shape->GetEllipseMajorRadius()
2482 &&
KiROUND( minor ) == vars.shape->GetEllipseMinorRadius()
2483 && sameAngle( rotation, vars.shape->GetEllipseRotation() )
2484 && sameAngle( startAngle, vars.shape->GetEllipseStartAngle() )
2485 && sameAngle( endAngle, vars.shape->GetEllipseEndAngle() ) )
2491 aBeforeWrite( vars.shape );
2493 vars.shape->SetEllipseCenter(
center );
2494 vars.shape->SetEllipseRotation( rotation );
2495 vars.shape->SetEllipseMajorRadius(
KiROUND( major ) );
2496 vars.shape->SetEllipseMinorRadius(
KiROUND( minor ) );
2500 vars.shape->SetEllipseStartAngle( startAngle );
2501 vars.shape->SetEllipseEndAngle( endAngle );
2504 changed.push_back( vars.shape );
2510 VECTOR2I start = pointAt( vars.startX );
2513 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2517 aBeforeWrite( vars.shape );
2521 VECTOR2I startDelta = start - vars.shape->GetStart();
2524 vars.shape->SetBezierC1( vars.shape->GetBezierC1() + startDelta );
2525 vars.shape->SetBezierC2( vars.shape->GetBezierC2() + endDelta );
2526 vars.shape->SetStart( start );
2527 vars.shape->SetEnd(
end );
2528 vars.shape->RebuildBezierToSegmentsPointsList();
2529 changed.push_back( vars.shape );
2545 std::vector<VECTOR2I> points;
2546 points.reserve( vars.vertexCount );
2548 for(
int i = 0; i < vars.vertexCount; ++i )
2549 points.push_back( pointAt( vars.startX + 2 * i ) );
2554 for(
int i = 0; i < vars.vertexCount; ++i )
2556 if( points[i] != outline.
CPoint( i ) )
2567 aBeforeWrite( vars.shape );
2571 vars.shape->SetPolyPoints( points );
2572 changed.push_back( vars.shape );
2578 VECTOR2I start = pointAt( vars.startX );
2581 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2586 const int collapseFloor = 1000;
2589 int curWidth =
std::abs( vars.shape->GetEnd().x - vars.shape->GetStart().x );
2590 int curHeight =
std::abs( vars.shape->GetEnd().y - vars.shape->GetStart().y );
2592 if( ( newWidth < collapseFloor && curWidth >= collapseFloor )
2593 || ( newHeight < collapseFloor && curHeight >= collapseFloor ) )
2599 aBeforeWrite( vars.shape );
2603 int cornerRadius = vars.shape->GetCornerRadius();
2605 vars.shape->SetStart( start );
2606 vars.shape->SetEnd(
end );
2608 if( cornerRadius > 0 )
2609 vars.shape->SetCornerRadius( cornerRadius );
2611 changed.push_back( vars.shape );
2615 VECTOR2I start = pointAt( vars.startX );
2618 if( start == vars.shape->GetStart() &&
end == vars.shape->GetEnd() )
2623 const double collapseFloor = 1000.0;
2624 double newLen = (
end - start ).EuclideanNorm();
2625 double curLen = ( vars.shape->GetEnd() - vars.shape->GetStart() ).EuclideanNorm();
2627 if( newLen < collapseFloor && curLen >= collapseFloor )
2631 aBeforeWrite( vars.shape );
2633 vars.shape->SetStart( start );
2634 vars.shape->SetEnd(
end );
2635 changed.push_back( vars.shape );
2652 GCS::VEC_I conflictingTags;
2653 GCS::VEC_I redundantTags;
2654 m_gcs->getConflicting( conflictingTags );
2655 m_gcs->getRedundant( redundantTags );
2657 auto tagsToKiids = [&](
const GCS::VEC_I& aTags, std::vector<KIID>& aOut )
2659 for(
int t : aTags )
2664 aOut.push_back( it->second );
2669 tagsToKiids( redundantTags, diag.
redundant );
2671 auto flagConflict = [&](
const KIID& aKiid )
2678 const double residualTol = 1e-3;
2687 double err =
m_gcs->calculateConstraintErrorByTag( tag );
2690 if( !std::isfinite( err ) ||
std::abs( err ) <= residualTol )
2693 flagConflict( kiid );
2698 const double normFloor = 1e-3;
2699 std::set<KIID> collapsedShapes;
2706 double solvedLen = std::hypot(
m_params[vars.endX] -
m_params[vars.startX],
2708 double origLen = ( vars.shape->GetEnd() - vars.shape->GetStart() ).EuclideanNorm() *
m_invScale;
2710 if( origLen > normFloor && solvedLen < normFloor )
2711 collapsedShapes.insert( k );
2714 if( !collapsedShapes.empty() )
2716 bool attributed =
false;
2729 bool incident = std::any_of( members->second.begin(), members->second.end(),
2730 [&](
const KIID& aMember )
2732 return collapsedShapes.contains( aMember );
2737 flagConflict( kiid );
2749 flagConflict( kiid );
2768 const std::unordered_set<KIID>& aClusterShapes,
2769 const std::vector<PCB_CONSTRAINT*>& aConstraints )
2777 if( ( shapes.empty() && dimensions.empty() ) || aConstraints.empty() )
2780 auto adapter = std::make_unique<BOARD_CONSTRAINT_ADAPTER>();
2782 if( !adapter->Build( shapes, aConstraints,
nullptr, dimensions ) )
2815 const std::vector<PCB_SHAPE*>& aEditedShapes,
2822 if( !aBoard || aEditedShapes.empty() )
2826 std::set<KIID> visited;
2827 std::unordered_set<KIID> sessionShapes;
2828 std::vector<PCB_CONSTRAINT*> sessionConstraints;
2834 if( !seed || visited.contains( seed->m_Uuid )
2835 || !shapeToConstraints.contains( seed->m_Uuid ) )
2840 std::unordered_set<KIID> clusterShapes;
2841 std::vector<PCB_CONSTRAINT*> clusterConstraints;
2843 clusterConstraints, &visited );
2845 for(
PCB_SHAPE* edited : aEditedShapes )
2847 if( edited && clusterShapes.contains( edited->m_Uuid ) )
2851 sessionShapes.insert( clusterShapes.begin(), clusterShapes.end() );
2852 sessionConstraints.insert( sessionConstraints.end(), clusterConstraints.begin(),
2853 clusterConstraints.end() );
2859 return buildCluster( aBoard, sessionShapes, sessionConstraints );
2897 allAccepted = allAccepted && referenceResult.
Accepted( candidate.id );
2900 return referenceResult;
2927 result.position = projected;
2938 const VECTOR2I& aTarget, std::vector<PCB_SHAPE*>* aModified,
2939 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
2950 std::vector<PCB_SHAPE*> changed =
m_adapter->Apply( aBeforeModify );
2951 m_adapter->ApplyReferenceValues( aBeforeModify );
2954 aModified->insert( aModified->end(), changed.begin(), changed.end() );
2971 std::unordered_set<KIID> clusterShapes;
2972 std::vector<PCB_CONSTRAINT*> clusterConstraints;
2996 std::optional<VECTOR2I> resolved = solved ?
m_adapter->AnchorPosition(
m_dragged ) : std::nullopt;
2997 bool quantizedValid = resolved && *resolved == aTarget &&
m_adapter->QuantizedRelationsSatisfied();
2999 return quantizedValid;
3033 result.position = *resolved;
3035 const auto candidateResidual =
3040 switch( aCandidate.relation )
3045 return ( point - aCandidate.origin ).EuclideanNorm();
3049 return std::abs( point.
x - aCandidate.origin.x );
3053 return std::abs( point.
y - aCandidate.origin.y );
3057 if( aCandidate.direction.x != 0.0 )
3058 return std::abs( point.
x - aCandidate.origin.x );
3060 if( aCandidate.direction.y != 0.0 )
3061 return std::abs( point.
y - aCandidate.origin.y );
3063 return std::nullopt;
3070 double divisor = aCandidate.direction.SquaredEuclideanNorm();
3072 if( divisor <= 1e-12 )
3073 return std::nullopt;
3075 VECTOR2D offset = point - aCandidate.origin;
3076 double parameter = offset.
Dot( aCandidate.direction ) / divisor;
3079 return std::nullopt;
3082 && ( parameter < 0.0 || parameter > 1.0 ) )
3084 return std::nullopt;
3087 return std::abs( offset.
x * aCandidate.direction.y
3088 - offset.
y * aCandidate.direction.x )
3089 / std::sqrt( divisor );
3095 if( !aCandidate.manifold )
3096 return std::nullopt;
3098 if(
const CIRCLE*
circle = std::get_if<CIRCLE>( &*aCandidate.manifold ) )
3104 if(
const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &*aCandidate.manifold ) )
3106 if( !arc->Collide(
result.position, 2 ) )
3107 return std::nullopt;
3110 - arc->GetRadius() );
3113 return std::nullopt;
3117 return std::nullopt;
3122 std::optional<double> residual = candidateResidual( candidate );
3133 if( !residual || ( discrete ? *residual != 0.0 : *residual > 2.0 ) )
3143 if( !
m_adapter->QuantizedRelationsSatisfied() )
3152 const VECTOR2I& aCursor, std::vector<PCB_SHAPE*>* aModified,
3153 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
bool aIncludeDragged,
3154 bool aStabilize,
const std::set<KIID>& aEdited,
3155 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged )
3162 const auto notifyModify = [&](
BOARD_ITEM* aItem )
3164 if( ( aIncludeDragged || aItem !=
m_draggedShape ) && aBeforeModify )
3165 aBeforeModify( aItem );
3181 std::vector<PCB_SHAPE*> changed =
m_adapter->Apply( notifyModify );
3183 m_adapter->ApplyReferenceValues( aBeforeModify );
3190 std::ranges::copy_if( changed, std::back_inserter( *aModified ),
3202 std::vector<PCB_SHAPE*>* aModified,
3203 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
bool aIncludeDragged,
3204 bool aStabilize,
const std::set<KIID>& aEdited,
3205 const std::optional<std::pair<CONSTRAINT_MEMBER, VECTOR2I>>& aCoDragged,
3206 const std::set<KIID>& aFixedShapes,
bool aHoldDraggedRigid )
3217 std::unordered_set<KIID> clusterShapes;
3218 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3224 if( ( shapes.empty() && dimensions.empty() ) || clusterConstraints.empty() )
3229 if( !adapter.
Build( shapes, clusterConstraints, &aFixedShapes, dimensions ) )
3232 bool solved = adapter.
Solve( aDragged, aCursor, aStabilize, aEdited, aCoDragged, aHoldDraggedRigid );
3241 std::vector<PCB_SHAPE*> changed = adapter.
Apply(
3244 if( ( aIncludeDragged || aItem != draggedShape ) && aBeforeModify )
3245 aBeforeModify( aItem );
3255 std::ranges::copy_if( changed, std::back_inserter( *aModified ),
3257 {
return aIncludeDragged || aShape != draggedShape; } );
3266 if( !aBoard || !aConstraint )
3274 const std::vector<CONSTRAINT_MEMBER>& members = aConstraint->
GetMembers();
3276 if( members.size() != 2 || members.front().m_item == members.back().m_item )
3293 && aOther->GetMembers().front() == members.front();
3299 return { members.back().m_item };
3304 std::vector<PCB_SHAPE*>* aModified,
3305 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify,
3306 const std::set<KIID>& aFixedShapes )
3308 if( !aBoard || !aConstraint || aConstraint->
GetMembers().empty() )
3325 if( anchors.empty() )
3328 pin.m_anchor = anchors.front().anchor;
3329 pin.m_index = anchors.front().index;
3339 return SolveCluster( aBoard,
pin, *pos, aModified, aBeforeModify,
true,
3340 true, {}, std::nullopt, aFixedShapes,
3341 !aFixedShapes.empty() );
3346 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3354 std::set<KIID> visited;
3358 if( !shape || visited.contains( shape->m_Uuid ) || !shapeToConstraints.contains( shape->m_Uuid ) )
3361 std::unordered_set<KIID> clusterShapes;
3362 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3367 if( anchors.empty() )
3372 std::set<KIID> edited;
3376 if( other && clusterShapes.contains( other->m_Uuid ) )
3377 edited.insert( other->m_Uuid );
3380 SolveCluster( aBoard, { shape->m_Uuid, anchors.front().anchor, anchors.front().index }, anchors.front().pos,
3381 aModified, aBeforeModify,
3382 true,
false, edited );
3388 std::vector<PCB_SHAPE*>* aModified,
3389 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3395 std::set<KIID> visited;
3396 std::vector<std::unique_ptr<BOARD_CONSTRAINT_ADAPTER>> solvedClusters;
3400 if( !seed || visited.contains( seed->m_Uuid ) || !shapeToConstraints.contains( seed->m_Uuid ) )
3403 std::unordered_set<KIID> clusterShapes;
3404 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3410 if( shapes.empty() || clusterConstraints.empty() )
3415 std::set<KIID> fixed;
3417 for(
PCB_SHAPE* edited : aEditedShapes )
3419 if( edited && clusterShapes.contains( edited->m_Uuid ) )
3420 fixed.insert( edited->m_Uuid );
3423 auto adapter = std::make_unique<BOARD_CONSTRAINT_ADAPTER>();
3425 if( !adapter->Build( shapes, clusterConstraints, &fixed, dimensions )
3426 || !adapter->Solve(
true ) || !adapter->CurrentRelationsSatisfied() )
3431 solvedClusters.push_back( std::move( adapter ) );
3434 for(
const std::unique_ptr<BOARD_CONSTRAINT_ADAPTER>& adapter : solvedClusters )
3436 std::vector<PCB_SHAPE*> changed = adapter->Apply( aBeforeModify );
3440 adapter->ApplyReferenceValues( aBeforeModify );
3443 aModified->insert( aModified->end(), changed.begin(), changed.end() );
3451 const std::function<
void(
BOARD_ITEM* )>& aBeforeModify )
3453 if( !aBoard || !aShape )
3458 if( !shapeToConstraints.contains( aShape->
m_Uuid ) )
3461 std::unordered_set<KIID> clusterShapes;
3462 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3468 if( shapes.empty() || clusterConstraints.empty() )
3473 if( !adapter.
Build( shapes, clusterConstraints,
nullptr, dimensions )
3479 std::vector<PCB_SHAPE*> changed = adapter.
Apply(
3483 aBeforeModify( aChanged );
3489 aModified->insert( aModified->end(), changed.begin(), changed.end() );
3496 const std::unordered_set<KIID>& aClusterShapes,
3497 const std::vector<PCB_CONSTRAINT*>& aClusterConstraints )
3506 if( ( shapes.empty() && dimensions.empty() ) || aClusterConstraints.empty()
3507 || !adapter.
Build( shapes, aClusterConstraints,
nullptr, dimensions ) )
3518 adapter.
Solve(
true );
3530 result.shapeIds.push_back( shape->m_Uuid );
3535 result.dimensionIds.push_back( dimension->m_Uuid );
3587 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> shapeToConstraints =
3590 std::set<KIID> visitedShapes;
3592 for(
const auto& [seedShape, seedConstraints] : shapeToConstraints )
3594 if( visitedShapes.contains( seedShape ) )
3597 std::unordered_set<KIID> clusterShapes;
3598 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3615 aSeed ^= aValue + 0x9e3779b97f4a7c15ULL + ( aSeed << 6 ) + ( aSeed >> 2 );
3619static void hashInt( std::size_t& aSeed,
long long aValue )
3621 hashCombine( aSeed,
static_cast<std::size_t
>( aValue ) );
3629 std::uint64_t bits = 0;
3630 static_assert(
sizeof( bits ) ==
sizeof( aValue ) );
3631 std::memcpy( &bits, &aValue,
sizeof( bits ) );
3632 hashCombine( aSeed,
static_cast<std::size_t
>( bits & 0xFFFFFFFFULL ) );
3633 hashCombine( aSeed,
static_cast<std::size_t
>( bits >> 32 ) );
3696 for(
int i = 0; i < outline.
PointCount(); ++i )
3738 hashInt( aSeed,
static_cast<int>( aDimension->
Type() ) );
3746 hashInt( aSeed,
static_cast<int>(
3765 hashInt( aSeed,
static_cast<int>( member.m_anchor ) );
3766 hashInt( aSeed, member.m_index );
3774 const std::vector<PCB_CONSTRAINT*>& aClusterConstraints )
3776 std::size_t seed = 0;
3778 std::vector<KIID> ids( aClusterShapes.begin(), aClusterShapes.end() );
3779 std::sort( ids.begin(), ids.end() );
3781 for(
const KIID&
id : ids )
3791 std::vector<PCB_CONSTRAINT*> constraints = aClusterConstraints;
3792 std::sort( constraints.begin(), constraints.end(),
3794 { return aLhs->m_Uuid < aRhs->m_Uuid; } );
3823 std::unordered_map<KIID, std::vector<PCB_CONSTRAINT*>> shapeToConstraints =
3826 std::set<KIID> visitedShapes;
3827 std::set<std::vector<KIID>> seenKeys;
3829 for(
const auto& [seedShape, seedConstraints] : shapeToConstraints )
3831 if( visitedShapes.contains( seedShape ) )
3834 std::unordered_set<KIID> clusterShapes;
3835 std::vector<PCB_CONSTRAINT*> clusterConstraints;
3841 std::vector<KIID> key;
3844 key.push_back( constraint->m_Uuid );
3846 std::sort( key.begin(), key.end() );
3848 std::size_t hash =
hashCluster( aBoard, clusterShapes, clusterConstraints );
3849 auto it =
m_cache.find( key );
3851 if( it ==
m_cache.end() || it->second.hash != hash )
3856 it =
m_cache.insert_or_assign( key,
CACHE_ENTRY{ hash, std::move( cluster ) } ).first;
3860 seenKeys.insert( key );
3867 if( seenKeys.contains( it->first ) )
static CLUSTER_DIAGNOSIS diagnoseSingleCluster(BOARD *aBoard, const std::unordered_set< KIID > &aClusterShapes, const std::vector< PCB_CONSTRAINT * > &aClusterConstraints)
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 std::vector< PCB_SHAPE * > resolveClusterShapes(BOARD *aBoard, const std::unordered_set< KIID > &aIds)
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)
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.
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 std::unordered_map< KIID, std::vector< PCB_CONSTRAINT * > > buildShapeConstraintMap(BOARD *aBoard, const std::vector< PCB_CONSTRAINT * > &aConstraints, std::vector< KIID > *aErrored=nullptr)
static void hashPoint(std::size_t &aSeed, const VECTOR2I &aPoint)
static std::size_t hashCluster(BOARD *aBoard, const std::unordered_set< KIID > &aClusterShapes, const std::vector< PCB_CONSTRAINT * > &aClusterConstraints)
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 void hashDimension(std::size_t &aSeed, const PCB_DIMENSION_BASE *aDimension)
static double arcSweepTarget(double aStartAngle, double aEndAngle, double aSweepDeg)
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)
BOARD_CONSTRAINT_DIAGNOSTICS DiagnoseBoardConstraints(BOARD *aBoard)
Diagnose every constraint cluster on the board (validate only – geometry is not changed) and return t...
static std::vector< PCB_DIMENSION_BASE * > resolveClusterDimensions(BOARD *aBoard, const std::unordered_set< KIID > &aIds)
static std::vector< T * > resolveClusterItems(BOARD *aBoard, const std::unordered_set< KIID > &aIds)
static constexpr int MAX_SOLVE_ITERATIONS
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
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.
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()
@ RECT
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
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
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
FOOTPRINT * GetParentFootprint() const
Information pertinent to a Pcbnew printed circuit board.
BOARD_USE GetBoardUse() const
Get what the board use is.
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.
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::optional< VECTOR2I > ConstraintAnchorPosition(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...
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...
BOARD_ITEM * ResolveConstrainableItem(BOARD *aBoard, const KIID &aId)
The board item a constraint may reference: a PCB_SHAPE or a dimension, or nullptr for anything else (...
@ 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