Merge pull request #21427 from tritao/async-occt-progress

Part: Introduce `OCCTProgressIndicator` and port operations to support it.
This commit is contained in:
Kacper Donat
2025-06-02 18:04:38 +02:00
committed by GitHub
10 changed files with 353 additions and 8 deletions

View File

@@ -36,6 +36,7 @@
#include <Base/Observer.h>
#include <Base/Parameter.h>
#include <Base/ProgressIndicator.h>
// forward declarations
using PyObject = struct _object;
@@ -475,6 +476,9 @@ public:
bool hasLinksTo(const DocumentObject *obj) const;
//@}
/// Gets the base progress indicator instance.
Base::ProgressIndicator& getProgressIndicator() { return _progressIndicator; }
friend class App::Document;
protected:
@@ -662,6 +666,8 @@ private:
int _activeTransactionGuard{0};
bool _activeTransactionTmpName{false};
Base::ProgressIndicator _progressIndicator;
static Base::ConsoleObserverStd *_pConsoleObserverStd;
static Base::ConsoleObserverFile *_pConsoleObserverFile;
};

View File

@@ -205,6 +205,7 @@ SET(FreeCADBase_CPP_SRCS
Placement.cpp
PlacementPyImp.cpp
PrecisionPyImp.cpp
ProgressIndicator.cpp
ProgressIndicatorPy.cpp
PyExport.cpp
PyObjectBase.cpp
@@ -271,6 +272,7 @@ SET(FreeCADBase_HPP_SRCS
Placement.h
Precision.h
ProgressIndicatorPy.h
ProgressIndicator.h
PyExport.h
PyObjectBase.h
PyWrapParseTupleAndKeywords.h

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2025 James Stanley <james@incoherency.co.uk> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include "ProgressIndicator.h"
namespace Base
{
ProgressIndicator::ProgressIndicator() = default;
ProgressIndicator::ProgressIndicator(const ProgressIndicator&) = default;
ProgressIndicator& ProgressIndicator::operator=(const ProgressIndicator&) = default;
ProgressIndicator::~ProgressIndicator() = default;
} // namespace Base

View File

@@ -0,0 +1,97 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2025 James Stanley <james@incoherency.co.uk> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef BASE_PROGRESSINDICATOR_H
#define BASE_PROGRESSINDICATOR_H
#include "FCGlobal.h"
namespace Base
{
/**
* @class ProgressIndicator
* @brief Base interface for reporting and controlling long-running operations.
*
* This abstract base class provides a uniform way to:
* - Display progress updates via @c show().
* - Query whether the user has requested cancellation via @c userBreak().
*
* By default:
* - @c userBreak() returns false (no cancellation).
* - @c show() is a no-op.
*
* Derived classes should override these methods to integrate with
* specific UI frameworks or console output.
*/
class BaseExport ProgressIndicator
{
public:
/**
* @brief Flags to control behavior of the show() update.
*/
enum class ShowFlags
{
None = 0, ///< Default behavior; update only if significant progress.
Force = 1 ///< Force update regardless of progress threshold.
};
ProgressIndicator();
ProgressIndicator(const ProgressIndicator&);
ProgressIndicator(ProgressIndicator&&) = delete;
ProgressIndicator& operator=(const ProgressIndicator&);
ProgressIndicator& operator=(ProgressIndicator&&) = delete;
virtual ~ProgressIndicator();
/**
* @brief Check if the user has requested to abort the operation.
*
* Override to implement cancellation logic.
*
* @return @c true if the user requested a break, @c false otherwise.
*/
virtual bool userBreak()
{
return false;
}
/**
* @brief Update the progress display.
*
* This method is called whenever the progress position changes.
*
* The parameter `position` is a normalized progress value:
* - Range [0.0, 1.0] for determinate progress (e.g., 0.57 for 57% complete).
* - A value of -1.0 indicates infinite (indeterminate) progress.
*
* @param position Normalized progress value or -1.0 for infinite progress.
* @param isForce If true, indicates a forced update even if the change threshold (≈1%) hasnt
* been met.
*/
virtual void show([[maybe_unused]] float position,
[[maybe_unused]] ShowFlags flags = ShowFlags::None)
{}
};
} // namespace Base
#endif

View File

@@ -82,7 +82,14 @@ void FCBRepAlgoAPIHelper::setAutoFuzzy(BRepAlgoAPI_BuilderAlgo* op) {
}
void FCBRepAlgoAPI_BooleanOperation::Build() {
Message_ProgressRange progressRange;
Build(progressRange);
}
void FCBRepAlgoAPI_BooleanOperation::Build(const Message_ProgressRange& progressRange) {
if (progressRange.UserBreak()) {
Standard_ConstructionError::Raise("User aborted");
}
if (myOperation == BOPAlgo_CUT && myArguments.Size() == 1 && myTools.Size() == 1 && myTools.First().ShapeType() == TopAbs_COMPOUND) {
// cut argument and compound tool
TopTools_ListOfShape myOriginalArguments = myArguments;
@@ -98,7 +105,14 @@ void FCBRepAlgoAPI_BooleanOperation::Build() {
myArguments = myOriginalArguments;
} else {
#if OCC_VERSION_HEX >= 0x070600
BRepAlgoAPI_BooleanOperation::Build(progressRange);
#else
BRepAlgoAPI_BooleanOperation::Build();
#endif
}
if (progressRange.UserBreak()) {
Standard_ConstructionError::Raise("User aborted");
}
}

View File

@@ -28,7 +28,9 @@
#ifndef FCREPALGOAPIBOOLEANOPERATION_H
#define FCREPALGOAPIBOOLEANOPERATION_H
#include <BRepAlgoAPI_BooleanOperation.hxx>
#include <Message_ProgressRange.hxx>
class FCBRepAlgoAPIHelper
{
@@ -52,6 +54,12 @@ public:
// not an override - real Build() has optionals, sadly type of those optionals that are differs between OCCT versions
Standard_EXPORT virtual void Build(); // NOLINT(clang-diagnostic-overloaded-virtual, -Woverloaded-virtual)
#if OCC_VERSION_HEX >= 0x070600
Standard_EXPORT void Build(const Message_ProgressRange& progressRange) Standard_OVERRIDE;
#else
Standard_EXPORT virtual void Build(const Message_ProgressRange& progressRange);
#endif
protected: //! @name Constructors
//! Constructor to perform Boolean operation on only two arguments.

View File

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2025 James Stanley <james@incoherency.co.uk> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef PART_OCCTPROGRESSINDICATOR_H
#define PART_OCCTPROGRESSINDICATOR_H
#include <App/Application.h>
#include <Base/ProgressIndicator.h>
#include <Mod/Part/PartGlobal.h>
#include <Message_ProgressIndicator.hxx>
namespace Part {
class PartExport OCCTProgressIndicator : public Message_ProgressIndicator
{
Base::ProgressIndicator& baseIndicator;
public:
OCCTProgressIndicator(Base::ProgressIndicator& indicator)
: baseIndicator(indicator) {}
Standard_Boolean UserBreak() override {
return baseIndicator.userBreak();
}
void Show(const Message_ProgressScope& scope, const Standard_Boolean isForce) override {
float pos = -1; // negative means indeterminate
if (!scope.IsInfinite()) {
pos = static_cast<float>(GetPosition());
}
using ShowFlags = Base::ProgressIndicator::ShowFlags;
baseIndicator.show(pos, isForce ? ShowFlags::Force : ShowFlags::None);
}
static OCCTProgressIndicator getAppIndicator() {
return {App::GetApplication().getProgressIndicator()};
}
};
#if OCC_VERSION_HEX < 0x070600
// Stubs out OCCT Message_ProgressRange for OCCT versions below 7.5
class Message_ProgressRange
{
public:
bool UserBreak() { return false; }
void Show([[maybe_unused]] float position, [[maybe_unused]] bool isForce) {}
};
#endif
} // namespace Part
#endif // PART_OCCTPROGRESSINDICATOR_H

View File

@@ -189,7 +189,7 @@
#include "TopoShapeSolidPy.h"
#include "TopoShapeVertexPy.h"
#include "TopoShapeWirePy.h"
#include "OCCTProgressIndicator.h"
FC_LOG_LEVEL_INIT("TopoShape",true,true)
@@ -1695,7 +1695,11 @@ TopoDS_Shape TopoShape::cut(const std::vector<TopoDS_Shape>& shapes, Standard_Re
} else if (tolerance < 0.0) {
mkCut.setAutoFuzzy();
}
#if OCC_VERSION_HEX >= 0x070600
mkCut.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkCut.Build();
#endif
if (!mkCut.IsDone())
throw Base::RuntimeError("Multi cut failed");
@@ -1734,7 +1738,11 @@ TopoDS_Shape TopoShape::common(const std::vector<TopoDS_Shape>& shapes, Standard
} else if (tolerance < 0.0) {
mkCommon.setAutoFuzzy();
}
#if OCC_VERSION_HEX >= 0x070600
mkCommon.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkCommon.Build();
#endif
if (!mkCommon.IsDone())
throw Base::RuntimeError("Multi common failed");
@@ -1773,7 +1781,11 @@ TopoDS_Shape TopoShape::fuse(const std::vector<TopoDS_Shape>& shapes, Standard_R
} else if (tolerance < 0.0) {
mkFuse.setAutoFuzzy();
}
#if OCC_VERSION_HEX >= 0x070600
mkFuse.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkFuse.Build();
#endif
if (!mkFuse.IsDone())
throw Base::RuntimeError("Multi fuse failed");
@@ -1792,7 +1804,11 @@ TopoDS_Shape TopoShape::section(TopoDS_Shape shape, Standard_Boolean approximate
mkSection.Init1(this->_Shape);
mkSection.Init2(shape);
mkSection.Approximation(approximate);
#if OCC_VERSION_HEX >= 0x070600
mkSection.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkSection.Build();
#endif
if (!mkSection.IsDone())
throw Base::RuntimeError("Section failed");
return mkSection.Shape();
@@ -1823,7 +1839,11 @@ TopoDS_Shape TopoShape::section(const std::vector<TopoDS_Shape>& shapes,
} else if (tolerance < 0.0) {
mkSection.setAutoFuzzy();
}
#if OCC_VERSION_HEX >= 0x070600
mkSection.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkSection.Build();
#endif
if (!mkSection.IsDone())
throw Base::RuntimeError("Multi section failed");
@@ -1886,7 +1906,11 @@ TopoDS_Shape TopoShape::generalFuse(const std::vector<TopoDS_Shape> &sOthers, St
FCBRepAlgoAPIHelper::setAutoFuzzy(&mkGFA);
}
mkGFA.SetNonDestructive(Standard_True);
#if OCC_VERSION_HEX >= 0x070600
mkGFA.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkGFA.Build();
#endif
if (!mkGFA.IsDone())
throw BooleanException("MultiFusion failed");
TopoDS_Shape resShape = mkGFA.Shape();
@@ -1940,8 +1964,11 @@ TopoDS_Shape TopoShape::makePipeShell(const TopTools_ListOfShape& profiles,
if (!mkPipeShell.IsReady())
throw Standard_Failure("shape is not ready to build");
#if OCC_VERSION_HEX >= 0x070600
mkPipeShell.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkPipeShell.Build();
#endif
if (make_solid)
mkPipeShell.MakeSolid();
@@ -2431,7 +2458,11 @@ TopoDS_Shape TopoShape::makeLoft(const TopTools_ListOfShape& profiles,
Standard_Boolean anIsCheck = Standard_True;
aGenerator.CheckCompatibility (anIsCheck); // use BRepFill_CompatibleWires on profiles. force #edges, orientation, "origin" to match.
#if OCC_VERSION_HEX >= 0x070600
aGenerator.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
aGenerator.Build();
#endif
if (!aGenerator.IsDone())
Standard_Failure::Raise("Failed to create loft face");
@@ -2573,7 +2604,11 @@ TopoDS_Shape TopoShape::makeOffsetShape(double offset, double tol, bool intersec
BRepOffsetAPI_ThruSections aGenerator;
aGenerator.AddWire(originalWire);
aGenerator.AddWire(offsetWire);
#if OCC_VERSION_HEX >= 0x070600
aGenerator.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
aGenerator.Build();
#endif
if (!aGenerator.IsDone())
{
Standard_Failure::Raise("ThruSections failed");
@@ -2880,8 +2915,11 @@ TopoDS_Shape TopoShape::makeOffset2D(double offset, short joinType, bool fill, b
//add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge());
#if OCC_VERSION_HEX >= 0x070600
mkWire.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkWire.Build();
#endif
wiresForMakingFaces.push_front(mkWire.Wire());
}
}
@@ -2893,7 +2931,11 @@ TopoDS_Shape TopoShape::makeOffset2D(double offset, short joinType, bool fill, b
for(TopoDS_Wire &w : wiresForMakingFaces){
mkFace.addWire(w);
}
#if OCC_VERSION_HEX >= 0x070600
mkFace.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkFace.Build();
#endif
if (mkFace.Shape().IsNull())
throw Base::CADKernelError("makeOffset2D: making face failed (null shape returned).");
TopoDS_Shape result = mkFace.Shape();
@@ -3633,7 +3675,11 @@ TopoDS_Shape TopoShape::defeaturing(const std::vector<TopoDS_Shape>& s) const
defeat.SetShape(this->_Shape);
for (const auto & it : s)
defeat.AddFaceToRemove(it);
#if OCC_VERSION_HEX >= 0x070600
defeat.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
defeat.Build();
#endif
if (!defeat.IsDone()) {
// error treatment
Standard_SStream aSStream;
@@ -3854,7 +3900,11 @@ TopoShape &TopoShape::makeFace(const std::vector<TopoShape> &shapes, const char
else if (s.getShape().ShapeType() != TopAbs_VERTEX)
mkFace->addShape(s.getShape());
}
#if OCC_VERSION_HEX >= 0x070600
mkFace->Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkFace->Build();
#endif
_Shape = mkFace->Shape();
return *this;
}

View File

@@ -95,8 +95,10 @@
#include "FaceMaker.h"
#include "Geometry.h"
#include "BRepOffsetAPI_MakeOffsetFix.h"
#include "Base/Tools.h"
#include "Base/BoundBox.h"
#include "Base/Exception.h"
#include "Base/Tools.h"
#include "OCCTProgressIndicator.h"
#include <App/ElementMap.h>
#include <App/ElementNamingUtils.h>
@@ -2428,7 +2430,11 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector<TopoShape>& shapes,
FC_THROWM(Base::CADKernelError, "shape is not ready to build");
}
else {
#if OCC_VERSION_HEX >= 0x070600
mkPipeShell.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkPipeShell.Build();
#endif
}
if (make_solid == MakeSolid::makeSolid) {
@@ -2534,7 +2540,11 @@ TopoShape& TopoShape::makeElementOffset(const TopoShape& shape,
BRepOffsetAPI_ThruSections aGenerator;
aGenerator.AddWire(TopoDS::Wire(originalWire.getShape()));
aGenerator.AddWire(offsetWire);
#if OCC_VERSION_HEX >= 0x070600
aGenerator.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
aGenerator.Build();
#endif
if (!aGenerator.IsDone()) {
FC_THROWM(Base::CADKernelError, "ThruSections failed");
}
@@ -2949,8 +2959,11 @@ TopoShape& TopoShape::makeElementOffset2D(const TopoShape& shape,
}
// add final joining edge
mkWire.Add(BRepBuilderAPI_MakeEdge(v3, v1).Edge());
#if OCC_VERSION_HEX >= 0x070600
mkWire.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkWire.Build();
#endif
wiresForMakingFaces.push_back(
TopoShape(Tag, Hasher).makeElementShape(mkWire, openWires, op));
@@ -3820,7 +3833,11 @@ TopoShape& TopoShape::makeElementFilledFace(const std::vector<TopoShape>& _shape
}
}
#if OCC_VERSION_HEX >= 0x070600
maker.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
maker.Build();
#endif
if (!maker.IsDone()) {
FC_THROWM(Base::CADKernelError, "Failed to created face by filling edges");
}
@@ -4089,7 +4106,11 @@ TopoShape& TopoShape::makeElementGeneralFuse(const std::vector<TopoShape>& _shap
FCBRepAlgoAPIHelper::setAutoFuzzy(&mkGFA);
}
mkGFA.SetNonDestructive(Standard_True);
#if OCC_VERSION_HEX >= 0x070600
mkGFA.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkGFA.Build();
#endif
if (!mkGFA.IsDone()) {
FC_THROWM(Base::CADKernelError, "GeneralFuse failed");
}
@@ -4228,7 +4249,11 @@ TopoShape& TopoShape::makeElementLoft(const std::vector<TopoShape>& shapes,
aGenerator.CheckCompatibility(anIsCheck); // use BRepFill_CompatibleWires on profiles. force
// #edges, orientation, "origin" to match.
#if OCC_VERSION_HEX >= 0x070600
aGenerator.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
aGenerator.Build();
#endif
return makeShapeWithElementMap(aGenerator.Shape(),
MapperThruSections(aGenerator, profiles),
shapes,
@@ -4555,7 +4580,11 @@ TopoShape& TopoShape::makeElementDraft(const TopoShape& shape,
}
} while (retry && !done);
#if OCC_VERSION_HEX >= 0x070600
mkDraft.Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkDraft.Build();
#endif
return makeElementShape(mkDraft, shape, op);
}
@@ -4600,7 +4629,11 @@ TopoShape& TopoShape::makeElementFace(const std::vector<TopoShape>& shapes,
mkFace->addTopoShape(shape);
}
}
#if OCC_VERSION_HEX >= 0x070600
mkFace->Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mkFace->Build();
#endif
const auto& ret = mkFace->getTopoShape();
setShape(ret._Shape);
@@ -5587,6 +5620,10 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker,
FC_THROWM(NullShapeException, "Null shape");
}
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
FC_THROWM(Base::CADKernelError, "User aborted");
}
if (strcmp(maker, Part::OpCodes::Compound) == 0) {
return makeElementCompound(shapes, op, SingleShapeCompoundCreationPolicy::returnShape);
}
@@ -5750,7 +5787,14 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker,
} else if (tolerance < 0.0) {
FCBRepAlgoAPIHelper::setAutoFuzzy(mk.get());
}
#if OCC_VERSION_HEX >= 0x070600
mk->Build(OCCTProgressIndicator::getAppIndicator().Start());
#else
mk->Build();
#endif
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
FC_THROWM(Base::CADKernelError, "User aborted");
}
makeElementShape(*mk, inputs, op);
if (buildShell) {

View File

@@ -37,6 +37,7 @@
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/ProgressIndicator.h>
#include <Base/Reader.h>
#include <Mod/Part/App/modelRefine.h>
@@ -49,12 +50,14 @@
#include "FeaturePolarPattern.h"
#include "FeatureSketchBased.h"
#include "Mod/Part/App/TopoShapeOpCode.h"
#include "Mod/Part/App/OCCTProgressIndicator.h"
using namespace PartDesign;
namespace PartDesign
{
using Part::OCCTProgressIndicator;
extern bool getPDRefineModelParameter();
PROPERTY_SOURCE(PartDesign::Transformed, PartDesign::FeatureRefine)
@@ -279,6 +282,9 @@ App::DocumentObjectExecReturn* Transformed::execute()
auto transformIter = transformations.cbegin();
transformIter++;
for ( ; transformIter != transformations.end(); transformIter++) {
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
return std::vector<TopoShape>();
}
auto opName = Data::indexSuffix(idx++);
shapes.emplace_back(shape.makeElementTransform(*transformIter, opName.c_str()));
}
@@ -319,15 +325,27 @@ App::DocumentObjectExecReturn* Transformed::execute()
cutShape = cutShape.makeElementTransform(trsf);
}
if (!fuseShape.isNull()) {
supportShape.makeElementFuse(getTransformedCompShape(supportShape, fuseShape));
auto shapes = getTransformedCompShape(supportShape, fuseShape);
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
return new App::DocumentObjectExecReturn("User aborted");
}
supportShape.makeElementFuse(shapes);
}
if (!cutShape.isNull()) {
supportShape.makeElementCut(getTransformedCompShape(supportShape, cutShape));
auto shapes = getTransformedCompShape(supportShape, cutShape);
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
return new App::DocumentObjectExecReturn("User aborted");
}
supportShape.makeElementCut(shapes);
}
}
break;
case Mode::TransformBody: {
supportShape.makeElementFuse(getTransformedCompShape(supportShape, supportShape));
auto shapes = getTransformedCompShape(supportShape, supportShape);
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
return new App::DocumentObjectExecReturn("User aborted");
}
supportShape.makeElementFuse(shapes);
break;
}
}