From a4e38baeb355cf4d0424fcdab140a24b35da6457 Mon Sep 17 00:00:00 2001 From: mwganson Date: Sun, 26 Jul 2020 13:53:57 -0500 Subject: [PATCH] [CheckGeometry] add new shapecontent builder instead of OCCT's to correct some errors in OCCT's values and to add for some more advanced information, e.g. volume or area, add new method to Base::Interpreter runStringWithKey() -- allows to run a python script and get a string return value --- src/Base/Interpreter.cpp | 39 ++++++++ src/Base/Interpreter.h | 2 + src/Mod/Part/Gui/TaskCheckGeometry.cpp | 126 +++++++++++++++++++++++-- src/Mod/Part/Gui/TaskCheckGeometry.h | 2 + 4 files changed, 160 insertions(+), 9 deletions(-) diff --git a/src/Base/Interpreter.cpp b/src/Base/Interpreter.cpp index 293208f624..fc4ee52399 100644 --- a/src/Base/Interpreter.cpp +++ b/src/Base/Interpreter.cpp @@ -293,6 +293,45 @@ std::string InterpreterSingleton::runString(const char *sCmd) } } +/** runStringWithKey(psCmd, key, key_initial_value) + * psCmd is python script to run + * key is the name of a python string variable the script will have read/write + * access to during script execution. It will be our return value. + * key_initial_value is the initial value c++ will set before calling the script + * To check for runtime errors during script execution compare the return value + * of runStringWithKey() to the inital value set. If they are the same then + * there was a runtime error (presuming the script is written to change the key). + * + * example: runStringWithKey("_key = 'new string'", "_key", "old string") + * If the return value of runStringWithKey() = "old string" then there was an error + * Enable logging and copy/paste the script to the console or to a macro to debug. + */ + +std::string InterpreterSingleton::runStringWithKey(const char *psCmd, const char *key, const char *key_initial_value){ + + PyGILStateLocker locker; + PyObject* main = PP_Load_Module("__main__"); + if (main == NULL) + throw PyException(); + PyObject* globalDictionary = PyModule_GetDict(main); + if (globalDictionary == NULL) + throw PyException(); + PyObject* localDictionary = PyDict_New(); + if (localDictionary == NULL) + throw PyException(); + PyObject* initial_value = PyUnicode_FromString(key_initial_value); + PyDict_SetItemString(localDictionary, key, initial_value); + PyRun_String(psCmd, Py_file_input, globalDictionary, localDictionary); + PyObject* key_return_value = PyDict_GetItemString(localDictionary, key); +#if PY_MAJOR_VERSION >= 3 + PyObject* str = PyUnicode_AsEncodedString(key_return_value, "utf-8", "~E~"); +#else + PyObject* str = PyString_FromString(key_return_value, "utf-8", "~E~"); +#endif + const char* result = PyBytes_AS_STRING(str); + return result; +} + Py::Object InterpreterSingleton::runStringObject(const char *sCmd) { PyObject *module, *dict, *presult; /* "exec code in d, d" */ diff --git a/src/Base/Interpreter.h b/src/Base/Interpreter.h index df4ce4a519..70e0a9513a 100644 --- a/src/Base/Interpreter.h +++ b/src/Base/Interpreter.h @@ -218,6 +218,8 @@ public: //@{ /// Run a statement on the python interpreter and gives back a string with the representation of the result. std::string runString(const char *psCmd); + /// Run a statement on the python interpreter with a key for exchanging strings + std::string runStringWithKey(const char *psCmd, const char *key, const char *key_initial_value=""); /// Run a statement on the python interpreter and return back the result object. Py::Object runStringObject(const char *sCmd); /// Run a statement on the python interpreter and gives back a string with the representation of the result. diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.cpp b/src/Mod/Part/Gui/TaskCheckGeometry.cpp index ecad1873fa..045210cb4d 100644 --- a/src/Mod/Part/Gui/TaskCheckGeometry.cpp +++ b/src/Mod/Part/Gui/TaskCheckGeometry.cpp @@ -27,6 +27,7 @@ # include # include # include +# include # include # include # include @@ -63,6 +64,7 @@ #endif //_PreComp_ #include "../App/PartFeature.h" +#include #include #include #include @@ -565,16 +567,101 @@ void TaskCheckGeometryResults::checkSub(const BRepCheck_Analyzer &shapeCheck, co void TaskCheckGeometryResults::buildShapeContent(const QString &baseName, const TopoDS_Shape &shape) { - std::ostringstream stream; - if (!shapeContentString.empty()) - stream << std::endl << std::endl; - stream << baseName.toLatin1().data() << ":" << std::endl; + ParameterGrp::handle group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Units"); + int decimals = group->GetInt("Decimals", 2); + group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("OutputWindow"); + bool logging = group->GetBool("checkLogging", true); + group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod")->GetGroup("Part")->GetGroup("CheckGeometry"); + bool advancedShapeContent = group->GetBool("AdvancedShapeContent", true); + std::ostringstream stream; + if (!shapeContentString.empty()) + stream << std::endl << std::endl; + stream << "CHECKED OBJECT: "; + std::ostringstream cmdstream; + cmdstream << "_basename = '" << baseName.toStdString().c_str() << "'" << std::endl; + cmdstream << "_obj = _basename[_basename.index('.')+1:]" << std::endl; + cmdstream << "_doc = _basename[:_basename.index(_obj)-1]" << std::endl; + cmdstream << "_shp = App.ActiveDocument.getObject(_obj).Shape" << std::endl; + cmdstream << "_type = str(_shp.ShapeType)" << std::endl; + cmdstream << "_result = _doc+'.'+App.ActiveDocument.getObject(_obj).Label+' ('+_obj+'):\\n'" << std::endl; + cmdstream << "_result += 'SHAPE_TYPE: '+_type+'\\n'" << std::endl; + cmdstream << "_result += 'VERTEX: '+str(len(_shp.Vertexes))+'\\n'" << std::endl; + cmdstream << "_result += 'EDGE: '+str(len(_shp.Edges))+'\\n'" << std::endl; + cmdstream << "_result += 'WIRE: '+str(len(_shp.Wires))+'\\n'" << std::endl; + cmdstream << "_result += 'FACE: '+str(len(_shp.Faces))+'\\n'" << std::endl; + cmdstream << "_result += 'SHELL: '+str(len(_shp.Shells))+'\\n'" << std::endl; + cmdstream << "_result += 'SOLID: '+str(len(_shp.Solids))+'\\n'" << std::endl; + cmdstream << "_result += 'COMPSOLID: '+str(len(_shp.CompSolids))+'\\n'" << std::endl; + cmdstream << "_result += 'COMPOUND: '+str(len(_shp.Compounds))+'\\n'" << std::endl; + cmdstream << "_result += 'SHAPE: '+str(len(_shp.Vertexes+_shp.Edges+_shp.Wires+_shp.Faces+_shp.Shells+_shp.Solids+_shp.CompSolids+_shp.Compounds))+'\\n'" << std::endl; + if (advancedShapeContent){ + cmdstream << "_result += '----------\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Area') and not 'Wire' in _type and not 'Edge' in _type and not 'Vertex' in _type:" << std::endl; + cmdstream << " _result += 'AREA: '+str(round(_shp.Area, " << decimals << "))+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Volume') and not 'Wire' in _type and not 'Edge' in _type and not 'Vertex' in _type and not 'Face' in _type:" << std::endl; + cmdstream << " _result += 'VOLUME: '+str(round(_shp.Volume, " << decimals << "))+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Mass'):" << std::endl; + cmdstream << " _result += 'MASS: '+str(round(_shp.Mass, " << decimals << "))+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Length'):" << std::endl; + cmdstream << " _result += 'LENGTH: '+str(round(_shp.Length, " << decimals << "))+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Curve') and hasattr(_shp.Curve,'Radius'):" << std::endl; + cmdstream << " _result += 'RADIUS: '+str(round(_shp.Curve.Radius, " << decimals << "))+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Curve') and hasattr(_shp.Curve,'Center'):" << std::endl; + cmdstream << " _result += 'CENTER_OF_CURVE: '+str([round(vv," << decimals << ") for vv in _shp.Curve.Center])+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'Curve') and hasattr(_shp.Curve,'Continuity'):" << std::endl; + cmdstream << " _result += 'CONTINUITY: '+str(_shp.Curve.Continuity)+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'CenterOfMass'):" << std::endl; + cmdstream << " _result += 'CENTER_OF_MASS: '+str([round(vv," << decimals << ") for vv in _shp.CenterOfMass])+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp,'normalAt'):" << std::endl; + cmdstream << " try:" << std::endl; + cmdstream << " _result += 'NORMAL_AT(0): '+str([round(vv," << decimals << ") for vv in _shp.normalAt(0)]) +'\\n'" << std::endl; + cmdstream << " except:" << std::endl; + cmdstream << " try:" << std::endl; + cmdstream << " _result += 'NORMAL_AT(0,0): '+str([round(vv," << decimals << ") for vv in _shp.normalAt(0,0)]) +'\\n'" << std::endl; + cmdstream << " except:" << std::endl; + cmdstream << " pass" << std::endl; + cmdstream << "if hasattr(_shp, 'isClosed') and ('Wire' in _type or 'Edge' in _type):" << std::endl; + cmdstream << " _result += 'IS_CLOSED?: '+str(_shp.isClosed())+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp, 'Orientation'):" << std::endl; + cmdstream << " _result += 'ORIENTATION: '+str(_shp.Orientation)+'\\n'" << std::endl; + cmdstream << "if hasattr(_shp, 'PrincipalProperties'):" << std::endl; + cmdstream << " _props = _shp.PrincipalProperties" << std::endl; + cmdstream << " for _p in _props:" << std::endl; + cmdstream << " if 'Base.Vector' in str(type(_props[_p])) or 'tuple' in str(type(_props[_p])):" << std::endl; + cmdstream << " _result += str(_p)+': '+str([round(vv," << decimals << ") for vv in _props[_p]]) +'\\n'" << std::endl; + cmdstream << " else:" << std::endl; + cmdstream << " _result += str(_p)+': '+str(_props[_p])+'\\n'" << std::endl; + } - BRepTools_ShapeSet set; - set.Add(shape); - set.DumpExtent(stream); - - shapeContentString += stream.str(); + std::string cmd = cmdstream.str(); + /** debugging can be a challenge if there is a runtime python error + * so log to report view so we can copy/paste into a macro to + * debug the script if it is failing + */ + if(logging){ + std::clog << "Building check geometry shape content using: " << std::endl + << cmd << std::endl; + } + std::string key_default = "script error"; + /** call runStringWithKey() with the key (_result) set to an error message + * if the script succeeds without error the key holds value set in the script + * if the script fails the key value remains unchanged + * so we check this to see if the script failed, so we can + * fallback on the old way of letting OCCT build the shape content + */ + std::string result = Base::Interpreter().runStringWithKey(cmd.c_str(),"_result",key_default.c_str()); + if (result.compare(key_default) != 0){ //success + stream << result; + } else { //use OCCT + stream << baseName.toLatin1().data() << std::endl; + BRepTools_ShapeSet set; + set.Add(shape); + set.DumpExtent(stream); + } + shapeContentString += stream.str(); } QString TaskCheckGeometryResults::getShapeContentString() @@ -1026,6 +1113,16 @@ the check geometry tool. Default: false")); this, SLOT(on_expandShapeContentCheckBox_toggled(bool))); settingsBox->groupLayout()->addWidget(expandShapeContentCheckBox); + advancedShapeContentCheckBox = new QCheckBox(); + advancedShapeContentCheckBox->setText(tr("Advanced shape content")); + advancedShapeContentCheckBox->setToolTip(tr("\ +Show advanced shape content. Changes will take effect next time you use \n\ +the check geometry tool. Default: false")); + advancedShapeContentCheckBox->setChecked(group->GetBool("AdvancedShapeContent", true)); + connect(advancedShapeContentCheckBox, SIGNAL(toggled(bool)), + this, SLOT(on_advancedShapeContentCheckBox_toggled(bool))); + settingsBox->groupLayout()->addWidget(advancedShapeContentCheckBox); + settingsBox->groupLayout()->addWidget(new QLabel(tr("\nIndividual BOP Checks:"))); argumentTypeModeCheckBox = new QCheckBox(); @@ -1116,7 +1213,11 @@ bool TaskCheckGeometryDialog::accept() shapeContentBox->show(); taskbox->show(); widget->goCheck(); + QScrollBar *v = contentLabel->verticalScrollBar(); + v->setValue(v->maximum()); //scroll to bottom + int curval = v->value(); //save position contentLabel->setText(widget->getShapeContentString()); + v->setValue(curval+(v->maximum()-curval)/5); return false; } @@ -1206,6 +1307,13 @@ void TaskCheckGeometryDialog::on_expandShapeContentCheckBox_toggled(bool isOn) group->SetBool("ExpandShapeContent", isOn); } +void TaskCheckGeometryDialog::on_advancedShapeContentCheckBox_toggled(bool isOn) +{ + ParameterGrp::handle group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod")->GetGroup("Part")->GetGroup("CheckGeometry"); + group->SetBool("AdvancedShapeContent", isOn); +} + void TaskCheckGeometryDialog::on_selfInterModeCheckBox_toggled(bool isOn) { ParameterGrp::handle group = App::GetApplication().GetUserParameter(). diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.h b/src/Mod/Part/Gui/TaskCheckGeometry.h index b45e7b1005..3f886c0ab9 100644 --- a/src/Mod/Part/Gui/TaskCheckGeometry.h +++ b/src/Mod/Part/Gui/TaskCheckGeometry.h @@ -148,6 +148,7 @@ private Q_SLOTS: void on_runSingleThreadedCheckBox_toggled(bool isOn); void on_logErrorsCheckBox_toggled(bool isOn); void on_expandShapeContentCheckBox_toggled(bool isOn); + void on_advancedShapeContentCheckBox_toggled(bool isOn); void on_autoRunCheckBox_toggled(bool isOn); void on_argumentTypeModeCheckBox_toggled(bool isOn); void on_selfInterModeCheckBox_toggled(bool isOn); @@ -171,6 +172,7 @@ private: QCheckBox *runSingleThreadedCheckBox; QCheckBox *logErrorsCheckBox; QCheckBox *expandShapeContentCheckBox; + QCheckBox *advancedShapeContentCheckBox; QCheckBox *argumentTypeModeCheckBox; QCheckBox *selfInterModeCheckBox; QCheckBox *smallEdgeModeCheckBox;