aDocument.removeFeature(aConstraint)
aSession.finishOperation()
aSession.startOperation()
-aLineStartPoint.setValue(70., 0.)
+aLineStartPoint.setValue(50., 0.)
aSession.finishOperation()
assert (anArcEndPoint.x() != aLineStartPoint.x() or anArcEndPoint.y() != aLineStartPoint.y())
__updated__ = "2014-10-28"
-def distance(pointA, pointB):
+def distancePointPoint(pointA, pointB):
"""
subroutine to calculate distance between two points
result of calculated distance is has 10**-5 precision
ydiff = math.pow((pointA.y() - pointB.y()), 2)
return round(math.sqrt(xdiff + ydiff), 5)
+def distancePointLine(point, line):
+ """
+ subroutine to calculate distance between point and line
+ result of calculated distance is has 10**-5 precision
+ """
+ aStartPoint = geomDataAPI_Point2D(line.attribute("StartPoint"))
+ aEndPoint = geomDataAPI_Point2D(line.attribute("EndPoint"))
+ # orthogonal direction
+ aDirX = -(aEndPoint.y() - aStartPoint.y())
+ aDirY = (aEndPoint.x() - aStartPoint.x())
+ aLen = math.sqrt(aDirX**2 + aDirY**2)
+ aDirX = aDirX / aLen
+ aDirY = aDirY / aLen
+ aVecX = point.x() - aStartPoint.x()
+ aVecY = point.y() - aStartPoint.y()
+ return round(math.fabs(aVecX * aDirX + aVecY * aDirY), 5)
+
aSession = ModelAPI_Session.get()
aDocument = aSession.moduleDocument()
#=========================================================================
#=========================================================================
# Make a constraint to keep the distance
#=========================================================================
-assert (distance(aSketchPointCoords, aLineAStartPoint) != 25.)
+PT_PT_DIST = 25.
+aDist = distancePointPoint(aSketchPointCoords, aLineAStartPoint);
+assert (aDist != PT_PT_DIST)
aSession.startOperation()
aConstraint = aSketchFeature.addFeature("SketchConstraintDistance")
aDistance = aConstraint.real("ConstraintValue")
assert (not aDistance.isInitialized())
assert (not refattrA.isInitialized())
assert (not refattrB.isInitialized())
-aDistance.setValue(25.)
aLineResult = aSketchLine.firstResult()
assert (aLineResult is not None)
refattrA.setAttr(aSketchPointCoords)
refattrB.setAttr(aLineAStartPoint)
aSession.finishOperation()
-assert (aDistance.isInitialized())
assert (refattrA.isInitialized())
assert (refattrB.isInitialized())
+assert (aDistance.isInitialized())
+assert math.fabs(aDistance.value() - aDist) < 1.e-4, "Distance values are different: {0} != {1}".format(aDistance.value(), aDist)
+#=========================================================================
+# Change distance value
+#=========================================================================
+aSession.startOperation()
+aDistance.setValue(PT_PT_DIST)
+aSession.finishOperation()
+assert (distancePointPoint(aSketchPointCoords, aLineAStartPoint) == PT_PT_DIST)
#=========================================================================
# Move line, check that distance is constant
#=========================================================================
-assert (distance(aSketchPointCoords, aLineAStartPoint) == 25.)
aSession.startOperation()
aLineAStartPoint.setValue(0., 40.)
aLineAEndPoint.setValue(100., 40.)
aSession.finishOperation()
-assert (distance(aSketchPointCoords, aLineAStartPoint) == 25.)
+assert (distancePointPoint(aSketchPointCoords, aLineAStartPoint) == PT_PT_DIST)
#=========================================================================
-# TODO: improve test
-# 1. remove constraint, move line's start point to
-# check that constraint are not applied
-# 2. check constrained distance between:
-# * point and line
-# * two lines
+# Remove constraint, check the points are unconstrained now
#=========================================================================
+aSession.startOperation()
+aDocument.removeFeature(aConstraint)
+aSession.finishOperation()
+aSession.startOperation()
+aSketchPointCoords.setValue(0., 0.)
+aSession.finishOperation()
+assert (distancePointPoint(aSketchPointCoords, aLineAStartPoint) != PT_PT_DIST)
+
+#=========================================================================
+# Add distance between point and line
+#=========================================================================
+PT_LINE_DIST = 50.
+aDist = distancePointLine(aSketchPointCoords, aSketchLine)
+aSession.startOperation()
+aConstraint = aSketchFeature.addFeature("SketchConstraintDistance")
+aDistance = aConstraint.real("ConstraintValue")
+refattrA = aConstraint.refattr("ConstraintEntityA")
+refattrB = aConstraint.refattr("ConstraintEntityB")
+assert (not aDistance.isInitialized())
+assert (not refattrA.isInitialized())
+assert (not refattrB.isInitialized())
+aLineResult = aSketchLine.firstResult()
+assert (aLineResult is not None)
+refattrA.setObject(aLineResult)
+refattrB.setAttr(aSketchPointCoords)
+aSession.finishOperation()
+assert (refattrA.isInitialized())
+assert (refattrB.isInitialized())
+assert (aDistance.isInitialized())
+assert math.fabs(aDistance.value() - aDist) < 1.e-4, "Distance values are different: {0} != {1}".format(aDistance.value(), aDist)
+#=========================================================================
+# Change distance value
+#=========================================================================
+aSession.startOperation()
+aDistance.setValue(PT_LINE_DIST)
+aSession.finishOperation()
+assert (distancePointLine(aSketchPointCoords, aSketchLine) == PT_LINE_DIST)
#=========================================================================
# End of test
#=========================================================================
# Auxiliary functions
#=========================================================================
def checkMirror(theListInit, theListMirr, theMirrorLine):
- TOL = 1.e-8
aListSize = theListInit.size()
aLineStartPoint = geomDataAPI_Point2D(theMirrorLine.attribute("StartPoint"))
aDirX = aPointC.x() - aPointB.x()
aDirY = aPointC.y() - aPointB.y()
aDot = aLineDirX * aDirX + aLineDirY * aDirY
- assert math.fabs(aDot) < TOL, "aDot = {0}".format(aDot)
+ assert(math.fabs(aDot) < 1.e-10)
aDirX = aLineEndPoint.x() - 0.5 * (aPointB.x() + aPointC.x())
aDirY = aLineEndPoint.y() - 0.5 * (aPointB.y() + aPointC.y())
aCross = aLineDirX * aDirY - aLineDirY * aDirX
- assert math.fabs(aCross) < TOL, "aCross = {0}".format(aCross)
+ assert(math.fabs(aCross) < 1.e-10)
#=========================================================================
#=========================================================================
aStartPoint1 = []
-def createSketch(theSketch):
+def createSketch1(theSketch):
global aStartPoint1
# Initialize sketch by two lines with coincident boundary
allFeatures = []
theSketch.execute()
return allFeatures
+
+
+def createSketch2(theSketch):
+ global aStartPoint1
+ # Initialize sketch by line and arc with coincident boundary
+ allFeatures = []
+ # Line
+ aSketchLine = theSketch.addFeature("SketchLine")
+ aStartPoint1 = geomDataAPI_Point2D(aSketchLine.attribute("StartPoint"))
+ aEndPoint1 = geomDataAPI_Point2D(aSketchLine.attribute("EndPoint"))
+ aStartPoint1.setValue(10., 10.)
+ aEndPoint1.setValue(30., 5.)
+ allFeatures.append(aSketchLine)
+ # Arc
+ aSketchArc = theSketch.addFeature("SketchArc")
+ aStartPoint2 = geomDataAPI_Point2D(aSketchArc.attribute("ArcStartPoint"))
+ aEndPoint2 = geomDataAPI_Point2D(aSketchArc.attribute("ArcEndPoint"))
+ aCenterPoint = geomDataAPI_Point2D(aSketchArc.attribute("ArcCenter"))
+ aCenterPoint.setValue(20., 10.)
+ aStartPoint2.setValue(10., 10.)
+ aEndPoint2.setValue(20., 0.)
+ allFeatures.append(aSketchArc)
+ # Coincidence
+ aCoincidence = theSketch.addFeature("SketchConstraintCoincidence")
+ aCoincidence.refattr("ConstraintEntityA").setAttr(aStartPoint1)
+ aCoincidence.refattr("ConstraintEntityB").setAttr(aStartPoint2)
+
+ theSketch.execute()
+ return allFeatures
def checkFillet(theObjects, theRadius):
# Verify the arc and lines are connected smoothly
+ print "Check Fillet"
aLine = []
anArc = []
aSize = theObjects.size()
aLine.append(feat)
elif (feat.getKind() == "SketchArc"):
anArc.append(feat)
- assert(anArc)
- assert(anArc[0] is not None)
+ aFilletArc = anArc[-1]
+ assert(aFilletArc is not None)
+ anArc.pop()
anArcPoints = []
- aPoint = geomDataAPI_Point2D(anArc[0].attribute("ArcStartPoint"))
+ aPoint = geomDataAPI_Point2D(aFilletArc.attribute("ArcStartPoint"))
print "ArcStartPoint " + repr(aPoint.x()) + " " + repr(aPoint.y())
anArcPoints.append((aPoint.x(), aPoint.y()))
- aPoint = geomDataAPI_Point2D(anArc[0].attribute("ArcEndPoint"))
+ aPoint = geomDataAPI_Point2D(aFilletArc.attribute("ArcEndPoint"))
print "ArcEndPoint " + repr(aPoint.x()) + " " + repr(aPoint.y())
anArcPoints.append((aPoint.x(), aPoint.y()))
- aPoint = geomDataAPI_Point2D(anArc[0].attribute("ArcCenter"))
+ aPoint = geomDataAPI_Point2D(aFilletArc.attribute("ArcCenter"))
print "ArcCenter " + repr(aPoint.x()) + " " + repr(aPoint.y())
aCenterX = aPoint.x()
aCenterY = aPoint.y()
+ aFilletRadius = math.hypot(anArcPoints[0][0]-aCenterX, anArcPoints[0][1]-aCenterY)
for line in aLine:
aStartPoint = geomDataAPI_Point2D(line.attribute("StartPoint"))
break;
+ for arc in anArc:
+ aStartPoint = geomDataAPI_Point2D(arc.attribute("ArcStartPoint"))
+ aEndPoint = geomDataAPI_Point2D(arc.attribute("ArcEndPoint"))
+ aCenterPoint = geomDataAPI_Point2D(arc.attribute("ArcCenter"))
+
+ aBaseArcPoints = []
+ aBaseArcPoints.append((aStartPoint.x(), aStartPoint.y()))
+ print "anArcStartPoint " + repr(aStartPoint.x()) + " " + repr(aStartPoint.y())
+ aBaseArcPoints.append((aEndPoint.x(), aEndPoint.y()))
+ print "anArcEndPoint " + repr(aEndPoint.x()) + " " + repr(aEndPoint.y())
+ print "anArcCenter " + repr(aCenterPoint.x()) + " " + repr(aCenterPoint.y())
+
+ aRadius = math.hypot(aStartPoint.x()-aCenterPoint.x(), aStartPoint.y()-aCenterPoint.y())
+ aDist = math.hypot(aCenterPoint.x() - aCenterX, aCenterPoint.y() - aCenterY)
+ assert math.fabs(aFilletRadius + aRadius - aDist) < 1.e-7 or math.fabs(math.fabs(aFilletRadius - aRadius) - aDist) < 1.e-7, \
+ "Fillet radius = {0}, Base arc radius = {1}, distance between centers = {2}".format(aFilletRadius, aRadius, aDist)
#=========================================================================
norm.setValue(0, 0, 1)
aSession.finishOperation()
#=========================================================================
-# Initialize sketch
+# Initialize sketch by two lines
#=========================================================================
aSession.startOperation()
-aFeaturesList = createSketch(aSketchFeature)
+aFeaturesList = createSketch1(aSketchFeature)
aSession.finishOperation()
#=========================================================================
# Global variables
# Verify the objects of fillet are created
#=========================================================================
assert(aResObjects)
+checkFillet(aResObjects, FILLET_RADIUS1)
+#=========================================================================
+# Change Fillet radius
+#=========================================================================
+aRadius.setValue(FILLET_RADIUS2)
+aSession.finishOperation()
+checkFillet(aResObjects, FILLET_RADIUS2)
+
+#=========================================================================
+# Create another sketch
+#=========================================================================
+aSession.startOperation()
+aSketchCommonFeature = aDocument.addFeature("Sketch")
+aSketchFeature = featureToCompositeFeature(aSketchCommonFeature)
+origin = geomDataAPI_Point(aSketchFeature.attribute("Origin"))
+origin.setValue(0, 0, 0)
+dirx = geomDataAPI_Dir(aSketchFeature.attribute("DirX"))
+dirx.setValue(1, 0, 0)
+norm = geomDataAPI_Dir(aSketchFeature.attribute("Norm"))
+norm.setValue(0, 1, 0)
+aSession.finishOperation()
+#=========================================================================
+# Initialize sketch by line and arc
+#=========================================================================
+aSession.startOperation()
+aFeaturesList = createSketch2(aSketchFeature)
aSession.finishOperation()
+#=========================================================================
+# Create the Fillet
+#=========================================================================
+aSession.startOperation()
+aFillet = aSketchFeature.addFeature("SketchConstraintFillet")
+aRefAttrA = aFillet.refattr("ConstraintEntityA");
+aRefAttrA.setAttr(aStartPoint1)
+aRadius = aFillet.real("ConstraintValue")
+aRadius.setValue(FILLET_RADIUS1)
+aFillet.execute()
+aResObjects = aFillet.reflist("ConstraintEntityB")
+#=========================================================================
+# Verify the objects of fillet are created
+#=========================================================================
+assert(aResObjects)
checkFillet(aResObjects, FILLET_RADIUS1)
#=========================================================================
+# Change Fillet radius
+#=========================================================================
+aRadius.setValue(FILLET_RADIUS2)
+aSession.finishOperation()
+checkFillet(aResObjects, FILLET_RADIUS2)
+#=========================================================================
# End of test
#=========================================================================
theSketch.execute()
return allFeatures
+def createLine(theSketch):
+ aSketchLine = theSketch.addFeature("SketchLine")
+ aStartPoint = geomDataAPI_Point2D(aSketchLine.attribute("StartPoint"))
+ aEndPoint = geomDataAPI_Point2D(aSketchLine.attribute("EndPoint"))
+ aStartPoint.setValue(7., 5.)
+ aEndPoint.setValue(1., 3.)
+ theSketch.execute()
+ return aSketchLine
+
def checkRotation(theObjects, theNbObjects, theCenterX, theCenterY, theAngle):
# Verify distances of the objects and the number of copies
aFeatures = []
aSession.finishOperation()
aRotated = aMultiRotation.reflist("ConstraintEntityB")
checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
+
+#=========================================================================
+# Create new feature and add it into the Rotation
+#=========================================================================
+aSession.startOperation()
+aLine = createLine(aSketchFeature)
+aSession.finishOperation()
+aSession.startOperation()
+aResult = modelAPI_ResultConstruction(aLine.lastResult())
+assert(aResult is not None)
+aRotList.append(aResult)
+aSession.finishOperation()
+checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
+#=========================================================================
+# Move line and check the copies are moved too
+#=========================================================================
+aSession.startOperation()
+aStartPoint = geomDataAPI_Point2D(aLine.attribute("StartPoint"))
+aStartPoint.setValue(12., 5.)
+aSession.finishOperation()
+checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
+#=========================================================================
+# Change number of copies and verify Rotation
+#=========================================================================
+aSession.startOperation()
+aNbCopies.setValue(2)
+aSession.finishOperation()
+checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
+
+#=========================================================================
+# Remove a feature from the Rotation
+#=========================================================================
+aSession.startOperation()
+aRemoveIt = aRotList.object(0)
+aRotList.remove(aRemoveIt)
+aSession.finishOperation()
+checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
+
#=========================================================================
-# TODO: improve test
-# 1. Add more features into Rotation
-# 2. Move one of initial features and check the Rotated is moved too
+# Clear the list of rotated features
#=========================================================================
+aSession.startOperation()
+aRotList.clear()
+checkRotation(aRotated, 1, CENTER_X, CENTER_Y, ANGLE)
+# Add line once again
+aRotList.append(aResult)
+aSession.finishOperation()
+checkRotation(aRotated, aNbCopies.value(), CENTER_X, CENTER_Y, ANGLE)
#=========================================================================
# End of test
#=========================================================================
theSketch.execute()
return allFeatures
+def createLine(theSketch):
+ aSketchLine = theSketch.addFeature("SketchLine")
+ aStartPoint = geomDataAPI_Point2D(aSketchLine.attribute("StartPoint"))
+ aEndPoint = geomDataAPI_Point2D(aSketchLine.attribute("EndPoint"))
+ aStartPoint.setValue(7., 5.)
+ aEndPoint.setValue(1., 3.)
+ theSketch.execute()
+ return aSketchLine
+
def checkTranslation(theObjects, theNbObjects, theDeltaX, theDeltaY):
# Verify distances of the objects and the number of copies
aFeatures = []
anInd = 0
for feat, next in zip(aFeatures[:-1], aFeatures[1:]):
anInd = anInd + 1
- if (anInd > theNbObjects):
+ if (anInd > theNbObjects-1):
anInd = 0
continue
assert(feat.getKind() == next.getKind())
aSession.finishOperation()
aTranslated = aMultiTranslation.reflist("ConstraintEntityB")
checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
+
+#=========================================================================
+# Create new feature and add it into the Rotation
+#=========================================================================
+aSession.startOperation()
+aLine = createLine(aSketchFeature)
+aSession.finishOperation()
+aSession.startOperation()
+aResult = modelAPI_ResultConstruction(aLine.lastResult())
+assert(aResult is not None)
+aTransList.append(aResult)
+aSession.finishOperation()
+checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
+#=========================================================================
+# Move line and check the copies are moved too
+#=========================================================================
+aSession.startOperation()
+aStartPoint = geomDataAPI_Point2D(aLine.attribute("StartPoint"))
+aStartPoint.setValue(12., 5.)
+aSession.finishOperation()
+checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
#=========================================================================
-# TODO: improve test
-# 1. Add more features into translation
-# 2. Move one of initial features and check the translated is moved too
+# Change number of copies and verify Rotation
#=========================================================================
+aSession.startOperation()
+aNbCopies.setValue(2)
+aSession.finishOperation()
+checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
+
+#=========================================================================
+# Remove a feature from the Rotation
+#=========================================================================
+aSession.startOperation()
+aRemoveIt = aTransList.object(0)
+aTransList.remove(aRemoveIt)
+aSession.finishOperation()
+checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
+
+#=========================================================================
+# Clear the list of rotated features
+#=========================================================================
+aSession.startOperation()
+aTransList.clear()
+checkTranslation(aTranslated, 1, DELTA_X, DELTA_Y)
+# Add line once again
+aTransList.append(aResult)
+aSession.finishOperation()
+checkTranslation(aTranslated, aNbCopies.value(), DELTA_X, DELTA_Y)
#=========================================================================
# End of test
#=========================================================================
myStorage->update(aFeature);
theEntities.push_back(myStorage->entity(aFeature));
- for (int i = 0; i < myNumberOfCopies && anObjIt != anObjectList.end(); ++i, ++anObjIt)
- ; // just skip copied features
+ myFeatures.insert(aFeature);
+ for (int i = 0; i < myNumberOfCopies && anObjIt != anObjectList.end(); ++i, ++anObjIt) {
+ // just add copied features into the list of objects
+ aFeature = ModelAPI_Feature::feature(*anObjIt);
+ if (aFeature)
+ myFeatures.insert(aFeature);
+ }
}
}
+bool SketchSolver_ConstraintMulti::remove()
+{
+ myFeatures.clear();
+ return SketchSolver_Constraint::remove();
+}
+
void SketchSolver_ConstraintMulti::update()
{
update(false);
}
-
void SketchSolver_ConstraintMulti::update(bool isForce)
{
cleanErrorMsg();
AttributeRefListPtr anInitialRefList = std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(
myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_A()));
AttributeIntegerPtr aNbObjects = myBaseConstraint->integer(nameNbObjects());
- if (anInitialRefList->size() != myNumberOfObjects || aNbObjects->value()-1 != myNumberOfCopies) {
+ bool isUpdated= anInitialRefList->size() != myNumberOfObjects || aNbObjects->value()-1 != myNumberOfCopies;
+ if (!isUpdated) {
+ // additional check that the features and their copies are changed
+ AttributeRefListPtr aRefList = std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(
+ myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_B()));
+ if (aRefList && aRefList->size() != 0) {
+ FeaturePtr aFeature;
+ std::list<ObjectPtr> anObjectList = aRefList->list();
+ std::list<ObjectPtr>::iterator anObjIt = anObjectList.begin();
+ for (; anObjIt != anObjectList.end(); ++anObjIt) {
+ aFeature = ModelAPI_Feature::feature(*anObjIt);
+ if (aFeature && myFeatures.find(aFeature) == myFeatures.end()) {
+ isUpdated = true;
+ break;
+ }
+ }
+ } else
+ isUpdated = true;
+ }
+ if (isUpdated) {
remove();
process();
return;
/// \brief Update constraint
void update(bool isForce);
+ /// \brief Tries to remove constraint
+ /// \return \c false, if current constraint contains another SketchPlugin constraints (like for multiple coincidence)
+ virtual bool remove();
+
protected:
/// \brief Converts SketchPlugin constraint to a list of SolveSpace constraints
virtual void process()
int myNumberOfCopies; ///< number of previous copies of initial objects
bool myAdjusted; ///< the constraint is already adjusted (to not do it several times)
+
+ std::set<FeaturePtr> myFeatures; ///< list of features and their copies to find whether some of them are disappeared
};
#endif
return true;
}
-
-void SketchSolver_Group::updateConstraints()
-{
- std::set<SolverConstraintPtr> aPostponed; // postponed constraints Multi-Rotation and Multi-Translation
-
- ConstraintConstraintMap::iterator anIt = myConstraints.begin();
- for (; anIt != myConstraints.end(); ++anIt) {
- if (myChangedConstraints.find(anIt->first) == myChangedConstraints.end())
- continue;
- if (anIt->first->getKind() == SketchPlugin_MultiRotation::ID() ||
- anIt->first->getKind() == SketchPlugin_MultiTranslation::ID())
- aPostponed.insert(anIt->second);
- else
- anIt->second->update();
- }
-
- // Update postponed constraints
- std::set<SolverConstraintPtr>::iterator aSCIter = aPostponed.begin();
- for (; aSCIter != aPostponed.end(); ++aSCIter)
- (*aSCIter)->update();
-
- myChangedConstraints.clear();
-}
-
static void updateMultiConstraints(ConstraintConstraintMap& theConstraints, FeaturePtr theFeature)
{
ConstraintConstraintMap::iterator aCIt = theConstraints.begin();
// ============================================================================
bool SketchSolver_Group::resolveConstraints()
{
- if (!myChangedConstraints.empty())
- updateConstraints();
-
bool aResolved = false;
bool isGroupEmpty = isEmpty();
if (myStorage->isNeedToResolve() && !isGroupEmpty) {
/// \brief Verifies is the feature valid
bool checkFeatureValidity(FeaturePtr theFeature);
- /// \brief Update just changed constraints
- void updateConstraints();
-
private:
GroupID myID; ///< Index of the group
EntityID myWorkplaneID; ///< Index of workplane, the group is based on
ConstraintConstraintMap myConstraints; ///< List of constraints
std::set<SolverConstraintPtr> myTempConstraints; ///< List of temporary constraints
std::map<AttributePtr, SolverConstraintPtr> myParametricConstraints; ///< List of parametric constraints
- std::set<ConstraintPtr> myChangedConstraints; ///< List of just updated constraints
StoragePtr myStorage; ///< Container for the set of SolveSpace constraints and their entities