Merge pull request #12028 from bgbsww/bgbsww-toponaming-makeElementFace

Toponaming move makEFace as makeElementFace and dependencies
This commit is contained in:
Chris Hennes
2024-01-23 08:45:21 -06:00
committed by GitHub
11 changed files with 767 additions and 34 deletions

View File

@@ -33,7 +33,9 @@
#include <memory>
#include "FaceMaker.h"
#include <App/MappedElement.h>
#include "TopoShape.h"
#include "TopoShapeOpCode.h"
TYPESYSTEM_SOURCE_ABSTRACT(Part::FaceMaker, Base::BaseClass)
@@ -46,6 +48,11 @@ void Part::FaceMaker::addWire(const TopoDS_Wire& w)
void Part::FaceMaker::addShape(const TopoDS_Shape& sh)
{
addTopoShape(sh);
}
void Part::FaceMaker::addTopoShape(const TopoShape& shape) {
const TopoDS_Shape &sh = shape.getShape();
if(sh.IsNull())
throw Base::ValueError("Input shape is null.");
switch(sh.ShapeType()){
@@ -58,11 +65,14 @@ void Part::FaceMaker::addShape(const TopoDS_Shape& sh)
case TopAbs_EDGE:
this->myWires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire());
break;
case TopAbs_FACE:
this->myInputFaces.push_back(sh);
break;
default:
throw Base::TypeError("Shape must be a wire, edge or compound. Something else was supplied.");
break;
}
this->mySourceShapes.push_back(sh);
this->mySourceShapes.push_back(shape);
}
void Part::FaceMaker::useCompound(const TopoDS_Compound& comp)
@@ -73,14 +83,29 @@ void Part::FaceMaker::useCompound(const TopoDS_Compound& comp)
}
}
void Part::FaceMaker::useTopoCompound(const TopoShape& comp)
{
for(auto &s : comp.getSubTopoShapes())
this->addTopoShape(s);
}
const TopoDS_Face& Part::FaceMaker::Face()
{
const TopoDS_Shape &sh = this->Shape();
if(sh.IsNull())
return TopoDS::Face(TopoFace().getShape());
}
const Part::TopoShape &Part::FaceMaker::TopoFace() const{
if(this->myTopoShape.isNull())
throw NullShapeException("Part::FaceMaker: result shape is null.");
if (sh.ShapeType() != TopAbs_FACE)
if (this->myTopoShape.getShape().ShapeType() != TopAbs_FACE)
throw Base::TypeError("Part::FaceMaker: return shape is not a single face.");
return TopoDS::Face(sh);
return this->myTopoShape;
}
const Part::TopoShape &Part::FaceMaker::getTopoShape() const{
if(this->myTopoShape.isNull())
throw NullShapeException("Part::FaceMaker: result shape is null.");
return this->myTopoShape;
}
#if OCC_VERSION_HEX >= 0x070600
@@ -90,7 +115,7 @@ void Part::FaceMaker::Build()
#endif
{
this->NotDone();
this->myShapesToReturn.clear();
this->myShapesToReturn = this->myInputFaces;
this->myGenerated.Clear();
this->Build_Essence();//adds stuff to myShapesToReturn
@@ -118,6 +143,7 @@ void Part::FaceMaker::Build()
if(this->myShapesToReturn.empty()){
//nothing to do, null shape will be returned.
this->myShape = TopoDS_Shape();
} else if (this->myShapesToReturn.size() == 1){
this->myShape = this->myShapesToReturn[0];
} else {
@@ -129,6 +155,67 @@ void Part::FaceMaker::Build()
}
this->myShape = cmp_res;
}
postBuild();
}
struct ElementName {
long tag;
Data::MappedName name;
Data::ElementIDRefs sids;
ElementName(long t, const Data::MappedName &n, const Data::ElementIDRefs &sids)
:tag(t),name(n), sids(sids)
{}
inline bool operator<(const ElementName &other) const {
if(tag<other.tag)
return true;
if(tag>other.tag)
return false;
return Data::ElementNameComparator()(name,other.name);
}
};
void Part::FaceMaker::postBuild() {
this->myTopoShape.setShape(this->myShape);
this->myTopoShape.Hasher = this->MyHasher;
this->myTopoShape.mapSubElement(this->mySourceShapes);
int index = 0;
const char *op = this->MyOp;
if(!op)
op = Part::OpCodes::Face;
const auto &faces = this->myTopoShape.getSubTopoShapes(TopAbs_FACE);
// name the face using the edges of its outer wire
for(auto &face : faces) {
++index;
TopoShape wire = face.splitWires();
wire.mapSubElement(face);
std::set<ElementName> edgeNames;
int count = wire.countSubShapes(TopAbs_EDGE);
for (int index2 = 1; index2 <= count; ++index2) {
Data::ElementIDRefs sids;
Data::MappedName name =
face.getMappedName(Data::IndexedName::fromConst("Edge", index2), false, &sids);
if (!name) {
continue;
}
edgeNames.emplace(wire.getElementHistory(name), name, sids);
}
if (edgeNames.empty()) {
continue;
}
std::vector<Data::MappedName> names;
Data::ElementIDRefs sids;
// We just use the first source element name to make the face name more
// stable
names.push_back(edgeNames.begin()->name);
sids = edgeNames.begin()->sids;
this->myTopoShape.setElementComboName(
Data::IndexedName::fromConst("Face",index),names,op,nullptr,&sids);
}
this->myTopoShape.initCache(true);
this->Done();
}
@@ -172,12 +259,12 @@ TYPESYSTEM_SOURCE(Part::FaceMakerSimple, Part::FaceMakerPublic)
std::string Part::FaceMakerSimple::getUserFriendlyName() const
{
return {QT_TRANSLATE_NOOP("Part_FaceMaker","Simple")};
return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Simple"));
}
std::string Part::FaceMakerSimple::getBriefExplanation() const
{
return {QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes.")};
return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes."));
}
void Part::FaceMakerSimple::Build_Essence()

View File

@@ -33,6 +33,8 @@
#include <Base/BaseClass.h>
#include <Mod/Part/PartGlobal.h>
#include <App/StringHasher.h>
#include "TopoShape.h"
namespace Part
{
@@ -47,11 +49,16 @@ namespace Part
*/
class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseClass
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
TYPESYSTEM_HEADER();
public:
FaceMaker() = default;
~FaceMaker() override = default;
FaceMaker() {}
virtual ~FaceMaker() {}
void addTopoShape(const TopoShape &s);
void useTopoCompound(const TopoShape &comp);
const TopoShape &getTopoShape() const;
const TopoShape &TopoFace() const;
virtual void addWire(const TopoDS_Wire& w);
/**
@@ -69,6 +76,8 @@ public:
*/
virtual void useCompound(const TopoDS_Compound &comp);
virtual void setPlane(const gp_Pln &) {}
/**
* @brief Face: returns the face (result). If result is not a single face,
* throws Base::TypeError. (hint: use .Shape() instead)
@@ -79,7 +88,7 @@ public:
#if OCC_VERSION_HEX >= 0x070600
void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) override;
#else
void Build() override;
virtual void Build();
#endif
//fails to compile, huh!
@@ -90,11 +99,16 @@ public:
static std::unique_ptr<FaceMaker> ConstructFromType(const char* className);
static std::unique_ptr<FaceMaker> ConstructFromType(Base::Type type);
const char *MyOp = 0;
App::StringHasherRef MyHasher;
protected:
std::vector<TopoDS_Shape> mySourceShapes; //wire or compound
std::vector<TopoShape> mySourceShapes; //wire or compound
std::vector<TopoDS_Wire> myWires; //wires from mySourceShapes
std::vector<TopoDS_Compound> myCompounds; //compounds, for recursive processing
std::vector<TopoDS_Shape> myShapesToReturn;
std::vector<TopoDS_Shape> myInputFaces;
TopoShape myTopoShape;
/**
* @brief Build_Essence: build routine that can assume there is no nesting.
@@ -106,6 +120,7 @@ protected:
* whole Build().
*/
virtual void Build_Essence() = 0;
void postBuild();
static void throwNotImplemented();
};
@@ -115,7 +130,7 @@ protected:
*/
class PartExport FaceMakerPublic : public FaceMaker
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
TYPESYSTEM_HEADER();
public:
virtual std::string getUserFriendlyName() const = 0;
virtual std::string getBriefExplanation() const = 0;

View File

@@ -356,14 +356,14 @@ void FaceMakerExtrusion::Build()
if (mySourceShapes.empty())
throw Base::ValueError("No input shapes!");
if (mySourceShapes.size() == 1) {
inputShape = mySourceShapes[0];
inputShape = mySourceShapes[0].getShape();
}
else {
TopoDS_Builder builder;
TopoDS_Compound cmp;
builder.MakeCompound(cmp);
for (const TopoDS_Shape& sh : mySourceShapes) {
builder.Add(cmp, sh);
for (const auto &sh : mySourceShapes) {
builder.Add(cmp, sh.getShape());
}
inputShape = cmp;
}

View File

@@ -81,7 +81,7 @@ class PartExport ShapeSegment: public Data::Segment
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
ShapeSegment(const TopoDS_Shape& ShapeIn)
explicit ShapeSegment(const TopoDS_Shape& ShapeIn)
: Shape(ShapeIn)
{}
ShapeSegment() = default;
@@ -257,7 +257,7 @@ public:
bool analyze(bool runBopCheck, std::ostream&) const;
bool isClosed() const;
bool isCoplanar(const TopoShape& other, double tol = -1) const;
bool findPlane(gp_Pln& pln, double tol = -1) const;
bool findPlane(gp_Pln& plane, double tol = -1) const;
/// Returns true if the expansion of the shape is infinite, false otherwise
bool isInfinite() const;
/// Checks whether the shape is a planar face
@@ -387,6 +387,27 @@ public:
TopoDS_Shape defeaturing(const std::vector<TopoDS_Shape>& s) const;
TopoDS_Shape makeShell(const TopoDS_Shape&) const;
//@}
/// Wire re-orientation when calling splitWires()
enum SplitWireReorient {
/// Keep original reorientation
NoReorient,
/// Make outer wire forward, and inner wires reversed
Reorient,
/// Make both outer and inner wires forward
ReorientForward,
/// Make both outer and inner wires reversed
ReorientReversed,
};
/** Return the outer and inner wires of a face
*
* @param inner: optional output of inner wires
* @param reorient: wire reorientation, see SplitWireReorient
*
* @return Return the outer wire
*/
TopoShape splitWires(std::vector<TopoShape> *inner = nullptr,
SplitWireReorient reorient = Reorient) const;
/** @name Element name mapping aware shape maker
*
@@ -571,6 +592,11 @@ public:
const std::string& shapeName(bool silent = false) const;
static std::pair<TopAbs_ShapeEnum, int> shapeTypeAndIndex(const char* name);
Data::MappedName setElementComboName(const Data::IndexedName & element,
const std::vector<Data::MappedName> &names,
const char *marker=nullptr,
const char *op=nullptr,
const Data::ElementIDRefs *sids=nullptr);
/** @name sub shape cached functions
*
@@ -650,6 +676,59 @@ public:
*/
TopoShape &makeElementCompound(const std::vector<TopoShape> &shapes, const char *op=nullptr, bool force=true);
TopoShape& makeElementFace(const std::vector<TopoShape>& shapes,
const char* op = nullptr,
const char* maker = nullptr,
const gp_Pln* plane = nullptr);
/** Make a planar face with the input wire or edge
*
* @param shape: input shape. Can be either edge, wire, or compound of
* those two types
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param maker: optional type name of the face maker. If not given,
* default to "Part::FaceMakerBullseye"
* @param plane: optional plane of the face.
*
* @return The function creates a planar face. The original content of this
* TopoShape is discarded and replaced with the new shape. The
* function returns the TopoShape itself as a reference so that
* multiple operations can be carried out for the same shape in the
* same line of code.
*/
TopoShape& makeElementFace(const TopoShape& shape,
const char* op = nullptr,
const char* maker = nullptr,
const gp_Pln* plane = nullptr);
/** Make a planar face using this shape
*
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param maker: optional type name of the face maker. If not given,
* default to "Part::FaceMakerBullseye"
* @param plane: optional plane of the face.
*
* @return The function returns a new planar face made using the wire or edge
* inside this shape. The shape itself is not modified.
*/
TopoShape makeElementFace(const char* op = nullptr,
const char* maker = nullptr,
const gp_Pln* plane = nullptr) const
{
return TopoShape(0, Hasher).makeElementFace(*this, op, maker, plane);
}
/// Filling style when making a BSpline face
enum class FillingStyle
{
/// The style with the flattest patches
Stretch,
/// A rounded style of patch with less depth than those of Curved
Coons,
/// The style with the most rounded patches
Curved,
};
struct BRepFillingParams;
/** Provides information about the continuity of a curve.

View File

@@ -26,11 +26,19 @@
#include "PreCompiled.h"
#ifndef _PreComp_
#include <BRep_Builder.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRep_Tool.hxx>
#include <BRepTools.hxx>
#include <ShapeFix_Shape.hxx>
#include <ShapeFix_ShapeTolerance.hxx>
#endif
#include "TopoShape.h"
#include "TopoShapeCache.h"
#include "FaceMaker.h"
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
@@ -475,7 +483,7 @@ void TopoShape::mapCompoundSubElements(const std::vector<TopoShape>& shapes, con
++count;
auto subshape = getSubShape(TopAbs_SHAPE, count, /*silent = */ true);
if (!subshape.IsPartner(topoShape._Shape)) {
return; // Not a partner shape, don't do any mapping at all
return; // Not a partner shape, don't do any mapping at all
}
}
auto children {createChildMap(count, shapes, op)};
@@ -543,4 +551,250 @@ TopoShape::makeElementCompound(const std::vector<TopoShape>& shapes, const char*
return *this;
}
TopoShape& TopoShape::makeElementFace(const TopoShape& shape,
const char* op,
const char* maker,
const gp_Pln* plane)
{
std::vector<TopoShape> shapes;
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
if (shape.getShape().ShapeType() == TopAbs_COMPOUND) {
shapes = shape.getSubTopoShapes();
}
else {
shapes.push_back(shape);
}
return makeElementFace(shapes, op, maker, plane);
}
TopoShape& TopoShape::makeElementFace(const std::vector<TopoShape>& shapes,
const char* op,
const char* maker,
const gp_Pln* plane)
{
if (!maker || !maker[0]) {
maker = "Part::FaceMakerBullseye";
}
std::unique_ptr<FaceMaker> mkFace = FaceMaker::ConstructFromType(maker);
mkFace->MyHasher = Hasher;
mkFace->MyOp = op;
if (plane) {
mkFace->setPlane(*plane);
}
for (auto& shape : shapes) {
if (shape.getShape().ShapeType() == TopAbs_COMPOUND) {
mkFace->useTopoCompound(shape);
}
else {
mkFace->addTopoShape(shape);
}
}
mkFace->Build();
const auto& ret = mkFace->getTopoShape();
setShape(ret._Shape);
Hasher = ret.Hasher;
resetElementMap(ret.elementMap());
if (!isValid()) {
ShapeFix_ShapeTolerance aSFT;
aSFT.LimitTolerance(getShape(),
Precision::Confusion(),
Precision::Confusion(),
TopAbs_SHAPE);
// In some cases, the OCC reports the returned shape having invalid
// tolerance. Not sure about the real cause.
//
// Update: one of the cause is related to OCC bug in
// BRepBuilder_FindPlane, A possible call sequence is,
//
// makEOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface
//
// See code comments in findPlane() for the description of the bug and
// work around.
ShapeFix_Shape fixer(getShape());
fixer.Perform();
setShape(fixer.Shape(), false);
if (!isValid()) {
FC_WARN("makeElementFace: resulting face is invalid");
}
}
return *this;
}
/**
* Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name.
*
* @param element The element name(type) that provides 1 one character suffix to the name IF <conditions>.
* @param names The subnames to build the name from. If empty, return the TopoShape MappedName.
* @param marker The elementMap name or suffix to start the name with. If null, use the
* elementMapPrefix.
* @param op The op text passed to the element name encoder along with the TopoShape Tag
* @param _sids If defined, records the sub ids processed.
*
* @return The encoded, possibly hashed name.
*/
Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element,
const std::vector<Data::MappedName>& names,
const char* marker,
const char* op,
const Data::ElementIDRefs* _sids)
{
if (names.empty()) {
return Data::MappedName();
}
std::string _marker;
if (!marker) {
marker = elementMapPrefix().c_str();
}
else if (!boost::starts_with(marker, elementMapPrefix())) {
_marker = elementMapPrefix() + marker;
marker = _marker.c_str();
}
auto it = names.begin();
Data::MappedName newName = *it;
std::ostringstream ss;
Data::ElementIDRefs sids;
if (_sids) {
sids = *_sids;
}
if (names.size() == 1) {
ss << marker;
}
else {
bool first = true;
ss.str("");
if (!Hasher) {
ss << marker;
}
ss << '(';
for (++it; it != names.end(); ++it) {
if (first) {
first = false;
}
else {
ss << '|';
}
ss << *it;
}
ss << ')';
if (Hasher) {
sids.push_back(Hasher->getID(ss.str().c_str()));
ss.str("");
ss << marker << sids.back().toString();
}
}
elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op);
return elementMap()->setElementName(element, newName, Tag, &sids);
}
/**
* Reorient the outer and inner wires of the TopoShape
*
* @param inner If this is not a nullptr, then any inner wires processed will be returned in this
* vector.
* @param reorient One of NoReorient, Reorient ( Outer forward, inner reversed ),
* ReorientForward ( all forward ), or ReorientReversed ( all reversed )
* @return The outer wire, or an empty TopoShape if this isn't a Face, has no Face subShapes, or the
* outer wire isn't found.
*/
TopoShape TopoShape::splitWires(std::vector<TopoShape>* inner, SplitWireReorient reorient) const
{
// ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source
// code shows it works by creating face using each wire, and then test using
// BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an out
// bound wire. And practice shows it sometimes returns the incorrect
// result. Need more investigation. Note that this may be related to
// unreliable solid face orientation
// (https://forum.freecadweb.org/viewtopic.php?p=446006#p445674)
//
// Use BrepTools::OuterWire() instead. OCC source code shows it is
// implemented using simple bound box checking. This should be a
// reliable method, especially so for a planar face.
TopoDS_Shape tmp;
if (shapeType(true) == TopAbs_FACE) {
tmp = BRepTools::OuterWire(TopoDS::Face(_Shape));
}
else if (countSubShapes(TopAbs_FACE) == 1) {
tmp = BRepTools::OuterWire(TopoDS::Face(getSubShape(TopAbs_FACE, 1)));
}
if (tmp.IsNull()) {
return TopoShape();
}
const auto& wires = getSubTopoShapes(TopAbs_WIRE);
auto it = wires.begin();
TopAbs_Orientation orientOuter, orientInner;
switch (reorient) {
case ReorientReversed:
orientOuter = orientInner = TopAbs_REVERSED;
break;
case ReorientForward:
orientOuter = orientInner = TopAbs_FORWARD;
break;
default:
orientOuter = TopAbs_FORWARD;
orientInner = TopAbs_REVERSED;
break;
}
auto doReorient = [](TopoShape& s, TopAbs_Orientation orient) {
// Special case of single edge wire. Make sure the edge is in the
// required orientation. This is necessary because BRepFill_OffsetWire
// has special handling of circular edge offset, which seem to only
// respect the edge orientation and disregard the wire orientation. The
// orientation is used to determine whether to shrink or expand.
if (s.countSubShapes(TopAbs_EDGE) == 1) {
TopoDS_Shape e = s.getSubShape(TopAbs_EDGE, 1);
if (e.Orientation() == orient) {
if (s._Shape.Orientation() == orient) {
return;
}
}
else {
e = e.Oriented(orient);
}
BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(e));
s.setShape(mkWire.Shape(), false);
}
else if (s._Shape.Orientation() != orient) {
s.setShape(s._Shape.Oriented(orient), false);
}
};
for (; it != wires.end(); ++it) {
auto& wire = *it;
if (wire.getShape().IsSame(tmp)) {
if (inner) {
for (++it; it != wires.end(); ++it) {
inner->push_back(*it);
if (reorient) {
doReorient(inner->back(), orientInner);
}
}
}
auto res = wire;
if (reorient) {
doReorient(res, orientOuter);
}
return res;
}
if (inner) {
inner->push_back(wire);
if (reorient) {
doReorient(inner->back(), orientInner);
}
}
}
return TopoShape();
}
} // namespace Part