Merge pull request #12237 from bgbsww/bgbsww-toponamingMakeRefine

Toponaming: Transfer in makeElementRefine
This commit is contained in:
Chris Hennes
2024-02-07 15:19:27 -06:00
committed by GitHub
9 changed files with 352 additions and 11 deletions

View File

@@ -4042,14 +4042,15 @@ TopoShape &TopoShape::makeFace(const std::vector<TopoShape> &shapes, const char
return *this;
}
TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, bool no_fail)
TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, RefineFail no_fail)
{
(void)op;
_Shape.Nullify();
if(shape.isNull()) {
if(!no_fail)
if (no_fail == RefineFail::throwException) {
HANDLE_NULL_SHAPE;
}
return *this;
}
try {
@@ -4057,7 +4058,9 @@ TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, bool no
_Shape = mkRefine.Shape();
return *this;
}catch (Standard_Failure &) {
if(!no_fail) throw;
if(no_fail == RefineFail::throwException ) {
throw;
}
}
*this = shape;
return *this;

View File

@@ -104,6 +104,13 @@ enum class HistoryTraceType
followTypeChange
};
/// Behavior of refines when a problem arises; either leave the shape untouched or throw an exception.
/// This replaces a boolean parameter in the original Toponaming branch by realthunder..
enum class RefineFail
{
shapeUntouched,
throwException
};
/** The representation for a CAD Shape
*/
@@ -387,6 +394,7 @@ public:
TopoDS_Shape removeShape(const std::vector<TopoDS_Shape>& s) const;
void sewShape(double tolerance = 1.0e-06);
bool fix(double, double, double);
bool fixSolidOrientation();
bool removeInternalWires(double);
TopoDS_Shape removeSplitter() const;
TopoDS_Shape defeaturing(const std::vector<TopoDS_Shape>& s) const;
@@ -582,9 +590,47 @@ public:
{
return TopoShape().makeGTransform(*this, mat, op, copy);
}
/** Refine the input shape by merging faces/edges that share the same geometry
*
* @param source: input shape
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param no_fail: if throwException, throw exception if failed to refine. Or else,
* if shapeUntouched the shape remains untouched if failed.
*
* @return The original content of this TopoShape is discarded and replaced
* with the refined shape. The function returns the TopoShape
* itself as a self reference so that multiple operations can be
* carried out for the same shape in the same line of code.
*/
TopoShape& makeElementRefine(const TopoShape& source,
const char* op = nullptr,
RefineFail no_fail = RefineFail::throwException);
TopoShape& makeRefine(const TopoShape& shape, const char* op = nullptr, bool no_fail = true);
TopoShape makeRefine(const char* op = nullptr, bool no_fail = true) const
/** Refine the input shape by merging faces/edges that share the same geometry
*
* @param source: input shape
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param no_fail: if throwException, throw exception if failed to refine. Or else,
* if shapeUntouched the shape remains untouched if failed.
*
* @return Return a refined shape. The shape itself is not modified
*/
TopoShape makeElementRefine(const char* op = nullptr,
RefineFail no_fail = RefineFail::throwException) const
{
return TopoShape(Tag, Hasher).makeElementRefine(*this, op, no_fail);
}
TopoShape& makeRefine(const TopoShape& shape,
const char* op = nullptr,
RefineFail no_fail = RefineFail::throwException);
TopoShape makeRefine(const char* op = nullptr,
RefineFail no_fail = RefineFail::throwException) const
{
return TopoShape().makeRefine(*this, op, no_fail);
}

View File

@@ -27,6 +27,7 @@
#ifndef _PreComp_
#include <BRepBuilderAPI_MakeWire.hxx>
#include <modelRefine.h>
#include <BRepCheck_Analyzer.hxx>
#include <BRepFill_Generator.hxx>
#include <BRepTools.hxx>
@@ -42,10 +43,12 @@
#include "TopoShape.h"
#include "TopoShapeCache.h"
#include "TopoShapeMapper.h"
#include "FaceMaker.h"
#include "TopoShapeOpCode.h"
#include <App/ElementNamingUtils.h>
#include <BRepLib.hxx>
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
@@ -751,7 +754,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape,
// First, collect names from other shapes that generates or modifies the
// new shape
for (auto& pinfo : infos) {
for (auto& pinfo : infos) { // Walk Vertexes, then Edges, then Faces
auto& info = *pinfo;
for (const auto & incomingShape : shapes) {
if (!canMapElement(incomingShape)) {
@@ -1511,6 +1514,57 @@ TopoShape& TopoShape::makeElementFace(const std::vector<TopoShape>& shapes,
return *this;
}
class MyRefineMaker : public BRepBuilderAPI_RefineModel
{
public:
explicit MyRefineMaker(const TopoDS_Shape &s)
:BRepBuilderAPI_RefineModel(s)
{}
void populate(ShapeMapper &mapper)
{
for (TopTools_DataMapIteratorOfDataMapOfShapeListOfShape it(this->myModified); it.More(); it.Next())
{
if (it.Key().IsNull()) continue;
mapper.populate(MappingStatus::Generated, it.Key(), it.Value());
}
}
};
TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, RefineFail no_fail)
{
if (shape.isNull()) {
if (no_fail == RefineFail::throwException) {
FC_THROWM(NullShapeException, "Null shape");
}
_Shape.Nullify();
return *this;
}
if (!op) {
op = Part::OpCodes::Refine;
}
bool closed = shape.isClosed();
try {
MyRefineMaker mkRefine(shape.getShape());
GenericShapeMapper mapper;
mkRefine.populate(mapper);
mapper.init(shape, mkRefine.Shape());
makeShapeWithElementMap(mkRefine.Shape(), mapper, {shape}, op);
// For some reason, refine operation may reverse the solid
fixSolidOrientation();
if (isClosed() == closed) {
return *this;
}
}
catch (Standard_Failure&) {
if (no_fail == RefineFail::throwException) {
throw;
}
}
*this = shape;
return *this;
}
/**
* Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name.
*
@@ -1851,4 +1905,59 @@ TopoShape& TopoShape::makeElementShellFromWires(const std::vector<TopoShape>& wi
return *this;
}
bool TopoShape::fixSolidOrientation()
{
if (isNull()) {
return false;
}
if (shapeType() == TopAbs_SOLID) {
TopoDS_Solid solid = TopoDS::Solid(_Shape);
BRepLib::OrientClosedSolid(solid);
if (solid.IsEqual(_Shape)) {
return false;
}
setShape(solid, false);
return true;
}
if (shapeType() == TopAbs_COMPOUND || shapeType() == TopAbs_COMPSOLID) {
auto shapes = getSubTopoShapes();
bool touched = false;
for (auto& s : shapes) {
if (s.fixSolidOrientation()) {
touched = true;
}
}
if (!touched) {
return false;
}
BRep_Builder builder;
if (shapeType() == TopAbs_COMPOUND) {
TopoDS_Compound comp;
builder.MakeCompound(comp);
for (auto& s : shapes) {
if (!s.isNull()) {
builder.Add(comp, s.getShape());
}
}
setShape(comp, false);
}
else {
TopoDS_CompSolid comp;
builder.MakeCompSolid(comp);
for (auto& s : shapes) {
if (!s.isNull()) {
builder.Add(comp, s.getShape());
}
}
setShape(comp, false);
}
return true;
}
return false;
}
} // namespace Part

View File

@@ -1,6 +1,31 @@
#include "PreCompiled.h"
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <BRep_Tool.hxx>
#include <TopoDS_Edge.hxx>
#include "TopoShapeMapper.h"
#include "Geometry.h"
namespace Part
{
@@ -96,4 +121,79 @@ void ShapeMapper::insert(MappingStatus status,
}
};
void GenericShapeMapper::init(const TopoShape& src, const TopoDS_Shape& dst)
{
for (TopExp_Explorer exp(dst, TopAbs_FACE); exp.More(); exp.Next()) {
const TopoDS_Shape& dstFace = exp.Current();
if (src.findShape(dstFace)) {
continue;
}
#if OCC_VERSION_HEX < 0x070800
struct TopoDS_ShapeHasher
{
std::size_t operator()(const TopoDS_Shape& key) const
{
return key.HashCode(IntegerLast());
}
};
std::unordered_map<TopoDS_Shape, int, TopoDS_ShapeHasher> map;
#else
std::unordered_map<TopoDS_Shape, int> map;
#endif
bool found = false;
// Try to find a face in the src that shares at least two edges (or one
// closed edge) with dstFace.
// TODO: consider degenerative cases of two or more edges on the same line.
for (TopExp_Explorer it(dstFace, TopAbs_EDGE); it.More(); it.Next()) {
int idx = src.findShape(it.Current());
if (!idx) {
continue;
}
TopoDS_Edge e = TopoDS::Edge(it.Current());
if (BRep_Tool::IsClosed(e)) {
// closed edge, one face is enough
TopoDS_Shape face =
src.findAncestorShape(src.getSubShape(TopAbs_EDGE, idx), TopAbs_FACE);
if (!face.IsNull()) {
this->insert(MappingStatus::Generated, face, dstFace);
found = true;
break;
}
continue;
}
for (auto& face :
src.findAncestorsShapes(src.getSubShape(TopAbs_EDGE, idx), TopAbs_FACE)) {
int& cnt = map[face];
if (++cnt == 2) {
this->insert(MappingStatus::Generated, face, dstFace);
found = true;
break;
}
if (found) {
break;
}
}
}
if (found) {
continue;
}
// if no face matches, try search by geometry surface
std::unique_ptr<Geometry> g(Geometry::fromShape(dstFace));
if (!g) {
continue;
}
for (auto& v : map) {
std::unique_ptr<Geometry> g2(Geometry::fromShape(v.first));
if (g2 && g2->isSame(*g, 1e-7, 1e-12)) {
this->insert(MappingStatus::Generated, v.first, dstFace);
break;
}
}
}
}
} // namespace Part

View File

@@ -1,9 +1,32 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <map>
#include <unordered_set>
#include <vector>
#include <Standard_Version.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS.hxx>
#include <TopExp_Explorer.hxx>
#include "TopoShape.h"
@@ -212,6 +235,13 @@ struct PartExport ShapeMapper: TopoShape::Mapper
std::unordered_set<TopoDS_Shape, ShapeHasher, ShapeHasher> _modifiedShapes;
};
/** Generic shape mapper from a given source to an output shape
*/
struct PartExport GenericShapeMapper: ShapeMapper {
/// Populate the map with a given source shape to an output shape
void init(const TopoShape &src, const TopoDS_Shape &dst);
};
/// Parameters for TopoShape::makeElementFilledFace()
struct PartExport TopoShape::BRepFillingParams
{

View File

@@ -210,7 +210,7 @@ public:
private:
void LogModifications(const ModelRefine::FaceUniter& uniter);
private:
protected:
TopTools_DataMapOfShapeListOfShape myModified;
TopTools_ListOfShape myEmptyList;
TopTools_ListOfShape myDeleted;

View File

@@ -15,6 +15,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeElementRefine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShape.cpp

View File

@@ -642,7 +642,6 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting)
auto transform {gp_Trsf()};
transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0));
cube2.Move(TopLoc_Location(transform));
// Arrange
Part::TopoShape topoShape {cube1};
std::vector<Part::TopoShape> shapes;
for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) {
@@ -655,7 +654,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting)
// Act
Part::TopoShape topoShape1 {1L};
topoShape1.makeElementCompound(shapes, "D");
// Act / Assert
// Assert
EXPECT_THROW(topoShape1.makeElementShell(false, nullptr), Base::CADKernelError);
}

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "gtest/gtest.h"
#include <src/App/InitApplication.h>
#include "PartTestHelpers.h"
class FeaturePartMakeElementRefineTest: public ::testing::Test,
public PartTestHelpers::PartTestHelperClass
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
createTestDoc();
}
void TearDown() override
{}
};
TEST_F(FeaturePartMakeElementRefineTest, makeElementRefineBoxes)
{
// Arrange
auto _doc = App::GetApplication().getActiveDocument();
auto _fuse = dynamic_cast<Part::Fuse*>(_doc->addObject("Part::Fuse"));
_fuse->Base.setValue(_boxes[0]);
_fuse->Tool.setValue(_boxes[3]);
// Act
_fuse->execute();
Part::TopoShape ts = _fuse->Shape.getValue();
Part::TopoShape refined = ts.makeElementRefine();
double volume = PartTestHelpers::getVolume(ts.getShape());
double refinedVolume = PartTestHelpers::getVolume(refined.getShape());
Base::BoundBox3d bb = ts.getBoundBox();
// Assert
EXPECT_TRUE(bb.IsValid());
EXPECT_DOUBLE_EQ(volume, 12.0);
EXPECT_DOUBLE_EQ(refinedVolume, 12.0); // Refine shouldn't change the volume
EXPECT_EQ(ts.countSubElements("Face"), 10); // Two boxes touching each loose one face
EXPECT_EQ(ts.countSubElements("Edge"), 20); // Two boxes touching loose 4 edges
EXPECT_EQ(refined.countSubElements("Face"), 6); // After refining it is one box
EXPECT_EQ(refined.countSubElements("Edge"), 12); // 12 edges in a box
// TODO: Make sure we have an elementMap for the refine.
// Refine doesn't work on compounds, so we're going to need a binary operation or the
// like, and those don't exist yet. Once they do, this test can be expanded
}