1 // Created on: 2015-04-24
2 // Created by: NIKOLAI BUKHALOV
3 // Copyright (c) 2015 OPEN CASCADE SAS
5 // This file is part of Open CASCADE Technology software library.
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
16 #include <BRepBuilderAPI_FastSewing.hxx>
18 #include <BRepTools_Quilt.hxx>
19 #include <Bnd_Box.hxx>
21 #include <Geom2d_Line.hxx>
22 #include <Geom2d_TrimmedCurve.hxx>
23 #include <Geom_Curve.hxx>
24 #include <Geom_RectangularTrimmedSurface.hxx>
25 #include <Geom_Surface.hxx>
27 #include <Precision.hxx>
29 #include <Standard_NullObject.hxx>
31 #include <TopExp_Explorer.hxx>
33 #include <TopTools_MapOfShape.hxx>
36 //=======================================================================
37 //function : IntersetctionOfSets
38 //purpose : Returns minimal value of intersection result
39 //=======================================================================
40 static Standard_Integer
41 IntersectionOfSets( const NCollection_List<Standard_Integer>& theSet1,
42 const NCollection_List<Standard_Integer>& theSet2)
44 const Standard_Integer anIntMax = IntegerLast();
45 Standard_Integer aRetVal = anIntMax;
46 for(NCollection_List<Standard_Integer>::Iterator
47 anIt1 = theSet1.begin().Iterator();
48 anIt1.More(); anIt1.Next())
50 const Standard_Integer aVal1 = anIt1.Value();
51 for(NCollection_List<Standard_Integer>::Iterator
52 anIt2 = theSet2.begin().Iterator();
53 anIt2.More(); anIt2.Next())
55 const Standard_Integer aVal2 = anIt2.Value();
58 //theIntersectionResult.Append(aVal1);
65 if(aRetVal == anIntMax)
71 //=======================================================================
72 //function : Get2DCurve
74 //=======================================================================
75 static Handle(Geom2d_Curve)
76 Get2DCurve( const Standard_Integer theIndex,
77 const Standard_Real theUfirst,
78 const Standard_Real theUlast,
79 const Standard_Real theVfirst,
80 const Standard_Real theVlast,
81 const Standard_Boolean theIsReverse = Standard_False)
83 if((theIndex < 0) || (theIndex > 3))
84 Standard_OutOfRange::Raise("BRepBuilderAPI_FastSewing.cxx, Get2DCurve(): OUT of Range");
86 Handle(Geom2d_Curve) a2dCurv;
94 new Geom2d_TrimmedCurve(new Geom2d_Line(
95 gp_Pnt2d(0.0, theVfirst), gp_Dir2d(1.0,0.0)),
100 new Geom2d_TrimmedCurve(new Geom2d_Line(
101 gp_Pnt2d(theUlast, 0.0), gp_Dir2d(0.0,1.0)),
102 theVfirst, theVlast);
106 new Geom2d_TrimmedCurve(new Geom2d_Line(
107 gp_Pnt2d(0.0, theVlast), gp_Dir2d(1.0,0.0)),
108 theUfirst, theUlast);
112 new Geom2d_TrimmedCurve(new Geom2d_Line(
113 gp_Pnt2d(theUfirst, 0.0), gp_Dir2d(0.0,1.0)),
114 theVfirst, theVlast);
126 new Geom2d_TrimmedCurve(new Geom2d_Line(
127 gp_Pnt2d(theUfirst+theUlast, theVfirst),
129 theUfirst, theUlast);
133 new Geom2d_TrimmedCurve(new Geom2d_Line(
134 gp_Pnt2d(theUlast, theVfirst+theVlast),
136 theVfirst, theVlast);
140 new Geom2d_TrimmedCurve(new Geom2d_Line(
141 gp_Pnt2d(theUfirst+theUlast, theVlast),
143 theUfirst, theUlast);
147 new Geom2d_TrimmedCurve(new Geom2d_Line(
148 gp_Pnt2d(theUfirst, theVfirst+theVlast),
150 theVfirst, theVlast);
160 //=======================================================================
161 //function : Constructor
163 //=======================================================================
164 BRepBuilderAPI_FastSewing::
165 BRepBuilderAPI_FastSewing( const Standard_Real theTol):
171 //=======================================================================
174 //=======================================================================
175 Standard_Boolean BRepBuilderAPI_FastSewing::Add(const TopoDS_Shape& theShape)
177 Standard_Boolean aResult = Standard_False;
178 if(theShape.IsNull())
180 SetStatus(FS_EmptyInput);
184 TopTools_MapOfShape aMS;
186 TopExp_Explorer aFExp(theShape,TopAbs_FACE);
187 for (; aFExp.More(); aFExp.Next())
189 const TopoDS_Face& aFace = TopoDS::Face(aFExp.Current());
192 Handle(Geom_Surface) aSurf = BRep_Tool::Surface(aFace);
195 SetStatus(FS_FaceWithNullSurface);
199 if(aSurf->IsKind(STANDARD_TYPE(Geom_RectangularTrimmedSurface)))
201 SetStatus(FS_NotNaturalBoundsFace);
205 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
206 aSurf->Bounds(aUf, aUl, aVf, aVl);
208 if(Precision::IsInfinite(aUf) || Precision::IsInfinite(aUl) ||
209 Precision::IsInfinite(aVf) || Precision::IsInfinite(aVl))
211 SetStatus(FS_InfiniteSurface);
216 aFFace.mySrcFace = aFace;
217 aFFace.myID = myFaceVec.Length();//because start index is 0
218 myFaceVec.Append(aFFace);
219 aResult = Standard_True;
226 //=======================================================================
229 //=======================================================================
230 Standard_Boolean BRepBuilderAPI_FastSewing::Add(const Handle(Geom_Surface)& theSurface)
232 if(theSurface.IsNull())
234 SetStatus(FS_FaceWithNullSurface);
235 return Standard_False;
238 if(theSurface->IsKind(STANDARD_TYPE(Geom_RectangularTrimmedSurface)))
240 SetStatus(FS_NotNaturalBoundsFace);
241 return Standard_False;
244 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
245 theSurface->Bounds(aUf, aUl, aVf, aVl);
247 if(Precision::IsInfinite(aUf) || Precision::IsInfinite(aUl) ||
248 Precision::IsInfinite(aVf) || Precision::IsInfinite(aVl))
250 SetStatus(FS_InfiniteSurface);
251 return Standard_False;
256 BRep_Builder aBuilder;
257 aBuilder.MakeFace(aFace.mySrcFace);
258 aBuilder.MakeFace(aFace.mySrcFace, theSurface, myTolerance);
259 aBuilder.NaturalRestriction(aFace.mySrcFace, Standard_True);
261 aFace.myID = myFaceVec.Length();//because start index is 0
262 myFaceVec.Append(aFace);
264 return Standard_True;
268 //=======================================================================
271 //=======================================================================
272 void BRepBuilderAPI_FastSewing::Perform(void)
274 if(myFaceVec.IsEmpty())
276 SetStatus(FS_EmptyInput);
283 // create vertices having unique coordinates
284 Standard_Real aRange = Compute3DRange();
285 Handle(NCollection_IncAllocator) anAlloc = new NCollection_IncAllocator;
286 NCollection_CellFilter<NodeInspector>
287 aCells(NodeInspector::Dimension, Max(myTolerance, aRange/IntegerLast()), anAlloc);
289 for(Standard_Integer i = myFaceVec.Lower(); i <= myFaceVec.Upper(); i++)
291 FindVertexes(i, aCells);
295 for(Standard_Integer i = myFaceVec.Lower(); i <= myFaceVec.Upper(); i++)
300 //Create topological structures
302 for(Standard_Integer i = myVertexVec.Lower(); i <= myVertexVec.Upper(); i++)
304 myVertexVec.ChangeValue(i).CreateTopologicalVertex(myTolerance);
308 for(Standard_Integer i = myEdgeVec.Lower(); i <= myEdgeVec.Upper(); i++)
310 myEdgeVec.ChangeValue(i).CreateTopologicalEdge(myVertexVec, myFaceVec, myTolerance);
314 BRepTools_Quilt aQuilt;
317 for(Standard_Integer i = myFaceVec.Lower(); i <= myFaceVec.Upper(); i++)
319 FS_Face& aFace = myFaceVec.ChangeValue(i);
320 aFace.CreateTopologicalWire(myEdgeVec, myTolerance);
321 aFace.CreateTopologicalFace();
322 aQuilt.Add(aFace.myRetFace);
325 myResShape = aQuilt.Shells();
327 catch(Standard_Failure)
329 SetStatus(FS_Exception);
331 //Standard_Failure::Caught()->Print(cout);
337 //=======================================================================
338 //function : UpdateEdgeInfo
340 //=======================================================================
341 void BRepBuilderAPI_FastSewing::UpdateEdgeInfo( const Standard_Integer theIDPrevVertex,
342 const Standard_Integer theIDCurrVertex,
343 const Standard_Integer theFaceID,
344 const Standard_Integer theIDCurvOnFace)
346 //Indeed, two vertices combine into one edge only.
347 const Standard_Integer anEdgeID =
348 IntersectionOfSets(myVertexVec.Value(theIDPrevVertex).myEdges,
349 myVertexVec.Value(theIDCurrVertex).myEdges);
351 //For DEBUG mode only
352 Standard_ProgramError_Raise_if(anEdgeID < 0,
353 "BRepBuilderAPI_FastSewing::UpdateEdgeInfo: Update not existing edge.");
355 FS_Edge& anEdge = myEdgeVec.ChangeValue(anEdgeID);
356 anEdge.myFaces.Append(theFaceID);
357 FS_Face& aFace = myFaceVec.ChangeValue(theFaceID);
358 aFace.SetEdge(theIDCurvOnFace, anEdge.myID);
361 //=======================================================================
362 //function : CreateNewEdge
363 //purpose : Creates FS_Edge
364 //=======================================================================
365 void BRepBuilderAPI_FastSewing::CreateNewEdge(const Standard_Integer theIDPrevVertex,
366 const Standard_Integer theIDCurrVertex,
367 const Standard_Integer theFaceID,
368 const Standard_Integer theIDCurvOnFace)
370 FS_Edge anEdge(theIDPrevVertex, theIDCurrVertex);
371 anEdge.myID = myEdgeVec.Length(); //because start index is 0
374 anEdge.myFaces.Append(theFaceID);
375 FS_Face& aFace = myFaceVec.ChangeValue(theFaceID);
376 aFace.SetEdge(theIDCurvOnFace, anEdge.myID);
378 myVertexVec.ChangeValue(theIDPrevVertex).myEdges.Append(anEdge.myID);
380 if(theIDPrevVertex == theIDCurrVertex)
381 {//the Edge is degenerated
382 SetStatus(FS_Degenerated);
386 myVertexVec.ChangeValue(theIDCurrVertex).myEdges.Append(anEdge.myID);
389 myEdgeVec.Append(anEdge);
392 //=======================================================================
393 //function : FindVertexes
395 //=======================================================================
396 void BRepBuilderAPI_FastSewing::
397 FindVertexes(const Standard_Integer theSurfID,
398 NCollection_CellFilter<NodeInspector>& theCells)
400 const Standard_Integer aNbPoints = 4;
401 FS_Face& aFace = myFaceVec.ChangeValue(theSurfID);
402 const Handle(Geom_Surface) aSurf = BRep_Tool::Surface(aFace.mySrcFace);
403 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
404 aSurf->Bounds(aUf, aUl, aVf, aVl);
406 const gp_Pnt aPnts[aNbPoints] = { aSurf->Value(aUf, aVf),
407 aSurf->Value(aUl, aVf),
408 aSurf->Value(aUl, aVl),
409 aSurf->Value(aUf, aVl)};
411 for(Standard_Integer i = 0; i < aNbPoints; i++)
415 NodeInspector anInspector(myVertexVec, aPnts[i], myTolerance);
418 aBox.Enlarge(myTolerance);
420 theCells.Inspect(aBox.CornerMin().XYZ(), aBox.CornerMax().XYZ(), anInspector);
421 NodeInspector::Target aResID = anInspector.GetResult();
425 aVert.myID = myVertexVec.Length(); //because start index is 0
426 aVert.myPnt = aPnts[i];
427 aVert.myFaces.Append(theSurfID);
428 myVertexVec.Append(aVert);
429 aFace.SetVertex(i, aVert.myID);
431 theCells.Add(aVert.myID, aBox.CornerMin().XYZ(), aBox.CornerMax().XYZ());
434 {//Change existing vertex
435 aFace.SetVertex(i, aResID);
436 myVertexVec.ChangeValue(aResID).myFaces.Append(theSurfID);
441 //=======================================================================
442 //function : FindEdges
444 //=======================================================================
445 void BRepBuilderAPI_FastSewing::FindEdges(const Standard_Integer theSurfID)
447 const Standard_Integer aNbPoints = 4;
448 FS_Face& aFace = myFaceVec.ChangeValue(theSurfID);
450 const Standard_Integer aFirstInd[aNbPoints] = {0, 1, 3, 0};
451 const Standard_Integer aLastInd[aNbPoints] = {1, 2, 2, 3};
453 for(Standard_Integer i = 0; i < aNbPoints; i++)
455 const Standard_Integer aFirstVertIndex = aFirstInd[i],
456 aLastVertIndex = aLastInd[i];
457 const Standard_Integer aFirstVertID = aFace.myVertices[aFirstVertIndex],
458 aLastVertID = aFace.myVertices[aLastVertIndex];
460 if(aFirstVertID == aLastVertID)
461 {//Edge is degenerated.
462 CreateNewEdge(aFirstVertID, aLastVertID, theSurfID, i);
466 //Must be minimal element from list
467 const Standard_Integer anIntRes =
468 IntersectionOfSets(myVertexVec.Value(aFirstVertID).myFaces,
469 myVertexVec.Value(aLastVertID).myFaces);
471 if((anIntRes < 0) || (anIntRes >= theSurfID))
473 CreateNewEdge(aFirstVertID, aLastVertID, theSurfID, i);
476 {//if(theSurfID > anIntRes) => The edge has been processed earlier
477 UpdateEdgeInfo(aFirstVertID, aLastVertID, theSurfID, i);
482 //=======================================================================
483 //function : GetStatuses
485 //=======================================================================
486 BRepBuilderAPI_FastSewing::FS_VARStatuses
487 BRepBuilderAPI_FastSewing::GetStatuses(Standard_OStream* const theOS)
494 *theOS << "Fast Sewing OK!\n";
499 const Standard_Integer aNumMax = 8*sizeof(myStatusList);
500 FS_Statuses anIDS = static_cast<FS_Statuses>(0x0001);
501 for(Standard_Integer i = 1; i <= aNumMax; i++,
502 anIDS = static_cast<FS_Statuses>(anIDS << 1))
504 if((anIDS & myStatusList) == 0)
510 *theOS << "Degenerated case. Try to reduce tolerance.\n";
512 case FS_FindVertexError:
513 *theOS << "Error while creating list of vertices.\n";
515 case FS_FindEdgeError:
516 *theOS << "Error while creating list of edges.\n";
519 *theOS << "Exception during the operation.\n";
521 case FS_FaceWithNullSurface:
522 *theOS << "Source face has null surface.\n";
524 case FS_NotNaturalBoundsFace:
525 *theOS << "Source face has trimmed surface.\n";
527 case FS_InfiniteSurface:
528 *theOS << "Source face has the surface with infinite boundaries.\n";
531 *theOS << "Empty source data.\n";
543 //=======================================================================
544 //function : Compute3DRange
546 //=======================================================================
547 Standard_Real BRepBuilderAPI_FastSewing::Compute3DRange()
551 for(Standard_Integer i = myFaceVec.Lower(); i <= myFaceVec.Upper(); i++)
553 FS_Face& aFace = myFaceVec.ChangeValue(i);
554 const Handle(Geom_Surface) aSurf = BRep_Tool::Surface(aFace.mySrcFace);
557 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
558 aSurf->Bounds(aUf, aUl, aVf, aVl);
560 aBox.Add(aSurf->Value(aUf, aVf));
561 aBox.Add(aSurf->Value(aUl, aVf));
562 aBox.Add(aSurf->Value(aUl, aVl));
563 aBox.Add(aSurf->Value(aUf, aVl));
566 Standard_Real aXm = 0.0, aYm = 0.0, aZm = 0.0, aXM = 0.0, aYM = 0.0, aZM = 0.0;
567 aBox.Get(aXm, aYm, aZm, aXM, aYM, aZM);
568 Standard_Real aDelta = aXM - aXm;
569 aDelta = Max(aDelta, aYM - aYm);
570 aDelta = Max(aDelta, aZM - aZm);
575 //=======================================================================
576 //function : NodeInspector constructor
578 //=======================================================================
579 BRepBuilderAPI_FastSewing::NodeInspector::
580 NodeInspector(const NCollection_Vector<FS_Vertex>& theVec,
581 const gp_Pnt& thePnt,
582 const Standard_Real theTol):
583 myVecOfVertexes(theVec), myPoint(thePnt), myResID(-1)
585 mySQToler = theTol*theTol;
588 //=======================================================================
589 //function : ::NodeInspector::Inspect
591 //=======================================================================
592 NCollection_CellFilter_Action BRepBuilderAPI_FastSewing::
593 NodeInspector::Inspect(const Target theID)
595 const gp_Pnt& aPt = myVecOfVertexes.Value(theID).myPnt;
596 const Standard_Real aSQDist = aPt.SquareDistance(myPoint);
597 if(aSQDist < mySQToler)
603 return CellFilter_Keep;
606 //=======================================================================
607 //function : ::FS_Edge::CreateTopologicalEdge
609 //=======================================================================
610 void BRepBuilderAPI_FastSewing::FS_Edge::
611 CreateTopologicalEdge(const NCollection_Vector<FS_Vertex>& theVertexVec,
612 const NCollection_Vector<FS_Face>& theFaceVec,
613 const Standard_Real theTol)
615 BRep_Builder aBuilder;
617 TopoDS_Vertex aV1 = theVertexVec(myVertices[0]).myTopoVert;
618 TopoDS_Vertex aV2 = theVertexVec(myVertices[1]).myTopoVert;
620 aV1.Orientation(TopAbs_FORWARD);
621 aV2.Orientation(TopAbs_REVERSED);
623 Handle(Geom_Curve) a3dCurv;
624 TopLoc_Location aLocation;
626 const FS_Face& aFace = theFaceVec.Value(myFaces.Value(myFaces.Lower()));
628 //3D-curves in 1st and 2nd faces are considered to be in same-range
629 const Handle(Geom_Surface)& aSurf = BRep_Tool::Surface(aFace.mySrcFace, aLocation);
631 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
632 aSurf->Bounds(aUf, aUl, aVf, aVl);
634 Standard_Integer anEdgeID = -1;
635 for(Standard_Integer anInd = 0; anInd < 4; anInd++)
637 if(myID == aFace.myEdges[anInd])
644 //For DEBUG mode only
645 Standard_ProgramError_Raise_if(anEdgeID < 0,
646 "BRepBuilderAPI_FastSewing::FS_Edge::CreateTopologicalEdge: Single edge.");
650 Handle(Geom2d_Curve) a2dCurv = Get2DCurve(anEdgeID, aUf, aUl, aVf, aVl);
651 const Standard_Real aFPar = a2dCurv->FirstParameter(),
652 aLPar = a2dCurv->LastParameter();
654 aBuilder.MakeEdge(myTopoEdge);
655 aBuilder.UpdateEdge(myTopoEdge, a2dCurv, aSurf, aLocation, theTol);
656 aBuilder.Add(myTopoEdge, aV1);
657 aBuilder.Add(myTopoEdge, aV2);
658 aBuilder.Range(myTopoEdge, aFPar, aLPar);
659 aBuilder.Degenerated(myTopoEdge, Standard_True);
666 a3dCurv = aSurf->VIso(aVf);
669 a3dCurv = aSurf->UIso(aUl);
672 a3dCurv = aSurf->VIso(aVl);
675 a3dCurv = aSurf->UIso(aUf);
678 Standard_OutOfRange::Raise("FS_Edge::CreateTopologicalEdge()");
682 aBuilder.MakeEdge(myTopoEdge, a3dCurv, theTol);
683 aBuilder.Add(myTopoEdge, aV1);
684 aBuilder.Add(myTopoEdge, aV2);
685 aBuilder.Range(myTopoEdge, a3dCurv->FirstParameter(), a3dCurv->LastParameter());
688 //=======================================================================
689 //function : ::FS_Face::CreateTopologicalWire
691 //=======================================================================
692 void BRepBuilderAPI_FastSewing::FS_Face::
693 CreateTopologicalWire(const NCollection_Vector<FS_Edge>& theEdgeVec,
694 const Standard_Real theToler)
696 TopLoc_Location aLocation;
697 //3D-curves in 1st and 2nd faces are considered to be in same-range
698 const Handle(Geom_Surface)& aSurf = BRep_Tool::Surface(mySrcFace, aLocation);
699 Standard_Real aUf = 0.0, aUl = 0.0, aVf = 0.0, aVl = 0.0;
700 aSurf->Bounds(aUf, aUl, aVf, aVl);
704 for(Standard_Integer anEdge = 0; anEdge < 4; anEdge++)
706 Standard_ProgramError_Raise_if(myEdges[anEdge] < 0,
707 "BRepBuilderAPI_FastSewing::FS_Face::CreateTopologicalWire: Wire is not closed.");
709 const BRepBuilderAPI_FastSewing::FS_Edge& aFSEdge = theEdgeVec.Value(myEdges[anEdge]);
710 TopAbs_Orientation anOri = anEdge < 2 ? TopAbs_FORWARD : TopAbs_REVERSED;
711 TopoDS_Edge anTopE = aFSEdge.myTopoEdge;
713 if(aFSEdge.IsDegenerated())
715 anTopE.Orientation(anOri);
716 aB.Add(myWire, anTopE);
720 //Check if 3D and 2D-curve have same-orientation.
721 //If it is not, 2d-curve will be reversed.
723 Standard_Real aFirstPar = 0.0, aLastPar = 0.0;
725 const Handle(Geom_Curve) a3dCurv = BRep_Tool::Curve(anTopE, aFirstPar, aLastPar);
726 Handle(Geom2d_Curve) a2dCurv = Get2DCurve(anEdge, aUf, aUl, aVf, aVl);
727 const gp_Pnt aPref(a3dCurv->Value(aFirstPar));
728 const gp_Pnt2d aP2df(a2dCurv->Value(aFirstPar)), aP2dl(a2dCurv->Value(aLastPar));
729 gp_Pnt aP3df(aSurf->Value(aP2df.X(), aP2df.Y()));
730 gp_Pnt aP3dl(aSurf->Value(aP2dl.X(), aP2dl.Y()));
731 aP3df.Transform(aLocation);
732 aP3dl.Transform(aLocation);
733 const Standard_Real aSqD1 = aP3df.SquareDistance(aPref);
734 const Standard_Real aSqD2 = aP3dl.SquareDistance(aPref);
738 a2dCurv = Get2DCurve(anEdge, aUf, aUl, aVf, aVl, Standard_True);
739 anOri = TopAbs::Reverse(anOri);
742 aB.UpdateEdge(anTopE, a2dCurv, aSurf, aLocation, theToler);
745 anTopE.Orientation(anOri);
747 aB.Add(myWire, anTopE);
750 myWire.Closed(Standard_True);
753 //=======================================================================
754 //function : ::FS_Face::CreateTopologicalFace
756 //=======================================================================
757 void BRepBuilderAPI_FastSewing::FS_Face::CreateTopologicalFace()
759 Standard_ProgramError_Raise_if(myWire.IsNull(),
760 "BRepBuilderAPI_FastSewing::FS_Face::CreateTopologicalFace: Cannot create wire.");
762 BRep_Builder aBuilder;
763 myRetFace = TopoDS::Face(mySrcFace.EmptyCopied());
764 aBuilder.Add(myRetFace, myWire);
765 aBuilder.NaturalRestriction(myRetFace, Standard_True);