Salome HOME
Copyright update 2020
[modules/shaper.git] / src / Selector / Selector_FilterByNeighbors.cpp
1 // Copyright (C) 2014-2020  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 email : webmaster.salome@opencascade.com
18 //
19
20 #include <Selector_FilterByNeighbors.h>
21
22 #include <Selector_NameGenerator.h>
23
24 #include <TopoDS.hxx>
25 #include <TopoDS_Builder.hxx>
26 #include <TopoDS_Compound.hxx>
27 #include <TopTools_MapOfShape.hxx>
28 #include <TopExp_Explorer.hxx>
29 #include <TDataStd_Integer.hxx>
30 #include <TDataStd_IntegerArray.hxx>
31 #include <TDF_ChildIterator.hxx>
32
33 // array of the neighbor levels
34 static const Standard_GUID kLEVELS_ARRAY("ee4c4b45-e859-4e86-aa4f-6eac68e0ca1f");
35
36 Selector_FilterByNeighbors::Selector_FilterByNeighbors() : Selector_AlgoWithSubs()
37 {}
38
39 /// Searches neighbor of theLevel of neighborhood to theValue in theContex
40 static void findNeighbors(const TopoDS_Shape theContext, const TopoDS_Shape theValue,
41   const int theLevel, TopTools_MapOfShape& theResult)
42 {
43   TopAbs_ShapeEnum aConnectorType = TopAbs_VERTEX; // type of the connector sub-shapes
44   TopAbs_ShapeEnum aValueType = theValue.ShapeType();
45   if (aValueType == TopAbs_FACE)
46     aConnectorType = TopAbs_EDGE;
47   else if (aValueType == TopAbs_COMPOUND) { // for geometrical naming: compound of faces
48     TopExp_Explorer anExp(theValue, TopAbs_FACE);
49     if (anExp.More()) {
50       aConnectorType = TopAbs_EDGE;
51       aValueType = TopAbs_FACE;
52     } else {
53       aValueType = TopAbs_EDGE;
54     }
55   }
56   TopTools_MapOfShape aNBConnectors; // connector shapes that already belong to neighbors
57   for(TopExp_Explorer aValExp(theValue, aConnectorType); aValExp.More(); aValExp.Next())
58     aNBConnectors.Add(aValExp.Current());
59
60   TopTools_MapOfShape alreadyProcessed;
61   if (aValueType == theValue.ShapeType())
62     alreadyProcessed.Add(theValue);
63   else
64     for(TopExp_Explorer aValExp(theValue, aValueType); aValExp.More(); aValExp.Next())
65       alreadyProcessed.Add(aValExp.Current());
66
67   for(int aLevel = 1; aLevel <= theLevel; aLevel++) {
68     TopoDS_ListOfShape aGoodCandidates;
69     TopExp_Explorer aCandidate(theContext, aValueType);
70     for(; aCandidate.More(); aCandidate.Next()) {
71       if (alreadyProcessed.Contains(aCandidate.Current()))
72         continue;
73       TopExp_Explorer aCandConnector(aCandidate.Current(), aConnectorType);
74       for(; aCandConnector.More(); aCandConnector.Next()) {
75         if (aNBConnectors.Contains(aCandConnector.Current())) // candidate is neighbor
76           break;
77       }
78       if (aCandConnector.More()) {
79         if (aLevel == theLevel) { // add a NB into result: it is connected to other neighbors
80           theResult.Add(aCandidate.Current());
81         } else { // add to the NB of the current level
82           aGoodCandidates.Append(aCandidate.Current());
83         }
84       }
85     }
86     if (aLevel != theLevel) { // good candidates are added to neighbor of this level by connectors
87       for(TopoDS_ListOfShape::Iterator aGood(aGoodCandidates); aGood.More(); aGood.Next()) {
88         TopExp_Explorer aGoodConnector(aGood.Value(), aConnectorType);
89         for(; aGoodConnector.More(); aGoodConnector.Next()) {
90           aNBConnectors.Add(aGoodConnector.Current());
91         }
92         alreadyProcessed.Add(aGood.Value());
93       }
94     }
95   }
96 }
97
98 /// Searches the neighbor shape by neighbors defined in theNB in theContext shape
99 static const TopoDS_Shape findNeighbor(const TopoDS_Shape theContext,
100   const std::list<std::pair<TopoDS_Shape, int> >& theNB, const bool theGeometrical)
101 {
102   // searching for neighbors with minimum level
103   int aMinLevel = 0;
104   std::list<std::pair<TopoDS_Shape, int> >::const_iterator aNBIter = theNB.cbegin();
105   for(; aNBIter != theNB.cend(); aNBIter++) {
106     if (aMinLevel == 0 || aNBIter->second < aMinLevel) {
107       aMinLevel = aNBIter->second;
108     }
109   }
110   // collect all neighbors which are neighbors of sub-shapes with minimum level
111   bool aFirst = true;
112   TopoDS_ListOfShape aMatches;
113   for(aNBIter = theNB.cbegin(); aNBIter != theNB.cend(); aNBIter++) {
114     if (aNBIter->second == aMinLevel) {
115       TopTools_MapOfShape aThisNBs;
116       findNeighbors(theContext, aNBIter->first, aMinLevel, aThisNBs);
117       if (aFirst) { // aMatches must contain common part of all NBs lists
118         for(TopTools_MapOfShape::Iterator aThisNB(aThisNBs); aThisNB.More(); aThisNB.Next()) {
119           aMatches.Append(aThisNB.Value());
120         }
121       } else { // remove all in aMatches which are not in this NBs
122         for(TopoDS_ListOfShape::Iterator aMatch(aMatches); aMatch.More(); ) {
123           if (aThisNBs.Contains(aMatch.Value())) {
124             aMatch.Next();
125           } else {
126             aMatches.Remove(aMatch);
127           }
128         }
129       }
130       aFirst = false;
131     }
132   }
133   if (aMatches.IsEmpty())
134     return TopoDS_Shape(); // not found any candidate
135   if (aMatches.Extent() == 1)
136     return aMatches.First(); // already found good candidate
137   TopoDS_Compound aResultCompound; // in case of geometrical name and many candidates
138   // iterate all matches to find by other (higher level) neighbors the best candidate
139   TopoDS_Shape aGoodCandidate;
140   TopTools_MapOfShape aGoodCandidates; // already added good candidates to the map
141   for(TopoDS_ListOfShape::Iterator aCandidate(aMatches); aCandidate.More(); aCandidate.Next()) {
142     bool aValidCadidate = true;
143     for(int aLevel = aMinLevel + 1; true; aLevel++) {
144       bool aFooundHigherLevel = false;
145       TopoDS_ListOfShape aLevelNBs;
146       for(aNBIter = theNB.cbegin(); aNBIter != theNB.cend(); aNBIter++) {
147         if (aNBIter->second == aLevel)
148           aLevelNBs.Append(aNBIter->first);
149         else if (aNBIter->second >= aLevel)
150           aFooundHigherLevel = true;
151       }
152       if (!aFooundHigherLevel && aLevelNBs.IsEmpty()) { // iterated all, so, good candidate
153         if (aGoodCandidate.IsNull()) {
154           aGoodCandidate = aCandidate.Value();
155         } else { // another good candidate
156           if (theGeometrical && Selector_Algo::sameGeometry(aGoodCandidate, aCandidate.Value())) {
157             if (!aGoodCandidates.Add(aCandidate.Value()))
158               break;
159             static TopoDS_Builder aBuilder;
160             if (aResultCompound.IsNull()) {
161               aBuilder.MakeCompound(aResultCompound);
162               aBuilder.Add(aResultCompound, aGoodCandidate);
163             }
164             aBuilder.Add(aResultCompound, aCandidate.Value());
165           } else
166             return TopoDS_Shape();
167         }
168         break; // no more NBs with higher levels
169       }
170       if (!aLevelNBs.IsEmpty()) {
171         TopTools_MapOfShape aNBsOfCandidate;
172         findNeighbors(theContext, aCandidate.Value(), aLevel, aNBsOfCandidate);
173         // check all stored neighbors are in the map of real neighbors
174         for(TopoDS_ListOfShape::Iterator aLevIter(aLevelNBs); aLevIter.More(); aLevIter.Next()) {
175           if (!aNBsOfCandidate.Contains(aLevIter.Value())) {
176             aValidCadidate = false;
177             break;
178           }
179         }
180       }
181       if (!aValidCadidate) // candidate is not valid, break the checking
182         break;
183     }
184   }
185   if (!aResultCompound.IsNull())
186     return aResultCompound;
187   return aGoodCandidate;
188 }
189
190 bool Selector_FilterByNeighbors::select(
191   const TDF_Label theContextLab, const TopoDS_Shape theContext, const TopoDS_Shape theValue)
192 {
193   myShapeType = theValue.ShapeType();
194   myContext = theContextLab;
195   // searching by neighbors
196   std::list<std::pair<TopoDS_Shape, int> > aNBs; /// neighbor sub-shape -> level of neighborhood
197   for(int aLevel = 1; true; aLevel++) {
198     TopTools_MapOfShape aNewNB;
199     findNeighbors(theContext, theValue, aLevel, aNewNB);
200     if (aNewNB.Extent() == 0) { // there are no neighbors of the given level, stop iteration
201       break;
202     }
203     // iterate by the order in theContext to keep same naming names
204     TopExp_Explorer anOrder(theContext, theValue.ShapeType());
205     TopTools_MapOfShape alreadyProcessed;
206     for (; anOrder.More(); anOrder.Next()) {
207       if (alreadyProcessed.Add(anOrder.Current()) && aNewNB.Contains(anOrder.Current())) {
208         TopoDS_Shape aNewNBShape = anOrder.Current();
209         // check which can be named correctly, without "by neighbors" type
210         Selector_Algo* aSubAlgo = Selector_Algo::select(theContext, aNewNBShape,
211           newSubLabel(), baseDocument(), geometricalNaming(), false, useIntersections());
212         if (aSubAlgo) {
213           // add to list of good NBs
214           aNBs.push_back(std::pair<TopoDS_Shape, int>(aNewNBShape, aLevel));
215         }
216         delete aSubAlgo; // don't keep this sub-algo until all subs and whole validity are checked
217       }
218     }
219     TopoDS_Shape aResult = findNeighbor(theContext, aNBs, geometricalNaming());
220     if (!aResult.IsNull() && aResult.IsSame(theValue)) {
221       std::list<std::pair<TopoDS_Shape, int> >::iterator aNBIter = aNBs.begin();
222       for(; aNBIter != aNBs.end(); aNBIter++) {
223         Selector_Algo* aSubAlgo = Selector_Algo::select(theContext, aNBIter->first,
224           newSubLabel(), baseDocument(), geometricalNaming(), false, useIntersections());
225         if (append(aSubAlgo)) {
226           myNBLevel.push_back(aNBIter->second);
227         } else {
228           delete aSubAlgo;
229         }
230       }
231       return true;
232     }
233   }
234   return false;
235 }
236
237 void Selector_FilterByNeighbors::store()
238 {
239   storeType(Selector_Algo::SELTYPE_FILTER_BY_NEIGHBOR);
240   TDataStd_Integer::Set(label(), shapeTypeID(), (int)myShapeType);
241   // store numbers of levels corresponded to the neighbors in sub-selectors
242   Handle(TDataStd_IntegerArray) anArray =
243     TDataStd_IntegerArray::Set(label(), kLEVELS_ARRAY, 0, int(myNBLevel.size()) - 1);
244   std::list<int>::iterator aLevel = myNBLevel.begin();
245   for(int anIndex = 0; aLevel != myNBLevel.end(); aLevel++, anIndex++) {
246     anArray->SetValue(anIndex, Abs(*aLevel));
247   }
248   // store all sub-selectors
249   std::list<Selector_Algo*>::const_iterator aSubSel = list().cbegin();
250   for(; aSubSel != list().cend(); aSubSel++) {
251     (*aSubSel)->store();
252   }
253   // store context reference if exists
254   if (!myContext.IsNull()) {
255     static const TDF_LabelList anEmptyRefList;
256     storeBaseArray(anEmptyRefList, myContext);
257   }
258 }
259
260 bool Selector_FilterByNeighbors::restore()
261 {
262   Handle(TDataStd_Integer) aShapeTypeAttr;
263   if (!label().FindAttribute(shapeTypeID(), aShapeTypeAttr))
264     return false;
265   myShapeType = TopAbs_ShapeEnum(aShapeTypeAttr->Get());
266   // restore levels indices
267   Handle(TDataStd_IntegerArray) anArray;
268   if (!label().FindAttribute(kLEVELS_ARRAY, anArray))
269     return false;
270   for(int anIndex = 0; anIndex <= anArray->Upper(); anIndex++) {
271     myNBLevel.push_back(anArray->Value(anIndex));
272   }
273   // restore sub-selectors
274   bool aSubResult = true;
275   for(TDF_ChildIterator aSub(label(), false); aSub.More(); aSub.Next()) {
276     Selector_Algo* aSubSel = restoreByLab(aSub.Value(), baseDocument());
277     if (!append(aSubSel, false)) {
278       if (!aSub.Value().HasAttribute())
279         break; // some empty label left in the end
280       // some selector fails, try to use rest selectors, myNBLevel becomes negative: unused
281       if (myNBLevel.size() > list().size()) {
282         std::list<int>::iterator aListIter = myNBLevel.begin();
283         for(int a = 0; a < list().size(); a++)
284           aListIter++;
285         *aListIter = -*aListIter;
286         list().push_back(NULL);
287       }
288     }
289   }
290   // restore context reference if exists
291   static TDF_LabelList anEmptyRefList;
292   restoreBaseArray(anEmptyRefList, myContext);
293
294   return myNBLevel.size() == list().size() && !myNBLevel.empty();
295 }
296
297 TDF_Label Selector_FilterByNeighbors::restoreByName(std::string theName,
298   const TopAbs_ShapeEnum theShapeType, Selector_NameGenerator* theNameGenerator)
299 {
300   myShapeType = theShapeType;
301   TDF_Label aContext;
302   std::string aLastLevel; // last level string (after '(' )  to check the context name in the end
303   for (size_t aStart = 0; aStart != std::string::npos;
304     aStart = theName.find('(', aStart + 1)) {
305     size_t anEndPos = theName.find(')', aStart + 1);
306     if (anEndPos != std::string::npos) {
307       std::string aSubStr = theName.substr(aStart + 1, anEndPos - aStart - 1);
308       TDF_Label aSubContext;
309       Selector_Algo* aSubSel =
310         Selector_Algo::restoreByName(newSubLabel(), baseDocument(), aSubStr, myShapeType,
311           geometricalNaming(), theNameGenerator, aSubContext);
312       if (!append(aSubSel))
313         return TDF_Label();
314
315       if (aSubContext.IsNull()) {
316         delete aSubSel;
317         clearSubAlgos();
318         return TDF_Label();
319       }
320       if (!aContext.IsNull() && !aContext.IsEqual(aSubContext)) {
321         if (!theNameGenerator->isLater(aContext, aSubContext))
322           aContext = aSubContext;
323       } else {
324         aContext = aSubContext;
325       }
326       //if (!aContext.IsNull()) // for filters by neighbor the latest context shape is vital
327       //  aContext = theNameGenerator->newestContext(aContext);
328
329       // searching for the level index
330       std::string aLevel;
331       for (anEndPos++; anEndPos != std::string::npos &&
332         theName[anEndPos] != '(' && theName[anEndPos] != 0;
333         anEndPos++) {
334         aLevel += theName[anEndPos];
335       }
336       aLastLevel = aLevel;
337       if (aLevel.empty() || aLevel[0] == '_')
338         myNBLevel.push_back(1); // by default it is 1
339       else {
340         int aNum = atoi(aLevel.c_str());
341         if (aNum > 0)
342           myNBLevel.push_back(aNum);
343         else
344           return TDF_Label(); // invalid number
345       }
346     } else
347       return TDF_Label(); // invalid parentheses
348   }
349   if (!aLastLevel.empty()) { // get the context
350     size_t aLinePos = aLastLevel.find("_");
351     if (aLinePos != std::string::npos) {
352       std::string aContextName = aLastLevel.substr(aLinePos + 1);
353       if (!aContextName.empty()) {
354         TDF_Label aThisContext, aValue;
355         if (theNameGenerator->restoreContext(aContextName, aThisContext, aValue)) {
356           if (!aThisContext.IsNull())
357             aContext = aThisContext;
358         }
359       }
360     }
361   }
362   myContext = aContext;
363   return aContext;
364 }
365
366 bool Selector_FilterByNeighbors::solve(const TopoDS_Shape& theContext)
367 {
368   TopoDS_Shape aResult;
369
370   std::list<std::pair<TopoDS_Shape, int> > aNBs; /// neighbor sub-shape -> level of neighborhood
371   std::list<int>::iterator aLevel = myNBLevel.begin();
372   std::list<Selector_Algo*>::const_iterator aSubSel = list().cbegin();
373   for(; aSubSel != list().cend(); aSubSel++, aLevel++) {
374     if (*aLevel < 0)
375       continue; // skip because sub-selector is not good
376     if ((*aSubSel)->solve(theContext)) {
377       aNBs.push_back(std::pair<TopoDS_Shape, int>((*aSubSel)->value(), *aLevel));
378     }
379   }
380   if (!aNBs.empty()) {
381     aResult = findNeighbor(theContext, aNBs, geometricalNaming());
382     if (!aResult.IsNull()) {
383       Selector_Algo::store(aResult);
384       return true;
385     }
386   }
387   return false;
388 }
389
390 std::string Selector_FilterByNeighbors::name(Selector_NameGenerator* theNameGenerator)
391 {
392   // (nb1)level_if_more_than_1(nb2)level_if_more_than_1(nb3)level_if_more_than_1
393   bool aThisContextNameNeeded = !myContext.IsNull();
394   std::string aContextName;
395   if (aThisContextNameNeeded)
396     aContextName = theNameGenerator->contextName(myContext);
397   std::string aResult;
398   std::list<int>::iterator aLevel = myNBLevel.begin();
399   std::list<Selector_Algo*>::const_iterator aSubSel = list().cbegin();
400   for(; aSubSel != list().cend(); aSubSel++, aLevel++) {
401     if (!*aSubSel)
402       continue;
403     std::string aSubName = (*aSubSel)->name(theNameGenerator);
404     aResult += "(" + aSubName + ")";
405     if (*aLevel > 1) {
406       std::ostringstream aLevelStr;
407       aLevelStr<<*aLevel;
408       aResult += aLevelStr.str();
409     }
410     // sub-name already contains the needed context name, so, here it is not needed
411     if (aThisContextNameNeeded && (
412          aSubName.find(aContextName) == 0 || aSubName.substr(1).find(aContextName) == 0))
413       aThisContextNameNeeded = false;
414   }
415   if (aThisContextNameNeeded) {
416     aResult = aResult + "_" + aContextName;
417   }
418   return aResult;
419 }