Salome HOME
Dump Python Extension - Debug
[modules/smesh.git] / src / SMESHGUI / SMESHGUI_Hypotheses.cxx
1 // SMESH SMESHGUI : GUI for SMESH component
2 //
3 // Copyright (C) 2003  CEA
4 //
5 // This library is free software; you can redistribute it and/or 
6 // modify it under the terms of the GNU Lesser General Public 
7 // License as published by the Free Software Foundation; either 
8 // version 2.1 of the License. 
9 //
10 // This library is distributed in the hope that it will be useful, 
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
13 // Lesser General Public License for more details. 
14 //
15 // You should have received a copy of the GNU Lesser General Public 
16 // License along with this library; if not, write to the Free Software 
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA 
18 //
19 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 //
21 // File   : SMESHGUI_Hypotheses.cxx
22 // Author : Julia DOROVSKIKH, Open CASCADE S.A.S.
23 //
24
25 // SMESH includes
26 #include "SMESHGUI_Hypotheses.h"
27
28 #include "SMESHGUI.h"
29 #include "SMESHGUI_HypothesesUtils.h"
30 #include "SMESHGUI_Utils.h"
31 #include "SMESHGUI_SpinBox.h"
32
33 // SALOME KERNEL includes
34 #include <SALOMEDSClient_Study.hxx>
35 #include <utilities.h>
36
37 // SALOME GUI includes
38 #include <SUIT_Session.h>
39 #include <SUIT_MessageBox.h>
40 #include <SUIT_ResourceMgr.h>
41 #include <LightApp_Application.h>
42 #include <SalomeApp_IntSpinBox.h>
43
44 // Qt includes
45 #include <QFrame>
46 #include <QLineEdit>
47 #include <QLabel>
48 #include <QGroupBox>
49 #include <QVBoxLayout>
50 #include <QEventLoop>
51
52 #define SPACING 6
53 #define MARGIN  11
54
55 SMESHGUI_GenericHypothesisCreator::SMESHGUI_GenericHypothesisCreator( const QString& theHypType )
56   : myHypType( theHypType ), myIsCreate( false ), myDlg( 0 ), myEventLoop( 0 )
57 {
58 }
59
60 SMESHGUI_GenericHypothesisCreator::~SMESHGUI_GenericHypothesisCreator()
61 {
62 }
63
64 void SMESHGUI_GenericHypothesisCreator::create( SMESH::SMESH_Hypothesis_ptr initParamsHyp,
65                                                 const QString& theHypName,
66                                                 QWidget* parent)
67 {
68   MESSAGE( "Creation of hypothesis with initial params" );
69
70   if ( !CORBA::is_nil( initParamsHyp ) && hypType() == initParamsHyp->GetName() )
71     myInitParamsHypo = SMESH::SMESH_Hypothesis::_duplicate( initParamsHyp );
72   create( false, theHypName, parent );
73 }
74
75 void SMESHGUI_GenericHypothesisCreator::create( bool isAlgo,
76                                                 const QString& theHypName,
77                                                 QWidget* theParent )
78 {
79   MESSAGE( "Creation of hypothesis" );
80
81   myIsCreate = true;
82
83   // Create hypothesis/algorithm
84   if (isAlgo)
85     SMESH::CreateHypothesis( hypType(), theHypName, isAlgo );
86   else
87   {
88     SMESH::SMESH_Hypothesis_var aHypothesis = 
89       SMESH::CreateHypothesis( hypType(), theHypName, false );
90     if( !editHypothesis( aHypothesis.in(), theHypName, theParent ) )
91     { //remove just created hypothesis
92       _PTR(SObject) aHypSObject = SMESH::FindSObject( aHypothesis.in() );
93       _PTR(Study) aStudy = SMESH::GetActiveStudyDocument();
94       if( aStudy && !aStudy->GetProperties()->IsLocked() )
95       {
96         _PTR(StudyBuilder) aBuilder = aStudy->NewBuilder();
97         aBuilder->RemoveObjectWithChildren( aHypSObject );
98       }
99     }
100   }
101   SMESHGUI::GetSMESHGUI()->updateObjBrowser( true, 0 );
102 }
103
104 void SMESHGUI_GenericHypothesisCreator::edit( SMESH::SMESH_Hypothesis_ptr theHypothesis,
105                                               const QString& theHypName,
106                                               QWidget* theParent )
107 {
108   if( CORBA::is_nil( theHypothesis ) )
109     return;
110
111   MESSAGE("Edition of hypothesis");
112
113   myIsCreate = false;
114
115   if( !editHypothesis( theHypothesis, theHypName, theParent ) )
116     return;
117
118   SMESH::SObjectList listSOmesh = SMESH::GetMeshesUsingAlgoOrHypothesis( theHypothesis );
119   if( listSOmesh.size() > 0 )
120     for( int i = 0; i < listSOmesh.size(); i++ )
121     {
122       _PTR(SObject) submSO = listSOmesh[i];
123       SMESH::SMESH_Mesh_var aMesh = SMESH::SObjectToInterface<SMESH::SMESH_Mesh>( submSO );
124       SMESH::SMESH_subMesh_var aSubMesh = SMESH::SObjectToInterface<SMESH::SMESH_subMesh>( submSO );
125       if( !aSubMesh->_is_nil() )
126         aMesh = aSubMesh->GetFather();
127       _PTR(SObject) meshSO = SMESH::FindSObject( aMesh );
128       SMESH::ModifiedMesh( meshSO, false, aMesh->NbNodes()==0);
129     }
130   SMESHGUI::GetSMESHGUI()->updateObjBrowser( true, 0 );
131 }
132
133 bool SMESHGUI_GenericHypothesisCreator::editHypothesis( SMESH::SMESH_Hypothesis_ptr h, 
134                                                         const QString& theHypName,
135                                                         QWidget* theParent )
136 {
137   if( CORBA::is_nil( h ) )
138     return false;
139
140   bool res = true;
141   myHypName = theHypName;
142   myHypo = SMESH::SMESH_Hypothesis::_duplicate( h );
143
144   SMESHGUI_HypothesisDlg* Dlg = new SMESHGUI_HypothesisDlg( this, theParent );
145   connect( Dlg, SIGNAL( finished( int ) ), this, SLOT( onDialogFinished( int ) ) );
146   myDlg = Dlg;
147   QFrame* fr = buildFrame();
148   if( fr )
149   {
150     Dlg->setCustomFrame( fr );
151     Dlg->setWindowTitle( caption() );
152     Dlg->setObjectName( theHypName );
153     Dlg->setHIcon( icon() );
154     Dlg->setType( type() );
155     retrieveParams();
156     Dlg->show();
157     if ( !myEventLoop )
158       myEventLoop = new QEventLoop( this );
159     myEventLoop->exec(); // make myDlg not modal
160     res = myDlg->result();
161     if( res ) {
162       QString paramValues = storeParams();
163       if ( !paramValues.isEmpty() ) {
164         if ( _PTR(SObject) SHyp = SMESH::FindSObject( myHypo ))
165           SMESH::SetValue( SHyp, paramValues );
166       }
167     }
168   }
169   delete Dlg; myDlg = 0;
170   changeWidgets().clear();
171   myHypo = SMESH::SMESH_Hypothesis::_nil();
172   myInitParamsHypo = SMESH::SMESH_Hypothesis::_nil();
173   return res;
174 }
175   
176 QFrame* SMESHGUI_GenericHypothesisCreator::buildStdFrame()
177 {
178   if( CORBA::is_nil( hypothesis() ) )
179     return 0;
180
181   ListOfStdParams params;
182   if( !stdParams( params ) || params.isEmpty() )
183     return 0;
184
185   QFrame* fr = new QFrame( 0 );
186   QVBoxLayout* lay = new QVBoxLayout( fr );
187   lay->setMargin( 5 );
188   lay->setSpacing( 0 );
189
190   QGroupBox* GroupC1 = new QGroupBox( tr( "SMESH_ARGUMENTS" ), fr );
191   lay->addWidget( GroupC1 );
192
193   QGridLayout* GroupC1Layout = new QGridLayout( GroupC1 );
194   GroupC1Layout->setSpacing( SPACING );
195   GroupC1Layout->setMargin( MARGIN );
196
197   ListOfStdParams::const_iterator anIt = params.begin(), aLast = params.end();
198   for( int i=0; anIt!=aLast; anIt++, i++ )
199   {
200     QLabel* lab = new QLabel( (*anIt).myName, GroupC1 );
201     GroupC1Layout->addWidget( lab, i, 0 );
202
203     QWidget* w = getCustomWidget( *anIt, GroupC1, i );
204     if ( !w ) 
205       switch( (*anIt).myValue.type() )
206       {
207       case QVariant::Int:
208         {
209           SalomeApp_IntSpinBox* sb = new SalomeApp_IntSpinBox( GroupC1 );
210           sb->setObjectName( (*anIt).myName );
211           attuneStdWidget( sb, i );
212           sb->setValue( (*anIt).myValue.toInt() );
213           connect( sb, SIGNAL( valueChanged( int ) ), this, SLOT( onValueChanged() ) );
214           w = sb;
215         }
216         break;
217       case QVariant::Double:
218         {
219           SalomeApp_DoubleSpinBox* sb = new SMESHGUI_SpinBox( GroupC1 );
220           sb->setObjectName( (*anIt).myName );
221           attuneStdWidget( sb, i );
222           sb->setValue( (*anIt).myValue.toDouble() );
223           connect( sb, SIGNAL( valueChanged( double ) ), this, SLOT( onValueChanged() ) );
224           w = sb;
225         }
226         break;
227       case QVariant::String:
228         {
229           if((*anIt).isVariable) {
230             _PTR(Study) aStudy = SMESH::GetActiveStudyDocument();
231             QString aVar = (*anIt).myValue.toString();
232             if(aStudy->IsInteger(aVar.toLatin1().constData())){
233               SalomeApp_IntSpinBox* sb = new SalomeApp_IntSpinBox( GroupC1 );
234               sb->setObjectName( (*anIt).myName );
235               attuneStdWidget( sb, i );
236               sb->setText( aVar );
237               connect( sb, SIGNAL( valueChanged( int ) ), this, SLOT( onValueChanged() ) );
238               w = sb;
239             }
240             else if(aStudy->IsReal(aVar.toLatin1().constData())){
241               SalomeApp_DoubleSpinBox* sb = new SalomeApp_DoubleSpinBox( GroupC1 );
242               sb->setObjectName( (*anIt).myName );
243               attuneStdWidget( sb, i );
244               sb->setText( aVar );
245               connect( sb, SIGNAL( valueChanged( double ) ), this, SLOT( onValueChanged() ) );
246               w = sb;
247             }
248           }
249           else {
250             QLineEdit* le = new QLineEdit( GroupC1 );
251             le->setObjectName( (*anIt).myName );
252             attuneStdWidget( le, i );
253             le->setText( (*anIt).myValue.toString() );
254             connect( le, SIGNAL( textChanged( const QString& ) ), this, SLOT( onValueChanged() ) );
255             w = le;
256           }
257         }
258         break;
259       }
260
261     if( w )
262     {
263       GroupC1Layout->addWidget( w, i, 1 );
264       changeWidgets().append( w );
265     }
266   }
267
268   return fr;
269 }
270
271 void SMESHGUI_GenericHypothesisCreator::onValueChanged()
272 {
273 }
274
275 void SMESHGUI_GenericHypothesisCreator::onDialogFinished( int /*result*/ )
276 {
277   if ( myEventLoop )
278     myEventLoop->exit();
279 }
280
281 bool SMESHGUI_GenericHypothesisCreator::stdParams( ListOfStdParams& ) const
282 {
283   return false;
284 }
285
286 bool SMESHGUI_GenericHypothesisCreator::getStdParamFromDlg( ListOfStdParams& params ) const
287 {
288   bool res = true;
289   StdParam item;
290   ListOfWidgets::const_iterator anIt = widgets().begin(), aLast = widgets().end();
291   for( ; anIt!=aLast; anIt++ )
292   {
293     item.myName = (*anIt)->objectName();
294     if( (*anIt)->inherits( "SalomeApp_IntSpinBox" ) )
295     {
296       SalomeApp_IntSpinBox* sb = ( SalomeApp_IntSpinBox* )( *anIt );
297       item.myValue = sb->value();
298       params.append( item );
299     }
300     
301     else if( (*anIt)->inherits( "SalomeApp_DoubleSpinBox" ) )
302     {
303       SalomeApp_DoubleSpinBox* sb = ( SalomeApp_DoubleSpinBox* )( *anIt );
304       item.myValue = sb->value();
305       params.append( item );
306     }
307
308     else if( (*anIt)->inherits( "QLineEdit" ) )
309     {
310       QLineEdit* line = ( QLineEdit* )( *anIt );
311       item.myValue = line->text();
312       params.append( item );
313     }
314
315     else if ( getParamFromCustomWidget( item, *anIt ))
316     {
317       params.append( item );
318     }
319
320     else
321       res = false;
322   }
323   return res;
324 }
325
326
327 QStringList SMESHGUI_GenericHypothesisCreator::getVariablesFromDlg() const
328 {
329   QStringList aResult;
330   ListOfWidgets::const_iterator anIt = widgets().begin(), aLast = widgets().end();
331   for( ; anIt!=aLast; anIt++ ) {
332     if( (*anIt)->inherits( "SalomeApp_IntSpinBox" ) ) {
333       SalomeApp_IntSpinBox* sb = ( SalomeApp_IntSpinBox* )( *anIt );
334       aResult.append(sb->text());
335     } 
336     else if( (*anIt)->inherits( "QtxDoubleSpinBox" ) ) {
337       QtxDoubleSpinBox* sb = ( QtxDoubleSpinBox* )( *anIt );
338       aResult.append(sb->text());
339     } 
340   }
341   return aResult;
342 }
343
344 QString SMESHGUI_GenericHypothesisCreator::stdParamValues( const ListOfStdParams& params)
345 {
346   QString valueStr = "";
347   ListOfStdParams::const_iterator param = params.begin(), aLast = params.end();
348   uint len0 = 0;
349   for( int i=0; param!=aLast; param++, i++ )
350   {
351     if ( valueStr.length() > len0 ) {
352       valueStr += "; ";
353       len0 = valueStr.length();
354     }
355     switch( (*param).myValue.type() )
356     {
357     case QVariant::Int:
358       valueStr += valueStr.number( (*param).myValue.toInt() );
359       break;
360     case QVariant::Double:
361       valueStr += valueStr.number( (*param).myValue.toDouble() );
362       break;
363     case QVariant::String:
364       valueStr += (*param).myValue.toString();
365       break;
366     default:
367       QVariant valCopy = (*param).myValue;
368       valueStr += valCopy.toString();
369     }
370   }
371   return valueStr;
372 }
373
374 SMESH::SMESH_Hypothesis_var SMESHGUI_GenericHypothesisCreator::hypothesis() const
375 {
376   return myHypo;
377 }
378
379 SMESH::SMESH_Hypothesis_var SMESHGUI_GenericHypothesisCreator::initParamsHypothesis() const
380 {
381   if ( CORBA::is_nil( myInitParamsHypo ))
382     return myHypo;
383   return myInitParamsHypo;
384 }
385
386 QString SMESHGUI_GenericHypothesisCreator::hypType() const
387 {
388   return myHypType;
389 }
390
391 QString SMESHGUI_GenericHypothesisCreator::hypName() const
392 {
393   return myHypName;
394 }
395
396 const SMESHGUI_GenericHypothesisCreator::ListOfWidgets& SMESHGUI_GenericHypothesisCreator::widgets() const
397 {
398   return myParamWidgets;
399 }
400
401 SMESHGUI_GenericHypothesisCreator::ListOfWidgets& SMESHGUI_GenericHypothesisCreator::changeWidgets()
402 {
403   return myParamWidgets;
404 }
405
406 QtxDialog* SMESHGUI_GenericHypothesisCreator:: dlg() const
407
408   return myDlg;
409 }
410
411 bool SMESHGUI_GenericHypothesisCreator::isCreation() const
412 {
413   return myIsCreate;
414 }
415
416 void SMESHGUI_GenericHypothesisCreator::attuneStdWidget( QWidget*, const int ) const
417 {
418 }
419
420 QString SMESHGUI_GenericHypothesisCreator::caption() const
421 {
422   return QString();
423 }
424
425 QPixmap SMESHGUI_GenericHypothesisCreator::icon() const
426 {
427   return QPixmap();
428 }
429
430 QString SMESHGUI_GenericHypothesisCreator::type() const
431 {
432   return QString();
433 }
434 QWidget* SMESHGUI_GenericHypothesisCreator::getCustomWidget( const StdParam & /*param*/,
435                                                              QWidget*   /*parent*/,
436                                                              const int  /*index*/) const
437 {
438   return 0;
439 }
440 bool SMESHGUI_GenericHypothesisCreator::getParamFromCustomWidget( StdParam&, QWidget* ) const
441 {
442   return false;
443 }
444
445 bool SMESHGUI_GenericHypothesisCreator::checkParams( QString& msg ) const
446 {
447   bool ok = true;
448   ListOfWidgets::const_iterator anIt = widgets().begin(), aLast = widgets().end();
449   for( ; anIt!=aLast; anIt++ )
450   {
451     if( (*anIt)->inherits( "SalomeApp_IntSpinBox" ) )
452     {
453       SalomeApp_IntSpinBox* sb = ( SalomeApp_IntSpinBox* )( *anIt );
454       ok = sb->isValid( msg, true ) && ok;
455     }
456     else if( (*anIt)->inherits( "SalomeApp_DoubleSpinBox" ) )
457     {
458       SalomeApp_DoubleSpinBox* sb = ( SalomeApp_DoubleSpinBox* )( *anIt );
459       ok = sb->isValid( msg, true ) && ok;
460     }
461   }
462   return ok;
463 }
464
465 void SMESHGUI_GenericHypothesisCreator::onReject()
466 {
467 }
468
469 QString SMESHGUI_GenericHypothesisCreator::helpPage() const
470 {
471   QString aHypType = hypType();
472   QString aHelpFileName;
473   if ( aHypType == "LocalLength" )
474     aHelpFileName = "a1d_meshing_hypo_page.html#average_length_anchor";
475   else if ( aHypType == "Arithmetic1D")
476     aHelpFileName = "a1d_meshing_hypo_page.html#arithmetic_1d_anchor";
477   else if ( aHypType == "MaxElementArea")
478     aHelpFileName = "a2d_meshing_hypo_page.html#max_element_area_anchor";
479   else if ( aHypType == "MaxElementVolume")
480     aHelpFileName = "max_element_volume_hypo_page.html";
481   else if ( aHypType == "StartEndLength")
482     aHelpFileName = "a1d_meshing_hypo_page.html#start_and_end_length_anchor";
483   else if ( aHypType == "Deflection1D")
484     aHelpFileName = "a1d_meshing_hypo_page.html#deflection_1d_anchor";
485   else if ( aHypType == "AutomaticLength")
486     aHelpFileName = "a1d_meshing_hypo_page.html#automatic_length_anchor";
487   else if ( aHypType == "NumberOfSegments")
488     aHelpFileName = "a1d_meshing_hypo_page.html#number_of_segments_anchor";
489   else
490     aHelpFileName = "";
491   return aHelpFileName;
492 }
493
494
495
496
497 SMESHGUI_HypothesisDlg::SMESHGUI_HypothesisDlg( SMESHGUI_GenericHypothesisCreator* creator, QWidget* parent )
498 : QtxDialog( parent, false, true ),
499   myCreator( creator )
500 {
501   setMinimumSize( 300, height() );
502 //  setFixedSize( 300, height() );
503   QVBoxLayout* topLayout = new QVBoxLayout( mainFrame() );
504   topLayout->setMargin( 0 );
505   topLayout->setSpacing( 0 );
506
507   QFrame* titFrame = new QFrame( mainFrame() );
508   QHBoxLayout* titLay = new QHBoxLayout( titFrame );
509   titLay->setMargin( 0 );
510   titLay->setSpacing( SPACING );
511   
512   myIconLabel = new QLabel( titFrame );
513   myIconLabel->setScaledContents( false );
514   myIconLabel->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
515   myTypeLabel = new QLabel( titFrame );
516   if( creator )
517     myTypeLabel->setText( creator->hypType() );
518
519   titLay->addWidget( myIconLabel, 0 );
520   titLay->addWidget( myTypeLabel, 0 );
521   titLay->addStretch( 1 );
522
523   topLayout->addWidget( titFrame, 0 );
524
525   myHelpFileName = creator->helpPage();
526
527   connect( this, SIGNAL( dlgHelp() ), this, SLOT( onHelp() ) );
528 }
529
530 SMESHGUI_HypothesisDlg::~SMESHGUI_HypothesisDlg()
531 {
532 }
533
534 void SMESHGUI_HypothesisDlg::setCustomFrame( QFrame* f )
535 {
536   if( f )
537   {
538     f->setParent( mainFrame() );
539     qobject_cast<QVBoxLayout*>( mainFrame()->layout() )->insertWidget( 1, f, 1 );
540   }
541 }
542
543 void SMESHGUI_HypothesisDlg::accept()
544 {
545   QString msg;
546   if ( myCreator && !myCreator->checkParams( msg ) )
547   {
548     QString str( tr( "SMESH_INCORRECT_INPUT" ) );
549     if ( !msg.isEmpty() )
550       str += "\n" + msg;
551     SUIT_MessageBox::critical( this, tr( "SMESH_ERROR" ), str );
552     return;
553   }
554   QtxDialog::accept();
555 }
556
557 void SMESHGUI_HypothesisDlg::reject()
558 {
559   if ( myCreator ) myCreator->onReject();
560   QtxDialog::reject();
561 }
562
563 void SMESHGUI_HypothesisDlg::onHelp()
564 {
565   LightApp_Application* app = (LightApp_Application*)(SUIT_Session::session()->activeApplication());
566   if (app) {
567     SMESHGUI* aSMESHGUI = dynamic_cast<SMESHGUI*>( app->activeModule() );
568     app->onHelpContextModule(aSMESHGUI ? app->moduleName(aSMESHGUI->moduleName()) : QString(""), myHelpFileName);
569   }
570   else {
571     QString platform;
572 #ifdef WIN32
573     platform = "winapplication";
574 #else
575     platform = "application";
576 #endif
577     SUIT_MessageBox::warning(this, tr("WRN_WARNING"),
578                              tr("EXTERNAL_BROWSER_CANNOT_SHOW_PAGE").
579                              arg(app->resourceMgr()->stringValue("ExternalBrowser", 
580                                                                  platform)).
581                              arg(myHelpFileName));
582   }
583 }
584
585 void SMESHGUI_HypothesisDlg::setHIcon( const QPixmap& p )
586 {
587   myIconLabel->setPixmap( p );  
588 }
589
590 void SMESHGUI_HypothesisDlg::setType( const QString& t )
591 {
592   myTypeLabel->setText( t );
593 }
594
595 HypothesisData::HypothesisData( const QString& theTypeName,
596                                 const QString& thePluginName,
597                                 const QString& theServerLibName,
598                                 const QString& theClientLibName,
599                                 const QString& theLabel,
600                                 const QString& theIconId,
601                                 const QList<int>& theDim,
602                                 const bool theIsAux,
603                                 const QStringList& theNeededHypos,
604                                 const QStringList& theOptionalHypos,
605                                 const QStringList& theInputTypes,
606                                 const QStringList& theOutputTypes,
607                                 const bool theIsNeedGeometry,
608                                 const bool supportSub)
609   : TypeName( theTypeName ),
610     PluginName( thePluginName ),
611     ServerLibName( theServerLibName ),
612     ClientLibName( theClientLibName ),
613     Label( theLabel ),
614     IconId( theIconId ),
615     Dim( theDim ),
616     IsAux( theIsAux ),
617     NeededHypos( theNeededHypos ), 
618     OptionalHypos( theOptionalHypos ),
619     InputTypes( theInputTypes ),
620     OutputTypes( theOutputTypes ),
621     IsNeedGeometry( theIsNeedGeometry ),
622     IsSupportSubmeshes( supportSub )
623 {
624 }
625
626 HypothesesSet::HypothesesSet( const QString& theSetName ) 
627   : HypoSetName( theSetName )
628 {
629 }
630
631 HypothesesSet::HypothesesSet( const QString&     theSetName,
632                               const QStringList& theHypoList,
633                               const QStringList& theAlgoList )
634   : HypoSetName( theSetName ), 
635     HypoList( theHypoList ), 
636     AlgoList( theAlgoList )
637 {
638 }