Salome HOME
Merge remote-tracking branch 'remotes/origin/master' into Dev_2.8.0
[modules/shaper.git] / src / Model / Model_Session.cpp
1 // Copyright (C) 2014-2017  CEA/DEN, EDF R&D
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
18 // email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
19 //
20
21 #include <Model_Session.h>
22 #include <ModelAPI_Feature.h>
23 #include <ModelAPI_Plugin.h>
24 #include <Model_Data.h>
25 #include <Model_Document.h>
26 #include <Model_Objects.h>
27 #include <Model_Application.h>
28 #include <Model_Events.h>
29 #include <Model_Validator.h>
30 #include <ModelAPI_Events.h>
31 #include <Events_Loop.h>
32 #include <Events_InfoMessage.h>
33 #include <Config_FeatureMessage.h>
34 #include <Config_AttributeMessage.h>
35 #include <Config_ValidatorMessage.h>
36 #include <Config_ModuleReader.h>
37 #include <Config_ValidatorReader.h>
38
39 #include <ModelAPI_CompositeFeature.h>
40 #include <ModelAPI_ResultPart.h>
41 #include <ModelAPI_Tools.h>
42
43 #include <TDF_ChildIDIterator.hxx>
44 #include <TDF_CopyTool.hxx>
45 #include <TDF_DataSet.hxx>
46 #include <TDF_RelocationTable.hxx>
47 #include <TDF_ClosureTool.hxx>
48
49 #include <TNaming_Builder.hxx>
50 #include <TNaming_Iterator.hxx>
51 #include <TNaming_NamedShape.hxx>
52
53 #include <TopoDS_Shape.hxx>
54
55 static Model_Session* myImpl = new Model_Session();
56
57 // t oredirect all calls to the root document
58 #define ROOT_DOC std::dynamic_pointer_cast<Model_Document>(moduleDocument())
59
60 bool Model_Session::load(const char* theFileName)
61 {
62   bool aRes = ROOT_DOC->load(theFileName, "root", ROOT_DOC);
63   return aRes;
64 }
65
66 bool Model_Session::save(const char* theFileName, std::list<std::string>& theResults)
67 {
68   return ROOT_DOC->save(theFileName, "root", theResults);
69 }
70
71 void Model_Session::closeAll()
72 {
73   Model_Application::getApplication()->deleteAllDocuments();
74 }
75
76 void Model_Session::startOperation(const std::string& theId, const bool theAttachedToNested)
77 {
78   myOperationAttachedToNext = theAttachedToNested;
79   ROOT_DOC->startOperation();
80   ROOT_DOC->operationId(theId);
81   static std::shared_ptr<Events_Message> aStartedMsg
82     (new Events_Message(Events_Loop::eventByName("StartOperation")));
83   Events_Loop::loop()->send(aStartedMsg);
84   // remove all useless documents that has been closed: on start of operation undo/redo is cleared
85   // MPV: this code is dangerous now because it may close the document that is activated right now
86   // but not in the list of the opened documents yet (create, delete, undo, activate Part)
87   // later this must be updated by correct usage of uniques IDs of documents, not names of results
88   //std::list<std::shared_ptr<ModelAPI_Document> > aUsedDocs = allOpenedDocuments();
89   //Model_Application::getApplication()->removeUselessDocuments(aUsedDocs);
90 }
91
92 void Model_Session::finishOperation()
93 {
94   setCheckTransactions(false);
95   ROOT_DOC->finishOperation();
96   if (myOperationAttachedToNext) { // twice, with nested
97     ROOT_DOC->finishOperation();
98     myOperationAttachedToNext = false;
99   }
100   setCheckTransactions(true);
101 }
102
103 void Model_Session::abortOperation()
104 {
105   setCheckTransactions(false);
106   ROOT_DOC->abortOperation();
107   if (myOperationAttachedToNext) { // twice, with nested
108     ROOT_DOC->abortOperation();
109     myOperationAttachedToNext = false;
110   }
111   setCheckTransactions(true);
112   // here the update mechanism may work after abort, so, supress the warnings about
113   // modifications outside of the transactions
114   bool aWasCheck = myCheckTransactions;
115   myCheckTransactions = false;
116   static std::shared_ptr<Events_Message> anAbortMsg
117     (new Events_Message(Events_Loop::eventByName("AbortOperation")));
118   Events_Loop::loop()->send(anAbortMsg);
119   myCheckTransactions = true;
120   myCheckTransactions = aWasCheck;
121 }
122
123 bool Model_Session::isOperation()
124 {
125   return ROOT_DOC->isOperation();
126 }
127
128 bool Model_Session::isModified()
129 {
130   return ROOT_DOC->isModified();
131 }
132
133 bool Model_Session::canUndo()
134 {
135   return ROOT_DOC->canUndo();
136 }
137
138 void Model_Session::undo()
139 {
140   setCheckTransactions(false);
141   ROOT_DOC->undo();
142   setCheckTransactions(true);
143 }
144
145 bool Model_Session::canRedo()
146 {
147   return ROOT_DOC->canRedo();
148 }
149
150 void Model_Session::redo()
151 {
152   setCheckTransactions(false);
153   ROOT_DOC->redo();
154   setCheckTransactions(true);
155 }
156
157 //! Returns stack of performed operations
158 std::list<std::string> Model_Session::undoList()
159 {
160   return ROOT_DOC->undoList();
161 }
162 //! Returns stack of rolled back operations
163 std::list<std::string> Model_Session::redoList()
164 {
165   return ROOT_DOC->redoList();
166 }
167
168 FeaturePtr Model_Session::createFeature(std::string theFeatureID, Model_Document* theDocOwner)
169 {
170   if (this != myImpl) {
171     return myImpl->createFeature(theFeatureID, theDocOwner);
172   }
173
174   // load all information about plugins, features and attributes
175   LoadPluginsInfo();
176
177   if (myPlugins.find(theFeatureID) != myPlugins.end()) {
178     std::pair<std::string, std::string>& aPlugin = myPlugins[theFeatureID]; // plugin and doc kind
179     if (!aPlugin.second.empty() && aPlugin.second != theDocOwner->kind()) {
180       Events_InfoMessage("Model_Session",
181           "Feature '%1' can be created only in document '%2' by the XML definition")
182           .arg(theFeatureID).arg(aPlugin.second).send();
183       return FeaturePtr();
184     }
185     myCurrentPluginName = aPlugin.first;
186     if (myPluginObjs.find(myCurrentPluginName) == myPluginObjs.end()) {
187       // load plugin library if not yet done
188       Config_ModuleReader::loadPlugin(myCurrentPluginName);
189     }
190     if (myPluginObjs.find(myCurrentPluginName) != myPluginObjs.end()) {
191       FeaturePtr aCreated = myPluginObjs[myCurrentPluginName]->createFeature(theFeatureID);
192       if (!aCreated) {
193         Events_InfoMessage("Model_Session",
194             "Can not initialize feature '%1' in plugin '%2'")
195             .arg(theFeatureID).arg(myCurrentPluginName).send();
196       }
197       return aCreated;
198     } else {
199       Events_InfoMessage("Model_Session",
200         "Can not load plugin '%1'").arg(myCurrentPluginName).send();
201     }
202   } else {
203     Events_InfoMessage("Model_Session",
204       "Feature '%1' not found in any plugin").arg(theFeatureID).send();
205   }
206
207   return FeaturePtr();  // return nothing
208 }
209
210 std::shared_ptr<ModelAPI_Document> Model_Session::moduleDocument()
211 {
212   Handle(Model_Application) anApp = Model_Application::getApplication();
213   bool aFirstCall = !anApp->hasRoot();
214   if (aFirstCall) {
215     // to be sure that plugins are loaded,
216     // even before the first "createFeature" call (in unit tests)
217
218     LoadPluginsInfo();
219     // creation of the root document is always outside of the transaction, so, avoid checking it
220     setCheckTransactions(false);
221     anApp->createDocument(0); // 0 is a root ID
222     // creation of the root document is always outside of the transaction, so, avoid checking it
223     setCheckTransactions(true);
224   }
225   return anApp->rootDocument();
226 }
227
228 std::shared_ptr<ModelAPI_Document> Model_Session::document(int theDocID)
229 {
230   return std::shared_ptr<ModelAPI_Document>(
231       Model_Application::getApplication()->document(theDocID));
232 }
233
234 bool Model_Session::hasModuleDocument()
235 {
236   return Model_Application::getApplication()->hasRoot();
237 }
238
239 std::shared_ptr<ModelAPI_Document> Model_Session::activeDocument()
240 {
241   if (!myCurrentDoc || !Model_Application::getApplication()->hasDocument(myCurrentDoc->id()))
242     myCurrentDoc = moduleDocument();
243   return myCurrentDoc;
244 }
245
246 /// makes the last feature in the document as the current
247 static void makeCurrentLast(std::shared_ptr<ModelAPI_Document> theDoc) {
248   if (theDoc.get()) {
249     FeaturePtr aLast = std::dynamic_pointer_cast<Model_Document>(theDoc)->lastFeature();
250     // if last is nested into something else, make this something else as last:
251     // otherwise it will look like edition of sub-element, so, the main will be disabled
252     if (aLast.get()) {
253       CompositeFeaturePtr aMain = ModelAPI_Tools::compositeOwner(aLast);
254       while(aMain.get()) {
255         aLast = aMain;
256         aMain = ModelAPI_Tools::compositeOwner(aLast);
257       }
258     }
259     theDoc->setCurrentFeature(aLast, false);
260   }
261 }
262
263 void Model_Session::setActiveDocument(
264   std::shared_ptr<ModelAPI_Document> theDoc, bool theSendSignal)
265 {
266   if (myCurrentDoc != theDoc) {
267     if (myCurrentDoc.get())
268       myCurrentDoc->setActive(false);
269     if (theDoc.get())
270       theDoc->setActive(true);
271
272     std::shared_ptr<ModelAPI_Document> aPrevious = myCurrentDoc;
273     myCurrentDoc = theDoc;
274     if (theDoc.get() && theSendSignal) {
275       // this must be before the synchronisation call because features in PartSet lower than this
276       // part feature must be disabled and don't recomputed anymore (issue 1156,
277       // translation feature is failed on activation of Part 2)
278       if (isOperation()) { // do it only in transaction, not on opening of document
279         DocumentPtr aRoot = moduleDocument();
280         if (myCurrentDoc != aRoot) {
281           FeaturePtr aPartFeat = ModelAPI_Tools::findPartFeature(aRoot, myCurrentDoc);
282           if (aPartFeat.get()) {
283             aRoot->setCurrentFeature(aPartFeat, false);
284           }
285         }
286       }
287       // syncronize the document: it may be just opened or opened but removed before
288       std::shared_ptr<Model_Document> aDoc = std::dynamic_pointer_cast<Model_Document>(theDoc);
289       if (aDoc.get()) {
290         bool aWasChecked = myCheckTransactions;
291         setCheckTransactions(false);
292         TDF_LabelList anEmptyUpdated;
293         aDoc->objects()->synchronizeFeatures(anEmptyUpdated, true, true, false, true);
294         if (aWasChecked)
295             setCheckTransactions(true);
296       }
297       static std::shared_ptr<Events_Message> aMsg(
298           new Events_Message(Events_Loop::eventByName(EVENT_DOCUMENT_CHANGED)));
299       Events_Loop::loop()->send(aMsg);
300     }
301     // make the current state correct and synchronised in the module and sub-documents
302     if (isOperation()) { // do it only in transaction, not on opening of document
303       if (myCurrentDoc == moduleDocument()) {
304         // make the current feature the latest in root, in previous root current become also last
305         makeCurrentLast(aPrevious);
306         makeCurrentLast(myCurrentDoc);
307       } else {
308         // make the current feature the latest in sub, root current feature becomes this sub
309         makeCurrentLast(myCurrentDoc);
310       }
311     }
312   }
313 }
314
315 std::list<std::shared_ptr<ModelAPI_Document> > Model_Session::allOpenedDocuments()
316 {
317   std::list<std::shared_ptr<ModelAPI_Document> > aResult;
318   aResult.push_back(moduleDocument());
319   // add subs recursively
320   std::list<std::shared_ptr<ModelAPI_Document> >::iterator aDoc = aResult.begin();
321   for(; aDoc != aResult.end(); aDoc++) {
322     DocumentPtr anAPIDoc = *aDoc;
323     std::shared_ptr<Model_Document> aDoc = std::dynamic_pointer_cast<Model_Document>(anAPIDoc);
324     if (aDoc) {
325       const std::set<int> aSubs = aDoc->subDocuments();
326       std::set<int>::const_iterator aSubIter = aSubs.cbegin();
327       for(; aSubIter != aSubs.cend(); aSubIter++) {
328         aResult.push_back(Model_Application::getApplication()->document(*aSubIter));
329       }
330     }
331   }
332   return aResult;
333 }
334
335 bool Model_Session::isLoadByDemand(const std::string theDocID, const int theDocIndex)
336 {
337   return Model_Application::getApplication()->isLoadByDemand(theDocID, theDocIndex);
338 }
339
340 std::shared_ptr<ModelAPI_Document> Model_Session::copy(
341     std::shared_ptr<ModelAPI_Document> theSource, const int theDestID)
342 {
343   std::shared_ptr<Model_Document> aNew = Model_Application::getApplication()->document(theDestID);
344   // make a copy of all labels
345   TDF_Label aSourceRoot = std::dynamic_pointer_cast<Model_Document>(theSource)->document()->Main()
346       .Father();
347   TDF_Label aTargetRoot = aNew->document()->Main().Father();
348   Handle(TDF_DataSet) aDS = new TDF_DataSet;
349   aDS->AddLabel(aSourceRoot);
350   TDF_ClosureTool::Closure(aDS);
351   Handle(TDF_RelocationTable) aRT = new TDF_RelocationTable(Standard_True);
352   aRT->SetRelocation(aSourceRoot, aTargetRoot);
353   TDF_CopyTool::Copy(aDS, aRT);
354
355   // TODO: remove after fix in OCCT.
356   // All named shapes are stored in reversed order, so to fix this we reverse them back.
357   for(TDF_ChildIDIterator aChildIter(aTargetRoot, TNaming_NamedShape::GetID(), true);
358       aChildIter.More();
359       aChildIter.Next()) {
360     Handle(TNaming_NamedShape) aNamedShape =
361       Handle(TNaming_NamedShape)::DownCast(aChildIter.Value());
362     if (aNamedShape.IsNull()) {
363       continue;
364     }
365
366     TopoDS_Shape aShape = aNamedShape->Get();
367     if(aShape.IsNull() || aShape.ShapeType() != TopAbs_COMPOUND) {
368       continue;
369     }
370
371     TNaming_Evolution anEvol = aNamedShape->Evolution();
372     std::list<std::pair<TopoDS_Shape, TopoDS_Shape> > aShapePairs; // to store old and new shapes
373     for(TNaming_Iterator anIter(aNamedShape); anIter.More(); anIter.Next()) {
374       aShapePairs.push_back(
375         std::pair<TopoDS_Shape, TopoDS_Shape>(anIter.OldShape(), anIter.NewShape()));
376     }
377
378     // Add in reverse order.
379     TDF_Label aLabel = aNamedShape->Label();
380     TNaming_Builder aBuilder(aLabel);
381     for(std::list<std::pair<TopoDS_Shape, TopoDS_Shape> >::iterator aPairsIter =
382           aShapePairs.begin();
383         aPairsIter != aShapePairs.end();
384         aPairsIter++) {
385       if (anEvol == TNaming_GENERATED) {
386         aBuilder.Generated(aPairsIter->first, aPairsIter->second);
387       } else if (anEvol == TNaming_MODIFY) {
388         aBuilder.Modify(aPairsIter->first, aPairsIter->second);
389       } else if (anEvol == TNaming_DELETE) {
390         aBuilder.Delete(aPairsIter->first);
391       } else if (anEvol == TNaming_PRIMITIVE) {
392         aBuilder.Generated(aPairsIter->second);
393       } else if (anEvol == TNaming_SELECTED) {
394         aBuilder.Select(aPairsIter->second, aPairsIter->first);
395       }
396     }
397   }
398
399   TDF_LabelList anEmptyUpdated;
400   aNew->objects()->synchronizeFeatures(anEmptyUpdated, true, true, true, true);
401   return aNew;
402 }
403
404 Model_Session::Model_Session()
405 {
406   myPluginsInfoLoaded = false;
407   myCheckTransactions = true;
408   myOperationAttachedToNext = false;
409   ModelAPI_Session::setSession(std::shared_ptr<ModelAPI_Session>(this));
410   // register the configuration reading listener
411   Events_Loop* aLoop = Events_Loop::loop();
412   static const Events_ID kFeatureEvent =
413     Events_Loop::eventByName(Config_FeatureMessage::MODEL_EVENT());
414   aLoop->registerListener(this, kFeatureEvent);
415   aLoop->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_CREATED), 0, true);
416   aLoop->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_UPDATED), 0, true);
417   aLoop->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_DELETED), 0, true);
418   aLoop->registerListener(this, Events_Loop::eventByName(EVENT_VALIDATOR_LOADED));
419 }
420
421 void Model_Session::processEvent(const std::shared_ptr<Events_Message>& theMessage)
422 {
423   static const Events_ID kFeatureEvent =
424     Events_Loop::eventByName(Config_FeatureMessage::MODEL_EVENT());
425   static const Events_ID kValidatorEvent = Events_Loop::eventByName(EVENT_VALIDATOR_LOADED);
426   if (theMessage->eventID() == kFeatureEvent) {
427     const std::shared_ptr<Config_FeatureMessage> aMsg =
428       std::dynamic_pointer_cast<Config_FeatureMessage>(theMessage);
429     if (aMsg) {
430
431       // process the plugin info, load plugin
432       if (myPlugins.find(aMsg->id()) == myPlugins.end()) {
433         myPlugins[aMsg->id()] = std::pair<std::string, std::string>(
434           aMsg->pluginLibrary(), aMsg->documentKind());
435       }
436     } else {
437       const std::shared_ptr<Config_AttributeMessage> aMsgAttr =
438         std::dynamic_pointer_cast<Config_AttributeMessage>(theMessage);
439       if (aMsgAttr) {
440
441         if (!aMsgAttr->isObligatory()) {
442           validators()->registerNotObligatory(aMsgAttr->featureId(), aMsgAttr->attributeId());
443         }
444         if(aMsgAttr->isConcealment()) {
445           validators()->registerConcealment(aMsgAttr->featureId(), aMsgAttr->attributeId());
446         }
447         const std::list<std::pair<std::string, std::string> >& aCases = aMsgAttr->getCases();
448         if (!aCases.empty()) {
449           validators()->registerCase(aMsgAttr->featureId(), aMsgAttr->attributeId(), aCases);
450         }
451       }
452     }
453     // plugins information was started to load, so, it will be loaded
454     myPluginsInfoLoaded = true;
455   } else if (theMessage->eventID() == kValidatorEvent) {
456     std::shared_ptr<Config_ValidatorMessage> aMsg =
457       std::dynamic_pointer_cast<Config_ValidatorMessage>(theMessage);
458     if (aMsg) {
459       if (aMsg->attributeId().empty()) {  // feature validator
460         validators()->assignValidator(aMsg->validatorId(), aMsg->featureId(), aMsg->parameters());
461       } else {  // attribute validator
462         validators()->assignValidator(aMsg->validatorId(), aMsg->featureId(), aMsg->attributeId(),
463                                       aMsg->parameters());
464       }
465     }
466   } else {  // create/update/delete
467     if (myCheckTransactions && !isOperation())
468       Events_InfoMessage("Model_Session",
469         "Modification of data structure outside of the transaction").send();
470     // if part is deleted, make the root as the current document (on undo of Parts creations)
471     static const Events_ID kDeletedEvent = Events_Loop::eventByName(EVENT_OBJECT_DELETED);
472     if (theMessage->eventID() == kDeletedEvent) {
473       std::shared_ptr<ModelAPI_ObjectDeletedMessage> aDeleted =
474         std::dynamic_pointer_cast<ModelAPI_ObjectDeletedMessage>(theMessage);
475       if (aDeleted &&
476           aDeleted->groups().find(ModelAPI_ResultPart::group()) != aDeleted->groups().end())
477       {
478          // check that the current feature of the session is still the active Part (even disabled)
479         bool aFound = false;
480         FeaturePtr aCurrentPart = moduleDocument()->currentFeature(true);
481         if (aCurrentPart.get()) {
482           const std::list<std::shared_ptr<ModelAPI_Result> >& aResList = aCurrentPart->results();
483           std::list<std::shared_ptr<ModelAPI_Result> >::const_iterator aRes = aResList.begin();
484           for(; !aFound && aRes != aResList.end(); aRes++) {
485             ResultPartPtr aPRes = std::dynamic_pointer_cast<ModelAPI_ResultPart>(*aRes);
486             if (aPRes.get() && aPRes->isActivated() && aPRes->partDoc() == activeDocument()) {
487               aFound = true;
488
489             }
490           }
491         }
492         if (!aFound) { // if not, the part was removed, so activate the module document
493           if (myCurrentDoc.get())
494             setActiveDocument(moduleDocument());
495         }
496       }
497     }
498   }
499 }
500
501 void Model_Session::LoadPluginsInfo()
502 {
503   if (myPluginsInfoLoaded)  // nothing to do
504     return;
505   // Read plugins information from XML files
506   Config_ModuleReader aModuleReader(Config_FeatureMessage::MODEL_EVENT());
507   aModuleReader.readAll();
508   std::set<std::string> aFiles = aModuleReader.modulePluginFiles();
509   std::set<std::string>::iterator it = aFiles.begin();
510   for ( ; it != aFiles.end(); it++ ) {
511     Config_ValidatorReader aValidatorReader (*it);
512     aValidatorReader.readAll();
513   };
514
515 }
516
517 void Model_Session::registerPlugin(ModelAPI_Plugin* thePlugin)
518 {
519   myPluginObjs[myCurrentPluginName] = thePlugin;
520   static Events_ID EVENT_LOAD = Events_Loop::loop()->eventByName(EVENT_PLUGIN_LOADED);
521   ModelAPI_EventCreator::get()->sendUpdated(ObjectPtr(), EVENT_LOAD, false);
522   // If the plugin has an ability to process GUI events, register it
523   Events_Listener* aListener = dynamic_cast<Events_Listener*>(thePlugin);
524   if (aListener) {
525     Events_Loop* aLoop = Events_Loop::loop();
526     static Events_ID aStateRequestEventId =
527         Events_Loop::loop()->eventByName(EVENT_FEATURE_STATE_REQUEST);
528     aLoop->registerListener(aListener, aStateRequestEventId);
529   }
530 }
531
532 ModelAPI_ValidatorsFactory* Model_Session::validators()
533 {
534   static Model_ValidatorsFactory* aFactory = new Model_ValidatorsFactory;
535   return aFactory;
536 }
537
538 int Model_Session::transactionID()
539 {
540   return ROOT_DOC->transactionID();
541 }