Merge branch 'FreeCAD:main' into fem_ccx_incrementation
This commit is contained in:
@@ -757,3 +757,235 @@ bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& seco
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getElementName to support the Export type. Other calls are passed to the original
|
||||
* method
|
||||
* @param name The name to search for, or if non existent, name of current Feature is returned
|
||||
* @param type An element type name.
|
||||
* @return The element name located, of
|
||||
*/
|
||||
std::pair<std::string, std::string> Feature::getElementName(const char* name,
|
||||
ElementNameType type) const
|
||||
{
|
||||
if (type != ElementNameType::Export) {
|
||||
return App::GeoFeature::getElementName(name, type);
|
||||
}
|
||||
|
||||
// This function is overridden to provide higher level shape topo names that
|
||||
// are generated on demand, e.g. Wire, Shell, Solid, etc.
|
||||
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyPartShape>(getPropertyOfGeometry());
|
||||
if (!prop) {
|
||||
return App::GeoFeature::getElementName(name, type);
|
||||
}
|
||||
|
||||
TopoShape shape = prop->getShape();
|
||||
Data::MappedElement mapped = shape.getElementName(name);
|
||||
auto res = shape.shapeTypeAndIndex(mapped.index);
|
||||
static const int MinLowerTopoNames = 3;
|
||||
static const int MaxLowerTopoNames = 10;
|
||||
if (res.second && !mapped.name) {
|
||||
// Here means valid index name, but no mapped name, check to see if
|
||||
// we shall generate the high level topo name.
|
||||
//
|
||||
// The general idea of the algorithm is to find the minimum number of
|
||||
// lower elements that can identify the given higher element, and
|
||||
// combine their names to generate the name for the higher element.
|
||||
//
|
||||
// In theory, all it takes to find one lower element that only appear
|
||||
// in the given higher element. To make the algorithm more robust
|
||||
// against model changes, we shall take minimum MinLowerTopoNames lower
|
||||
// elements.
|
||||
//
|
||||
// On the other hand, it may be possible to take too many elements for
|
||||
// disambiguation. We shall limit to maximum MaxLowerTopoNames. If the
|
||||
// chosen elements are not enough to disambiguate the higher element,
|
||||
// we'll include an index for disambiguation.
|
||||
|
||||
auto subshape = shape.getSubTopoShape(res.first, res.second, true);
|
||||
TopAbs_ShapeEnum lower;
|
||||
Data::IndexedName idxName;
|
||||
if (!subshape.isNull()) {
|
||||
switch (res.first) {
|
||||
case TopAbs_WIRE:
|
||||
lower = TopAbs_EDGE;
|
||||
idxName = Data::IndexedName::fromConst("Edge", 1);
|
||||
break;
|
||||
case TopAbs_SHELL:
|
||||
case TopAbs_SOLID:
|
||||
case TopAbs_COMPOUND:
|
||||
case TopAbs_COMPSOLID:
|
||||
lower = TopAbs_FACE;
|
||||
idxName = Data::IndexedName::fromConst("Face", 1);
|
||||
break;
|
||||
default:
|
||||
lower = TopAbs_SHAPE;
|
||||
}
|
||||
if (lower != TopAbs_SHAPE) {
|
||||
typedef std::pair<size_t, std::vector<int>> NameEntry;
|
||||
std::vector<NameEntry> indices;
|
||||
std::vector<Data::MappedName> names;
|
||||
std::vector<int> ancestors;
|
||||
int count = 0;
|
||||
for (auto& ss : subshape.getSubTopoShapes(lower)) {
|
||||
auto name = ss.getMappedName(idxName);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
indices.emplace_back(name.size(),
|
||||
shape.findAncestors(ss.getShape(), res.first));
|
||||
names.push_back(name);
|
||||
if (indices.back().second.size() == 1 && ++count >= MinLowerTopoNames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (names.size() >= MaxLowerTopoNames) {
|
||||
std::stable_sort(indices.begin(),
|
||||
indices.end(),
|
||||
[](const NameEntry& a, const NameEntry& b) {
|
||||
return a.second.size() < b.second.size();
|
||||
});
|
||||
std::vector<Data::MappedName> sorted;
|
||||
auto pos = 0;
|
||||
sorted.reserve(names.size());
|
||||
for (auto& v : indices) {
|
||||
size_t size = ancestors.size();
|
||||
if (size == 0) {
|
||||
ancestors = v.second;
|
||||
}
|
||||
else if (size > 1) {
|
||||
for (auto it = ancestors.begin(); it != ancestors.end();) {
|
||||
if (std::find(v.second.begin(), v.second.end(), *it)
|
||||
== v.second.end()) {
|
||||
it = ancestors.erase(it);
|
||||
if (ancestors.size() == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto itPos = sorted.end();
|
||||
if (size == 1 || size != ancestors.size()) {
|
||||
itPos = sorted.begin() + pos;
|
||||
++pos;
|
||||
}
|
||||
sorted.insert(itPos, names[v.first]);
|
||||
if (size == 1 && sorted.size() >= MinLowerTopoNames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
names.resize(std::min((int)names.size(), MaxLowerTopoNames));
|
||||
if (names.size()) {
|
||||
std::string op;
|
||||
if (ancestors.size() > 1) {
|
||||
// The current chosen elements are not enough to
|
||||
// identify the higher element, generate an index for
|
||||
// disambiguation.
|
||||
auto it = std::find(ancestors.begin(), ancestors.end(), res.second);
|
||||
if (it == ancestors.end()) {
|
||||
assert(0 && "ancestor not found"); // this shouldn't happened
|
||||
}
|
||||
else {
|
||||
op = Data::POSTFIX_TAG + std::to_string(it - ancestors.begin());
|
||||
}
|
||||
}
|
||||
|
||||
// Note: setting names to shape will change its underlying
|
||||
// shared element name table. This actually violates the
|
||||
// const'ness of this function.
|
||||
//
|
||||
// To be const correct, we should have made the element
|
||||
// name table to be implicit sharing (i.e. copy on change).
|
||||
//
|
||||
// Not sure if there is any side effect of indirectly
|
||||
// change the element map inside the Shape property without
|
||||
// recording the change in undo stack.
|
||||
//
|
||||
mapped.name = shape.setElementComboName(mapped.index,
|
||||
names,
|
||||
mapped.index.getType(),
|
||||
op.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return App::GeoFeature::_getElementName(name, mapped);
|
||||
}
|
||||
|
||||
if (!res.second && mapped.name) {
|
||||
const char* dot = strchr(name, '.');
|
||||
if (dot) {
|
||||
++dot;
|
||||
// Here means valid mapped name, but cannot find the corresponding
|
||||
// indexed name. This usually means the model has been changed. The
|
||||
// original indexed name is usually appended to the mapped name
|
||||
// separated by a dot. We use it as a clue to decode the combo name
|
||||
// set above, and try to single out one sub shape that has all the
|
||||
// lower elements encoded in the combo name. But since we don't
|
||||
// always use all the lower elements for encoding, this can only be
|
||||
// consider a heuristics.
|
||||
if (Data::hasMissingElement(dot)) {
|
||||
dot += strlen(Data::MISSING_PREFIX);
|
||||
}
|
||||
std::pair<TopAbs_ShapeEnum, int> occindex = shape.shapeTypeAndIndex(dot);
|
||||
if (occindex.second > 0) {
|
||||
auto idxName = Data::IndexedName::fromConst(shape.shapeName(occindex.first).c_str(),
|
||||
occindex.second);
|
||||
std::string postfix;
|
||||
auto names =
|
||||
shape.decodeElementComboName(idxName, mapped.name, idxName.getType(), &postfix);
|
||||
std::vector<int> ancestors;
|
||||
for (auto& name : names) {
|
||||
auto index = shape.getIndexedName(name);
|
||||
if (!index) {
|
||||
ancestors.clear();
|
||||
break;
|
||||
}
|
||||
auto oidx = shape.shapeTypeAndIndex(index);
|
||||
auto subshape = shape.getSubShape(oidx.first, oidx.second);
|
||||
if (subshape.IsNull()) {
|
||||
ancestors.clear();
|
||||
break;
|
||||
}
|
||||
auto current = shape.findAncestors(subshape, occindex.first);
|
||||
if (ancestors.empty()) {
|
||||
ancestors = std::move(current);
|
||||
}
|
||||
else {
|
||||
for (auto it = ancestors.begin(); it != ancestors.end();) {
|
||||
if (std::find(current.begin(), current.end(), *it) == current.end()) {
|
||||
it = ancestors.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (ancestors.empty()) { // model changed beyond recognition, bail!
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ancestors.size() > 1 && boost::starts_with(postfix, Data::POSTFIX_INDEX)) {
|
||||
std::istringstream iss(postfix.c_str() + strlen(Data::POSTFIX_INDEX));
|
||||
int idx;
|
||||
if (iss >> idx && idx >= 0 && idx < (int)ancestors.size()) {
|
||||
ancestors.resize(1, ancestors[idx]);
|
||||
}
|
||||
}
|
||||
if (ancestors.size() == 1) {
|
||||
idxName.setIndex(ancestors.front());
|
||||
mapped.index = idxName;
|
||||
return App::GeoFeature::_getElementName(name, mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return App::GeoFeature::getElementName(name, type);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ public:
|
||||
|
||||
PyObject* getPyObject() override;
|
||||
|
||||
std::pair<std::string,std::string> getElementName(
|
||||
const char *name, ElementNameType type=Normal) const override;
|
||||
|
||||
TopLoc_Location getLocation() const;
|
||||
|
||||
DocumentObject *getSubObject(const char *subname, PyObject **pyObj,
|
||||
|
||||
@@ -477,6 +477,22 @@ std::pair<TopAbs_ShapeEnum,int> TopoShape::shapeTypeAndIndex(const char *name) {
|
||||
return std::make_pair(type,idx);
|
||||
}
|
||||
|
||||
std::pair<TopAbs_ShapeEnum, int> TopoShape::shapeTypeAndIndex(const Data::IndexedName& element)
|
||||
{
|
||||
if (!element) {
|
||||
return std::make_pair(TopAbs_SHAPE, 0);
|
||||
}
|
||||
static const std::string _subshape("SubShape");
|
||||
if (boost::equals(element.getType(), _subshape)) {
|
||||
return std::make_pair(TopAbs_SHAPE, element.getIndex());
|
||||
}
|
||||
TopAbs_ShapeEnum shapetype = shapeType(element.getType(), true);
|
||||
if (shapetype == TopAbs_SHAPE) {
|
||||
return std::make_pair(TopAbs_SHAPE, 0);
|
||||
}
|
||||
return std::make_pair(shapetype, element.getIndex());
|
||||
}
|
||||
|
||||
TopAbs_ShapeEnum TopoShape::shapeType(const char *type, bool silent) {
|
||||
if(type) {
|
||||
initShapeNameMap();
|
||||
|
||||
@@ -1040,7 +1040,52 @@ public:
|
||||
const char* op = nullptr,
|
||||
double tol3d = 0.0,
|
||||
double tolBound = 0.0,
|
||||
double tolAngluar = 0.0);
|
||||
double tolAngular = 0.0);
|
||||
|
||||
/* Make a shape with some subshapes replaced.
|
||||
*
|
||||
* @param source: the source shape
|
||||
* @param s: replacement mapping the existing sub shape of source to new shapes
|
||||
*
|
||||
* @return The original content of this TopoShape is discarded and replaced
|
||||
* with the new 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& replaceElementShape(const TopoShape& source,
|
||||
const std::vector<std::pair<TopoShape, TopoShape>>& s);
|
||||
/* Make a new shape using this shape with some subshapes replaced by others
|
||||
*
|
||||
* @param s: replacement mapping the existing sub shape of source to new shapes
|
||||
*
|
||||
* @return Return the new shape. The TopoShape itself is not modified.
|
||||
*/
|
||||
TopoShape replaceElementShape(const std::vector<std::pair<TopoShape, TopoShape>>& s) const
|
||||
{
|
||||
return TopoShape(0, Hasher).replaceElementShape(*this, s);
|
||||
}
|
||||
|
||||
/* Make a shape with some subshapes removed
|
||||
*
|
||||
* @param source: the source shape
|
||||
* @param s: the subshapes to be removed
|
||||
*
|
||||
* @return The original content of this TopoShape is discarded and replaced
|
||||
* with the new 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& removeElementShape(const TopoShape& source, const std::vector<TopoShape>& s);
|
||||
/* Make a new shape using this shape with some subshapes removed
|
||||
*
|
||||
* @param s: the subshapes to be removed
|
||||
*
|
||||
* @return Return the new shape. The TopoShape itself is not modified.
|
||||
*/
|
||||
TopoShape removeElementShape(const std::vector<TopoShape>& s) const
|
||||
{
|
||||
return TopoShape(0, Hasher).removeElementShape(*this, s);
|
||||
}
|
||||
|
||||
/** Make shape using generalized fusion and return the modified sub shapes
|
||||
*
|
||||
@@ -1131,13 +1176,19 @@ public:
|
||||
static const std::string& shapeName(TopAbs_ShapeEnum type, bool silent = false);
|
||||
const std::string& shapeName(bool silent = false) const;
|
||||
static std::pair<TopAbs_ShapeEnum, int> shapeTypeAndIndex(const char* name);
|
||||
static std::pair<TopAbs_ShapeEnum, int> shapeTypeAndIndex(const Data::IndexedName &name);
|
||||
|
||||
Data::MappedName setElementComboName(const Data::IndexedName & element,
|
||||
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);
|
||||
|
||||
std::vector<Data::MappedName> decodeElementComboName(const Data::IndexedName& element,
|
||||
const Data::MappedName& name,
|
||||
const char* marker = nullptr,
|
||||
std::string* postfix = nullptr) const;
|
||||
|
||||
/** @name sub shape cached functions
|
||||
*
|
||||
* Mapped element names introduces some overhead when getting sub shapes
|
||||
|
||||
@@ -3059,7 +3059,7 @@ TopoShape& TopoShape::makeElementSolid(const TopoShape& shape, const char* op)
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
TopoShape& TopoShape::makeElementMirror(const TopoShape& shape, const gp_Ax2& ax2, const char* op)
|
||||
{
|
||||
if (!op) {
|
||||
@@ -3108,6 +3108,48 @@ TopoShape& TopoShape::makeElementSlices(const TopoShape& shape,
|
||||
return makeElementCompound(wires, op, SingleShapeCompoundCreationPolicy::returnShape);
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::replaceElementShape(const TopoShape& shape,
|
||||
const std::vector<std::pair<TopoShape, TopoShape>>& s)
|
||||
{
|
||||
if (shape.isNull()) {
|
||||
FC_THROWM(NullShapeException, "Null shape");
|
||||
}
|
||||
BRepTools_ReShape reshape;
|
||||
std::vector<TopoShape> shapes;
|
||||
shapes.reserve(s.size() + 1);
|
||||
for (auto& v : s) {
|
||||
if (v.first.isNull() || v.second.isNull()) {
|
||||
FC_THROWM(NullShapeException, "Null input shape");
|
||||
}
|
||||
reshape.Replace(v.first.getShape(), v.second.getShape());
|
||||
shapes.push_back(v.second);
|
||||
}
|
||||
// TODO: This does not work when replacing a shape in a compound. Should we replace with
|
||||
// something else?
|
||||
// Note that remove works with a compound.
|
||||
shapes.push_back(shape);
|
||||
setShape(reshape.Apply(shape.getShape(), TopAbs_SHAPE));
|
||||
mapSubElement(shapes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::removeElementShape(const TopoShape& shape, const std::vector<TopoShape>& s)
|
||||
{
|
||||
if (shape.isNull()) {
|
||||
FC_THROWM(NullShapeException, "Null shape");
|
||||
}
|
||||
BRepTools_ReShape reshape;
|
||||
for (auto& sh : s) {
|
||||
if (sh.isNull()) {
|
||||
FC_THROWM(NullShapeException, "Null input shape");
|
||||
}
|
||||
reshape.Remove(sh.getShape());
|
||||
}
|
||||
setShape(reshape.Apply(shape.getShape(), TopAbs_SHAPE));
|
||||
mapSubElement(shape);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TopoShape& TopoShape::makeElementFillet(const TopoShape& shape,
|
||||
const std::vector<TopoShape>& edges,
|
||||
double radius1,
|
||||
@@ -4054,6 +4096,97 @@ Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element
|
||||
return elementMap()->setElementName(element, newName, Tag, &sids);
|
||||
}
|
||||
|
||||
std::vector<Data::MappedName> TopoShape::decodeElementComboName(const Data::IndexedName& element,
|
||||
const Data::MappedName& name,
|
||||
const char* marker,
|
||||
std::string* postfix) const
|
||||
{
|
||||
std::vector<Data::MappedName> names;
|
||||
if (!element) {
|
||||
return names;
|
||||
}
|
||||
if (!marker) {
|
||||
marker = "";
|
||||
}
|
||||
int plen = (int)elementMapPrefix().size();
|
||||
int markerLen = strlen(marker);
|
||||
int len;
|
||||
int pos = name.findTagInElementName(nullptr, &len);
|
||||
if (pos < 0) {
|
||||
// It is possible to encode combo name without using a tag, e.g.
|
||||
// Sketcher object creates wire using edges that are created by itself,
|
||||
// so there will be no tag to encode.
|
||||
//
|
||||
// In this case, just search for the brackets
|
||||
len = name.find("(");
|
||||
if (len < 0) {
|
||||
// No bracket is also possible, if there is only one name in the combo
|
||||
pos = len = name.size();
|
||||
}
|
||||
else {
|
||||
pos = name.find(")");
|
||||
if (pos < 0) {
|
||||
// non closing bracket?
|
||||
return {};
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
if (len <= (int)markerLen) {
|
||||
return {};
|
||||
}
|
||||
len -= markerLen + plen;
|
||||
}
|
||||
|
||||
if (name.find(elementMapPrefix(), len) != len || name.find(marker, len + plen) != len + plen) {
|
||||
return {};
|
||||
}
|
||||
|
||||
names.emplace_back(name, 0, len);
|
||||
|
||||
std::string text;
|
||||
len += plen + markerLen;
|
||||
name.appendToBuffer(text, len, pos - len);
|
||||
|
||||
if (this->Hasher) {
|
||||
if (auto id = App::StringID::fromString(names.back().toRawBytes())) {
|
||||
if (App::StringIDRef sid = this->Hasher->getID(id)) {
|
||||
names.pop_back();
|
||||
names.emplace_back(sid);
|
||||
}
|
||||
else {
|
||||
return names;
|
||||
}
|
||||
}
|
||||
if (auto id = App::StringID::fromString(text.c_str())) {
|
||||
if (App::StringIDRef sid = this->Hasher->getID(id)) {
|
||||
text = sid.dataToText();
|
||||
}
|
||||
else {
|
||||
return names;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text.empty() || text[0] != '(') {
|
||||
return names;
|
||||
}
|
||||
auto endPos = text.rfind(')');
|
||||
if (endPos == std::string::npos) {
|
||||
return names;
|
||||
}
|
||||
|
||||
if (postfix) {
|
||||
*postfix = text.substr(endPos + 1);
|
||||
}
|
||||
|
||||
text.resize(endPos);
|
||||
std::istringstream iss(text.c_str() + 1);
|
||||
std::string token;
|
||||
while (std::getline(iss, token, '|')) {
|
||||
names.emplace_back(token);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorient the outer and inner wires of the TopoShape
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user