diff --git a/src/module/CMakeLists.txt b/src/module/CMakeLists.txt index c24f99a..9f86692 100644 --- a/src/module/CMakeLists.txt +++ b/src/module/CMakeLists.txt @@ -90,6 +90,7 @@ set(HEADERS wizards/gtpy_wizardgeometryitem.h wizards/python_task/gtpy_taskwizardpage.h wizards/script_calculator/gtpy_scriptcalculatorwizardpage.h + utilities/gtpy_moduleupgrader.h ) set(SOURCES @@ -162,6 +163,7 @@ set(SOURCES wizards/gtpy_wizardgeometryitem.cpp wizards/python_task/gtpy_taskwizardpage.cpp wizards/script_calculator/gtpy_scriptcalculatorwizardpage.cpp + utilities/gtpy_moduleupgrader.cpp ) set(MODULE_ID "Python Module (Python ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR})") diff --git a/src/module/gt_python.cpp b/src/module/gt_python.cpp index 4b23cc7..42ec815 100644 --- a/src/module/gt_python.cpp +++ b/src/module/gt_python.cpp @@ -55,9 +55,11 @@ #include "gt_accessdataconnection.h" #include "gtpy_scriptcollectionsettings.h" +#include "gtpy_moduleupgrader.h" #include "gt_python.h" + #if GT_VERSION >= 0x010700 GtVersionNumber GtPythonModule::version() @@ -327,6 +329,18 @@ QList GtPythonModule::sharedFunctions() const } #endif +QList +GtPythonModule::upgradeRoutines() const +{ + return { +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + gt::VersionUpgradeRoutine {GtVersionNumber(2, 0, 0), + >py::module_upgrader::to_2_0_0::run} +#endif + }; +} + + namespace PythonExecution { @@ -350,6 +364,7 @@ parseScriptFile(QFile& file) return out.readAll(); } + int runPythonInterpreter(const QStringList& args) { diff --git a/src/module/gt_python.h b/src/module/gt_python.h index a538bce..b41702e 100644 --- a/src/module/gt_python.h +++ b/src/module/gt_python.h @@ -177,6 +177,9 @@ class GtPythonModule: public QObject, public GtModuleInterface, QList commandLineFunctions() const override; QList sharedFunctions() const override; + + /** We are providing routines to upgrade the datamodel */ + QList upgradeRoutines() const override; #endif private: diff --git a/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp b/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp index 7ab7005..70b8fe0 100644 --- a/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp +++ b/src/module/processcomponents/gtpy_abstractscriptcomponent.cpp @@ -41,13 +41,14 @@ setStructContainerValue(GtPropertyStructContainer& con, const QString& argName, { auto entry = std::find_if(con.begin(), con.end(), [&argName](const GtPropertyStructInstance& entry){ +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + return entry.ident() == argName; +#elif GT_VERSION >= GT_VERSION_CHECK(2, 0, 0) return entry.getMemberVal("name") == argName; +#endif }); - if (entry == con.end()) - { - return false; - } + if (entry == con.end()) return false; return entry->setMemberVal("value", value); } @@ -57,13 +58,15 @@ structContainerValue(const GtPropertyStructContainer& con, const QString& argNam { auto entry = std::find_if(con.begin(), con.end(), [&argName](const GtPropertyStructInstance& entry){ +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + return entry.ident() == argName; +#elif GT_VERSION >= GT_VERSION_CHECK(2, 0, 0) return entry.getMemberVal("name") == argName; +#endif + }); - if (entry == con.end()) - { - return {}; - } + if (entry == con.end()) return {}; return entry->getMemberValToVariant("value"); } @@ -92,9 +95,11 @@ GtpyAbstractScriptComponent::GtpyAbstractScriptComponent() : m_pyThreadId{-1}, m_replaceTabBySpaces{"replaceTab", "Replace tab by spaces"}, m_tabSize{"tabSize", "Tab size"}, - m_script{"script", "Script"} -#if GT_VERSION >= GT_VERSION_CHECK(2, 0, 0) - , + m_script{"script", "Script"}, +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + m_inputArgs{"input_args", GtPropertyStructContainer::Associative}, + m_outputArgs{"output_args", GtPropertyStructContainer::Associative} +#elif GT_VERSION >= GT_VERSION_CHECK(2, 0, 0) m_inputArgs{"input_args"}, m_outputArgs{"output_args"} #endif @@ -104,7 +109,9 @@ GtpyAbstractScriptComponent::GtpyAbstractScriptComponent() : auto createStructDef = [](const QString& typeName, gt::PropertyFactoryFunction f){ GtPropertyStructDefinition structDef(typeName); + #if GT_VERSION < GT_VERSION_CHECK(2, 1, 0) structDef.defineMember("name", gt::makeStringProperty()); + #endif // GT_VERSION < GT_VERSION_CHECK(2, 1, 0) structDef.defineMember("value", f); return structDef; }; diff --git a/src/module/utilities/gtpy_decorator.cpp b/src/module/utilities/gtpy_decorator.cpp index 419e769..d30e107 100644 --- a/src/module/utilities/gtpy_decorator.cpp +++ b/src/module/utilities/gtpy_decorator.cpp @@ -1038,6 +1038,58 @@ GtpyDecorator::getPropertyContainerVal(GtObject* obj, QString const& id, return structCon.getMemberValToVariant(memberId); } +QVariant +GtpyDecorator::getPropertyContainerVal(GtObject* obj, + const QString& id, + const QString& entryId, + const QString& memberId) +{ + GtPropertyStructContainer* s = structContainerOfObject(obj, id); + + if (!s) + { + gtError() << __func__ << " -> PropertyStruct container of " + "object not found!"; + return {}; + } + + GtPropertyStructContainer::const_iterator it = s->findEntry(entryId); + + if (it != s->end()) + { + return it->getMemberValToVariant(memberId); + } + + gtError() << __func__ << tr("-> Cannot get parameter %1 of entry %2" + "in container %3").arg(memberId, entryId, id); + + return {}; +} + +QStringList +GtpyDecorator::getPropertyContainerEntryIds(GtObject* obj, + const QString& containerId) +{ + GtPropertyStructContainer* s = structContainerOfObject(obj, containerId); + + if (!s) + { + gtError() << __func__ << " -> PropertyStruct container of " + "object not found!"; + return {}; + } + + QStringList retVal; + + std::transform(s->begin(), s->end(), std::back_inserter(retVal), + [](const auto& v) + { + return v.ident(); + }); + + return retVal; +} + bool GtpyDecorator::setPropertyContainerVal(GtObject* obj, const QString& id, int index, const QString& memberId, @@ -1059,11 +1111,39 @@ GtpyDecorator::setPropertyContainerVal(GtObject* obj, const QString& id, return false; } - GtPropertyStructInstance& structCon = s->at(index); return structCon.setMemberVal(memberId, val); } + +bool +GtpyDecorator::setPropertyContainerVal(GtObject* obj, const QString& id, + const QString& entryId, + const QString& memberId, + const QVariant& val) +{ + GtPropertyStructContainer* s = structContainerOfObject(obj, id); + + if (!s) + { + gtError() << __func__ << " -> PropertyStruct container of " + "object not found!"; + return false; + } + + GtPropertyStructContainer::iterator it = s->findEntry(entryId); + + if (it != s->end()) + { + return it->setMemberVal(memberId, val); + } + + gtError() << __func__ << tr("-> Cannot set parameter %1 of entry %2" + "in container %3").arg(memberId, entryId, id); + + return false; +} + #endif QList GtpyDecorator::findGtProperties(GtObject* obj) diff --git a/src/module/utilities/gtpy_decorator.h b/src/module/utilities/gtpy_decorator.h index 8014671..8f32162 100644 --- a/src/module/utilities/gtpy_decorator.h +++ b/src/module/utilities/gtpy_decorator.h @@ -423,13 +423,40 @@ public slots: * @param id of the property struct container * @param index of the element of the items of the container * @param memberId of the property to call - * @return the value as a variant - * + * @return the value as a variant - empty for invalid inputs * Example call in python: * task.getPropertyContainerVal("args_input", 0, "name") */ QVariant getPropertyContainerVal (GtObject* obj, const QString& id, - int index, const QString& memberId); + int index, const QString& memberId); + + /** + * @brief Find a struct container property and return a value of + * a defined property. This function uses the id string of the + * container element. + * + * @param obj - parent object of the struct propety + * @param id of the property struct container + * @param entryId - id of the entry to read the value from. + * @param memberId of the property to call + * @return the value as a variant - empty for invalid inputs + * Example call in python: + * task.getPropertyContainerVal("constraints", "minEta", "value") + */ + QVariant getPropertyContainerVal(GtObject* obj, const QString& id, + const QString& entryId, + const QString& memberId); + + /** + * @brief getPropertyContainerEntryIds + * Returns a list with all property entry ids of a given container element + * The container element is specified by its containerId + * @param obj - object which has the property container + * @param containerId - Id of the property container to evaluate + * @return a ist of all ids of the contained properties + */ + QStringList getPropertyContainerEntryIds(GtObject* obj, + const QString& containerId); /** * @brief setPropertyContainerVal @@ -444,6 +471,20 @@ public slots: */ bool setPropertyContainerVal(GtObject* obj, QString const& id, int index, QString const& memberId, const QVariant& val); + + /** + * @brief Find a struct container property and sets a value of + * a defined propety + * @param obj - parent object of the struct propety + * @param id of the property struct container + * @param entryId - Id of the container element to modify. + * @param memberId of the property to call + * @param val - value to set + * @return true in case of success, else false + */ + bool setPropertyContainerVal(GtObject* obj, QString const& id, + const QString& entryId, + QString const& memberId, const QVariant& val); #endif /** * @brief findGtProperties returns all properties of a GtObject diff --git a/src/module/utilities/gtpy_moduleupgrader.cpp b/src/module/utilities/gtpy_moduleupgrader.cpp new file mode 100644 index 0000000..e02928e --- /dev/null +++ b/src/module/utilities/gtpy_moduleupgrader.cpp @@ -0,0 +1,210 @@ +/* GTlab - Gas Turbine laboratory + * Source File: gtpy_code.h + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) + * + * Created on: 14.11.2025 + * Author: Jens Schmeink (DLR AT-TWK) + */ + +#include "gtpy_moduleupgrader.h" + +#include "gt_logging.h" +#include "gt_xmlexpr.h" + +namespace { + + +bool handleElement(const QDomNode& node, + const QStringList& classNames, + QList& results, + bool allowNestedClassElements) +{ + QDomElement elem = node.toElement(); + QString c = elem.attribute(gt::xml::S_CLASS_TAG); + + if (classNames.contains(c)) + { + results.append(elem); + return allowNestedClassElements; + } + return true; +} + + +// internal helper +void findElementsByClass(const QDomNode& node, + const QStringList& classNames, + QList& results, + bool allowNestedClassElements = false) +{ + if (node.isElement()) + { + if (!handleElement(node, classNames, results, allowNestedClassElements)) + { + return; + } + } + + QDomNode child = node.firstChild(); + while (!child.isNull()) + { + if (child.isElement()) + { + if (!handleElement(child, classNames, results, allowNestedClassElements)) + { + child = child.nextSibling(); + continue; + } + } + + findElementsByClass(child, classNames, results, allowNestedClassElements); + child = child.nextSibling(); + } +} + +/** + * @brief normalizePropertyContainerId + * Important change of the update is the replacement of the names + * of the property elements. + * Therefore a map is used to collect the old and new names of the entries. + * @param container - QDomElement of the container property + * @param formerNameKey - identifier if the property in the old structure + * @param replaceMap - map to collect the required renamings + */ +void normalizePropertyContainerId( + QDomElement const& container, QString const& formerNameKey, + QMap& replaceMap) +{ + QDomNode child = container.firstChild(); + + while (!child.isNull()) + { + if (child.isElement() == false) + { + child = child.nextSibling(); + continue; + } + + QDomElement prop = child.toElement(); + + if (prop.tagName() != gt::xml::S_PROPERTY_TAG) + { + child = child.nextSibling(); + continue; + } + + QString oldUUID = prop.attribute(gt::xml::S_NAME_TAG); + + if (oldUUID.isEmpty()) + { + child = child.nextSibling(); + continue; + } + + // Search for sub element NewName + QDomElement nameElem; + QDomNode sub = prop.firstChild(); + + while (!sub.isNull()) + { + QDomElement e = sub.toElement(); + if (!e.isNull() + && e.tagName() == gt::xml::S_PROPERTY_TAG + && e.attribute(gt::xml::S_NAME_TAG) == formerNameKey) + { + nameElem = e; + break; + } + sub = sub.nextSibling(); + } + + if (!nameElem.isNull()) + { + QString newName = nameElem.text().trimmed(); + + // keep connection info + replaceMap.insert(oldUUID, newName); + + // Replace UUID in name attribute + prop.setAttribute(gt::xml::S_NAME_TAG, newName); + + // Remove subelement + prop.removeChild(nameElem); + } + + child = child.nextSibling(); + } +} + +void replaceUUIDsInTextNodes(QDomNode node, + const QMap& replaceMap) +{ + QDomNode child = node.firstChild(); + + while (!child.isNull()) + { + + if (child.isText()) + { + QString txt = child.nodeValue(); + QString newTxt = txt; + + for (auto it = replaceMap.begin(); it != replaceMap.end(); ++it) + { + newTxt.replace(it.key(), it.value()); + } + + if (newTxt != txt) + child.setNodeValue(newTxt); + } + + replaceUUIDsInTextNodes(child, replaceMap); + child = child.nextSibling(); + } +} +} + +bool +gtpy::module_upgrader::to_2_0_0::run(QDomElement& root, + const QString& targetPath) +{ + // only do updates for process elements + if (!targetPath.endsWith(".gttask")) return true; + + if (root.isNull()) + { + gtError() << QObject::tr("Invalid .gttask file!"); + return false; + } + + QList found; + + // Step 1: Find elements with class=... + findElementsByClass(root, {"GtpyScriptCalculator", "GtpyTask"}, found); + + // Step 2: Replace UUIDs in input/output_args and build map + QMap replaceMap; + + for (QDomElement& elem : found) + { + QDomElement c = elem.firstChildElement(gt::xml::S_PROPERTYCONT_TAG); + while (!c.isNull()) + { + QString name = c.attribute(gt::xml::S_NAME_TAG); + + if (name == "input_args" || name == "output_args") + { + normalizePropertyContainerId(c, gt::xml::S_NAME_TAG, replaceMap); + } + + c = c.nextSiblingElement(gt::xml::S_PROPERTYCONT_TAG); + } + } + + // Step 3: Replace all uuids in the document + replaceUUIDsInTextNodes(root, replaceMap); + + return true; +} diff --git a/src/module/utilities/gtpy_moduleupgrader.h b/src/module/utilities/gtpy_moduleupgrader.h new file mode 100644 index 0000000..300d76d --- /dev/null +++ b/src/module/utilities/gtpy_moduleupgrader.h @@ -0,0 +1,31 @@ +/* GTlab - Gas Turbine laboratory + * Source File: gtpy_code.h + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) + * + * Created on: 14.11.2025 + * Author: Jens Schmeink (DLR AT-TWK) + */ +#ifndef GTPY_MODULEUPGRADER_H +#define GTPY_MODULEUPGRADER_H + +#include + +namespace gtpy { + +namespace module_upgrader { + +namespace to_2_0_0 { + /** + * @brief Performs the upgrade to predign data model 3.0.0 + * + * @return true in case of success + */ + bool run(QDomElement& root, const QString& targetPath); +} // to_2_0_0 +} // module_upgrader +} // gtpy + + +#endif // GTPY_MODULEUPGRADER_H diff --git a/src/module/utilities/gtpy_transfer.cpp b/src/module/utilities/gtpy_transfer.cpp index 7cf3154..0fad250 100644 --- a/src/module/utilities/gtpy_transfer.cpp +++ b/src/module/utilities/gtpy_transfer.cpp @@ -65,7 +65,11 @@ gtpy::transfer::propStructToPython( for (const auto& instance : container) { +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + auto name = instance.ident(); +#else auto name = instance.getMemberVal("name"); +#endif if (!name.isEmpty()) { @@ -92,8 +96,11 @@ gtpy::transfer::propStructFromPython( for (auto& entry : container) { +#if GT_VERSION >= GT_VERSION_CHECK(2, 1, 0) + auto argName = entry.ident(); +#else auto argName = entry.getMemberVal("name"); - +#endif auto iter = dict.find(argName); if (iter != dict.end()) {