Salome HOME
0023450: Fields are not displayed in GEOM
[modules/geom.git] / src / GEOMImpl / GEOMImpl_IBooleanOperations.cxx
1 // Copyright (C) 2007-2016  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 #include <Standard_Stream.hxx>
24
25 #include <GEOMImpl_IBooleanOperations.hxx>
26
27 #include <GEOM_Function.hxx>
28 #include <GEOM_PythonDump.hxx>
29
30 #include <GEOMImpl_Types.hxx>
31
32 #include <GEOMImpl_BooleanDriver.hxx>
33 #include <GEOMImpl_IBoolean.hxx>
34
35 #include <GEOMImpl_PartitionDriver.hxx>
36 #include <GEOMImpl_IPartition.hxx>
37
38 #include <TDF_Tool.hxx>
39
40 #include "utilities.h"
41
42 #include <Standard_Failure.hxx>
43 #include <Standard_ErrorHandler.hxx> // CAREFUL ! position of this file is critic : see Lucien PIGNOLONI / OCC
44
45 //=============================================================================
46 /*!
47  *   constructor:
48  */
49 //=============================================================================
50 GEOMImpl_IBooleanOperations::GEOMImpl_IBooleanOperations (GEOM_Engine* theEngine, int theDocID)
51 : GEOM_IOperations(theEngine, theDocID)
52 {
53   MESSAGE("GEOMImpl_IBooleanOperations::GEOMImpl_IBooleanOperations");
54 }
55
56 //=============================================================================
57 /*!
58  *  destructor
59  */
60 //=============================================================================
61 GEOMImpl_IBooleanOperations::~GEOMImpl_IBooleanOperations()
62 {
63   MESSAGE("GEOMImpl_IBooleanOperations::~GEOMImpl_IBooleanOperations");
64 }
65
66
67 //=============================================================================
68 /*!
69  *  MakeBoolean
70  */
71 //=============================================================================
72 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeBoolean
73                                   (Handle(GEOM_Object)    theShape1,
74                                    Handle(GEOM_Object)    theShape2,
75                                    const Standard_Integer theOp,
76                                    const Standard_Boolean IsCheckSelfInte)
77 {
78   SetErrorCode(KO);
79
80   if (theShape1.IsNull() || theShape2.IsNull()) return NULL;
81
82   //Add a new Boolean object
83   Handle(GEOM_Object) aBool = GetEngine()->AddObject(GetDocID(), GEOM_BOOLEAN);
84
85   //Add a new Boolean function
86   Handle(GEOM_Function) aFunction;
87   if (theOp == 1) {
88     aFunction = aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_COMMON);
89   } else if (theOp == 2) {
90     aFunction = aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_CUT);
91   } else if (theOp == 3) {
92     aFunction = aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_FUSE);
93   } else if (theOp == 4) {
94     aFunction = aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_SECTION);
95   } else {
96   }
97   if (aFunction.IsNull()) return NULL;
98
99   //Check if the function is set correctly
100   if (aFunction->GetDriverGUID() != GEOMImpl_BooleanDriver::GetID()) return NULL;
101
102   GEOMImpl_IBoolean aCI (aFunction);
103
104   Handle(GEOM_Function) aRef1 = theShape1->GetLastFunction();
105   Handle(GEOM_Function) aRef2 = theShape2->GetLastFunction();
106
107   if (aRef1.IsNull() || aRef2.IsNull()) return NULL;
108
109   aCI.SetShape1(aRef1);
110   aCI.SetShape2(aRef2);
111   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
112
113   //Compute the Boolean value
114   try {
115     OCC_CATCH_SIGNALS;
116     if (!GetSolver()->ComputeFunction(aFunction)) {
117       SetErrorCode("Boolean driver failed");
118       return NULL;
119     }
120   }
121   catch (Standard_Failure) {
122     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
123     SetErrorCode(aFail->GetMessageString());
124     return NULL;
125   }
126
127   //Make a Python command
128   GEOM::TPythonDump pd (aFunction);
129   pd << aBool;
130   if      (theOp == 1) pd << " = geompy.MakeCommon(";
131   else if (theOp == 2) pd << " = geompy.MakeCut(";
132   else if (theOp == 3) pd << " = geompy.MakeFuse(";
133   else if (theOp == 4) pd << " = geompy.MakeSection(";
134   else {}
135   pd << theShape1 << ", " << theShape2;
136
137   if (IsCheckSelfInte) {
138     pd << ", True";
139   }
140
141   pd << ")";
142
143   SetErrorCode(OK);
144   return aBool;
145 }
146
147 //=============================================================================
148 /*!
149  *  MakeFuse
150  */
151 //=============================================================================
152 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeFuse
153                                   (Handle(GEOM_Object)    theShape1,
154                                    Handle(GEOM_Object)    theShape2,
155                                    const bool             IsCheckSelfInte,
156                                    const bool             IsRmExtraEdges)
157 {
158   SetErrorCode(KO);
159
160   if (theShape1.IsNull() || theShape2.IsNull()) return NULL;
161
162   //Add a new Boolean object
163   Handle(GEOM_Object) aBool = GetEngine()->AddObject(GetDocID(), GEOM_BOOLEAN);
164
165   //Add a new Boolean function
166   Handle(GEOM_Function) aFunction =
167     aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_FUSE);
168
169   if (aFunction.IsNull()) return NULL;
170
171   //Check if the function is set correctly
172   if (aFunction->GetDriverGUID() != GEOMImpl_BooleanDriver::GetID()) return NULL;
173
174   GEOMImpl_IBoolean aCI (aFunction);
175
176   Handle(GEOM_Function) aRef1 = theShape1->GetLastFunction();
177   Handle(GEOM_Function) aRef2 = theShape2->GetLastFunction();
178
179   if (aRef1.IsNull() || aRef2.IsNull()) return NULL;
180
181   aCI.SetShape1(aRef1);
182   aCI.SetShape2(aRef2);
183   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
184   aCI.SetRmExtraEdges(IsRmExtraEdges);
185
186   //Compute the Boolean value
187   try {
188     OCC_CATCH_SIGNALS;
189     if (!GetSolver()->ComputeFunction(aFunction)) {
190       SetErrorCode("Boolean driver failed");
191       return NULL;
192     }
193   }
194   catch (Standard_Failure) {
195     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
196     SetErrorCode(aFail->GetMessageString());
197     return NULL;
198   }
199
200   //Make a Python command
201   GEOM::TPythonDump pd (aFunction);
202
203   pd << aBool << " = geompy.MakeFuse(";
204   pd << theShape1 << ", " << theShape2 << ", "
205      << IsCheckSelfInte << ", " << IsRmExtraEdges << ")";
206
207   SetErrorCode(OK);
208   return aBool;
209 }
210
211 //=============================================================================
212 /*!
213  *  MakeFuseList
214  */
215 //=============================================================================
216 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeFuseList
217                   (const Handle(TColStd_HSequenceOfTransient)& theShapes,
218                    const bool                                  IsCheckSelfInte,
219                    const bool                                  IsRmExtraEdges)
220 {
221   SetErrorCode(KO);
222
223   if (theShapes.IsNull()) return NULL;
224
225   //Add a new Boolean object
226   Handle(GEOM_Object) aBool = GetEngine()->AddObject(GetDocID(), GEOM_BOOLEAN);
227
228   //Add a new Boolean function
229   Handle(GEOM_Function) aFunction =
230     aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_FUSE_LIST);
231
232   if (aFunction.IsNull()) return NULL;
233
234   //Check if the function is set correctly
235   if (aFunction->GetDriverGUID() != GEOMImpl_BooleanDriver::GetID()) return NULL;
236
237   GEOMImpl_IBoolean aCI (aFunction);
238
239   TCollection_AsciiString aDescription;
240   Handle(TColStd_HSequenceOfTransient) aShapesSeq =
241     getShapeFunctions(theShapes, aDescription);
242
243   if (aShapesSeq.IsNull()) return NULL;
244
245   aCI.SetShapes(aShapesSeq);
246   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
247   aCI.SetRmExtraEdges(IsRmExtraEdges);
248
249   //Compute the Boolean value
250   try {
251     OCC_CATCH_SIGNALS;
252     if (!GetSolver()->ComputeFunction(aFunction)) {
253       SetErrorCode("Boolean driver failed");
254       return NULL;
255     }
256   }
257   catch (Standard_Failure) {
258     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
259     SetErrorCode(aFail->GetMessageString());
260     return NULL;
261   }
262
263   //Make a Python command
264   GEOM::TPythonDump pd (aFunction);
265
266   pd << aBool << " = geompy.MakeFuseList([" << aDescription.ToCString() << "], "
267      << IsCheckSelfInte << ", " << IsRmExtraEdges << ")";
268
269   SetErrorCode(OK);
270   return aBool;
271 }
272
273 //=============================================================================
274 /*!
275  *  MakeCommonList
276  */
277 //=============================================================================
278 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeCommonList
279                   (const Handle(TColStd_HSequenceOfTransient)& theShapes,
280                    const Standard_Boolean IsCheckSelfInte)
281 {
282   SetErrorCode(KO);
283
284   if (theShapes.IsNull()) return NULL;
285
286   //Add a new Boolean object
287   Handle(GEOM_Object) aBool = GetEngine()->AddObject(GetDocID(), GEOM_BOOLEAN);
288
289   //Add a new Boolean function
290   Handle(GEOM_Function) aFunction =
291     aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_COMMON_LIST);
292
293   if (aFunction.IsNull()) return NULL;
294
295   //Check if the function is set correctly
296   if (aFunction->GetDriverGUID() != GEOMImpl_BooleanDriver::GetID()) return NULL;
297
298   GEOMImpl_IBoolean aCI (aFunction);
299
300   TCollection_AsciiString aDescription;
301   Handle(TColStd_HSequenceOfTransient) aShapesSeq =
302     getShapeFunctions(theShapes, aDescription);
303
304   if (aShapesSeq.IsNull()) return NULL;
305
306   aCI.SetShapes(aShapesSeq);
307   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
308
309   //Compute the Boolean value
310   try {
311     OCC_CATCH_SIGNALS;
312     if (!GetSolver()->ComputeFunction(aFunction)) {
313       SetErrorCode("Boolean driver failed");
314       return NULL;
315     }
316   }
317   catch (Standard_Failure) {
318     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
319     SetErrorCode(aFail->GetMessageString());
320     return NULL;
321   }
322
323   //Make a Python command
324   GEOM::TPythonDump pd (aFunction);
325
326   pd << aBool <<
327     " = geompy.MakeCommonList([" << aDescription.ToCString() << "]";
328
329   if (IsCheckSelfInte) {
330     pd << ", True";
331   }
332
333   pd << ")";
334
335   SetErrorCode(OK);
336   return aBool;
337 }
338
339 //=============================================================================
340 /*!
341  *  MakeCutList
342  */
343 //=============================================================================
344 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeCutList
345                   (Handle(GEOM_Object) theMainShape,
346                    const Handle(TColStd_HSequenceOfTransient)& theShapes,
347                    const Standard_Boolean IsCheckSelfInte)
348 {
349   SetErrorCode(KO);
350
351   if (theShapes.IsNull()) return NULL;
352
353   //Add a new Boolean object
354   Handle(GEOM_Object) aBool = GetEngine()->AddObject(GetDocID(), GEOM_BOOLEAN);
355
356   //Add a new Boolean function
357   Handle(GEOM_Function) aFunction =
358     aBool->AddFunction(GEOMImpl_BooleanDriver::GetID(), BOOLEAN_CUT_LIST);
359
360   if (aFunction.IsNull()) return NULL;
361
362   //Check if the function is set correctly
363   if (aFunction->GetDriverGUID() != GEOMImpl_BooleanDriver::GetID()) return NULL;
364
365   GEOMImpl_IBoolean aCI (aFunction);
366   Handle(GEOM_Function) aMainRef = theMainShape->GetLastFunction();
367
368   if (aMainRef.IsNull()) return NULL;
369
370   TCollection_AsciiString aDescription;
371   Handle(TColStd_HSequenceOfTransient) aShapesSeq =
372     getShapeFunctions(theShapes, aDescription);
373
374   if (aShapesSeq.IsNull()) return NULL;
375
376   aCI.SetShape1(aMainRef);
377   aCI.SetShapes(aShapesSeq);
378   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
379
380   //Compute the Boolean value
381   try {
382     OCC_CATCH_SIGNALS;
383     if (!GetSolver()->ComputeFunction(aFunction)) {
384       SetErrorCode("Boolean driver failed");
385       return NULL;
386     }
387   }
388   catch (Standard_Failure) {
389     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
390     SetErrorCode(aFail->GetMessageString());
391     return NULL;
392   }
393
394   //Make a Python command
395   GEOM::TPythonDump pd (aFunction);
396
397   pd << aBool << " = geompy.MakeCutList("
398     << theMainShape << ", [" << aDescription.ToCString() << "]";
399
400   if (IsCheckSelfInte) {
401     pd << ", True";
402   }
403
404   pd << ")";
405
406   SetErrorCode(OK);
407   return aBool;
408 }
409
410 //=============================================================================
411 /*!
412  *  MakePartition
413  */
414 //=============================================================================
415 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakePartition
416                              (const Handle(TColStd_HSequenceOfTransient)& theShapes,
417                               const Handle(TColStd_HSequenceOfTransient)& theTools,
418                               const Handle(TColStd_HSequenceOfTransient)& theKeepIns,
419                               const Handle(TColStd_HSequenceOfTransient)& theRemoveIns,
420                               const Standard_Integer                      theLimit,
421                               const Standard_Boolean                      theRemoveWebs,
422                               const Handle(TColStd_HArray1OfInteger)&     theMaterials,
423                               const Standard_Integer theKeepNonlimitShapes,
424                               const Standard_Boolean thePerformSelfIntersections,
425                               const Standard_Boolean IsCheckSelfInte)
426 {
427   SetErrorCode(KO);
428
429   //Add a new Partition object
430   Handle(GEOM_Object) aPartition = GetEngine()->AddObject(GetDocID(), GEOM_PARTITION);
431
432   //Add a new Partition function
433   Handle(GEOM_Function) aFunction;
434   if (thePerformSelfIntersections)
435     aFunction = aPartition->AddFunction(GEOMImpl_PartitionDriver::GetID(), PARTITION_PARTITION);
436   else
437     aFunction = aPartition->AddFunction(GEOMImpl_PartitionDriver::GetID(), PARTITION_NO_SELF_INTERSECTIONS);
438   if (aFunction.IsNull()) return NULL;
439
440   //Check if the function is set correctly
441   if (aFunction->GetDriverGUID() != GEOMImpl_PartitionDriver::GetID()) return NULL;
442
443   GEOMImpl_IPartition aCI (aFunction);
444
445   Handle(TColStd_HSequenceOfTransient) aShapesSeq;
446   Handle(TColStd_HSequenceOfTransient) aToolsSeq;
447   Handle(TColStd_HSequenceOfTransient) aKeepInsSeq;
448   Handle(TColStd_HSequenceOfTransient) aRemInsSeq;
449   TCollection_AsciiString aShapesDescr, aToolsDescr, aKeepInsDescr, aRemoveInsDescr;
450
451   // Shapes
452   aShapesSeq = getShapeFunctions(theShapes, aShapesDescr);
453
454   if (aShapesSeq.IsNull()) {
455     SetErrorCode("NULL shape for Partition");
456     return NULL;
457   }
458
459   // Tools
460   aToolsSeq = getShapeFunctions(theTools, aToolsDescr);
461
462   if (aToolsSeq.IsNull()) {
463     SetErrorCode("NULL tool shape for Partition");
464     return NULL;
465   }
466
467   // Keep Inside
468   aKeepInsSeq = getShapeFunctions(theKeepIns, aKeepInsDescr);
469
470   if (aKeepInsSeq.IsNull()) {
471     SetErrorCode("NULL <keep inside> shape for Partition");
472     return NULL;
473   }
474
475   // Remove Inside
476   aRemInsSeq  = getShapeFunctions(theRemoveIns, aRemoveInsDescr);
477
478   if (aRemInsSeq.IsNull()) {
479     SetErrorCode("NULL <remove inside> shape for Partition");
480     return NULL;
481   }
482
483   aCI.SetShapes(aShapesSeq);
484   aCI.SetTools(aToolsSeq);
485   aCI.SetKeepIns(aKeepInsSeq);
486   aCI.SetRemoveIns(aRemInsSeq);
487
488   // Limit
489   aCI.SetLimit(theLimit);
490   aCI.SetKeepNonlimitShapes(theKeepNonlimitShapes);
491   aCI.SetCheckSelfIntersection(IsCheckSelfInte);
492
493   // Materials
494   if (theRemoveWebs) {
495     if (theMaterials.IsNull()) {
496       Handle(TColStd_HArray1OfInteger) aMaterials =
497         new TColStd_HArray1OfInteger (1, aShapesSeq->Length());
498       aMaterials->Init(0);
499       aCI.SetMaterials(aMaterials);
500     } else {
501       aCI.SetMaterials(theMaterials);
502     }
503   }
504
505   //Compute the Partition
506   try {
507     OCC_CATCH_SIGNALS;
508     if (!GetSolver()->ComputeFunction(aFunction)) {
509       SetErrorCode("Partition driver failed");
510       return NULL;
511     }
512   }
513   catch (Standard_Failure) {
514     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
515     SetErrorCode(aFail->GetMessageString());
516     return NULL;
517   }
518
519   //Make a Python command
520   GEOM::TPythonDump pd (aFunction);
521   if (thePerformSelfIntersections)
522     pd << aPartition << " = geompy.MakePartition([";
523   else
524     pd << aPartition << " = geompy.MakePartitionNonSelfIntersectedShape([";
525
526   // Shapes, Tools
527   pd << aShapesDescr.ToCString() << "], [" << aToolsDescr.ToCString() << "], [";
528   // Keep Ins, Remove Ins
529   pd << aKeepInsDescr.ToCString() << "], [" << aRemoveInsDescr.ToCString() << "], ";
530   // Limit, Remove Webs
531   pd << TopAbs_ShapeEnum(theLimit) << ", " << (int)theRemoveWebs << ", [";
532   // Materials
533   if (!theMaterials.IsNull() && theMaterials->Length() > 0) {
534     int i = theMaterials->Lower();
535     pd << theMaterials->Value(i);
536     i++;
537     for (; i <= theMaterials->Upper(); i++) {
538       pd << ", " << theMaterials->Value(i);
539     }
540   }
541   pd << "], " << theKeepNonlimitShapes;
542
543   if (IsCheckSelfInte && !thePerformSelfIntersections) {
544     pd << ", True";
545   }
546
547   pd << ")";
548
549   SetErrorCode(OK);
550   return aPartition;
551 }
552
553 //=============================================================================
554 /*!
555  *  MakeHalfPartition
556  */
557 //=============================================================================
558 Handle(GEOM_Object) GEOMImpl_IBooleanOperations::MakeHalfPartition
559        (Handle(GEOM_Object) theShape, Handle(GEOM_Object) thePlane)
560 {
561   SetErrorCode(KO);
562
563   if (theShape.IsNull() || thePlane.IsNull()) return NULL;
564
565   //Add a new Boolean object
566   Handle(GEOM_Object) aPart = GetEngine()->AddObject(GetDocID(), GEOM_PARTITION);
567
568   //Add a new Partition function
569   Handle(GEOM_Function) aFunction =
570     aPart->AddFunction(GEOMImpl_PartitionDriver::GetID(), PARTITION_HALF);
571   if (aFunction.IsNull()) return NULL;
572
573   //Check if the function is set correctly
574   if (aFunction->GetDriverGUID() != GEOMImpl_PartitionDriver::GetID()) return NULL;
575
576   GEOMImpl_IPartition aCI (aFunction);
577
578   Handle(GEOM_Function) aRef1 = theShape->GetLastFunction();
579   Handle(GEOM_Function) aRef2 = thePlane->GetLastFunction();
580
581   if (aRef1.IsNull() || aRef2.IsNull()) return NULL;
582
583   aCI.SetShape(aRef1);
584   aCI.SetPlane(aRef2);
585
586   //Compute the Partition value
587   try {
588     OCC_CATCH_SIGNALS;
589     if (!GetSolver()->ComputeFunction(aFunction)) {
590       SetErrorCode("Partition driver failed");
591       return NULL;
592     }
593   }
594   catch (Standard_Failure) {
595     Handle(Standard_Failure) aFail = Standard_Failure::Caught();
596     SetErrorCode(aFail->GetMessageString());
597     return NULL;
598   }
599
600   //Make a Python command
601   GEOM::TPythonDump pd (aFunction);
602   pd << aPart << " = geompy.MakeHalfPartition("
603      << theShape << ", " << thePlane << ")";
604
605   SetErrorCode(OK);
606   return aPart;
607 }
608
609 //=============================================================================
610 /*!
611  *  getShapeFunctions
612  */
613 //=============================================================================
614 Handle(TColStd_HSequenceOfTransient)
615   GEOMImpl_IBooleanOperations::getShapeFunctions
616                   (const Handle(TColStd_HSequenceOfTransient)& theObjects,
617                          TCollection_AsciiString &theDescription)
618 {
619   Handle(TColStd_HSequenceOfTransient) aResult =
620     new TColStd_HSequenceOfTransient;
621   Standard_Integer aNbObjects = theObjects->Length();
622   Standard_Integer i;
623   TCollection_AsciiString anEntry;
624   Handle(GEOM_Object) anObj;
625   Handle(GEOM_Function) aRefObj;
626
627   // Shapes
628   for (i = 1; i <= aNbObjects; i++) {
629     anObj = Handle(GEOM_Object)::DownCast(theObjects->Value(i));
630     aRefObj = anObj->GetLastFunction();
631
632     if (aRefObj.IsNull()) {
633       aResult.Nullify();
634       break;
635     }
636
637     aResult->Append(aRefObj);
638
639     // For Python command
640     TDF_Tool::Entry(anObj->GetEntry(), anEntry);
641
642     if (i > 1) {
643       theDescription += ", ";
644     }
645
646     theDescription += anEntry;
647   }
648
649   return aResult;
650 }