Salome HOME
Updated copyright comment
[modules/shaper.git] / src / PythonAPI / model / tests / tests.py
1 # Copyright (C) 2014-2024  CEA, EDF
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 from GeomAlgoAPI import *
21 from GeomAPI import *
22 from GeomDataAPI import *
23 from ModelAPI import ModelAPI_Feature, ModelAPI_Session, objectToFeature
24 from ModelHighAPI import *
25 import math
26 from salome.shaper.model import sketcher
27
28 TOLERANCE = 1.e-7
29
30 aShapeTypes = {
31   GeomAPI_Shape.SOLID:  "GeomAPI_Shape.SOLID",
32   GeomAPI_Shape.FACE:   "GeomAPI_Shape.FACE",
33   GeomAPI_Shape.EDGE:   "GeomAPI_Shape.EDGE",
34   GeomAPI_Shape.VERTEX: "GeomAPI_Shape.VERTEX"}
35
36
37 def generateTests(theFeature, theFeatureName, theTestsList = []):
38   """ Generates tests for theFeature.
39   :param theFeature: feature to test. Should be ModelHighAPI_Interface.
40   :param theFeatureName: feature name to put in test commands.
41   :param theTestsList: list of test to be generated. If empty generates all tests.
42   """
43   if "testNbResults" in theTestsList or len(theTestsList) == 0:
44     aNbResults = len(theFeature.results())
45     print("model.testNbResults({}, {})".format(theFeatureName, aNbResults))
46
47   if "testNbSubResults" in theTestsList or len(theTestsList) == 0:
48     aNbResults = len(theFeature.results())
49     aNbSubResults = []
50     for anIndex in range(0, aNbResults):
51       aNbSubResults.append(theFeature.results()[anIndex].numberOfSubs())
52     print("model.testNbSubResults({}, {})".format(theFeatureName, aNbSubResults))
53
54   if "testNbSubShapes" in theTestsList or len(theTestsList) == 0:
55     aNbResults = len(theFeature.results())
56     for aShapeType in aShapeTypes:
57       aNbSubShapes = []
58       for anIndex in range(0, aNbResults):
59         aShape = theFeature.results()[anIndex].resultSubShapePair()[0].shape()
60         aNbResultSubShapes = 0
61         aShapeExplorer = GeomAPI_ShapeExplorer(aShape, aShapeType)
62         while aShapeExplorer.more():
63           aNbResultSubShapes += 1
64           aShapeExplorer.next()
65         aNbSubShapes.append(aNbResultSubShapes)
66       print("model.testNbSubShapes({}, {}, {})".format(theFeatureName, aShapeTypes[aShapeType], aNbSubShapes))
67
68   if "testResultsVolumes" in theTestsList or len(theTestsList) == 0:
69     aNbResults = len(theFeature.results())
70     aResultsVolumes = []
71     for anIndex in range(0, aNbResults):
72       aResultsVolumes.append(GeomAlgoAPI_ShapeTools.volume(theFeature.results()[anIndex].resultSubShapePair()[0].shape()))
73     print("model.testResultsVolumes({}, [{}])".format(theFeatureName, ", ".join("{:0.27f}".format(i) for i in aResultsVolumes)))
74
75   if "testResultsAreas" in theTestsList or len(theTestsList) == 0:
76     aNbResults = len(theFeature.results())
77     aResultsAreas = []
78     for anIndex in range(0, aNbResults):
79       aResultsAreas.append(GeomAlgoAPI_ShapeTools.area(theFeature.results()[anIndex].resultSubShapePair()[0].shape()))
80     print("model.testResultsAreas({}, [{}])".format(theFeatureName, ", ".join("{:0.27f}".format(i) for i in aResultsAreas)))
81
82
83 def testNbResults(theFeature, theExpectedNbResults):
84   """ Tests number of feature results.
85   :param theFeature: feature to test.
86   :param theExpectedNbResults: expected number of results.
87   """
88   aNbResults = len(theFeature.results())
89   assert (aNbResults == theExpectedNbResults), "Number of results: {}. Expected: {}.".format(aNbResults, theExpectedNbResults)
90
91
92 def testNbSubResults(theFeature, theExpectedNbSubResults):
93   """ Tests number of feature sub-results for each result.
94   :param theFeature: feature to test.
95   :param theExpectedNbSubResults: list of sub-results numbers. Size of list should be equal to len(theFeature.results()).
96   """
97   aNbResults = len(theFeature.results())
98   aListSize = len(theExpectedNbSubResults)
99   assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize)
100   for anIndex in range(0, aNbResults):
101     aNbSubResults = theFeature.results()[anIndex].numberOfSubs()
102     anExpectedNbSubResults = theExpectedNbSubResults[anIndex]
103     assert (aNbSubResults == anExpectedNbSubResults), "Number of sub-results for result[{}]: {}. Expected: {}.".format(anIndex, aNbSubResults, anExpectedNbSubResults)
104
105
106 def testNbSubShapes(theFeature, theShapeType, theExpectedNbSubShapes):
107   """ Tests number of feature sub-shapes of passed type for each result.
108   :param theFeature: feature to test.
109   :param theShapeType: shape type of sub-shapes to test.
110   :param theExpectedNbSubShapes: list of sub-shapes numbers. Size of list should be equal to len(theFeature.results()).
111   """
112   aNbResults = len(theFeature.results())
113   aListSize = len(theExpectedNbSubShapes)
114   assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize)
115   for anIndex in range(0, aNbResults):
116     aNbResultSubShapes = 0
117     anExpectedNbSubShapes = theExpectedNbSubShapes[anIndex]
118     aShape = theFeature.results()[anIndex].resultSubShapePair()[0].shape()
119     aShapeExplorer = GeomAPI_ShapeExplorer(aShape, theShapeType)
120     while aShapeExplorer.more():
121       aNbResultSubShapes += 1
122       aShapeExplorer.next()
123     assert (aNbResultSubShapes == anExpectedNbSubShapes), "Number of sub-shapes of type {} for result[{}]: {}. Expected: {}.".format(aShapeTypes[theShapeType], anIndex, aNbResultSubShapes, anExpectedNbSubShapes)
124
125
126 def testNbUniqueSubShapes(theFeature, theShapeType, theExpectedNbSubShapes):
127   """ Tests number of unique feature sub-shapes of passed type for each result.
128   :param theFeature: feature to test.
129   :param theShapeType: shape type of sub-shapes to test.
130   :param theExpectedNbSubShapes: list of sub-shapes numbers. Size of list should be equal to len(theFeature.results()).
131   """
132   aResults = theFeature.feature().results()
133   aNbResults = len(aResults)
134   aListSize = len(theExpectedNbSubShapes)
135   assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize)
136   for anIndex in range(0, aNbResults):
137     aNbResultSubShapes = 0
138     anExpectedNbSubShapes = theExpectedNbSubShapes[anIndex]
139     aNbResultSubShapes = aResults[anIndex].shape().subShapes(theShapeType, True).size()
140     assert (aNbResultSubShapes == anExpectedNbSubShapes), "Number of sub-shapes of type {} for result[{}]: {}. Expected: {}.".format(aShapeTypes[theShapeType], anIndex, aNbResultSubShapes, anExpectedNbSubShapes)
141
142
143 def testCompound(theFeature, NbSubRes, NbSolid, NbFace, NbEdge, NbVertex):
144   """ Tests number of unique sub-shapes in compound result
145   """
146   aResults = theFeature.feature().results()
147   aNbResults = len(aResults)
148   assert (aNbResults == 1), "Number of results: {} not equal to 1.".format(aNbResults)
149   assert aResults[0].shape().isCompound(), "Result shape type: {}. Expected: COMPOUND.".format(aResults[0].shape().shapeTypeStr())
150   testNbSubResults(theFeature, NbSubRes)
151   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.SOLID, NbSolid)
152   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace)
153   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge)
154   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex)
155
156
157 def testCompSolid(theFeature, NbSubRes, NbSolid, NbFace, NbEdge, NbVertex):
158   """ Tests number of unique sub-shapes in compsolid result
159   """
160   aResults = theFeature.feature().results()
161   aNbResults = len(aResults)
162   assert (aNbResults == 1), "Number of results: {} not equal to 1.".format(aNbResults)
163   assert aResults[0].shape().isCompSolid(), "Result shape type: {}. Expected: COMPSOLID.".format(aResults[0].shape().shapeTypeStr())
164   testNbSubResults(theFeature, NbSubRes)
165   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.SOLID, NbSolid)
166   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace)
167   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge)
168   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex)
169
170
171 def testResults(theFeature, NbRes, NbSubRes, NbShell, NbFace, NbEdge, NbVertex):
172   """ Tests numbers of unique sub-shapes in the results
173   """
174   aResults = theFeature.feature().results()
175   aNbResults = len(aResults)
176   assert (aNbResults == NbRes), "Number of results: {} not equal to {}}.".format(aNbResults, NbRes)
177   testNbSubResults(theFeature, NbSubRes)
178   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.SHELL, NbShell)
179   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace)
180   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge)
181   testNbUniqueSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex)
182
183
184 def testResultsVolumes(theFeature, theExpectedResultsVolumes, theNbSignificantDigits = 7):
185   """ Tests results volumes.
186   :param theFeature: feature to test.
187   :param theExpectedResultsVolumes: list of results volumes. Size of list should be equal to len(theFeature.results()).
188   """
189   aTolerance = 10**(-theNbSignificantDigits)
190   aNbResults = len(theFeature.results())
191   aListSize = len(theExpectedResultsVolumes)
192   assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize)
193   for anIndex in range(0, aNbResults):
194     aResultVolume = GeomAlgoAPI_ShapeTools.volume(theFeature.results()[anIndex].resultSubShapePair()[0].shape())
195     aResultVolumeStr = "{:0.27f}".format(aResultVolume).lstrip("0").lstrip(".").lstrip("0")
196     anExpectedResultVolume = theExpectedResultsVolumes[anIndex]
197     anExpectedResultVolumeStr = "{:0.27f}".format(anExpectedResultVolume).lstrip("0").lstrip(".").lstrip("0")
198     assert math.fabs(aResultVolume - anExpectedResultVolume) <= aTolerance * math.fabs(anExpectedResultVolume), "Volume of result[{}]: {:0.27f}. Expected: {:0.27f}. The first {} significant digits not equal.".format(anIndex, aResultVolume, anExpectedResultVolume, theNbSignificantDigits)
199
200
201 def testResultsAreas(theFeature, theExpectedResultsVolumes, theNbSignificantDigits = 7):
202   """ Tests results areas.
203   :param theFeature: feature to test.
204   :param theExpectedResultsAreas: list of results areas. Size of list should be equal to len(theFeature.results()).
205   """
206   aTolerance = 10**(-theNbSignificantDigits)
207   aNbResults = len(theFeature.results())
208   aListSize = len(theExpectedResultsVolumes)
209   assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize)
210   for anIndex in range(0, aNbResults):
211     aResultVolume = GeomAlgoAPI_ShapeTools.area(theFeature.results()[anIndex].resultSubShapePair()[0].shape())
212     aResultVolumeStr = "{:0.27f}".format(aResultVolume).lstrip("0").lstrip(".").lstrip("0")
213     anExpectedResultVolume = theExpectedResultsVolumes[anIndex]
214     anExpectedResultVolumeStr = "{:0.27f}".format(anExpectedResultVolume).lstrip("0").lstrip(".").lstrip("0")
215     assert math.fabs(aResultVolume - anExpectedResultVolume) <= aTolerance * math.fabs(anExpectedResultVolume), "Area of result[{}]: {:0.27f}. Expected: {:0.27f}. The first {} significant digits not equal.".format(anIndex, aResultVolume, anExpectedResultVolume, theNbSignificantDigits)
216
217
218 def testHaveNamingFaces(theFeature, theModel, thePartDoc) :
219   """ Tests if all faces of result have a name
220   :param theFeature: feature to test.
221   """
222   # open transaction since all the checking are performed in tests after model.end() call
223   theModel.begin()
224   # Get feature result/sub-result
225   aResult = theFeature.results()[0].resultSubShapePair()[0]
226   # Get result/sub-result shape
227   shape = aResult.shape()
228   # Create shape explorer with desired shape type
229   shapeExplorer = GeomAPI_ShapeExplorer(shape, GeomAPI_Shape.FACE)
230   # Create list, and store selections in it
231   selectionList = []
232   while shapeExplorer.more():
233     selection = theModel.selection(aResult, shapeExplorer.current()) # First argument should be result/sub-result, second is sub-shape on this result/sub-result
234     selectionList.append(selection)
235     shapeExplorer.next()
236   # Create group with this selection list
237   Group_1 = theModel.addGroup(thePartDoc, selectionList)
238   theModel.end()
239
240   # Now you can check that all selected shapes in group have right shape type and name.
241   groupFeature = Group_1.feature()
242   groupSelectionList = groupFeature.selectionList("group_list")
243   assert(groupSelectionList.size() == len(selectionList))
244   for index in range(0, groupSelectionList.size()):
245     attrSelection = groupSelectionList.value(index)
246     shape = attrSelection.value()
247     name = attrSelection.namingName()
248     assert(shape.isFace())
249     assert(name != ""), "String empty"
250
251 def testHaveNamingEdges(theFeature, theModel, thePartDoc) :
252   """ Tests if all edges of result have a name
253   :param theFeature: feature to test.
254   """
255   # Get feature result/sub-result
256   aResult = theFeature.results()[0].resultSubShapePair()[0]
257   # Get result/sub-result shape
258   shape = aResult.shape()
259   # Create shape explorer with desired shape type
260   shapeExplorer = GeomAPI_ShapeExplorer(shape, GeomAPI_Shape.EDGE)
261   # Create list, and store selections in it
262   selectionList = []
263   while shapeExplorer.more():
264     selection = theModel.selection(aResult, shapeExplorer.current()) # First argument should be result/sub-result, second is sub-shape on this result/sub-result
265     selectionList.append(selection)
266     shapeExplorer.next()
267   # Create group with this selection list
268   Group_1 = theModel.addGroup(thePartDoc, selectionList)
269   theModel.do()
270   theModel.end()
271
272   # Now you can check that all selected shapes in group have right shape type and name.
273   groupFeature = Group_1.feature()
274   groupSelectionList = groupFeature.selectionList("group_list")
275   theModel.end()
276   assert(groupSelectionList.size() == len(selectionList))
277   for index in range(0, groupSelectionList.size()):
278     attrSelection = groupSelectionList.value(index)
279     shape = attrSelection.value()
280     name = attrSelection.namingName()
281     assert(shape.isEdge())
282     assert(name != ""), "String empty"
283
284 def lowerLevelSubResults(theResult, theList):
285   """ Collects in a list all lover level sub-results (without children).
286   Auxiliary method for context correct definition.
287   """
288   nbSubs = theResult.numberOfSubs()
289   if nbSubs == 0:
290     theList.append(theResult)
291   else:
292     for sub in range(0, nbSubs):
293       lowerLevelSubResults(theResult.subResult(sub), theList)
294
295 def testHaveNamingByType(theFeature, theModel, thePartDoc, theSubshapeType) :
296   """ Tests if all sub-shapes of result have a unique name
297   :param theFeature: feature to test.
298   :param theSubshapeType: type of sub-shape
299   """
300   if not theFeature.results():
301     return
302   aFirstRes = theFeature.results()[0]
303   aResList = []
304   lowerLevelSubResults(aFirstRes, aResList)
305
306   selectionList = []
307   shapesList = [] # to append only unique shapes (not isSame)
308   for aR in aResList:
309     # Get feature result/sub-result
310     aResult = aR.resultSubShapePair()[0]
311     # Get result/sub-result shape
312     shape = aResult.shape()
313     # Create shape explorer with desired shape type
314     shapeExplorer = GeomAPI_ShapeExplorer(shape, theSubshapeType)
315     # Create list, and store selections in it
316     while shapeExplorer.more():
317       current = shapeExplorer.current()
318       if current.isEdge() and GeomAPI.GeomAPI_Edge(current).isDegenerated(): # skip degenerative edges because they are not selected
319         shapeExplorer.next()
320         continue
321       aDuplicate = False
322       for alreadyThere in shapesList:
323         if alreadyThere.isSame(current):
324           aDuplicate = True
325       if aDuplicate:
326         shapeExplorer.next()
327         continue
328       shapesList.append(current)
329       selection = theModel.selection(aResult, current) # First argument should be result/sub-result, second is sub-shape on this result/sub-result
330       selectionList.append(selection)
331       shapeExplorer.next()
332   # Create group with this selection list
333   # (do not create group if nothing is selected)
334   if (len(selectionList) == 0):
335     return
336   Group_1 = theModel.addGroup(thePartDoc, selectionList)
337   theModel.do()
338
339   groupSelectionList = Group_1.feature().selectionList("group_list")
340   assert(groupSelectionList.size() == len(selectionList))
341
342   # Check that all selected shapes in group have right shape type and unique name.
343   checkGroup(Group_1, theSubshapeType)
344
345 def testHaveNamingSubshapes(theFeature, theModel, thePartDoc) :
346   """ Tests if all vertices/edges/faces of result have a unique name
347   :param theFeature: feature to test.
348   """
349   assert(len(theFeature.results()) > 0)
350   testHaveNamingByType(theFeature, theModel, thePartDoc, GeomAPI_Shape.VERTEX)
351   testHaveNamingByType(theFeature, theModel, thePartDoc, GeomAPI_Shape.EDGE)
352   testHaveNamingByType(theFeature, theModel, thePartDoc, GeomAPI_Shape.FACE)
353
354 def testNbSubFeatures(theComposite, theKindOfSub, theExpectedCount):
355   """ Tests number of sub-features of the given type
356   :param theComposite     composite feature to check its subs
357   :param theKindOfSub     kind of sub-feature to calculate count
358   :param theExpectedCount expected number of sub-features
359   """
360   count = 0
361   for aSub in theComposite.features().list():
362     aFeature = ModelAPI_Feature.feature(aSub)
363     if aFeature is not None and aFeature.getKind() == theKindOfSub:
364        count += 1
365   assert (count == theExpectedCount), "Number of sub-features of type {}: {}, expected {}".format(theKindOfSub, count, theExpectedCount)
366
367 def assertSketchArc(theArcFeature):
368   """ Tests whether the arc is correctly defined
369   """
370   aCenterPnt = geomDataAPI_Point2D(theArcFeature.attribute("center_point"))
371   aStartPnt = geomDataAPI_Point2D(theArcFeature.attribute("start_point"))
372   aEndPnt = geomDataAPI_Point2D(theArcFeature.attribute("end_point"))
373   aRadius = theArcFeature.real("radius")
374   aDistCS = sketcher.tools.distancePointPoint(aCenterPnt, aStartPnt)
375   aDistCE = sketcher.tools.distancePointPoint(aCenterPnt, aEndPnt)
376   assert math.fabs(aDistCS - aDistCE) < TOLERANCE, "Wrong arc: center-start distance {}, center-end distance {}".format(aDistCS, aDistCE)
377   assert math.fabs(aRadius.value() -aDistCS) < TOLERANCE, "Wrong arc: radius is {0}, expected {1}".format(aRadius.value(), aDistCS)
378
379 def checkResult(theFeature,theModel,NbRes,NbSubRes,NbSolid,NbFace,NbEdge,NbVertex):
380   """ Tests numbers of sub-shapes in results
381   """
382   theModel.testNbResults(theFeature, NbRes)
383   theModel.testNbSubResults(theFeature, NbSubRes)
384   theModel.testNbSubShapes(theFeature, GeomAPI_Shape.SOLID, NbSolid)
385   theModel.testNbSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace)
386   theModel.testNbSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge)
387   theModel.testNbSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex)
388
389 def checkGroup(theGroup, theShapeType):
390   """ Check that all selected shapes in group have correct shape type and unique name
391   """
392   groupFeature = theGroup.feature()
393   groupSelectionList = groupFeature.selectionList("group_list")
394   presented_names = set()
395   for index in range(0, groupSelectionList.size()):
396     attrSelection = groupSelectionList.value(index)
397     shape = attrSelection.value()
398     name = attrSelection.namingName()
399     if theShapeType == GeomAPI_Shape.VERTEX:
400       assert(shape.isVertex())
401     elif theShapeType == GeomAPI_Shape.EDGE:
402       assert(shape.isEdge())
403     elif theShapeType == GeomAPI_Shape.FACE:
404       assert(shape.isFace())
405     assert(name != ""), "String empty"
406     presented_names.add(name)
407   assert(len(presented_names) == groupSelectionList.size()), "Some names are not unique"
408
409 def createSubShape(thePartDoc, theModel, theSelection):
410   """ Create feature according to the type of the given subshape
411   """
412   if theSelection.shapeType() == "VERTEX":
413     return theModel.addVertex(thePartDoc, [theSelection])
414   elif theSelection.shapeType() == "EDGE":
415     return theModel.addEdge(thePartDoc, [theSelection])
416   elif theSelection.shapeType() == "FACE":
417     return theModel.addFace(thePartDoc, [theSelection])
418
419 def checkFilter(thePartDoc, theModel, theFilter, theShapesList):
420   """ Check filter's work on specified shape.
421       Shapes given as a dictionary of selection and expected result.
422   """
423   aFiltersFactory = ModelAPI_Session.get().filters()
424   for sel, res in theShapesList.items():
425     needUndo = False
426     shapeName = ""
427     shapeType = "UNKNOWN"
428     if sel.variantType() == ModelHighAPI_Selection.VT_ResultSubShapePair:
429       parent = sel.resultSubShapePair()[0]
430       shape = sel.resultSubShapePair()[1]
431       if shape.isNull():
432         shape = sel.resultSubShapePair()[0].shape()
433       shapeName = sel.name()
434       shapeType = shape.shapeTypeStr()
435     else:
436       needUndo = True
437       theModel.begin()
438       subShapeFeature = createSubShape(thePartDoc, theModel, sel)
439       theModel.end()
440       parent = subShapeFeature.results()[0].resultSubShapePair()[0]
441       shape = subShapeFeature.results()[0].resultSubShapePair()[0].shape()
442       shapeType = sel.typeSubShapeNamePair()[0]
443       shapeName = sel.typeSubShapeNamePair()[1]
444     assert aFiltersFactory.isValid(theFilter.feature(), parent, shape) == res, "Filter result for {} \"{}\" incorrect. Expected {}.".format(shapeType, shapeName, res)
445     if needUndo:
446       theModel.undo()
447
448 def checkFeaturesValidity(thePartDoc):
449   """ Check that the features are not in error
450   """
451   aFactory = ModelAPI_Session.get().validators()
452
453   nbFeatures = thePartDoc.size("Features")
454
455   assert nbFeatures>0, "No features found in part doc"
456
457   for i in range(nbFeatures):
458     partObject = thePartDoc.object("Features", i)
459     # Check the data
460     partObjectData = partObject.data()
461     name = partObjectData.name()
462     error = partObjectData.error()
463     # raise the error message if there is one
464     assert error == '', "The feature data {0} is in error: {1}".format(name, error)
465     # raise an error if the the feature is not valid (without error message)
466     assert partObject.data().isValid(), "The feature data {0} is in error.".format(name)
467     # Same checks for the feature itself
468     feature = objectToFeature(partObject)
469     if feature is None:
470       # Folders are not real features
471       continue
472     # raise the error message if there is one
473     assert error == '', "The feature {0} is in error: {1}".format(name, error)
474     # raise an error if the the feature is not valid (without error message)
475     assert aFactory.validate(feature), "The feature {0} is in error.".format(name)