Files
create/src/Mod/Part/App/PartFeature.cpp
2024-12-13 18:04:10 +01:00

2037 lines
77 KiB
C++

/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <sstream>
# include <Bnd_Box.hxx>
# include <BRepAdaptor_Curve.hxx>
# include <Mod/Part/App/FCBRepAlgoAPI_Fuse.h>
# include <Mod/Part/App/FCBRepAlgoAPI_Common.h>
# include <BRepBndLib.hxx>
# include <BRepBuilderAPI_MakeEdge.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
# include <BRepBuilderAPI_MakeVertex.hxx>
# include <BRepBuilderAPI_MakeShape.hxx>
# include <BRepBuilderAPI_MakeVertex.hxx>
# include <BRepExtrema_DistShapeShape.hxx>
# include <BRepGProp.hxx>
# include <BRepGProp_Face.hxx>
# include <BRepIntCurveSurface_Inter.hxx>
# include <gce_MakeDir.hxx>
# include <gce_MakeLin.hxx>
# include <gp_Ax1.hxx>
# include <gp_Dir.hxx>
# include <gp_Pln.hxx>
# include <gp_Trsf.hxx>
# include <GProp_GProps.hxx>
# include <IntCurveSurface_IntersectionPoint.hxx>
# include <Precision.hxx>
# include <Standard_Failure.hxx>
# include <Standard_Version.hxx>
# include <TopExp.hxx>
# include <TopExp_Explorer.hxx>
# include <TopoDS.hxx>
# include <TopTools_IndexedMapOfShape.hxx>
# include <TopTools_ListIteratorOfListOfShape.hxx>
#endif
#include <App/Application.h>
#include <App/Document.h>
#include <App/FeaturePythonPyImp.h>
#include <App/GeoFeature.h>
#include <App/Link.h>
#include <App/GeoFeatureGroupExtension.h>
#include <App/ElementNamingUtils.h>
#include <App/Placement.h>
#include <App/Datums.h>
#include <Base/Exception.h>
#include <Base/Placement.h>
#include <Base/Rotation.h>
#include <Base/Stream.h>
#include <Mod/Material/App/MaterialManager.h>
#include "Geometry.h"
#include "PartFeature.h"
#include "PartFeaturePy.h"
#include "PartPyCXX.h"
#include "TopoShapePy.h"
#include "Base/Tools.h"
using namespace Part;
namespace sp = std::placeholders;
FC_LOG_LEVEL_INIT("Part",true,true)
PROPERTY_SOURCE(Part::Feature, App::GeoFeature)
Feature::Feature()
{
ADD_PROPERTY(Shape, (TopoDS_Shape()));
auto mat = Materials::MaterialManager::defaultMaterial();
ADD_PROPERTY(ShapeMaterial, (*mat));
// Read only properties based on the material
static const char* group = "PhysicalProperties";
ADD_PROPERTY_TYPE(MaterialName,
(""),
group,
static_cast<App::PropertyType>(App::Prop_ReadOnly | App::Prop_Output
| App::Prop_NoRecompute | App::Prop_NoPersist),
"Feature material");
ADD_PROPERTY_TYPE(Density,
(0.0),
group,
static_cast<App::PropertyType>(App::Prop_ReadOnly | App::Prop_Output
| App::Prop_NoRecompute | App::Prop_NoPersist),
"Feature density");
ADD_PROPERTY_TYPE(Mass,
(0.0),
group,
static_cast<App::PropertyType>(App::Prop_ReadOnly | App::Prop_Output
| App::Prop_NoRecompute | App::Prop_NoPersist),
"Feature mass");
ADD_PROPERTY_TYPE(Volume,
(1.0),
group,
static_cast<App::PropertyType>(App::Prop_ReadOnly | App::Prop_Output
| App::Prop_NoRecompute | App::Prop_NoPersist),
"Feature volume");
}
Feature::~Feature() = default;
short Feature::mustExecute() const
{
return GeoFeature::mustExecute();
}
App::DocumentObjectExecReturn *Feature::recompute()
{
try {
return App::GeoFeature::recompute();
}
catch (Standard_Failure& e) {
App::DocumentObjectExecReturn* ret = new App::DocumentObjectExecReturn(e.GetMessageString());
if (ret->Why.empty()) ret->Why = "Unknown OCC exception";
return ret;
}
}
App::DocumentObjectExecReturn *Feature::execute()
{
this->Shape.touch();
return GeoFeature::execute();
}
PyObject *Feature::getPyObject()
{
if (PythonObject.is(Py::_None())){
// ref counter is set to 1
PythonObject = Py::Object(new PartFeaturePy(this),true);
}
return Py::new_reference_to(PythonObject);
}
void Feature::copyMaterial(Feature* feature)
{
auto mat = Materials::MaterialManager::defaultMaterial();
if (feature) {
if (ShapeMaterial.getValue().getUUID() != feature->ShapeMaterial.getValue().getUUID()) {
if (ShapeMaterial.getValue().getUUID() == mat->getUUID()) {
ShapeMaterial.setValue(feature->ShapeMaterial.getValue());
}
}
}
}
void Feature::copyMaterial(App::DocumentObject* link)
{
auto feature = dynamic_cast<Part::Feature*>(link);
if (feature) {
copyMaterial(feature);
}
}
/**
* 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 a struct with the newName and oldName. New element name may be empty.
*/
App::ElementNamePair 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);
}
return getExportElementName(prop->getShape(), name);
}
App::ElementNamePair Feature::getExportElementName(TopoShape shape,
const char* name) const
{
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(names.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 happen
}
else {
op = Data::POSTFIX_INDEX + 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());
}
}
}
}
else 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;
if ( names.empty() ) {
// Naming based heuristic has failed to find the element. Let's see if we can
// find it by matching either planes for faces or lines for edges.
auto searchShape = this->Shape.getShape();
// If we're still out at a Shell, Solid, CompSolid, or Compound drill in
while (searchShape.getShape().ShapeType() < TopAbs_FACE ) {
auto shapes = searchShape.getSubTopoShapes();
if ( shapes.empty() ) // No more subshapes, so don't continue
break;
searchShape = shapes.front(); // After the break, so we stopped at innermost container
}
auto newMapped = TopoShape::chooseMatchingSubShapeByPlaneOrLine(shape, searchShape);
if ( ! newMapped.name.empty() )
mapped = newMapped;
}
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);
}
App::DocumentObject* Feature::getSubObject(const char* subname,
PyObject** pyObj,
Base::Matrix4D* pmat,
bool transform,
int depth) const
{
while(subname && *subname=='.') ++subname; // skip leading .
// having '.' inside subname means it is referencing some children object,
// instead of any sub-element from ourself
if (subname && !Data::isMappedElement(subname) && strchr(subname, '.')) {
return App::DocumentObject::getSubObject(subname, pyObj, pmat, transform, depth);
}
Base::Matrix4D _mat;
auto& mat = pmat ? *pmat : _mat;
if (transform) {
mat *= Placement.getValue().toMatrix();
}
if (!pyObj) {
// TopoShape::hasSubShape is kind of slow, let's cut outself some slack here.
return const_cast<Feature*>(this);
}
try {
TopoShape ts(Shape.getShape());
bool doTransform = mat != ts.getTransform();
if (doTransform) {
ts.setShape(ts.getShape().Located(TopLoc_Location()), false);
}
if (subname && *subname && !ts.isNull()) {
ts = ts.getSubTopoShape(subname,true);
}
if (doTransform && !ts.isNull()) {
static int sCopy = -1;
if (sCopy < 0) {
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Part/General");
sCopy = hGrp->GetBool("CopySubShape", false) ? 1 : 0;
}
bool copy = sCopy ? true : false;
if (!copy) {
// Work around OCC bug on transforming circular edge with an
// offset surface. The bug probably affect other shape type,
// too.
TopExp_Explorer exp(ts.getShape(), TopAbs_EDGE);
if (exp.More()) {
auto edge = TopoDS::Edge(exp.Current());
exp.Next();
if (!exp.More()) {
BRepAdaptor_Curve curve(edge);
copy = curve.GetType() == GeomAbs_Circle;
}
}
}
ts.transformShape(mat, copy, true);
}
*pyObj = Py::new_reference_to(shape2pyshape(ts));
return const_cast<Feature*>(this);
}
catch (Standard_Failure& e) {
// FIXME: Do not handle the exception here because it leads to a flood of irrelevant and
// annoying error messages.
// For example: https://forum.freecad.org/viewtopic.php?f=19&t=42216
// Instead either raise a sub-class of Base::Exception and let it handle by the calling
// instance or do simply nothing. For now the error message is degraded to a log message.
std::ostringstream str;
Standard_CString msg = e.GetMessageString();
// Avoid name mangling
str << e.DynamicType()->get_type_name() << " ";
if (msg) {
str << msg;
}
else {
str << "No OCCT Exception Message";
}
str << ": " << getFullName();
if (subname) {
str << '.' << subname;
}
FC_LOG(str.str());
return nullptr;
}
}
static std::vector<std::pair<long, Data::MappedName>> getElementSource(App::DocumentObject* owner,
TopoShape shape,
const Data::MappedName& name,
char type)
{
std::set<std::pair<App::Document*, long>> tagSet;
std::vector<std::pair<long, Data::MappedName>> ret;
ret.emplace_back(0, name);
int depth = 0;
while (1) {
Data::MappedName original;
std::vector<Data::MappedName> history;
// It is possible the name does not belong to the shape, e.g. when user
// changes modeling order in PartDesign. So we try to assign the
// document hasher here in case getElementHistory() needs to de-hash
if (!shape.Hasher && owner) {
shape.Hasher = owner->getDocument()->getStringHasher();
}
long tag = shape.getElementHistory(ret.back().second, &original, &history);
if (!tag) {
break;
}
auto obj = owner;
App::Document* doc = nullptr;
if (owner) {
doc = owner->getDocument();
for (;; ++depth) {
auto linked = owner->getLinkedObject(false, nullptr, false, depth);
if (linked == owner) {
break;
}
owner = linked;
if (owner->getDocument() != doc) {
doc = owner->getDocument();
break;
}
}
if (owner->isDerivedFrom(App::GeoFeature::getClassTypeId())) {
auto ownerGeoFeature =
static_cast<App::GeoFeature*>(owner)->getElementOwner(ret.back().second);
if (ownerGeoFeature) {
doc = ownerGeoFeature->getDocument();
}
}
obj = doc->getObjectByID(tag < 0 ? -tag : tag);
if (type) {
for (auto& hist : history) {
if (shape.elementType(hist) != type) {
return ret;
}
}
}
}
owner = 0;
if (!obj) {
// Object maybe deleted, but it is still possible to extract the
// source element name from hasher table.
shape.setShape(TopoDS_Shape());
doc = nullptr;
}
else {
shape = Part::Feature::getTopoShape(obj, 0, false, 0, &owner);
}
if (type && shape.elementType(original) != type) {
break;
}
if (std::abs(tag) != ret.back().first && !tagSet.insert(std::make_pair(doc, tag)).second) {
// Because an object might be deleted, which may be a link/binder
// that points to an external object that contain element name
// using external hash table. We shall prepare for circular element
// map due to looking up in the wrong table.
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("circular element mapping");
}
break;
}
ret.emplace_back(tag, original);
}
return ret;
}
std::list<Data::HistoryItem> Feature::getElementHistory(App::DocumentObject* feature,
const char* name,
bool recursive,
bool sameType)
{
std::list<Data::HistoryItem> ret;
TopoShape shape = getTopoShape(feature);
Data::IndexedName idx(name);
Data::MappedName element;
Data::MappedName prevElement;
if (idx) {
element = shape.getMappedName(idx, true);
}
else if (Data::isMappedElement(name)) {
element = Data::MappedName(Data::newElementName(name));
}
else {
element = Data::MappedName(name);
}
char element_type = 0;
if (sameType) {
element_type = shape.elementType(element);
}
int depth = 0;
do {
Data::MappedName original;
ret.emplace_back(feature, element);
long tag = shape.getElementHistory(element, &original, &ret.back().intermediates);
ret.back().index = shape.getIndexedName(element);
if (!ret.back().index && prevElement) {
ret.back().index = shape.getIndexedName(prevElement);
if (ret.back().index) {
ret.back().intermediates.insert(ret.back().intermediates.begin(), element);
ret.back().element = prevElement;
}
}
if (ret.back().intermediates.size()) {
prevElement = ret.back().intermediates.back();
}
else {
prevElement = Data::MappedName();
}
App::DocumentObject* obj = nullptr;
if (tag) {
App::Document* doc = feature->getDocument();
for (;; ++depth) {
auto linked = feature->getLinkedObject(false, nullptr, false, depth);
if (linked == feature) {
break;
}
feature = linked;
if (feature->getDocument() != doc) {
doc = feature->getDocument();
break;
}
}
if (feature->isDerivedFrom(App::GeoFeature::getClassTypeId())) {
auto ownerGeoFeature =
static_cast<App::GeoFeature*>(feature)->getElementOwner(element);
if (ownerGeoFeature) {
doc = ownerGeoFeature->getDocument();
}
}
obj = doc->getObjectByID(std::abs(tag));
}
if (!recursive) {
ret.emplace_back(obj, original);
ret.back().tag = tag;
return ret;
}
if (!obj) {
break;
}
if (element_type) {
for (auto& hist : ret.back().intermediates) {
if (shape.elementType(hist) != element_type) {
return ret;
}
}
}
feature = obj;
shape = Feature::getTopoShape(feature);
element = original;
if (element_type && shape.elementType(original) != element_type) {
break;
}
} while (feature);
return ret;
}
QVector<Data::MappedElement> Feature::getElementFromSource(App::DocumentObject* obj,
const char* subname,
App::DocumentObject* src,
const char* srcSub,
bool single)
{
QVector<Data::MappedElement> res;
if (!obj || !src) {
return res;
}
auto shape = getTopoShape(obj,
subname,
false,
nullptr,
nullptr,
true,
/*transform = */ false);
App::DocumentObject* owner = nullptr;
auto srcShape = getTopoShape(src, srcSub, false, nullptr, &owner);
int tagChanges;
Data::MappedElement element;
Data::IndexedName checkingSubname;
std::string sub = Data::noElementName(subname);
auto checkHistory = [&](const Data::MappedName& name, size_t, long, long tag) {
if (std::abs(tag) == owner->getID()) {
if (!tagChanges) {
tagChanges = 1;
}
}
else if (tagChanges && ++tagChanges > 3) {
// Once we found the tag, trace no more than 2 addition tag changes
// to limited the search depth.
return true;
}
if (name == element.name) {
App::ElementNamePair objElement;
std::size_t len = sub.size();
checkingSubname.appendToStringBuffer(sub);
GeoFeature::resolveElement(obj, sub.c_str(), objElement);
sub.resize(len);
if (objElement.oldName.size()) {
res.push_back(Data::MappedElement(Data::MappedName(objElement.newName),
Data::IndexedName(objElement.oldName.c_str())));
return true;
}
}
return false;
};
// obtain both the old and new style element name
App::ElementNamePair objElement;
GeoFeature::resolveElement(src, srcSub, objElement, false);
element.index = Data::IndexedName(objElement.oldName.c_str());
if (!objElement.newName.empty()) {
// Strip prefix and indexed based name at the tail of the new style element name
auto mappedName = Data::newElementName(objElement.newName.c_str());
auto mapped = Data::isMappedElement(mappedName.c_str());
if (mapped) {
element.name = Data::MappedName(mapped);
}
}
// Translate the element name for datum
if (objElement.oldName == "Plane") {
objElement.oldName = "Face1";
}
else if (objElement.oldName == "Line") {
objElement.oldName = "Edge1";
}
else if (objElement.oldName == "Point") {
objElement.oldName = "Vertex1";
}
// Use the old style name to obtain the shape type
auto type = TopoShape::shapeType(Data::findElementName(objElement.oldName.c_str()));
// If the given shape has the same number of sub shapes as the source (e.g.
// a compound operation), then take a shortcut and assume the element index
// remains the same. But we still need to trace the shape history to
// confirm.
if (type != TopAbs_SHAPE && element.name
&& shape.countSubShapes(type) == srcShape.countSubShapes(type)) {
tagChanges = 0;
checkingSubname = element.index;
auto mapped = shape.getMappedName(element.index);
shape.traceElement(mapped, checkHistory);
if (res.size()) {
return res;
}
}
// Try geometry search first
auto subShape = srcShape.getSubShape(objElement.oldName.c_str());
std::vector<std::string> names;
shape.findSubShapesWithSharedVertex(subShape, &names);
if (names.size()) {
for (auto& name : names) {
Data::MappedElement e;
e.index = Data::IndexedName(name.c_str());
e.name = shape.getMappedName(e.index, true);
res.append(e);
if (single) {
break;
}
}
return res;
}
if (!element.name || type == TopAbs_SHAPE) {
return res;
}
// No shortcut, need to search every element of the same type. This may
// result in multiple matches, e.g. a compound of array of the same
// instance.
const char* shapetype = TopoShape::shapeName(type).c_str();
for (int i = 0, count = shape.countSubShapes(type); i < count; ++i) {
checkingSubname = Data::IndexedName::fromConst(shapetype, i + 1);
auto mapped = shape.getMappedName(checkingSubname);
tagChanges = 0;
shape.traceElement(mapped, checkHistory);
if (single && res.size()) {
break;
}
}
return res;
}
QVector<Data::MappedElement> Feature::getRelatedElements(App::DocumentObject* obj,
const char* name,
HistoryTraceType sameType,
bool withCache)
{
auto owner = obj;
auto shape = getTopoShape(obj, nullptr, false, 0, &owner);
QVector<Data::MappedElement> ret;
Data::MappedElement mapped = shape.getElementName(name);
if (!mapped.name) {
return ret;
}
if (withCache && shape.getRelatedElementsCached(mapped.name, sameType, ret)) {
return ret;
}
char element_type = shape.elementType(mapped.name);
TopAbs_ShapeEnum type = TopoShape::shapeType(element_type, true);
if (type == TopAbs_SHAPE) {
return ret;
}
auto source =
getElementSource(owner,
shape,
mapped.name,
sameType == HistoryTraceType::followTypeChange ? element_type : 0);
for (auto& src : source) {
auto srcIndex = shape.getIndexedName(src.second);
if (srcIndex) {
ret.push_back(Data::MappedElement(src.second, srcIndex));
shape.cacheRelatedElements(mapped.name, sameType, ret);
return ret;
}
}
std::map<int, QVector<Data::MappedElement>> retMap;
const char* shapetype = TopoShape::shapeName(type).c_str();
std::ostringstream ss;
for (size_t i = 1; i <= shape.countSubShapes(type); ++i) {
Data::MappedElement related;
related.index = Data::IndexedName::fromConst(shapetype, i);
related.name = shape.getMappedName(related.index);
if (!related.name) {
continue;
}
auto src =
getElementSource(owner,
shape,
related.name,
sameType == HistoryTraceType::followTypeChange ? element_type : 0);
int idx = (int)source.size() - 1;
for (auto rit = src.rbegin(); idx >= 0 && rit != src.rend(); ++rit, --idx) {
// TODO: shall we ignore source tag when comparing? It could cause
// matching unrelated element, but it does help dealing with feature
// recording in PartDesign::Body.
if (rit->second != source[idx].second) {
++idx;
break;
}
}
if (idx < (int)source.size()) {
retMap[idx].push_back(related);
}
}
if (retMap.size()) {
ret = retMap.begin()->second;
}
shape.cacheRelatedElements(mapped.name, sameType, ret);
return ret;
}
TopoDS_Shape Feature::getShape(const App::DocumentObject *obj, const char *subname,
bool needSubElement, Base::Matrix4D *pmat, App::DocumentObject **powner,
bool resolveLink, bool transform)
{
return getTopoShape(obj,subname,needSubElement,pmat,powner,resolveLink,transform,true).getShape();
}
App::Material Feature::getMaterialAppearance() const
{
return ShapeMaterial.getValue().getMaterialAppearance();
}
void Feature::setMaterialAppearance(const App::Material& material)
{
try {
ShapeMaterial.setValue(material);
}
catch (const Base::Exception& e) {
e.ReportException();
}
}
// Toponaming project March 2024: This method should be going away when we get to the python layer.
void Feature::clearShapeCache() {
// _ShapeCache.cache.clear();
}
static TopoShape _getTopoShape(const App::DocumentObject* obj,
const char* subname,
bool needSubElement,
Base::Matrix4D* pmat,
App::DocumentObject** powner,
bool resolveLink,
bool noElementMap,
const std::set<std::string> hiddens,
const App::DocumentObject* lastLink)
{
TopoShape shape;
if (!obj) {
return shape;
}
PyObject* pyobj = nullptr;
Base::Matrix4D mat;
if (powner) {
*powner = nullptr;
}
std::string _subname;
auto subelement = Data::findElementName(subname);
if (!needSubElement && subname) {
// strip out element name if not needed
if (subelement && *subelement) {
_subname = std::string(subname, subelement);
subname = _subname.c_str();
}
}
auto canCache = [&](const App::DocumentObject* o) {
return !lastLink || (hiddens.empty() && !App::GeoFeatureGroupExtension::isNonGeoGroup(o));
};
if (canCache(obj) && PropertyShapeCache::getShape(obj, shape, subname)) {
if (noElementMap) {
shape.resetElementMap();
shape.Tag = 0;
if ( shape.Hasher ) {
shape.Hasher = nullptr;
}
}
}
App::DocumentObject* linked = nullptr;
App::DocumentObject* owner = nullptr;
Base::Matrix4D linkMat;
App::StringHasherRef hasher;
long tag;
{
Base::PyGILStateLocker lock;
owner = obj->getSubObject(subname, shape.isNull() ? &pyobj : nullptr, &mat, false);
if (!owner) {
return shape;
}
tag = owner->getID();
hasher = owner->getDocument()->getStringHasher();
linked = owner->getLinkedObject(true, &linkMat, false);
if (pmat) {
if (resolveLink && obj != owner) {
*pmat = mat * linkMat;
}
else {
*pmat = mat;
}
}
if (!linked) {
linked = owner;
}
if (powner) {
*powner = resolveLink ? linked : owner;
}
if (!shape.isNull()) {
return shape;
}
if (pyobj && PyObject_TypeCheck(pyobj, &TopoShapePy::Type)) {
shape = *static_cast<TopoShapePy*>(pyobj)->getTopoShapePtr();
if (!shape.isNull()) {
if (canCache(obj)) {
if (obj->getDocument() != linked->getDocument()
|| mat.hasScale() != Base::ScaleType::NoScaling
|| (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) {
PropertyShapeCache::setShape(obj, shape, subname);
}
}
if (noElementMap) {
shape.resetElementMap();
shape.Tag = 0;
if ( shape.Hasher ) {
shape.Hasher = nullptr;
}
}
Py_DECREF(pyobj);
return shape;
}
}
else {
if (linked->isDerivedFrom(App::Line::getClassTypeId())) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeEdge builder(gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
_shape = builder.Shape();
_shape.Infinite(Standard_True);
}
shape = TopoShape(tag, hasher, _shape);
}
else if (linked->isDerivedFrom(App::Plane::getClassTypeId())) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
_shape = builder.Shape();
_shape.Infinite(Standard_True);
}
shape = TopoShape(tag, hasher, _shape);
}
else if (linked->isDerivedFrom(App::Point::getClassTypeId())) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeVertex builder(gp_Pnt(0, 0, 0));
_shape = builder.Shape();
}
shape = TopoShape(tag, hasher, _shape);
}
else if (linked->isDerivedFrom(App::Placement::getClassTypeId())) {
auto element = Data::findElementName(subname);
if (element) {
if (boost::iequals("x", element) || boost::iequals("x-axis", element)
|| boost::iequals("y", element) || boost::iequals("y-axis", element)
|| boost::iequals("z", element) || boost::iequals("z-axis", element)) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeEdge builder(
gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
_shape = builder.Shape();
_shape.Infinite(Standard_True);
}
shape = TopoShape(tag, hasher, _shape);
}
else if (boost::iequals("o", element) || boost::iequals("origin", element)) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeVertex builder(gp_Pnt(0, 0, 0));
_shape = builder.Shape();
_shape.Infinite(Standard_True);
}
shape = TopoShape(tag, hasher, _shape);
}
}
if (shape.isNull()) {
static TopoDS_Shape _shape;
if (_shape.IsNull()) {
BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
_shape = builder.Shape();
_shape.Infinite(Standard_True);
}
shape = TopoShape(tag, hasher, _shape);
}
}
if (!shape.isNull()) {
shape.transformShape(mat * linkMat, false, true);
return shape;
}
}
Py_XDECREF(pyobj);
}
// nothing can be done if there is sub-element references
if (needSubElement && subelement && *subelement) {
return shape;
}
if (obj != owner) {
if (canCache(owner) && PropertyShapeCache::getShape(owner, shape)) {
bool scaled = shape.transformShape(mat, false, true);
if (owner->getDocument() != obj->getDocument()) {
shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher());
PropertyShapeCache::setShape(obj, shape, subname);
}
else if (scaled
|| (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) {
PropertyShapeCache::setShape(obj, shape, subname);
}
}
if (!shape.isNull()) {
if (noElementMap) {
shape.resetElementMap();
shape.Tag = 0;
if ( shape.Hasher) {
shape.Hasher = nullptr;
}
}
return shape;
}
}
bool cacheable = true;
auto link = owner->getExtensionByType<App::LinkBaseExtension>(true);
if (owner != linked
&& (!link || (!link->_ChildCache.getSize() && link->getSubElements().size() <= 1))) {
// if there is a linked object, and there is no child cache (which is used
// for special handling of plain group), obtain shape from the linked object
shape = Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false);
if (shape.isNull()) {
return shape;
}
if (owner == obj) {
shape.transformShape(mat * linkMat, false, true);
}
else {
shape.transformShape(linkMat, false, true);
}
shape.reTagElementMap(tag, hasher);
}
else {
// Construct a compound of sub objects
std::vector<TopoShape> shapes;
// Acceleration for link array. Unlike non-array link, a link array does
// not return the linked object when calling getLinkedObject().
// Therefore, it should be handled here.
TopoShape baseShape;
Base::Matrix4D baseMat;
std::string op;
if (link && link->getElementCountValue()) {
linked = link->getTrueLinkedObject(false, &baseMat);
if (linked && linked != owner) {
baseShape =
Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false);
if (!link->getShowElementValue()) {
baseShape.reTagElementMap(owner->getID(),
owner->getDocument()->getStringHasher());
}
}
}
for (auto& sub : owner->getSubObjects()) {
if (sub.empty()) {
continue;
}
int visible;
std::string childName;
App::DocumentObject* parent = nullptr;
Base::Matrix4D mat = baseMat;
App::DocumentObject* subObj = nullptr;
if (sub.find('.') == std::string::npos) {
visible = 1;
}
else {
subObj =
owner->resolve(sub.c_str(), &parent, &childName, nullptr, nullptr, &mat, false);
if (!parent || !subObj) {
continue;
}
if (lastLink && App::GeoFeatureGroupExtension::isNonGeoGroup(parent)) {
visible = lastLink->isElementVisible(childName.c_str());
}
else {
visible = parent->isElementVisible(childName.c_str());
}
}
if (visible == 0) {
continue;
}
std::set<std::string> nextHiddens = hiddens;
const App::DocumentObject* nextLink = lastLink;
// Todo: This might belong.
// Toponaming project March 2024: This appears to be a non toponaming feature:
// if (!checkLinkVisibility(nextHiddens, true, nextLink, owner, sub.c_str())) {
// cacheable = false;
// continue;
// }
TopoShape shape;
bool doGetShape = (!subObj || baseShape.isNull());
if (!doGetShape) {
auto type = mat.hasScale();
if (type != Base::ScaleType::NoScaling && type != Base::ScaleType::Uniform) {
doGetShape = true;
}
}
if (doGetShape) {
shape = _getTopoShape(owner,
sub.c_str(),
true,
0,
&subObj,
false,
false,
nextHiddens,
nextLink);
if (shape.isNull()) {
continue;
}
if (visible < 0 && subObj && !subObj->Visibility.getValue()) {
continue;
}
}
else {
if (link && !link->getShowElementValue()) {
shape =
baseShape.makeElementTransform(mat,
(Data::POSTFIX_INDEX + childName).c_str());
}
else {
shape = baseShape.makeElementTransform(mat);
shape.reTagElementMap(subObj->getID(),
subObj->getDocument()->getStringHasher());
}
}
shapes.push_back(shape);
}
if (shapes.empty()) {
return shape;
}
shape.Tag = tag;
shape.Hasher = hasher;
shape.makeElementCompound(shapes);
}
if (cacheable && canCache(owner)) {
PropertyShapeCache::setShape(owner, shape);
}
if (owner != obj) {
bool scaled = shape.transformShape(mat, false, true);
if (owner->getDocument() != obj->getDocument()) {
shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher());
scaled = true; // force cache
}
if (canCache(obj) && scaled) {
PropertyShapeCache::setShape(obj, shape, subname);
}
}
if (noElementMap) {
shape.resetElementMap();
shape.Tag = 0;
if ( shape.Hasher ) {
shape.Hasher = nullptr;
}
}
return shape;
}
TopoShape Feature::getTopoShape(const App::DocumentObject* obj,
const char* subname,
bool needSubElement,
Base::Matrix4D* pmat,
App::DocumentObject** powner,
bool resolveLink,
bool transform,
bool noElementMap)
{
if (!obj || !obj->getNameInDocument()) {
return TopoShape();
}
const App::DocumentObject* lastLink = 0;
std::set<std::string> hiddens;
// Toponaming project March 2024: This appears to be a non toponaming feature:
// Todo is this a cause behind #13886 ?
// if (!checkLinkVisibility(hiddens, false, lastLink, obj, subname)) {
// return TopoShape();
// }
// NOTE! _getTopoShape() always return shape without top level
// transformation for easy shape caching, i.e. with `transform` set
// to false. So we manually apply the top level transform if asked.
if (needSubElement && (!pmat || *pmat == Base::Matrix4D())
&& obj->isDerivedFrom(Part::Feature::getClassTypeId())
&& !obj->hasExtension(App::LinkBaseExtension::getExtensionClassTypeId())) {
// Some OCC shape making is very sensitive to shape transformation. So
// check here if a direct sub shape is required, and bypass all extra
// processing here.
if (subname && *subname && Data::findElementName(subname) == subname) {
TopoShape ts = static_cast<const Part::Feature*>(obj)->Shape.getShape();
if (!transform) {
ts.setShape(ts.getShape().Located(TopLoc_Location()), false);
}
if (noElementMap) {
ts = ts.getSubShape(subname, true);
}
else {
ts = ts.getSubTopoShape(subname, true);
}
if (!ts.isNull()) {
if (powner) {
*powner = const_cast<App::DocumentObject*>(obj);
}
if (pmat && transform) {
*pmat = static_cast<const Part::Feature*>(obj)->Placement.getValue().toMatrix();
}
return ts;
}
}
}
Base::Matrix4D mat;
auto shape = _getTopoShape(obj,
subname,
needSubElement,
&mat,
powner,
resolveLink,
noElementMap,
hiddens,
lastLink);
if (needSubElement && shape.shapeType(true) == TopAbs_COMPOUND) {
if (shape.countSubShapes(TopAbs_SOLID) == 1)
shape = shape.getSubTopoShape(TopAbs_SOLID, 1);
else if (shape.countSubShapes(TopAbs_COMPSOLID) == 1)
shape = shape.getSubTopoShape(TopAbs_COMPSOLID, 1);
else if (shape.countSubShapes(TopAbs_FACE) == 1)
shape = shape.getSubTopoShape(TopAbs_FACE, 1);
else if (shape.countSubShapes(TopAbs_SHELL) == 1)
shape = shape.getSubTopoShape(TopAbs_SHELL, 1);
else if (shape.countSubShapes(TopAbs_EDGE) == 1)
shape = shape.getSubTopoShape(TopAbs_EDGE, 1);
else if (shape.countSubShapes(TopAbs_WIRE) == 1)
shape = shape.getSubTopoShape(TopAbs_WIRE, 1);
else if (shape.countSubShapes(TopAbs_VERTEX) == 1)
shape = shape.getSubTopoShape(TopAbs_VERTEX, 1);
}
Base::Matrix4D topMat;
if (pmat || transform) {
// Obtain top level transformation
if (pmat) {
topMat = *pmat;
}
if (transform) {
obj->getSubObject(nullptr, nullptr, &topMat);
}
// Apply the top level transformation
if (!shape.isNull()) {
shape.transformShape(topMat, false, true);
}
if (pmat) {
*pmat = topMat * mat;
}
}
return shape;
}
App::DocumentObject *Feature::getShapeOwner(const App::DocumentObject *obj, const char *subname)
{
if(!obj)
return nullptr;
auto owner = obj->getSubObject(subname);
if(owner) {
auto linked = owner->getLinkedObject(true);
if(linked)
owner = linked;
}
return owner;
}
struct Feature::ElementCache
{
TopoShape shape;
mutable std::vector<std::string> names;
mutable bool searched;
};
void Feature::registerElementCache(const std::string& prefix, PropertyPartShape* prop)
{
if (prop) {
_elementCachePrefixMap.emplace_back(prefix, prop);
return;
}
for (auto it = _elementCachePrefixMap.begin(); it != _elementCachePrefixMap.end();) {
if (it->first == prefix) {
_elementCachePrefixMap.erase(it);
break;
}
}
}
void Feature::onBeforeChange(const App::Property* prop)
{
PropertyPartShape* propShape = nullptr;
const std::string* prefix = nullptr;
if (prop == &Shape) {
propShape = &Shape;
}
else {
for (const auto& v : _elementCachePrefixMap) {
if (prop == v.second) {
prefix = &v.first;
propShape = v.second;
}
}
}
if (propShape) {
if (_elementCachePrefixMap.empty()) {
_elementCache.clear();
}
else {
for (auto it = _elementCache.begin(); it != _elementCache.end();) {
bool remove;
if (prefix) {
remove = boost::starts_with(it->first, *prefix);
}
else {
remove = true;
for (const auto& v : _elementCache) {
if (boost::starts_with(it->first, v.first)) {
remove = false;
break;
}
}
}
if (remove) {
it = _elementCache.erase(it);
}
else {
++it;
}
}
}
if (getDocument() && !getDocument()->testStatus(App::Document::Restoring)
&& !getDocument()->isPerformingTransaction()) {
std::vector<App::DocumentObject*> objs;
std::vector<std::string> subs;
for (auto prop : App::PropertyLinkBase::getElementReferences(this)) {
if (!prop->getContainer()) {
continue;
}
objs.clear();
subs.clear();
prop->getLinks(objs, true, &subs, false);
for (auto& sub : subs) {
auto element = Data::findElementName(sub.c_str());
if (!element || !element[0] || Data::hasMissingElement(element)) {
continue;
}
if (prefix) {
if (!boost::starts_with(element, *prefix)) {
continue;
}
}
else {
bool found = false;
for (const auto& v : _elementCachePrefixMap) {
if (boost::starts_with(element, v.first)) {
found = true;
break;
}
}
if (found) {
continue;
}
}
auto res =
_elementCache.insert(std::make_pair(std::string(element), ElementCache()));
if (res.second) {
res.first->second.searched = false;
res.first->second.shape = propShape->getShape().getSubTopoShape(
element + (prefix ? prefix->size() : 0),
true);
}
}
}
}
}
GeoFeature::onBeforeChange(prop);
}
void Feature::onChanged(const App::Property* prop)
{
// if the placement has changed apply the change to the point data as well
if (prop == &this->Placement) {
TopoShape shape = this->Shape.getShape();
auto oldTransform = shape.getTransform();
auto newTransform = this->Placement.getValue().toMatrix();
shape.setTransform(newTransform);
Base::ObjectStatusLocker<App::Property::Status, App::Property> guard(
App::Property::NoRecompute,
&this->Shape);
if ( oldTransform != newTransform) {
this->Shape.setValue(shape);
}
}
// if the point data has changed check and adjust the transformation as well
else if (prop == &this->Shape) {
if (this->isRecomputing()) {
this->Shape._Shape.setTransform(this->Placement.getValue().toMatrix());
}
else {
Base::Placement p;
// shape must not be null to override the placement
if (!this->Shape.getValue().IsNull()) {
try {
p.fromMatrix(this->Shape.getShape().getTransform());
this->Placement.setValueIfChanged(p);
}
catch (const Base::ValueError&) {
}
}
}
updatePhysicalProperties();
} else if (prop == &this->ShapeMaterial) {
updatePhysicalProperties();
}
GeoFeature::onChanged(prop);
}
void Feature::updatePhysicalProperties()
{
MaterialName.setValue(ShapeMaterial.getValue().getName().toStdString());
if (ShapeMaterial.getValue().hasPhysicalProperty(QString::fromLatin1("Density"))) {
Density.setValue(ShapeMaterial.getValue()
.getPhysicalQuantity(QString::fromLatin1("Density"))
.getValue());
} else {
Base::Console().Log("Density is undefined\n");
Density.setValue(0.0);
}
auto topoShape = Shape.getValue();
if (!topoShape.IsNull()) {
GProp_GProps props;
BRepGProp::VolumeProperties(topoShape, props);
Volume.setValue(props.Mass());
Mass.setValue(Volume.getValue() * Density.getValue());
} else {
// No shape
Base::Console().Log("No shape defined\n");
Volume.setValue(0.0);
Mass.setValue(0.0);
}
}
const std::vector<std::string>& Feature::searchElementCache(const std::string& element,
Data::SearchOptions options,
double tol,
double atol) const
{
static std::vector<std::string> none;
if (element.empty()) {
return none;
}
auto it = _elementCache.find(element);
if (it == _elementCache.end() || it->second.shape.isNull()) {
return none;
}
if (!it->second.searched) {
auto propShape = &Shape;
const std::string* prefix = nullptr;
for (const auto& v : _elementCachePrefixMap) {
if (boost::starts_with(element, v.first)) {
propShape = v.second;
prefix = &v.first;
break;
}
}
it->second.searched = true;
propShape->getShape().findSubShapesWithSharedVertex(it->second.shape,
&it->second.names,
options,
tol,
atol);
if (prefix) {
for (auto& name : it->second.names) {
if (auto dot = strrchr(name.c_str(), '.')) {
name.insert(dot + 1 - name.c_str(), *prefix);
}
else {
name.insert(0, *prefix);
}
}
}
}
return it->second.names;
}
TopLoc_Location Feature::getLocation() const
{
Base::Placement pl = this->Placement.getValue();
Base::Rotation rot(pl.getRotation());
Base::Vector3d axis;
double angle;
rot.getValue(axis, angle);
gp_Trsf trf;
trf.SetRotation(gp_Ax1(gp_Pnt(), gp_Dir(axis.x, axis.y, axis.z)), angle);
trf.SetTranslationPart(gp_Vec(pl.getPosition().x,pl.getPosition().y,pl.getPosition().z));
return TopLoc_Location(trf);
}
Feature* Feature::create(const TopoShape& shape, const char* name, App::Document* document)
{
if (!name || !name[0]) {
name = "Shape";
}
if (!document) {
document = App::GetApplication().getActiveDocument();
if (!document) {
document = App::GetApplication().newDocument();
}
}
auto res = static_cast<Part::Feature*>(document->addObject("Part::Feature", name));
res->Shape.setValue(shape);
res->purgeTouched();
return res;
}
ShapeHistory Feature::buildHistory(BRepBuilderAPI_MakeShape& mkShape, TopAbs_ShapeEnum type,
const TopoDS_Shape& newS, const TopoDS_Shape& oldS)
{
ShapeHistory history;
history.type = type;
TopTools_IndexedMapOfShape newM, oldM;
TopExp::MapShapes(newS, type, newM); // map containing all old objects of type "type"
TopExp::MapShapes(oldS, type, oldM); // map containing all new objects of type "type"
// Look at all objects in the old shape and try to find the modified object in the new shape
for (int i=1; i<=oldM.Extent(); i++) {
bool found = false;
TopTools_ListIteratorOfListOfShape it;
// Find all new objects that are a modification of the old object (e.g. a face was resized)
for (it.Initialize(mkShape.Modified(oldM(i))); it.More(); it.Next()) {
found = true;
for (int j=1; j<=newM.Extent(); j++) { // one old object might create several new ones!
if (newM(j).IsPartner(it.Value())) {
history.shapeMap[i-1].push_back(j-1); // adjust indices to start at zero
break;
}
}
}
// Find all new objects that were generated from an old object (e.g. a face generated from an edge)
for (it.Initialize(mkShape.Generated(oldM(i))); it.More(); it.Next()) {
found = true;
for (int j=1; j<=newM.Extent(); j++) {
if (newM(j).IsPartner(it.Value())) {
history.shapeMap[i-1].push_back(j-1);
break;
}
}
}
if (!found) {
// Find all old objects that don't exist any more (e.g. a face was completely cut away)
if (mkShape.IsDeleted(oldM(i))) {
history.shapeMap[i-1] = std::vector<int>();
}
else {
// Mop up the rest (will this ever be reached?)
for (int j=1; j<=newM.Extent(); j++) {
if (newM(j).IsPartner(oldM(i))) {
history.shapeMap[i-1].push_back(j-1);
break;
}
}
}
}
}
return history;
}
ShapeHistory Feature::joinHistory(const ShapeHistory& oldH, const ShapeHistory& newH)
{
ShapeHistory join;
join.type = oldH.type;
for (const auto & it : oldH.shapeMap) {
int old_shape_index = it.first;
if (it.second.empty())
join.shapeMap[old_shape_index] = ShapeHistory::List();
for (const auto& jt : it.second) {
const auto& kt = newH.shapeMap.find(jt);
if (kt != newH.shapeMap.end()) {
ShapeHistory::List& ary = join.shapeMap[old_shape_index];
ary.insert(ary.end(), kt->second.begin(), kt->second.end());
}
}
}
return join;
}
/// returns the type name of the ViewProvider
const char* Feature::getViewProviderName() const {
return "PartGui::ViewProviderPart";
}
const App::PropertyComplexGeoData* Feature::getPropertyOfGeometry() const
{
return &Shape;
}
bool Feature::isElementMappingDisabled(App::PropertyContainer* container)
{
(void)container;
return false;
// TODO: March 2024 consider if any of this RT branch logic makes sense:
// if (!container) {
// return false;
// }
// auto prop = propDisableMapping(container, /*forced*/ false);
// if (prop && prop->getValue()) {
// return true;
// }
// if (auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(container)) {
// if (auto doc = obj->getDocument()) {
// if (auto prop = propDisableMapping(doc, /*forced*/ false)) {
// return prop->getValue();
// }
// }
// }
// return false;
}
bool Feature::getCameraAlignmentDirection(Base::Vector3d& direction, const char* subname) const
{
const auto topoShape = getTopoShape(this, subname, true);
if (topoShape.isNull()) {
return false;
}
// Face normal
if (topoShape.isPlanar()) {
try {
const auto face = TopoDS::Face(topoShape.getShape());
gp_Pnt point;
gp_Vec vector;
BRepGProp_Face(face).Normal(0, 0, point, vector);
direction = Base::Vector3d(vector.X(), vector.Y(), vector.Z()).Normalize();
return true;
}
catch (Standard_TypeMismatch&) {
// Shape is not a face, do nothing
}
}
// Edge direction
const size_t edgeCount = topoShape.countSubShapes(TopAbs_EDGE);
if (edgeCount == 1 && topoShape.isLinearEdge()) {
if (const std::unique_ptr<Geometry> geometry = Geometry::fromShape(topoShape.getSubShape(TopAbs_EDGE, 1), true)) {
const std::unique_ptr<GeomLine> geomLine(static_cast<GeomCurve*>(geometry.get())->toLine());
if (geomLine) {
direction = geomLine->getDir().Normalize();
return true;
}
}
}
return GeoFeature::getCameraAlignmentDirection(direction, subname);
}
void Feature::guessNewLink(std::string &replacementName, DocumentObject *base, const char *oldLink) {
for (auto &element : Part::Feature::getRelatedElements(base, oldLink)) {
replacementName.clear();
element.index.appendToStringBuffer(replacementName);
FC_WARN("Feature guess element reference " << oldLink << " -> " << replacementName);
return;
}
replacementName = oldLink;
}
// ---------------------------------------------------------
PROPERTY_SOURCE(Part::FilletBase, Part::Feature)
FilletBase::FilletBase()
{
ADD_PROPERTY(Base,(nullptr));
ADD_PROPERTY(Edges,(0,0,0));
ADD_PROPERTY_TYPE(EdgeLinks,(0), 0,
(App::PropertyType)(App::Prop_ReadOnly|App::Prop_Hidden),0);
Edges.setSize(0);
}
short FilletBase::mustExecute() const
{
if (Base.isTouched() || Edges.isTouched() || EdgeLinks.isTouched())
return 1;
return 0;
}
App::DocumentObjectExecReturn* FilletBase::execute()
{
App::DocumentObject* link = this->Base.getValue();
if (!link) {
return new App::DocumentObjectExecReturn("No object linked");
}
copyMaterial(link);
return Part::Feature::execute();
}
void FilletBase::onChanged(const App::Property *prop) {
if(getDocument() && !getDocument()->testStatus(App::Document::Restoring)) {
if(prop == &Edges || prop == &Base) {
if(!prop->testStatus(App::Property::User3))
syncEdgeLink();
}
}
Feature::onChanged(prop);
}
void FilletBase::onDocumentRestored() {
if(EdgeLinks.getSubValues().empty())
syncEdgeLink();
Feature::onDocumentRestored();
}
void FilletBase::syncEdgeLink() {
if(!Base.getValue() || !Edges.getSize()) {
EdgeLinks.setValue(0);
return;
}
std::vector<std::string> subs;
std::string sub("Edge");
for(auto &info : Edges.getValues())
subs.emplace_back(sub+std::to_string(info.edgeid));
EdgeLinks.setValue(Base.getValue(),subs);
}
void FilletBase::onUpdateElementReference(const App::Property *prop) {
if(prop!=&EdgeLinks || !getNameInDocument())
return;
auto values = Edges.getValues();
const auto &subs = EdgeLinks.getSubValues();
for(size_t i=0;i<values.size();++i) {
if(i>=subs.size()) {
FC_WARN("fillet edge count mismatch in object " << getFullName());
break;
}
int idx = 0;
sscanf(subs[i].c_str(),"Edge%d",&idx);
if(idx)
values[i].edgeid = idx;
else
FC_WARN("invalid fillet edge link '" << subs[i] << "' in object "
<< getFullName());
}
Edges.setStatus(App::Property::User3,true);
Edges.setValues(values);
Edges.setStatus(App::Property::User3,false);
}
// ---------------------------------------------------------
PROPERTY_SOURCE(Part::FeatureExt, Part::Feature)
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(Part::FeaturePython, Part::Feature)
template<> const char* Part::FeaturePython::getViewProviderName() const {
return "PartGui::ViewProviderPython";
}
template<> PyObject* Part::FeaturePython::getPyObject() {
if (PythonObject.is(Py::_None())) {
// ref counter is set to 1
PythonObject = Py::Object(new FeaturePythonPyT<Part::PartFeaturePy>(this),true);
}
return Py::new_reference_to(PythonObject);
}
/// @endcond
// explicit template instantiation
template class PartExport FeaturePythonT<Part::Feature>;
}
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
std::vector<Part::cutFaces> Part::findAllFacesCutBy(
const TopoDS_Shape& shape, const TopoDS_Shape& face, const gp_Dir& dir)
{
// Find the centre of gravity of the face
GProp_GProps props;
BRepGProp::SurfaceProperties(face,props);
gp_Pnt cog = props.CentreOfMass();
// create a line through the centre of gravity
gp_Lin line = gce_MakeLin(cog, dir);
// Find intersection of line with all faces of the shape
std::vector<cutFaces> result;
BRepIntCurveSurface_Inter mkSection;
// TODO: Less precision than Confusion() should be OK?
for (mkSection.Init(shape, line, Precision::Confusion()); mkSection.More(); mkSection.Next()) {
gp_Pnt iPnt = mkSection.Pnt();
double dsq = cog.SquareDistance(iPnt);
if (dsq < Precision::Confusion())
continue; // intersection with original face
// Find out which side of the original face the intersection is on
gce_MakeDir mkDir(cog, iPnt);
if (!mkDir.IsDone())
continue; // some error (appears highly unlikely to happen, though...)
if (mkDir.Value().IsOpposite(dir, Precision::Confusion()))
continue; // wrong side of face (opposite to extrusion direction)
cutFaces newF;
newF.face = mkSection.Face();
newF.distsq = dsq;
result.push_back(newF);
}
return result;
}
std::vector<Part::cutTopoShapeFaces>
Part::findAllFacesCutBy(const TopoShape& shape, const TopoShape& face, const gp_Dir& dir)
{
// Find the centre of gravity of the face
GProp_GProps props;
BRepGProp::SurfaceProperties(face.getShape(), props);
gp_Pnt cog = props.CentreOfMass();
// create a line through the centre of gravity
gp_Lin line = gce_MakeLin(cog, dir);
// Find intersection of line with all faces of the shape
std::vector<cutTopoShapeFaces> result;
BRepIntCurveSurface_Inter mkSection;
// TODO: Less precision than Confusion() should be OK?
for (mkSection.Init(shape.getShape(), line, Precision::Confusion()); mkSection.More();
mkSection.Next()) {
gp_Pnt iPnt = mkSection.Pnt();
double dsq = cog.SquareDistance(iPnt);
if (dsq < Precision::Confusion()) {
continue; // intersection with original face
}
// Find out which side of the original face the intersection is on
gce_MakeDir mkDir(cog, iPnt);
if (!mkDir.IsDone()) {
continue; // some error (appears highly unlikely to happen, though...)
}
if (mkDir.Value().IsOpposite(dir, Precision::Confusion())) {
continue; // wrong side of face (opposite to extrusion direction)
}
cutTopoShapeFaces newF;
newF.face = mkSection.Face();
newF.face.mapSubElement(shape);
newF.distsq = dsq;
result.push_back(newF);
}
return result;
}
bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& second,
const bool quick, const bool touch_is_intersection) {
Bnd_Box first_bb, second_bb;
BRepBndLib::Add(first, first_bb);
first_bb.SetGap(0);
BRepBndLib::Add(second, second_bb);
second_bb.SetGap(0);
// Note: This test fails if the objects are touching one another at zero distance
// Improving reliability: If it fails sometimes when touching and touching is intersection,
// then please check further unless the user asked for a quick potentially unreliable result
if (first_bb.IsOut(second_bb) && !touch_is_intersection)
return false; // no intersection
if (quick && !first_bb.IsOut(second_bb))
return true; // assumed intersection
if (touch_is_intersection) {
// If both shapes fuse to a single solid, then they intersect
FCBRepAlgoAPI_Fuse mkFuse(first, second);
if (!mkFuse.IsDone())
return false;
if (mkFuse.Shape().IsNull())
return false;
// Did we get one or two solids?
TopExp_Explorer xp;
xp.Init(mkFuse.Shape(),TopAbs_SOLID);
if (xp.More()) {
// At least one solid
xp.Next();
return (xp.More() == Standard_False);
} else {
return false;
}
} else {
// If both shapes have common material, then they intersect
FCBRepAlgoAPI_Common mkCommon(first, second);
if (!mkCommon.IsDone())
return false;
if (mkCommon.Shape().IsNull())
return false;
// Did we get a solid?
TopExp_Explorer xp;
xp.Init(mkCommon.Shape(),TopAbs_SOLID);
return (xp.More() == Standard_True);
}
}