From 8cfc3f2475c73a622a8b2ff7152b304df39d36b4 Mon Sep 17 00:00:00 2001 From: tetektoza Date: Mon, 1 Sep 2025 03:12:22 +0200 Subject: [PATCH] Merge pull request #23192 from tetektoza/fix/22253_fix_losing_expression_during_sketcher_tools_usage Sketcher: Copy expressions when rotating/moving geometry --- src/Mod/Sketcher/Gui/CMakeLists.txt | 2 + .../Sketcher/Gui/DrawSketchHandlerRotate.h | 11 ++ src/Mod/Sketcher/Gui/DrawSketchHandlerScale.h | 1 + .../Sketcher/Gui/DrawSketchHandlerTranslate.h | 13 ++ ...SketcherTransformationExpressionHelper.cpp | 174 ++++++++++++++++++ .../SketcherTransformationExpressionHelper.h | 107 +++++++++++ 6 files changed, 308 insertions(+) create mode 100644 src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.cpp create mode 100644 src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.h diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index f5a3d0dd37..6954c4cfb5 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -112,6 +112,8 @@ SET(SketcherGui_SRCS TaskSketcherValidation.h ShortcutListener.cpp ShortcutListener.h + SketcherTransformationExpressionHelper.cpp + SketcherTransformationExpressionHelper.h EditModeInformationOverlayCoinConverter.cpp EditModeInformationOverlayCoinConverter.h EditModeGeometryCoinConverter.cpp diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerRotate.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerRotate.h index a9632f540b..7872a27dc2 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerRotate.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerRotate.h @@ -33,6 +33,7 @@ #include "DrawSketchDefaultWidgetController.h" #include "DrawSketchControllableHandler.h" +#include "SketcherTransformationExpressionHelper.h" #include "GeometryCreationMode.h" #include "Utils.h" @@ -143,10 +144,18 @@ private: try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Rotate geometries")); + expressionHelper.storeOriginalExpressions(sketchgui->getSketchObject(), listOfGeoIds); + createShape(false); commandAddShapeGeometryAndConstraints(); + expressionHelper.copyExpressionsToNewConstraints(sketchgui->getSketchObject(), + listOfGeoIds, + ShapeGeometry.size(), + numberOfCopies, + 1); + if (deleteOriginal) { deleteOriginalGeos(); } @@ -237,6 +246,8 @@ private: double length, startAngle, endAngle, totalAngle, individualAngle; int numberOfCopies; + SketcherTransformationExpressionHelper expressionHelper; + void deleteOriginalGeos() { std::stringstream stream; diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerScale.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerScale.h index c2fd018445..52745dea3b 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerScale.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerScale.h @@ -36,6 +36,7 @@ #include "DrawSketchDefaultWidgetController.h" #include "DrawSketchControllableHandler.h" +#include "SketcherTransformationExpressionHelper.h" #include "GeometryCreationMode.h" #include "Utils.h" diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerTranslate.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerTranslate.h index ab26da8c6a..6d2b514016 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerTranslate.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerTranslate.h @@ -25,6 +25,7 @@ #define SKETCHERGUI_DrawSketchHandlerTranslate_H #include +#include #include @@ -38,6 +39,7 @@ #include "DrawSketchDefaultWidgetController.h" #include "DrawSketchControllableHandler.h" +#include "SketcherTransformationExpressionHelper.h" #include "GeometryCreationMode.h" #include "Utils.h" @@ -115,10 +117,18 @@ private: try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Translate geometries")); + expressionHelper.storeOriginalExpressions(sketchgui->getSketchObject(), listOfGeoIds); + createShape(false); commandAddShapeGeometryAndConstraints(); + expressionHelper.copyExpressionsToNewConstraints(sketchgui->getSketchObject(), + listOfGeoIds, + ShapeGeometry.size(), + numberOfCopies, + secondNumberOfCopies); + if (deleteOriginal) { deleteOriginalGeos(); } @@ -227,6 +237,8 @@ private: bool deleteOriginal, cloneConstraints; int numberOfCopies, secondNumberOfCopies; + SketcherTransformationExpressionHelper expressionHelper; + void deleteOriginalGeos() { std::stringstream stream; @@ -427,6 +439,7 @@ private: } } + public: std::list getToolHints() const override { diff --git a/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.cpp b/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.cpp new file mode 100644 index 0000000000..34815a3fd9 --- /dev/null +++ b/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.cpp @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 The FreeCAD Project Association AISBL * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#include "SketcherTransformationExpressionHelper.h" + +#include +#include + +using namespace SketcherGui; + +void SketcherTransformationExpressionHelper::storeOriginalExpressions( + Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds) +{ + if (!sketchObject) { + return; + } + + const std::vector& vals = sketchObject->Constraints.getValues(); + + originalExpressions.clear(); + + for (auto& geoId : listOfGeoIds) { + for (size_t i = 0; i < vals.size(); i++) { + const auto& cstr = vals[i]; + if (cstr->isDriving && cstr->isDimensional() + && (cstr->First == geoId + || (cstr->Second == geoId && cstr->Type != Sketcher::Radius + && cstr->Type != Sketcher::Diameter && cstr->Type != Sketcher::Weight))) { + + App::ObjectIdentifier spath = + sketchObject->Constraints.createPath(static_cast(i)); + App::PropertyExpressionEngine::ExpressionInfo expr_info = + sketchObject->getExpression(spath); + + if (expr_info.expression) { + // map expression to geoid as a key + originalExpressions[geoId] = + std::shared_ptr(expr_info.expression->copy()); + } + } + } + } +} + +void SketcherTransformationExpressionHelper::copyExpressionsToNewConstraints( + Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds, + size_t shapeGeometrySize, + int numberOfCopies, + int secondNumberOfCopies) +{ + // apply stored expressions to new constraints, but bail out if we haven't stored anything + if (originalExpressions.empty() || !sketchObject) { + return; + } + + std::string sketchObj = Gui::Command::getObjectCmd(sketchObject); + const std::vector& vals = sketchObject->Constraints.getValues(); + + CopyCalculationParams params = + calculateCopyParams(sketchObject, listOfGeoIds, shapeGeometrySize, numberOfCopies); + for (size_t i = 0; i < vals.size(); i++) { + const auto& cstr = vals[i]; + if (!cstr->isDriving || !cstr->isDimensional()) { + continue; + } + + // try to find and apply a matching expression for this constraint + bool expressionApplied = false; + for (const auto& exprPair : originalExpressions) { + int originalGeoId = exprPair.first; + int originalIndex = indexOfGeoId(listOfGeoIds, originalGeoId); + + if (originalIndex >= 0) { + expressionApplied = tryApplyExpressionToConstraint(cstr, + i, + originalIndex, + params, + secondNumberOfCopies, + exprPair.second, + sketchObj); + + if (expressionApplied) { + break; + } + } + } + } +} + +void SketcherTransformationExpressionHelper::clear() +{ + originalExpressions.clear(); +} + +bool SketcherTransformationExpressionHelper::hasStoredExpressions() const +{ + return !originalExpressions.empty(); +} + +SketcherTransformationExpressionHelper::CopyCalculationParams +SketcherTransformationExpressionHelper::calculateCopyParams(Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds, + size_t shapeGeometrySize, + int numberOfCopies) const +{ + CopyCalculationParams params; + params.firstCurveCreated = + sketchObject->getHighestCurveIndex() + 1 - static_cast(shapeGeometrySize); + params.size = static_cast(listOfGeoIds.size()); + params.numberOfCopiesToMake = numberOfCopies == 0 ? 1 : numberOfCopies; + return params; +} + +bool SketcherTransformationExpressionHelper::tryApplyExpressionToConstraint( + const Sketcher::Constraint* cstr, + size_t constraintIndex, + int originalIndex, + const CopyCalculationParams& params, + int secondNumberOfCopies, + const std::shared_ptr& expression, + const std::string& sketchObj) const +{ + // check all copies of this geometry as we assign them the same expression + for (int k = 0; k < secondNumberOfCopies; k++) { + for (int copy = 1; copy <= params.numberOfCopiesToMake; copy++) { + int expectedNewGeoId = params.firstCurveCreated + originalIndex + + params.size * (copy - 1) + params.size * params.numberOfCopiesToMake * k; + + // if this constraint references our copied geometry, apply the expression + if (constraintReferencesGeometry(cstr, expectedNewGeoId)) { + Gui::Command::doCommand(Gui::Command::Doc, + "%s.setExpression('Constraints[%d]', '%s')", + sketchObj.c_str(), + static_cast(constraintIndex), + expression->toString().c_str()); + return true; + } + } + } + return false; +} + +bool SketcherTransformationExpressionHelper::constraintReferencesGeometry( + const Sketcher::Constraint* cstr, + int geoId) const +{ + return cstr->First == geoId + || (cstr->Second == geoId && cstr->Type != Sketcher::Radius + && cstr->Type != Sketcher::Diameter && cstr->Type != Sketcher::Weight); +} diff --git a/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.h b/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.h new file mode 100644 index 0000000000..f8ae8d9dfb --- /dev/null +++ b/src/Mod/Sketcher/Gui/SketcherTransformationExpressionHelper.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 The FreeCAD Project Association AISBL * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef SKETCHERGUI_SketcherTransformationExpressionHelper_H +#define SKETCHERGUI_SketcherTransformationExpressionHelper_H + +#include +#include +#include + +#include +#include +#include + +#include "Utils.h" + +namespace SketcherGui +{ + +/** @brief Helper class for preserving expressions during sketch transformations + * + * This class provides functionality to preserve expressions when transforming + * sketch geometries (translate, rotate, scale, etc.). It stores expressions + * from original constraints and applies them to new constraints after transformation. + */ +class SketcherGuiExport SketcherTransformationExpressionHelper +{ +public: + /** @brief Store expressions from constraints affecting the given geometry list + * + * @param sketchObject The sketch object containing the constraints + * @param listOfGeoIds List of geometry IDs that are being transformed + */ + void storeOriginalExpressions(Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds); + + /** @brief Apply stored expressions to new constraints after transformation + * + * @param sketchObject The sketch object containing the new constraints + * @param listOfGeoIds Original list of geometry IDs that were transformed + * @param shapeGeometrySize Number of new geometries created + * @param numberOfCopies Number of copies made (0 means single operation on the original + * geometry) + * @param secondNumberOfCopies Number of rows for array operations + */ + void copyExpressionsToNewConstraints(Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds, + size_t shapeGeometrySize, + int numberOfCopies, + int secondNumberOfCopies); + + void clear(); + bool hasStoredExpressions() const; + +private: + struct CopyCalculationParams + { + int firstCurveCreated; + int size; + int numberOfCopiesToMake; + }; + + /// calculate parameters needed for copy operations + CopyCalculationParams calculateCopyParams(Sketcher::SketchObject* sketchObject, + const std::vector& listOfGeoIds, + size_t shapeGeometrySize, + int numberOfCopies) const; + + /// try to apply an expression to a constraint if it matches copied geometry + bool tryApplyExpressionToConstraint(const Sketcher::Constraint* cstr, + size_t constraintIndex, + int originalIndex, + const CopyCalculationParams& params, + int secondNumberOfCopies, + const std::shared_ptr& expression, + const std::string& sketchObj) const; + + /// check if a constraint references the specified geometry ID + bool constraintReferencesGeometry(const Sketcher::Constraint* cstr, int geoId) const; + + // original geo id to expression mapping + std::map> originalExpressions; +}; + +} // namespace SketcherGui + +#endif // SKETCHERGUI_SketcherTransformationExpressionHelper_H