"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
1128 lines
42 KiB
C++
1128 lines
42 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2015 Stefan Tröger <stefantroeger@gmx.net> *
|
|
* *
|
|
* 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 <gp_Lin.hxx>
|
|
# include <gp_Pln.hxx>
|
|
# include <BRep_Builder.hxx>
|
|
# include <BRepBuilderAPI_MakeEdge.hxx>
|
|
# include <BRepBuilderAPI_MakeFace.hxx>
|
|
# include <BRepBuilderAPI_MakeVertex.hxx>
|
|
|
|
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Document.h>
|
|
#include <App/GroupExtension.h>
|
|
#include <App/Link.h>
|
|
#include <App/Datums.h>
|
|
#include <App/ElementNamingUtils.h>
|
|
#include <Mod/Part/App/TopoShape.h>
|
|
|
|
#include "ShapeBinder.h"
|
|
#include "Mod/Part/App/TopoShapeOpCode.h"
|
|
#include "Base/Tools.h"
|
|
|
|
FC_LOG_LEVEL_INIT("PartDesign",true,true)
|
|
|
|
using namespace PartDesign;
|
|
namespace sp = std::placeholders;
|
|
|
|
namespace PartDesign
|
|
{
|
|
extern bool getPDRefineModelParameter();
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
PROPERTY_SOURCE(PartDesign::ShapeBinder, Part::Feature)
|
|
|
|
ShapeBinder::ShapeBinder()
|
|
{
|
|
ADD_PROPERTY_TYPE(Support, (nullptr, nullptr), "", (App::PropertyType)(App::Prop_None), "Support of the geometry");
|
|
Placement.setStatus(App::Property::Hidden, true);
|
|
ADD_PROPERTY_TYPE(TraceSupport, (false), "", App::Prop_None, "Trace support shape");
|
|
}
|
|
|
|
ShapeBinder::~ShapeBinder()
|
|
{
|
|
this->connectDocumentChangedObject.disconnect();
|
|
}
|
|
|
|
void ShapeBinder::onChanged(const App::Property* prop)
|
|
{
|
|
Feature::onChanged(prop);
|
|
}
|
|
|
|
short int ShapeBinder::mustExecute() const {
|
|
|
|
if (Support.isTouched()) {
|
|
return 1;
|
|
}
|
|
if (TraceSupport.isTouched()) {
|
|
return 1;
|
|
}
|
|
|
|
return Part::Feature::mustExecute();
|
|
}
|
|
|
|
Part::TopoShape ShapeBinder::updatedShape() const
|
|
{
|
|
Part::TopoShape shape;
|
|
App::GeoFeature* obj = nullptr;
|
|
std::vector<std::string> subs;
|
|
|
|
ShapeBinder::getFilteredReferences(&Support, obj, subs);
|
|
|
|
//if we have a link we rebuild the shape, but we change nothing if we are a simple copy
|
|
if (obj) {
|
|
shape = ShapeBinder::buildShapeFromReferences(obj, subs);
|
|
//now, shape is in object's CS, and includes local Placement of obj but nothing else.
|
|
|
|
if (TraceSupport.getValue()) {
|
|
//compute the transform, and apply it to the shape.
|
|
Base::Placement sourceCS = //full placement of container of obj
|
|
obj->globalPlacement() * obj->Placement.getValue().inverse();
|
|
Base::Placement targetCS = //full placement of container of this shapebinder
|
|
this->globalPlacement() * this->Placement.getValue().inverse();
|
|
Base::Placement transform = targetCS.inverse() * sourceCS;
|
|
shape.setPlacement(transform * shape.getPlacement());
|
|
}
|
|
}
|
|
|
|
return shape;
|
|
}
|
|
|
|
bool ShapeBinder::hasPlacementChanged() const
|
|
{
|
|
Part::TopoShape shape(updatedShape());
|
|
Base::Placement placement(shape.getTransform());
|
|
return this->Placement.getValue() != placement;
|
|
}
|
|
|
|
App::DocumentObjectExecReturn* ShapeBinder::execute()
|
|
{
|
|
if (!this->isRestoring()) {
|
|
Part::TopoShape shape(updatedShape());
|
|
if (!shape.isNull()) {
|
|
this->Placement.setValue(shape.getTransform());
|
|
this->Shape.setValue(shape);
|
|
}
|
|
}
|
|
|
|
return Part::Feature::execute();
|
|
}
|
|
|
|
void ShapeBinder::getFilteredReferences(const App::PropertyLinkSubList* prop,
|
|
App::GeoFeature*& obj,
|
|
std::vector< std::string >& subobjects)
|
|
{
|
|
obj = nullptr;
|
|
subobjects.clear();
|
|
|
|
auto objs = prop->getValues();
|
|
auto subs = prop->getSubValues();
|
|
|
|
if (objs.empty()) {
|
|
return;
|
|
}
|
|
|
|
//we only allow one part feature, so get the first one we find
|
|
size_t index = 0;
|
|
for (auto* it : objs) {
|
|
if (auto part = dynamic_cast<Part::Feature*>(it)) {
|
|
obj = part;
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
//do we have any part feature?
|
|
if (obj) {
|
|
//if we have no subshpape we use the whole shape
|
|
if (subs[index].empty()) {
|
|
return;
|
|
}
|
|
|
|
//collect all subshapes for the object
|
|
for (index = 0; index < objs.size(); index++) {
|
|
//we only allow subshapes from a single Part::Feature
|
|
if (objs[index] != obj) {
|
|
continue;
|
|
}
|
|
|
|
//in this mode the full shape is not allowed, as we already started the subshape
|
|
//processing
|
|
if (subs[index].empty()) {
|
|
continue;
|
|
}
|
|
|
|
subobjects.push_back(subs[index]);
|
|
}
|
|
}
|
|
else {
|
|
// search for Origin features
|
|
for (auto* it : objs) {
|
|
if (auto line = dynamic_cast<App::Line*>(it)) {
|
|
obj = line;
|
|
break;
|
|
}
|
|
if (auto plane = dynamic_cast<App::Plane*>(it)) {
|
|
obj = plane;
|
|
break;
|
|
}
|
|
if (auto point = dynamic_cast<App::Point*>(it)) {
|
|
obj = point;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Part::TopoShape ShapeBinder::buildShapeFromReferences(App::GeoFeature* obj, std::vector< std::string > subs) {
|
|
|
|
if (!obj) {
|
|
return TopoDS_Shape();
|
|
}
|
|
|
|
if (obj->isDerivedFrom<Part::Feature>()) {
|
|
auto part = static_cast<Part::Feature*>(obj);
|
|
if (subs.empty()) {
|
|
return part->Shape.getValue();
|
|
}
|
|
|
|
std::vector<TopoDS_Shape> shapes;
|
|
shapes.reserve(subs.size());
|
|
for (const std::string& sub : subs) {
|
|
shapes.push_back(part->Shape.getShape().getSubShape(sub.c_str()));
|
|
}
|
|
|
|
if (shapes.size() == 1) {
|
|
//single subshape. Return directly.
|
|
return shapes[0];
|
|
}
|
|
|
|
//multiple subshapes. Make a compound.
|
|
BRep_Builder builder;
|
|
TopoDS_Compound cmp;
|
|
builder.MakeCompound(cmp);
|
|
for (const TopoDS_Shape& sh : shapes) {
|
|
builder.Add(cmp, sh);
|
|
}
|
|
return cmp;
|
|
}
|
|
else if (obj->isDerivedFrom<App::Line>()) {
|
|
gp_Lin line;
|
|
BRepBuilderAPI_MakeEdge mkEdge(line);
|
|
Part::TopoShape shape(mkEdge.Shape());
|
|
shape.setPlacement(obj->Placement.getValue());
|
|
return shape;
|
|
}
|
|
else if (obj->isDerivedFrom<App::Plane>()) {
|
|
gp_Pln plane;
|
|
BRepBuilderAPI_MakeFace mkFace(plane);
|
|
Part::TopoShape shape(mkFace.Shape());
|
|
shape.setPlacement(obj->Placement.getValue());
|
|
return shape;
|
|
}
|
|
else if (obj->isDerivedFrom<App::Point>()) {
|
|
gp_Pnt point;
|
|
BRepBuilderAPI_MakeVertex mkPoint(point);
|
|
Part::TopoShape shape(mkPoint.Shape());
|
|
shape.setPlacement(obj->Placement.getValue());
|
|
return shape;
|
|
}
|
|
|
|
return TopoDS_Shape();
|
|
}
|
|
|
|
void ShapeBinder::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop)
|
|
{
|
|
// The type of Support was App::PropertyLinkSubList in the past
|
|
if (prop == &Support && strcmp(TypeName, "App::PropertyLinkSubList") == 0) {
|
|
Support.Restore(reader);
|
|
}
|
|
else {
|
|
Part::Feature::handleChangedPropertyType(reader, TypeName, prop);
|
|
}
|
|
}
|
|
|
|
void ShapeBinder::onSettingDocument()
|
|
{
|
|
App::Document* document = getDocument();
|
|
if (document) {
|
|
//NOLINTBEGIN
|
|
this->connectDocumentChangedObject = document->signalChangedObject.connect(std::bind
|
|
(&ShapeBinder::slotChangedObject, this, sp::_1, sp::_2));
|
|
//NOLINTEND
|
|
}
|
|
}
|
|
|
|
void ShapeBinder::slotChangedObject(const App::DocumentObject& Obj, const App::Property& Prop)
|
|
{
|
|
App::Document* doc = getDocument();
|
|
if (!doc || doc->testStatus(App::Document::Restoring)) {
|
|
return;
|
|
}
|
|
if (this == &Obj) {
|
|
return;
|
|
}
|
|
if (!TraceSupport.getValue()) {
|
|
return;
|
|
}
|
|
if (!Prop.isDerivedFrom<App::PropertyPlacement>()) {
|
|
return;
|
|
}
|
|
|
|
App::GeoFeature* obj = nullptr;
|
|
std::vector<std::string> subs;
|
|
ShapeBinder::getFilteredReferences(&Support, obj, subs);
|
|
if (obj) {
|
|
if (obj == &Obj) {
|
|
// the directly referenced object has changed
|
|
if (hasPlacementChanged()) {
|
|
enforceRecompute();
|
|
}
|
|
}
|
|
else if (Obj.hasExtension(App::GroupExtension::getExtensionClassTypeId())) {
|
|
// check if the changed property belongs to a group-like object
|
|
// like Body or Part
|
|
std::vector<App::DocumentObject*> chain;
|
|
std::vector<App::DocumentObject*> list = getInListRecursive();
|
|
chain.insert(chain.end(), list.begin(), list.end());
|
|
list = obj->getInListRecursive();
|
|
chain.insert(chain.end(), list.begin(), list.end());
|
|
|
|
if (const auto it = std::ranges::find(chain, &Obj); it != chain.end()) {
|
|
if (hasPlacementChanged()) {
|
|
enforceRecompute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
|
|
PROPERTY_SOURCE(PartDesign::SubShapeBinder, Part::Feature)
|
|
|
|
SubShapeBinder::SubShapeBinder()
|
|
{
|
|
ADD_PROPERTY_TYPE(Support, (nullptr), "", (App::PropertyType)(App::Prop_None), "Support of the geometry");
|
|
Support.setStatus(App::Property::ReadOnly, true);
|
|
ADD_PROPERTY_TYPE(Fuse, (false), "Base", App::Prop_None, "Fuse solids from bound shapes");
|
|
ADD_PROPERTY_TYPE(MakeFace, (true), "Base", App::Prop_None, "Create face using wires from bound shapes");
|
|
ADD_PROPERTY_TYPE(Offset, (0.0), "Offsetting", App::Prop_None, "2D offset face or wires, 0.0 = no offset");
|
|
ADD_PROPERTY_TYPE(OffsetJoinType, ((long)0), "Offsetting", App::Prop_None, "Arcs, Tangent, Intersection");
|
|
static const char* JoinTypeEnum[] = { "Arcs", "Tangent", "Intersection", nullptr };
|
|
OffsetJoinType.setEnums(JoinTypeEnum);
|
|
ADD_PROPERTY_TYPE(OffsetFill, (false), "Offsetting", App::Prop_None, "True = make face between original wire and offset.");
|
|
ADD_PROPERTY_TYPE(OffsetOpenResult, (false), "Offsetting", App::Prop_None, "False = make closed offset from open wire.");
|
|
ADD_PROPERTY_TYPE(OffsetIntersection, (false), "Offsetting", App::Prop_None, "False = offset child wires independently.");
|
|
ADD_PROPERTY_TYPE(ClaimChildren, (false), "Base", App::Prop_Output, "Claim linked object as children");
|
|
ADD_PROPERTY_TYPE(Relative, (true), "Base", App::Prop_None, "Enable relative sub-object binding");
|
|
ADD_PROPERTY_TYPE(BindMode, ((long)0), "Base", App::Prop_None,
|
|
"Synchronized: auto update binder shape on changed of bound object.\n"
|
|
"Frozen: disable auto update, but can be updated manually using context menu.\n"
|
|
"Detached: copy the shape of bound object and then remove the binding immediately.");
|
|
ADD_PROPERTY_TYPE(PartialLoad, (false), "Base", App::Prop_None,
|
|
"Enable partial loading, which disables auto loading of external document for"
|
|
"external bound object.");
|
|
PartialLoad.setStatus(App::Property::PartialTrigger, true);
|
|
static const char* BindModeEnum[] = { "Synchronized", "Frozen", "Detached", nullptr };
|
|
BindMode.setEnums(BindModeEnum);
|
|
|
|
ADD_PROPERTY_TYPE(Context, (nullptr), "Base", App::Prop_Hidden,
|
|
"Stores the context of this binder. It is used for monitoring and auto updating\n"
|
|
"the relative placement of the bound shape");
|
|
|
|
static const char* BindCopyOnChangeEnum[] = { "Disabled", "Enabled", "Mutated", nullptr };
|
|
BindCopyOnChange.setEnums(BindCopyOnChangeEnum);
|
|
ADD_PROPERTY_TYPE(BindCopyOnChange, ((long)0), "Base", App::Prop_None,
|
|
"Disabled: disable copy on change.\n"
|
|
"Enabled: duplicate properties from binding object that are marked with 'CopyOnChange'.\n"
|
|
" Make internal copy of the object with any changed properties to obtain the\n"
|
|
" shape of an alternative configuration\n"
|
|
"Mutated: indicate the binder has already mutated by changing any properties marked with\n"
|
|
" 'CopyOnChange'. Those properties will not longer be kept in sync between the\n"
|
|
" binder and the binding object");
|
|
|
|
ADD_PROPERTY_TYPE(Refine, (true), "Base", (App::PropertyType)(App::Prop_None),
|
|
"Refine shape (clean up redundant edges) after adding/subtracting");
|
|
|
|
Context.setScope(App::LinkScope::Hidden);
|
|
|
|
ADD_PROPERTY_TYPE(_Version, (0), "Base", (App::PropertyType)(
|
|
App::Prop_Hidden | App::Prop_ReadOnly), "");
|
|
|
|
_CopiedLink.setScope(App::LinkScope::Hidden);
|
|
ADD_PROPERTY_TYPE(_CopiedLink, (nullptr), "Base", (App::PropertyType)(
|
|
App::Prop_Hidden | App::Prop_ReadOnly | App::Prop_NoPersist), "");
|
|
}
|
|
|
|
SubShapeBinder::~SubShapeBinder() {
|
|
try {
|
|
clearCopiedObjects();
|
|
}
|
|
catch (const Base::ValueError& e) {
|
|
e.reportException();
|
|
}
|
|
}
|
|
|
|
void SubShapeBinder::setupObject() {
|
|
_Version.setValue(2);
|
|
checkPropertyStatus();
|
|
}
|
|
|
|
App::DocumentObject* SubShapeBinder::getSubObject(const char* subname, PyObject** pyObj,
|
|
Base::Matrix4D* mat, bool transform, int depth) const
|
|
{
|
|
auto sobj = Part::Feature::getSubObject(subname, pyObj, mat, transform, depth);
|
|
if (sobj)
|
|
return sobj;
|
|
if (Data::findElementName(subname) == subname)
|
|
return nullptr;
|
|
|
|
const char* dot = strchr(subname, '.');
|
|
if (!dot)
|
|
return nullptr;
|
|
|
|
App::GetApplication().checkLinkDepth(depth);
|
|
std::string name(subname, dot - subname);
|
|
for (auto& l : Support.getSubListValues()) {
|
|
auto obj = l.getValue();
|
|
if (!obj || !obj->isAttachedToDocument())
|
|
continue;
|
|
for (auto& sub : l.getSubValues()) {
|
|
auto sobj = obj->getSubObject(sub.c_str());
|
|
if (!sobj || !sobj->isAttachedToDocument())
|
|
continue;
|
|
if (subname[0] == '$') {
|
|
if (sobj->Label.getStrValue() != name.c_str() + 1)
|
|
continue;
|
|
}
|
|
else if (!boost::equals(sobj->getNameInDocument(), name))
|
|
continue;
|
|
name = Data::noElementName(sub.c_str());
|
|
name += dot + 1;
|
|
if (mat && transform)
|
|
*mat *= Placement.getValue().toMatrix();
|
|
return obj->getSubObject(name.c_str(), pyObj, mat, true, depth + 1);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SubShapeBinder::setupCopyOnChange() {
|
|
copyOnChangeConns.clear();
|
|
|
|
const auto& support = Support.getSubListValues();
|
|
if (BindCopyOnChange.getValue() == 0 || support.size() != 1) {
|
|
if (hasCopyOnChange) {
|
|
hasCopyOnChange = false;
|
|
std::vector<App::Property*> props;
|
|
getPropertyList(props);
|
|
for (auto prop : props) {
|
|
if (App::LinkBaseExtension::isCopyOnChangeProperty(this, *prop)) {
|
|
try {
|
|
removeDynamicProperty(prop->getName());
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto linked = support.front().getValue();
|
|
hasCopyOnChange = App::LinkBaseExtension::setupCopyOnChange(this, linked,
|
|
BindCopyOnChange.getValue() == 1 ? ©OnChangeConns : nullptr, hasCopyOnChange);
|
|
if (hasCopyOnChange) {
|
|
copyOnChangeConns.emplace_back(linked->signalChanged.connect(
|
|
[this](const App::DocumentObject&, const App::Property& prop) {
|
|
if (!prop.testStatus(App::Property::Output)
|
|
&& !prop.testStatus(App::Property::PropOutput))
|
|
{
|
|
if (!this->_CopiedObjs.empty()) {
|
|
FC_LOG("Clear binder " << getFullName() << " cache on change of "
|
|
<< prop.getFullName());
|
|
this->clearCopiedObjects();
|
|
}
|
|
}
|
|
}
|
|
));
|
|
}
|
|
}
|
|
|
|
void SubShapeBinder::clearCopiedObjects() {
|
|
std::vector<App::DocumentObjectT> objs;
|
|
objs.swap(_CopiedObjs);
|
|
for (auto& o : objs) {
|
|
auto obj = o.getObject();
|
|
if (obj)
|
|
obj->getDocument()->removeObject(obj->getNameInDocument());
|
|
}
|
|
_CopiedLink.setValue(nullptr);
|
|
}
|
|
|
|
void SubShapeBinder::update(SubShapeBinder::UpdateOption options) {
|
|
Part::TopoShape result;
|
|
std::vector<Part::TopoShape> shapes;
|
|
std::vector<std::pair<int,int> > shapeOwners;
|
|
std::vector<const Base::Matrix4D*> shapeMats;
|
|
|
|
bool forced = (Shape.getValue().IsNull() || (options & UpdateForced)) ? true : false;
|
|
bool init = (!forced && (options & UpdateForced)) ? true : false;
|
|
|
|
std::string errMsg;
|
|
auto parent = Context.getValue();
|
|
std::string parentSub = Context.getSubName(false);
|
|
if (!Relative.getValue())
|
|
parent = nullptr;
|
|
else {
|
|
if (parent && parent->getSubObject(parentSub.c_str()) == this) {
|
|
auto parents = parent->getParents();
|
|
if (!parents.empty()) {
|
|
parent = parents.begin()->first;
|
|
parentSub = parents.begin()->second + parentSub;
|
|
}
|
|
}
|
|
else
|
|
parent = nullptr;
|
|
if (!parent && parentSub.empty()) {
|
|
auto parents = getParents();
|
|
if (!parents.empty()) {
|
|
parent = parents.begin()->first;
|
|
parentSub = parents.begin()->second;
|
|
}
|
|
}
|
|
if (parent && (parent != Context.getValue() || parentSub != Context.getSubName(false)))
|
|
Context.setValue(parent, parentSub.c_str());
|
|
}
|
|
|
|
bool first = false;
|
|
std::unordered_map<const App::DocumentObject*, Base::Matrix4D> mats;
|
|
int idx = -1;
|
|
for (auto& l : Support.getSubListValues()) {
|
|
++idx;
|
|
auto obj = l.getValue();
|
|
if (!obj || !obj->isAttachedToDocument())
|
|
continue;
|
|
auto res = mats.emplace(obj, Base::Matrix4D());
|
|
if (parent && res.second) {
|
|
std::string resolvedSub = parentSub;
|
|
std::string linkSub;
|
|
auto link = obj;
|
|
auto resolved = parent->resolveRelativeLink(resolvedSub, link, linkSub);
|
|
if (!resolved) {
|
|
if (!link) {
|
|
FC_WARN(getFullName() << " cannot resolve relative link of "
|
|
<< parent->getFullName() << '.' << parentSub
|
|
<< " -> " << obj->getFullName());
|
|
}
|
|
}
|
|
else {
|
|
Base::Matrix4D mat;
|
|
auto sobj = resolved->getSubObject(resolvedSub.c_str(), nullptr, &mat);
|
|
if (sobj != this) {
|
|
FC_LOG(getFullName() << " skip invalid parent " << resolved->getFullName()
|
|
<< '.' << resolvedSub);
|
|
}
|
|
else if (_Version.getValue() == 0) {
|
|
// For existing legacy SubShapeBinder, we use its Placement
|
|
// to store the position adjustment of the first Support
|
|
if (first) {
|
|
auto pla = Placement.getValue() * Base::Placement(mat).inverse();
|
|
Placement.setValue(pla);
|
|
first = false;
|
|
}
|
|
else {
|
|
// The remaining support will cancel the Placement
|
|
mat.inverseGauss();
|
|
res.first->second = mat;
|
|
}
|
|
}
|
|
else {
|
|
// For newer SubShapeBinder, the Placement property is free
|
|
// to use by the user to add additional offset to the
|
|
// binding object
|
|
mat.inverseGauss();
|
|
res.first->second = Placement.getValue().toMatrix() * mat;
|
|
}
|
|
}
|
|
}
|
|
if (init)
|
|
continue;
|
|
|
|
App::DocumentObject* copied = nullptr;
|
|
|
|
if (BindCopyOnChange.getValue() == 2 && Support.getSubListValues().size() == 1) {
|
|
if (!_CopiedObjs.empty())
|
|
copied = _CopiedObjs.front().getObject();
|
|
|
|
bool recomputeCopy = false;
|
|
int copyerror = 0;
|
|
if (!copied || !copied->isValid()) {
|
|
recomputeCopy = true;
|
|
clearCopiedObjects();
|
|
|
|
App::DocumentInitFlags initFlags {
|
|
.createView = false,
|
|
.temporary = true
|
|
};
|
|
auto tmpDoc = App::GetApplication().newDocument("_tmp_binder", nullptr, initFlags);
|
|
tmpDoc->setUndoMode(0);
|
|
auto objs = tmpDoc->copyObject({ obj }, true, true);
|
|
if (!objs.empty()) {
|
|
for (auto it = objs.rbegin(); it != objs.rend(); ++it)
|
|
_CopiedObjs.emplace_back(*it);
|
|
copied = objs.back();
|
|
// IMPORTANT! must make a recomputation first before any
|
|
// further change so that we can generate the correct
|
|
// geometry element map.
|
|
if (!copied->recomputeFeature(true))
|
|
copyerror = 1;
|
|
}
|
|
}
|
|
|
|
if (copied) {
|
|
if (!copyerror) {
|
|
std::vector<App::Property*> props;
|
|
getPropertyList(props);
|
|
// lambda for copying values of copy-on-change properties
|
|
const auto copyPropertyValues = [this, &recomputeCopy, &props, copied](const bool to_support) {
|
|
for (auto prop : props) {
|
|
if (!App::LinkBaseExtension::isCopyOnChangeProperty(this, *prop))
|
|
continue;
|
|
// we only copy read-only and output properties from support to binder
|
|
if (!to_support && !(prop->testStatus(App::Property::Output) && prop->testStatus(App::Property::ReadOnly)))
|
|
continue;
|
|
auto p = copied->getPropertyByName(prop->getName());
|
|
if (p && p->getContainer() == copied
|
|
&& p->getTypeId() == prop->getTypeId()
|
|
&& !p->isSame(*prop))
|
|
{
|
|
recomputeCopy = true;
|
|
auto* const from = to_support ? prop : p;
|
|
auto* const to = to_support ? p : prop;
|
|
|
|
std::unique_ptr<App::Property> pcopy(from->Copy());
|
|
to->Paste(*pcopy);
|
|
}
|
|
}
|
|
};
|
|
|
|
copyPropertyValues(true);
|
|
if (recomputeCopy && !copied->recomputeFeature(true))
|
|
copyerror = 2;
|
|
if (!copyerror)
|
|
copyPropertyValues(false);
|
|
}
|
|
obj = copied;
|
|
_CopiedLink.setValue(copied, l.getSubValues(false));
|
|
if (copyerror) {
|
|
FC_THROWM(Base::RuntimeError,
|
|
(copyerror == 1 ? "Initial copy failed." : "Copy on change failed.")
|
|
<< " Please check report view for more details.\n"
|
|
"You can show temporary document to reveal the failed objects using\n"
|
|
"tree view context menu.");
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto& subvals = copied ? _CopiedLink.getSubValues() : l.getSubValues();
|
|
std::set<std::string> subs(subvals.begin(), subvals.end());
|
|
int sidx = copied?-1:idx;
|
|
int subidx = -1;
|
|
static std::string none;
|
|
if (subs.empty())
|
|
subs.insert(none);
|
|
else if (subs.size() > 1)
|
|
subs.erase(none);
|
|
for (const auto& sub : subs) {
|
|
++subidx;
|
|
try {
|
|
auto shape = Part::Feature::getTopoShape(obj,
|
|
Part::ShapeOption::NeedSubElement
|
|
| Part::ShapeOption::ResolveLink
|
|
| Part::ShapeOption::Transform,
|
|
sub.c_str());
|
|
if (!shape.isNull()) {
|
|
shapes.push_back(shape);
|
|
shapeOwners.emplace_back(sidx, subidx);
|
|
shapeMats.push_back(&res.first->second);
|
|
}
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
FC_ERR(getFullName() << " failed to obtain shape from "
|
|
<< obj->getFullName() << '.' << sub);
|
|
if (errMsg.empty()) {
|
|
std::ostringstream ss;
|
|
ss << "Failed to obtain shape " <<
|
|
obj->getFullName() << '.'
|
|
<< Data::oldElementName(sub.c_str());
|
|
errMsg = ss.str();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string objName;
|
|
// lambda function for generating matrix cache property
|
|
auto cacheName = [this, &objName](const App::DocumentObject* obj) {
|
|
objName = "Cache_";
|
|
objName += obj->getNameInDocument();
|
|
if (obj->getDocument() != getDocument()) {
|
|
objName += "_";
|
|
objName += obj->getDocument()->getName();
|
|
}
|
|
return objName.c_str();
|
|
};
|
|
|
|
if (!init) {
|
|
|
|
if (!errMsg.empty()) {
|
|
if (!(options & UpdateInit))
|
|
FC_THROWM(Base::RuntimeError, errMsg);
|
|
if (!Shape.getValue().IsNull())
|
|
return;
|
|
}
|
|
|
|
// If not forced, only rebuild when there is any change in
|
|
// transformation matrix
|
|
if (!forced) {
|
|
bool hit = true;
|
|
for (auto& v : mats) {
|
|
auto prop = freecad_cast<App::PropertyMatrix*>(
|
|
getDynamicPropertyByName(cacheName(v.first)));
|
|
if (!prop || prop->getValue() != v.second) {
|
|
hit = false;
|
|
break;
|
|
}
|
|
}
|
|
if (hit)
|
|
return;
|
|
}
|
|
std::ostringstream ss;
|
|
int idx = -1;
|
|
for(auto &shape : shapes) {
|
|
++idx;
|
|
if(shape.Hasher
|
|
&& shape.getElementMapSize()
|
|
&& shape.Hasher != getDocument()->getStringHasher())
|
|
{
|
|
ss.str("");
|
|
ss << Data::POSTFIX_EXTERNAL_TAG
|
|
<< Data::ComplexGeoData::elementMapPrefix()
|
|
<< Part::OpCodes::Shapebinder << ':' << shapeOwners[idx].first
|
|
<< ':' << shapeOwners[idx].second;
|
|
shape.reTagElementMap(-getID(),
|
|
getDocument()->getStringHasher(),ss.str().c_str());
|
|
}
|
|
if (!shape.hasSubShape(TopAbs_FACE) && shape.hasSubShape(TopAbs_EDGE))
|
|
shape = shape.makeElementCopy();
|
|
}
|
|
|
|
if (shapes.size() == 1 && !Relative.getValue())
|
|
shapes.back().setPlacement(Base::Placement());
|
|
else {
|
|
for (size_t i = 0; i < shapes.size(); ++i) {
|
|
auto& shape = shapes[i];
|
|
shape = shape.makeElementTransform(*shapeMats[i]);
|
|
// if(shape.Hasher
|
|
// && shape.getElementMapSize()
|
|
// && shape.Hasher != getDocument()->getStringHasher())
|
|
// {
|
|
// shape.reTagElementMap(getID(),
|
|
// getDocument()->getStringHasher(),TOPOP_SHAPEBINDER);
|
|
// }
|
|
}
|
|
}
|
|
|
|
if (shapes.empty()) {
|
|
// Shape.resetElementMapVersion();
|
|
return;
|
|
}
|
|
result.makeElementCompound(shapes);
|
|
bool fused = false;
|
|
if (Fuse.getValue()) {
|
|
// If the compound has solid, fuse them together, and ignore other type of
|
|
// shapes
|
|
std::vector<TopoDS_Shape> solids;
|
|
Part::TopoShape solid;
|
|
for (auto& s : result.getSubTopoShapes(TopAbs_SOLID)) {
|
|
if (solid.isNull())
|
|
solid = s;
|
|
else
|
|
solids.push_back(s.getShape());
|
|
}
|
|
if (!solids.empty()) {
|
|
result = solid.fuse(solids);
|
|
fused = true;
|
|
}
|
|
else if (!solid.isNull()) {
|
|
// wrap the single solid in compound to keep its placement
|
|
result.makeElementCompound({ solid });
|
|
fused = true;
|
|
}
|
|
}
|
|
|
|
if (!fused && (MakeFace.getValue() || Offset.getValue() != 0.0)
|
|
&& !result.hasSubShape(TopAbs_FACE)
|
|
&& result.hasSubShape(TopAbs_EDGE))
|
|
{
|
|
result = result.makeElementWires();
|
|
if (MakeFace.getValue()) {
|
|
try {
|
|
result = result.makeElementFace(nullptr);
|
|
}
|
|
catch (...) {}
|
|
}
|
|
}
|
|
|
|
if (!fused && result.hasSubShape(TopAbs_WIRE)
|
|
&& Offset.getValue() != 0.0) {
|
|
try {
|
|
result = result.makeElementOffset2D(Offset.getValue(),
|
|
(Part::JoinType) OffsetJoinType.getValue() ,
|
|
OffsetFill.getValue() ? Part::FillType::fill : Part::FillType::noFill,
|
|
OffsetOpenResult.getValue() ? Part::OpenResult::allowOpenResult : Part::OpenResult::noOpenResult,
|
|
OffsetIntersection.getValue());
|
|
}
|
|
catch (...) {
|
|
std::ostringstream msg;
|
|
msg << Label.getValue() << ": failed to make 2D offset" << std::endl;
|
|
Base::Console().error(msg.str().c_str());
|
|
}
|
|
}
|
|
|
|
if (Refine.getValue())
|
|
result = result.makeElementRefine();
|
|
result.setPlacement(Placement.getValue());
|
|
Shape.setValue(result);
|
|
}
|
|
|
|
// collect transformation matrix cache entries
|
|
std::unordered_set<std::string> caches;
|
|
for (const auto& name : getDynamicPropertyNames()) {
|
|
if (boost::starts_with(name, "Cache_"))
|
|
caches.emplace(name);
|
|
}
|
|
|
|
// update transformation matrix cache
|
|
for (auto& v : mats) {
|
|
const char* name = cacheName(v.first);
|
|
auto prop = getDynamicPropertyByName(name);
|
|
if (!prop || !prop->isDerivedFrom<App::PropertyMatrix>()) {
|
|
if (prop)
|
|
removeDynamicProperty(name);
|
|
prop = addDynamicProperty("App::PropertyMatrix", name, "Cache", nullptr, 0, false, true);
|
|
}
|
|
caches.erase(name);
|
|
static_cast<App::PropertyMatrix*>(prop)->setValue(v.second);
|
|
}
|
|
|
|
// remove any non-used cache entries.
|
|
for (const auto& name : caches) {
|
|
try {
|
|
removeDynamicProperty(name.c_str());
|
|
}
|
|
catch (...) {}
|
|
}
|
|
}
|
|
|
|
void SubShapeBinder::slotRecomputedObject(const App::DocumentObject& Obj) {
|
|
if (Context.getValue() == &Obj
|
|
&& !this->testStatus(App::ObjectStatus::Recompute2))
|
|
{
|
|
try {
|
|
update();
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
}
|
|
}
|
|
}
|
|
|
|
App::DocumentObjectExecReturn* SubShapeBinder::execute() {
|
|
|
|
setupCopyOnChange();
|
|
|
|
if (BindMode.getValue() == 0)
|
|
update(UpdateForced);
|
|
|
|
return inherited::execute();
|
|
}
|
|
|
|
void SubShapeBinder::onDocumentRestored() {
|
|
if (_Version.getValue() < 2)
|
|
update(UpdateInit);
|
|
inherited::onDocumentRestored();
|
|
}
|
|
|
|
void SubShapeBinder::collapseGeoChildren()
|
|
{
|
|
// Geo children, i.e. children of GeoFeatureGroup may group some tool
|
|
// features under itself but does not function as a container. In addition,
|
|
// its parent group can directly reference the tool feature grouped without
|
|
// referencing the child. The purpose of this function is to remove any
|
|
// intermediate Non group features in the object path to avoid unnecessary
|
|
// dependencies.
|
|
if (Support.testStatus(App::Property::User3))
|
|
return;
|
|
|
|
Base::ObjectStatusLocker<App::Property::Status, App::Property>
|
|
guard(App::Property::User3, &Support);
|
|
App::PropertyXLinkSubList::atomic_change guard2(Support, false);
|
|
|
|
std::vector<App::DocumentObject*> removes;
|
|
std::map<App::DocumentObject*, std::vector<std::string> > newVals;
|
|
std::ostringstream ss;
|
|
for(auto &l : Support.getSubListValues()) {
|
|
auto obj = l.getValue();
|
|
if(!obj || !obj->getNameInDocument())
|
|
continue;
|
|
auto subvals = l.getSubValues();
|
|
if (subvals.empty())
|
|
continue;
|
|
bool touched = false;
|
|
for (auto itSub=subvals.begin(); itSub!=subvals.end();) {
|
|
auto &sub = *itSub;
|
|
App::SubObjectT sobjT(obj, sub.c_str());
|
|
if (sobjT.normalize(App::SubObjectT::NormalizeOption::KeepSubName)) {
|
|
touched = true;
|
|
auto newobj = sobjT.getObject();
|
|
sub = sobjT.getSubName();
|
|
if (newobj != obj) {
|
|
newVals[newobj].push_back(std::move(sub));
|
|
itSub = subvals.erase(itSub);
|
|
continue;
|
|
}
|
|
}
|
|
++itSub;
|
|
}
|
|
if (touched)
|
|
removes.push_back(obj);
|
|
if (!subvals.empty() && touched) {
|
|
auto &newSubs = newVals[obj];
|
|
if (newSubs.empty())
|
|
newSubs = std::move(subvals);
|
|
else
|
|
newSubs.insert(newSubs.end(),
|
|
std::make_move_iterator(subvals.begin()),
|
|
std::make_move_iterator(subvals.end()));
|
|
}
|
|
}
|
|
|
|
if (removes.size() || newVals.size())
|
|
guard2.aboutToChange();
|
|
for (auto obj : removes)
|
|
Support.removeValue(obj);
|
|
if (newVals.size())
|
|
setLinks(std::move(newVals));
|
|
}
|
|
|
|
void SubShapeBinder::onChanged(const App::Property* prop) {
|
|
if (prop == &Context || prop == &Relative) {
|
|
if (!Context.getValue() || !Relative.getValue()) {
|
|
connRecomputedObj.disconnect();
|
|
}
|
|
else if (contextDoc != Context.getValue()->getDocument()
|
|
|| !connRecomputedObj.connected())
|
|
{
|
|
//NOLINTBEGIN
|
|
contextDoc = Context.getValue()->getDocument();
|
|
connRecomputedObj = contextDoc->signalRecomputedObject.connect(
|
|
std::bind(&SubShapeBinder::slotRecomputedObject, this, sp::_1));
|
|
//NOLINTEND
|
|
}
|
|
}
|
|
else if (!isRestoring()) {
|
|
if (prop == &Support) {
|
|
collapseGeoChildren();
|
|
clearCopiedObjects();
|
|
setupCopyOnChange();
|
|
if (!Support.getSubListValues().empty()) {
|
|
update();
|
|
if (BindMode.getValue() == 2)
|
|
Support.setValue(nullptr);
|
|
}
|
|
}
|
|
else if (prop == &BindCopyOnChange) {
|
|
setupCopyOnChange();
|
|
}
|
|
else if (prop == &BindMode) {
|
|
if (BindMode.getValue() == 2)
|
|
Support.setValue(nullptr);
|
|
else if (BindMode.getValue() == 0)
|
|
update();
|
|
checkPropertyStatus();
|
|
}
|
|
else if (prop == &PartialLoad) {
|
|
checkPropertyStatus();
|
|
}
|
|
else if (prop && !prop->testStatus(App::Property::User3))
|
|
checkCopyOnChange(*prop);
|
|
}
|
|
inherited::onChanged(prop);
|
|
}
|
|
|
|
void SubShapeBinder::checkCopyOnChange(const App::Property& prop) {
|
|
if (BindCopyOnChange.getValue() != 1
|
|
|| getDocument()->isPerformingTransaction()
|
|
|| !App::LinkBaseExtension::isCopyOnChangeProperty(this, prop)
|
|
|| Support.getSubListValues().size() != 1)
|
|
return;
|
|
|
|
auto linked = Support.getSubListValues().front().getValue();
|
|
if (!linked)
|
|
return;
|
|
auto linkedProp = linked->getPropertyByName(prop.getName());
|
|
if (linkedProp && linkedProp->getTypeId() == prop.getTypeId() && !linkedProp->isSame(prop))
|
|
BindCopyOnChange.setValue(2);
|
|
}
|
|
|
|
void SubShapeBinder::checkPropertyStatus() {
|
|
Support.setAllowPartial(PartialLoad.getValue());
|
|
|
|
// Make Shape transient can reduce some file size, and maybe reduce file
|
|
// loading time as well. But there maybe complication arise when doing
|
|
// TopoShape version upgrade. So we DO NOT set transient at the moment.
|
|
//
|
|
// Shape.setStatus(App::Property::Transient, !PartialLoad.getValue() && BindMode.getValue()==0);
|
|
}
|
|
|
|
void SubShapeBinder::setLinks(std::map<App::DocumentObject*, std::vector<std::string> >&& values, bool reset)
|
|
{
|
|
if (values.empty()) {
|
|
if (reset) {
|
|
Support.setValue(nullptr);
|
|
Shape.setValue(Part::TopoShape());
|
|
}
|
|
return;
|
|
}
|
|
auto inSet = getInListEx(true);
|
|
inSet.insert(this);
|
|
|
|
for (auto& v : values) {
|
|
if (!v.first || !v.first->isAttachedToDocument())
|
|
FC_THROWM(Base::ValueError, "Invalid document object");
|
|
if (inSet.find(v.first) != inSet.end())
|
|
FC_THROWM(Base::ValueError, "Cyclic reference to " << v.first->getFullName());
|
|
|
|
if (v.second.empty()) {
|
|
v.second.emplace_back("");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> wholeSubs;
|
|
for (auto& sub : v.second) {
|
|
if (sub.empty()) {
|
|
wholeSubs.clear();
|
|
v.second.resize(1);
|
|
v.second[0].clear();
|
|
break;
|
|
}
|
|
else if (sub[sub.size() - 1] == '.')
|
|
wholeSubs.push_back(sub);
|
|
}
|
|
for (auto& whole : wholeSubs) {
|
|
for (auto it = v.second.begin(); it != v.second.end();) {
|
|
auto& sub = *it;
|
|
if (!boost::starts_with(sub, whole) || sub.size() == whole.size())
|
|
++it;
|
|
else {
|
|
FC_LOG("Remove subname " << sub << " because of whole selection " << whole);
|
|
it = v.second.erase(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!reset) {
|
|
for (auto& link : Support.getSubListValues()) {
|
|
auto subs = link.getSubValues();
|
|
auto& s = values[link.getValue()];
|
|
if (s.empty()) {
|
|
s = std::move(subs);
|
|
continue;
|
|
}
|
|
else if (subs.empty() || s[0].empty())
|
|
continue;
|
|
|
|
for (auto& sub : s) {
|
|
for (auto it = subs.begin(); it != subs.end();) {
|
|
if (sub[sub.size() - 1] == '.') {
|
|
if (boost::starts_with(*it, sub)) {
|
|
FC_LOG("Remove subname " << *it << " because of whole selection " << sub);
|
|
it = subs.erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
else if (it->empty()
|
|
|| (it->back() == '.' && boost::starts_with(sub, *it)))
|
|
{
|
|
FC_LOG("Remove whole subname " << *it << " because of " << sub);
|
|
it = subs.erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
subs.insert(subs.end(), s.begin(), s.end());
|
|
s = std::move(subs);
|
|
}
|
|
}
|
|
Support.setValues(std::move(values));
|
|
}
|
|
|
|
void SubShapeBinder::handleChangedPropertyType(
|
|
Base::XMLReader& reader, const char* TypeName, App::Property* prop)
|
|
{
|
|
if (prop == &Support) {
|
|
Support.upgrade(reader, TypeName);
|
|
}
|
|
else {
|
|
inherited::handleChangedPropertyType(reader, TypeName, prop);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace App {
|
|
PROPERTY_SOURCE_TEMPLATE(PartDesign::SubShapeBinderPython, PartDesign::SubShapeBinder)
|
|
template<> const char* PartDesign::SubShapeBinderPython::getViewProviderName() const {
|
|
return "PartDesignGui::ViewProviderSubShapeBinderPython";
|
|
}
|
|
template class PartDesignExport FeaturePythonT<PartDesign::SubShapeBinder>;
|
|
}
|
|
|