diff --git a/src/Base/Console.cpp b/src/Base/Console.cpp index ec51800014..3a970657a8 100644 --- a/src/Base/Console.cpp +++ b/src/Base/Console.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * (c) Jürgen Riegel (juergen.riegel@web.de) 2002 * + * (c) Jürgen Riegel (juergen.riegel@web.de) 2002 * * * * This file is part of the FreeCAD CAx development system. * * * @@ -10,12 +10,12 @@ * for detail see the LICENCE text file. * * * * FreeCAD is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * - * License along with FreeCAD; if not, write to the Free Software * + * License along with FreeCAD; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * * USA * * * @@ -49,7 +49,7 @@ using namespace Base; //========================================================================= namespace Base { - + class ConsoleEvent : public QEvent { public: ConsoleSingleton::FreeCAD_ConsoleMsgType msgtype; @@ -141,7 +141,7 @@ ConsoleSingleton::~ConsoleSingleton() //************************************************************************** // methods -/** +/** * sets the console in a special mode */ void ConsoleSingleton::SetConsoleMode(ConsoleMode m) @@ -150,7 +150,7 @@ void ConsoleSingleton::SetConsoleMode(ConsoleMode m) _bVerbose = true; } -/** +/** * unsets the console from a special mode */ void ConsoleSingleton::UnsetConsoleMode(ConsoleMode m) @@ -165,7 +165,7 @@ void ConsoleSingleton::UnsetConsoleMode(ConsoleMode m) * The return value is an OR'ed value of all message types that have changed their state. For example * @code * // switch off warnings and error messages - * ConsoleMsgFlags ret = Base::Console().SetEnabledMsgType("myObs", + * ConsoleMsgFlags ret = Base::Console().SetEnabledMsgType("myObs", * ConsoleMsgType::MsgType_Wrn|ConsoleMsgType::MsgType_Err, false); * // do something without notifying observer myObs * ... @@ -236,10 +236,10 @@ void ConsoleSingleton::SetConnectionMode(ConnectionMode mode) } /** Prints a Message - * This method issues a Message. + * This method issues a Message. * Messages are used to show some non vital information. That means when - * FreeCAD is running in GUI mode a Message appears on the status bar. - * In console mode a message is printed to the console. + * FreeCAD is running in GUI mode a Message appears on the status bar. + * In console mode a message is printed to the console. * \par * You can use a printf like interface like: * \code @@ -251,8 +251,8 @@ void ConsoleSingleton::SetConnectionMode(ConnectionMode mode) */ void ConsoleSingleton::Message( const char *pMsg, ... ) { - char format[4024]; - const unsigned int format_len = 4024; + char format[BufferSize]; + const unsigned int format_len = BufferSize; va_list namelessVars; va_start(namelessVars, pMsg); // Get the "..." vars @@ -266,7 +266,7 @@ void ConsoleSingleton::Message( const char *pMsg, ... ) } /** Prints a Message - * This method issues a Warning. + * This method issues a Warning. * Messages are used to get the users attention. That means when * FreeCAD is in GUI mode a Message Box pops up. In console * mode a colored message is returned to the console! Don't use this carelessly. @@ -282,8 +282,8 @@ void ConsoleSingleton::Message( const char *pMsg, ... ) */ void ConsoleSingleton::Warning( const char *pMsg, ... ) { - char format[4024]; - const unsigned int format_len = 4024; + char format[BufferSize]; + const unsigned int format_len = BufferSize; va_list namelessVars; va_start(namelessVars, pMsg); // Get the "..." vars @@ -297,10 +297,10 @@ void ConsoleSingleton::Warning( const char *pMsg, ... ) } /** Prints a Message - * This method issues an Error which makes some execution impossible. - * Errors are used to get the users attention. That means when FreeCAD + * This method issues an Error which makes some execution impossible. + * Errors are used to get the users attention. That means when FreeCAD * is running in GUI mode an Error Message Box pops up. In console - * mode a colored message is printed to the console! Don't use this carelessly. + * mode a colored message is printed to the console! Don't use this carelessly. * For information purposes the 'Log' or 'Message' methods are more appropriate. * \par * You can use a printf like interface like: @@ -313,8 +313,8 @@ void ConsoleSingleton::Warning( const char *pMsg, ... ) */ void ConsoleSingleton::Error( const char *pMsg, ... ) { - char format[4024]; - const unsigned int format_len = 4024; + char format[BufferSize]; + const unsigned int format_len = BufferSize; va_list namelessVars; va_start(namelessVars, pMsg); // Get the "..." vars @@ -331,7 +331,7 @@ void ConsoleSingleton::Error( const char *pMsg, ... ) /** Prints a Message * This method is appropriate for development and tracking purposes. * It can be used to track execution of algorithms and functions. - * The normal user doesn't need to see it, it's more for developers + * The normal user doesn't need to see it, it's more for developers * and experienced users. So in normal user mode the logging is switched off. * \par * You can use a printf-like interface for example: @@ -346,8 +346,8 @@ void ConsoleSingleton::Error( const char *pMsg, ... ) void ConsoleSingleton::Log( const char *pMsg, ... ) { - char format[4024]; - const unsigned int format_len = 4024; + char format[BufferSize]; + const unsigned int format_len = BufferSize; if (_bVerbose) { @@ -373,8 +373,8 @@ const char* ConsoleSingleton::Time(void) { struct tm *newtime; time_t aclock; - time( &aclock ); // Get time in seconds - newtime = localtime( &aclock ); // Convert time to struct tm form + time( &aclock ); // Get time in seconds + newtime = localtime( &aclock ); // Convert time to struct tm form char* st = asctime( newtime ); st[24] = 0; return st; @@ -386,7 +386,7 @@ const char* ConsoleSingleton::Time(void) // Observer stuff /** Attaches an Observer to Console - * Use this method to attach a ConsoleObserver derived class to + * Use this method to attach a ConsoleObserver derived class to * the Console. After the observer is attached all messages will also * be forwarded to it. * @see ConsoleObserver @@ -703,8 +703,8 @@ PyObject *ConsoleSingleton::sPyGetStatus(PyObject * /*self*/, PyObject *args) { char *pstr1; char *pstr2; - if (!PyArg_ParseTuple(args, "ss", &pstr1, &pstr2)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "ss", &pstr1, &pstr2)) // convert args: Python->C + return NULL; // NULL triggers exception PY_TRY{ bool b=false; @@ -723,7 +723,7 @@ PyObject *ConsoleSingleton::sPyGetStatus(PyObject * /*self*/, PyObject *args) b = pObs->bMsg; else if(strcmp(pstr2,"Err") == 0) b = pObs->bErr; - + return Py_BuildValue("i",b?1:0); }PY_CATCH; } @@ -733,8 +733,8 @@ PyObject *ConsoleSingleton::sPySetStatus(PyObject * /*self*/, PyObject *args) char *pstr1; char *pstr2; int Bool; - if (!PyArg_ParseTuple(args, "ssi", &pstr1, &pstr2,&Bool)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "ssi", &pstr1, &pstr2,&Bool)) // convert args: Python->C + return NULL; // NULL triggers exception PY_TRY{ ConsoleObserver *pObs = Instance().Get(pstr1); @@ -754,7 +754,7 @@ PyObject *ConsoleSingleton::sPySetStatus(PyObject * /*self*/, PyObject *args) Py_INCREF(Py_None); return Py_None; } else { - Py_Error(Base::BaseExceptionFreeCADError,"Unknown Console Type"); + Py_Error(Base::BaseExceptionFreeCADError,"Unknown Console Type"); } } PY_CATCH; @@ -887,7 +887,7 @@ void ConsoleObserverStd::Log (const char *sErr) } } -RedirectStdOutput::RedirectStdOutput() +RedirectStdOutput::RedirectStdOutput() { buffer.reserve(80); } @@ -909,7 +909,7 @@ int RedirectStdOutput::sync() return 0; } -RedirectStdLog::RedirectStdLog() +RedirectStdLog::RedirectStdLog() { buffer.reserve(80); } @@ -931,7 +931,7 @@ int RedirectStdLog::sync() return 0; } -RedirectStdError::RedirectStdError() +RedirectStdError::RedirectStdError() { buffer.reserve(80); } diff --git a/src/Base/Console.h b/src/Base/Console.h index 9c290b0f1e..51c2ebd80f 100644 --- a/src/Base/Console.h +++ b/src/Base/Console.h @@ -1,5 +1,5 @@ /*************************************************************************** - * (c) Jürgen Riegel (juergen.riegel@web.de) 2002 * + * (c) Jürgen Riegel (juergen.riegel@web.de) 2002 * * * * This file is part of the FreeCAD CAx development system. * * * @@ -10,18 +10,18 @@ * for detail see the LICENCE text file. * * * * FreeCAD is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * - * License along with FreeCAD; if not, write to the Free Software * + * License along with FreeCAD; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * * USA * * * * Juergen Riegel 2002 * ***************************************************************************/ - + @@ -45,11 +45,11 @@ #ifdef FC_DEBUG /// switch on the logging of python object creation and destruction -# undef FC_LOGPYOBJECTS +# undef FC_LOGPYOBJECTS /// switch on the logging of Feature update and execution -# define FC_LOGFEATUREUPDATE +# define FC_LOGFEATUREUPDATE /// switch on the logging of the Update execution through Doc, App, GuiApp and GuiDoc -# undef FC_LOGUPDATECHAIN +# undef FC_LOGUPDATECHAIN #endif ///////////////////////////////////////////////////////////////////////////////////// @@ -57,7 +57,7 @@ /** \page LogLevelPage Tag based log helpers * Simple tag based log and timing helper macros and functions. * - * \section Motivation + * \section Motivation * * FreeCAD Base::Console() is capable of outputting to different targets, and has * some basic enable/disable control of different types of logs. There is, @@ -84,7 +84,7 @@ * the same source file. * * Predefined log levels are, - * + * * \code{.c} * #define FC_LOGLEVEL_ERR 0 * #define FC_LOGLEVEL_WARN 1 @@ -118,7 +118,7 @@ * tag log levels, and \c FreeCAD.getLogLevel(tag), which outputs only integer * log level. * - * You can fine tune how the log is output by passing extra parameters to + * You can fine tune how the log is output by passing extra parameters to * #FC_LOG_LEVEL_INIT(). All the extra parameters are boolean value, which are * shown blew along with their default values. * @@ -157,7 +157,7 @@ * and \c FC_TRACE uses Base::Console().Log(), same as \c FC_LOG. These macros * checks the log level defined in \c FC_LOG_LEVEL_INIT to decide whether to * print log or not. \c msg here shall be a C++ streaming expression. End of - * line will be automatically appended by default. + * line will be automatically appended by default. * * \code * FC_ERR("error: " << code << ". exiting") @@ -171,7 +171,7 @@ * \code{.c} * void operation() { * FC_TIME_INIT(t); - * + * * //do stuff * * FC_TIME_LOG(t,"operation done."); @@ -182,7 +182,7 @@ * * \code * operation done. time: 1.12s - * \endcode + * \endcode * * Every time you call \c FC_TIME_LOG it will calculate the time duration * between this call and the last \c FC_TIME_LOG or \c FC_TIME_INIT. Time @@ -194,7 +194,7 @@ * \code{.cpp} * void operation() { * FC_TIME_INIT2(t,t1); - * + * * //do stage 1 * * FC_TIME_LOG(t1,"stage1"); @@ -234,7 +234,7 @@ * }; * * void operation1(Timing &timing) { - * + * * FC_TIME_INIT(t); * * for(...) { @@ -245,7 +245,7 @@ * FC_DURATION_PLUS(timing.d1_1,t1); * * // do step 2 - * + * * FC_DURATION_PLUS(timing.d1_2,t1); * } * @@ -255,7 +255,7 @@ * } * * void operation2(Timing &timing) { - * + * * FC_TIME_INIT(t); * * // do stuff @@ -264,7 +264,7 @@ * } * * void operation() { - * + * * Timing timing; * * FC_TIME_INIT(t); @@ -273,7 +273,7 @@ * operation1(timing); * * // do other stuff - * + * * operation2(timing); * } * @@ -285,10 +285,10 @@ * } * \endcode * - * You can also use FC_DURATION_MSG, FC_DURATION_TRACE as usual. + * You can also use FC_DURATION_MSG, FC_DURATION_TRACE as usual. * * If you use only macros provided here to do timing, the entire timing code - * can be compiled out by defining \c FC_LOG_NO_TIMING before including + * can be compiled out by defining \c FC_LOG_NO_TIMING before including * \c App/Console.h. * * \section Customization @@ -300,7 +300,7 @@ * function returns a pointer to an integer representing the log level. Python * developer or end-user can set/get the same tag based log level using * FreeCAD.setLogLevel/getLogLevel. Any change to the log level is reflected - * through the pointer returned by Base::Console().GetLogLevel(). What + * through the pointer returned by Base::Console().GetLogLevel(). What * \c FC_LOG_LEVEL_INIT(tag) does is to define a class Base::LogLevel, and then * a file static instance of that class to store the pointer to the desired tag * log level. The class and instance name is predefined. Various log macros @@ -326,7 +326,7 @@ * * You can also define your own log levels the same way. Macro * #_FC_PRINT(_instance,_l,_func,_msg) checks to see if the log shall proceed, - * where \c _instance is the static loglevel instance name (default is + * where \c _instance is the static loglevel instance name (default is * \c FC_LOG_INSTANCE), and \c _l is the log level constant to be checked, * \c _func is the Base::Console() function to print the log. * @@ -428,7 +428,7 @@ # define FC_DURATION_PLUS(...) do{}while(0) #endif //FC_LOG_NO_TIMING - + namespace Base { class ConsoleSingleton; } // namespace Base @@ -449,7 +449,7 @@ namespace Base { #endif /** The console observer class - * This class distribute the Messages issued to the FCConsole class. + * This class distribute the Messages issued to the FCConsole class. * If you need to catch some of the Messages you need to inherit from * this class and implement some of the methods. * @see Console @@ -475,7 +475,7 @@ public: /** The console class - * This class manage all the stdio stuff. This includes + * This class manage all the stdio stuff. This includes * Messages, Warnings, Log entries and Errors. The incoming * Messages are distributed with the FCConsoleObserver. The * FCConsole class itself makes no IO, it's more like a manager. @@ -495,18 +495,18 @@ class BaseExport ConsoleSingleton { public: - + static const unsigned int BufferSize = 4024; // exported functions goes here +++++++++++++++++++++++++++++++++++++++ - /// Prints a Message + /// Prints a Message virtual void Message ( const char * pMsg, ... ) ; - /// Prints a warning Message + /// Prints a warning Message virtual void Warning ( const char * pMsg, ... ) ; - /// Prints a error Message + /// Prints a error Message virtual void Error ( const char * pMsg, ... ) ; - /// Prints a log Message + /// Prints a log Message virtual void Log ( const char * pMsg, ... ) ; - /// Delivers a time/date string + /// Delivers a time/date string const char* Time(void); /// Attaches an Observer to FCConsole void AttachObserver(ConsoleObserver *pcObserver); @@ -548,7 +548,7 @@ public: return level<0?_defaultLogLevel:level; } - /// singleton + /// singleton static ConsoleSingleton &Instance(void); // retrieval of an observer by name @@ -560,7 +560,7 @@ public: void EnableRefresh(bool enable); protected: - // python exports goes here +++++++++++++++++++++++++++++++++++++++++++ + // python exports goes here +++++++++++++++++++++++++++++++++++++++++++ // static python wrapper of the exported functions static PyObject *sPyLog (PyObject *self,PyObject *args); static PyObject *sPyMessage (PyObject *self,PyObject *args); @@ -582,7 +582,7 @@ private: static void Destruct(void); static ConsoleSingleton *_pcSingleton; - // observer processing + // observer processing void NotifyMessage(const char *sMsg); void NotifyWarning(const char *sMsg); void NotifyError (const char *sMsg); @@ -598,9 +598,9 @@ private: }; /** Access to the Console - * This method is used to gain access to the one and only instance of + * This method is used to gain access to the one and only instance of * the ConsoleSingleton class. - */ + */ inline ConsoleSingleton &Console(void){ return ConsoleSingleton::Instance(); } @@ -617,7 +617,7 @@ public: }; -/** LogLevel helper class */ +/** LogLevel helper class */ class BaseExport LogLevel { public: std::string tag; @@ -652,7 +652,7 @@ public: /** The LoggingConsoleObserver class * This class is used by the main modules to write Console messages and logs to a file - */ + */ class BaseExport ConsoleObserverFile : public ConsoleObserver { public: @@ -677,9 +677,9 @@ public: ConsoleObserverStd(); virtual ~ConsoleObserverStd(); virtual void Warning(const char *sWarn); - virtual void Message(const char *sMsg); - virtual void Error (const char *sErr); - virtual void Log (const char *sErr); + virtual void Message(const char *sMsg); + virtual void Error (const char *sErr); + virtual void Log (const char *sErr); const char* Name(void){return "Console";} protected: bool useColorStderr; @@ -725,6 +725,6 @@ private: }; -} // namespace Base +} // namespace Base #endif // BASE_CONSOLE_H diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py index 325214b26d..dbbfc056c2 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py @@ -1,6 +1,6 @@ # *************************************************************************** -# * * -# * Copyright (c) 2013 Juergen Riegel * +# * Copyright (c) 2013 Juergen Riegel * +# * Copyright (c) 2016 Bernd Hahnebach * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -77,10 +77,12 @@ class _ViewProviderFemMaterial: FreeCADGui.Control.closeDialog() return True - # overwrite the doubleClicked of material object python to make sure no other Material taskd (and thus no selection observer) is still active + # overwrite the doubleClicked of material object python to make sure no other Material taskd + # (and thus no selection observer) is still active def doubleClicked(self, vobj): guidoc = FreeCADGui.getDocument(vobj.Object.Document) - # check if another VP is in edit mode, https://forum.freecadweb.org/viewtopic.php?t=13077#p104702 + # check if another VP is in edit mode + # https://forum.freecadweb.org/viewtopic.php?t=13077#p104702 if not guidoc.getInEdit(): guidoc.setEdit(vobj.Object.Name) else: @@ -106,31 +108,79 @@ class _TaskPanelFemMaterial: self.material = self.obj.Material # FreeCAD material dictionary of current material self.card_path = '' self.materials = {} # { card_path : FreeCAD material dict } + self.icons = {} # { card_path : icon_path } # mat_card is the FCMat file # card_name is the file name of the mat_card # card_path is the whole file path of the mat_card # material_name is the value of the key name in FreeCAD material dictionary - # they might not match because of special letters in the material_name which are changed in the card_name to english standard characters + # they might not match because of special letters in the material_name + # which are changed in the card_name to english standard characters self.has_transient_mat = False # parameter widget - self.parameterWidget = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/Material.ui") + self.parameterWidget = FreeCADGui.PySideUic.loadUi( + FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/Material.ui" + ) # globals - QtCore.QObject.connect(self.parameterWidget.cb_materials, QtCore.SIGNAL("activated(int)"), self.choose_material) - QtCore.QObject.connect(self.parameterWidget.chbu_allow_edit, QtCore.SIGNAL("clicked()"), self.toggleInputFieldsReadOnly) - QtCore.QObject.connect(self.parameterWidget.pushButton_editMat, QtCore.SIGNAL("clicked()"), self.edit_material) + QtCore.QObject.connect( + self.parameterWidget.cb_materials, + QtCore.SIGNAL("activated(int)"), + self.choose_material + ) + QtCore.QObject.connect( + self.parameterWidget.chbu_allow_edit, + QtCore.SIGNAL("clicked()"), + self.toggleInputFieldsReadOnly + ) + QtCore.QObject.connect( + self.parameterWidget.pushButton_editMat, + QtCore.SIGNAL("clicked()"), + self.edit_material + ) # basic properties must be provided - QtCore.QObject.connect(self.parameterWidget.input_fd_density, QtCore.SIGNAL("editingFinished()"), self.density_changed) + QtCore.QObject.connect( + self.parameterWidget.input_fd_density, + QtCore.SIGNAL("editingFinished()"), + self.density_changed + ) # mechanical properties - QtCore.QObject.connect(self.parameterWidget.input_fd_young_modulus, QtCore.SIGNAL("editingFinished()"), self.ym_changed) - QtCore.QObject.connect(self.parameterWidget.spinBox_poisson_ratio, QtCore.SIGNAL("editingFinished()"), self.pr_changed) + QtCore.QObject.connect( + self.parameterWidget.input_fd_young_modulus, + QtCore.SIGNAL("editingFinished()"), + self.ym_changed + ) + QtCore.QObject.connect( + self.parameterWidget.spinBox_poisson_ratio, + QtCore.SIGNAL("editingFinished()"), + self.pr_changed + ) # thermal properties - QtCore.QObject.connect(self.parameterWidget.input_fd_thermal_conductivity, QtCore.SIGNAL("editingFinished()"), self.tc_changed) - QtCore.QObject.connect(self.parameterWidget.input_fd_expansion_coefficient, QtCore.SIGNAL("editingFinished()"), self.tec_changed) - QtCore.QObject.connect(self.parameterWidget.input_fd_specific_heat, QtCore.SIGNAL("editingFinished()"), self.sh_changed) + QtCore.QObject.connect( + self.parameterWidget.input_fd_thermal_conductivity, + QtCore.SIGNAL("editingFinished()"), + self.tc_changed + ) + QtCore.QObject.connect( + self.parameterWidget.input_fd_expansion_coefficient, + QtCore.SIGNAL("editingFinished()"), + self.tec_changed + ) + QtCore.QObject.connect( + self.parameterWidget.input_fd_specific_heat, + QtCore.SIGNAL("editingFinished()"), + self.sh_changed + ) # fluidic properties, only volumetric thermal expansion coeff makes sense - QtCore.QObject.connect(self.parameterWidget.input_fd_kinematic_viscosity, QtCore.SIGNAL("editingFinished()"), self.kinematic_viscosity_changed) - QtCore.QObject.connect(self.parameterWidget.input_fd_vol_expansion_coefficient, QtCore.SIGNAL("editingFinished()"), self.vtec_changed) + QtCore.QObject.connect( + self.parameterWidget.input_fd_kinematic_viscosity, + QtCore.SIGNAL("editingFinished()"), + self.kinematic_viscosity_changed + ) + QtCore.QObject.connect( + self.parameterWidget.input_fd_vol_expansion_coefficient, + QtCore.SIGNAL("editingFinished()"), + self.vtec_changed + ) # init all parameter input files with read only self.parameterWidget.chbu_allow_edit.setCheckState(QtCore.Qt.CheckState.Unchecked) @@ -147,8 +197,10 @@ class _TaskPanelFemMaterial: self.parameterWidget.label_vol_expansion_coefficient.setVisible(0) self.parameterWidget.input_fd_vol_expansion_coefficient.setVisible(0) - # fill self.materials dict and fill the combobox with material cards + # get all available materials (fill self.materials and self.icons) self.import_materials() + # fill the material comboboxes with material cards + self.add_cards_to_combo_box() # search for exact this mat_card in all known cards, choose the current material self.card_path = self.get_material_card(self.material) @@ -156,22 +208,38 @@ class _TaskPanelFemMaterial: if not self.card_path: # we have not found our material in self.materials dict :-( # we're going to add a user-defined temporary material: a document material - FreeCAD.Console.PrintMessage("Previously used material card cannot be found in material directories. Add document material.\n") + FreeCAD.Console.PrintMessage( + "Previously used material card cannot be found in material directories. " + "Add document material.\n" + ) self.card_path = '_document_material' self.materials[self.card_path] = self.material - self.parameterWidget.cb_materials.addItem(QtGui.QIcon(":/icons/help-browser.svg"), self.card_path, self.card_path) + self.parameterWidget.cb_materials.addItem( + QtGui.QIcon(":/icons/help-browser.svg"), + self.card_path, + self.card_path + ) index = self.parameterWidget.cb_materials.findData(self.card_path) # print(index) - self.choose_material(index) # fill input fields and set the current material in the cb widget + # fill input fields and set the current material in the cb widget + self.choose_material(index) else: # we found our exact material in self.materials dict :-) - FreeCAD.Console.PrintMessage("Previously used material card was found in material directories. We will use this material.\n") + FreeCAD.Console.PrintMessage( + "Previously used material card was found in material directories. " + "We will use this material.\n" + ) index = self.parameterWidget.cb_materials.findData(self.card_path) # print(index) - self.choose_material(index) # fill input fields and set the current material in the cb widget + # fill input fields and set the current material in the cb widget + self.choose_material(index) # geometry selection widget - self.selectionWidget = FemSelectionWidgets.GeometryElementsSelection(obj.References, ['Solid', 'Face', 'Edge'], False) # start with Solid in list! + self.selectionWidget = FemSelectionWidgets.GeometryElementsSelection( + obj.References, + ['Solid', 'Face', 'Edge'], + False + ) # start with Solid in list! # form made from param and selection widget self.form = [self.parameterWidget, self.selectionWidget] @@ -179,10 +247,11 @@ class _TaskPanelFemMaterial: # check references, has to be after initialisation of selectionWidget self.selectionWidget.has_equal_references_shape_types() - # leave task panel ********************************************** + # leave task panel *************************************************************************** def accept(self): # print(self.material) if self.selectionWidget.has_equal_references_shape_types(): + self.do_not_set_thermal_zeros() self.obj.Material = self.material self.obj.References = self.selectionWidget.references self.recompute_and_set_back_all() @@ -200,11 +269,35 @@ class _TaskPanelFemMaterial: FreeCADGui.Selection.removeObserver(self.selectionWidget.sel_server) doc.resetEdit() - # choose material *********************************************** + def do_not_set_thermal_zeros(self): + ''' thermal material parameter are set to 0.0 if not available + this leads to wrong material values and to not finding the card + on reopen the task pane, thus do not write thermal parameter, + if they are 0.0 + ''' + if Units.Quantity(self.material['ThermalConductivity']) == 0.0: + self.material.pop('ThermalConductivity', None) + FreeCAD.Console.PrintMessage( + 'Zero ThermalConductivity value. It is not saved in the card data.\n' + ) + if Units.Quantity(self.material['ThermalExpansionCoefficient']) == 0.0: + self.material.pop('ThermalExpansionCoefficient', None) + FreeCAD.Console.PrintMessage( + 'Zero ThermalExpansionCoefficient value. It is not saved in the card data.\n' + ) + if Units.Quantity(self.material['SpecificHeat']) == 0.0: + self.material.pop('SpecificHeat', None) + FreeCAD.Console.PrintMessage( + 'Zero SpecificHeat value. It is not saved in the card data.\n' + ) + + # choose material **************************************************************************** def get_material_card(self, material): for a_mat in self.materials: unmatched_items = set(self.materials[a_mat].items()) ^ set(material.items()) # print(a_mat + ' --> unmatched_items = ' + str(len(unmatched_items))) + # if len(unmatched_items) < 4: + # print(unmatched_items) if len(unmatched_items) == 0: return a_mat return "" @@ -212,7 +305,7 @@ class _TaskPanelFemMaterial: def choose_material(self, index): if index < 0: return - self.card_path = self.parameterWidget.cb_materials.itemData(index) # returns the whole path ! + self.card_path = self.parameterWidget.cb_materials.itemData(index) # returns whole path # print('choose_material: ' + self.card_path) self.material = self.materials[self.card_path] self.check_material_keys() @@ -237,28 +330,52 @@ class _TaskPanelFemMaterial: def add_transient_material(self): self.has_transient_mat = True self.card_path = '_transient_material' - self.parameterWidget.cb_materials.addItem(QtGui.QIcon(":/icons/help-browser.svg"), self.card_path, self.card_path) + self.parameterWidget.cb_materials.addItem( + QtGui.QIcon(":/icons/help-browser.svg"), + self.card_path, + self.card_path + ) self.set_transient_material() - # how to edit a material **************************************** + # how to edit a material ********************************************************************* def edit_material(self): # opens the material editor to choose a material or edit material params # self.print_material_params() import MaterialEditor new_material_params = self.material.copy() new_material_params = MaterialEditor.editMaterial(new_material_params) - # if the material editor was canceled a empty params dict will be returned, do not change the self.material + # if the material editor was canceled a empty params dict will be returned + # do not change the self.material # self.print_material_params(new_material_params) - if new_material_params: # returns True if dict is not empty (do not use 'is True', this would return False for a non empty dict) + # check if dict is not empty (do not use 'is True' + if new_material_params: self.material = new_material_params - self.check_material_keys() - self.set_mat_params_in_input_fields(self.material) - if self.has_transient_mat is False: - self.add_transient_material() + self.card_path = self.get_material_card(self.material) + # print('card_path: ' + self.card_path) + self.check_material_keys() + self.set_mat_params_in_input_fields(self.material) + if not self.card_path: + if self.has_transient_mat is False: + self.add_transient_material() + else: + self.set_transient_material() + else: + # we found our exact material in self.materials dict :-) + FreeCAD.Console.PrintMessage( + "Material card was found in material directories. " + "We will use this material.\n" + ) + index = self.parameterWidget.cb_materials.findData(self.card_path) + # print(index) + # set the current material in the cb widget + self.choose_material(index) else: - self.set_transient_material() + FreeCAD.Console.PrintMessage( + 'No changes where made by the material editor.\n' + ) # self.print_material_params() - # material editor returns the mat_dict only not a card_path, if a standard FreeCAD mat_card was used + # material editor returns the mat_dict only not a card_path + # if a standard FreeCAD mat_card was used def toggleInputFieldsReadOnly(self): if self.parameterWidget.chbu_allow_edit.isChecked(): @@ -280,10 +397,11 @@ class _TaskPanelFemMaterial: self.parameterWidget.input_fd_kinematic_viscosity.setReadOnly(True) self.parameterWidget.input_fd_vol_expansion_coefficient.setReadOnly(True) - # material parameter input fields ******************************* + # material parameter input fields ************************************************************ def print_material_params(self, material=None): # in rare cases we gone pass a empty dict - # in such a case a empty dict should be printed and not self.material thus we check for None + # in such a case a empty dict should be printed and not self.material + # thus we check for None if material is None: material = self.material if not material: @@ -297,72 +415,131 @@ class _TaskPanelFemMaterial: def check_material_keys(self): # FreeCAD units definition is at file end of src/Base/Unit.cpp if not self.material: - print('For some reason all material data is empty!') + FreeCAD.Console.PrintMessage('For some reason all material data is empty!\n') self.material['Name'] = 'Empty' if 'Density' in self.material: if 'Density' not in str(Units.Unit(self.material['Density'])): - print('Density in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'Density in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['Density'] = '0 kg/m^3' else: - print('Density not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'Density not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['Density'] = '0 kg/m^3' if self.obj.Category == 'Solid': # mechanical properties if 'YoungsModulus' in self.material: - if 'Pressure' not in str(Units.Unit(self.material['YoungsModulus'])): # unit type of YoungsModulus is Pressure - print('YoungsModulus in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + # unit type of YoungsModulus is Pressure + if 'Pressure' not in str(Units.Unit(self.material['YoungsModulus'])): + FreeCAD.Console.PrintMessage( + 'YoungsModulus in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['YoungsModulus'] = '0 MPa' else: - print('YoungsModulus not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'YoungsModulus not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['YoungsModulus'] = '0 MPa' if 'PoissonRatio' in self.material: # PoissonRatio does not have a unit, but it is checked it there is no value at all try: float(self.material['PoissonRatio']) except: - print('PoissonRatio has wrong or no data (reset the value): ' + self.material['PoissonRatio']) + FreeCAD.Console.PrintMessage( + 'PoissonRatio has wrong or no data (reset the value): {}\n' + .format(self.material['PoissonRatio']) + ) self.material['PoissonRatio'] = '0' else: - print('PoissonRatio not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'PoissonRatio not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['PoissonRatio'] = '0' if self.obj.Category == 'Fluid': # Fluidic properties if 'KinematicViscosity' in self.material: - if 'KinematicViscosity' not in str(Units.Unit(self.material['KinematicViscosity'])): - print('KinematicViscosity in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + ki_vis = self.material['KinematicViscosity'] + if 'KinematicViscosity' not in str(Units.Unit(ki_vis)): + FreeCAD.Console.PrintMessage( + 'KinematicViscosity in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['KinematicViscosity'] = '0 m^2/s' else: - print('KinematicViscosity not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'KinematicViscosity not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['KinematicViscosity'] = '0 m^2/s' if 'VolumetricThermalExpansionCoefficient' in self.material: - # unit type of VolumetricThermalExpansionCoefficient is ThermalExpansionCoefficient - if 'VolumetricThermalExpansionCoefficient' not in str(Units.Unit(self.material['VolumetricThermalExpansionCoefficient'])): - print('VolumetricThermalExpansionCoefficient in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + # unit type VolumetricThermalExpansionCoefficient is ThermalExpansionCoefficient + vol_ther_ex_co = self.material['VolumetricThermalExpansionCoefficient'] + if 'VolumetricThermalExpansionCoefficient' not in str(Units.Unit(vol_ther_ex_co)): + FreeCAD.Console.PrintMessage( + 'VolumetricThermalExpansionCoefficient in material data ' + 'seems to have no unit or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['VolumetricThermalExpansionCoefficient'] = '0 m/m/K' else: - print('VolumetricThermalExpansionCoefficient not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'VolumetricThermalExpansionCoefficient not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['VolumetricThermalExpansionCoefficient'] = '0 m/m/K' # Thermal properties if 'ThermalConductivity' in self.material: if 'ThermalConductivity' not in str(Units.Unit(self.material['ThermalConductivity'])): - print('ThermalConductivity in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'ThermalConductivity in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['ThermalConductivity'] = '0 W/m/K' else: - print('ThermalConductivity not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'ThermalConductivity not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['ThermalConductivity'] = '0 W/m/K' if 'ThermalExpansionCoefficient' in self.material: - if 'ThermalExpansionCoefficient' not in str(Units.Unit(self.material['ThermalExpansionCoefficient'])): - print('ThermalExpansionCoefficient in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + the_ex_co = self.material['ThermalExpansionCoefficient'] + if 'ThermalExpansionCoefficient' not in str(Units.Unit(the_ex_co)): + FreeCAD.Console.PrintMessage( + 'ThermalExpansionCoefficient in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['ThermalExpansionCoefficient'] = '0 um/m/K' else: - print('ThermalExpansionCoefficient not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'ThermalExpansionCoefficient not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['ThermalExpansionCoefficient'] = '0 um/m/K' if 'SpecificHeat' in self.material: if 'SpecificHeat' not in str(Units.Unit(self.material['SpecificHeat'])): - print('SpecificHeat in material data seems to have no unit or a wrong unit (reset the value): ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'SpecificHeat in material data seems to have no unit ' + 'or a wrong unit (reset the value): {}\n' + .format(self.material['Name']) + ) self.material['SpecificHeat'] = '0 J/kg/K' else: - print('SpecificHeat not found in material data of: ' + self.material['Name']) + FreeCAD.Console.PrintMessage( + 'SpecificHeat not found in material data of: {}\n' + .format(self.material['Name']) + ) self.material['SpecificHeat'] = '0 J/kg/K' # mechanical input fields @@ -392,7 +569,8 @@ class _TaskPanelFemMaterial: # density has changed material = self.material value_in_kg_per_m3 = value * 1e9 - material['Density'] = unicode(value_in_kg_per_m3) + " kg/m^3" # SvdW:Keep density in SI units for easier readability + # SvdW:Keep density in SI units for easier readability + material['Density'] = unicode(value_in_kg_per_m3) + " kg/m^3" self.material = material if self.has_transient_mat is False: self.add_transient_material() @@ -442,7 +620,9 @@ class _TaskPanelFemMaterial: def tec_changed(self): value = self.parameterWidget.input_fd_expansion_coefficient.property("rawValue") - old_tec = Units.Quantity(self.material['ThermalExpansionCoefficient']).getValueAs("um/m/K") + old_tec = Units.Quantity( + self.material['ThermalExpansionCoefficient'] + ).getValueAs("um/m/K") variation = 0.001 if value: if not (1 - variation < float(old_tec) / value < 1 + variation): @@ -475,14 +655,16 @@ class _TaskPanelFemMaterial: # fluidic input fields def vtec_changed(self): value = self.parameterWidget.input_fd_vol_expansion_coefficient.property("rawValue") - old_vtec = Units.Quantity(self.material['VolumetricThermalExpansionCoefficient']).getValueAs("m/m/K") + old_vtec = Units.Quantity( + self.material['VolumetricThermalExpansionCoefficient'] + ).getValueAs("m/m/K") variation = 0.001 if value: if not (1 - variation < float(old_vtec) / value < 1 + variation): # VolumetricThermalExpansionCoefficient has changed material = self.material - value_in_one_per_K = value - material['VolumetricThermalExpansionCoefficient'] = unicode(value_in_one_per_K) + " m/m/K" + value_in_one_per_K = unicode(value) + " m/m/K" + material['VolumetricThermalExpansionCoefficient'] = value_in_one_per_K self.material = material if self.has_transient_mat is False: self.add_transient_material() @@ -521,7 +703,8 @@ class _TaskPanelFemMaterial: nu_with_new_unit = nu.getValueAs(nu_new_unit) q = FreeCAD.Units.Quantity("{} {}".format(nu_with_new_unit, nu_new_unit)) self.parameterWidget.input_fd_kinematic_viscosity.setText(q.UserString) - # For isotropic materials the volumetric thermal expansion coefficient is three times the linear coefficient: + # For isotropic materials the volumetric thermal expansion coefficient + # is three times the linear coefficient: if 'VolumetricThermalExpansionCoefficient' in matmap: # linear, only for solid vtec_new_unit = "m/m/K" vtec = FreeCAD.Units.Quantity(matmap['VolumetricThermalExpansionCoefficient']) @@ -532,7 +715,9 @@ class _TaskPanelFemMaterial: density_new_unit = "kg/m^3" density = FreeCAD.Units.Quantity(matmap['Density']) density_with_new_unit = density.getValueAs(density_new_unit) - # self.parameterWidget.input_fd_density.setText("{} {}".format(density_with_new_unit, density_new_unit)) + # self.parameterWidget.input_fd_density.setText( + # "{} {}".format(density_with_new_unit, density_new_unit) + # ) q = FreeCAD.Units.Quantity("{} {}".format(density_with_new_unit, density_new_unit)) self.parameterWidget.input_fd_density.setText(q.UserString) # thermal properties @@ -555,7 +740,20 @@ class _TaskPanelFemMaterial: q = FreeCAD.Units.Quantity("{} {}".format(sh_with_new_unit, sh_new_unit)) self.parameterWidget.input_fd_specific_heat.setText(q.UserString) - # material import and export ************************************ + # fill the combo box with cards ************************************************************** + def add_cards_to_combo_box(self): + # fill combobox, in combo box the card name is used not the material name + from os.path import basename + self.parameterWidget.cb_materials.clear() + card_name_list = [] + for a_path in self.materials: + card_name = basename(a_path[:-(len(".FCMat"))]) + card_name_list.append([card_name, a_path, self.icons[a_path]]) + card_name_list.sort() + for mat in card_name_list: + self.parameterWidget.cb_materials.addItem(QtGui.QIcon(mat[2]), mat[0], mat[1]) + + # material card handling ********************************************************************* def print_materialsdict(self): print('\n\n') for mat_card in self.materials: @@ -564,10 +762,9 @@ class _TaskPanelFemMaterial: print('\n\n') def import_materials(self): - self.pathList = [] - self.parameterWidget.cb_materials.clear() - - self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Material/Resources") + self.fem_prefs = FreeCAD.ParamGet( + "User parameter:BaseApp/Preferences/Mod/Material/Resources" + ) if self.obj.Category == 'Fluid': self.import_fluid_materials() else: @@ -607,18 +804,15 @@ class _TaskPanelFemMaterial: self.add_cards_from_a_dir(custom_mat_dir, ":/icons/user.svg") def add_cards_from_a_dir(self, mat_dir, icon): + # fill self.materials and self.icons import glob - import os - import Material - mat_file_extension = ".FCMat" - ext_len = len(mat_file_extension) - dir_path_list = glob.glob(mat_dir + '/*' + mat_file_extension) - self.pathList = self.pathList + dir_path_list - card_name_list = [] + from importFCMat import read + dir_path_list = glob.glob(mat_dir + '/*' + ".FCMat") + for a_path in dir_path_list: - card_name = os.path.basename(a_path[:-ext_len]) - self.materials[a_path] = Material.importFCMat(a_path) - card_name_list.append([card_name, a_path]) - card_name_list.sort() - for mat in card_name_list: - self.parameterWidget.cb_materials.addItem(QtGui.QIcon(icon), mat[0], mat[1]) + mat_dict = read(a_path) + # check if the dict exists in materials + # TODO if the unit is different two cards would be different too + if mat_dict not in self.materials.values(): + self.materials[a_path] = mat_dict + self.icons[a_path] = icon diff --git a/src/Mod/Material/Material.py b/src/Mod/Material/Material.py index 837bb8c718..d62548ca39 100644 --- a/src/Mod/Material/Material.py +++ b/src/Mod/Material/Material.py @@ -20,6 +20,9 @@ # * * # *************************************************************************** +import FreeCAD + + # here the usage description if you use this tool from the command line ("__main__") CommandlineUsage = """Material - Tool to work with FreeCAD Material definition cards @@ -49,8 +52,9 @@ Version: """ -# see comments in module importFCMat, there is an independent parser implementation for reading and writing FCMat files -# inside FreeCAD a mixture of these parsers and the ones in importFCMat.py is used +# see comments in module importFCMat, there is an independent parser implementation +# for reading and writing FCMat files +# inside FreeCAD mostly the one from importFCMat.py is used def importFCMat(fileName): @@ -60,6 +64,10 @@ def importFCMat(fileName): except ImportError: import configparser + FreeCAD.Console.PrintError( + 'This mat card reader is probably depretiated and not widely used in FreeCAD. ' + 'See comment in Material.py module.\n' + ) Config = configparser.RawConfigParser() Config.optionxform = str Config.read(fileName) @@ -81,6 +89,10 @@ def exportFCMat(fileName, matDict): import string Config = configparser.RawConfigParser() + FreeCAD.Console.PrintError( + 'This mat card writer is probably depretiated and not widely used in FreeCAD. ' + 'See comment in Material.py module.\n' + ) # create groups for x in matDict.keys(): grp, key = string.split(x, sep='_') @@ -104,7 +116,8 @@ def getMaterialAttributeStructure(withSpaces=None): '''''' # material properties - # see the following resources in the FreeCAD wiki for more information about the material specific properties: + # see the following resources in the FreeCAD wiki for more information + # about the material specific properties: # https://www.freecadweb.org/wiki/Material_data_model # https://www.freecadweb.org/wiki/Material @@ -115,7 +128,8 @@ def getMaterialAttributeStructure(withSpaces=None): tree = ElementTree.parse(infile) if withSpaces: - # on attributes, add a space before a capital letter, will be used for better display in the ui + # on attributes, add a space before a capital letter + # will be used for better display in the ui import re root = tree.getroot() for group in root.getchildren(): @@ -131,7 +145,7 @@ def read_cards_from_path(cards_path): from os.path import isfile, join, basename, splitext from importFCMat import read only_files = [f for f in listdir(cards_path) if isfile(join(cards_path, f))] - mat_files = [f for f in only_files if basename(splitext(f)[1]) == '.FCMat' or basename(splitext(f)[1]) == '.fcmat'] + mat_files = [f for f in only_files if basename(splitext(f)[1]).upper() == '.FCMAT'] # print(mat_files) mat_cards = [] for f in sorted(mat_files): diff --git a/src/Mod/Material/MaterialEditor.py b/src/Mod/Material/MaterialEditor.py index f10ea58096..b9cde2a69a 100644 --- a/src/Mod/Material/MaterialEditor.py +++ b/src/Mod/Material/MaterialEditor.py @@ -20,23 +20,21 @@ # * * # *************************************************************************** - -from __future__ import print_function -import FreeCAD -import FreeCADGui -from Material import getMaterialAttributeStructure -import os -from PySide import QtCore, QtGui -# from PySide import QtUiTools, QtSvg -import sys -if sys.version_info.major >= 3: - unicode = str - - __title__ = "FreeCAD material editor" __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" +import os +import sys +from PySide import QtCore, QtGui +# from PySide import QtUiTools, QtSvg + +import FreeCAD +import FreeCADGui + +if sys.version_info.major >= 3: + unicode = str + class MaterialEditor: @@ -78,7 +76,8 @@ class MaterialEditor: standardButtons.button(QtGui.QDialogButtonBox.Ok).setAutoDefault(False) standardButtons.button(QtGui.QDialogButtonBox.Cancel).setAutoDefault(False) self.updateCards() - # TODO allow to enter a custom property by pressing Enter in the lineedit (currently closes the dialog) + # TODO allow to enter a custom property by pressing Enter in the lineedit + # currently closes the dialog standardButtons.rejected.connect(self.reject) standardButtons.accepted.connect(self.accept) @@ -119,6 +118,7 @@ class MaterialEditor: treeView.setColumnWidth(1, 250) treeView.setColumnHidden(2, True) + from Material import getMaterialAttributeStructure tree = getMaterialAttributeStructure(True) MatPropDict = tree.getroot() @@ -149,9 +149,12 @@ class MaterialEditor: def updateContents(self, data): '''updates the contents of the editor with the given data, can be: - - the name of a card, if material is changed in editors combo box - - a dictionary, if the editor was called with data.''' + - a dictionary, if the editor was called with data + - a string, the name of a card, if material is changed in editors combo box + the material property keys where added to the editor already + not known material property keys will be added to the user defined group''' + # print type(data) if isinstance(data, dict): # a standard material property dict is provided model = self.widget.treeView.model() @@ -182,18 +185,21 @@ class MaterialEditor: self.customprops.append(k) elif isinstance(data, unicode): - # a card name is provided, search card, read material data and call this def once more with std material property dict + # a card name is provided, search card, read material data and call + # this def once more with std material property dict k = str(data) if k: if k in self.cards: - import importFCMat - d = importFCMat.read(self.cards[k]) + from importFCMat import read + d = read(self.cards[k]) if d: self.updateContents(d) def getMaterialResources(self): - self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Material/Resources") + self.fem_prefs = FreeCAD.ParamGet( + "User parameter:BaseApp/Preferences/Mod/Material/Resources" + ) use_built_in_materials = self.fem_prefs.GetBool("UseBuiltInMaterials", True) use_mat_from_config_dir = self.fem_prefs.GetBool("UseMaterialsFromConfigDir", True) use_mat_from_custom_dir = self.fem_prefs.GetBool("UseMaterialsFromCustomDir", True) @@ -203,7 +209,10 @@ class MaterialEditor: # FreeCAD returns paths with / at the end, thus not os.sep is needed on first + self.resources = [] if use_built_in_materials: - self.resources.append(FreeCAD.getResourceDir() + "Mod" + os.sep + "Material" + os.sep + "StandardMaterial") + res_dir = FreeCAD.getResourceDir() + self.resources.append( + res_dir + "Mod" + os.sep + "Material" + os.sep + "StandardMaterial" + ) if use_mat_from_config_dir: self.resources.append(FreeCAD.ConfigGet("UserAppData") + "Material") if use_mat_from_custom_dir: @@ -220,12 +229,14 @@ class MaterialEditor: def outputCards(self): print('material cards:') - for card in self.cards: + for card in sorted(self.cards.keys()): print(' ' + card + ': ' + self.cards[card]) print('\n') def updateCards(self): - "updates the contents of the materials combo with existing material cards" + + '''updates the contents of the materials combo with existing material cards''' + self.getMaterialResources() self.cards = {} for p in self.resources: @@ -238,11 +249,12 @@ class MaterialEditor: if self.cards: self.widget.ComboMaterial.clear() self.widget.ComboMaterial.addItem("") # add a blank item first - for k, i in self.cards.items(): - self.widget.ComboMaterial.addItem(k) + for card in sorted(self.cards.keys()): + self.widget.ComboMaterial.addItem(card) # all keys in self.cards are unicode def openProductURL(self): - "opens the contents of the ProductURL field in an external browser." + + '''opens the contents of the ProductURL field in an external browser.''' model = self.widget.treeView.model() item = model.findItems(translate("Material", "Product URL"), @@ -383,7 +395,8 @@ class MaterialEditor: kk = group.child(row, 0).text() ii = group.child(row, 1).text() - # TODO the following should be translated back to english,since text(0) could be translated + # TODO the following should be translated back to english + # since text(0) could be translated matkey = self.collapseKey(str(kk)) matvalue = unicode(ii) if matvalue or (matkey == 'Name'): @@ -395,9 +408,10 @@ class MaterialEditor: def outputDict(self, d): print('MaterialEditor dictionary') for param in d: - print(' ' + param + ' : ' + d[param]) + print(' {} : {}'.format(param, d[param])) - '''def setTexture(self, pattern): + ''' + def setTexture(self, pattern): "displays a texture preview if needed" self.widget.PreviewVector.hide() if pattern: @@ -409,16 +423,23 @@ class MaterialEditor: pattern = DrawingPatterns.buildFileSwatch(pattern, size=96, png=True) if pattern: self.widget.PreviewVector.setPixmap(QtGui.QPixmap(pattern)) - self.widget.PreviewVector.show()''' + self.widget.PreviewVector.show() + ''' def openfile(self): "Opens a FCMat file" - filetuple = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(), 'Open FreeCAD Material file', self.directory, '*.FCMat') - filename = filetuple[0] # a tuple of two empty strings returns True, so use the filename directly + filetuple = QtGui.QFileDialog.getOpenFileName( + QtGui.QApplication.activeWindow(), + 'Open FreeCAD Material file', + self.directory, + '*.FCMat' + ) + # a tuple of two empty strings returns True, so use the filename directly + filename = filetuple[0] if filename: - import importFCMat + from importFCMat import read self.directory = os.path.dirname(filename) - d = importFCMat.read(filename) + d = read(filename) if d: self.updateContents(d) @@ -436,18 +457,21 @@ class MaterialEditor: name = name.encode("utf8") if not name: name = "Material" - filetuple =\ - QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), - 'Save FreeCAD Material file', - self.directory + '/' + name + '.FCMat', '*.FCMat') - filename = filetuple[0] # a tuple of two empty strings returns True, so use the filename directly + filetuple = QtGui.QFileDialog.getSaveFileName( + QtGui.QApplication.activeWindow(), + 'Save FreeCAD Material file', + self.directory + '/' + name + '.FCMat', + '*.FCMat' + ) + # a tuple of two empty strings returns True, so use the filename directly + filename = filetuple[0] if filename: self.directory = os.path.dirname(filename) d = self.getDict() # self.outputDict(d) if d: - import importFCMat - importFCMat.write(filename, d) + from importFCMat import write + write(filename, d) self.updateCards() def show(self): @@ -567,7 +591,7 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, quantity = FreeCAD.Units.Quantity(1, unit) widget.setProperty('unit', quantity.getUserPreferred()[2]) else: - FreeCAD.Console.PrintError('Not known unit for property: ' + matproperty + '\n') + FreeCAD.Console.PrintError('Not known unit for property: {}\n'.format(matproperty)) elif Type == "Integer": @@ -644,9 +668,11 @@ def editMaterial(material): """editMaterial(material): opens the editor to edit the contents of the given material dictionary. Returns the modified material dictionary.""" # if the material editor is opened with this def the combo box with the card name is empty - # this makes sense, because the editor was not opened with a card but with material dictionary instead + # this makes sense ... + # because the editor was not opened with a card but with material dictionary instead # TODO: add some text in combo box, may be "custom material data" or "user material data" - # TODO: all card could be checked if one fits exact ALL provided data and than this card name could be displayed + # TODO: all card could be checked if one fits exact ALL provided data + # than this card name could be displayed editor = MaterialEditor(material=material) result = editor.exec_() if result: @@ -660,12 +686,19 @@ def editMaterial(material): import MaterialEditor MaterialEditor.openEditor() -doc = FreeCAD.open(FreeCAD.ConfigGet("AppHomePath") + 'data/examples/FemCalculixCantilever3D.FCStd') +doc = FreeCAD.open( + FreeCAD.ConfigGet("AppHomePath") + 'data/examples/FemCalculixCantilever3D.FCStd' +) import MaterialEditor MaterialEditor.openEditor('SolidMaterial', 'Material') import MaterialEditor -MaterialEditor.editMaterial({'Density': '1234.0 kg/m^3', 'Name': 'My-Material-Data', 'PoissonRatio': '0.66', 'YoungsModulus': '123456 MPa'}) +MaterialEditor.editMaterial({ + 'Density': '1234.0 kg/m^3', + 'Name': 'My-Material-Data', + 'PoissonRatio': '0.66', + 'YoungsModulus': '123456 MPa' +}) import MaterialEditor MaterialEditor.editMaterial('ABS') diff --git a/src/Mod/Material/importFCMat.py b/src/Mod/Material/importFCMat.py index 4e14ca5e81..f6e6ddfe52 100644 --- a/src/Mod/Material/importFCMat.py +++ b/src/Mod/Material/importFCMat.py @@ -78,13 +78,16 @@ def decode(name): return decodedName -# the reader and writer do not use some Library to read and write the ini file format, they are implemented here +# the reader and writer do not use some Library to read and write the ini file format +# they are implemented here # thus non standard ini files will be read and written too -# in standard ini file format a = in the value without any encapsulation or string quotes is not allowed (AFAIK) +# in standard ini file format: +# a = in the value without any encapsulation or string quotes is not allowed (AFAIK) # https://en.wikipedia.org/wiki/INI_file # http://www.docuxplorer.com/WebHelp/INI_File_Format.htm # mainly this parser here is used in FreeCAD -# in the module Material.py is another implementation of reading and writing FCMat files which uses the module ConfigParser +# in the module Material.py is another implementation of reading and writing FCMat files +# this implementation uses the ConfigParser module # in ViewProviderFemMaterial in add_cards_from_a_dir() the parser from Material.py is used # since this mixture seams to be there for ages it should not be changed for 0.18 # TODO: get rid of this mixture in FreeCAD 0.19 @@ -117,7 +120,10 @@ def read(filename): v = v.decode('utf-8') card_name_content = v if card_name_content != d["CardName"]: - FreeCAD.Console.PrintError("File CardName (" + card_name_file + ") is not content CardName (" + card_name_content + ")\n") + FreeCAD.Console.PrintLog( + "File CardName ( {} ) is not content CardName ( {} )\n" + .format(card_name_file, card_name_content) + ) elif ln == 1: v = line.split(";")[1].strip() # Line 2 if hasattr(v, "decode"): @@ -128,7 +134,9 @@ def read(filename): # # might be a comment too ? # [ is a Section if line[0] not in ";#[": - k = line.split("=", 1) # only split once on first occurrence, a link could contain a = and thus would be splitted + # split once on first occurrence + # a link could contain a = and thus would be split + k = line.split("=", 1) if len(k) == 2: v = k[1].strip() if hasattr(v, "decode"): @@ -166,12 +174,18 @@ def write(filename, dictionary, write_group_section=True): user[k] = i # delete empty properties for group in contents: - for k in list(group.keys()): # iterating over a dict and changing it is not allowed, thus we iterate over a list of the keys + # iterating over a dict and changing it is not allowed + # thus it is iterated over a list of the keys + for k in list(group.keys()): if group[k] == '': del group[k] # card writer - rev = FreeCAD.ConfigGet("BuildVersionMajor") + "." + FreeCAD.ConfigGet("BuildVersionMinor") + "." + FreeCAD.ConfigGet("BuildRevision") + rev = "{}.{}.{}".format( + FreeCAD.ConfigGet("BuildVersionMajor"), + FreeCAD.ConfigGet("BuildVersionMinor"), + FreeCAD.ConfigGet("BuildRevision") + ) if isinstance(filename, unicode): if sys.version_info.major < 3: filename = filename.encode(sys.getfilesystemencoding()) @@ -182,7 +196,8 @@ def write(filename, dictionary, write_group_section=True): # write header # first five lines are the same in any card file, see comment above read def if header["CardName"] != card_name_file: - FreeCAD.Console.PrintMessage("File CardName is used: " + card_name_file + " \n") # CardName is the MatCard file name + # CardName is the MatCard file name + FreeCAD.Console.PrintMessage("File CardName is used: {}\n".format(card_name_file)) if sys.version_info.major >= 3: f.write("; " + card_name_file + "\n") f.write("; " + header["AuthorAndLicense"] + "\n") diff --git a/src/Mod/Material/materials-editor.ui b/src/Mod/Material/materials-editor.ui index 7b29f1e6cf..ef1990540c 100644 --- a/src/Mod/Material/materials-editor.ui +++ b/src/Mod/Material/materials-editor.ui @@ -17,77 +17,70 @@ - - - - - - 22 - 22 - - - - Opens the Product URL of this material in an external browser - - - - - - - - - - Material card: - - - - - - - - 120 - 0 - - - - Existing material cards - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Opens an existing material card - - - Open... - - - - - - - Saves this material as a card - - - Save as... - - - - + + + Material card + + + + + + + + + 22 + 22 + + + + Opens the Product URL of this material in an external browser + + + + + + + + + + + 120 + 0 + + + + Existing material cards + + + + + + + + + + + Opens an existing material card + + + Open... + + + + + + + Saves this material as a card + + + Save as... + + + + + + + @@ -138,19 +131,28 @@ - - - QLayout::SetMaximumSize + + + Material parameter - - - - + + + + + QLayout::SetMaximumSize + + + + + + + + - Add / Remove + Add / remove parameter diff --git a/src/Mod/Mesh/App/Core/Algorithm.cpp b/src/Mod/Mesh/App/Core/Algorithm.cpp index 1eb86d511e..6b55b84da4 100644 --- a/src/Mod/Mesh/App/Core/Algorithm.cpp +++ b/src/Mod/Mesh/App/Core/Algorithm.cpp @@ -1746,6 +1746,24 @@ std::set MeshRefPointToFacets::NeighbourPoints(const std::vector< return nb; } +std::set MeshRefPointToFacets::NeighbourPoints(unsigned long pos) const +{ + std::set p; + const std::set& vf = _map[pos]; + for (std::set::const_iterator it = vf.begin(); it != vf.end(); ++it) { + unsigned long p1, p2, p3; + _rclMesh.GetFacetPoints(*it, p1, p2, p3); + if (p1 != pos) + p.insert(p1); + if (p2 != pos) + p.insert(p2); + if (p3 != pos) + p.insert(p3); + } + + return p; +} + void MeshRefPointToFacets::Neighbours (unsigned long ulFacetInd, float fMaxDist, MeshCollector& collect) const { std::set visited; diff --git a/src/Mod/Mesh/App/Core/Algorithm.h b/src/Mod/Mesh/App/Core/Algorithm.h index 8ea4861a5a..5ac8bb2d92 100644 --- a/src/Mod/Mesh/App/Core/Algorithm.h +++ b/src/Mod/Mesh/App/Core/Algorithm.h @@ -375,6 +375,7 @@ public: const std::set& operator[] (unsigned long) const; MeshFacetArray::_TConstIterator GetFacet (unsigned long) const; std::set NeighbourPoints(const std::vector& , int level) const; + std::set NeighbourPoints(unsigned long) const; void Neighbours (unsigned long ulFacetInd, float fMaxDist, MeshCollector& collect) const; Base::Vector3f GetNormal(unsigned long) const; void AddNeighbour(unsigned long, unsigned long); diff --git a/src/Mod/Mesh/App/Core/Degeneration.cpp b/src/Mod/Mesh/App/Core/Degeneration.cpp index 297b5f41b5..91ec5189ac 100644 --- a/src/Mod/Mesh/App/Core/Degeneration.cpp +++ b/src/Mod/Mesh/App/Core/Degeneration.cpp @@ -576,6 +576,13 @@ bool MeshRemoveNeedles::Fixup() vf.erase(neighbour); ce._changeFacets.insert(ce._changeFacets.begin(), vf.begin(), vf.end()); + // get adjacent points + std::set vv; + vv = vf_it.NeighbourPoints(ce._fromPoint); + ce._adjacentFrom.insert(ce._adjacentFrom.begin(), vv.begin(),vv.end()); + vv = vf_it.NeighbourPoints(ce._toPoint); + ce._adjacentTo.insert(ce._adjacentTo.begin(), vv.begin(),vv.end()); + if (topAlg.IsCollapseEdgeLegal(ce)) { topAlg.CollapseEdge(ce); for (auto it : ce._removeFacets) { diff --git a/src/Mod/Mesh/App/Core/Elements.h b/src/Mod/Mesh/App/Core/Elements.h index ead0169d97..951d63ce30 100644 --- a/src/Mod/Mesh/App/Core/Elements.h +++ b/src/Mod/Mesh/App/Core/Elements.h @@ -75,6 +75,8 @@ struct MeshExport EdgeCollapse { unsigned long _fromPoint; unsigned long _toPoint; + std::vector _adjacentFrom; // adjacent points to _fromPoint + std::vector _adjacentTo; // adjacent points to _toPoint std::vector _removeFacets; std::vector _changeFacets; }; diff --git a/src/Mod/Mesh/App/Core/TopoAlgorithm.cpp b/src/Mod/Mesh/App/Core/TopoAlgorithm.cpp index 6ad518dd84..491f3d0a94 100644 --- a/src/Mod/Mesh/App/Core/TopoAlgorithm.cpp +++ b/src/Mod/Mesh/App/Core/TopoAlgorithm.cpp @@ -955,6 +955,18 @@ bool MeshTopoAlgorithm::CollapseEdge(unsigned long ulFacetPos, unsigned long ulN bool MeshTopoAlgorithm::IsCollapseEdgeLegal(const EdgeCollapse& ec) const { + // http://stackoverflow.com/a/27049418/148668 + // Check connectivity + // + std::vector commonPoints; + std::set_intersection(ec._adjacentFrom.begin(), ec._adjacentFrom.end(), + ec._adjacentTo.begin(), ec._adjacentTo.end(), + std::back_insert_iterator >(commonPoints)); + if (commonPoints.size() > 2) { + return false; + } + + // Check geometry std::vector::const_iterator it; for (it = ec._changeFacets.begin(); it != ec._changeFacets.end(); ++it) { MeshFacet f = _rclMesh._aclFacetArray[*it]; diff --git a/src/Mod/Sketcher/App/ConstraintPy.xml b/src/Mod/Sketcher/App/ConstraintPy.xml index 4c264a2635..5ef77d5aaa 100644 --- a/src/Mod/Sketcher/App/ConstraintPy.xml +++ b/src/Mod/Sketcher/App/ConstraintPy.xml @@ -1,13 +1,13 @@  - @@ -28,7 +28,7 @@ - + Position of first geometry index the Constraint refers to @@ -40,7 +40,7 @@ - + Position of second geometry index the Constraint refers to @@ -52,7 +52,7 @@ - + Position of third geometry index the Constraint refers to diff --git a/src/Mod/Sketcher/App/ConstraintPyImp.cpp b/src/Mod/Sketcher/App/ConstraintPyImp.cpp index 99c85f96d8..a309e9c66e 100644 --- a/src/Mod/Sketcher/App/ConstraintPyImp.cpp +++ b/src/Mod/Sketcher/App/ConstraintPyImp.cpp @@ -31,7 +31,7 @@ using namespace Sketcher; PyObject *ConstraintPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper { - // create a new instance of ConstraintPy and the Twin object + // create a new instance of ConstraintPy and the Twin object return new ConstraintPy(new Constraint); } @@ -108,18 +108,18 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) } else if (strstr(ConstraintType,"InternalAlignment") != NULL) { this->getConstraintPtr()->Type = InternalAlignment; - + valid = true; if(strstr(ConstraintType,"EllipseMajorDiameter") != NULL) this->getConstraintPtr()->AlignmentType=EllipseMajorDiameter; else if(strstr(ConstraintType,"EllipseMinorDiameter") != NULL) - this->getConstraintPtr()->AlignmentType=EllipseMinorDiameter; + this->getConstraintPtr()->AlignmentType=EllipseMinorDiameter; else { this->getConstraintPtr()->AlignmentType=Undef; valid = false; } } - + if (valid) { this->getConstraintPtr()->First = FirstIndex; this->getConstraintPtr()->Second = SecondIndex; @@ -200,11 +200,11 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) } else if (strstr(ConstraintType,"InternalAlignment") != NULL) { this->getConstraintPtr()->Type = InternalAlignment; - + valid = true; - + if(strstr(ConstraintType,"EllipseFocus1") != NULL) - this->getConstraintPtr()->AlignmentType=EllipseFocus1; + this->getConstraintPtr()->AlignmentType=EllipseFocus1; else if(strstr(ConstraintType,"EllipseFocus2") != NULL) this->getConstraintPtr()->AlignmentType=EllipseFocus2; else { @@ -324,13 +324,13 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) valid = true; if(strstr(ConstraintType,"BSplineControlPoint") != NULL) { - this->getConstraintPtr()->AlignmentType=BSplineControlPoint; + this->getConstraintPtr()->AlignmentType=BSplineControlPoint; } else { this->getConstraintPtr()->AlignmentType=Undef; valid = false; } - + if (valid) { this->getConstraintPtr()->First = intArg1; this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; @@ -338,7 +338,7 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) this->getConstraintPtr()->InternalAlignmentIndex = intArg4; return 0; } - + } if (valid) { this->getConstraintPtr()->First = intArg1; @@ -521,7 +521,7 @@ std::string ConstraintPy::representation(void) const break; case Symmetric : result << "'Symmetric'>"; break; case SnellsLaw : result << "'SnellsLaw'>"; break; - case InternalAlignment : + case InternalAlignment : switch(this->getConstraintPtr()->AlignmentType) { case Undef : result << "'InternalAlignment:Undef'>";break; case EllipseMajorDiameter : result << "'InternalAlignment:EllipseMajorDiameter'>";break; @@ -583,6 +583,25 @@ Py::Long ConstraintPy::getFirstPos(void) const return Py::Long(static_cast(this->getConstraintPtr()->FirstPos)); } +void ConstraintPy::setFirstPos(Py::Long arg) +{ + #if PY_MAJOR_VERSION < 3 + int pos = Py::Int(arg); + #else + int pos = arg; + #endif + + if(pos>=Sketcher::none && pos<=Sketcher::mid) { + this->getConstraintPtr()->FirstPos = (Sketcher::PointPos)pos; + } + else { + std::stringstream str; + str << "Invalid PointPos parameter: " << arg << std::endl; + + PyErr_SetString(PyExc_TypeError, str.str().c_str()); + } +} + Py::Long ConstraintPy::getSecond(void) const { return Py::Long(this->getConstraintPtr()->Second); @@ -602,6 +621,25 @@ Py::Long ConstraintPy::getSecondPos(void) const return Py::Long(static_cast(this->getConstraintPtr()->SecondPos)); } +void ConstraintPy::setSecondPos(Py::Long arg) +{ + #if PY_MAJOR_VERSION < 3 + int pos = Py::Int(arg); + #else + int pos = arg; + #endif + + if(pos>=Sketcher::none && pos<=Sketcher::mid) { + this->getConstraintPtr()->SecondPos = (Sketcher::PointPos)pos; + } + else { + std::stringstream str; + str << "Invalid PointPos parameter: " << arg << std::endl; + + PyErr_SetString(PyExc_TypeError, str.str().c_str()); + } +} + Py::Long ConstraintPy::getThird(void) const { return Py::Long(this->getConstraintPtr()->Third); @@ -621,6 +659,25 @@ Py::Long ConstraintPy::getThirdPos(void) const return Py::Long(static_cast(this->getConstraintPtr()->ThirdPos)); } +void ConstraintPy::setThirdPos(Py::Long arg) +{ + #if PY_MAJOR_VERSION < 3 + int pos = Py::Int(arg); + #else + int pos = arg; + #endif + + if(pos>=Sketcher::none && pos<=Sketcher::mid) { + this->getConstraintPtr()->ThirdPos = (Sketcher::PointPos)pos; + } + else { + std::stringstream str; + str << "Invalid PointPos parameter: " << arg << std::endl; + + PyErr_SetString(PyExc_TypeError, str.str().c_str()); + } +} + Py::String ConstraintPy::getName(void) const { return Py::String(this->getConstraintPtr()->Name); @@ -653,5 +710,5 @@ PyObject *ConstraintPy::getCustomAttributes(const char* /*attr*/) const int ConstraintPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) { - return 0; + return 0; } diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 6c34c07e2f..aaff3c7087 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -1488,10 +1488,16 @@ int Sketch::addConstraint(const Constraint *constraint) int Sketch::addConstraints(const std::vector &ConstraintList) { int rtn = -1; + int cid = 0; - for (std::vector::const_iterator it = ConstraintList.begin();it!=ConstraintList.end();++it) + for (std::vector::const_iterator it = ConstraintList.begin();it!=ConstraintList.end();++it,++cid) { rtn = addConstraint (*it); + if(rtn == -1) { + Base::Console().Error("Sketcher constraint number %d is malformed!\n",cid); + } + } + return rtn; } @@ -1504,6 +1510,10 @@ int Sketch::addConstraints(const std::vector &ConstraintList, for (std::vector::const_iterator it = ConstraintList.begin();it!=ConstraintList.end();++it,++cid) { if (!unenforceableConstraints[cid] && (*it)->Type != Block) { rtn = addConstraint (*it); + + if(rtn == -1) { + Base::Console().Error("Sketcher constraint number %d is malformed!\n",cid); + } } else { ++ConstraintsCounter; // For correct solver redundant reporting diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index f6ad9f0c86..4836d0fb7e 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -37,7 +37,7 @@ #ifdef _GCS_DEBUG #define EIGEN_DEFAULT_IO_FORMAT Eigen::IOFormat(3,0,",",",\n","[","]","[","]") /* Parameters: - * + * * StreamPrecision, * int _flags = 0, * const std::string & _coeffSeparator = " ", @@ -108,184 +108,279 @@ typedef Eigen::FullPivHouseholderQR::IntDiagSizeVectorType MatrixIndexType; -#ifdef _GCS_DEBUG -void LogMatrix(std::string str, Eigen::MatrixXd matrix ) -{ -#ifdef _DEBUG_TO_FILE - std::ofstream stream; - stream.open("GCS_debug.txt", std::ofstream::out | std::ofstream::app); -#else - // Debug code starts - std::stringstream stream; -#endif - - stream << '\n' << " " << str << " =" << '\n'; - stream << "[" << '\n'; - stream << matrix << '\n' ; - stream << "]" << '\n'; - -#ifdef _DEBUG_TO_FILE - stream.flush(); - stream.close(); -#else - const std::string tmp = stream.str(); - - Base::Console().Log(tmp.c_str()); -#endif -} - -void LogMatrix(std::string str, MatrixIndexType matrix ) -{ - #ifdef _DEBUG_TO_FILE - std::ofstream stream; - stream.open("GCS_debug.txt", std::ofstream::out | std::ofstream::app); - #else - // Debug code starts - std::stringstream stream; - #endif - - stream << '\n' << " " << str << " =" << '\n'; - stream << "[" << '\n'; - stream << matrix << '\n' ; - stream << "]" << '\n'; - - #ifdef _DEBUG_TO_FILE - stream.flush(); - stream.close(); - #else - const std::string tmp = stream.str(); - - Base::Console().Log(tmp.c_str()); - #endif -} -#endif - -void LogString(std::string str) -{ - #ifdef _DEBUG_TO_FILE - std::ofstream stream; - stream.open("GCS_debug.txt", std::ofstream::out | std::ofstream::app); - #else - // Debug code starts - std::stringstream stream; - #endif - - stream << str << std::endl; - - #ifdef _DEBUG_TO_FILE - stream.flush(); - stream.close(); - #else - const std::string tmp = stream.str(); - - Base::Console().Log(tmp.c_str()); - #endif -} - #ifndef EIGEN_STOCK_FULLPIVLU_COMPUTE namespace Eigen { -typedef Matrix MatrixdType; + typedef Matrix MatrixdType; -template<> -FullPivLU& FullPivLU::compute(const MatrixdType& matrix) -{ - m_isInitialized = true; - m_lu = matrix; - - const Index size = matrix.diagonalSize(); - const Index rows = matrix.rows(); - const Index cols = matrix.cols(); - - // will store the transpositions, before we accumulate them at the end. - // can't accumulate on-the-fly because that will be done in reverse order for the rows. - m_rowsTranspositions.resize(matrix.rows()); - m_colsTranspositions.resize(matrix.cols()); - Index number_of_transpositions = 0; // number of NONTRIVIAL transpositions, i.e. m_rowsTranspositions[i]!=i - - m_nonzero_pivots = size; // the generic case is that in which all pivots are nonzero (invertible case) - m_maxpivot = RealScalar(0); - RealScalar cutoff(0); - - for(Index k = 0; k < size; ++k) - { - // First, we need to find the pivot. - - // biggest coefficient in the remaining bottom-right corner (starting at row k, col k) - Index row_of_biggest_in_corner, col_of_biggest_in_corner; - RealScalar biggest_in_corner; - biggest_in_corner = m_lu.bottomRightCorner(rows-k, cols-k) - .cwiseAbs() - .maxCoeff(&row_of_biggest_in_corner, &col_of_biggest_in_corner); - row_of_biggest_in_corner += k; // correct the values! since they were computed in the corner, - col_of_biggest_in_corner += k; // need to add k to them. - - // when k==0, biggest_in_corner is the biggest coeff absolute value in the original matrix - if(k == 0) cutoff = biggest_in_corner * NumTraits::epsilon(); - - // if the pivot (hence the corner) is "zero", terminate to avoid generating nan/inf values. - // Notice that using an exact comparison (biggest_in_corner==0) here, as Golub-van Loan do in - // their pseudo-code, results in numerical instability! The cutoff here has been validated - // by running the unit test 'lu' with many repetitions. - if(biggest_in_corner < cutoff) + template<> + FullPivLU& FullPivLU::compute(const MatrixdType& matrix) { - // before exiting, make sure to initialize the still uninitialized transpositions - // in a sane state without destroying what we already have. - m_nonzero_pivots = k; - for(Index i = k; i < size; ++i) - { - m_rowsTranspositions.coeffRef(i) = i; - m_colsTranspositions.coeffRef(i) = i; - } - break; + m_isInitialized = true; + m_lu = matrix; + + const Index size = matrix.diagonalSize(); + const Index rows = matrix.rows(); + const Index cols = matrix.cols(); + + // will store the transpositions, before we accumulate them at the end. + // can't accumulate on-the-fly because that will be done in reverse order for the rows. + m_rowsTranspositions.resize(matrix.rows()); + m_colsTranspositions.resize(matrix.cols()); + Index number_of_transpositions = 0; // number of NONTRIVIAL transpositions, i.e. m_rowsTranspositions[i]!=i + + m_nonzero_pivots = size; // the generic case is that in which all pivots are nonzero (invertible case) + m_maxpivot = RealScalar(0); + RealScalar cutoff(0); + + for(Index k = 0; k < size; ++k) + { + // First, we need to find the pivot. + + // biggest coefficient in the remaining bottom-right corner (starting at row k, col k) + Index row_of_biggest_in_corner, col_of_biggest_in_corner; + RealScalar biggest_in_corner; + biggest_in_corner = m_lu.bottomRightCorner(rows-k, cols-k) + .cwiseAbs() + .maxCoeff(&row_of_biggest_in_corner, &col_of_biggest_in_corner); + row_of_biggest_in_corner += k; // correct the values! since they were computed in the corner, + col_of_biggest_in_corner += k; // need to add k to them. + + // when k==0, biggest_in_corner is the biggest coeff absolute value in the original matrix + if(k == 0) cutoff = biggest_in_corner * NumTraits::epsilon(); + + // if the pivot (hence the corner) is "zero", terminate to avoid generating nan/inf values. + // Notice that using an exact comparison (biggest_in_corner==0) here, as Golub-van Loan do in + // their pseudo-code, results in numerical instability! The cutoff here has been validated + // by running the unit test 'lu' with many repetitions. + if(biggest_in_corner < cutoff) + { + // before exiting, make sure to initialize the still uninitialized transpositions + // in a sane state without destroying what we already have. + m_nonzero_pivots = k; + for(Index i = k; i < size; ++i) + { + m_rowsTranspositions.coeffRef(i) = i; + m_colsTranspositions.coeffRef(i) = i; + } + break; + } + + if(biggest_in_corner > m_maxpivot) m_maxpivot = biggest_in_corner; + + // Now that we've found the pivot, we need to apply the row/col swaps to + // bring it to the location (k,k). + + m_rowsTranspositions.coeffRef(k) = row_of_biggest_in_corner; + m_colsTranspositions.coeffRef(k) = col_of_biggest_in_corner; + if(k != row_of_biggest_in_corner) { + m_lu.row(k).swap(m_lu.row(row_of_biggest_in_corner)); + ++number_of_transpositions; + } + if(k != col_of_biggest_in_corner) { + m_lu.col(k).swap(m_lu.col(col_of_biggest_in_corner)); + ++number_of_transpositions; + } + + // Now that the pivot is at the right location, we update the remaining + // bottom-right corner by Gaussian elimination. + + if(k= 0; --k) + m_p.applyTranspositionOnTheRight(k, m_rowsTranspositions.coeff(k)); + + m_q.setIdentity(cols); + for(Index k = 0; k < size; ++k) + m_q.applyTranspositionOnTheRight(k, m_colsTranspositions.coeff(k)); + + m_det_pq = (number_of_transpositions%2) ? -1 : 1; + return *this; } - if(biggest_in_corner > m_maxpivot) m_maxpivot = biggest_in_corner; - - // Now that we've found the pivot, we need to apply the row/col swaps to - // bring it to the location (k,k). - - m_rowsTranspositions.coeffRef(k) = row_of_biggest_in_corner; - m_colsTranspositions.coeffRef(k) = col_of_biggest_in_corner; - if(k != row_of_biggest_in_corner) { - m_lu.row(k).swap(m_lu.row(row_of_biggest_in_corner)); - ++number_of_transpositions; - } - if(k != col_of_biggest_in_corner) { - m_lu.col(k).swap(m_lu.col(col_of_biggest_in_corner)); - ++number_of_transpositions; - } - - // Now that the pivot is at the right location, we update the remaining - // bottom-right corner by Gaussian elimination. - - if(k= 0; --k) - m_p.applyTranspositionOnTheRight(k, m_rowsTranspositions.coeff(k)); - - m_q.setIdentity(cols); - for(Index k = 0; k < size; ++k) - m_q.applyTranspositionOnTheRight(k, m_colsTranspositions.coeff(k)); - - m_det_pq = (number_of_transpositions%2) ? -1 : 1; - return *this; -} - } // Eigen #endif namespace GCS { +class SolverReportingManager +{ +public: + SolverReportingManager(SolverReportingManager const&) = delete; + SolverReportingManager(SolverReportingManager&&) = delete; + SolverReportingManager& operator=(SolverReportingManager const&) = delete; + SolverReportingManager& operator=(SolverReportingManager &&) = delete; + + static SolverReportingManager& Manager(); + + inline void LogString(const std::string& str); + + inline void LogToConsole(const std::string& str); + + inline void LogToFile(const std::string& str); + + void LogQRSystemInformation(const System &system, int paramsNum = 0, int constrNum = 0, int rank = 0); + + void LogMatrix(std::string str, Eigen::MatrixXd matrix); + void LogMatrix(std::string str, MatrixIndexType matrix ); + +private: + SolverReportingManager(); + ~SolverReportingManager(); + + inline void initStream(); + inline void flushStream(); + +private: + #ifdef _DEBUG_TO_FILE + std::ofstream stream; + #endif +}; + +SolverReportingManager::SolverReportingManager() +{ + initStream(); +} + +SolverReportingManager::~SolverReportingManager() +{ + #ifdef _DEBUG_TO_FILE + stream.flush(); + stream.close(); + #endif +} + +void SolverReportingManager::initStream() +{ + #ifdef _DEBUG_TO_FILE + if(!stream.is_open()) { + stream.open("GCS_debug.txt", std::ofstream::out | std::ofstream::app); + } + #endif +} + +void SolverReportingManager::flushStream() +{ + // Akwardly in some systems flushing does not force the write to the file, requiring a close + #ifdef _DEBUG_TO_FILE + stream.flush(); + stream.close(); + #endif +} + +SolverReportingManager& SolverReportingManager::Manager() +{ + static SolverReportingManager theInstance; + + return theInstance; +} + +void SolverReportingManager::LogToConsole(const std::string& str) +{ + if(str.size() < Base::Console().BufferSize) + Base::Console().Log(str.c_str()); + else + Base::Console().Log("SolverReportingManager - Too long string suppressed"); +} + +void SolverReportingManager::LogToFile(const std::string& str) +{ + #ifdef _DEBUG_TO_FILE + initStream(); + + stream << str << std::endl; + + flushStream(); + #else + (void)(str); // silence unused parameter + LogToConsole("Debugging to file not enabled!"); + #endif +} + +void SolverReportingManager::LogString(const std::string& str) +{ + LogToConsole(str); + + #ifdef _DEBUG_TO_FILE + LogToFile(str); + #endif +} + +void SolverReportingManager::LogQRSystemInformation(const System &system, int paramsNum, int constrNum, int rank) +{ + + std::stringstream tempstream; + + tempstream << (system.qrAlgorithm==EigenSparseQR?"EigenSparseQR":(system.qrAlgorithm==EigenDenseQR?"DenseQR":"")); + + if (paramsNum > 0) { + tempstream + #ifdef EIGEN_SPARSEQR_COMPATIBLE + << ", Threads: " << Eigen::nbThreads() + #endif + #ifdef EIGEN_VECTORIZE + << ", Vectorization: On" + #endif + << ", Pivot Threshold: " << system.qrpivotThreshold + << ", Params: " << paramsNum + << ", Constr: " << constrNum + << ", Rank: " << rank + << std::endl; + } + else { + tempstream + #ifdef EIGEN_SPARSEQR_COMPATIBLE + << ", Threads: " << Eigen::nbThreads() + #endif + #ifdef EIGEN_VECTORIZE + << ", Vectorization: On" + #endif + << ", Empty Sketch, nothing to solve" + << std::endl; + } + + LogString(tempstream.str()); + +} + + +#ifdef _GCS_DEBUG +void SolverReportingManager::LogMatrix(std::string str, Eigen::MatrixXd matrix ) +{ + std::stringstream tempstream; + + tempstream << '\n' << " " << str << " =" << '\n'; + tempstream << "[" << '\n'; + tempstream << matrix << '\n' ; + tempstream << "]" << '\n'; + + LogString(tempstream.str()); + +} + +void SolverReportingManager::LogMatrix(std::string str, MatrixIndexType matrix ) +{ + std::stringstream tempstream; + + stream << '\n' << " " << str << " =" << '\n'; + stream << "[" << '\n'; + stream << matrix << '\n' ; + stream << "]" << '\n'; + + LogString(tempstream.str()); +} +#endif + + typedef boost::adjacency_list Graph; /////////////////////////////////////// @@ -914,7 +1009,7 @@ int System::addConstraintEqualRadii(Ellipse &e1, Ellipse &e2, int tagId, bool dr int System::addConstraintEqualRadii(ArcOfHyperbola &a1, ArcOfHyperbola &a2, int tagId, bool driving) { addConstraintEqual(a1.radmin, a2.radmin, tagId, driving); - + Constraint *constr = new ConstraintEqualMajorAxesConic(&a1,&a2); constr->setTag(tagId); constr->setDriving(driving); @@ -967,7 +1062,7 @@ int System::addConstraintInternalAlignmentPoint2Ellipse(Ellipse &e, Point &p1, I { Constraint *constr = new ConstraintInternalAlignmentPoint2Ellipse(e, p1, alignmentType); constr->setTag(tagId); - constr->setDriving(driving); + constr->setDriving(driving); return addConstraint(constr); } @@ -1125,7 +1220,7 @@ int System::addConstraintInternalAlignmentHyperbolaMinorDiameter(Hyperbola &e, P double X_F1=*e.focus1.x; double Y_F1=*e.focus1.y; double b=*e.radmin; - + // Same idea as for major above, but for minor // DMC=(P1-PA)*(P1-PA)-(P2-PA)*(P2-PA) double closertopositiveminor= pow(-X_1 + X_c + b*(Y_F1 - Y_c)/sqrt(pow(X_F1 - X_c, 2) + @@ -1140,7 +1235,7 @@ int System::addConstraintInternalAlignmentHyperbolaMinorDiameter(Hyperbola &e, P b*(X_F1 - X_c)/sqrt(pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + (Y_F1 - Y_c)*(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2))/sqrt(pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)), 2); - + if(closertopositiveminor<0){ addConstraintInternalAlignmentPoint2Hyperbola(e,p2,HyperbolaPositiveMinorX,tagId, driving); addConstraintInternalAlignmentPoint2Hyperbola(e,p2,HyperbolaPositiveMinorY,tagId, driving); @@ -3664,6 +3759,45 @@ void System::undoSolution() resetToReference(); } +void System::makeReducedJacobian(Eigen::MatrixXd &J, + std::map &jacobianconstraintmap, + GCS::VEC_pD &pdiagnoselist, + std::map< int , int> &tagmultiplicity) +{ + // construct specific parameter list for diagonose ignoring driven constraint parameters + for (int j=0; j < int(plist.size()); j++) { + auto result1 = std::find(std::begin(pdrivenlist), std::end(pdrivenlist), plist[j]); + + if (result1 == std::end(pdrivenlist)) { + pdiagnoselist.push_back(plist[j]); + } + } + + + J = Eigen::MatrixXd::Zero(clist.size(), pdiagnoselist.size()); + + int jacobianconstraintcount=0; + int allcount=0; + for (std::vector::iterator constr=clist.begin(); constr != clist.end(); ++constr) { + (*constr)->revertParams(); + ++allcount; + if ((*constr)->getTag() >= 0 && (*constr)->isDriving()) { + jacobianconstraintcount++; + for (int j=0; j < int(pdiagnoselist.size()); j++) { + J(jacobianconstraintcount-1,j) = (*constr)->grad(pdiagnoselist[j]); + } + + // parallel processing: create tag multiplicity map + if(tagmultiplicity.find((*constr)->getTag()) == tagmultiplicity.end()) + tagmultiplicity[(*constr)->getTag()] = 0; + else + tagmultiplicity[(*constr)->getTag()]++; + + jacobianconstraintmap[jacobianconstraintcount-1] = allcount-1; + } + } +} + int System::diagnose(Algorithm alg) { // Analyses the constrainess grad of the system and provides feedback @@ -3681,16 +3815,15 @@ int System::diagnose(Algorithm alg) dofs = -1; return dofs; } -#ifdef _GCS_DEBUG + #ifdef _DEBUG_TO_FILE - std::ofstream stream; - stream.open("GCS_debug.txt", std::ofstream::out | std::ofstream::app); - stream << "GCS::System::diagnose()" << std::endl; - stream.flush(); - stream.close(); -#endif +SolverReportingManager::Manager().LogToFile("GCS::System::diagnose()\n"); #endif + // Input parameters' lists: + // plist => list of all the parameters of the system, e.g. each coordinate of a point + // pdrivenlist => list of the parameters that are driven by other parameters (e.g. value of driven constraints) + // When adding an external geometry or a constraint on an external geometry the array 'plist' is empty. // So, we must abort here because otherwise we would create an invalid matrix and make the application // eventually crash. This fixes issues #0002372/#0002373. @@ -3704,44 +3837,30 @@ int System::diagnose(Algorithm alg) conflictingTags.clear(); redundantTags.clear(); - // construct specific parameter list for diagonose ignoring driven constraint parameters - GCS::VEC_pD pdiagnoselist; - for (int j=0; j < int(plist.size()); j++) { - auto result1 = std::find(std::begin(pdrivenlist), std::end(pdrivenlist), plist[j]); + // This QR diagnosis uses a reduced Jacobian matrix to calculate the rank of the system and identify + // conflicting and redundant constraints. + // + // reduced Jacobian matrix + // The Jacobian has been reduced to: + // 1. only contain driving constraints, but keep a full size (zero padded). + // 2. remove the parameters of the values of driven constraints. + Eigen::MatrixXd J; - if (result1 == std::end(pdrivenlist)) - pdiagnoselist.push_back(plist[j]); - } - - // map tag to a tag multiplicity (the number of solver constraints associated with the same tag) - std::map< int , int> tagmultiplicity; - - Eigen::MatrixXd J = Eigen::MatrixXd::Zero(clist.size(), pdiagnoselist.size()); - - // The jacobian has been reduced to only contain driving constraints. Identification - // of constraint indices from this reduced jacobian requires a mapping. + // maps the index of the rows of the reduced jacobian matrix (solver constraints) to + // the index those constraints would have in a full size Jacobian matrix std::map jacobianconstraintmap; - - int jacobianconstraintcount=0; - int allcount=0; - for (std::vector::iterator constr=clist.begin(); constr != clist.end(); ++constr) { - (*constr)->revertParams(); - ++allcount; - if ((*constr)->getTag() >= 0 && (*constr)->isDriving()) { - jacobianconstraintcount++; - for (int j=0; j < int(pdiagnoselist.size()); j++) { - J(jacobianconstraintcount-1,j) = (*constr)->grad(pdiagnoselist[j]); - } - - // parallel processing: create tag multiplicity map - if(tagmultiplicity.find((*constr)->getTag()) == tagmultiplicity.end()) - tagmultiplicity[(*constr)->getTag()] = 0; - else - tagmultiplicity[(*constr)->getTag()]++; - - jacobianconstraintmap[jacobianconstraintcount-1] = allcount-1; - } - } + + // list of parameters to be diagnosed in this routine (removes value parameters from driven constraints) + GCS::VEC_pD pdiagnoselist; + + // tag multiplicity gives the number of solver constraints associated with the same tag + // A tag generally corresponds to the Sketcher constraint index - There are special tag values, like 0 and -1. + std::map< int , int> tagmultiplicity; + + + makeReducedJacobian(J, jacobianconstraintmap, pdiagnoselist, tagmultiplicity); + + // QR decomposition method selection: SparseQR vs DenseQR #ifdef EIGEN_SPARSEQR_COMPATIBLE Eigen::SparseMatrix SJ; @@ -3762,8 +3881,10 @@ int System::diagnose(Algorithm alg) } #endif + + #ifdef _GCS_DEBUG - LogMatrix("J",J); + SolverReportingManager::Manager().LogMatrix("J",J); #endif Eigen::MatrixXd R; @@ -3773,6 +3894,7 @@ int System::diagnose(Algorithm alg) Eigen::MatrixXd R2; // Intended for a trapezoidal matrix, where R is the top triangular matrix of the R2 trapezoidal matrix #endif + int paramsNum = 0; int constrNum = 0; int rank = 0; @@ -3780,7 +3902,7 @@ int System::diagnose(Algorithm alg) if(qrAlgorithm==EigenDenseQR){ if (J.rows() > 0) { - qrJT.compute(J.topRows(jacobianconstraintcount).transpose()); + qrJT.compute(J.topRows(jacobianconstraintmap.size()).transpose()); //Eigen::MatrixXd Q = qrJT.matrixQ (); paramsNum = qrJT.rows(); @@ -3803,7 +3925,7 @@ int System::diagnose(Algorithm alg) #ifdef EIGEN_SPARSEQR_COMPATIBLE else if(qrAlgorithm==EigenSparseQR){ if (SJ.rows() > 0) { - auto SJT = SJ.topRows(jacobianconstraintcount).transpose(); + auto SJT = SJ.topRows(jacobianconstraintmap.size()).transpose(); if (SJT.rows() > 0 && SJT.cols() > 0) { SqrJT.compute(SJT); // Do not ask for Q Matrix!! @@ -3838,53 +3960,22 @@ int System::diagnose(Algorithm alg) #endif if(debugMode==IterationLevel) { - std::stringstream stream; - - stream << (qrAlgorithm==EigenSparseQR?"EigenSparseQR":(qrAlgorithm==EigenDenseQR?"DenseQR":"")); - - if (J.rows() > 0) { - stream -#ifdef EIGEN_SPARSEQR_COMPATIBLE - << ", Threads: " << Eigen::nbThreads() -#endif -#ifdef EIGEN_VECTORIZE - << ", Vectorization: On" -#endif - << ", Pivot Threshold: " << qrpivotThreshold - << ", Params: " << paramsNum - << ", Constr: " << constrNum - << ", Rank: " << rank; - } - else { - stream -#ifdef EIGEN_SPARSEQR_COMPATIBLE - << ", Threads: " << Eigen::nbThreads() -#endif -#ifdef EIGEN_VECTORIZE - << ", Vectorization: On" -#endif - << ", Empty Sketch, nothing to solve"; - } - - const std::string tmp = stream.str(); - - LogString(tmp); - + SolverReportingManager::Manager().LogQRSystemInformation(*this, paramsNum, constrNum, rank); } if (J.rows() > 0) { #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX - LogMatrix("R", R); + SolverReportingManager::Manager().LogMatrix("R", R); - LogMatrix("R2", R2); + SolverReportingManager::Manager().LogMatrix("R2", R2); if(qrAlgorithm == EigenDenseQR){ // There is no rowsTranspositions in SparseQR. obtaining Q is buggy in Eigen for SparseQR - LogMatrix("Q", Q); - LogMatrix("RowTransp", qrJT.rowsTranspositions()); + SolverReportingManager::Manager().LogMatrix("Q", Q); + SolverReportingManager::Manager().LogMatrix("RowTransp", qrJT.rowsTranspositions()); } #ifdef SPARSE_Q_MATRIX else if(qrAlgorithm == EigenSparseQR) { - LogMatrix("Q", Q); + SolverReportingManager::Manager().LogMatrix("Q", Q); } #endif #endif @@ -3912,7 +4003,7 @@ int System::diagnose(Algorithm alg) rowPermutations.applyTranspositionOnTheRight(k, rowTranspositions.coeff(k)); } #ifdef EIGEN_SPARSEQR_COMPATIBLE - else if(qrAlgorithm==EigenSparseQR){ + else if(qrAlgorithm==EigenSparseQR){ // J.P = Q.R, see https://eigen.tuxfamily.org/dox/classEigen_1_1SparseQR.html // There is no rowsTransposition in this QR decomposition. // TODO: This detection method won't work for SparseQR @@ -3944,7 +4035,7 @@ int System::diagnose(Algorithm alg) #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX std::string tmp = stream.str(); - LogString(tmp); + SolverReportingManager::Manager().LogString(tmp); #endif // If not independent, must be dependent @@ -3992,7 +4083,7 @@ int System::diagnose(Algorithm alg) stream << "]" << std::endl; tmp = stream.str(); - LogString(tmp); + SolverReportingManager::Manager().LogString(tmp); #endif for( auto param : depParamCols) { pdependentparameters.push_back(pdiagnoselist[param]); @@ -4024,7 +4115,7 @@ int System::diagnose(Algorithm alg) origCol=SqrJT.colsPermutation().indices()[row]; #endif //conflictGroups[j-rank].push_back(clist[origCol]); - conflictGroups[j-rank].push_back(clist[jacobianconstraintmap[origCol]]); + conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); } } int origCol = 0; @@ -4037,9 +4128,9 @@ int System::diagnose(Algorithm alg) origCol=SqrJT.colsPermutation().indices()[j]; #endif //conflictGroups[j-rank].push_back(clist[origCol]); - conflictGroups[j-rank].push_back(clist[jacobianconstraintmap[origCol]]); + conflictGroups[j-rank].push_back(clist[jacobianconstraintmap.at(origCol)]); } - + #ifdef _GCS_DEBUG_SOLVER_JACOBIAN_QR_DECOMPOSITION_TRIANGULAR_MATRIX stream.flush(); @@ -4047,16 +4138,16 @@ int System::diagnose(Algorithm alg) stream << "ConflictGroups: ["; for(auto group :conflictGroups) { stream << "["; - + for(auto c :group) stream << c->getTag(); - + stream << "]"; } stream << "]" << std::endl; tmp = stream.str(); - LogString(tmp); + SolverReportingManager::Manager().LogString(tmp); #endif // try to remove the conflicting constraints and solve the @@ -4084,12 +4175,12 @@ int System::diagnose(Algorithm alg) it != conflictingMap.end(); ++it) { if (static_cast(it->second.size()) > maxPopularity || (static_cast(it->second.size()) == maxPopularity && mostPopular && - tagmultiplicity[it->first->getTag()] < tagmultiplicity[mostPopular->getTag()]) || + tagmultiplicity.at(it->first->getTag()) < tagmultiplicity.at(mostPopular->getTag())) || (static_cast(it->second.size()) == maxPopularity && mostPopular && - tagmultiplicity[it->first->getTag()] == tagmultiplicity[mostPopular->getTag()] && + tagmultiplicity.at(it->first->getTag()) == tagmultiplicity.at(mostPopular->getTag()) && it->first->getTag() > mostPopular->getTag()) - + ) { mostPopular = it->first; maxPopularity = it->second.size(); diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index b6d227d683..1e8bc8cb43 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -51,18 +51,18 @@ namespace GCS LevenbergMarquardt = 1, DogLeg = 2 }; - + enum DogLegGaussStep { FullPivLU = 0, LeastNormFullPivLU = 1, LeastNormLdlt = 2 }; - + enum QRAlgorithm { EigenDenseQR = 0, EigenSparseQR = 1 }; - + enum DebugMode { NoDebug = 0, Minimal = 1, @@ -77,7 +77,7 @@ namespace GCS VEC_pD plist; // list of the unknown parameters VEC_pD pdrivenlist; // list of parameters of driven constraints MAP_pD_I pIndex; - + VEC_pD pdependentparameters; // list of dependent parameters by the system std::vector clist; @@ -107,6 +107,8 @@ namespace GCS int solve_LM(SubSystem *subsys, bool isRedundantsolving=false); int solve_DL(SubSystem *subsys, bool isRedundantsolving=false); + void makeReducedJacobian(Eigen::MatrixXd &J, std::map &jacobianconstraintmap, GCS::VEC_pD &pdiagnoselist, std::map< int , int> &tagmultiplicity); + #ifdef _GCS_EXTRACT_SOLVER_SUBSYSTEM_ void extractSubsystem(SubSystem *subsys, bool isRedundantsolving); #endif @@ -122,18 +124,18 @@ namespace GCS double qrpivotThreshold; DebugMode debugMode; double LM_eps; - double LM_eps1; + double LM_eps1; double LM_tau; double DL_tolg; - double DL_tolx; + double DL_tolx; double DL_tolf; double LM_epsRedundant; - double LM_eps1Redundant; + double LM_eps1Redundant; double LM_tauRedundant; double DL_tolgRedundant; - double DL_tolxRedundant; - double DL_tolfRedundant; - + double DL_tolxRedundant; + double DL_tolfRedundant; + public: System(); /*System(std::vector clist_);*/ @@ -227,7 +229,7 @@ namespace GCS double* n1, double* n2, bool flipn1, bool flipn2, int tagId, bool driving = true); - + // internal alignment constraints int addConstraintInternalAlignmentPoint2Ellipse(Ellipse &e, Point &p1, InternalAlignmentType alignmentType, int tagId=0, bool driving = true); int addConstraintInternalAlignmentEllipseMajorDiameter(Ellipse &e, Point &p1, Point &p2, int tagId=0, bool driving = true); @@ -251,7 +253,7 @@ namespace GCS // If there's only one, a signed value is returned. // Effectively, it calculates the error of a UI constraint double calculateConstraintErrorByTag(int tagId); - + void rescaleConstraint(int id, double coeff); void declareUnknowns(VEC_pD ¶ms); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index d6acce8812..8fd6469139 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -1749,7 +1749,10 @@ std::set ViewProviderSketch::detectPreselectionConstr(const SoPickedPoint * b != edit->combinedConstrBoxes[constrIdsStr].end(); ++b) { #ifdef FC_DEBUG - Base::Console().Log("Abs(%f,%f),Trans(%f,%f),Coords(%d,%d),iCoords(%f,%f),icon(%d,%d),isize(%d,%d),boundingbox([%d,%d],[%d,%d])\n", absPos[0],absPos[1],trans[0], trans[1], cursorPos[0], cursorPos[1], iconCoords[0], iconCoords[1], iconX, iconY, iconSize[0], iconSize[1], b->first.topLeft().x(),b->first.topLeft().y(),b->first.bottomRight().x(),b->first.bottomRight().y()); + // Useful code to debug coordinates and bounding boxes that does not need to be compiled in for + // any debug operations. + + /*Base::Console().Log("Abs(%f,%f),Trans(%f,%f),Coords(%d,%d),iCoords(%f,%f),icon(%d,%d),isize(%d,%d),boundingbox([%d,%d],[%d,%d])\n", absPos[0],absPos[1],trans[0], trans[1], cursorPos[0], cursorPos[1], iconCoords[0], iconCoords[1], iconX, iconY, iconSize[0], iconSize[1], b->first.topLeft().x(),b->first.topLeft().y(),b->first.bottomRight().x(),b->first.bottomRight().y());*/ #endif if (b->first.contains(iconX, iconY)) { diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 8f6b6feffb..9bffceb6b8 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -27,13 +27,14 @@ # include # include #include +#include # include # include # include # include # include #include - +#include #include #include @@ -74,22 +75,24 @@ using namespace TechDraw; /*static*/ int DrawUtil::getIndexFromName(std::string geomName) { +// Base::Console().Message("DU::getIndexFromName(%s)\n", geomName.c_str()); boost::regex re("\\d+$"); // one of more digits at end of string boost::match_results what; boost::match_flag_type flags = boost::match_default; - char* endChar; +// char* endChar; std::string::const_iterator begin = geomName.begin(); std::string::const_iterator end = geomName.end(); std::stringstream ErrorMsg; if (!geomName.empty()) { if (boost::regex_search(begin, end, what, re, flags)) { - return int (std::strtol(what.str().c_str(), &endChar, 10)); //TODO: use std::stoi() in c++11 + return int (std::stoi(what.str())); } else { ErrorMsg << "getIndexFromName: malformed geometry name - " << geomName; throw Base::ValueError(ErrorMsg.str()); } } else { + Base::Console().Log("DU::getIndexFromName(%s) - empty geometry name\n",geomName.c_str()); throw Base::ValueError("getIndexFromName - empty geometry name"); } } @@ -327,6 +330,15 @@ std::string DrawUtil::formatVector(const gp_Pnt& v) return result; } +std::string DrawUtil::formatVector(const QPointF& v) +{ + std::string result; + std::stringstream builder; + builder << std::fixed << std::setprecision(3) ; + builder << " (" << v.x() << "," << v.y() << ") "; + result = builder.str(); + return result; +} //! compare 2 vectors for sorting - true if v1 < v2 bool DrawUtil::vectorLess(const Base::Vector3d& v1, const Base::Vector3d& v2) diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 485a2c2686..3445dfc7f6 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -74,6 +75,8 @@ class TechDrawExport DrawUtil { static std::string formatVector(const gp_Dir& v); static std::string formatVector(const gp_Vec& v); static std::string formatVector(const gp_Pnt& v); + static std::string formatVector(const QPointF& v); + static bool vectorLess(const Base::Vector3d& v1, const Base::Vector3d& v2); static Base::Vector3d toR3(const gp_Ax2 fromSystem, const Base::Vector3d fromPoint); static bool checkParallel(const Base::Vector3d v1, const Base::Vector3d v2, double tolerance = FLT_EPSILON); diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index c3a1eb1b26..13e59fac08 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -107,7 +108,7 @@ DrawViewDimension::DrawViewDimension(void) ADD_PROPERTY_TYPE(UnderTolerance ,(0.0),"",App::Prop_None,"- Tolerance value"); //hide the properties the user can't edit in the property editor - References2D.setStatus(App::Property::Hidden,true); +// References2D.setStatus(App::Property::Hidden,true); References3D.setStatus(App::Property::Hidden,true); //hide the DrawView properties that don't apply to Dimensions @@ -208,24 +209,26 @@ short DrawViewDimension::mustExecute() const App::DocumentObjectExecReturn *DrawViewDimension::execute(void) { +// Base::Console().Message("DVD::execute() - %s\n", getNameInDocument()); if (!keepUpdated()) { return App::DocumentObject::StdReturn; } - if (!has2DReferences()) { //too soon - return App::DocumentObject::StdReturn; - } - - if (!getViewPart()->hasGeometry()) { //happens when loading saved document - Base::Console().Log("INFO - DVD::getDimValue ViewPart has no Geometry yet\n"); + //any empty Reference2D?? + if (!has2DReferences()) { //too soon? return App::DocumentObject::StdReturn; } - if (!checkReferences2D()) { - Base::Console().Log("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); + //can't do anything until Source has geometry + if (!getViewPart()->hasGeometry()) { //happens when loading saved document return App::DocumentObject::StdReturn; } - + + //now we can check if Reference2ds have valid targets. + if (!checkReferences2D()) { + return App::DocumentObject::StdReturn; + } + const std::vector &subElements = References2D.getSubValues(); if ( Type.isValue("Distance") || @@ -433,6 +436,7 @@ App::DocumentObjectExecReturn *DrawViewDimension::execute(void) std::string DrawViewDimension::getFormatedValue(bool obtuse) { +// Base::Console().Message("DVD::getFormatedValue()\n"); std::string result; if (Arbitrary.getValue()) { return FormatSpec.getStrValue(); @@ -554,14 +558,13 @@ std::string DrawViewDimension::getFormatedValue(bool obtuse) //!NOTE: this returns the Dimension value in internal units (ie mm)!!!! double DrawViewDimension::getDimValue() { +// Base::Console().Message("DVD::getDimValue()\n"); double result = 0.0; if (!has2DReferences()) { //happens during Dimension creation - Base::Console().Log("INFO - DVD::getDimValue - Dimension has no References\n"); return result; } if (!getViewPart()->hasGeometry()) { //happens when loading saved document - Base::Console().Log("INFO - DVD::getDimValue ViewPart has no Geometry yet\n"); return result; } @@ -586,7 +589,7 @@ double DrawViewDimension::getDimValue() } else { // Projected Values if (!checkReferences2D()) { - Base::Console().Log("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); + Base::Console().Warning("Error: DVD::getDimValue - %s - 2D references are corrupt\n",getNameInDocument()); return result; } if ( Type.isValue("Distance") || @@ -633,6 +636,7 @@ double DrawViewDimension::getDimValue() pointPair DrawViewDimension::getPointsOneEdge() { +// Base::Console().Message("DVD::getPointsOneEdge() - %s\n",getNameInDocument()); pointPair result; const std::vector &subElements = References2D.getSubValues(); @@ -643,7 +647,7 @@ pointPair DrawViewDimension::getPointsOneEdge() if (geom && geom->geomType == TechDrawGeometry::GeomType::GENERIC) { gen = static_cast(geom); } else { - Base::Console().Log("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); + Base::Console().Error("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); return result; } result.first = DrawUtil::vector23(gen->points[0]); @@ -653,6 +657,7 @@ pointPair DrawViewDimension::getPointsOneEdge() pointPair DrawViewDimension::getPointsTwoEdges() { +// Base::Console().Message("DVD::getPointsTwoEdges() - %s\n",getNameInDocument()); pointPair result; const std::vector &subElements = References2D.getSubValues(); @@ -671,6 +676,7 @@ pointPair DrawViewDimension::getPointsTwoEdges() pointPair DrawViewDimension::getPointsTwoVerts() { +// Base::Console().Message("DVD::getPointsTwoVerts() - %s\n",getNameInDocument()); pointPair result; const std::vector &subElements = References2D.getSubValues(); @@ -690,6 +696,7 @@ pointPair DrawViewDimension::getPointsTwoVerts() pointPair DrawViewDimension::getPointsEdgeVert() { +// Base::Console().Message("DVD::getPointsEdgeVert() - %s\n",getNameInDocument()); pointPair result; const std::vector &subElements = References2D.getSubValues(); @@ -706,7 +713,7 @@ pointPair DrawViewDimension::getPointsEdgeVert() } if ((v == nullptr) || (e == nullptr) ) { - Base::Console().Log("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); + Base::Console().Error("Error: DVD - %s - 2D references are corrupt\n",getNameInDocument()); return result; } result = closestPoints(e->occEdge,v->occVertex); @@ -781,28 +788,42 @@ int DrawViewDimension::getRefType3(const std::string g1, } -//! validate 2D references - only checks if they exist, not if they are the right type +//! validate 2D references - only checks if the target exists bool DrawViewDimension::checkReferences2D() const { +// Base::Console().Message("DVD::checkReFerences2d() - %s\n",getNameInDocument()); bool result = true; - //const std::vector &objects = References2D.getValues(); - const std::vector &subElements = References2D.getSubValues(); - - for (auto& s: subElements) { - int idx = DrawUtil::getIndexFromName(s); - if (DrawUtil::getGeomTypeFromName(s) == "Edge") { - TechDrawGeometry::BaseGeom* geom = getViewPart()->getProjEdgeByIndex(idx); - if (geom == nullptr) { - result = false; - break; - } - } else if (DrawUtil::getGeomTypeFromName(s) == "Vertex") { - TechDrawGeometry::Vertex* v = getViewPart()->getProjVertexByIndex(idx); - if (v == nullptr) { - result = false; - break; + const std::vector &objects = References2D.getValues(); + if (!objects.empty()) { + const std::vector &subElements = References2D.getSubValues(); + if (!subElements.empty()) { + for (auto& s: subElements) { + if (!s.empty()) { + int idx = DrawUtil::getIndexFromName(s); + if (DrawUtil::getGeomTypeFromName(s) == "Edge") { + TechDrawGeometry::BaseGeom* geom = getViewPart()->getProjEdgeByIndex(idx); + if (geom == nullptr) { + result = false; + break; + } + } else if (DrawUtil::getGeomTypeFromName(s) == "Vertex") { + TechDrawGeometry::Vertex* v = getViewPart()->getProjVertexByIndex(idx); + if (v == nullptr) { + result = false; + break; + } + } + } else { + result = false; + } } + } else { + Base::Console().Log("DVD::checkRegerences2d() - %s - subelements empty!\n",getNameInDocument()); + result = false; } + } else { + Base::Console().Log("DVD::checkRegerences2d() - %s - objects empty!\n",getNameInDocument()); + result = false; } return result; } @@ -913,16 +934,25 @@ bool DrawViewDimension::leaderIntersectsArc(Base::Vector3d s, Base::Vector3d poi return result; } +//are there non-blank references? bool DrawViewDimension::has2DReferences(void) const { +// Base::Console().Message("DVD::has2DReferences() - %s\n",getNameInDocument()); bool result = false; + const std::vector &objects = References2D.getValues(); const std::vector &SubNames = References2D.getSubValues(); if (!objects.empty()) { App::DocumentObject* testRef = objects.at(0); if (testRef != nullptr) { if (!SubNames.empty()) { - result = true; + result = true; //not empty is good + for (auto& s: SubNames) { //but check individual entries + if (s.empty()) { + result = false; + break; + } + } } } } diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index 620df235e6..c99f250907 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -159,6 +159,8 @@ DrawViewPart::~DrawViewPart() TopoDS_Shape DrawViewPart::getSourceShape(void) const { +// Base::Console().Message("DVP::getSourceShape() - %s\n", getNameInDocument()); + TopoDS_Shape result; const std::vector& links = Source.getValues(); if (links.empty()) { @@ -203,6 +205,7 @@ TopoDS_Shape DrawViewPart::getSourceShape(void) const std::vector DrawViewPart::getShapesFromObject(App::DocumentObject* docObj) const { +// Base::Console().Message("DVP::getShapesFromObject() - %s\n", getNameInDocument()); std::vector result; App::GroupExtension* gex = dynamic_cast(docObj); App::Property* gProp = docObj->getPropertyByName("Group"); @@ -250,6 +253,7 @@ std::vector DrawViewPart::getShapesFromObject(App::DocumentObject* TopoDS_Shape DrawViewPart::getSourceShapeFused(void) const { +// Base::Console().Message("DVP::getSourceShapeFused() - %s\n", getNameInDocument()); TopoDS_Shape baseShape = getSourceShape(); if (!baseShape.IsNull()) { TopoDS_Iterator it(baseShape); @@ -272,6 +276,7 @@ TopoDS_Shape DrawViewPart::getSourceShapeFused(void) const App::DocumentObjectExecReturn *DrawViewPart::execute(void) { +// Base::Console().Message("DVP::execute() - %s\n", getNameInDocument()); if (!keepUpdated()) { return App::DocumentObject::StdReturn; } @@ -382,6 +387,7 @@ void DrawViewPart::onChanged(const App::Property* prop) //note: slightly different than routine with same name in DrawProjectSplit TechDrawGeometry::GeometryObject* DrawViewPart::buildGeometryObject(TopoDS_Shape shape, gp_Ax2 viewAxis) { +// Base::Console().Message("DVP::buildGO() - %s\n", getNameInDocument()); TechDrawGeometry::GeometryObject* go = new TechDrawGeometry::GeometryObject(getNameInDocument(), this); go->setIsoCount(IsoCount.getValue()); go->isPerspective(Perspective.getValue()); @@ -436,11 +442,15 @@ TechDrawGeometry::GeometryObject* DrawViewPart::buildGeometryObject(TopoDS_Shape go->extractGeometry(TechDrawGeometry::ecUVISO, false); } + auto end = chrono::high_resolution_clock::now(); auto diff = end - start; double diffOut = chrono::duration (diff).count(); Base::Console().Log("TIMING - %s DVP spent: %.3f millisecs in GO::extractGeometry\n",getNameInDocument(),diffOut); - + const std::vector & edges = go->getEdgeGeometry(); + if (edges.empty()) { + Base::Console().Log("DVP::buildGO - NO extracted edges!\n"); + } bbox = go->calcBoundingBox(); return go; } diff --git a/src/Mod/TechDraw/App/GeometryObject.cpp b/src/Mod/TechDraw/App/GeometryObject.cpp index 30c79364be..1bf2b8e454 100644 --- a/src/Mod/TechDraw/App/GeometryObject.cpp +++ b/src/Mod/TechDraw/App/GeometryObject.cpp @@ -162,7 +162,8 @@ void GeometryObject::clear() void GeometryObject::projectShape(const TopoDS_Shape& input, const gp_Ax2 viewAxis) { - // Clear previous Geometry +// Base::Console().Message("GO::projectShape()\n"); + // Clear previous Geometry clear(); auto start = chrono::high_resolution_clock::now(); @@ -183,6 +184,7 @@ void GeometryObject::projectShape(const TopoDS_Shape& input, } brep_hlr->Update(); brep_hlr->Hide(); + } catch (Standard_Failure e) { Base::Console().Error("GO::projectShape - OCC error - %s - while projecting shape\n", @@ -386,6 +388,7 @@ void GeometryObject::extractGeometry(edgeClass category, bool visible) //! update edgeGeom and vertexGeom from Compound of edges void GeometryObject::addGeomFromCompound(TopoDS_Shape edgeCompound, edgeClass category, bool visible) { +// Base::Console().Message("GO::addGeomFromCompound()\n"); if(edgeCompound.IsNull()) { Base::Console().Log("TechDraw::GeometryObject::addGeomFromCompound edgeCompound is NULL\n"); return; // There is no OpenCascade Geometry to be calculated @@ -393,7 +396,8 @@ void GeometryObject::addGeomFromCompound(TopoDS_Shape edgeCompound, edgeClass ca BaseGeom* base; TopExp_Explorer edges(edgeCompound, TopAbs_EDGE); - for (int i = 1 ; edges.More(); edges.Next(),i++) { + int i = 1; + for ( ; edges.More(); edges.Next(),i++) { const TopoDS_Edge& edge = TopoDS::Edge(edges.Current()); if (edge.IsNull()) { //Base::Console().Log("INFO - GO::addGeomFromCompound - edge: %d is NULL\n",i); @@ -406,7 +410,7 @@ void GeometryObject::addGeomFromCompound(TopoDS_Shape edgeCompound, edgeClass ca base = BaseGeom::baseFactory(edge); if (base == nullptr) { - Base::Console().Message("Error - GO::addGeomFromCompound - baseFactory failed for edge: %d\n",i); + Base::Console().Log("Error - GO::addGeomFromCompound - baseFactory failed for edge: %d\n",i); throw Base::ValueError("GeometryObject::addGeomFromCompound - baseFactory failed"); } base->classOfEdge = category; @@ -533,18 +537,22 @@ bool GeometryObject::isWithinArc(double theta, double first, Base::BoundBox3d GeometryObject::calcBoundingBox() const { +// Base::Console().Message("GO::calcBoundingBox() - edges: %d\n", edgeGeom.size()); Bnd_Box testBox; testBox.SetGap(0.0); - for (std::vector::const_iterator it( edgeGeom.begin() ); - it != edgeGeom.end(); ++it) { - BRepBndLib::Add((*it)->occEdge, testBox); + if (!edgeGeom.empty()) { + for (std::vector::const_iterator it( edgeGeom.begin() ); + it != edgeGeom.end(); ++it) { + BRepBndLib::Add((*it)->occEdge, testBox); + } } + + double xMin = 0,xMax = 0,yMin = 0,yMax = 0, zMin = 0, zMax = 0; if (testBox.IsVoid()) { Base::Console().Log("INFO - GO::calcBoundingBox - testBox is void\n"); + } else { + testBox.Get(xMin,yMin,zMin,xMax,yMax,zMax); } - double xMin,xMax,yMin,yMax,zMin,zMax; - testBox.Get(xMin,yMin,zMin,xMax,yMax,zMax); - Base::BoundBox3d bbox(xMin,yMin,zMin,xMax,yMax,zMax); return bbox; } diff --git a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp index c9ca3b0652..b3fc660cac 100644 --- a/src/Mod/TechDraw/Gui/QGIViewDimension.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewDimension.cpp @@ -516,6 +516,9 @@ void QGIViewDimension::draw() const char *dimType = dim->Type.getValueAsString(); + datumLabel->show(); + show(); + if (strcmp(dimType, "Distance") == 0 || strcmp(dimType, "DistanceX") == 0 || strcmp(dimType, "DistanceY") == 0) {