// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2014 Jürgen Riegel * * Copyright (c) 2015 Alexander Golubev (Fat-Zer) * * * * 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 #include #include "GeoFeatureGroupExtension.h" #include "Link.h" #include "Origin.h" #include "Datums.h" #include "OriginGroupExtension.h" using namespace App; EXTENSION_PROPERTY_SOURCE(App::GeoFeatureGroupExtension, App::GroupExtension) //=========================================================================== // Feature //=========================================================================== GeoFeatureGroupExtension::GeoFeatureGroupExtension() { initExtensionType(GeoFeatureGroupExtension::getExtensionClassTypeId()); Group.setScope(LinkScope::Child); } GeoFeatureGroupExtension::~GeoFeatureGroupExtension() = default; void GeoFeatureGroupExtension::initExtension(ExtensionContainer* obj) { if (!obj->isDerivedFrom()) { throw Base::RuntimeError("GeoFeatureGroupExtension can only be applied to GeoFeatures"); } App::GroupExtension::initExtension(obj); } PropertyPlacement& GeoFeatureGroupExtension::placement() { if (!getExtendedContainer()) { throw Base::RuntimeError("GeoFeatureGroupExtension was not applied to GeoFeature"); } return static_cast(getExtendedContainer())->Placement; } void GeoFeatureGroupExtension::transformPlacement(const Base::Placement& transform) { // NOTE: Keep in sync with APP::GeoFeature Base::Placement plm = this->placement().getValue(); plm = transform * plm; this->placement().setValue(plm); } DocumentObject* GeoFeatureGroupExtension::getGroupOfObject(const DocumentObject* obj) { if (!obj) { return nullptr; } // we will find origins, but not origin features if (obj->isDerivedFrom()) { return OriginGroupExtension::getGroupOfObject(obj); } // compared to GroupExtension we do return here all GeoFeatureGroups including all extensions // derived from it like OriginGroup. That is needed as we use this function to get all local // coordinate systems. Also there is no reason to distinguish between GeoFeatuerGroups, there is // only between group/geofeaturegroup auto list = obj->getInList(); for (auto inObj : list) { // There is a chance that a derived geofeaturegroup links with a local link and hence is not // the parent group even though it links to the object. We use hasObject as one and only // truth if it has the object within the group auto group = inObj->getExtensionByType(true); if (group && group->hasObject(obj)) { return inObj; } } return nullptr; } Base::Placement GeoFeatureGroupExtension::globalGroupPlacement() { if (getExtendedObject()->isRecomputing()) { throw Base::RuntimeError("Global placement cannot be calculated on recompute"); } std::unordered_set history; history.insert(this); return recursiveGroupPlacement(this, history); } Base::Placement GeoFeatureGroupExtension::recursiveGroupPlacement( GeoFeatureGroupExtension* group, std::unordered_set& history) { history.insert(this); auto inList = group->getExtendedObject()->getInList(); for (auto* link : inList) { auto parent = link->getExtensionByType(true); if (parent && parent->hasObject(group->getExtendedObject())) { // Cyclic dependencies detected if (history.contains(parent)) { break; } return recursiveGroupPlacement(parent, history) * group->placement().getValue(); } } return group->placement().getValue(); } std::vector GeoFeatureGroupExtension::addObjects(std::vector objects) { std::vector grp = Group.getValues(); std::vector ret; for (auto object : objects) { if (!allowObject(object)) { continue; } // cross CoordinateSystem links are not allowed, so we need to move the whole link group std::vector links = getCSRelevantLinks(object); links.push_back(object); for (auto obj : links) { // only one geofeaturegroup per object. auto* group = App::GeoFeatureGroupExtension::getGroupOfObject(obj); if (group && group != getExtendedObject()) { group->getExtensionByType()->removeObject(obj); } if (!hasObject(obj)) { grp.push_back(obj); ret.push_back(obj); } } } Group.setValues(grp); return ret; } std::vector GeoFeatureGroupExtension::removeObjects(std::vector objects) { std::vector removed; std::vector grp = Group.getValues(); for (auto object : objects) { // cross CoordinateSystem links are not allowed, so we need to remove the whole link group std::vector links = getCSRelevantLinks(object); links.push_back(object); // remove all links out of group for (auto link : links) { auto end = std::remove(grp.begin(), grp.end(), link); if (end != grp.end()) { grp.erase(end, grp.end()); removed.push_back(link); } } } if (!removed.empty()) { Group.setValues(grp); } return removed; } void GeoFeatureGroupExtension::extensionOnChanged(const Property* p) { // objects are only allowed in a single GeoFeatureGroup if (p == &Group && !Group.testStatus(Property::User3)) { if ((!getExtendedObject()->isRestoring() || getExtendedObject()->getDocument()->testStatus(Document::Importing)) && !getExtendedObject()->getDocument()->isPerformingTransaction()) { bool error = false; auto corrected = Group.getValues(); for (auto obj : Group.getValues()) { // we have already set the obj into the group, so in a case of multiple groups // getGroupOfObject would return anyone of it and hence it is possible that we miss // an error. We need a custom check auto list = obj->getInList(); for (auto in : list) { if (in == getExtendedObject()) { continue; } auto parent = in->getExtensionByType(true); if (parent && parent->hasObject(obj)) { error = true; corrected.erase(std::remove(corrected.begin(), corrected.end(), obj), corrected.end()); } } } // if an error was found we need to correct the values and inform the user if (error) { Base::ObjectStatusLocker guard(Property::User3, &Group); Group.setValues(corrected); throw Base::RuntimeError("Object can only be in a single GeoFeatureGroup"); } } } App::GroupExtension::extensionOnChanged(p); } std::vector GeoFeatureGroupExtension::getScopedObjectsFromLinks(const DocumentObject* obj, LinkScope scope) { if (!obj) { return {}; } // we get all linked objects. We can't use outList() as this includes the links from expressions std::vector result; std::vector list; obj->getPropertyList(list); for (App::Property* prop : list) { auto vec = getScopedObjectsFromLink(prop, scope); result.insert(result.end(), vec.begin(), vec.end()); } // clear all null objects and duplicates std::sort(result.begin(), result.end()); result.erase(std::unique(result.begin(), result.end()), result.end()); return result; } std::vector GeoFeatureGroupExtension::getScopedObjectsFromLink(App::Property* prop, LinkScope scope) { if (!prop) { return {}; } std::vector result; auto link = freecad_cast(prop); if (link && link->getScope() == scope) { link->getLinks(result); } return result; } void GeoFeatureGroupExtension::getCSOutList(const App::DocumentObject* obj, std::vector& vec) { if (!obj) { return; } // we get all relevant linked objects. We can't use outList() as this includes the links from // expressions, also we only want links with scope Local auto result = getScopedObjectsFromLinks(obj, LinkScope::Local); //we remove all links to origin features and origins, they belong to a CS too and can't be moved result.erase(std::remove_if(result.begin(), result.end(), [](App::DocumentObject* obj)->bool { return (obj->isDerivedFrom() || obj->isDerivedFrom()); }), result.end()); vec.insert(vec.end(), result.begin(), result.end()); // post process the vector std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } void GeoFeatureGroupExtension::getCSInList(const DocumentObject* obj, std::vector& vec) { if (!obj) { return; } // search the inlist for objects that have non-expression links to us for (App::DocumentObject* parent : obj->getInList()) { // not interested in other groups (and here we mean all groups, normal ones and // geofeaturegroup) if (parent->hasExtension(App::GroupExtension::getExtensionClassTypeId())) { continue; } // check if the link is real Local scope one or if it is a expression one (could also be // both, so it is not enough to check the expressions) auto res = getScopedObjectsFromLinks(parent, LinkScope::Local); if (std::ranges::find(res, obj) != res.end()) { vec.push_back(parent); } } // clear all duplicates std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } std::vector GeoFeatureGroupExtension::getCSRelevantLinks(const DocumentObject* obj) { if (!obj) { return {}; } // get all out links std::vector vec; recursiveCSRelevantLinks(obj, vec); // post process the list after we added many things std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); vec.erase(std::remove(vec.begin(), vec.end(), obj), vec.end()); return vec; } void GeoFeatureGroupExtension::recursiveCSRelevantLinks(const DocumentObject* obj, std::vector& vec) { if (!obj) { return; } std::vector links; getCSOutList(obj, links); getCSInList(obj, links); // go on traversing the graph in all directions! for (auto o : links) { if (!o || o == obj || std::ranges::find(vec, o) != vec.end()) { continue; } vec.push_back(o); recursiveCSRelevantLinks(o, vec); } } bool GeoFeatureGroupExtension::extensionGetSubObject(DocumentObject*& ret, const char* subname, PyObject** pyObj, Base::Matrix4D* mat, bool transform, int depth) const { ret = nullptr; const char* dot; if (!subname || *subname == 0) { auto obj = dynamic_cast(getExtendedContainer()); ret = const_cast(obj); if (mat && transform) { *mat *= const_cast(this)->placement().getValue().toMatrix(); } } else if ((dot = strchr(subname, '.'))) { if (subname[0] != '$') { ret = Group.findUsingMap(std::string(subname, dot)); } else { std::string name = std::string(subname + 1, dot); for (auto child : Group.getValues()) { if (name == child->Label.getStrValue()) { ret = child; break; } } } if (ret) { ++dot; if (*dot && !ret->hasExtension(App::LinkBaseExtension::getExtensionClassTypeId()) && !ret->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { // Consider this // Body // | -- Pad // | -- Sketch // // Suppose we want to getSubObject(Body,"Pad.Sketch.") // Because of the special property of geo feature group, both // Pad and Sketch are children of Body, so we can't call // getSubObject(Pad,"Sketch.") as this will transform Sketch // using Pad's placement. // const char* next = strchr(dot, '.'); if (next) { App::DocumentObject* nret = nullptr; extensionGetSubObject(nret, dot, pyObj, mat, transform, depth + 1); if (nret) { ret = nret; return true; } } } if (mat && transform) { *mat *= const_cast(this)->placement().getValue().toMatrix(); } ret = ret->getSubObject(dot, pyObj, mat, true, depth + 1); } } return true; } bool GeoFeatureGroupExtension::areLinksValid(const DocumentObject* obj) { if (!obj) { return true; } std::vector list; obj->getPropertyList(list); for (App::Property* prop : list) { if (!isLinkValid(prop)) { return false; } } return true; } bool GeoFeatureGroupExtension::isLinkValid(App::Property* prop) { if (!prop) { return true; } // get the object that holds the property if (!prop->getContainer()->isDerivedFrom()) { return true; // this link comes not from a document object, scopes are meaningless } auto obj = static_cast(prop->getContainer()); // no cross CS link for local links. auto result = getScopedObjectsFromLink(prop, LinkScope::Local); auto group = getGroupOfObject(obj); for (auto link : result) { if (getGroupOfObject(link) != group) { return false; } } // for links with scope SubGroup we need to check if all features are part of subgroups if (obj->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { result = getScopedObjectsFromLink(prop, LinkScope::Child); auto groupExt = obj->getExtensionByType(); for (auto link : result) { if (!groupExt->hasObject(link, true)) { return false; } } } return true; } void GeoFeatureGroupExtension::getInvalidLinkObjects(const DocumentObject* obj, std::vector& vec) { if (!obj) { return; } // no cross CS link for local links. auto result = getScopedObjectsFromLinks(obj, LinkScope::Local); auto group = obj->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId()) ? obj : getGroupOfObject(obj); for (auto link : result) { if (getGroupOfObject(link) != group) { vec.push_back(link); } } // for links with scope SubGroup we need to check if all features are part of subgroups if (group) { result = getScopedObjectsFromLinks(obj, LinkScope::Child); auto groupExt = group->getExtensionByType(); for (auto link : result) { if (!groupExt->hasObject(link, true)) { vec.push_back(link); } } } } bool GeoFeatureGroupExtension::extensionGetSubObjects(std::vector& ret, int) const { for (auto obj : Group.getValues()) { if (obj && obj->isAttachedToDocument() && !obj->testStatus(ObjectStatus::GeoExcluded)) { ret.push_back(std::string(obj->getNameInDocument()) + '.'); } } return true; } // Python feature --------------------------------------------------------- namespace App { EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::GeoFeatureGroupExtensionPython, App::GeoFeatureGroupExtension) // explicit template instantiation template class AppExport ExtensionPythonT>; } // namespace App