1 // Copyright (C) 2014-2017 CEA/DEN, EDF R&D
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.
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.
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
17 // See http://www.salome-platform.org/ or
18 // email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
21 #include "XGUI_InspectionPanel.h"
22 #include "XGUI_SelectionMgr.h"
23 #include "XGUI_Selection.h"
24 #include "XGUI_Tools.h"
26 #include <ModuleBase_ViewerPrs.h>
27 #include <ModuleBase_Tools.h>
29 #include <ModelAPI_Result.h>
31 #include <GeomAPI_Ax3.h>
32 #include <GeomAPI_Box.h>
33 #include <GeomAPI_Circ.h>
34 #include <GeomAPI_Cone.h>
35 #include <GeomAPI_Cylinder.h>
36 #include <GeomAPI_Edge.h>
37 #include <GeomAPI_Ellipse.h>
38 #include <GeomAPI_Face.h>
39 #include <GeomAPI_Pln.h>
40 #include <GeomAPI_Pnt.h>
41 #include <GeomAPI_Shell.h>
42 #include <GeomAPI_Solid.h>
43 #include <GeomAPI_Sphere.h>
44 #include <GeomAPI_Torus.h>
45 #include <GeomAPI_Vertex.h>
46 #include <GeomAPI_Wire.h>
49 #include <QScrollArea>
52 #include <QTableWidget>
53 #include <QHeaderView>
54 #include <QTextBrowser>
55 #include <QResizeEvent>
57 #include <BRepBndLib.hxx>
58 #include <TopoDS_Iterator.hxx>
59 #include <TopTools_MapOfShape.hxx>
60 #include <TopTools_ListOfShape.hxx>
61 #include <Standard_ErrorHandler.hxx> // CAREFUL ! position of this file is critic
63 // ================ Auxiliary functions ================
64 #define TITLE(val) ("<b>" + (val) + "</b>")
66 static void appendPointToParameters(const QString& thePointTitle,
67 const GeomPointPtr& theCoord,
70 theParams += TITLE(thePointTitle) +
71 "<br> X: " + QString::number(theCoord->x()) +
72 "<br> Y: " + QString::number(theCoord->y()) +
73 "<br> Z: " + QString::number(theCoord->z()) +
77 static void appendDirToParameters(const QString& theDirTitle,
78 const GeomDirPtr& theDirection,
81 theParams += TITLE(theDirTitle) +
82 "<br> DX: " + QString::number(theDirection->x()) +
83 "<br> DY: " + QString::number(theDirection->y()) +
84 "<br> DZ: " + QString::number(theDirection->z()) +
88 static void appendGroupNameToParameters(const QString& theGroupTitle, QString& theParams)
90 theParams += TITLE(theGroupTitle) + "<br>";
93 static void appendNamedValueToParameters(const QString& theName,
94 const double theValue,
97 theParams += theName + ": " + QString::number(theValue) + "<br>";
100 static void appendNamedValueToParameters(const QString& theName,
104 theParams += theName + ": " + (theValue ? "True" : "False") + "<br>";
108 // ================ XGUI_InspectionPanel ================
110 XGUI_InspectionPanel::XGUI_InspectionPanel(QWidget* theParent, XGUI_SelectionMgr* theMgr)
111 : QDockWidget(theParent),
112 mySelectionMgr(theMgr)
114 setWindowTitle(tr("Inspection Panel"));
115 setObjectName(INSPECTION_PANEL);
116 setStyleSheet("::title { position: relative; padding-left: 5px; text-align: left center }");
118 QScrollArea* aScrollArea = new QScrollArea(this);
119 setWidget(aScrollArea);
121 // Create an internal widget
122 myMainWidget = new QWidget(aScrollArea);
124 myMainLayout = new QVBoxLayout(myMainWidget);
125 myMainLayout->setContentsMargins(5, 5, 5, 5);
127 QWidget* aNameWgt = new QWidget(myMainWidget);
128 QHBoxLayout* aNameLayout = new QHBoxLayout(aNameWgt);
129 aNameLayout->setContentsMargins(0, 0, 0, 0);
130 aNameLayout->addWidget(new QLabel(tr("Object"), aNameWgt));
131 myNameEdt = new QLineEdit(aNameWgt);
132 myNameEdt->setReadOnly(true);
133 aNameLayout->addWidget(myNameEdt);
135 myMainLayout->addWidget(aNameWgt);
137 // Table with sub-shapes
138 mySubShapesTab = new QTableWidget(9, 2, myMainWidget);
139 mySubShapesTab->setFocusPolicy(Qt::NoFocus);
140 mySubShapesTab->verticalHeader()->hide();
142 aTitles << tr("Sub-shapes") << tr("Number");
143 mySubShapesTab->setHorizontalHeaderLabels(aTitles);
145 QStringList aSubShapes;
146 aSubShapes << "SHAPE" << "COMPOUND" << "COMPSOLID" <<
147 "SOLID" << "SHELL" << "FACE" << "WIRE" << "EDGE" << "VERTEX";
149 foreach(QString aType, aSubShapes) {
150 QTableWidgetItem* aItem = new QTableWidgetItem(aType);
151 aItem->setFlags(Qt::ItemIsEnabled);
152 mySubShapesTab->setItem(i++, 0, aItem);
154 for (i = 0; i < 9; i++) {
155 QTableWidgetItem* aItem = new QTableWidgetItem("");
156 aItem->setFlags(Qt::ItemIsEnabled);
157 aItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
158 mySubShapesTab->setItem(i, 1, aItem);
160 mySubShapesTab->setColumnWidth(0, 90);
161 mySubShapesTab->setColumnWidth(1, 70);
163 mySubShapesTab->setMaximumWidth(170);
164 mySubShapesTab->setMinimumHeight(300);
166 myMainLayout->addWidget(mySubShapesTab);
169 QWidget* aTypeWgt = new QWidget(myMainWidget);
170 QHBoxLayout* aTypeLayout = new QHBoxLayout(aTypeWgt);
171 aTypeLayout->setContentsMargins(0, 0, 0, 0);
173 aTypeLayout->addWidget(new QLabel(tr("Type:"), aTypeWgt));
174 myTypeLbl = new QLabel("", aTypeWgt);
175 aTypeLayout->addWidget(myTypeLbl);
177 myMainLayout->addWidget(aTypeWgt);
179 myTypeParams = new QTextBrowser(myMainWidget);
180 myTypeParams->setFixedWidth(170);
181 myTypeParams->setReadOnly(true);
182 myTypeParams->setFocusPolicy(Qt::NoFocus);
183 myTypeParams->setFrameStyle(QFrame::NoFrame);
184 myTypeParams->viewport()->setBackgroundRole(QPalette::Window);
186 myMainLayout->addWidget(myTypeParams, 1);
188 aScrollArea->setWidget(myMainWidget);
190 connect(mySelectionMgr, SIGNAL(selectionChanged()), SLOT(onSelectionChanged()));
193 //********************************************************************
194 XGUI_InspectionPanel::~XGUI_InspectionPanel()
198 //********************************************************************
199 void XGUI_InspectionPanel::setSubShapeValue(SudShape theId, int theVal)
201 mySubShapesTab->item(theId, 1)->setText(QString::number(theVal));
204 //********************************************************************
205 void XGUI_InspectionPanel::clearContent()
208 for (int i = 0; i <= VertexId; i++) {
209 mySubShapesTab->item((SudShape)i, 1)->setText("");
211 myTypeLbl->setText("");
215 //********************************************************************
216 void XGUI_InspectionPanel::onSelectionChanged()
219 XGUI_Selection* aSelection = mySelectionMgr->selection();
220 QList<ModuleBase_ViewerPrsPtr> aSelectedList =
221 aSelection->getSelected(ModuleBase_ISelection::Viewer);
222 if (aSelectedList.count() > 0) {
223 ModuleBase_ViewerPrsPtr aPrs = aSelectedList.first();
224 TopoDS_Shape aShape = ModuleBase_Tools::getSelectedShape(aPrs);
227 setName(XGUI_Tools::generateName(aPrs));
228 setShapeContent(aShape);
229 setShapeParams(aShape);
233 //********************************************************************
234 void XGUI_InspectionPanel::setName(const QString& theName)
236 myNameEdt->setText(theName);
239 //********************************************************************
240 void XGUI_InspectionPanel::setShapeContent(const TopoDS_Shape& theShape)
244 int iType, nbTypes[TopAbs_SHAPE];
245 for (iType = 0; iType < TopAbs_SHAPE; ++iType) {
248 nbTypes[theShape.ShapeType()]++;
250 TopTools_MapOfShape aMapOfShape;
251 aMapOfShape.Add(theShape);
252 TopTools_ListOfShape aListOfShape;
253 aListOfShape.Append(theShape);
255 TopTools_ListIteratorOfListOfShape itL(aListOfShape);
256 for (; itL.More(); itL.Next()) {
257 TopoDS_Shape sp = itL.Value();
258 TopoDS_Iterator it(sp);
259 for (; it.More(); it.Next()) {
260 TopoDS_Shape s = it.Value();
261 if (aMapOfShape.Add(s)) {
262 aListOfShape.Append(s);
263 nbTypes[s.ShapeType()]++;
267 setSubShapeValue(VertexId, nbTypes[TopAbs_VERTEX]);
268 setSubShapeValue(EdgeId, nbTypes[TopAbs_EDGE]);
269 setSubShapeValue(WireId, nbTypes[TopAbs_WIRE]);
270 setSubShapeValue(FaceId, nbTypes[TopAbs_FACE]);
271 setSubShapeValue(ShellId, nbTypes[TopAbs_SHELL]);
272 setSubShapeValue(SolidId, nbTypes[TopAbs_SOLID]);
273 setSubShapeValue(CompsolidId, nbTypes[TopAbs_COMPSOLID]);
274 setSubShapeValue(CompoundId, nbTypes[TopAbs_COMPOUND]);
275 setSubShapeValue(ShapeId, aMapOfShape.Extent());
277 catch (Standard_Failure) {
281 //********************************************************************
282 void XGUI_InspectionPanel::setShapeParams(const TopoDS_Shape& theShape)
284 GeomShapePtr aShape(new GeomAPI_Shape);
285 aShape->setImpl(new TopoDS_Shape(theShape));
287 switch (aShape->shapeType()) {
288 case GeomAPI_Shape::VERTEX:
289 fillVertex(aShape->vertex());
291 case GeomAPI_Shape::EDGE:
292 fillEdge(aShape->edge());
294 case GeomAPI_Shape::FACE:
295 fillFace(aShape->face());
297 case GeomAPI_Shape::SOLID:
298 fillSolid(aShape->solid());
300 case GeomAPI_Shape::WIRE:
301 fillWire(aShape->wire());
303 case GeomAPI_Shape::SHELL:
304 fillShell(aShape->shell());
306 case GeomAPI_Shape::COMPSOLID:
307 case GeomAPI_Shape::COMPOUND:
308 fillContainer(aShape);
313 //********************************************************************
314 void XGUI_InspectionPanel::fillVertex(const GeomVertexPtr& theVertex)
316 GeomPointPtr aPoint = theVertex->point();
318 myTypeLbl->setText(tr("Vertex"));
321 appendPointToParameters(tr("Coordinates"), aPoint, aParams);
322 setParamsText(aParams);
325 //********************************************************************
326 void XGUI_InspectionPanel::fillEdge(const GeomEdgePtr& theEdge)
329 if (theEdge->isDegenerated())
330 appendNamedValueToParameters(tr("Degenerated"), true, aParams);
332 GeomPointPtr aStartPnt = theEdge->firstPoint();
333 GeomPointPtr aEndPnt = theEdge->lastPoint();
334 bool addStartEndPoints = false;
336 if (theEdge->isLine()) {
337 myTypeLbl->setText(tr("Line segment"));
338 addStartEndPoints = true;
341 GeomCirclePtr aCircle = theEdge->circle();
343 addStartEndPoints = aStartPnt->distance(aEndPnt) >= Precision::Confusion();
344 if (addStartEndPoints)
345 myTypeLbl->setText("Arc of circle");
347 myTypeLbl->setText("Circle");
349 appendPointToParameters(tr("Center"), aCircle->center(), aParams);
350 appendDirToParameters(tr("Normal"), aCircle->normal(), aParams);
351 appendGroupNameToParameters(tr("Dimensions"), aParams);
352 appendNamedValueToParameters(tr("Radius"), aCircle->radius(), aParams);
355 GeomEllipsePtr anEllipse = theEdge->ellipse();
357 addStartEndPoints = aStartPnt->distance(aEndPnt) >= Precision::Confusion();
358 if (addStartEndPoints)
359 myTypeLbl->setText("Arc of ellipse");
361 myTypeLbl->setText("Ellipse");
363 appendPointToParameters(tr("Center"), anEllipse->center(), aParams);
364 appendDirToParameters(tr("Normal"), anEllipse->normal(), aParams);
365 appendGroupNameToParameters(tr("Dimensions"), aParams);
366 appendNamedValueToParameters(tr("Major radius"), anEllipse->majorRadius(), aParams);
367 appendNamedValueToParameters(tr("Minor radius"), anEllipse->minorRadius(), aParams);
371 myTypeLbl->setText(tr("Edge"));
375 if (addStartEndPoints) {
376 appendPointToParameters(tr("Start point"), aStartPnt, aParams);
377 appendPointToParameters(tr("End point"), aEndPnt, aParams);
379 setParamsText(aParams);
382 //********************************************************************
383 void XGUI_InspectionPanel::fillWire(const GeomWirePtr& theWire)
386 appendNamedValueToParameters(tr("Closed"), theWire->isClosed(), aParams);
388 // check the wire is a polygon
389 std::list<GeomPointPtr> aPolygonPoints;
390 if (theWire->isPolygon(aPolygonPoints)) {
391 myTypeLbl->setText(tr("Polygon"));
392 int aCornerIndex = 0;
393 for (std::list<GeomPointPtr>::const_iterator aPtIt = aPolygonPoints.begin();
394 aPtIt != aPolygonPoints.end(); ++aPtIt)
395 appendPointToParameters(tr("Point") + " " + QString::number(++aCornerIndex),
399 myTypeLbl->setText(tr("Wire"));
401 setParamsText(aParams);
404 //********************************************************************
405 void XGUI_InspectionPanel::fillFace(const GeomFacePtr& theFace)
408 // 1. Plane and planar faces
409 GeomPlanePtr aPlane = theFace->getPlane();
411 bool isCommonCase = true;
412 // Check face bounded by circle or ellipse
413 std::list<GeomShapePtr> aSubs = theFace->subShapes(GeomAPI_Shape::EDGE);
414 if (aSubs.size() == 1) {
415 GeomEdgePtr anEdge = aSubs.front()->edge();
416 if (anEdge->isCircle() || anEdge->isEllipse()) {
418 isCommonCase = false;
422 // Check face bounded by a single wire which is rectangle
423 aSubs = theFace->subShapes(GeomAPI_Shape::WIRE);
424 if (aSubs.size() == 1) {
425 GeomWirePtr aWire = aSubs.front()->wire();
426 std::list<GeomPointPtr> aCorners;
427 if (aWire->isRectangle(aCorners)) {
428 GeomPointPtr aBaseCorner = aCorners.front();
429 aCorners.pop_front();
431 double aWidth = aBaseCorner->distance(aCorners.front());
432 double aHeight = aBaseCorner->distance(aCorners.back());
434 myTypeLbl->setText(tr("Rectangle"));
435 appendPointToParameters(tr("Corner"), aBaseCorner, aParams);
436 appendDirToParameters(tr("Normal"), aPlane->direction(), aParams);
437 appendGroupNameToParameters(tr("Dimensions"), aParams);
438 appendNamedValueToParameters(tr("Width"), aWidth, aParams);
439 appendNamedValueToParameters(tr("Height"), aHeight, aParams);
440 setParamsText(aParams);
442 isCommonCase = false;
448 setPlaneType(tr("Plane"), aPlane);
452 GeomSpherePtr aSphere = theFace->getSphere();
454 setSphereType(tr("Sphere"), aSphere);
457 GeomCylinderPtr aCylinder = theFace->getCylinder();
459 setCylinderType(tr("Cylinder"), aCylinder);
462 GeomConePtr aCone = theFace->getCone();
464 setConeType(tr("Cone"), aCone);
467 GeomTorusPtr aTorus = theFace->getTorus();
469 setTorusType(tr("Torus"), aTorus);
472 myTypeLbl->setText(tr("Face"));
479 //********************************************************************
480 void XGUI_InspectionPanel::fillShell(const GeomShellPtr& theShell)
483 GeomSpherePtr aSphere = theShell->getSphere();
485 setSphereType(tr("Sphere"), aSphere);
488 GeomCylinderPtr aCylinder = theShell->getCylinder();
490 setCylinderType(tr("Cylinder"), aCylinder);
493 GeomConePtr aCone = theShell->getCone();
495 setConeType(tr("Cone"), aCone);
498 GeomTorusPtr aTorus = theShell->getTorus();
500 setTorusType(tr("Torus"), aTorus);
502 // 5. Axis-aligned/Rotated Box
503 GeomBoxPtr aBox = theShell->getParallelepiped();
505 if (aBox->isAxesAligned())
506 setBoxType(tr("Box"), aBox);
508 setRotatedBoxType(tr("Rotated Box"), aBox);
512 myTypeLbl->setText(tr("Shell"));
519 //********************************************************************
520 void XGUI_InspectionPanel::fillSolid(const GeomSolidPtr& theSolid)
523 GeomSpherePtr aSphere = theSolid->getSphere();
525 setSphereType(tr("Sphere"), aSphere);
528 GeomCylinderPtr aCylinder = theSolid->getCylinder();
530 setCylinderType(tr("Cylinder"), aCylinder);
533 GeomConePtr aCone = theSolid->getCone();
535 setConeType(tr("Cone"), aCone);
538 GeomTorusPtr aTorus = theSolid->getTorus();
540 setTorusType(tr("Torus"), aTorus);
542 // 5. Axis-aligned/Rotated Box
543 GeomBoxPtr aBox = theSolid->getParallelepiped();
545 if (aBox->isAxesAligned())
546 setBoxType(tr("Box"), aBox);
548 setRotatedBoxType(tr("Rotated Box"), aBox);
552 myTypeLbl->setText(tr("Solid"));
559 //********************************************************************
560 void XGUI_InspectionPanel::fillContainer(const GeomShapePtr& theShape)
562 if (theShape->shapeType() == GeomAPI_Shape::COMPSOLID)
563 myTypeLbl->setText("CompSolid");
564 else if (theShape->shapeType() == GeomAPI_Shape::COMPOUND)
565 myTypeLbl->setText("Compound");
569 BRepBndLib::Add(theShape->impl<TopoDS_Shape>(), aBB);
571 gp_Pnt aMinPnt = aBB.CornerMin();
572 GeomPointPtr aMinPoint(new GeomAPI_Pnt(aMinPnt.X(), aMinPnt.Y(), aMinPnt.Z()));
574 gp_Pnt aMaxPnt = aBB.CornerMax();
575 GeomPointPtr aMaxPoint(new GeomAPI_Pnt(aMaxPnt.X(), aMaxPnt.Y(), aMaxPnt.Z()));
578 appendGroupNameToParameters(tr("Bounding box"), aParams);
579 appendPointToParameters(tr("Minimal corner"), aMinPoint, aParams);
580 appendPointToParameters(tr("Maximal corner"), aMaxPoint, aParams);
583 void XGUI_InspectionPanel::setPlaneType(const QString& theTitle,
584 const std::shared_ptr<GeomAPI_Pln>& thePlane)
586 myTypeLbl->setText(theTitle);
588 appendPointToParameters(tr("Origin"), thePlane->location(), aParams);
589 appendDirToParameters(tr("Normal"), thePlane->direction(), aParams);
590 setParamsText(aParams);
593 void XGUI_InspectionPanel::setSphereType(const QString& theTitle,
594 const std::shared_ptr<GeomAPI_Sphere>& theSphere)
596 myTypeLbl->setText(theTitle);
598 appendPointToParameters(tr("Center"), theSphere->center(), aParams);
599 appendGroupNameToParameters(tr("Dimensions"), aParams);
600 appendNamedValueToParameters(tr("Radius"), theSphere->radius(), aParams);
601 setParamsText(aParams);
604 void XGUI_InspectionPanel::setCylinderType(const QString& theTitle,
605 const std::shared_ptr<GeomAPI_Cylinder>& theCyl)
607 myTypeLbl->setText(theTitle);
609 appendPointToParameters(tr("Position"), theCyl->location(), aParams);
610 appendDirToParameters(tr("Axis"), theCyl->axis(), aParams);
611 appendGroupNameToParameters(tr("Dimensions"), aParams);
612 appendNamedValueToParameters(tr("Radius"), theCyl->radius(), aParams);
613 appendNamedValueToParameters(tr("Height"), theCyl->height(), aParams);
614 setParamsText(aParams);
617 void XGUI_InspectionPanel::setConeType(const QString& theTitle,
618 const std::shared_ptr<GeomAPI_Cone>& theCone)
620 myTypeLbl->setText(theTitle);
622 appendPointToParameters(tr("Position"), theCone->location(), aParams);
623 appendDirToParameters(tr("Axis"), theCone->axis(), aParams);
624 appendGroupNameToParameters(tr("Dimensions"), aParams);
625 appendNamedValueToParameters(tr("Radius 1"), theCone->radius1(), aParams);
626 appendNamedValueToParameters(tr("Radius 2"), theCone->radius2(), aParams);
627 appendNamedValueToParameters(tr("Height"), theCone->height(), aParams);
628 setParamsText(aParams);
631 void XGUI_InspectionPanel::setTorusType(const QString& theTitle,
632 const std::shared_ptr<GeomAPI_Torus>& theTorus)
634 myTypeLbl->setText(theTitle);
636 appendPointToParameters(tr("Center"), theTorus->center(), aParams);
637 appendDirToParameters(tr("Axis"), theTorus->direction(), aParams);
638 appendGroupNameToParameters(tr("Dimensions"), aParams);
639 appendNamedValueToParameters(tr("Major radius"), theTorus->majorRadius(), aParams);
640 appendNamedValueToParameters(tr("Minor radius"), theTorus->minorRadius(), aParams);
641 setParamsText(aParams);
644 void XGUI_InspectionPanel::setBoxType(const QString& theTitle,
645 const std::shared_ptr<GeomAPI_Box>& theBox)
647 myTypeLbl->setText(theTitle);
649 appendPointToParameters(tr("Position"), theBox->axes()->origin(), aParams);
650 appendGroupNameToParameters(tr("Dimensions"), aParams);
651 appendNamedValueToParameters(tr("Width"), theBox->width(), aParams);
652 appendNamedValueToParameters(tr("Depth"), theBox->depth(), aParams);
653 appendNamedValueToParameters(tr("Height"), theBox->height(), aParams);
654 setParamsText(aParams);
657 void XGUI_InspectionPanel::setRotatedBoxType(const QString& theTitle,
658 const std::shared_ptr<GeomAPI_Box>& theBox)
660 myTypeLbl->setText(theTitle);
662 std::shared_ptr<GeomAPI_Ax3> anAxes = theBox->axes();
663 appendPointToParameters(tr("Position"), anAxes->origin(), aParams);
664 appendDirToParameters(tr("Z axis"), anAxes->normal(), aParams);
665 appendDirToParameters(tr("X axis"), anAxes->dirX(), aParams);
666 appendGroupNameToParameters(tr("Dimensions"), aParams);
667 appendNamedValueToParameters(tr("Width"), theBox->width(), aParams);
668 appendNamedValueToParameters(tr("Depth"), theBox->depth(), aParams);
669 appendNamedValueToParameters(tr("Height"), theBox->height(), aParams);
670 setParamsText(aParams);
674 void XGUI_InspectionPanel::setParamsText(const QString& theText)
676 myTypeParams->setText(theText);
679 void XGUI_InspectionPanel::resizeEvent(QResizeEvent* theEvent)
681 QSize aSize = theEvent->size();
683 int aHeight = aSize.height();
685 if (aHeight > 450) // 450 is a a minimal height
686 myMainWidget->setFixedHeight(aHeight - 30);