0021762: Integration of new Boolean Operation algorithm to OCCT.
[occt.git] / src / BOPAlgo / BOPAlgo_BOP.cxx
CommitLineData
4e57c75e 1// Created by: Peter KURNEV
2// Copyright (c) 1999-2012 OPEN CASCADE SAS
3//
4// The content of this file is subject to the Open CASCADE Technology Public
5// License Version 6.5 (the "License"). You may not use the content of this file
6// except in compliance with the License. Please obtain a copy of the License
7// at http://www.opencascade.org and read it completely before using this file.
8//
9// The Initial Developer of the Original Code is Open CASCADE S.A.S., having its
10// main offices at: 1, place des Freres Montgolfier, 78280 Guyancourt, France.
11//
12// The Original Code and all software distributed under the License is
13// distributed on an "AS IS" basis, without warranty of any kind, and the
14// Initial Developer hereby disclaims all such warranties, including without
15// limitation, any warranties of merchantability, fitness for a particular
16// purpose or non-infringement. Please see the License for the specific terms
17// and conditions governing the rights and limitations under the License.
18
19
20#include <BOPAlgo_BOP.ixx>
21
22#include <TopAbs_ShapeEnum.hxx>
23
24#include <TopoDS_Compound.hxx>
25#include <TopoDS_Iterator.hxx>
26#include <BRep_Builder.hxx>
27#include <TopExp_Explorer.hxx>
28
29#include <BOPTools.hxx>
30#include <BOPTools_AlgoTools.hxx>
31#include <BOPTools_AlgoTools3D.hxx>
32
33#include <BOPCol_ListOfShape.hxx>
34#include <BOPAlgo_BuilderSolid.hxx>
35#include <TopoDS_Edge.hxx>
36
37static
38 TopAbs_ShapeEnum TypeToExplore(const Standard_Integer theDim);
39
40//=======================================================================
41//function :
42//purpose :
43//=======================================================================
44 BOPAlgo_BOP::BOPAlgo_BOP()
45:
46 BOPAlgo_Builder(),
47 myTools(myAllocator),
48 myMapTools(100, myAllocator)
49{
50 myNbArgs=2;
51 Clear();
52}
53//=======================================================================
54//function :
55//purpose :
56//=======================================================================
57 BOPAlgo_BOP::BOPAlgo_BOP(const Handle(NCollection_BaseAllocator)& theAllocator)
58:
59 BOPAlgo_Builder(theAllocator),
60 myTools(myAllocator),
61 myMapTools(100, myAllocator)
62{
63 myNbArgs=2;
64 Clear();
65}
66//=======================================================================
67//function : ~
68//purpose :
69//=======================================================================
70 BOPAlgo_BOP::~BOPAlgo_BOP()
71{
72}
73//=======================================================================
74//function : Clear
75//purpose :
76//=======================================================================
77 void BOPAlgo_BOP::Clear()
78{
79 myOperation=BOPAlgo_UNKNOWN;
80 myTools.Clear();
81 myMapTools.Clear();
82 myDims[0]=-1;
83 myDims[1]=-1;
84 //
85 BOPAlgo_Builder::Clear();
86}
87//=======================================================================
88//function : AddArgument
89//purpose :
90//=======================================================================
91 void BOPAlgo_BOP::AddArgument(const TopoDS_Shape& theShape)
92{
93 if (myMapFence.Add(theShape)) {
94 myArguments.Append(theShape);
95 myArgs[0]=theShape;
96 }
97}
98//=======================================================================
99//function : AddTool
100//purpose :
101//=======================================================================
102 void BOPAlgo_BOP::AddTool(const TopoDS_Shape& theShape)
103{
104 if (myMapTools.Add(theShape)) {
105 myTools.Append(theShape);
106 myArgs[1]=theShape;
107 //
108 if (myMapFence.Add(theShape)) {
109 myArguments.Append(theShape);
110 }
111 }
112}
113//=======================================================================
114//function : Object
115//purpose :
116//=======================================================================
117 const TopoDS_Shape& BOPAlgo_BOP::Object()const
118{
119 return myArgs[0];
120}
121//=======================================================================
122//function : Tool
123//purpose :
124//=======================================================================
125 const TopoDS_Shape& BOPAlgo_BOP::Tool()const
126{
127 return myArgs[1];
128}
129//=======================================================================
130//function : SetOperation
131//purpose :
132//=======================================================================
133 void BOPAlgo_BOP::SetOperation(const BOPAlgo_Operation theOperation)
134{
135 myOperation=theOperation;
136}
137//=======================================================================
138//function : Operation
139//purpose :
140//=======================================================================
141 BOPAlgo_Operation BOPAlgo_BOP::Operation()const
142{
143 return myOperation;
144}
145//=======================================================================
146//function : CheckData
147//purpose :
148//=======================================================================
149 void BOPAlgo_BOP::CheckData()
150{
151 Standard_Integer i, aNb;
152 Standard_Boolean bFlag;
153 //
154 myErrorStatus=0;
155 //
156 aNb=myArguments.Extent();
157 if (aNb!=myNbArgs) {
158 myErrorStatus=10; // invalid number of arguments
159 return;
160 }
161 //
162 BOPAlgo_Builder::CheckData();
163 if (myErrorStatus) {
164 return;
165 }
166 //
167 for (i=0; i<myNbArgs; ++i) {
168 if (myArgs[i].IsNull()) {
169 myErrorStatus=11; // argument is null shape
170 return;
171 }
172 }
173 //
174 for (i=0; i<myNbArgs; ++i) {
175 bFlag = BOPTools_AlgoTools3D::IsEmptyShape(myArgs[i]);
176 if(bFlag) {
177 myWarningStatus = 2;
178 }
179 }
180 //
181 for (i=0; i<myNbArgs; ++i) {
182 myDims[i]=BOPTools_AlgoTools::Dimension(myArgs[i]);
183 if (myDims[i]<0) {
184 myErrorStatus=13; // non-homogenious argument
185 return;
186 }
187 }
188 //
189 if (myOperation==BOPAlgo_UNKNOWN) {
190 myErrorStatus=14; // non-licit oprtation
191 return;
192 }
193 else if (myDims[0]<myDims[1]) {
194 if (myOperation==BOPAlgo_FUSE ||
195 myOperation==BOPAlgo_CUT21) {
196 myErrorStatus=14; // non-licit oprtation for the arguments
197 return;
198 }
199 }
200 else if (myDims[0]>myDims[1]) {
201 if (myOperation==BOPAlgo_FUSE ||
202 myOperation==BOPAlgo_CUT) {
203 myErrorStatus=14; // non-licit oprtation for the arguments
204 return;
205 }
206 }
207}
208//=======================================================================
209//function : Prepare
210//purpose :
211//=======================================================================
212 void BOPAlgo_BOP::Prepare()
213{
214 Standard_Integer i;
215 BRep_Builder aBB;
216 //
217 BOPAlgo_Builder::Prepare();
218 //
219 if(myWarningStatus == 2) {
220 switch(myOperation) {
221 case BOPAlgo_FUSE:
222 for ( i = 0; i < myNbArgs; i++ ) {
223 aBB.Add(myShape, myArgs[i]);
224 }
225 break;
226 case BOPAlgo_COMMON:
227 case BOPAlgo_SECTION:
228 break;
229 case BOPAlgo_CUT:
230 if(BOPTools_AlgoTools3D::IsEmptyShape(myArgs[0])) {
231 break;
232 } else {
233 aBB.Add(myShape, myArgs[0]);
234 }
235 break;
236 case BOPAlgo_CUT21:
237 if(BOPTools_AlgoTools3D::IsEmptyShape(myArgs[1])) {
238 break;
239 } else {
240 aBB.Add(myShape, myArgs[1]);
241 }
242 break;
243 default:
244 break;
245 }
246 }
247}
248//=======================================================================
249//function : PerformInternal
250//purpose :
251//=======================================================================
252 void BOPAlgo_BOP::PerformInternal(const BOPAlgo_PaveFiller& theFiller)
253{
254 myErrorStatus=0;
255 myWarningStatus=0;
256 //
257 myPaveFiller=(BOPAlgo_PaveFiller*)&theFiller;
258 myDS=myPaveFiller->PDS();
259 myContext=myPaveFiller->Context();
260 //
261 // 1. CheckData
262 CheckData();
263 if (myErrorStatus && !myWarningStatus) {
264 return;
265 }
266 //
267 // 2. Prepare
268 Prepare();
269 if (myErrorStatus) {
270 return;
271 }
272 if(myWarningStatus == 2) {
273 return;
274 }
275 //
276 // 3. Fill Images
277 // 3.1 Vertices
278 FillImagesVertices();
279 if (myErrorStatus) {
280 return;
281 }
282 //
283 BuildResult(TopAbs_VERTEX);
284 if (myErrorStatus) {
285 return;
286 }
287 // 3.2 Edges
288 FillImagesEdges();
289 if (myErrorStatus) {
290 return;
291 }
292
293 BuildResult(TopAbs_EDGE);
294 if (myErrorStatus) {
295 return;
296 }
297 //-------------------------------- SECTION f
298 if (myOperation==BOPAlgo_SECTION) {
299 BuildSection();
300 PrepareHistory();
301 PostTreat();
302 return;
303 }
304 //-------------------------------- SECTION t
305 //
306 // 3.3 Wires
307 FillImagesContainers(TopAbs_WIRE);
308 if (myErrorStatus) {
309 return;
310 }
311
312 BuildResult(TopAbs_WIRE);
313 if (myErrorStatus) {
314 return;
315 }
316
317 // 3.4 Faces
318 FillImagesFaces();
319 if (myErrorStatus) {
320 return;
321 }
322
323 BuildResult(TopAbs_FACE);
324 if (myErrorStatus) {
325 return;
326 }
327 // 3.5 Shells
328
329 FillImagesContainers(TopAbs_SHELL);
330 if (myErrorStatus) {
331 return;
332 }
333
334 BuildResult(TopAbs_SHELL);
335 if (myErrorStatus) {
336 return;
337 }
338 // 3.6 Solids
339 FillImagesSolids();
340 if (myErrorStatus) {
341 return;
342 }
343
344 BuildResult(TopAbs_SOLID);
345 if (myErrorStatus) {
346 return;
347 }
348 // 3.7 CompSolids
349 FillImagesContainers(TopAbs_COMPSOLID);
350 if (myErrorStatus) {
351 return;
352 }
353
354 BuildResult(TopAbs_COMPSOLID);
355 if (myErrorStatus) {
356 return;
357 }
358 // 3.8 Compounds
359 FillImagesCompounds();
360 if (myErrorStatus) {
361 return;
362 }
363
364 BuildResult(TopAbs_COMPOUND);
365 if (myErrorStatus) {
366 return;
367 }
368 //
369 // 6.BuildShape;
370 BuildShape();
371 //
372 // 4.History
373 PrepareHistory();
374
375 //
376 // 5 Post-treatment
377 PostTreat();
378}
379//=======================================================================
380//function : BuildShape
381//purpose :
382//=======================================================================
383 void BOPAlgo_BOP::BuildShape()
384{
385 Standard_Integer aDmin, aNbLCB;
386 TopAbs_ShapeEnum aT1, aT2, aTR;
387 TopoDS_Shape aR, aRC;
388 TopoDS_Iterator aIt;
389 BRep_Builder aBB;
390 BOPCol_ListOfShape aLCB;
391 BOPCol_ListIteratorOfListOfShape aItLCB;
392 //
393 myErrorStatus=0;
394 //
395 BuildRC();
396 //myShape=myRC;
397 //
398 aDmin=myDims[1];
399 if (myDims[0]<myDims[1]) {
400 aDmin=myDims[0];
401 }
402 //
403 if (!aDmin) {
404 myShape=myRC;
405 return;
406 }
407 //
408 else if (aDmin==1 || aDmin==2) { //edges, faces
409 aT1=TopAbs_VERTEX;
410 aT2=TopAbs_EDGE;
411 aTR=TopAbs_WIRE;
412 if (aDmin==2) {
413 aT1=TopAbs_EDGE;
414 aT2=TopAbs_FACE;
415 aTR=TopAbs_SHELL;
416 }
417 //
418 BOPTools_AlgoTools::MakeConnexityBlocks(myRC, aT1, aT2, aLCB);
419 aNbLCB=aLCB.Extent();
420 if (!aNbLCB) {
421 myShape=myRC;
422 return;
423 }
424 //
425 BOPTools_AlgoTools::MakeContainer(TopAbs_COMPOUND, aRC);
426 //
427 aItLCB.Initialize(aLCB);
428 for (; aItLCB.More(); aItLCB.Next()) {
429 BOPTools_AlgoTools::MakeContainer(aTR, aR);
430 //
431 const TopoDS_Shape& aCB=aItLCB.Value();
432 aIt.Initialize(aCB);
433 for (; aIt.More(); aIt.Next()) {
434 const TopoDS_Shape& aS=aIt.Value();
435 aBB.Add(aR, aS);
436 }
437 //
438 if (aTR==TopAbs_SHELL) {
439 BOPTools_AlgoTools::OrientFacesOnShell(aR);
440 }
441 //
442 aBB.Add(aRC, aR);
443 }
444 myShape=aRC;
445 }// elase if (aDmin==1 || aDmin==2) {
446
447 else { //aDmin=3
448 if (myOperation==BOPAlgo_FUSE) {
449 BuildSolid();
450 }
451 else {
452 myShape=myRC;
453 }
454 }
455}
456//=======================================================================
457//function : BuildRC
458//purpose :
459//=======================================================================
460 void BOPAlgo_BOP::BuildRC()
461{
462 Standard_Boolean bFlag;
463 Standard_Integer i, aDmin, aNb[2], iX, iY;
464 TopAbs_ShapeEnum aTmin;
465 TopoDS_Compound aC, aCS[2];
466 BRep_Builder aBB;
467 TopExp_Explorer aExp;
468 BOPCol_ListIteratorOfListOfShape aItIm;
469 BOPCol_IndexedMapOfShape aMS[2];
470 BOPCol_IndexedMapOfShape aMSV[2];
471 //
472 myErrorStatus=0;
473 //
474 // A. Fuse
475 if (myOperation==BOPAlgo_FUSE) {
476 aBB.MakeCompound(aC);
477 aTmin=TypeToExplore(myDims[0]);
478 aExp.Init(myShape, aTmin);
479 for (; aExp.More(); aExp.Next()) {
480 const TopoDS_Shape& aS=aExp.Current();
481 aBB.Add(aC, aS);
482 }
483 myRC=aC;
484 return;
485 }
486 //
487 // B. Non-Fuse
488 //
489 // 1. Compounds CS that consist of an Arg or Images of the Arg
490 for (i=0; i<myNbArgs; ++i) {
491 aBB.MakeCompound(aCS[i]);
492 const TopoDS_Shape& aS=myArgs[i];
493 if (myImages.IsBound(aS)){
494 const BOPCol_ListOfShape& aLSIm=myImages.Find(aS);
495 aItIm.Initialize(aLSIm);
496 for (; aItIm.More(); aItIm.Next()) {
497 const TopoDS_Shape& aSIm=aItIm.Value();
498 aBB.Add(aCS[i], aSIm);
499 }
500 }
501 else {
502 aBB.Add(aCS[i], aS);
503 }
504 }
505 //
506 aDmin=myDims[1];
507 if (myDims[0]<myDims[1]) {
508 aDmin=myDims[0];
509 }
510 aTmin=TypeToExplore(aDmin);
511 for (i=0; i<myNbArgs; ++i) {
512 aExp.Init(aCS[i], aTmin);
513 for (; aExp.More(); aExp.Next()) {
514 const TopoDS_Shape aS=aExp.Current();
515 if (aTmin == TopAbs_EDGE) {
516 const TopoDS_Edge& aE = (*(TopoDS_Edge*)(&aS));
517 if (BRep_Tool::Degenerated(aE)) {
518 TopExp_Explorer aExpE(aE, TopAbs_VERTEX);
519 TopoDS_Shape aS1 = aExpE.Current();
520 if (myImages.IsBound(aS1)){
521 const BOPCol_ListOfShape& aLSIm=myImages.Find(aS1);
522 const TopoDS_Shape& aSIm=aLSIm.First();
523 aMSV[i].Add(aSIm);
524 } else {
525 aMSV[i].Add(aS1);
526 }
527 }
528 }
529 //
530 if (myImages.IsBound(aS)){
531 const BOPCol_ListOfShape& aLSIm=myImages.Find(aS);
532 aItIm.Initialize(aLSIm);
533 for (; aItIm.More(); aItIm.Next()) {
534 const TopoDS_Shape& aSIm=aItIm.Value();
535 aMS[i].Add(aSIm);
536 }
537 }
538 else {
539 aMS[i].Add(aS);
540 }
541 }
542 aNb[i]=aMS[i].Extent();
543 }
544 //
545 aBB.MakeCompound(aC);
546 //
547 // 3. Find common parts
548 if (myOperation==BOPAlgo_COMMON) {
549 iX=(aNb[0]>aNb[1])? 1 : 0;
550 iY=(iX+1)%2;
551 }
552 else if (myOperation==BOPAlgo_CUT) {
553 iX=0;
554 iY=1;
555 }
556 else if (myOperation==BOPAlgo_CUT21) {
557 iX=1;
558 iY=0;
559 }
560 for (i=1; i<=aNb[iX]; ++i) {
561 const TopoDS_Shape& aSx=aMS[iX].FindKey(i);
562 bFlag=aMS[iY].Contains(aSx);
563 if (aTmin == TopAbs_EDGE) {
564 const TopoDS_Edge& aE = (*(TopoDS_Edge*)(&aSx));
565 if (BRep_Tool::Degenerated(aE)) {
566 TopExp_Explorer aExpE(aE, TopAbs_VERTEX);
567 TopoDS_Shape aSx1 = aExpE.Current();
568 TopoDS_Shape aSIm;
569 if (myImages.IsBound(aSx1)) {
570 const BOPCol_ListOfShape& aLSIm=myImages.Find(aSx1);
571 aSIm=aLSIm.First();
572 } else {
573 aSIm = aSx1;
574 }
575 bFlag=aMSV[iY].Contains(aSIm);
576 }
577 }
578 //
579 if (myOperation!=BOPAlgo_COMMON) {
580 bFlag=!bFlag;
581 }
582 //
583 if (bFlag) {
584 aBB.Add(aC, aSx);
585 }
586 }
587 //
588 myRC=aC;
589}
590//
591//=======================================================================
592//function : TypeToExplore
593//purpose :
594//=======================================================================
595TopAbs_ShapeEnum TypeToExplore(const Standard_Integer theDim)
596{
597 TopAbs_ShapeEnum aRet;
598 //
599 switch(theDim) {
600 case 0:
601 aRet=TopAbs_VERTEX;
602 break;
603 case 1:
604 aRet=TopAbs_EDGE;
605 break;
606 case 2:
607 aRet=TopAbs_FACE;
608 break;
609 case 3:
610 aRet=TopAbs_SOLID;
611 break;
612 default:
613 aRet=TopAbs_SHAPE;
614 break;
615 }
616 return aRet;
617}
618//=======================================================================
619//function : BuildSolid
620//purpose :
621//=======================================================================
622 void BOPAlgo_BOP::BuildSolid()
623{
624 Standard_Integer i, aNbF, aNbSx, iX, iErr;
625 TopAbs_Orientation aOr, aOr1;
626 TopoDS_Iterator aIt;
627 TopoDS_Shape aRC;
628 BRep_Builder aBB;
629 TopExp_Explorer aExp;
630 BOPCol_IndexedMapOfShape aMFI;
631 BOPCol_IndexedDataMapOfShapeListOfShape aMFS, aMEF;
632 BOPCol_ListIteratorOfListOfShape aItLS;
633 BOPCol_ListOfShape aSFS;
634 BOPAlgo_BuilderSolid aSB;
635 //
636 myErrorStatus=0;
637 //
638 aIt.Initialize(myRC);
639 for (; aIt.More(); aIt.Next()) {
640 const TopoDS_Shape& aSx=aIt.Value();
641 aExp.Init(aSx, TopAbs_FACE);
642 for (; aExp.More(); aExp.Next()) {
643 const TopoDS_Shape& aFx=aExp.Current();
644 //
645 aOr=aFx.Orientation();
646 if (aOr==TopAbs_INTERNAL) {
647 aMFI.Add(aFx);
648 continue;
649 }
650 //
651 if (!aMFS.Contains(aFx)) {
652 BOPCol_ListOfShape aLSx;
653 //
654 aLSx.Append(aSx);
655 aMFS.Add(aFx, aLSx);
656 }
657 else {
658 iX=aMFS.FindIndex(aFx);
659 const TopoDS_Shape& aFx1=aMFS.FindKey(iX);
660 aOr1=aFx1.Orientation();
661 if (aOr1!=aOr) {
662 BOPCol_ListOfShape& aLSx=aMFS.ChangeFromKey(aFx);
663 aLSx.Append(aSx);
664 aMFS.Add(aFx, aLSx);
665 }
666 }
667 }
668 }
669 //
670 BOPCol_ListOfShape aLF, aLFx; //faces that will be added in the end;
671 // SFS
672 aNbF=aMFS.Extent();
673 for (i=1; i<=aNbF; ++i) {
674 const TopoDS_Shape& aFx=aMFS.FindKey(i);
675 const BOPCol_ListOfShape& aLSx=aMFS(i);
676 aNbSx=aLSx.Extent();
677 if (aNbSx==1) {
678 BOPTools::MapShapesAndAncestors(aFx, TopAbs_EDGE, TopAbs_FACE, aMEF);
679 if (IsBoundSplits(aFx, aMEF)){
680 aLFx.Append(aFx);
681 continue;
682 }
683 aLF.Append(aFx);
684 }
685 }
686
687 aItLS.Initialize(aLF);
688 for(; aItLS.More(); aItLS.Next()) {
689 const TopoDS_Shape& aFx=aItLS.Value();
690 aSFS.Append(aFx);
691 }
692 // add faces from aLFx to aSFS;
693 aItLS.Initialize(aLFx);
694 for (; aItLS.More(); aItLS.Next()) {
695 const TopoDS_Shape& aFx=aItLS.Value();
696 aSFS.Append(aFx);
697 }
698 //
699 aNbF=aMFI.Extent();
700 for (i=1; i<=aNbF; ++i) {
701 TopoDS_Shape aFx;
702 //
703 aFx=aMFI.FindKey(i);
704 aFx.Orientation(TopAbs_FORWARD);
705 aSFS.Append(aFx);
706 aFx.Orientation(TopAbs_REVERSED);
707 aSFS.Append(aFx);
708 }
709 //
710 // BuilderSolid
711 BOPTools_AlgoTools::MakeContainer(TopAbs_COMPOUND, aRC);
712 //
713 aSB.SetContext(myContext);
714 aSB.SetShapes(aSFS);
715 aSB.Perform();
716 iErr=aSB.ErrorStatus();
717 if (iErr) {
718 myErrorStatus=30; // SolidBuilder failed
719 return;
720 }
721 //
722 const BOPCol_ListOfShape& aLSR=aSB.Areas();
723 //
724 aItLS.Initialize(aLSR);
725 for (; aItLS.More(); aItLS.Next()) {
726 const TopoDS_Shape& aSR=aItLS.Value();
727 aBB.Add(aRC, aSR);
728 }
729 myShape=aRC;
730}
731
732//=======================================================================
733//function : IsBoundImages
734//purpose :
735//=======================================================================
736 Standard_Boolean BOPAlgo_BOP::IsBoundSplits(const TopoDS_Shape& aS,
737 BOPCol_IndexedDataMapOfShapeListOfShape& aMEF)
738{
739 Standard_Boolean bRet = Standard_False;
740 if (mySplits.IsBound(aS) || myOrigins.IsBound(aS)) {
741 return !bRet;
742 }
743
744 BOPCol_ListIteratorOfListOfShape aIt;
745 Standard_Integer aNbLS;
746 TopAbs_Orientation anOr;
747 //
748 //check face aF may be connected to face from mySplits
749 TopExp_Explorer aExp(aS, TopAbs_EDGE);
750 for (; aExp.More(); aExp.Next()) {
751 const TopoDS_Edge& aE = (*(TopoDS_Edge*)(&aExp.Current()));
752 //
753 anOr = aE.Orientation();
754 if (anOr==TopAbs_INTERNAL) {
755 continue;
756 }
757 //
758 if (BRep_Tool::Degenerated(aE)) {
759 continue;
760 }
761 //
762 const BOPCol_ListOfShape& aLS=aMEF.FindFromKey(aE);
763 aNbLS = aLS.Extent();
764 if (!aNbLS) {
765 continue;
766 }
767 //
768 aIt.Initialize(aLS);
769 for (; aIt.More(); aIt.Next()) {
770 const TopoDS_Shape& aSx = aIt.Value();
771 if (mySplits.IsBound(aSx) || myOrigins.IsBound(aS)) {
772 return !bRet;
773 }
774 }
775 }
776
777 return bRet;
778}