From bbb6eeb1edff7a4b46fe2ad46e82c232fd9e2c6b Mon Sep 17 00:00:00 2001 From: supermixed <144506423+supermixed@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:25:23 +1000 Subject: [PATCH] Core: Import STEP: Cancel button does not cancel (#16499) * Fix importing .step file when user cancelled import settings dialog * Refactor object loading python code, fix not using settings when STEP options dialog not shown * Use custom exception type for user cancelling import instead of `RuntimeError` * Pull python code out to external file --- src/App/Application.cpp | 4 ++ src/Base/PyObjectBase.cpp | 1 + src/Base/PyObjectBase.h | 1 + src/Ext/freecad/CMakeLists.txt | 1 + src/Ext/freecad/module_io.py | 13 ++++++ src/Gui/Application.cpp | 67 +++++++++++++-------------- src/Mod/Import/Gui/AppImportGuiPy.cpp | 25 +++++----- 7 files changed, 65 insertions(+), 47 deletions(-) create mode 100644 src/Ext/freecad/module_io.py diff --git a/src/App/Application.cpp b/src/App/Application.cpp index c7e1f6ac3f..e348c19662 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -427,6 +427,10 @@ void Application::setupPythonException(PyObject* module) Base::PyExc_FC_PropertyError = PyErr_NewException("Base.PropertyError", PyExc_AttributeError, nullptr); Py_INCREF(Base::PyExc_FC_PropertyError); PyModule_AddObject(module, "PropertyError", Base::PyExc_FC_PropertyError); + + Base::PyExc_FC_AbortIOException = PyErr_NewException("Base.PyExc_FC_AbortIOException", PyExc_BaseException, nullptr); + Py_INCREF(Base::PyExc_FC_AbortIOException); + PyModule_AddObject(module, "AbortIOException", Base::PyExc_FC_AbortIOException); } // clang-format on diff --git a/src/Base/PyObjectBase.cpp b/src/Base/PyObjectBase.cpp index 45c62c8f27..b3e1db9cd9 100644 --- a/src/Base/PyObjectBase.cpp +++ b/src/Base/PyObjectBase.cpp @@ -49,6 +49,7 @@ PyObject* Base::PyExc_FC_ExpressionError = nullptr; PyObject* Base::PyExc_FC_ParserError = nullptr; PyObject* Base::PyExc_FC_CADKernelError = nullptr; PyObject* Base::PyExc_FC_PropertyError = nullptr; +PyObject* Base::PyExc_FC_AbortIOException = nullptr; typedef struct { //NOLINT PyObject_HEAD diff --git a/src/Base/PyObjectBase.h b/src/Base/PyObjectBase.h index 2f40a24a61..76b0e72fcd 100644 --- a/src/Base/PyObjectBase.h +++ b/src/Base/PyObjectBase.h @@ -431,6 +431,7 @@ BaseExport extern PyObject* PyExc_FC_ExpressionError; BaseExport extern PyObject* PyExc_FC_ParserError; BaseExport extern PyObject* PyExc_FC_CADKernelError; BaseExport extern PyObject* PyExc_FC_PropertyError; +BaseExport extern PyObject* PyExc_FC_AbortIOException; /** Exception handling for python callback functions * Is a convenience macro to manage the exception handling of python callback diff --git a/src/Ext/freecad/CMakeLists.txt b/src/Ext/freecad/CMakeLists.txt index d1d660f337..caaf5b5eb5 100644 --- a/src/Ext/freecad/CMakeLists.txt +++ b/src/Ext/freecad/CMakeLists.txt @@ -28,6 +28,7 @@ configure_file(__init__.py.template ${NAMESPACE_INIT}) set(EXT_FILES freecad_doc.py + module_io.py part.py partdesign.py project_utility.py diff --git a/src/Ext/freecad/module_io.py b/src/Ext/freecad/module_io.py new file mode 100644 index 0000000000..321aa03bf6 --- /dev/null +++ b/src/Ext/freecad/module_io.py @@ -0,0 +1,13 @@ +def OpenInsertObject(importerModule, objectPath, importMethod, docName = ""): + try: + importArgs = [] + importKwargs = {} + + if docName: + importArgs.append(docName) + if hasattr(importerModule, "importOptions"): + importKwargs["options"] = importerModule.importOptions(objectPath) + + getattr(importerModule, importMethod)(objectPath, *importArgs, **importKwargs) + except PyExc_FC_AbortIOException: + pass diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 97dcce33cc..9690f9a919 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -595,6 +595,7 @@ Application::~Application() // creating std commands //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + void Application::open(const char* FileName, const char* Module) { WaitCursor wc; @@ -636,19 +637,21 @@ void Application::open(const char* FileName, const char* Module) } } else { - // issue module loading - Command::doCommand(Command::App, "import %s", Module); - - // check for additional import options - std::stringstream str; - str << "if hasattr(" << Module << ", \"importOptions\"):\n" - << " options = " << Module << ".importOptions(u\"" << unicodepath << "\")\n" - << " " << Module << ".open(u\"" << unicodepath << "\", options = options)\n" - << "else:\n" - << " " << Module << ".open(u\"" << unicodepath << "\")\n"; - - std::string code = str.str(); - Gui::Command::runCommand(Gui::Command::App, code.c_str()); + // Load using provided python module + { + Base::PyGILStateLocker locker; + Py::Module moduleIo(PyImport_ImportModule("freecad.module_io")); + const auto dictS = moduleIo.getDict().keys().as_string(); + if (!moduleIo.isNull() && moduleIo.hasAttr("OpenInsertObject")) + { + const Py::TupleN args( + Py::Module(PyImport_ImportModule(Module)), + Py::String(unicodepath), + Py::String("open") + ); + moduleIo.callMemberFunction("OpenInsertObject", args); + } + } // ViewFit if (sendHasMsgToActiveView("ViewFit")) { @@ -713,30 +716,22 @@ void Application::importFrom(const char* FileName, const char* DocName, const ch } } - // check for additional import options - std::stringstream str; - if (DocName) { - str << "if hasattr(" << Module << ", \"importOptions\"):\n" - << " options = " << Module << ".importOptions(u\"" << unicodepath - << "\")\n" - << " " << Module << ".insert(u\"" << unicodepath << "\", \"" << DocName - << "\", options = options)\n" - << "else:\n" - << " " << Module << ".insert(u\"" << unicodepath << "\", \"" << DocName - << "\")\n"; + // Load using provided python module + { + Base::PyGILStateLocker locker; + Py::Module moduleIo(PyImport_ImportModule("freecad.module_io")); + const auto dictS = moduleIo.getDict().keys().as_string(); + if (!moduleIo.isNull() && moduleIo.hasAttr("OpenInsertObject")) + { + const Py::TupleN args( + Py::Module(PyImport_ImportModule(Module)), + Py::String(unicodepath), + Py::String("insert"), + Py::String(DocName) + ); + moduleIo.callMemberFunction("OpenInsertObject", args); + } } - else { - str << "if hasattr(" << Module << ", \"importOptions\"):\n" - << " options = " << Module << ".importOptions(u\"" << unicodepath - << "\")\n" - << " " << Module << ".insert(u\"" << unicodepath - << "\", options = options)\n" - << "else:\n" - << " " << Module << ".insert(u\"" << unicodepath << "\")\n"; - } - - std::string code = str.str(); - Gui::Command::runCommand(Gui::Command::App, code.c_str()); // Commit the transaction if (doc && !pendingCommand) { diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index 076b5bab8c..a65e1832ef 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -128,18 +128,21 @@ private: Base::FileInfo file(name8bit.c_str()); if (file.hasExtension({"stp", "step"})) { PartGui::TaskImportStep dlg(Gui::getMainWindow()); - if (!dlg.showDialog() || dlg.exec()) { - auto stepSettings = dlg.getSettings(); - options.setItem("merge", Py::Boolean(stepSettings.merge)); - options.setItem("useLinkGroup", Py::Boolean(stepSettings.useLinkGroup)); - options.setItem("useBaseName", Py::Boolean(stepSettings.useBaseName)); - options.setItem("importHidden", Py::Boolean(stepSettings.importHidden)); - options.setItem("reduceObjects", Py::Boolean(stepSettings.reduceObjects)); - options.setItem("showProgress", Py::Boolean(stepSettings.showProgress)); - options.setItem("expandCompound", Py::Boolean(stepSettings.expandCompound)); - options.setItem("mode", Py::Long(stepSettings.mode)); - options.setItem("codePage", Py::Long(stepSettings.codePage)); + if (dlg.showDialog()) { + if (!dlg.exec()) { + throw Py::Exception(Base::PyExc_FC_AbortIOException, "User cancelled import"); + } } + auto stepSettings = dlg.getSettings(); + options.setItem("merge", Py::Boolean(stepSettings.merge)); + options.setItem("useLinkGroup", Py::Boolean(stepSettings.useLinkGroup)); + options.setItem("useBaseName", Py::Boolean(stepSettings.useBaseName)); + options.setItem("importHidden", Py::Boolean(stepSettings.importHidden)); + options.setItem("reduceObjects", Py::Boolean(stepSettings.reduceObjects)); + options.setItem("showProgress", Py::Boolean(stepSettings.showProgress)); + options.setItem("expandCompound", Py::Boolean(stepSettings.expandCompound)); + options.setItem("mode", Py::Long(stepSettings.mode)); + options.setItem("codePage", Py::Long(stepSettings.codePage)); } return options; }