]> SALOME platform Git repositories - modules/shaper.git/blob - src/ModelHighAPI/ModelHighAPI_Dumper.cpp
Salome HOME
Fix unit tests for ParametersPlugin
[modules/shaper.git] / src / ModelHighAPI / ModelHighAPI_Dumper.cpp
1 // Copyright (C) 2016-20xx CEA/DEN, EDF R&D -->
2
3 // File:        ModelHighAPI_Dumper.cpp
4 // Created:     1 August 2016
5 // Author:      Artem ZHIDKOV
6
7 //--------------------------------------------------------------------------------------
8 #include "ModelHighAPI_Dumper.h"
9
10 #include <GeomAPI_Pnt.h>
11 #include <GeomAPI_Dir.h>
12
13 #include <GeomDataAPI_Dir.h>
14 #include <GeomDataAPI_Point.h>
15 #include <GeomDataAPI_Point2D.h>
16
17 #include <ModelAPI_AttributeBoolean.h>
18 #include <ModelAPI_AttributeDouble.h>
19 #include <ModelAPI_AttributeInteger.h>
20 #include <ModelAPI_AttributeRefAttr.h>
21 #include <ModelAPI_AttributeRefAttrList.h>
22 #include <ModelAPI_AttributeReference.h>
23 #include <ModelAPI_AttributeRefList.h>
24 #include <ModelAPI_AttributeSelection.h>
25 #include <ModelAPI_AttributeSelectionList.h>
26 #include <ModelAPI_AttributeString.h>
27 #include <ModelAPI_CompositeFeature.h>
28 #include <ModelAPI_Document.h>
29 #include <ModelAPI_Entity.h>
30 #include <ModelAPI_Feature.h>
31 #include <ModelAPI_Result.h>
32 #include <ModelAPI_ResultPart.h>
33
34 #include <PartSetPlugin_Part.h>
35
36 #include <OSD_OpenFile.hxx>
37
38 #include <fstream>
39
40 #define DUMP_USER_DEFINED_NAMES
41
42 static int gCompositeStackDepth = 0;
43
44 ModelHighAPI_Dumper* ModelHighAPI_Dumper::mySelf = 0;
45
46 ModelHighAPI_Dumper::ModelHighAPI_Dumper()
47 {
48   clear();
49 }
50
51 void ModelHighAPI_Dumper::setInstance(ModelHighAPI_Dumper* theDumper)
52 {
53   if (mySelf == 0)
54     mySelf = theDumper;
55 }
56
57 ModelHighAPI_Dumper* ModelHighAPI_Dumper::getInstance()
58 {
59   return mySelf;
60 }
61
62 #define CLEAR_STREAM(theStream) { \
63     std::ostringstream anOther;   \
64     swap(theStream, anOther);     \
65   }
66
67 void ModelHighAPI_Dumper::clear(bool bufferOnly)
68 {
69   CLEAR_STREAM(myDumpBuffer);
70   myDumpBuffer << std::setprecision(16);
71
72   clearNotDumped();
73
74   if (!bufferOnly) {
75     CLEAR_STREAM(myFullDump);
76     myFullDump << std::setprecision(16);
77
78     myNames.clear();
79     myModules.clear();
80     myFeatureCount.clear();
81     myLastEntityWithName = EntityPtr();
82   }
83 }
84
85 void ModelHighAPI_Dumper::clearNotDumped()
86 {
87   myNotDumpedEntities.clear();
88 }
89
90 const std::string& ModelHighAPI_Dumper::name(const EntityPtr& theEntity, bool theSaveNotDumped)
91 {
92   EntityNameMap::const_iterator aFound = myNames.find(theEntity);
93   if (aFound != myNames.end())
94     return aFound->second.first;
95
96   // entity is not found, store it
97   std::string aName;
98   bool isUserDefined = false;
99   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theEntity);
100   if (aFeature) {
101     isUserDefined = true;
102     aName = aFeature->name();
103     const std::string& aKind = aFeature->getKind();
104     DocumentPtr aDoc = aFeature->document();
105     int& aNbFeatures = myFeatureCount[aDoc][aKind];
106
107     size_t anIndex = aName.find(aKind);
108     if (anIndex == 0 && aName[aKind.length()] == '_') { // name starts with "FeatureKind_"
109       std::string anIdStr = aName.substr(aKind.length() + 1, std::string::npos);
110       int anId = std::stoi(anIdStr);
111
112       // Check number of already registered objects of such kind. Index of current object
113       // should be the same to identify feature's name as automatically generated.
114       if (aNbFeatures + 1 == anId) {
115         isUserDefined = false;
116         //aNbFeatures = anId - 1;
117       }
118     }
119
120     aNbFeatures += 1;
121   }
122
123   myNames[theEntity] = std::pair<std::string, bool>(aName, isUserDefined);
124   if (theSaveNotDumped)
125     myNotDumpedEntities.insert(theEntity);
126   return myNames[theEntity].first;
127 }
128
129 const std::string& ModelHighAPI_Dumper::parentName(const FeaturePtr& theEntity)
130 {
131   const std::set<AttributePtr>& aRefs = theEntity->data()->refsToMe();
132   std::set<AttributePtr>::const_iterator aRefIt = aRefs.begin();
133   for (; aRefIt != aRefs.end(); ++aRefIt) {
134     CompositeFeaturePtr anOwner = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(
135         ModelAPI_Feature::feature((*aRefIt)->owner()));
136     if (anOwner)
137       return name(anOwner);
138   }
139
140   static const std::string DUMMY;
141   return DUMMY;
142 }
143
144 bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_Document>& theDoc,
145                                   const std::string& theFileName)
146 {
147   // dump top level document feature
148   static const std::string aDocName("partSet");
149   myNames[theDoc] = std::pair<std::string, bool>(aDocName, false);
150   *this << aDocName << " = model.moduleDocument()" << std::endl;
151
152   // dump subfeatures and store result to file
153   return process(theDoc) && exportTo(theFileName);
154 }
155
156 bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_Document>& theDoc)
157 {
158   bool isOk = true;
159   std::list<FeaturePtr> aFeatures = theDoc->allFeatures();
160   std::list<FeaturePtr>::const_iterator aFeatIt = aFeatures.begin();
161   // firstly, dump all parameters
162   for (; aFeatIt != aFeatures.end(); ++ aFeatIt)
163     dumpParameter(*aFeatIt);
164   // dump all other features
165   for (aFeatIt = aFeatures.begin(); aFeatIt != aFeatures.end(); ++aFeatIt) {
166     CompositeFeaturePtr aCompFeat = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(*aFeatIt);
167     if (aCompFeat) // iteratively process composite features
168       isOk = process(aCompFeat) && isOk;
169     else if (!isDumped(*aFeatIt)) // dump common feature 
170       dumpFeature(*aFeatIt);
171   }
172   return isOk;
173 }
174
175 bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_CompositeFeature>& theComposite, bool isForce)
176 {
177   // increase composite features stack
178   ++gCompositeStackDepth;
179   // dump composite itself
180   if (!isDumped(theComposite) || isForce)
181     dumpFeature(theComposite, isForce);
182
183   // sub-part is processed independently, because it provides separate document
184   if (theComposite->getKind() == PartSetPlugin_Part::ID()) {
185     // decrease composite features stack because we run into separate document
186     --gCompositeStackDepth;
187
188     ResultPartPtr aPartResult =
189         std::dynamic_pointer_cast<ModelAPI_ResultPart>(theComposite->lastResult());
190     if (!aPartResult)
191       return false;
192     DocumentPtr aSubDoc = aPartResult->partDoc();
193     // set name of document
194     const std::string& aPartName = myNames[theComposite].first;
195     std::string aDocName = aPartName + "_doc";
196     myNames[aSubDoc] = std::pair<std::string, bool>(aDocName, false);
197
198     // dump document in a separate line
199     *this << aDocName << " = " << aPartName << ".document()" << std::endl;
200     // dump features in the document
201     return process(aSubDoc);
202   }
203
204   // dump sub-features
205   bool isOk = processSubs(theComposite);
206   // decrease composite features stack
207   --gCompositeStackDepth;
208
209   return isOk;
210 }
211
212 bool ModelHighAPI_Dumper::processSubs(const std::shared_ptr<ModelAPI_CompositeFeature>& theComposite,
213                                       bool theDumpModelDo)
214 {
215   bool isOk = true;
216   // dump all sub-features;
217   int aNbSubs = theComposite->numberOfSubs();
218 //////////////////////////////////////
219   std::list<ObjectPtr> aList = theComposite->reflist("Features")->list();
220 //////////////////////////////////////
221   for (int anIndex = 0; anIndex < aNbSubs; ++anIndex) {
222     FeaturePtr aFeature = theComposite->subFeature(anIndex);
223     if (isDumped(aFeature))
224       continue;
225
226     CompositeFeaturePtr aCompFeat = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aFeature);
227     if (aCompFeat) // iteratively process composite features
228       isOk = process(aCompFeat) && isOk;
229     else
230       dumpFeature(aFeature, true);
231   }
232
233   // It is necessary for the sketch to create its result when complete (command "model.do()").
234   // This option is set by flat theDumpModelDo.
235   // However, nested sketches are rebuilt by parent feature, so, they do not need
236   // explicit call of "model.do()". This will be controlled by the depth of the stack.
237   if (theDumpModelDo && gCompositeStackDepth <= 1)
238     *this << "model.do()" << std::endl;
239   return isOk;
240 }
241
242 bool ModelHighAPI_Dumper::exportTo(const std::string& theFileName)
243 {
244   std::ofstream aFile;
245   OSD_OpenStream(aFile, theFileName.c_str(), std::ofstream::out);
246   if (!aFile.is_open())
247     return false;
248
249   // standard header
250   for (ModulesMap::const_iterator aModIt = myModules.begin();
251        aModIt != myModules.end(); ++aModIt) {
252     aFile << "from " << aModIt->first << " import ";
253     if (aModIt->second.empty() || 
254         aModIt->second.find(std::string()) != aModIt->second.end())
255       aFile << "*"; // import whole module
256     else {
257       // import specific features
258       std::set<std::string>::const_iterator anObjIt = aModIt->second.begin();
259       aFile << *anObjIt;
260       for (++anObjIt; anObjIt != aModIt->second.end(); ++anObjIt)
261         aFile << ", " << *anObjIt;
262     }
263     aFile << std::endl;
264   }
265   if (!myModules.empty())
266     aFile << std::endl;
267
268   aFile << "import model" << std::endl << std::endl;
269   aFile << "model.begin()" << std::endl;
270
271   // dump collected data
272   aFile << myFullDump.str();
273
274   // standard footer
275   aFile << "model.end()" << std::endl;
276
277   aFile.close();
278   clear();
279
280   return true;
281 }
282
283 void ModelHighAPI_Dumper::importModule(const std::string& theModuleName,
284                                        const std::string& theObject)
285 {
286   myModules[theModuleName].insert(theObject);
287 }
288
289 void ModelHighAPI_Dumper::dumpEntitySetName()
290 {
291   if (!myLastEntityWithName)
292     return;
293
294 #ifdef DUMP_USER_DEFINED_NAMES
295   const std::string& aName = name(myLastEntityWithName);
296   myDumpBuffer << aName;
297   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(myLastEntityWithName);
298   if (aFeature)
299     myDumpBuffer << ".feature()";
300   myDumpBuffer << ".data().setName(\"" << aName << "\")" << std::endl;
301 #endif
302   myNames[myLastEntityWithName].second = false; // don't dump "setName" for the entity twice
303   myLastEntityWithName = EntityPtr();
304 }
305
306 bool ModelHighAPI_Dumper::isDumped(const EntityPtr& theEntity) const
307 {
308   EntityNameMap::const_iterator aFound = myNames.find(theEntity);
309   return aFound != myNames.end();
310 }
311
312 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const char theChar)
313 {
314   myDumpBuffer << theChar;
315   return *this;
316 }
317
318 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const char* theString)
319 {
320   myDumpBuffer << theString;
321   return *this;
322 }
323
324 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const std::string& theString)
325 {
326   myDumpBuffer << theString;
327   return *this;
328 }
329
330 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const bool theValue)
331 {
332   myDumpBuffer << (theValue ? "True" : "False");
333   return *this;
334 }
335
336 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const int theValue)
337 {
338   myDumpBuffer << theValue;
339   return *this;
340 }
341
342 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const double theValue)
343 {
344   myDumpBuffer << theValue;
345   return *this;
346 }
347
348 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const std::shared_ptr<GeomAPI_Pnt>& thePoint)
349 {
350   importModule("GeomAPI", "GeomAPI_Pnt");
351   myDumpBuffer << "GeomAPI_Pnt(" << thePoint->x() << ", "
352                << thePoint->y() << ", " << thePoint->z() << ")";
353   return *this;
354 }
355
356 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const std::shared_ptr<GeomAPI_Dir>& theDir)
357 {
358   importModule("GeomAPI", "GeomAPI_Dir");
359   myDumpBuffer << "GeomAPI_Dir(" << theDir->x() << ", "
360                << theDir->y() << ", " << theDir->z() << ")";
361   return *this;
362 }
363
364 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
365     const std::shared_ptr<GeomDataAPI_Dir>& theDir)
366 {
367   myDumpBuffer << theDir->x() << ", " << theDir->y() << ", " << theDir->z();
368   return *this;
369 }
370
371 static void dumpArray(std::ostringstream& theOutput, int theSize,
372                       double* theValues, std::string* theTexts)
373 {
374   for (int i = 0; i < theSize; ++i) {
375     if (i > 0)
376       theOutput << ", ";
377     if (theTexts[i].empty())
378       theOutput << theValues[i];
379     else
380       theOutput << "\"" << theTexts[i] << "\"";
381   }
382 }
383
384 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
385     const std::shared_ptr<GeomDataAPI_Point>& thePoint)
386 {
387   static const int aSize = 3;
388   double aValues[aSize] = {thePoint->x(), thePoint->y(), thePoint->z()};
389   std::string aTexts[aSize] = {thePoint->textX(), thePoint->textY(), thePoint->textZ()};
390   dumpArray(myDumpBuffer, aSize, aValues, aTexts);
391   return *this;
392 }
393
394 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
395     const std::shared_ptr<GeomDataAPI_Point2D>& thePoint)
396 {
397   static const int aSize = 2;
398   double aValues[aSize] = {thePoint->x(), thePoint->y()};
399   std::string aTexts[aSize] = {thePoint->textX(), thePoint->textY()};
400   dumpArray(myDumpBuffer, aSize, aValues, aTexts);
401   return *this;
402 }
403
404 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
405     const std::shared_ptr<ModelAPI_AttributeBoolean>& theAttrBool)
406 {
407   myDumpBuffer << (theAttrBool->value() ? "True" : "False");
408   return *this;
409 }
410
411 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
412     const std::shared_ptr<ModelAPI_AttributeInteger>& theAttrInt)
413 {
414   std::string aText = theAttrInt->text();
415   if (aText.empty())
416     myDumpBuffer << theAttrInt->value();
417   else
418     myDumpBuffer << "\"" << aText << "\"";
419   return *this;
420 }
421
422 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
423     const std::shared_ptr<ModelAPI_AttributeDouble>& theAttrReal)
424 {
425   std::string aText = theAttrReal->text();
426   if (aText.empty())
427     myDumpBuffer << theAttrReal->value();
428   else
429     myDumpBuffer << "\"" << aText << "\"";
430   return *this;
431 }
432
433 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
434     const std::shared_ptr<ModelAPI_AttributeString>& theAttrStr)
435 {
436   myDumpBuffer << "\"" << theAttrStr->value() << "\"";
437   return *this;
438 }
439
440 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const FeaturePtr& theEntity)
441 {
442   myDumpBuffer << name(theEntity);
443   if (myNames[theEntity].second)
444     myLastEntityWithName = theEntity;
445   myNotDumpedEntities.erase(theEntity);
446   return *this;
447 }
448
449 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const ResultPtr& theResult)
450 {
451   FeaturePtr aFeature = ModelAPI_Feature::feature(theResult);
452   int anIndex = 0;
453   std::list<ResultPtr> aResults = aFeature->results();
454   for(std::list<ResultPtr>::const_iterator anIt = aResults.cbegin(); anIt != aResults.cend(); ++anIt, ++anIndex) {
455     if(theResult->isSame(*anIt)) {
456       break;
457     }
458   }
459   myDumpBuffer << name(aFeature) << ".result()[" << anIndex << "]";
460   return *this;
461 }
462
463 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const ObjectPtr& theObject)
464 {
465   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObject);
466   if(aFeature.get()) {
467     myDumpBuffer << name(aFeature);
468     return *this;
469   }
470
471   ResultPtr aResult = std::dynamic_pointer_cast<ModelAPI_Result>(theObject);
472   if(aResult.get()) {
473     *this << aResult;
474     return *this;
475   }
476
477   return *this;
478 }
479
480 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const AttributePtr& theAttr)
481 {
482   FeaturePtr anOwner = ModelAPI_Feature::feature(theAttr->owner());
483   myDumpBuffer << name(anOwner) << "." << attributeGetter(anOwner, theAttr->id()) << "()";
484   return *this;
485 }
486
487 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
488     const std::shared_ptr<ModelAPI_AttributeRefAttr>& theRefAttr)
489 {
490   if (theRefAttr->isObject())
491     *this << theRefAttr->object();
492   else
493     *this << theRefAttr->attr();
494   return *this;
495 }
496
497 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
498     const std::shared_ptr<ModelAPI_AttributeRefAttrList>& theRefAttrList)
499 {
500   myDumpBuffer << "[";
501   std::list<std::pair<ObjectPtr, AttributePtr> > aList = theRefAttrList->list();
502   bool isAdded = false;
503   std::list<std::pair<ObjectPtr, AttributePtr> >::const_iterator anIt = aList.begin();
504   for (; anIt != aList.end(); ++anIt) {
505     if (isAdded)
506       myDumpBuffer << ", ";
507     else
508       isAdded = true;
509     if (anIt->first)
510       *this << anIt->first;
511     else if (anIt->second)
512       * this << anIt->second;
513   }
514   myDumpBuffer << "]";
515   return *this;
516 }
517
518 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
519     const std::shared_ptr<ModelAPI_AttributeReference>& theReference)
520 {
521   *this << theReference->value();
522   return *this;
523 }
524
525 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
526     const std::shared_ptr<ModelAPI_AttributeRefList>& theRefList)
527 {
528   static const int aThreshold = 2;
529   // if number of elements in the list if greater than a threshold,
530   // dump it in a separate line with specific name
531   std::string aDumped = myDumpBuffer.str();
532   if (aDumped.empty() || theRefList->size() <= aThreshold) {
533     myDumpBuffer << "[";
534     std::list<ObjectPtr> aList = theRefList->list();
535     bool isAdded = false;
536     std::list<ObjectPtr>::const_iterator anIt = aList.begin();
537     for (; anIt != aList.end(); ++anIt) {
538       if (isAdded)
539         myDumpBuffer << ", ";
540       else
541         isAdded = true;
542
543       *this << *anIt;
544     }
545     myDumpBuffer << "]";
546   } else {
547     // clear buffer and store list "as is"
548     CLEAR_STREAM(myDumpBuffer);
549     *this << theRefList;
550     // save buffer and clear it again
551     std::string aDumpedList = myDumpBuffer.str();
552     CLEAR_STREAM(myDumpBuffer);
553     // obtain name of list
554     FeaturePtr anOwner = ModelAPI_Feature::feature(theRefList->owner());
555     std::string aListName = name(anOwner) + "_objects";
556     // store all previous data
557     myDumpBuffer << aListName << " = " << aDumpedList << std::endl
558                  << aDumped << aListName;
559   }
560   return *this;
561 }
562
563 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
564     const std::shared_ptr<ModelAPI_AttributeSelection>& theAttrSelect)
565 {
566   myDumpBuffer << "model.selection(";
567
568   if(!theAttrSelect->isInitialized()) {
569     myDumpBuffer << ")";
570     return *this;
571   }
572
573   GeomShapePtr aShape = theAttrSelect->value();
574   if(!aShape.get()) {
575     aShape = theAttrSelect->context()->shape();
576   }
577
578   if(!aShape.get()) {
579     myDumpBuffer << ")";
580     return *this;
581   }
582
583   myDumpBuffer << "\"" << aShape->shapeTypeStr() << "\", \"" << theAttrSelect->namingName() << "\")";
584   return *this;
585 }
586
587 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
588     const std::shared_ptr<ModelAPI_AttributeSelectionList>& theAttrSelList)
589 {
590   myDumpBuffer << "[";
591
592   GeomShapePtr aShape;
593   std::string aShapeTypeStr;
594
595   bool isAdded = false;
596
597   for(int anIndex = 0; anIndex < theAttrSelList->size(); ++anIndex) {
598     AttributeSelectionPtr anAttribute = theAttrSelList->value(anIndex);
599     aShape = anAttribute->value();
600     if(!aShape.get()) {
601       aShape = anAttribute->context()->shape();
602     }
603
604     if(!aShape.get()) {
605       continue;
606     }
607
608     if(isAdded) {
609       myDumpBuffer << ", ";
610     } else {
611       isAdded = true;
612     }
613     myDumpBuffer << "model.selection(\"" << aShape->shapeTypeStr() << "\", \"" << anAttribute->namingName() << "\")";
614   }
615
616   myDumpBuffer << "]";
617   return *this;
618 }
619
620 /// Dump std::endl
621 MODELHIGHAPI_EXPORT
622 ModelHighAPI_Dumper& operator<<(ModelHighAPI_Dumper& theDumper,
623                                 std::basic_ostream<char>& (*theEndl)(std::basic_ostream<char>&))
624 {
625   theDumper.myDumpBuffer << theEndl;
626   theDumper.dumpEntitySetName();
627
628   // store all not-dumped entities first
629   std::set<EntityPtr> aNotDumped = theDumper.myNotDumpedEntities;
630   std::string aBufCopy = theDumper.myDumpBuffer.str();
631   theDumper.clear(true);
632   std::set<EntityPtr>::const_iterator anIt = aNotDumped.begin();
633   for (; anIt != aNotDumped.end(); ++anIt) {
634     // if the feature is composite, dump it with all subs
635     CompositeFeaturePtr aCompFeat =
636         std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(*anIt);
637     if (aCompFeat)
638       theDumper.process(aCompFeat, true);
639     else {
640       FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(*anIt);
641       theDumper.dumpFeature(aFeature, true);
642     }
643   }
644
645   // avoid multiple empty lines
646   size_t anInd = std::string::npos;
647   while ((anInd = aBufCopy.find("\n\n\n")) != std::string::npos)
648     aBufCopy.erase(anInd, 1);
649   // then store currently dumped string
650   theDumper.myFullDump << aBufCopy;
651
652   return theDumper;
653 }