From 6751772840f8e9ba68d34b69d4ee159334dfbdb6 Mon Sep 17 00:00:00 2001 From: jfa Date: Sat, 31 Aug 2024 01:47:48 +0100 Subject: [PATCH] [bos #40618] [CEA] Offset/Thickness Feature --- src/FeaturesAPI/CMakeLists.txt | 4 + src/FeaturesAPI/FeaturesAPI.i | 4 + src/FeaturesAPI/FeaturesAPI_Offset.cpp | 121 +++++++++++++ src/FeaturesAPI/FeaturesAPI_Offset.h | 102 +++++++++++ src/FeaturesAPI/FeaturesAPI_Thickness.cpp | 129 +++++++++++++ src/FeaturesAPI/FeaturesAPI_Thickness.h | 104 +++++++++++ src/FeaturesAPI/FeaturesAPI_swig.h | 2 + src/FeaturesPlugin/CMakeLists.txt | 6 + src/FeaturesPlugin/FeaturesPlugin_Offset.cpp | 118 ++++++++++++ src/FeaturesPlugin/FeaturesPlugin_Offset.h | 107 +++++++++++ src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp | 10 ++ .../FeaturesPlugin_Thickness.cpp | 131 ++++++++++++++ src/FeaturesPlugin/FeaturesPlugin_Thickness.h | 111 ++++++++++++ .../FeaturesPlugin_Validators.cpp | 155 ++++++++++++++++ .../FeaturesPlugin_Validators.h | 32 ++++ src/FeaturesPlugin/Test/TestOffset.py | 82 +++++++++ src/FeaturesPlugin/Test/TestThickness.py | 83 +++++++++ src/FeaturesPlugin/doc/FeaturesPlugin.rst | 2 + src/FeaturesPlugin/doc/TUI_offsetFeature.rst | 11 ++ .../doc/TUI_thicknessFeature.rst | 11 ++ src/FeaturesPlugin/doc/examples/offset.py | 30 ++++ src/FeaturesPlugin/doc/examples/thickness.py | 34 ++++ src/FeaturesPlugin/doc/images/offset.png | Bin 0 -> 1006 bytes .../doc/images/offsetPartialPropertyPanel.png | Bin 0 -> 2751 bytes .../doc/images/offsetPropertyPanel.png | Bin 0 -> 2718 bytes .../doc/images/offset_partial.png | Bin 0 -> 870 bytes .../doc/images/offset_partial_result.png | Bin 0 -> 9582 bytes .../doc/images/offset_result.png | Bin 0 -> 12215 bytes src/FeaturesPlugin/doc/images/thickness.png | Bin 0 -> 471 bytes src/FeaturesPlugin/doc/images/thickness2.png | Bin 0 -> 798 bytes .../doc/images/thicknessPropertyPanel.png | Bin 0 -> 3581 bytes .../doc/images/thicknessPropertyPanel2.png | Bin 0 -> 4197 bytes .../doc/images/thickness_result.png | Bin 0 -> 3962 bytes .../doc/images/thickness_result2.png | Bin 0 -> 7627 bytes src/FeaturesPlugin/doc/offsetFeature.rst | 108 +++++++++++ src/FeaturesPlugin/doc/thicknessFeature.rst | 100 +++++++++++ src/FeaturesPlugin/icons/offset.png | Bin 0 -> 1006 bytes src/FeaturesPlugin/icons/offset_partial.png | Bin 0 -> 870 bytes src/FeaturesPlugin/icons/thickness.png | Bin 0 -> 471 bytes src/FeaturesPlugin/icons/thickness2.png | Bin 0 -> 798 bytes src/FeaturesPlugin/offset_widget.xml | 33 ++++ src/FeaturesPlugin/plugin-Features.xml | 8 + src/FeaturesPlugin/tests.set | 2 + src/FeaturesPlugin/thickness_widget.xml | 44 +++++ src/GeomAlgoAPI/CMakeLists.txt | 7 +- src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.cpp | 19 ++ src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.h | 3 +- src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp | 138 ++++++++++++-- src/GeomAlgoAPI/GeomAlgoAPI_Offset.h | 47 ++++- src/GeomAlgoAPI/GeomAlgoAPI_Thickness.cpp | 170 ++++++++++++++++++ src/GeomAlgoAPI/GeomAlgoAPI_Thickness.h | 65 +++++++ src/PythonAPI/model/features/__init__.py | 1 + src/SketchPlugin/Test/TestOffset1.py | 2 +- 53 files changed, 2112 insertions(+), 24 deletions(-) create mode 100644 src/FeaturesAPI/FeaturesAPI_Offset.cpp create mode 100644 src/FeaturesAPI/FeaturesAPI_Offset.h create mode 100644 src/FeaturesAPI/FeaturesAPI_Thickness.cpp create mode 100644 src/FeaturesAPI/FeaturesAPI_Thickness.h create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Offset.cpp create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Offset.h create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Thickness.cpp create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Thickness.h create mode 100644 src/FeaturesPlugin/Test/TestOffset.py create mode 100644 src/FeaturesPlugin/Test/TestThickness.py create mode 100644 src/FeaturesPlugin/doc/TUI_offsetFeature.rst create mode 100644 src/FeaturesPlugin/doc/TUI_thicknessFeature.rst create mode 100644 src/FeaturesPlugin/doc/examples/offset.py create mode 100644 src/FeaturesPlugin/doc/examples/thickness.py create mode 100644 src/FeaturesPlugin/doc/images/offset.png create mode 100644 src/FeaturesPlugin/doc/images/offsetPartialPropertyPanel.png create mode 100644 src/FeaturesPlugin/doc/images/offsetPropertyPanel.png create mode 100644 src/FeaturesPlugin/doc/images/offset_partial.png create mode 100644 src/FeaturesPlugin/doc/images/offset_partial_result.png create mode 100644 src/FeaturesPlugin/doc/images/offset_result.png create mode 100644 src/FeaturesPlugin/doc/images/thickness.png create mode 100644 src/FeaturesPlugin/doc/images/thickness2.png create mode 100644 src/FeaturesPlugin/doc/images/thicknessPropertyPanel.png create mode 100644 src/FeaturesPlugin/doc/images/thicknessPropertyPanel2.png create mode 100644 src/FeaturesPlugin/doc/images/thickness_result.png create mode 100644 src/FeaturesPlugin/doc/images/thickness_result2.png create mode 100644 src/FeaturesPlugin/doc/offsetFeature.rst create mode 100644 src/FeaturesPlugin/doc/thicknessFeature.rst create mode 100644 src/FeaturesPlugin/icons/offset.png create mode 100644 src/FeaturesPlugin/icons/offset_partial.png create mode 100644 src/FeaturesPlugin/icons/thickness.png create mode 100644 src/FeaturesPlugin/icons/thickness2.png create mode 100644 src/FeaturesPlugin/offset_widget.xml create mode 100644 src/FeaturesPlugin/thickness_widget.xml create mode 100644 src/GeomAlgoAPI/GeomAlgoAPI_Thickness.cpp create mode 100644 src/GeomAlgoAPI/GeomAlgoAPI_Thickness.h diff --git a/src/FeaturesAPI/CMakeLists.txt b/src/FeaturesAPI/CMakeLists.txt index 4652651c9..7c4224928 100644 --- a/src/FeaturesAPI/CMakeLists.txt +++ b/src/FeaturesAPI/CMakeLists.txt @@ -36,9 +36,11 @@ SET(PROJECT_HEADERS FeaturesAPI_NormalToFace.h FeaturesAPI_MultiRotation.h FeaturesAPI_MultiTranslation.h + FeaturesAPI_Offset.h FeaturesAPI_Partition.h FeaturesAPI_Pipe.h FeaturesAPI_Loft.h + FeaturesAPI_Thickness.h FeaturesAPI_Placement.h FeaturesAPI_PointCloudOnFace.h FeaturesAPI_Recover.h @@ -79,9 +81,11 @@ SET(PROJECT_SOURCES FeaturesAPI_NormalToFace.cpp FeaturesAPI_MultiRotation.cpp FeaturesAPI_MultiTranslation.cpp + FeaturesAPI_Offset.cpp FeaturesAPI_Partition.cpp FeaturesAPI_Pipe.cpp FeaturesAPI_Loft.cpp + FeaturesAPI_Thickness.cpp FeaturesAPI_Placement.cpp FeaturesAPI_PointCloudOnFace.cpp FeaturesAPI_Recover.cpp diff --git a/src/FeaturesAPI/FeaturesAPI.i b/src/FeaturesAPI/FeaturesAPI.i index d482e844e..9e0c4e8f2 100644 --- a/src/FeaturesAPI/FeaturesAPI.i +++ b/src/FeaturesAPI/FeaturesAPI.i @@ -85,6 +85,7 @@ %shared_ptr(FeaturesAPI_MultiRotation) %shared_ptr(FeaturesAPI_MultiTranslation) %shared_ptr(FeaturesAPI_NormalToFace) +%shared_ptr(FeaturesAPI_Offset) %shared_ptr(FeaturesAPI_Partition) %shared_ptr(FeaturesAPI_Pipe) %shared_ptr(FeaturesAPI_Placement) @@ -101,6 +102,7 @@ %shared_ptr(FeaturesAPI_Sewing) %shared_ptr(FeaturesAPI_SharedFaces) %shared_ptr(FeaturesAPI_Symmetry) +%shared_ptr(FeaturesAPI_Thickness) %shared_ptr(FeaturesAPI_Translation) %shared_ptr(FeaturesAPI_Union) @@ -233,6 +235,7 @@ %include "FeaturesAPI_NormalToFace.h" %include "FeaturesAPI_MultiRotation.h" %include "FeaturesAPI_MultiTranslation.h" +%include "FeaturesAPI_Offset.h" %include "FeaturesAPI_Partition.h" %include "FeaturesAPI_Pipe.h" %include "FeaturesAPI_Placement.h" @@ -248,5 +251,6 @@ %include "FeaturesAPI_Sewing.h" %include "FeaturesAPI_SharedFaces.h" %include "FeaturesAPI_Symmetry.h" +%include "FeaturesAPI_Thickness.h" %include "FeaturesAPI_Translation.h" %include "FeaturesAPI_Union.h" diff --git a/src/FeaturesAPI/FeaturesAPI_Offset.cpp b/src/FeaturesAPI/FeaturesAPI_Offset.cpp new file mode 100644 index 000000000..2d7446ae3 --- /dev/null +++ b/src/FeaturesAPI/FeaturesAPI_Offset.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2017-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "FeaturesAPI_Offset.h" + +#include +#include +#include +#include + +//================================================================================================== + +FeaturesAPI_Offset::FeaturesAPI_Offset(const std::shared_ptr& theFeature) + : ModelHighAPI_Interface(theFeature) +{ + initialize(); +} + +FeaturesAPI_Offset::FeaturesAPI_Offset(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const bool isPipeJoint) + : FeaturesAPI_Offset(theFeature) +{ + if (initialize()) { + fillAttribute(FeaturesPlugin_Offset::CREATION_METHOD_EQUAL(), mycreationMethod); + fillAttribute(theBaseObject, mybaseObject); + fillAttribute(theOffset, myoffsetValue); + fillAttribute(isPipeJoint, myisPipeJoint); + + execute(); + } +} + +FeaturesAPI_Offset::FeaturesAPI_Offset(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const std::list& theFaces) + : FeaturesAPI_Offset(theFeature) +{ + if (initialize()) { + fillAttribute(FeaturesPlugin_Offset::CREATION_METHOD_PARTIAL(), mycreationMethod); + fillAttribute(theBaseObject, mybaseObject); + fillAttribute(theOffset, myoffsetValue); + fillAttribute(theFaces, myfaces); + + execute(); + } +} + +FeaturesAPI_Offset::~FeaturesAPI_Offset() +{ +} + +void FeaturesAPI_Offset::dump(ModelHighAPI_Dumper& theDumper) const +{ + FeaturePtr aBase = feature(); + const std::string& aDocName = theDumper.name(aBase->document()); + + AttributeSelectionPtr anAttrShape = + aBase->selection(FeaturesPlugin_Offset::BASE_SHAPE_ID()); + + AttributeDoublePtr anAttrOffset = aBase->real(FeaturesPlugin_Offset::OFFSET_VALUE_ID()); + + std::string aCreationMethod = + aBase->string(FeaturesPlugin_Offset::CREATION_METHOD_ID())->value(); + + if (aCreationMethod == FeaturesPlugin_Offset::CREATION_METHOD_EQUAL()) { + AttributeBooleanPtr anAttrIsPipe = aBase->boolean(FeaturesPlugin_Offset::PIPE_JOINT_ID()); + + theDumper << aBase << " = model.addOffset(" << aDocName << ", " << anAttrShape; + theDumper << ", " << anAttrOffset << ", " << anAttrIsPipe << ")" << std::endl; + } + else if (aCreationMethod == FeaturesPlugin_Offset::CREATION_METHOD_PARTIAL()) { + AttributeSelectionListPtr anAttrFaces = + aBase->selectionList(FeaturesPlugin_Offset::FACES_ID()); + + theDumper << aBase << " = model.addOffsetPartial(" << aDocName << ", " << anAttrShape; + theDumper << ", " << anAttrOffset << ", " << anAttrFaces << ")" << std::endl; + } +} + +//================================================================================================== + +OffsetPtr addOffset(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const bool isPipeJoint) +{ + FeaturePtr aFeature = thePart->addFeature(FeaturesAPI_Offset::ID()); + + OffsetPtr aOffset (new FeaturesAPI_Offset(aFeature, theBaseObject, theOffset, isPipeJoint)); + return aOffset; +} + +OffsetPtr addOffsetPartial(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const std::list& theFaces) +{ + FeaturePtr aFeature = thePart->addFeature(FeaturesAPI_Offset::ID()); + + OffsetPtr aOffset (new FeaturesAPI_Offset(aFeature, theBaseObject, theOffset, theFaces)); + return aOffset; +} diff --git a/src/FeaturesAPI/FeaturesAPI_Offset.h b/src/FeaturesAPI/FeaturesAPI_Offset.h new file mode 100644 index 000000000..64727089e --- /dev/null +++ b/src/FeaturesAPI/FeaturesAPI_Offset.h @@ -0,0 +1,102 @@ +// Copyright (C) 2017-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef FeaturesAPI_Offset_H_ +#define FeaturesAPI_Offset_H_ + +#include "FeaturesAPI.h" + +#include + +#include +#include +#include + +class ModelHighAPI_Selection; + +/// \class FeaturesAPI_Offset +/// \ingroup CPPHighAPI +/// \brief Interface for Offset feature. +class FeaturesAPI_Offset: public ModelHighAPI_Interface +{ +public: + /// Constructor without values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Offset(const std::shared_ptr& theFeature); + + /// Constructor with values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Offset(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const bool isPipeJoint); + + /// Constructor with values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Offset(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const std::list& theFaces); + + /// Destructor. + FEATURESAPI_EXPORT + virtual ~FeaturesAPI_Offset(); + + INTERFACE_5(FeaturesPlugin_Offset::ID(), + + creationMethod, FeaturesPlugin_Offset::CREATION_METHOD_ID(), + ModelAPI_AttributeString, /** Creation method */, + + baseObject, FeaturesPlugin_Offset::BASE_SHAPE_ID(), + ModelAPI_AttributeSelection, /** Base object */, + + offsetValue, FeaturesPlugin_Offset::OFFSET_VALUE_ID(), + ModelAPI_AttributeDouble, /** Value of the offset */, + + isPipeJoint, FeaturesPlugin_Offset::PIPE_JOINT_ID(), + ModelAPI_AttributeBoolean, /** Is pipe or intersection joint */, + + faces, FeaturesPlugin_Offset::FACES_ID(), + ModelAPI_AttributeSelectionList, /** List of faces for partial offset */) + + /// Dump wrapped feature + FEATURESAPI_EXPORT + virtual void dump(ModelHighAPI_Dumper& theDumper) const; +}; + +/// Pointer on the offset object. +typedef std::shared_ptr OffsetPtr; + +/// \ingroup CPPHighAPI +/// \brief Create Offset feature. +FEATURESAPI_EXPORT +OffsetPtr addOffset(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const bool isPipeJoint = true); + +/// \ingroup CPPHighAPI +/// \brief Create Offset feature. +FEATURESAPI_EXPORT +OffsetPtr addOffsetPartial(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theOffset, + const std::list& theFaces); + +#endif // FeaturesAPI_Offset_H_ diff --git a/src/FeaturesAPI/FeaturesAPI_Thickness.cpp b/src/FeaturesAPI/FeaturesAPI_Thickness.cpp new file mode 100644 index 000000000..0a0a6c5f3 --- /dev/null +++ b/src/FeaturesAPI/FeaturesAPI_Thickness.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2017-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "FeaturesAPI_Thickness.h" + +#include +#include +#include +#include + +//================================================================================================== + +FeaturesAPI_Thickness::FeaturesAPI_Thickness(const std::shared_ptr& theFeature) + : ModelHighAPI_Interface(theFeature) +{ + initialize(); +} + +FeaturesAPI_Thickness::FeaturesAPI_Thickness(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const bool isInside) + : FeaturesAPI_Thickness(theFeature) +{ + if (initialize()) { + fillAttribute(FeaturesPlugin_Thickness::CREATION_METHOD_THICK(), mycreationMethod); + fillAttribute(theBaseObject, mybaseObject); + fillAttribute(theThickness, mythicknessValue); + fillAttribute(isInside, myisInside); + + execute(); + } +} + +FeaturesAPI_Thickness::FeaturesAPI_Thickness(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const std::list& theFaces, + const bool isInside) + : FeaturesAPI_Thickness(theFeature) +{ + if (initialize()) { + fillAttribute(FeaturesPlugin_Thickness::CREATION_METHOD_HOLLOWED(), mycreationMethod); + fillAttribute(theBaseObject, mybaseObject); + fillAttribute(theThickness, mythicknessValue); + fillAttribute(theFaces, myfaces); + fillAttribute(isInside, myisInside); + + execute(); + } +} + +FeaturesAPI_Thickness::~FeaturesAPI_Thickness() +{ +} + +void FeaturesAPI_Thickness::dump(ModelHighAPI_Dumper& theDumper) const +{ + FeaturePtr aBase = feature(); + const std::string& aDocName = theDumper.name(aBase->document()); + + AttributeSelectionPtr anAttrShape = + aBase->selection(FeaturesPlugin_Thickness::BASE_SHAPE_ID()); + + AttributeDoublePtr anAttrThickness = + aBase->real(FeaturesPlugin_Thickness::THICKNESS_VALUE_ID()); + + AttributeBooleanPtr anAttrIsInside = + aBase->boolean(FeaturesPlugin_Thickness::INSIDE_ID()); + + std::string aCreationMethod = + aBase->string(FeaturesPlugin_Thickness::CREATION_METHOD_ID())->value(); + + if (aCreationMethod == FeaturesPlugin_Thickness::CREATION_METHOD_THICK()) { + theDumper << aBase << " = model.addThickness(" << aDocName; + theDumper << ", " << anAttrShape << ", " << anAttrThickness; + theDumper << ", " << anAttrIsInside << ")" << std::endl; + } + else if (aCreationMethod == FeaturesPlugin_Thickness::CREATION_METHOD_HOLLOWED()) { + AttributeSelectionListPtr anAttrFaces = + aBase->selectionList(FeaturesPlugin_Thickness::FACES_ID()); + + theDumper << aBase << " = model.addHollowedSolid(" << aDocName; + theDumper << ", " << anAttrShape << ", " << anAttrThickness; + theDumper << ", " << anAttrFaces; + theDumper << ", " << anAttrIsInside << ")" << std::endl; + } +} + +//================================================================================================== + +ThicknessPtr addThickness(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const bool isInside) +{ + FeaturePtr aFeature = thePart->addFeature(FeaturesAPI_Thickness::ID()); + + ThicknessPtr aThickness (new FeaturesAPI_Thickness(aFeature, theBaseObject, theThickness, isInside)); + return aThickness; +} + +ThicknessPtr addHollowedSolid(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const std::list& theFaces, + const bool isInside) +{ + FeaturePtr aFeature = thePart->addFeature(FeaturesAPI_Thickness::ID()); + + ThicknessPtr aThickness (new FeaturesAPI_Thickness(aFeature, theBaseObject, theThickness, theFaces, isInside)); + return aThickness; +} diff --git a/src/FeaturesAPI/FeaturesAPI_Thickness.h b/src/FeaturesAPI/FeaturesAPI_Thickness.h new file mode 100644 index 000000000..386413190 --- /dev/null +++ b/src/FeaturesAPI/FeaturesAPI_Thickness.h @@ -0,0 +1,104 @@ +// Copyright (C) 2017-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef FeaturesAPI_Thickness_H_ +#define FeaturesAPI_Thickness_H_ + +#include "FeaturesAPI.h" + +#include + +#include +#include +#include + +class ModelHighAPI_Selection; + +/// \class FeaturesAPI_Thickness +/// \ingroup CPPHighAPI +/// \brief Interface for Thickness feature. +class FeaturesAPI_Thickness: public ModelHighAPI_Interface +{ +public: + /// Constructor without values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Thickness(const std::shared_ptr& theFeature); + + /// Constructor with values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Thickness(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const bool isInside); + + /// Constructor with values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Thickness(const std::shared_ptr& theFeature, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const std::list& theFaces, + const bool isInside); + + /// Destructor. + FEATURESAPI_EXPORT + virtual ~FeaturesAPI_Thickness(); + + INTERFACE_5(FeaturesPlugin_Thickness::ID(), + + creationMethod, FeaturesPlugin_Thickness::CREATION_METHOD_ID(), + ModelAPI_AttributeString, /** Creation method */, + + baseObject, FeaturesPlugin_Thickness::BASE_SHAPE_ID(), + ModelAPI_AttributeSelection, /** Base object */, + + thicknessValue, FeaturesPlugin_Thickness::THICKNESS_VALUE_ID(), + ModelAPI_AttributeDouble, /** Value of the thickness */, + + faces, FeaturesPlugin_Thickness::FACES_ID(), + ModelAPI_AttributeSelectionList, /** List of faces to remove in hollowed solid mode */, + + isInside, FeaturesPlugin_Thickness::INSIDE_ID(), + ModelAPI_AttributeBoolean, /** Do thicken towards inside */) + + /// Dump wrapped feature + FEATURESAPI_EXPORT + virtual void dump(ModelHighAPI_Dumper& theDumper) const; +}; + +/// Pointer on the thickness object. +typedef std::shared_ptr ThicknessPtr; + +/// \ingroup CPPHighAPI +/// \brief Create Thickness feature. +FEATURESAPI_EXPORT +ThicknessPtr addThickness(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const bool isInside = true); + +/// \ingroup CPPHighAPI +/// \brief Create Thickness feature. +FEATURESAPI_EXPORT +ThicknessPtr addHollowedSolid(const std::shared_ptr& thePart, + const ModelHighAPI_Selection& theBaseObject, + const ModelHighAPI_Double& theThickness, + const std::list& theFaces, + const bool isInside = true); + +#endif // FeaturesAPI_Thickness_H_ diff --git a/src/FeaturesAPI/FeaturesAPI_swig.h b/src/FeaturesAPI/FeaturesAPI_swig.h index 4dbce1c75..1620196f8 100644 --- a/src/FeaturesAPI/FeaturesAPI_swig.h +++ b/src/FeaturesAPI/FeaturesAPI_swig.h @@ -38,9 +38,11 @@ #include "FeaturesAPI_NormalToFace.h" #include "FeaturesAPI_MultiRotation.h" #include "FeaturesAPI_MultiTranslation.h" + #include "FeaturesAPI_Offset.h" #include "FeaturesAPI_Partition.h" #include "FeaturesAPI_Pipe.h" #include "FeaturesAPI_Loft.h" + #include "FeaturesAPI_Thickness.h" #include "FeaturesAPI_Placement.h" #include "FeaturesAPI_PointCloudOnFace.h" #include "FeaturesAPI_Recover.h" diff --git a/src/FeaturesPlugin/CMakeLists.txt b/src/FeaturesPlugin/CMakeLists.txt index 4946d5434..01cfeea11 100644 --- a/src/FeaturesPlugin/CMakeLists.txt +++ b/src/FeaturesPlugin/CMakeLists.txt @@ -40,6 +40,7 @@ SET(PROJECT_HEADERS FeaturesPlugin_Partition.h FeaturesPlugin_Pipe.h FeaturesPlugin_Loft.h + FeaturesPlugin_Thickness.h FeaturesPlugin_Placement.h FeaturesPlugin_PointCloudOnFace.h FeaturesPlugin_CompositeBoolean.h @@ -53,6 +54,7 @@ SET(PROJECT_HEADERS FeaturesPlugin_Union.h FeaturesPlugin_ValidatorTransform.h FeaturesPlugin_Validators.h + FeaturesPlugin_Offset.h FeaturesPlugin_RemoveSubShapes.h FeaturesPlugin_Tools.h FeaturesPlugin_Symmetry.h @@ -102,6 +104,7 @@ SET(PROJECT_SOURCES FeaturesPlugin_Partition.cpp FeaturesPlugin_Pipe.cpp FeaturesPlugin_Loft.cpp + FeaturesPlugin_Thickness.cpp FeaturesPlugin_Placement.cpp FeaturesPlugin_PointCloudOnFace.cpp FeaturesPlugin_CompositeBoolean.cpp @@ -115,6 +118,7 @@ SET(PROJECT_SOURCES FeaturesPlugin_Union.cpp FeaturesPlugin_ValidatorTransform.cpp FeaturesPlugin_Validators.cpp + FeaturesPlugin_Offset.cpp FeaturesPlugin_RemoveSubShapes.cpp FeaturesPlugin_Tools.cpp FeaturesPlugin_Symmetry.cpp @@ -167,6 +171,8 @@ SET(XML_RESOURCES intersection_widget.xml pipe_widget.xml loft_widget.xml + thickness_widget.xml + offset_widget.xml remove_subshapes_widget.xml union_widget.xml symmetry_widget.xml diff --git a/src/FeaturesPlugin/FeaturesPlugin_Offset.cpp b/src/FeaturesPlugin/FeaturesPlugin_Offset.cpp new file mode 100644 index 000000000..f6345ef57 --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Offset.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2014-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "FeaturesPlugin_Offset.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//================================================================================================== +FeaturesPlugin_Offset::FeaturesPlugin_Offset() +{ +} + +//================================================================================================== +void FeaturesPlugin_Offset::initAttributes() +{ + data()->addAttribute(CREATION_METHOD_ID(), ModelAPI_AttributeString::typeId()); + data()->addAttribute(BASE_SHAPE_ID(), ModelAPI_AttributeSelection::typeId()); + data()->addAttribute(OFFSET_VALUE_ID(), ModelAPI_AttributeDouble::typeId()); + data()->addAttribute(PIPE_JOINT_ID(), ModelAPI_AttributeBoolean::typeId()); + data()->addAttribute(FACES_ID(), ModelAPI_AttributeSelectionList::typeId()); + + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PIPE_JOINT_ID()); + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), FACES_ID()); +} + +//================================================================================================== +void FeaturesPlugin_Offset::execute() +{ + // Get base shape and sub-shapes list. + AttributeSelectionPtr aShapeAttrSelection = selection(BASE_SHAPE_ID()); + if (!aShapeAttrSelection.get()) { + return; + } + + // Get base shape. + GeomShapePtr aBaseShape = aShapeAttrSelection->value(); + if (!aBaseShape.get()) { + return; + } + + // Get offset value. + double anOffset = real(OFFSET_VALUE_ID())->value(); + + // Getting creation method. + std::string aCreationMethod = string(CREATION_METHOD_ID())->value(); + + // Perform total or partial offset + std::shared_ptr anOffsetAlgo; + + if (aCreationMethod == CREATION_METHOD_EQUAL()) { + // total offset can have pipe or intersection joints + bool isPipeJoint = boolean(PIPE_JOINT_ID())->value(); + + anOffsetAlgo = std::shared_ptr + (new GeomAlgoAPI_Offset (aBaseShape, anOffset, isPipeJoint)); + } + else { + // partial offset has faces argument + AttributeSelectionListPtr aFacesAttrList = selectionList(FACES_ID()); + if (!aShapeAttrSelection.get() || !aFacesAttrList.get()) { + return; + } + + ListOfShape aFaces; + for (int anIndex = 0; anIndex < aFacesAttrList->size(); ++anIndex) { + AttributeSelectionPtr aFaceAttrInList = aFacesAttrList->value(anIndex); + GeomShapePtr aFace = aFaceAttrInList->value(); + if (!aFace.get()) { + return; + } + aFaces.push_back(aFace); + } + + anOffsetAlgo = std::shared_ptr + (new GeomAlgoAPI_Offset (aBaseShape, aFaces, anOffset)); + } + + std::string anError; + if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(anOffsetAlgo, getKind(), anError)) { + setError(anError); + return; + } + + GeomShapePtr aResult = anOffsetAlgo->shape(); + + // Store result. + int anIndex = 0; + ResultBodyPtr aResultBody = document()->createBody(data(), anIndex); + aResultBody->storeModified(aBaseShape, aResult); + setResult(aResultBody, anIndex); +} diff --git a/src/FeaturesPlugin/FeaturesPlugin_Offset.h b/src/FeaturesPlugin/FeaturesPlugin_Offset.h new file mode 100644 index 000000000..e320efe23 --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Offset.h @@ -0,0 +1,107 @@ +// Copyright (C) 2014-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef FeaturesPlugin_Offset_H_ +#define FeaturesPlugin_Offset_H_ + +#include "FeaturesPlugin.h" + +#include + +/// \class FeaturesPlugin_Offset +/// \ingroup Plugins +/// \brief Feature for offset. +class FeaturesPlugin_Offset: public ModelAPI_Feature +{ +public: + /// Use plugin manager for features creation + FeaturesPlugin_Offset(); + + /// Feature kind. + inline static const std::string& ID() + { + static const std::string MY_ID("Offset3d"); + return MY_ID; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_ID() + { + static const std::string MY_CREATION_METHOD_ID("creation_method"); + return MY_CREATION_METHOD_ID; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_EQUAL() + { + static const std::string MY_CREATION_METHOD_EQUAL("offset_equal"); + return MY_CREATION_METHOD_EQUAL; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_PARTIAL() + { + static const std::string MY_CREATION_METHOD_PARTIAL("offset_partial"); + return MY_CREATION_METHOD_PARTIAL; + } + + /// Attribute name of base shape. + inline static const std::string& BASE_SHAPE_ID() + { + static const std::string MY_BASE_SHAPE_ID("base_shape"); + return MY_BASE_SHAPE_ID; + } + + /// Attribute name of offset value. + inline static const std::string& OFFSET_VALUE_ID() + { + static const std::string MY_OFFSET_VALUE_ID("offset_value"); + return MY_OFFSET_VALUE_ID; + } + + /// Attribute name of pipe/intersection joint bool flag. + inline static const std::string& PIPE_JOINT_ID() + { + static const std::string MY_PIPE_JOINT_ID("pipe_joint"); + return MY_PIPE_JOINT_ID; + } + + /// Attribute name of sub-faces to offset (for partial offset). + inline static const std::string& FACES_ID() + { + static const std::string MY_FACES_ID("faces_to_offset"); + return MY_FACES_ID; + } + + + /// \return the kind of a feature. + FEATURESPLUGIN_EXPORT virtual const std::string& getKind() + { + static std::string MY_KIND = FeaturesPlugin_Offset::ID(); + return MY_KIND; + } + + /// Request for initialization of data model of the feature: adding all attributes. + FEATURESPLUGIN_EXPORT virtual void initAttributes(); + + /// Performs the algorithm and stores results it in the data structure. + FEATURESPLUGIN_EXPORT virtual void execute(); +}; + +#endif diff --git a/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp b/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp index 53669c8f2..4d0c56a74 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include @@ -101,6 +103,10 @@ FeaturesPlugin_Plugin::FeaturesPlugin_Plugin() new FeaturesPlugin_ValidatorExtrusionBoundaryFace); aFactory->registerValidator("FeaturesPlugin_ValidatorBooleanSelection", new FeaturesPlugin_ValidatorBooleanSelection); + aFactory->registerValidator("FeaturesPlugin_ValidatorOffsetFacesSelection", + new FeaturesPlugin_ValidatorOffsetFacesSelection); + aFactory->registerValidator("FeaturesPlugin_ValidatorThicknessSelection", + new FeaturesPlugin_ValidatorThicknessSelection); aFactory->registerValidator("FeaturesPlugin_ValidatorPartitionSelection", new FeaturesPlugin_ValidatorPartitionSelection); aFactory->registerValidator("FeaturesPlugin_ValidatorRemoveSubShapesSelection", @@ -176,6 +182,8 @@ FeaturePtr FeaturesPlugin_Plugin::createFeature(std::string theFeatureID) return FeaturePtr(new FeaturesPlugin_Pipe); } else if (theFeatureID == FeaturesPlugin_Loft::ID()) { return FeaturePtr(new FeaturesPlugin_Loft); + } else if (theFeatureID == FeaturesPlugin_Thickness::ID()) { + return FeaturePtr(new FeaturesPlugin_Thickness); } else if (theFeatureID == FeaturesPlugin_Placement::ID()) { return FeaturePtr(new FeaturesPlugin_Placement); } else if (theFeatureID == FeaturesPlugin_Recover::ID()) { @@ -198,6 +206,8 @@ FeaturePtr FeaturesPlugin_Plugin::createFeature(std::string theFeatureID) return FeaturePtr(new FeaturesPlugin_Symmetry); } else if (theFeatureID == FeaturesPlugin_Scale::ID()) { return FeaturePtr(new FeaturesPlugin_Scale); + } else if (theFeatureID == FeaturesPlugin_Offset::ID()) { + return FeaturePtr(new FeaturesPlugin_Offset); } else if (theFeatureID == FeaturesPlugin_Sewing::ID()) { return FeaturePtr(new FeaturesPlugin_Sewing); } else if (theFeatureID == FeaturesPlugin_MultiTranslation::ID()) { diff --git a/src/FeaturesPlugin/FeaturesPlugin_Thickness.cpp b/src/FeaturesPlugin/FeaturesPlugin_Thickness.cpp new file mode 100644 index 000000000..011e74bab --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Thickness.cpp @@ -0,0 +1,131 @@ +// Copyright (C) 2014-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "FeaturesPlugin_Thickness.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//================================================================================================== +FeaturesPlugin_Thickness::FeaturesPlugin_Thickness() +{ +} + +//================================================================================================== +void FeaturesPlugin_Thickness::initAttributes() +{ + data()->addAttribute(CREATION_METHOD_ID(), ModelAPI_AttributeString::typeId()); + data()->addAttribute(BASE_SHAPE_ID(), ModelAPI_AttributeSelection::typeId()); + data()->addAttribute(FACES_ID(), ModelAPI_AttributeSelectionList::typeId()); + data()->addAttribute(THICKNESS_VALUE_ID(), ModelAPI_AttributeDouble::typeId()); + data()->addAttribute(INSIDE_ID(), ModelAPI_AttributeBoolean::typeId()); + + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), FACES_ID()); +} + +//================================================================================================== +void FeaturesPlugin_Thickness::execute() +{ + // Getting creation method. + std::string aCreationMethod = string(CREATION_METHOD_ID())->value(); + + // Get base shape + AttributeSelectionPtr aShapeAttrSelection = selection(BASE_SHAPE_ID()); + if (!aShapeAttrSelection.get()) { + return; + } + GeomShapePtr aBaseShape = aShapeAttrSelection->value(); + if (!aBaseShape.get()) { + return; + } + + // Get thickness value. + double aThickness = real(THICKNESS_VALUE_ID())->value(); + + // Get thickening direction + bool isInside = boolean(INSIDE_ID())->value(); + + // Perform thickness + std::shared_ptr aThicknessAlgo; + + if (aCreationMethod == CREATION_METHOD_THICK()) { + aThicknessAlgo = std::shared_ptr + (new GeomAlgoAPI_Thickness (aBaseShape, aThickness, isInside)); + } + else { + // hollowed solid algorithm has faces argument + AttributeSelectionListPtr aFacesAttrList = selectionList(FACES_ID()); + if (!aShapeAttrSelection.get() || !aFacesAttrList.get()) { + return; + } + + ListOfShape aFaces; + for (int anIndex = 0; anIndex < aFacesAttrList->size(); ++anIndex) { + AttributeSelectionPtr aFaceAttrInList = aFacesAttrList->value(anIndex); + GeomShapePtr aFace = aFaceAttrInList->value(); + if (!aFace.get()) { + return; + } + aFaces.push_back(aFace); + } + + aThicknessAlgo = std::shared_ptr + (new GeomAlgoAPI_Thickness (aBaseShape, aFaces, aThickness, isInside)); + } + + std::string anError; + if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aThicknessAlgo, getKind(), anError)) { + setError(anError); + return; + } + + GeomShapePtr aResult = aThicknessAlgo->shape(); + + // Store result. + int anIndex = 0; + ResultBodyPtr aResultBody = document()->createBody(data(), anIndex); + aResultBody->storeModified(aBaseShape, aResult); + setResult(aResultBody, anIndex); +} + +//================================================================================================== +void FeaturesPlugin_Thickness::attributeChanged(const std::string& theID) +{ + if (theID == CREATION_METHOD_ID()) { + // clean base shape, as it has different type in different modes, + // so, it cannot be the same + AttributeSelectionPtr aBaseAttr = selection(BASE_SHAPE_ID()); + AttributeSelectionListPtr aFaceAttr = selectionList(FACES_ID()); + data()->blockSendAttributeUpdated(true, false); + aBaseAttr->reset(); + aFaceAttr->clear(); // reset doesn't work for a list + data()->blockSendAttributeUpdated(false, false); + } + ModelAPI_Feature::attributeChanged(theID); +} diff --git a/src/FeaturesPlugin/FeaturesPlugin_Thickness.h b/src/FeaturesPlugin/FeaturesPlugin_Thickness.h new file mode 100644 index 000000000..a070bb1db --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Thickness.h @@ -0,0 +1,111 @@ +// Copyright (C) 2014-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef FeaturesPlugin_Thickness_H_ +#define FeaturesPlugin_Thickness_H_ + +#include "FeaturesPlugin.h" + +#include + +/// \class FeaturesPlugin_Thickness +/// \ingroup Plugins +/// \brief Feature for thickness. +class FeaturesPlugin_Thickness: public ModelAPI_Feature +{ +public: + /// Use plugin manager for features creation + FeaturesPlugin_Thickness(); + + /// Feature kind. + inline static const std::string& ID() + { + static const std::string MY_ID("Thickness"); + return MY_ID; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_ID() + { + static const std::string MY_CREATION_METHOD_ID("creation_method"); + return MY_CREATION_METHOD_ID; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_THICK() + { + static const std::string MY_CREATION_METHOD_THICK("thickness"); + return MY_CREATION_METHOD_THICK; + } + + /// Attribute name for creation method. + inline static const std::string& CREATION_METHOD_HOLLOWED() + { + static const std::string MY_CREATION_METHOD_HOLLOWED("hollowed_solid"); + return MY_CREATION_METHOD_HOLLOWED; + } + + /// Attribute name of base shape. + inline static const std::string& BASE_SHAPE_ID() + { + static const std::string MY_BASE_SHAPE_ID("base_shape"); + return MY_BASE_SHAPE_ID; + } + + /// Attribute name of sub-faces to remove (for hollowed solid mode). + inline static const std::string& FACES_ID() + { + static const std::string MY_FACES_ID("faces_to_remove"); + return MY_FACES_ID; + } + + /// Attribute name of thickness value. + inline static const std::string& THICKNESS_VALUE_ID() + { + static const std::string MY_THICKNESS_VALUE_ID("thickness_value"); + return MY_THICKNESS_VALUE_ID; + } + + /// Attribute name of pipe/intersection joint bool flag. + inline static const std::string& INSIDE_ID() + { + static const std::string MY_INSIDE_ID("is_inside"); + return MY_INSIDE_ID; + } + + + /// \return the kind of a feature. + FEATURESPLUGIN_EXPORT virtual const std::string& getKind() + { + static std::string MY_KIND = FeaturesPlugin_Thickness::ID(); + return MY_KIND; + } + + /// Request for initialization of data model of the feature: adding all attributes. + FEATURESPLUGIN_EXPORT virtual void initAttributes(); + + /// Performs the algorithm and stores results it in the data structure. + FEATURESPLUGIN_EXPORT virtual void execute(); + + /// Called on change of any argument-attribute of this object. + /// \param[in] theID identifier of changed attribute. + FEATURESPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID); +}; + +#endif diff --git a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp index 3b9784fc1..56054e8e0 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -1115,6 +1116,160 @@ bool FeaturesPlugin_ValidatorFillet1DSelection::isValid(const AttributePtr& theA return true; } +//============================================================================================= +bool FeaturesPlugin_ValidatorOffsetFacesSelection::isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const +{ + AttributeSelectionListPtr aSubShapesAttrList = + std::dynamic_pointer_cast(theAttribute); + if (!aSubShapesAttrList.get()) { + theError = "Error: This validator can only work with selection list in \"Offset\" feature."; + return false; + } + + static const std::string aBaseShapeID = "base_shape"; + FeaturePtr aFeature = std::dynamic_pointer_cast(theAttribute->owner()); + AttributeSelectionPtr aShapeAttrSelection = aFeature->selection(aBaseShapeID); + + if (!aShapeAttrSelection.get()) { + theError = "Error: Could not get \"%1\" attribute."; + theError.arg(aBaseShapeID); + return false; + } + + GeomShapePtr aBaseShape = aShapeAttrSelection->value(); + ResultPtr aContext = aShapeAttrSelection->context(); + if (!aContext.get()) { + theError = "Error: Empty context."; + return false; + } + if (!aBaseShape.get()) { + aBaseShape = aContext->shape(); + } + if (!aBaseShape.get()) { + theError = "Error: Empty base shape."; + return false; + } + + if (!aSubShapesAttrList->size()) { + theError = "Error: No faces selected."; + return false; + } + + GeomAPI_IndexedMapOfShape aSubShapesMap (aBaseShape); + for (int anIndex = 0; anIndex < aSubShapesAttrList->size(); ++anIndex) { + AttributeSelectionPtr anAttrSelectionInList = aSubShapesAttrList->value(anIndex); + GeomShapePtr aShapeToAdd = anAttrSelectionInList->value(); + if (!aShapeToAdd.get()) { + theError = "Error: Wrong shape selected."; + return false; + } + if (!aSubShapesMap.FindIndex(aShapeToAdd)) { + theError = "Error: Only sub-shapes of selected shape are allowed for selection."; + return false; + } + } + + return true; +} + +//============================================================================================= +bool FeaturesPlugin_ValidatorThicknessSelection::isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const +{ + static const std::string aBaseShapeID = "base_shape"; + static const std::string aCreationMethodID = "creation_method"; + static const std::string aCreationMethodTH = "thickness"; + + FeaturePtr aFeature = std::dynamic_pointer_cast(theAttribute->owner()); + + AttributeSelectionPtr aShapeAttrSelection = + std::dynamic_pointer_cast(theAttribute); + if (aShapeAttrSelection.get()) { + // 1. Check main objects type (depends on creation method) + GeomShapePtr aBaseShape = aShapeAttrSelection->value(); + ResultPtr aContext = aShapeAttrSelection->context(); + if (!aContext.get()) { + theError = "Error: Empty context."; + return false; + } + if (!aBaseShape.get()) { + aBaseShape = aContext->shape(); + } + if (!aBaseShape.get()) { + theError = "Error: Empty base shape."; + return false; + } + + AttributeStringPtr aCreationMethodAttr = aFeature->string(aCreationMethodID); + if (aCreationMethodAttr->value() == aCreationMethodTH) { + // should be a face or a shell + return aBaseShape->isShell() || aBaseShape->isFace(); + } + + // shoud be a solid + return aBaseShape->isSolid(); + } + + // 2. Check faces selection + AttributeSelectionListPtr aSubShapesAttrList = + std::dynamic_pointer_cast(theAttribute); + if (!aSubShapesAttrList.get()) { + theError = "Error: This validator can only work with selection in \"Thickness\" feature."; + return false; + } + + // base shape + aShapeAttrSelection = aFeature->selection(aBaseShapeID); + + if (!aShapeAttrSelection.get()) { + theError = "Error: Could not get \"%1\" attribute."; + theError.arg(aBaseShapeID); + return false; + } + + GeomShapePtr aBaseShape = aShapeAttrSelection->value(); + ResultPtr aContext = aShapeAttrSelection->context(); + if (!aContext.get()) { + theError = "Error: Empty context."; + return false; + } + if (!aBaseShape.get()) { + aBaseShape = aContext->shape(); + } + if (!aBaseShape.get()) { + theError = "Error: Empty base shape."; + return false; + } + + if (!aSubShapesAttrList->size()) { + theError = "Error: No faces selected."; + return false; + } + + GeomAPI_IndexedMapOfShape aSubShapesMap (aBaseShape); + for (int anIndex = 0; anIndex < aSubShapesAttrList->size(); ++anIndex) { + AttributeSelectionPtr anAttrSelectionInList = aSubShapesAttrList->value(anIndex); + GeomShapePtr aShapeToAdd = anAttrSelectionInList->value(); + if (!aShapeToAdd.get()) { + theError = "Error: Wrong shape selected."; + return false; + } + if (!aSubShapesMap.FindIndex(aShapeToAdd)) { + theError = "Error: Only sub-shapes of selected shape are allowed for selection."; + return false; + } + if (!aShapeToAdd->isFace()) { + theError = "Error: Only faces are allowed for selection."; + return false; + } + } + + return true; +} + //================================================================================================== bool FeaturesPlugin_ValidatorPartitionSelection::isValid(const AttributePtr& theAttribute, const std::list& theArguments, diff --git a/src/FeaturesPlugin/FeaturesPlugin_Validators.h b/src/FeaturesPlugin/FeaturesPlugin_Validators.h index 7839f8ee7..a0c684a46 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Validators.h +++ b/src/FeaturesPlugin/FeaturesPlugin_Validators.h @@ -219,6 +219,38 @@ public: Events_InfoMessage& theError) const; }; +/// \class FeaturesPlugin_ValidatorOffsetFacesSelection +/// \ingroup Validators +/// \brief Validates selection for "Offset" feature. +class FeaturesPlugin_ValidatorOffsetFacesSelection: public ModelAPI_AttributeValidator +{ +public: + /// \return True if the attribute is valid. It checks whether the selection + /// is acceptable for operation. + /// \param[in] theAttribute an attribute to check. + /// \param[in] theArguments a filter parameters. + /// \param[out] theError error message. + virtual bool isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const; +}; + +/// \class FeaturesPlugin_ValidatorThicknessSelection +/// \ingroup Validators +/// \brief Validates selection for "Thickness" feature. +class FeaturesPlugin_ValidatorThicknessSelection: public ModelAPI_AttributeValidator +{ +public: + /// \return True if the attribute is valid. It checks whether the selection + /// is acceptable for operation. + /// \param[in] theAttribute an attribute to check. + /// \param[in] theArguments a filter parameters. + /// \param[out] theError error message. + virtual bool isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const; +}; + /// \class FeaturesPlugin_ValidatorPartitionSelection /// \ingroup Validators /// \brief Validates selection for partition. diff --git a/src/FeaturesPlugin/Test/TestOffset.py b/src/FeaturesPlugin/Test/TestOffset.py new file mode 100644 index 000000000..8c2f511e5 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestOffset.py @@ -0,0 +1,82 @@ +# Copyright (C) 2018-2024 CEA, EDF +# +# This library 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. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 200, 200, 200) +Box_2 = model.addBox(Part_1_doc, 200, 200, 200) +Box_3 = model.addBox(Part_1_doc, 200, 200, 200) + +### Create Shell +Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Front"), + model.selection("FACE", "Box_1_1/Left")]) + +# Global offset (pipe joints) +Offset_1 = model.addOffset(Part_1_doc, model.selection("SHELL", "Shell_1_1"), 70., True) + +# Global offset (intersection joints) +Offset_2 = model.addOffset(Part_1_doc, model.selection("SOLID", "Box_2_1"), 60., False) + +# Partial offset +Offset_3 = model.addOffsetPartial(Part_1_doc, + model.selection("SOLID", "Box_3_1"), + 50., + [model.selection("FACE", "Box_3_1/Top"), + model.selection("FACE", "Box_3_1/Back"), + model.selection("FACE", "Box_3_1/Right")]) + +model.end() + +from GeomAPI import GeomAPI_Shape + +#test Offset_1 +model.testNbResults(Offset_1, 1) +model.testNbSubResults(Offset_1, [0]) +model.testNbSubShapes(Offset_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Offset_1, GeomAPI_Shape.FACE, [7]) +model.testNbSubShapes(Offset_1, GeomAPI_Shape.EDGE, [27]) +model.testNbSubShapes(Offset_1, GeomAPI_Shape.VERTEX, [54]) +model.testResultsAreas(Offset_1, [193670.3477]) + +#test Offset_2 +model.testNbResults(Offset_1, 1) +model.testNbSubResults(Offset_2, [0]) +model.testNbSubShapes(Offset_2, GeomAPI_Shape.SOLID, [1]) +model.testNbSubShapes(Offset_2, GeomAPI_Shape.FACE, [6]) +model.testNbSubShapes(Offset_2, GeomAPI_Shape.EDGE, [24]) +model.testNbSubShapes(Offset_2, GeomAPI_Shape.VERTEX, [48]) +model.testResultsVolumes(Offset_2, [32768000]) + +#test Offset_3 +model.testNbResults(Offset_3, 1) +model.testNbSubResults(Offset_3, [0]) +model.testNbSubShapes(Offset_3, GeomAPI_Shape.SOLID, [1]) +model.testNbSubShapes(Offset_3, GeomAPI_Shape.FACE, [6]) +model.testNbSubShapes(Offset_3, GeomAPI_Shape.EDGE, [24]) +model.testNbSubShapes(Offset_3, GeomAPI_Shape.VERTEX, [48]) +model.testResultsVolumes(Offset_3, [15625000]) diff --git a/src/FeaturesPlugin/Test/TestThickness.py b/src/FeaturesPlugin/Test/TestThickness.py new file mode 100644 index 000000000..f39a7c1fb --- /dev/null +++ b/src/FeaturesPlugin/Test/TestThickness.py @@ -0,0 +1,83 @@ +# Copyright (C) 2018-2024 CEA, EDF +# +# This library 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. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 200, 200, 200) +Box_2 = model.addBox(Part_1_doc, 200, 200, 200) +Box_3 = model.addBox(Part_1_doc, 200, 200, 200) + +### Create Shell +Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Front"), + model.selection("FACE", "Box_1_1/Left")]) +Shell_2 = model.addShell(Part_1_doc, [model.selection("FACE", "Box_2_1/Top"), + model.selection("FACE", "Box_2_1/Front"), + model.selection("FACE", "Box_2_1/Left")]) + +# Thickness mode 1: thicken a shell/face +Thickness_1 = model.addThickness(Part_1_doc, model.selection("SHELL", "Shell_1_1"), 30, False) +Thickness_2 = model.addThickness(Part_1_doc, model.selection("SHELL", "Shell_2_1"), 30, True) + +# Thickness mode 2: hollowed solid +Thickness_3 = model.addHollowedSolid(Part_1_doc, + model.selection("SOLID", "Box_3_1"), + 40, + [model.selection("FACE", "Box_3_1/Top"), + model.selection("FACE", "Box_3_1/Left")], + False) + +model.end() + +from GeomAPI import GeomAPI_Shape + +#test Thickness_1 +model.testNbResults(Thickness_1, 1) +model.testNbSubResults(Thickness_1, [0]) +model.testNbSubShapes(Thickness_1, GeomAPI_Shape.SOLID, [1]) +model.testNbSubShapes(Thickness_1, GeomAPI_Shape.FACE, [12]) +model.testNbSubShapes(Thickness_1, GeomAPI_Shape.EDGE, [48]) +model.testNbSubShapes(Thickness_1, GeomAPI_Shape.VERTEX, [96]) +model.testResultsVolumes(Thickness_1, [4167000]) + +#test Thickness_2 +model.testNbResults(Thickness_1, 1) +model.testNbSubResults(Thickness_2, [0]) +model.testNbSubShapes(Thickness_2, GeomAPI_Shape.SOLID, [1]) +model.testNbSubShapes(Thickness_2, GeomAPI_Shape.FACE, [12]) +model.testNbSubShapes(Thickness_2, GeomAPI_Shape.EDGE, [48]) +model.testNbSubShapes(Thickness_2, GeomAPI_Shape.VERTEX, [96]) +model.testResultsVolumes(Thickness_2, [3087000]) + +#test Thickness_3 +model.testNbResults(Thickness_3, 1) +model.testNbSubResults(Thickness_3, [0]) +model.testNbSubShapes(Thickness_3, GeomAPI_Shape.SOLID, [1]) +model.testNbSubShapes(Thickness_3, GeomAPI_Shape.FACE, [10]) +model.testNbSubShapes(Thickness_3, GeomAPI_Shape.EDGE, [48]) +model.testNbSubShapes(Thickness_3, GeomAPI_Shape.VERTEX, [96]) +model.testResultsVolumes(Thickness_3, [8128000]) diff --git a/src/FeaturesPlugin/doc/FeaturesPlugin.rst b/src/FeaturesPlugin/doc/FeaturesPlugin.rst index 43d13da81..a89beaf80 100644 --- a/src/FeaturesPlugin/doc/FeaturesPlugin.rst +++ b/src/FeaturesPlugin/doc/FeaturesPlugin.rst @@ -31,6 +31,7 @@ Features plug-in provides a set of common topological operations. It implements loftFeature.rst measurementFeature.rst normalToFaceFeature.rst + offsetFeature.rst pipeFeature.rst placementFeature.rst pointCoordinatesFeature.rst @@ -42,5 +43,6 @@ Features plug-in provides a set of common topological operations. It implements rotationFeature.rst sewingFeature.rst symmetryFeature.rst + thicknessFeature.rst transformationFeature.rst translationFeature.rst diff --git a/src/FeaturesPlugin/doc/TUI_offsetFeature.rst b/src/FeaturesPlugin/doc/TUI_offsetFeature.rst new file mode 100644 index 000000000..d53f635ff --- /dev/null +++ b/src/FeaturesPlugin/doc/TUI_offsetFeature.rst @@ -0,0 +1,11 @@ + + .. _tui_offset: + +offset +====== + +.. literalinclude:: examples/offset.py + :linenos: + :language: python + +:download:`Download this script ` diff --git a/src/FeaturesPlugin/doc/TUI_thicknessFeature.rst b/src/FeaturesPlugin/doc/TUI_thicknessFeature.rst new file mode 100644 index 000000000..ded4f9217 --- /dev/null +++ b/src/FeaturesPlugin/doc/TUI_thicknessFeature.rst @@ -0,0 +1,11 @@ + + .. _tui_thickness: + +thickness +========= + +.. literalinclude:: examples/thickness.py + :linenos: + :language: python + +:download:`Download this script ` diff --git a/src/FeaturesPlugin/doc/examples/offset.py b/src/FeaturesPlugin/doc/examples/offset.py new file mode 100644 index 000000000..2e1423829 --- /dev/null +++ b/src/FeaturesPlugin/doc/examples/offset.py @@ -0,0 +1,30 @@ +from SketchAPI import * + +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 200, 200, 200) +Box_2 = model.addBox(Part_1_doc, 200, 200, 200) + +### Create Shell +Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Front"), + model.selection("FACE", "Box_1_1/Left")]) + +# Offset of the whole shape, pipe joints (isPipeJoint = True) +Offset_1 = model.addOffset(Part_1_doc, model.selection("SHELL", "Shell_1_1"), 70., True) + +# Partial offset. Negative offset value means offset in direction, opposite to normale. +Offset_2 = model.addOffsetPartial(Part_1_doc, + model.selection("SOLID", "Box_2_1"), + -50., + [model.selection("FACE", "Box_2_1/Top")]) + +model.end() diff --git a/src/FeaturesPlugin/doc/examples/thickness.py b/src/FeaturesPlugin/doc/examples/thickness.py new file mode 100644 index 000000000..364d13e29 --- /dev/null +++ b/src/FeaturesPlugin/doc/examples/thickness.py @@ -0,0 +1,34 @@ +from SketchAPI import * + +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 200, 200, 200) +Box_2 = model.addBox(Part_1_doc, 200, 200, 200) + +### Create Shell +Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Front"), + model.selection("FACE", "Box_1_1/Left")]) + +# Thickness mode 1: thicken a shell/face +# isInside = False, so thicken towards outside +Thickness_1 = model.addThickness(Part_1_doc, model.selection("SHELL", "Shell_1_1"), 30, False) + +# Thickness mode 2: hollowed solid +# isInside = True, so thicken towards inside +Thickness_2 = model.addHollowedSolid(Part_1_doc, + model.selection("SOLID", "Box_2_1"), + 40, + [model.selection("FACE", "Box_2_1/Top"), + model.selection("FACE", "Box_2_1/Left")], + True) + +model.end() diff --git a/src/FeaturesPlugin/doc/images/offset.png b/src/FeaturesPlugin/doc/images/offset.png new file mode 100644 index 0000000000000000000000000000000000000000..47e03cc92dd49e1ddaf5246475fa2ae2acaab8eb GIT binary patch literal 1006 zcmVz@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ1iPK(5_?gVWL&zJ2?~@ZiA%WHT550th(_ zznEOSX!HNiABO*b|AGwj0E#Kw+uQ%w4rziK^pD}^@4pP+fBt2dYii11kTjcti;L?& zGcz;X#Q*_>Y~WWQe!2k8eH`X3;G7}3KKh;4uv=7sKCQ-@*9bzdsBoW~PF)cmM<>>+gFC)-#$X{_fIgxwtx%-2q0!G5%mk?bqfoNQ^s+940?Z_Fj($g z%&Sg;rfa^+VhCMKX)7#OCg zgfo1T31+C6m(K7>RTb*ZZ(zefY*>T>@dY5>0T4hexDv2VSS!$*e;9uM0md#cQogIJ zGc>8jF{EFZ&hYv7Z-!a2o(vaP)x%u(0vb#g00IcZKs!4-uwg%eG5Q^h|1f+5hTT_C zYy(|2Nih)U<*y7Ox7UH41a;jVDE{(>jU4YUI00G1zDJl71D{2A*sL%nGJa0e$grqqj z{`2c6L&1fqK*Pcr{s3e1&(Cj=Wb*ena@fIL2ND1XAW%BwW@l&r@)wxS>`FEQ75`x< zy*L#ZZb0t>@dTw1hQB};K_cS^*s!acx?rvY8MFo(eqaLu0tjTFI5Z)Ha0E!uB7HW{ zTR$28{Q)LkAPq7Q#0D95ZF3LIurE-9KnC80IT;{;7@@ht0GdNofLH>EeL#|+0s&;; z<#jE{@pb??nLz2UFiC&_VnoVv5>NvT&<%zff|2IX4Fdsy0K%4+u^G$&jm0m>X%5{m c5C8};08`ed)Y}dM_W%F@07*qoM6N<$f(@{@Hvj+t literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/offsetPartialPropertyPanel.png b/src/FeaturesPlugin/doc/images/offsetPartialPropertyPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..15f368409a508703a81f98ffe3bcd3206e48172c GIT binary patch literal 2751 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 literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/offsetPropertyPanel.png b/src/FeaturesPlugin/doc/images/offsetPropertyPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..272372c56f41d8e10593adc44ba35560fb4c03b6 GIT binary patch literal 2718 zcmY*bdpy$%A0{19=^}I!C#NO++?L!@iaFwEt{a=XVO`|j)M|0cbxW&eh9gvrh|E2s z#iDvlIvxCC9 zcELRN^YHuOg^JMyR{I}Mc;DDHHLqU1dYE6Gm6b)MKJ=$Ig;G-^BO@$5iC6T7RPU3yoo2bWm;Mxn|z7-rn^)KCJ_G zxt|q^6E)GMWh47JlcaAsVMXocZnkKnd`W3dpre;LeS^!n+t=HyV2*+<&UJ@Wo_Ox= z>UcQSFlL0-OhZ~Hs}xu(W8%7JBRm`vPjnAt^}@=E!o9ud!flXtWBM9cinDb0KiAKU z@zAwC|2~v7;>8v54SCE6Ek96|AXE=9$L}0ayUe^*^kRptVB&OJSy3#E@VG=Dp-LFI z_~)~PYHK4(SiAh|JI=*-`Wni2pFaMkN*u&A$o?&tGoXa5SEcZBYbzoBK-wUGBg90>^O5F^kByJ0w}EE5 zu{x2>L6znfV`u@(@qNn1-F@3aDNes?8XDItIW1|BnrV+jC1^zDu<|1v#q2`I%!64n zsr<324X)DdM&D06EcYs%n=f(tp#CBtb=_vaw&sN5V81zh@l#3X(r?#!wN>u8J#?Bp z_`GR`tH#gCOf0;QhWOQ>REPtEn=!7BoH zSxP@(qHi2tKTh}Re_YG1Bi)S^?2G8XW0-sMwRwQC8@j{zinFb!sVNYuyGz?f*q`geYM5Ho)CBM9VXmBMQLpX}^KmoV zcmgE7zcp%+q&Q`Sf5jHsKmD<(IM?x`FJNB z@yop}l98r;y8wz5HgorF+3Q~PxjN<~=T{knr>I!lv;3ExgXRu;m z+d$057e<@$xNYHg7|1+St+0GUmKoX19lP4Obc0bd$7|l8Frz;gF#QbLhvcDe8m!vO z)q~`5of>@FyKT^5s9ALn+snN|&ts2eTH|~s(?0Im*;WgWPTfGqgKJ>2{E7m@?6B|0 z`)nN1CZ9LKK&5Phc7e~o>bhtS>l&V~(sQVv=W^NSBZg^vI8eBX473X}nr0|5M#~T$ zK~*_d%{+pNLAFcE)FC+_s-CtzDXyR5pY8~;Y7biK9pjLlr{PS0j~TkAr> zr#DTo;yhSqs>u#To~@E=&!kOuzLoi~v~mut|N4q1wM?$WS|_3e6j;fOD!x~67c$zj z%!|r`WiDS&@Xb9A*wo?jLp+IfT39s`v(TGB^WIQIny< zpKBGnzSED!lR#mEV*|2Bn^_+uH5JBl)a!9ed0M;}I;{7?NdJ@QBd1?;FEacVGB}=E zK-3nLY6bRSl8%&+W{*76>IaxQPT@Ae=loL~6&PoGnm#bAa)qbXBw6kiCQY9b>05Ot zV3qqFY`7}11Da;sCz_nhug*=n22wyMBh;3?();uKu;F>M@n0vB8`5Be1LJEzE}7qzW+2xYfwD8yGD%j657hXrv)?F zIX~0$2x|0>kIzI9_p20v$b(M+_D(yf@!{Z$cNcp|XF_Zy`7W9ZH5aHB{onkFLDkpo zkJ5tnLLk)dXq<{I%I%N=ydHYV(XN}p& z>N=IMM*eOvw-1Mj9Oc62c>DN9<5gLY%C2N4aNkfT_=l?Q%_06V#=lGGe~i)~%V;?X z`cM23w5kt+s`U~jfebs}-Cd?(eolDjJ@Oebf!*b@-=6raF47lfAX*TH=2mqAW9=@j zL9nu^b4_WQm5+dW|G5o|NbIArchYV_f0_&7?p`P5PDa>wvrgyaO;UL)7l8<2T!hLoe+2F1A*uE_$bF9Hpov1-KC)^|RAF zU(8I{mv_KH0#-Uh8_z07R3=;sZW*?@Y?u48I`P3#KtqMP^w;t*eEZ2r`uQ2~ajAQs z|I0P`CI5WNk>!+V&tt8snZQq>m0z84PqvpD-CodHgWRpZI(XXESS3m1jeQV+Jb!d- z?hntrcq3|jp&fu0;Bas(RX0;>8t$6HTp>y8x#EL;$LmXbD?8(EP@Rbu$yNHX9M)5~ z8#T{VeS9)OfnasahMIZD>?}5p9)OJYPc*s&I;mpkbn{$IC*R88*}u<)m>7g&?HCao zHK=a?)X*-Gvtb_ucDBkcYu6n{%K>!(WGHyZB_0O&kl)Mxf9L&;|B1u5j7Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5dZ)S5dnW>Uy%R+0{clsK~y+Tt&~eh zR8bhm|1+Z~X~ow@g(JE!kEb4MH>MBoR1&V8KoyZ`fj-?=U+CDtzJFHTg=GGVPa!6x@O{aVN%|d1a!)sM%7lXQgPa{rl3x+JI*^x zQ&q04tdx0qd3=e$o*~t5?bE~R{@A)*=$<}@lfGsJ69luZ6h7SR6tdMEqojsp!KS<3}WCHe!vck3JMAm zHuYKf@EA=N@Q_=EC4YYBIm%I{7^w{G!D)WH%pFMqOGwm6EoTz&`{A&fd?ojT2dzY7=K%=%4J-V<>|d$nYF6 w1w*p2DvTow%*JY94(>IEE%Y2I2L=Ft0kR^coDSSZApigX07*qoM6N<$g0&Zp>;M1& literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/offset_partial_result.png b/src/FeaturesPlugin/doc/images/offset_partial_result.png new file mode 100644 index 0000000000000000000000000000000000000000..a39dba13138d5e288bbeb1240025b66ecacd34c3 GIT binary patch literal 9582 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 J*Bc?R{|6-BBd`Df literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/offset_result.png b/src/FeaturesPlugin/doc/images/offset_result.png new file mode 100644 index 0000000000000000000000000000000000000000..6404310e03a92367697339bdaa78d1a20e962225 GIT binary patch literal 12215 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 literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/thickness.png b/src/FeaturesPlugin/doc/images/thickness.png new file mode 100644 index 0000000000000000000000000000000000000000..bee29286b03d0a9bef39b17ef20d61bb9920b8fc GIT binary patch literal 471 zcmV;|0Vw{7P)Px#32;bRa{vGi!~g&di2>5OfeHWs00(qQO+^Ra0}TuYDDZ<&PXGV`X-PyuR5;76 z)V)r^P!z!NKNK|)FD%$aLL_!D8kRmp;~O~X;$txL80?I?HQii{Q)6OefrQCHbmIC! zFnC>h$!#eGlAh`I+~(KQ`*9%WD5NDjuB*)Fb{JNGZ(zO-ET&1dfzAQqslGMC`u5z>H`CkGeUC=qu<04$y5jmwwgm z`!Q%J1nn@q0^Ur}ib`&ETsKIPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01b%&(z<~P00007bV*G`2j2q? z3Nbt2x6Ky-00N##L_t(I%e9nGNYp_T$3L@fe5q&u|gZ`~pKt1!*A0W;0?Xp0&fUf*><+qTV4Bs%v{wi=<1q}Ij%7wg8?uG?QL>s$6tdP_nCQFPK=8Y-BKiK7G;1P>0Zw)$71F0GD;)c2Po;S$XKPTEY`y2Hk~;<(UcM z`MOCq8@qdtlVN3fLMgQhkP#UlSa8)et;@HMs-nfGAS;h(elc?7C7JV|i53(y=gMWy zE%E3P<_;c3cD2SkZpfVZNHiL4jzl7>NjkJDYRcvOWWV literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/thicknessPropertyPanel.png b/src/FeaturesPlugin/doc/images/thicknessPropertyPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..79cd361d1ab6adb71d4db7389f8f199a34336ebe GIT binary patch literal 3581 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 literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/thicknessPropertyPanel2.png b/src/FeaturesPlugin/doc/images/thicknessPropertyPanel2.png new file mode 100644 index 0000000000000000000000000000000000000000..382f0e250cb6b93b7fe7f0a3a209830bdfca94ca GIT binary patch literal 4197 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 literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/thickness_result2.png b/src/FeaturesPlugin/doc/images/thickness_result2.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6087a4c8e430f6059ee569e51d6f713c81366c GIT binary patch literal 7627 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
-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 literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/offsetFeature.rst b/src/FeaturesPlugin/doc/offsetFeature.rst new file mode 100644 index 000000000..0b96ccf33 --- /dev/null +++ b/src/FeaturesPlugin/doc/offsetFeature.rst @@ -0,0 +1,108 @@ +.. |offset.icon| image:: images/offset.png + +Offset +====== + +**Offset** feature translates each point of the **Shape** +along a local normal by the given **Offset distance** +(signed number, negative value means inner offset). + +To create an Offset in the active part: + +#. select in the Main Menu *Features - > Offset* item or +#. click |offset.icon| **Offset** button in the toolbar + +Two Offset algorithms are: + +.. figure:: images/offset.png + :align: left + :height: 24px + +offset the whole shape by the same value + +.. figure:: images/offset_partial.png + :align: left + :height: 24px + +offset selected faces by the given value, other faces by zero. + +-------------------------------------------------------------------------------- + +Offset the whole shape by the same value +---------------------------------------- + +.. figure:: images/offsetPropertyPanel.png + :align: center + + Offset by the same value property panel + +Input fields: + +- **Shape** defines the base shape (solid, shell or face) selected in 3D OCC viewer or object browser; +- **Distance** defines the offset value. Negative value meaning inner offset; +- **Join by pipes** check box defines the mode of filling the gaps between translated + adjacent surfaces: + + - if Join by pipes is activated, they are filled with pipes; + - else the surfaces are extended and intersected, so that sharp edges are preserved; + +**TUI Command**: + +.. py:function:: model.addOffset(Part_doc, shape, dist, isPipeJoint) + + :param part: The current part object. + :param object: A shape in format *model.selection(TYPE, shape)*. + :param real: Offset distance value. + :param boolean: Join by pipes/intersection flag. + :return: Created object. + +Result +"""""" + +Result of offset of a box. Join by pipes activated. + +.. figure:: images/offset_result.png + :align: center + + Offset of a box + +**See Also** a sample TUI Script of :ref:`tui_offset` operation. + + +Offset selected faces by the given value, other faces by zero +------------------------------------------------------------- + +.. figure:: images/offsetPartialPropertyPanel.png + :align: center + + Partial Offset property panel + +Input fields: + +- **Shape** defines the base shape (solid, shell or face) selected in 3D OCC viewer or object browser; +- **Distance** defines the offset value. Negative value meaning inner offset; +- **Faces to offset** defines the faces of the base shape, which should be offset; + +*Note*: In Partial Offset mode gaps are allways filled by intersection. + +**TUI Command**: + +.. py:function:: model.addOffset(Part_doc, shape, dist, faces) + + :param part: The current part object. + :param object: A shape in format *model.selection(TYPE, shape)*. + :param real: Offset distance value. + :param objects: Faces of the shape in format *[model.selection(TYPE, shape), ...]*. + :return: Created object. + +Result +"""""" + +Result of partial offset of a box. Top and front faces selected. + +.. figure:: images/offset_partial_result.png + :align: center + + Partial offset of a box + +**See Also** a sample TUI Script of :ref:`tui_offset` operation. diff --git a/src/FeaturesPlugin/doc/thicknessFeature.rst b/src/FeaturesPlugin/doc/thicknessFeature.rst new file mode 100644 index 000000000..fb8eba53f --- /dev/null +++ b/src/FeaturesPlugin/doc/thicknessFeature.rst @@ -0,0 +1,100 @@ +.. |thickness.icon| image:: images/thickness.png + +Thickness +========= + +To create a **Thickness** or a **Hollowed Solid** in the active part: + +#. select in the Main Menu *Features - > Thickness* item or +#. click |thickness.icon| **Thickness** button in the toolbar + +Two Thickness algorithms are: + +.. figure:: images/thickness.png + :align: left + :height: 24px + +make a thickness of a shell or a face + +.. figure:: images/thickness2.png + :align: left + :height: 24px + +make a hollowed solid + +-------------------------------------------------------------------------------- + +Thickness of a shell or a face +------------------------------ + +.. figure:: images/thicknessPropertyPanel.png + :align: center + + Thickness property panel + +Input fields: + +- **Shape** defines the base shape (shell or face) selected in 3D OCC viewer or object browser; +- **Distance** defines the thickness value. Negative values are not allowed; +- **Thicken towards the inside** check box defines the thickening direction; + +**TUI Command**: + +.. py:function:: model.addThickness(Part_doc, shape, dist, isInside) + + :param part: The current part object. + :param object: A shape in format *model.selection(TYPE, shape)*. + :param real: Thickness value. + :param boolean: Inside/outside direction flag. + :return: Created object. + +Result +"""""" + +Result of thickness of a shell. + +.. figure:: images/thickness_result.png + :align: center + + Thickness of a shell + +**See Also** a sample TUI Script of :ref:`tui_thickness` operation. + + +Hollowed solid +-------------- + +.. figure:: images/thicknessPropertyPanel2.png + :align: center + + Hollowed Solid property panel + +Input fields: + +- **Shape** defines the base shape (solid) selected in 3D OCC viewer or object browser; +- **Distance** defines the thickness value. Negative values are not allowed; +- **Faces to remove** defines the faces of the base solid, where the hole is done; +- **Thicken towards the inside** check box defines the thickening direction; + +**TUI Command**: + +.. py:function:: model.addHollowedSolid(Part_doc, shape, dist, faces, isInside) + + :param part: The current part object. + :param object: A shape in format *model.selection("SOLID", shape)*. + :param real: Thickness value. + :param objects: Faces of the solid in format *[model.selection("FACE", shape), ...]*. + :param boolean: Inside/outside direction flag. + :return: Created object. + +Result +"""""" + +Result of hollowed solid of a box. Left and front faces selected. + +.. figure:: images/thickness_result2.png + :align: center + + Hollowed solid of a box + +**See Also** a sample TUI Script of :ref:`tui_thickness` operation. diff --git a/src/FeaturesPlugin/icons/offset.png b/src/FeaturesPlugin/icons/offset.png new file mode 100644 index 0000000000000000000000000000000000000000..47e03cc92dd49e1ddaf5246475fa2ae2acaab8eb GIT binary patch literal 1006 zcmVz@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ1iPK(5_?gVWL&zJ2?~@ZiA%WHT550th(_ zznEOSX!HNiABO*b|AGwj0E#Kw+uQ%w4rziK^pD}^@4pP+fBt2dYii11kTjcti;L?& zGcz;X#Q*_>Y~WWQe!2k8eH`X3;G7}3KKh;4uv=7sKCQ-@*9bzdsBoW~PF)cmM<>>+gFC)-#$X{_fIgxwtx%-2q0!G5%mk?bqfoNQ^s+940?Z_Fj($g z%&Sg;rfa^+VhCMKX)7#OCg zgfo1T31+C6m(K7>RTb*ZZ(zefY*>T>@dY5>0T4hexDv2VSS!$*e;9uM0md#cQogIJ zGc>8jF{EFZ&hYv7Z-!a2o(vaP)x%u(0vb#g00IcZKs!4-uwg%eG5Q^h|1f+5hTT_C zYy(|2Nih)U<*y7Ox7UH41a;jVDE{(>jU4YUI00G1zDJl71D{2A*sL%nGJa0e$grqqj z{`2c6L&1fqK*Pcr{s3e1&(Cj=Wb*ena@fIL2ND1XAW%BwW@l&r@)wxS>`FEQ75`x< zy*L#ZZb0t>@dTw1hQB};K_cS^*s!acx?rvY8MFo(eqaLu0tjTFI5Z)Ha0E!uB7HW{ zTR$28{Q)LkAPq7Q#0D95ZF3LIurE-9KnC80IT;{;7@@ht0GdNofLH>EeL#|+0s&;; z<#jE{@pb??nLz2UFiC&_VnoVv5>NvT&<%zff|2IX4Fdsy0K%4+u^G$&jm0m>X%5{m c5C8};08`ed)Y}dM_W%F@07*qoM6N<$f(@{@Hvj+t literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/icons/offset_partial.png b/src/FeaturesPlugin/icons/offset_partial.png new file mode 100644 index 0000000000000000000000000000000000000000..ced1d32dfbce1d4587a717f7849e9e7df74b99f9 GIT binary patch literal 870 zcmV-s1DX7ZP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5dZ)S5dnW>Uy%R+0{clsK~y+Tt&~eh zR8bhm|1+Z~X~ow@g(JE!kEb4MH>MBoR1&V8KoyZ`fj-?=U+CDtzJFHTg=GGVPa!6x@O{aVN%|d1a!)sM%7lXQgPa{rl3x+JI*^x zQ&q04tdx0qd3=e$o*~t5?bE~R{@A)*=$<}@lfGsJ69luZ6h7SR6tdMEqojsp!KS<3}WCHe!vck3JMAm zHuYKf@EA=N@Q_=EC4YYBIm%I{7^w{G!D)WH%pFMqOGwm6EoTz&`{A&fd?ojT2dzY7=K%=%4J-V<>|d$nYF6 w1w*p2DvTow%*JY94(>IEE%Y2I2L=Ft0kR^coDSSZApigX07*qoM6N<$g0&Zp>;M1& literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/icons/thickness.png b/src/FeaturesPlugin/icons/thickness.png new file mode 100644 index 0000000000000000000000000000000000000000..bee29286b03d0a9bef39b17ef20d61bb9920b8fc GIT binary patch literal 471 zcmV;|0Vw{7P)Px#32;bRa{vGi!~g&di2>5OfeHWs00(qQO+^Ra0}TuYDDZ<&PXGV`X-PyuR5;76 z)V)r^P!z!NKNK|)FD%$aLL_!D8kRmp;~O~X;$txL80?I?HQii{Q)6OefrQCHbmIC! zFnC>h$!#eGlAh`I+~(KQ`*9%WD5NDjuB*)Fb{JNGZ(zO-ET&1dfzAQqslGMC`u5z>H`CkGeUC=qu<04$y5jmwwgm z`!Q%J1nn@q0^Ur}ib`&ETsKIPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01b%&(z<~P00007bV*G`2j2q? z3Nbt2x6Ky-00N##L_t(I%e9nGNYp_T$3L@fe5q&u|gZ`~pKt1!*A0W;0?Xp0&fUf*><+qTV4Bs%v{wi=<1q}Ij%7wg8?uG?QL>s$6tdP_nCQFPK=8Y-BKiK7G;1P>0Zw)$71F0GD;)c2Po;S$XKPTEY`y2Hk~;<(UcM z`MOCq8@qdtlVN3fLMgQhkP#UlSa8)et;@HMs-nfGAS;h(elc?7C7JV|i53(y=gMWy zE%E3P<_;c3cD2SkZpfVZNHiL4jzl7>NjkJDYRcvOWWV literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/offset_widget.xml b/src/FeaturesPlugin/offset_widget.xml new file mode 100644 index 000000000..02f54bb57 --- /dev/null +++ b/src/FeaturesPlugin/offset_widget.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/src/FeaturesPlugin/plugin-Features.xml b/src/FeaturesPlugin/plugin-Features.xml index 3bebe12a5..851f659c7 100644 --- a/src/FeaturesPlugin/plugin-Features.xml +++ b/src/FeaturesPlugin/plugin-Features.xml @@ -5,6 +5,10 @@ icon="icons/Features/scale.png" helpfile="transformationFeature.html"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GeomAlgoAPI/CMakeLists.txt b/src/GeomAlgoAPI/CMakeLists.txt index e01dbf2c7..65dffd544 100644 --- a/src/GeomAlgoAPI/CMakeLists.txt +++ b/src/GeomAlgoAPI/CMakeLists.txt @@ -88,6 +88,7 @@ SET(PROJECT_HEADERS GeomAlgoAPI_CurveBuilder.h GeomAlgoAPI_NExplode.h GeomAlgoAPI_Offset.h + GeomAlgoAPI_Thickness.h GeomAlgoAPI_SolidClassifier.h GeomAlgoAPI_MapShapesAndAncestors.h GeomAlgoAPI_Projection.h @@ -97,7 +98,7 @@ SET(PROJECT_HEADERS GeomAlgoAPI_NormalToFace.h GeomAlgoAPI_Tube.h GeomAlgoAPI_ShapeInfo.h - GeomAlgoAPI_CanonicalRecognition.h + GeomAlgoAPI_CanonicalRecognition.h GeomAlgoAPI_GlueFaces.h GeomAlgoAPI_LimitTolerance.h GeomAlgoAPI_Utils.h @@ -169,6 +170,7 @@ SET(PROJECT_SOURCES GeomAlgoAPI_CurveBuilder.cpp GeomAlgoAPI_NExplode.cpp GeomAlgoAPI_Offset.cpp + GeomAlgoAPI_Thickness.cpp GeomAlgoAPI_SolidClassifier.cpp GeomAlgoAPI_MapShapesAndAncestors.cpp GeomAlgoAPI_Projection.cpp @@ -178,9 +180,8 @@ SET(PROJECT_SOURCES GeomAlgoAPI_NormalToFace.cpp GeomAlgoAPI_Tube.cpp GeomAlgoAPI_ShapeInfo.cpp - GeomAlgoAPI_CanonicalRecognition.cpp + GeomAlgoAPI_CanonicalRecognition.cpp GeomAlgoAPI_GlueFaces.cpp - GeomAlgoAPI_CanonicalRecognition.cpp GeomAlgoAPI_LimitTolerance.cpp GeomAlgoAPI_Utils.cpp GeomAlgoAPI_NonPlanarFace.cpp diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.cpp index 1206dd3ee..1e0b54dbc 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,9 @@ void GeomAlgoAPI_MakeShape::generated(const GeomShapePtr theOldShape, } else if(myBuilderType == OCCT_BOPAlgo_Builder) { BOPAlgo_Builder* aBOPBuilder = implPtr(); aList = aBOPBuilder->Generated(theOldShape->impl()); + } else if(myBuilderType == OCCT_BRepOffset_MakeOffset) { + BRepOffset_MakeOffset* aMakeOffset = implPtr(); + aList = aMakeOffset->Generated(theOldShape->impl()); } for(TopTools_ListIteratorOfListOfShape anIt(aList); anIt.More(); anIt.Next()) { GeomShapePtr aShape(new GeomAPI_Shape()); @@ -126,6 +130,12 @@ void GeomAlgoAPI_MakeShape::modified(const GeomShapePtr theOldShape, } else if(myBuilderType == OCCT_BOPAlgo_Builder) { BOPAlgo_Builder* aBOPBuilder = implPtr(); aList = aBOPBuilder->Modified(theOldShape->impl()); + } else if(myBuilderType == OCCT_BRepOffset_MakeOffset) { + BRepOffset_MakeOffset* aMakeOffset = implPtr(); + try { + aList = aMakeOffset->Modified(theOldShape->impl()); + } catch(Standard_NoSuchObject const&) { + } } for(TopTools_ListIteratorOfListOfShape anIt(aList); anIt.More(); anIt.Next()) { GeomShapePtr aShape(new GeomAPI_Shape()); @@ -163,6 +173,9 @@ bool GeomAlgoAPI_MakeShape::isDeleted(const GeomShapePtr theOldShape) } else if(myBuilderType == OCCT_BOPAlgo_Builder) { BOPAlgo_Builder* aBOPBuilder = implPtr(); isDeleted = aBOPBuilder->IsDeleted(theOldShape->impl()) == Standard_True; + } else if(myBuilderType == OCCT_BRepOffset_MakeOffset) { + BRepOffset_MakeOffset* aMakeOffset = implPtr(); + isDeleted = aMakeOffset->IsDeleted(theOldShape->impl()) == Standard_True; } return isDeleted; @@ -257,6 +270,12 @@ void GeomAlgoAPI_MakeShape::initialize() myShape->setImpl(new TopoDS_Shape(implPtr()->Shape())); break; } + case OCCT_BRepOffset_MakeOffset: { + myDone = implPtr()->IsDone() == Standard_True; + myShape.reset(new GeomAPI_Shape()); + myShape->setImpl(new TopoDS_Shape(implPtr()->Shape())); + break; + } default: break; } diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.h b/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.h index 7acd2291a..29abccfb7 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_MakeShape.h @@ -38,7 +38,8 @@ public: enum BuilderType { Unknown, OCCT_BRepBuilderAPI_MakeShape, - OCCT_BOPAlgo_Builder + OCCT_BOPAlgo_Builder, + OCCT_BRepOffset_MakeOffset }; public: diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp index 590b37bbc..2d74029e6 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp @@ -27,16 +27,51 @@ #include #include +#include #include #include GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomShapePtr& theShape, - const double theOffsetValue) + const double theOffsetValue) { - build(theShape, theOffsetValue); + buildSimple(theShape, theOffsetValue); } -void GeomAlgoAPI_Offset::build(const GeomShapePtr& theShape, const double theOffsetValue) +GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomShapePtr& theShape, + const double theOffsetValue, + const bool isPipeJoint) +{ + buildByJoin(theShape, theOffsetValue, isPipeJoint); +} + +GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theOffsetValue) +{ + buildPartial(theShape, theFaces, theOffsetValue); +} + +GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomPlanePtr& thePlane, + const GeomShapePtr& theEdgeOrWire, + const double theOffsetValue, + const GeomAlgoAPI_OffsetJoint theJoint, + const bool theIsApprox) +{ + build2d(thePlane, theEdgeOrWire, theOffsetValue, theJoint, theIsApprox); +} + +void GeomAlgoAPI_Offset::generated(const GeomShapePtr theOldShape, + ListOfShape& theNewShapes) +{ + try { + GeomAlgoAPI_MakeShape::generated(theOldShape, theNewShapes); + } catch(...) { + // nothing is generated + } +} + +void GeomAlgoAPI_Offset::buildSimple(const GeomShapePtr& theShape, + const double theOffsetValue) { BRepOffsetAPI_MakeOffsetShape* anOffsetAlgo = new BRepOffsetAPI_MakeOffsetShape; anOffsetAlgo->PerformBySimple(theShape->impl(), theOffsetValue); @@ -52,21 +87,96 @@ void GeomAlgoAPI_Offset::build(const GeomShapePtr& theShape, const double theOff } } -void GeomAlgoAPI_Offset::generated(const GeomShapePtr theOldShape, - ListOfShape& theNewShapes) +void GeomAlgoAPI_Offset::buildByJoin(const GeomShapePtr& theShape, + const double theOffsetValue, + const bool isPipeJoint) { - try { - GeomAlgoAPI_MakeShape::generated(theOldShape, theNewShapes); - } catch(...) { - // nothing is generated + Standard_Real aTol = Precision::Confusion(); + BRepOffset_Mode aMode = BRepOffset_Skin; + Standard_Boolean anIntersection = Standard_False; + Standard_Boolean aSelfInter = Standard_False; + + BRepOffsetAPI_MakeOffsetShape* anOffsetAlgo = new BRepOffsetAPI_MakeOffsetShape; + anOffsetAlgo->PerformByJoin(theShape->impl(), + theOffsetValue, + aTol, + aMode, + anIntersection, + aSelfInter, + isPipeJoint ? GeomAbs_Arc : GeomAbs_Intersection); + setImpl(anOffsetAlgo); + setBuilderType(OCCT_BRepBuilderAPI_MakeShape); + + if (anOffsetAlgo->IsDone()) { + const TopoDS_Shape& aResult = anOffsetAlgo->Shape(); + std::shared_ptr aShape(new GeomAPI_Shape()); + aShape->setImpl(new TopoDS_Shape(aResult)); + setShape(aShape); + setDone(true); } } -GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomPlanePtr& thePlane, - const GeomShapePtr& theEdgeOrWire, - const double theOffsetValue, - const GeomAlgoAPI_OffsetJoint theJoint, - const bool theIsApprox) +void GeomAlgoAPI_Offset::buildPartial(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theOffsetValue) +{ + if (theFaces.empty()) + return; + + TopoDS_Shape aShapeBase = theShape->impl(); + + Standard_Real aTol = Precision::Confusion(); + BRepOffset_Mode aMode = BRepOffset_Skin; + Standard_Boolean anIntersection = Standard_False; + Standard_Boolean aSelfInter = Standard_False; + + BRepOffset_MakeOffset* aMakeOffset = new BRepOffset_MakeOffset; + aMakeOffset->Initialize(aShapeBase, + theOffsetValue, // set offset on all faces to anOffset + aTol, + aMode, + anIntersection, + aSelfInter, + GeomAbs_Intersection, + Standard_False); + + // put selected faces into a map + TopTools_MapOfShape aMapFaces; + for (ListOfShape::const_iterator anIt = theFaces.begin(); + anIt != theFaces.end(); ++anIt) { + if ((*anIt)->isFace()) + aMapFaces.Add((*anIt)->impl()); + } + + // set offset on non-selected faces to zero + TopExp_Explorer anExp (aShapeBase, TopAbs_FACE); + for (; anExp.More(); anExp.Next()) { + const TopoDS_Shape &aFace = anExp.Current(); + if (!aMapFaces.Contains(aFace)) { + aMakeOffset->SetOffsetOnFace(TopoDS::Face(aFace), 0.0); + } + } + + // perform offset operation + aMakeOffset->MakeOffsetShape(); + + setImpl(aMakeOffset); + setBuilderType(OCCT_BRepOffset_MakeOffset); + + if (aMakeOffset->IsDone()) { + const TopoDS_Shape& aResult = aMakeOffset->Shape(); + std::shared_ptr aShape(new GeomAPI_Shape()); + aShape->setImpl(new TopoDS_Shape(aResult)); + setShape(aShape); + setDone(true); + } +} + +void GeomAlgoAPI_Offset::build2d(const GeomPlanePtr& thePlane, + const GeomShapePtr& theEdgeOrWire, + const double theOffsetValue, + const GeomAlgoAPI_OffsetJoint theJoint, + const bool theIsApprox) { // 1. Make wire from edge, if need TopoDS_Wire aWire; diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h index 7c58f8e70..5bc01a6b6 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h @@ -41,11 +41,31 @@ enum class GeomAlgoAPI_OffsetJoint { KeepDistance, Arcs, Lines }; class GeomAlgoAPI_Offset : public GeomAlgoAPI_MakeShape { public: - /// \brief Construct offset. + /// \brief Perform simple offset. + /// \param[in] theShape base shape + /// \param[in] theOffsetValue offset distance, it can be negative GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset(const GeomShapePtr& theShape, const double theOffsetValue); - /// \brief Perform the offset algorithm on the plane + /// \brief Perform 3d offset algorithm + /// \param[in] theShape base shape + /// \param[in] theOffsetValue offset distance, it can be negative + /// \param[in] isPipeJoint type of joint of faces (pipes or intersections) + GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset + (const GeomShapePtr& theShape, + const double theOffsetValue, + const bool isPipeJoint); + + /// \brief Perform partial 3d offset algorithm + /// \param[in] theShape base shape + /// \param[in] theFaces list of faces to be offset + /// \param[in] theOffsetValue offset distance for selected faces, it can be negative + GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset + (const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theOffsetValue); + + /// \brief Perform 2d offset algorithm on the plane /// \param[in] thePlane base plane for all offsets /// \param[in] theEdgesOrWire base shapes /// \param[in] theOffsetValue offset distance, it can be negative @@ -63,10 +83,27 @@ public: GEOMALGOAPI_EXPORT virtual void generated(const GeomShapePtr theOldShape, ListOfShape& theNewShapes); - private: - /// \brief Perform offset operation - void build(const GeomShapePtr& theShape, const double theOffsetValue); + /// \brief Perform simple offset operation + void buildSimple(const GeomShapePtr& theShape, + const double theOffsetValue); + + /// \brief Perform 3d offset algorithm by join + void buildByJoin(const GeomShapePtr& theShape, + const double theOffsetValue, + const bool isPipeJoint); + + /// \brief Perform partial 3d offset algorithm + void buildPartial(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theOffsetValue); + + /// \brief Perform 2d offset algorithm on the plane + void build2d(const std::shared_ptr& thePlane, + const GeomShapePtr& theEdgeOrWire, + const double theOffsetValue, + const GeomAlgoAPI_OffsetJoint theJoint = GeomAlgoAPI_OffsetJoint::KeepDistance, + const bool theIsApprox = false); }; #endif diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.cpp new file mode 100644 index 000000000..0a7409b93 --- /dev/null +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.cpp @@ -0,0 +1,170 @@ +// Copyright (C) 2019-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "GeomAlgoAPI_Thickness.h" + +#include + +#include +#include +#include + +#include +#include + +GeomAlgoAPI_Thickness::GeomAlgoAPI_Thickness(const GeomShapePtr& theShape, + const double theThickness, + const bool isInside) +{ + buildThickening(theShape, theThickness, isInside); +} + +GeomAlgoAPI_Thickness::GeomAlgoAPI_Thickness(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theThickness, + const bool isInside) +{ + buildHollowedSolid(theShape, theFaces, theThickness, isInside); +} + +void GeomAlgoAPI_Thickness::buildThickening(const GeomShapePtr& theShape, + const double theThickness, + const bool isInside) +{ + Standard_Real aTol = Precision::Confusion(); + if (theThickness < aTol) { + myError = "The thickness value is Too small or negative"; + return; + } + + Standard_Real anOffset = theThickness; + if (isInside) + anOffset = -anOffset; + + const TopoDS_Shape& aShapeBase = theShape->impl(); + const TopAbs_ShapeEnum aType = aShapeBase.ShapeType(); + + if (aType != TopAbs_FACE && aType != TopAbs_SHELL) { + myError = "The base shape for thickening should be a face or a shell"; + return; + } + + BRepClass3d_SolidClassifier aClassifier(aShapeBase); + aClassifier.PerformInfinitePoint(Precision::Confusion()); + if (aClassifier.State()==TopAbs_IN) { + // If the generated pipe faces normals are oriented towards the inside, + // the offset is negative, so that the thickening is still towards outside + anOffset = -anOffset; + } + + Standard_Boolean anIntersection = Standard_False; + Standard_Boolean aSelfInter = Standard_False; + Standard_Boolean isThickening = Standard_True; + + BRepOffset_MakeOffset* aMakeOffset = + new BRepOffset_MakeOffset(aShapeBase, + anOffset, + aTol, + BRepOffset_Skin, + anIntersection, + aSelfInter, + GeomAbs_Intersection, + isThickening); + + setImpl(aMakeOffset); + setBuilderType(OCCT_BRepOffset_MakeOffset); + + if (aMakeOffset->IsDone()) { + const TopoDS_Shape& aResult = aMakeOffset->Shape(); + + // Control the solid orientation. This is mostly done to fix a bug in case of extrusion + // of a circle. The built solid is then badly oriented + BRepClass3d_SolidClassifier anotherClassifier(aResult); + anotherClassifier.PerformInfinitePoint(Precision::Confusion()); + if (anotherClassifier.State() == TopAbs_IN) { + aResult.Reverse(); + } + + std::shared_ptr aShape(new GeomAPI_Shape()); + aShape->setImpl(new TopoDS_Shape(aResult)); + setShape(aShape); + setDone(true); + } +} + +void GeomAlgoAPI_Thickness::buildHollowedSolid(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theThickness, + const bool isInside) +{ + if (theFaces.empty()) + return; + + Standard_Real aTol = Precision::Confusion(); + if (theThickness < aTol) { + myError = "The thickness value is Too small or negative"; + return; + } + + Standard_Real anOffset = theThickness; + if (isInside) + anOffset = -anOffset; + + //TopoDS_Shape aShapeBase = theShape->impl(); + const TopoDS_Shape& aShapeBase = theShape->impl(); + const TopAbs_ShapeEnum aType = aShapeBase.ShapeType(); + + if (aType != TopAbs_SOLID) { + myError = "The base shape for hollowed solid creation should be a solid"; + return; + } + + // put selected faces into a list + TopTools_ListOfShape aFacesToRm; + for (ListOfShape::const_iterator anIt = theFaces.begin(); + anIt != theFaces.end(); ++anIt) { + if ((*anIt)->isFace()) + aFacesToRm.Append((*anIt)->impl()); + } + + Standard_Boolean anIntersection = Standard_False; + Standard_Boolean aSelfInter = Standard_False; + + // Create a hollowed solid. + BRepOffsetAPI_MakeThickSolid* aMkSolid = new BRepOffsetAPI_MakeThickSolid(); + aMkSolid->MakeThickSolidByJoin(aShapeBase, + aFacesToRm, + anOffset, + aTol, + BRepOffset_Skin, + anIntersection, + aSelfInter, + GeomAbs_Intersection); + + setImpl(aMkSolid); + setBuilderType(OCCT_BRepBuilderAPI_MakeShape); + + if (aMkSolid->IsDone()) { + const TopoDS_Shape& aResult = aMkSolid->Shape(); + std::shared_ptr aShape(new GeomAPI_Shape()); + aShape->setImpl(new TopoDS_Shape(aResult)); + setShape(aShape); + setDone(true); + } +} diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.h b/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.h new file mode 100644 index 000000000..dfe272cf8 --- /dev/null +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Thickness.h @@ -0,0 +1,65 @@ +// Copyright (C) 2019-2024 CEA, EDF +// +// This library 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. +// +// This library 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 this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef GeomAlgoAPI_Thickness_H_ +#define GeomAlgoAPI_Thickness_H_ + +#include +#include + +/// \class GeomAlgoAPI_Thickness +/// \ingroup DataAlgo +/// \brief Perform Thickness or Hollowed Solid algorithm for the shape +class GeomAlgoAPI_Thickness : public GeomAlgoAPI_MakeShape +{ +public: + /// \brief Perform thickening algorithm + /// \param[in] theShape base shape (Face or Shell) + /// \param[in] theThickness thickness of the resulting solid + /// \param[in] isInside if true, the thickness is applied towards inside + GEOMALGOAPI_EXPORT GeomAlgoAPI_Thickness + (const GeomShapePtr& theShape, + const double theThickness, + const bool isInside); + + /// \brief Perform hollowed solid algorithm + /// \param[in] theShape base shape (Solid) + /// \param[in] theFaces the list of faces to be removed from the result + /// \param[in] theThickness thickness of the resulting solid + /// \param[in] isInside if true, the thickness is applied towards inside + GEOMALGOAPI_EXPORT GeomAlgoAPI_Thickness + (const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theThickness, + const bool isInside); + +private: + /// \brief Perform thickening algorithm + void buildThickening(const GeomShapePtr& theShape, + const double theThickness, + const bool isInside); + + /// \brief Perform hollowed solid algorithm + void buildHollowedSolid(const GeomShapePtr& theShape, + const ListOfShape& theFaces, + const double theThickness, + const bool isInside); +}; + +#endif diff --git a/src/PythonAPI/model/features/__init__.py b/src/PythonAPI/model/features/__init__.py index ba9b2542c..c591bfc14 100644 --- a/src/PythonAPI/model/features/__init__.py +++ b/src/PythonAPI/model/features/__init__.py @@ -24,6 +24,7 @@ from FeaturesAPI import addMultiTranslation, addMultiRotation from FeaturesAPI import addExtrusion, addExtrusionCut, addExtrusionFuse from FeaturesAPI import addRevolution, addRevolutionCut, addRevolutionFuse from FeaturesAPI import addPipe, addLoft +from FeaturesAPI import addOffset, addOffsetPartial, addThickness, addHollowedSolid from FeaturesAPI import addCut, addFuse, addCommon, addSmash, addSplit from FeaturesAPI import addIntersection, addPartition, addUnion, addRemoveSubShapes from FeaturesAPI import addRecover diff --git a/src/SketchPlugin/Test/TestOffset1.py b/src/SketchPlugin/Test/TestOffset1.py index ee59d3889..39524a032 100644 --- a/src/SketchPlugin/Test/TestOffset1.py +++ b/src/SketchPlugin/Test/TestOffset1.py @@ -18,7 +18,7 @@ # """ - TestOffset.py + TestOffset1.py Unit test of SketchPlugin_Offset class SketchPlugin_Offset -- 2.39.2