Salome HOME
90d3ef270fab4006f4e7f8cf70ac116fb572a771
[modules/geom.git] / src / MeasureGUI / MeasureGUI_ShapeStatisticsDlg.cxx
1 // Copyright (C) 2015-2023  CEA/DEN, EDF R&D, OPEN CASCADE
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 // File   : MeasureGUI_ShapeStatisticsDlg.cxx
21 // Author : Alexander KOVALEV, OPEN CASCADE S.A.S.
22
23 // internal includes
24 #include "MeasureGUI_ShapeStatisticsDlg.h"
25
26 // GEOM includes
27 #include <GEOMBase.h>
28 #include <GEOMUtils_ShapeStatistics.hxx>
29 #include <GeometryGUI.h>
30 #include <DlgRef.h>
31
32 // GUI includes
33 #include <SUIT_Desktop.h>
34 #include <SUIT_MessageBox.h>
35 #include <SUIT_Session.h>
36 #include <SUIT_ResourceMgr.h>
37 #include <SUIT_ViewManager.h>
38 #include <SUIT_ViewWindow.h>
39
40 #include <LightApp_SelectionMgr.h>
41
42 #include <SalomeApp_Application.h>
43
44 #include <Plot2d_Histogram.h>
45 #include <Plot2d_ViewFrame.h>
46 #include <Plot2d_ViewModel.h>
47 #include <Plot2d_ViewWindow.h>
48
49 // Qt includes
50 #include <QIcon>
51 #include <QGridLayout>
52 #include <QPushButton>
53 #include <QLabel>
54
55 // Qtx includes
56 #include <QtxValidator.h>
57
58 // OCC includes
59 #include <TopoDS_Shape.hxx>
60
61 #include <GEOMImpl_Types.hxx>
62
63 //===========================================================================
64 // class    : MeasureGUI_ShapeStatisticsDlg()
65 //===========================================================================
66 MeasureGUI_ShapeStatisticsDlg::MeasureGUI_ShapeStatisticsDlg( QWidget* parent, TopoDS_Shape aShape, TopAbs_ShapeEnum aSubShapeType )
67 : QDialog( parent ),
68   GEOMBase_Helper( SUIT_Session::session()->activeApplication()->desktop() ),
69   myHistogram ( 0 )
70 {
71   myShapes.push_back( aShape );
72
73   QIcon iconSelect( SUIT_Session::session()->resourceMgr()->loadPixmap( "GEOM", tr( "ICON_SELECT" ) ) );
74
75   setWindowTitle( tr( "GEOM_SHAPE_STATISTICS" ) );
76   setAttribute( Qt::WA_DeleteOnClose );
77
78   myApp = dynamic_cast< SalomeApp_Application* >( SUIT_Session::session()->activeApplication() );
79
80   QVBoxLayout* topLayout = new QVBoxLayout( this );
81
82   QGridLayout* settingsLayout = new QGridLayout();
83
84   /**********************   Selected Objects    **********************/
85
86   QLabel* objsLabel = new QLabel( tr( "GEOM_SELECTED_OBJECTS" ), this );
87   QPushButton* selBtn = new QPushButton( this );
88   selBtn->setIcon( iconSelect );
89   myEditMainShape = new QLineEdit( this );
90   myEditMainShape->setReadOnly(true);
91
92   settingsLayout->addWidget( objsLabel, 0, 0 );
93   settingsLayout->addWidget( selBtn, 0, 1 );
94   settingsLayout->addWidget( myEditMainShape, 0, 2 );
95
96   if ( !aShape.IsNull() ) {
97     objsLabel->hide();
98     selBtn->hide();
99     myEditMainShape->hide();
100   }
101
102   /**********************   Type    **********************/
103
104   QLabel* typeLabel = new QLabel( tr( "GEOM_SHAPE_STATISTICS_TYPE" ), this );
105   myCBTypes = new QtxComboBox( this );
106   myCBTypes->setCleared( true );
107   if ( aSubShapeType != TopAbs_SHAPE ) {
108     fillTypes( aSubShapeType == TopAbs_EDGE,
109                aSubShapeType == TopAbs_FACE,
110                aSubShapeType == TopAbs_SOLID );
111     myCBTypes->setEnabled( false );
112   }
113
114   settingsLayout->addWidget( typeLabel, 1, 0 );
115   settingsLayout->addWidget( myCBTypes, 1, 2 );
116
117   /**********************   Number of intervals    **********************/
118
119   QLabel* nbIntervalsLabel = new QLabel( tr( "GEOM_SHAPE_STATISTICS_NB_INTERVALS" ), this );
120   myNbIntervals = new QtxIntSpinBox( 1, 1000, 1, this );
121   myNbIntervals->setValue( 10 );
122
123   settingsLayout->addWidget( nbIntervalsLabel, 2, 0 );
124   settingsLayout->addWidget( myNbIntervals, 2, 2 );
125
126   /**********************   Scalar Range    **********************/
127
128   myScalarRangeBox = new QGroupBox( tr( "GEOM_SHAPE_STATISTICS_SCALAR_RANGE" ), this );
129   myScalarRangeBox->setCheckable( true );
130   myScalarRangeBox->setChecked( false );
131   QLabel* minLabel = new QLabel( tr( "GEOM_SHAPE_STATISTICS_MIN" ), this );
132   myMin = new QLineEdit( this );
133   QtxDoubleValidator* aValid = new QtxDoubleValidator( this );
134   aValid->setBottom( 0.0 );
135   myMin->setValidator( aValid );
136   QLabel* maxLabel = new QLabel( tr( "GEOM_SHAPE_STATISTICS_MAX" ), this );
137   myMax = new QLineEdit( this );
138   myMax->setValidator( aValid );
139
140   QPushButton* buttonCompute = new QPushButton( tr( "GEOM_SHAPE_STATISTICS_COMPUTE" ), this );
141   connect( buttonCompute, SIGNAL( clicked() ), this, SLOT( clickOnCompute() ) );
142
143   QGridLayout* scalarRangeLayout = new QGridLayout();
144   scalarRangeLayout->setMargin( 11 ); settingsLayout->setSpacing( 6 );
145
146   scalarRangeLayout->addWidget( minLabel, 0, 0 );
147   scalarRangeLayout->addWidget( myMin,    0, 1 );
148   scalarRangeLayout->addWidget( maxLabel, 1, 0 );
149   scalarRangeLayout->addWidget( myMax,    1, 1 );
150   scalarRangeLayout->addWidget( buttonCompute, 0, 2, 2, 1 );
151
152   myScalarRangeBox->setLayout( scalarRangeLayout );
153
154   /**********************   Buttons    **********************/
155
156   myButtonPlot   = new QPushButton( tr( "GEOM_PLOT_DISTRIBUTION" ), this );
157   myButtonPlot->setDefault( true );
158   myButtonCreateGr = new QPushButton( tr( "GEOM_SHAPE_STATISTICS_CREATE_GROUPS" ), this );
159   QPushButton* buttonClose  = new QPushButton( tr( "GEOM_BUT_CLOSE" ), this );
160   QPushButton* buttonHelp   = new QPushButton( tr( "GEOM_BUT_HELP" ), this );
161
162   QHBoxLayout* buttonsLayout = new QHBoxLayout();
163   buttonsLayout->addWidget( myButtonPlot );
164   buttonsLayout->addWidget( myButtonCreateGr );
165   buttonsLayout->addWidget( buttonClose );
166   buttonsLayout->addWidget( buttonHelp );
167
168   if ( !aShape.IsNull() ) {
169     myButtonCreateGr->hide();
170   }
171   /**********************   Layouting    **********************/
172
173   topLayout->addLayout( settingsLayout );
174   topLayout->addWidget( myScalarRangeBox );
175   topLayout->addLayout( buttonsLayout );
176
177   // Signals and slots connections
178
179   connect( selBtn, SIGNAL( clicked() ), this, SLOT( onEditMainShape() ) );
180
181   connect( myButtonPlot,     SIGNAL( clicked() ), this, SLOT( clickOnPlot() ) );
182   connect( myButtonCreateGr, SIGNAL( clicked() ), this, SLOT( clickOnCreateGroups() ) );
183
184   connect( buttonClose,    SIGNAL( clicked() ), this, SLOT( reject() ) );
185   connect( buttonHelp,     SIGNAL( clicked() ), this, SLOT( clickOnHelp() ) );
186
187   connect(myApp->selectionMgr(),
188           SIGNAL(currentSelectionChanged()), this, SLOT(onEditMainShape()));
189
190   if ( aShape.IsNull() )
191     onEditMainShape();
192 }
193
194 //===========================================================================
195 // function : ~MeasureGUI_ShapeStatisticsDlg()
196 // purpose  : Destroys the object and frees any allocated resources
197 //===========================================================================
198 MeasureGUI_ShapeStatisticsDlg::~MeasureGUI_ShapeStatisticsDlg()
199 {
200 }
201
202 //=================================================================================
203 // function : createOperation
204 // purpose  :
205 //=================================================================================
206 GEOM::GEOM_IOperations_ptr MeasureGUI_ShapeStatisticsDlg::createOperation()
207 {
208   return getGeomEngine()->GetIGroupOperations();
209 }
210
211 #define RETURN_WITH_MSG(a, b)                   \
212   if (!(a)) {                                   \
213     theMessage += (b);                          \
214     return false;                               \
215   }
216
217 //================================================================
218 // Function : getFather
219 // Purpose  : Get father object for object to be added in study
220 //            (called with addInStudy method)
221 //================================================================
222 GEOM::GEOM_Object_ptr MeasureGUI_ShapeStatisticsDlg::getFather(GEOM::GEOM_Object_ptr theObj)
223 {
224   GEOM::GEOM_Object_var aFatherObj;
225   if (theObj->GetType() == GEOM_GROUP) {
226     GEOM::GEOM_IGroupOperations_var anOper = GEOM::GEOM_IGroupOperations::_narrow(getOperation());
227     aFatherObj = anOper->GetMainShape(theObj);
228   }
229   return aFatherObj._retn();
230 }
231
232 //=================================================================================
233 // function : getSourceObjects
234 // purpose  : virtual method to get source objects
235 //=================================================================================
236 QList<GEOM::GeomObjPtr> MeasureGUI_ShapeStatisticsDlg::getSourceObjects()
237 {
238   QList<GEOM::GeomObjPtr> res;
239   res << myMainObj;
240   return res;
241 }
242
243 //=================================================================================
244 // function : onEditMainShape()
245 // purpose  : called when selection button was clicked
246 //=================================================================================
247 void MeasureGUI_ShapeStatisticsDlg::onEditMainShape()
248 {
249   // restore initial parameters for dialog box
250   myEditMainShape->setText("");
251   myEditMainShape->setFocus();
252
253   //get shapes from selection
254   QList<GEOM::GeomObjPtr> selShapes = getSelected( TopAbs_SHAPE, -1 );
255
256   myButtonPlot->setEnabled( !selShapes.isEmpty() );
257   myButtonCreateGr->setEnabled( selShapes.count() == 1 );
258
259   if ( !selShapes.isEmpty() ) {
260     if ( selShapes.count() == 1 )
261       myMainObj = selShapes[0];
262     QString aName = selShapes.count() > 1 ? QString( "%1_objects").arg( selShapes.count() ) : GEOMBase::GetName( myMainObj.get() );
263     myEditMainShape->setText( aName );
264   }
265
266   updateTypes( selShapes );
267 }
268
269 //=================================================================================
270 // function : currentType()
271 // purpose  : returns currently selected type of shapes in 'Type' combobox
272 //=================================================================================
273 void MeasureGUI_ShapeStatisticsDlg::fillTypes( bool hasEdges, bool hasFaces, bool hasSolids )
274 {
275   if ( hasEdges )
276     myCBTypes->addItem( tr("GEOM_SHAPE_STATISTICS_LENGTH"), (int)TopAbs_EDGE );
277   if ( hasFaces )
278     myCBTypes->addItem( tr("GEOM_SHAPE_STATISTICS_AREA"), (int)TopAbs_FACE );
279   if ( hasSolids )
280     myCBTypes->addItem( tr("GEOM_SHAPE_STATISTICS_VOLUME"), (int)TopAbs_SOLID );
281
282   myCBTypes->setEnabled( myCBTypes->count() > 0 );
283 }
284
285 //=================================================================================
286 // function : updateTypes()
287 // purpose  : update 'Type' combobox with available types
288 //=================================================================================
289 void MeasureGUI_ShapeStatisticsDlg::updateTypes( QList<GEOM::GeomObjPtr> theShapes )
290 {
291   myCBTypes->clear();
292   myCBTypes->setEnabled( false );
293
294   int hasEdges = -1, hasFaces = -1, hasSolids = -1;
295
296   myShapes.clear();
297   // get types of the shapes and its sub-shapes
298   foreach( GEOM::GeomObjPtr aShapePtr, theShapes ) {
299     if ( !aShapePtr )
300       return;
301
302     TopoDS_Shape aShape;
303     if ( !GEOMBase::GetShape( aShapePtr.get(), aShape ) || aShape.IsNull() )
304       return;
305
306     myShapes.push_back( aShape );
307
308     GEOM::ListOfLong_var aSubShapes;
309     GEOM::GEOM_IShapesOperations_var aShOp = getGeomEngine()->GetIShapesOperations();
310     if ( hasEdges != 0 )
311       hasEdges = aShOp->NumberOfSubShapes( aShapePtr.get(), TopAbs_EDGE ) > 0;
312     if ( hasFaces != 0 )
313       hasFaces = aShOp->NumberOfSubShapes( aShapePtr.get(), TopAbs_FACE ) > 0;
314     if ( hasSolids != 0 )
315       hasSolids = aShOp->NumberOfSubShapes( aShapePtr.get(), TopAbs_SOLID ) > 0;
316   }
317   fillTypes( hasEdges, hasFaces, hasSolids );
318 }
319
320 //=================================================================================
321 // function : currentType()
322 // purpose  : returns currently selected type of shapes in 'Type' combobox
323 //=================================================================================
324 TopAbs_ShapeEnum MeasureGUI_ShapeStatisticsDlg::currentType()
325 {
326   return (TopAbs_ShapeEnum)( myCBTypes->itemData( myCBTypes->currentIndex() ).toInt() );
327 }
328
329 //=================================================================================
330 // function : clickOnPlot()
331 // purpose  : called when Plot button was clicked
332 //=================================================================================
333 bool MeasureGUI_ShapeStatisticsDlg::isValid(QString& theMessage)
334 {
335   if ( myScalarRangeBox->isChecked() ) {
336     RETURN_WITH_MSG( !myMin->text().isEmpty(), tr("GEOM_SHAPE_STATISTICS_MIN_ERROR") );
337     RETURN_WITH_MSG( !myMax->text().isEmpty(), tr("GEOM_SHAPE_STATISTICS_MAX_ERROR") );
338     RETURN_WITH_MSG( myMin->text().toDouble() <= myMax->text().toDouble(), tr("GEOM_SHAPE_STATISTICS_MIN_MAX_ERROR") );
339   }
340   return true;
341 }
342 //=================================================================================
343 // function : clickOnPlot()
344 // purpose  : called when Plot button was clicked
345 //=================================================================================
346 void MeasureGUI_ShapeStatisticsDlg::clickOnPlot()
347 {
348   GEOMUtils::Range aRange;
349   if ( myScalarRangeBox->isChecked() ) {
350     QString msg;
351     if ( !isValid( msg ) ) {
352       showError( msg );
353       return;
354     }
355     aRange.min = myMin->text().toDouble();
356     aRange.max = myMax->text().toDouble();
357   } else {
358     aRange.min = -1.0; // flag that range is empty
359     aRange.max = -1.0; // flag that range is empty
360   }
361
362   GEOMUtils::Distribution aShapesDistr =
363     GEOMUtils::ComputeDistribution( myShapes, currentType(), myNbIntervals->value(), aRange );
364
365   QList<double> xVals, yVals;
366   double width = -1, min = -1;
367   double xmin = 1e+32, xmax = 0.0, ymax = 0.0;
368   int i=0;
369   GEOMUtils::Distribution::const_iterator it;
370   for (it = aShapesDistr.begin(); it != aShapesDistr.end(); it++) {
371     GEOMUtils::Range ran = *it;
372     if ( width < 0 ) width = ran.max - ran.min; // bar width
373     if ( min < 0 ) min = ran.min; // global min
374     xVals << width / 2. + i*width + min; // get a middle of bar
375     yVals << ran.count;
376     // get global boundary max values
377     if ( ran.min < xmin ) xmin = ran.min;
378     if ( ran.max > xmax ) xmax = ran.max;
379     if ( ran.count > ymax ) ymax = ran.count;
380     i++;
381   }
382
383   // plot the computed distribution
384   SUIT_ViewManager* aViewManager = myApp->getViewManager( Plot2d_Viewer::Type(), true ); // create if necessary
385   if( !aViewManager )
386     return;
387   Plot2d_ViewWindow* aViewWnd = dynamic_cast<Plot2d_ViewWindow*>(aViewManager->getActiveView());
388   if( !aViewWnd )
389     return;
390   Plot2d_ViewFrame* aPlot = aViewWnd->getViewFrame();
391   if ( !aPlot )
392     return;
393
394   aPlot->EraseAll();
395
396   // create or reuse histogram
397   if( !myHistogram )
398     myHistogram = new Plot2d_Histogram();
399   else
400     myHistogram->clearAllPoints();
401   // set histogram parameters
402   myHistogram->setData( xVals, yVals );
403   if ( width != 0.0 )
404     myHistogram->setWidth( width );
405   myHistogram->setAutoAssign(true);
406   myHistogram->setName( myEditMainShape->text() );
407   myHistogram->setHorTitle( myCBTypes->currentText() );
408   myHistogram->setVerTitle( tr("GEOM_SHAPE_STATISTICS_DISTRIBUTION_NB_ENT") );
409   myHistogram->setColor( QColor(0, 85, 0) );
410   // display histogram
411   aPlot->displayObject( myHistogram, true );
412   if ( width == 0.0 ) // only one X value
413     aPlot->fitAll();
414   else
415     aPlot->fitData( 0, xmin, xmax, 0.0, ymax );
416 }
417
418 //=================================================================================
419 // function : clickOnCompute()
420 // purpose  : called when Compute button was clicked
421 //=================================================================================
422 void MeasureGUI_ShapeStatisticsDlg::clickOnCompute()
423 {
424   GEOMUtils::Range aRange;
425   aRange.min = -1.0; // flag that range is empty
426   aRange.max = -1.0; // flag that range is empty
427   std::map<int,double> measures = GEOMUtils::ComputeMeasures( myShapes, currentType(), aRange );
428   if ( measures.size() != 0 ) {
429     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
430     int aPrecision = resMgr->integerValue( "Geometry", "length_precision", 6 );
431     myMin->setText( DlgRef::PrintDoubleValue( aRange.min, aPrecision ) );
432     myMax->setText( DlgRef::PrintDoubleValue( aRange.max, aPrecision ) );
433   }
434 }
435
436 //=================================================================================
437 // function : clickOnCreateGroups()
438 // purpose  : called when Create Groups button was clicked
439 //=================================================================================
440 void MeasureGUI_ShapeStatisticsDlg::clickOnCreateGroups()
441 {
442   onAccept(false, false, false);
443 }
444
445 //=================================================================================
446 // function : execute(ObjectList& objects)
447 // purpose  :
448 //=================================================================================
449 bool MeasureGUI_ShapeStatisticsDlg::execute(ObjectList& objects)
450 {
451   if ( myMainObj.isNull() )
452     return false;
453
454   GEOM::GroupOpPtr anOper = GEOM::GEOM_IGroupOperations::_narrow(getOperation());
455
456   GEOMUtils::Range aRange;
457   if ( myScalarRangeBox->isChecked() ) {
458     QString msg;
459     if ( !isValid( msg ) ) {
460       showError( msg );
461       return false;
462     }
463     aRange.min = myMin->text().toDouble();
464     aRange.max = myMax->text().toDouble();
465   } else {
466     aRange.min = -1.0; // flag that range is empty
467     aRange.max = -1.0; // flag that range is empty
468   }
469
470   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
471   int aPrecision = resMgr->integerValue( "Geometry", "length_precision", 6 );
472   QString aTypePrefix = myCBTypes->currentText().replace(' ', '_');
473   QString objIOR, aMin, aMax, aGroupName;
474
475   GEOMUtils::Distribution aShapesDistr =
476     GEOMUtils::ComputeDistribution( myShapes, currentType(), myNbIntervals->value(), aRange );
477
478   int nbGroups = 0;
479
480   GEOMUtils::Distribution::const_iterator it;
481   for (it = aShapesDistr.begin(); it != aShapesDistr.end(); it++) {
482     std::list<long> idList = (*it).indices;
483     int nn = idList.size();
484     if ( nn > 0 ) {
485       GEOM::ListOfLong_var aNewList = new GEOM::ListOfLong;
486       aNewList->length(nn);
487       int ii = 0;
488       std::list<long>::const_iterator id_it;
489       for ( id_it = idList.begin(); id_it != idList.end(); id_it++ ) {
490         aNewList[ii++] = *id_it;
491       }
492
493       // Create an empty group
494       GEOM::GEOM_Object_var aGroup;
495       aGroup = anOper->CreateGroup( myMainObj.get(), currentType() );
496
497       if (CORBA::is_nil(aGroup) || !anOper->IsDone())
498         return false;
499
500       // Add sub-shapes into group
501       anOper->UnionIDs(aGroup, aNewList);
502       if (!anOper->IsDone())
503         return false;
504
505       // publish group
506       aMin = DlgRef::PrintDoubleValue( (*it).min, aPrecision );
507       aMax = DlgRef::PrintDoubleValue( (*it).max, aPrecision );
508       aGroupName =  aTypePrefix + "_" + aMin + "_" + aMax;
509       GEOMBase::PublishSubObject( aGroup, aGroupName );
510
511       // this is needed just to avoid error message
512       objects.push_back(aGroup._retn());
513
514       nbGroups++;
515     }
516   }
517
518   SUIT_MessageBox::information( this, tr( "INF_INFO" ), tr( "GEOM_MSG_GROUPS_CREATED" ).arg( nbGroups ) );
519
520   return true;
521 }
522
523 //=================================================================================
524 // function : clickOnHelp()
525 // purpose  : called when Help button was clicked
526 //=================================================================================
527 void MeasureGUI_ShapeStatisticsDlg::clickOnHelp()
528 {
529   GeometryGUI* aGeomGUI = dynamic_cast<GeometryGUI*>( myApp->module( "Geometry" ) );
530   myApp->onHelpContextModule( aGeomGUI ? myApp->moduleName( aGeomGUI->moduleName() ) : QString(""), "shape_statistics_operation_page.html" );
531 }