App: using fmtlib causes a linking failure together with boost's graphviz classes
To fix this failure a workaround is move the graphviz handling from Document.cpp to a separate source file See also: https://github.com/FreeCAD/FreeCAD/pull/8350
This commit is contained in:
@@ -64,7 +64,6 @@ recompute path. Also, it enables more complicated dependencies beyond trees.
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/graph/graphviz.hpp>
|
||||
#include <boost/graph/strong_components.hpp>
|
||||
|
||||
#ifdef USE_OLD_DAG
|
||||
@@ -95,18 +94,14 @@ recompute path. Also, it enables more complicated dependencies beyond trees.
|
||||
#include <Base/Stream.h>
|
||||
|
||||
#include "Document.h"
|
||||
#include "private/DocumentP.h"
|
||||
#include "Application.h"
|
||||
#include "AutoTransaction.h"
|
||||
#include "DocumentObserver.h"
|
||||
#include "DocumentObject.h"
|
||||
#include "ExpressionParser.h"
|
||||
#include "GeoFeature.h"
|
||||
#include "GeoFeatureGroupExtension.h"
|
||||
#include "License.h"
|
||||
#include "Link.h"
|
||||
#include "MergeDocuments.h"
|
||||
#include "Origin.h"
|
||||
#include "OriginGroupExtension.h"
|
||||
#include "Transactions.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -134,132 +129,34 @@ using namespace zipios;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// using VertexProperty = boost::property<boost::vertex_root_t, DocumentObject* >;
|
||||
using DependencyList = boost::adjacency_list <
|
||||
boost::vecS, // class OutEdgeListS : a Sequence or an AssociativeContainer
|
||||
boost::vecS, // class VertexListS : a Sequence or a RandomAccessContainer
|
||||
boost::directedS, // class DirectedS : This is a directed graph
|
||||
boost::no_property, // class VertexProperty:
|
||||
boost::no_property, // class EdgeProperty:
|
||||
boost::no_property, // class GraphProperty:
|
||||
boost::listS // class EdgeListS:
|
||||
>;
|
||||
using Traits = boost::graph_traits<DependencyList>;
|
||||
using Vertex = Traits::vertex_descriptor;
|
||||
using Edge = Traits::edge_descriptor;
|
||||
using Node = std::vector <size_t>;
|
||||
using Path = std::vector <size_t>;
|
||||
|
||||
namespace App {
|
||||
|
||||
static bool globalIsRestoring;
|
||||
static bool globalIsRelabeling;
|
||||
// Pimpl class
|
||||
struct DocumentP
|
||||
|
||||
DocumentP::DocumentP()
|
||||
{
|
||||
// Array to preserve the creation order of created objects
|
||||
std::vector<DocumentObject*> objectArray;
|
||||
std::unordered_set<App::DocumentObject*> touchedObjs;
|
||||
std::unordered_map<std::string, DocumentObject*> objectMap;
|
||||
std::unordered_map<long, DocumentObject*> objectIdMap;
|
||||
std::unordered_map<std::string, bool> partialLoadObjects;
|
||||
std::vector<DocumentObjectT> pendingRemove;
|
||||
long lastObjectId;
|
||||
DocumentObject* activeObject;
|
||||
Transaction *activeUndoTransaction;
|
||||
// pointer to the python class
|
||||
Py::Object DocumentPythonObject;
|
||||
int iTransactionMode;
|
||||
bool rollback;
|
||||
bool undoing; ///< document in the middle of undo or redo
|
||||
bool committing;
|
||||
bool opentransaction;
|
||||
std::bitset<32> StatusBits;
|
||||
int iUndoMode;
|
||||
unsigned int UndoMemSize;
|
||||
unsigned int UndoMaxStackSize;
|
||||
std::string programVersion;
|
||||
#ifdef USE_OLD_DAG
|
||||
DependencyList DepList;
|
||||
std::map<DocumentObject*, Vertex> VertexObjectList;
|
||||
std::map<Vertex, DocumentObject*> vertexMap;
|
||||
#endif //USE_OLD_DAG
|
||||
std::multimap<const App::DocumentObject*,
|
||||
std::unique_ptr<App::DocumentObjectExecReturn> > _RecomputeLog;
|
||||
|
||||
DocumentP() {
|
||||
static std::random_device _RD;
|
||||
static std::mt19937 _RGEN(_RD());
|
||||
static std::uniform_int_distribution<> _RDIST(0, 5000);
|
||||
// Set some random offset to reduce likelihood of ID collision when
|
||||
// copying shape from other document. It is probably better to randomize
|
||||
// on each object ID.
|
||||
lastObjectId = _RDIST(_RGEN);
|
||||
activeObject = nullptr;
|
||||
activeUndoTransaction = nullptr;
|
||||
iTransactionMode = 0;
|
||||
rollback = false;
|
||||
undoing = false;
|
||||
committing = false;
|
||||
opentransaction = false;
|
||||
StatusBits.set((size_t)Document::Closable, true);
|
||||
StatusBits.set((size_t)Document::KeepTrailingDigits, true);
|
||||
StatusBits.set((size_t)Document::Restoring, false);
|
||||
iUndoMode = 0;
|
||||
UndoMemSize = 0;
|
||||
UndoMaxStackSize = 20;
|
||||
}
|
||||
|
||||
void addRecomputeLog(const char *why, App::DocumentObject *obj) {
|
||||
addRecomputeLog(new DocumentObjectExecReturn(why, obj));
|
||||
}
|
||||
|
||||
void addRecomputeLog(const std::string &why, App::DocumentObject *obj) {
|
||||
addRecomputeLog(new DocumentObjectExecReturn(why, obj));
|
||||
}
|
||||
|
||||
void addRecomputeLog(DocumentObjectExecReturn *returnCode) {
|
||||
if(!returnCode->Which) {
|
||||
delete returnCode;
|
||||
return;
|
||||
}
|
||||
_RecomputeLog.emplace(returnCode->Which, std::unique_ptr<DocumentObjectExecReturn>(returnCode));
|
||||
returnCode->Which->setStatus(ObjectStatus::Error, true);
|
||||
}
|
||||
|
||||
void clearRecomputeLog(const App::DocumentObject *obj=nullptr) {
|
||||
if(!obj)
|
||||
_RecomputeLog.clear();
|
||||
else
|
||||
_RecomputeLog.erase(obj);
|
||||
}
|
||||
|
||||
void clearDocument() {
|
||||
objectArray.clear();
|
||||
for(auto &v : objectMap) {
|
||||
v.second->setStatus(ObjectStatus::Destroy, true);
|
||||
delete(v.second);
|
||||
v.second = nullptr;
|
||||
}
|
||||
objectMap.clear();
|
||||
objectIdMap.clear();
|
||||
}
|
||||
|
||||
const char *findRecomputeLog(const App::DocumentObject *obj) {
|
||||
auto range = _RecomputeLog.equal_range(obj);
|
||||
if(range.first == range.second)
|
||||
return nullptr;
|
||||
return (--range.second)->second->Why.c_str();
|
||||
}
|
||||
|
||||
static
|
||||
void findAllPathsAt(const std::vector <Node> &all_nodes, size_t id,
|
||||
std::vector <Path> &all_paths, Path tmp);
|
||||
std::vector<App::DocumentObject*>
|
||||
topologicalSort(const std::vector<App::DocumentObject*>& objects) const;
|
||||
std::vector<App::DocumentObject*>
|
||||
static partialTopologicalSort(const std::vector<App::DocumentObject*>& objects);
|
||||
};
|
||||
static std::random_device _RD;
|
||||
static std::mt19937 _RGEN(_RD());
|
||||
static std::uniform_int_distribution<> _RDIST(0, 5000);
|
||||
// Set some random offset to reduce likelihood of ID collision when
|
||||
// copying shape from other document. It is probably better to randomize
|
||||
// on each object ID.
|
||||
lastObjectId = _RDIST(_RGEN);
|
||||
activeObject = nullptr;
|
||||
activeUndoTransaction = nullptr;
|
||||
iTransactionMode = 0;
|
||||
rollback = false;
|
||||
undoing = false;
|
||||
committing = false;
|
||||
opentransaction = false;
|
||||
StatusBits.set((size_t)Document::Closable, true);
|
||||
StatusBits.set((size_t)Document::KeepTrailingDigits, true);
|
||||
StatusBits.set((size_t)Document::Restoring, false);
|
||||
iUndoMode = 0;
|
||||
UndoMemSize = 0;
|
||||
UndoMaxStackSize = 20;
|
||||
}
|
||||
|
||||
} // namespace App
|
||||
|
||||
@@ -275,641 +172,6 @@ void Document::setStatus(Status pos, bool on)
|
||||
d->StatusBits.set((size_t)pos, on);
|
||||
}
|
||||
|
||||
void Document::writeDependencyGraphViz(std::ostream &out)
|
||||
{
|
||||
// // caching vertex to DocObject
|
||||
//std::map<Vertex,DocumentObject*> VertexMap;
|
||||
//for(std::map<DocumentObject*,Vertex>::const_iterator It1= _DepConMap.begin();It1 != _DepConMap.end(); ++It1)
|
||||
// VertexMap[It1->second] = It1->first;
|
||||
|
||||
out << "digraph G {" << std::endl;
|
||||
out << "\tordering=out;" << std::endl;
|
||||
out << "\tnode [shape = box];" << std::endl;
|
||||
|
||||
for (const auto &It : d->objectMap) {
|
||||
out << "\t" << It.first << ";" << std::endl;
|
||||
std::vector<DocumentObject*> OutList = It.second->getOutList();
|
||||
for (const auto &It2 : OutList)
|
||||
if (It2)
|
||||
out << "\t" << It.first << "->" << It2->getNameInDocument() << ";" << std::endl;
|
||||
}
|
||||
|
||||
/*
|
||||
graph_traits<DependencyList>::edge_iterator ei, ei_end;
|
||||
for (tie(ei,ei_end) = edges(_DepList); ei != ei_end; ++ei)
|
||||
out << "\t"
|
||||
<< VertexMap[source(*ei, _DepList)]->getNameInDocument()
|
||||
<< " -> "
|
||||
<< VertexMap[target(*ei, _DepList)]->getNameInDocument()
|
||||
<< ";" << endl;
|
||||
*/
|
||||
out << "}" << endl;
|
||||
}
|
||||
|
||||
void Document::exportGraphviz(std::ostream& out) const
|
||||
{
|
||||
/* Type defs for a graph with graphviz attributes */
|
||||
using GraphvizAttributes = std::map<std::string, std::string>;
|
||||
using Graph = boost::subgraph< adjacency_list<vecS, vecS, directedS,
|
||||
property<vertex_attribute_t, GraphvizAttributes>,
|
||||
property<edge_index_t, int, property<edge_attribute_t, GraphvizAttributes> >,
|
||||
property<graph_name_t, std::string,
|
||||
property<graph_graph_attribute_t, GraphvizAttributes,
|
||||
property<graph_vertex_attribute_t, GraphvizAttributes,
|
||||
property<graph_edge_attribute_t, GraphvizAttributes>
|
||||
> > > > >;
|
||||
|
||||
/**
|
||||
* @brief The GraphCreator class
|
||||
*
|
||||
* This class creates the dependency graph for a document.
|
||||
*
|
||||
*/
|
||||
|
||||
class GraphCreator {
|
||||
public:
|
||||
|
||||
explicit GraphCreator(struct DocumentP* _d) : d(_d), vertex_no(0), seed(std::random_device()()), distribution(0,255) {
|
||||
build();
|
||||
}
|
||||
|
||||
const Graph & getGraph() const { return DepList; }
|
||||
|
||||
private:
|
||||
|
||||
void build() {
|
||||
// Set attribute(s) for main graph
|
||||
get_property(DepList, graph_graph_attribute)["compound"] = "true";
|
||||
|
||||
addSubgraphs();
|
||||
buildAdjacencyList();
|
||||
addEdges();
|
||||
markCycles();
|
||||
markOutOfScopeLinks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief getId returns a canonical string for a DocumentObject.
|
||||
* @param docObj Document object to get an ID from
|
||||
* @return A string
|
||||
*/
|
||||
|
||||
std::string getId(const DocumentObject * docObj) {
|
||||
return std::string((docObj)->getDocument()->getName()) + "#" + docObj->getNameInDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief getId returns a canonical string for an ObjectIdentifier;
|
||||
* @param path
|
||||
* @return A string
|
||||
*/
|
||||
|
||||
std::string getId(const ObjectIdentifier & path) {
|
||||
DocumentObject * docObj = path.getDocumentObject();
|
||||
if (!docObj)
|
||||
return std::string();
|
||||
|
||||
return std::string((docObj)->getDocument()->getName()) + "#" + docObj->getNameInDocument() + "." + path.getPropertyName() + path.getSubPathStr();
|
||||
}
|
||||
|
||||
std::string getClusterName(const DocumentObject * docObj) const {
|
||||
return std::string("cluster") + docObj->getNameInDocument();
|
||||
}
|
||||
|
||||
void setGraphLabel(Graph& g, const DocumentObject* obj) const {
|
||||
std::string name(obj->getNameInDocument());
|
||||
std::string label(obj->Label.getValue());
|
||||
if (name == label)
|
||||
get_property(g, graph_graph_attribute)["label"] = name;
|
||||
else
|
||||
get_property(g, graph_graph_attribute)["label"] = name + "\n(" + label + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief setGraphAttributes Set graph attributes on a subgraph for a DocumentObject node.
|
||||
* @param obj DocumentObject
|
||||
*/
|
||||
|
||||
void setGraphAttributes(const DocumentObject * obj) {
|
||||
assert(GraphList.find(obj) != GraphList.end());
|
||||
get_property(*GraphList[obj], graph_name) = getClusterName(obj);
|
||||
|
||||
get_property(*GraphList[obj], graph_graph_attribute)["bgcolor"] = "#e0e0e0";
|
||||
|
||||
get_property(*GraphList[obj], graph_graph_attribute)["style"] = "rounded,filled";
|
||||
setGraphLabel(*GraphList[obj], obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief setPropertyVertexAttributes Set vertex attributes for a Property node in a graph.
|
||||
* @param g Graph
|
||||
* @param vertex Property node
|
||||
* @param name Name of node
|
||||
*/
|
||||
|
||||
void setPropertyVertexAttributes(Graph & g, Vertex vertex, const std::string & name) {
|
||||
get(vertex_attribute, g)[vertex]["label"] = name;
|
||||
get(vertex_attribute, g)[vertex]["shape"] = "box";
|
||||
get(vertex_attribute, g)[vertex]["style"] = "dashed";
|
||||
get(vertex_attribute, g)[vertex]["fontsize"] = "8pt";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief addExpressionSubgraphIfNeeded Add a subgraph to the main graph if it is needed, i.e. there are defined at least one
|
||||
* expression in the document object, or other objects are referencing properties in it.
|
||||
* @param obj DocumentObject to assess.
|
||||
* @param CSSubgraphs Boolean if the GeoFeatureGroups are created as subgraphs
|
||||
*/
|
||||
|
||||
void addExpressionSubgraphIfNeeded(DocumentObject * obj, bool CSsubgraphs) {
|
||||
|
||||
auto expressions = obj->ExpressionEngine.getExpressions();
|
||||
|
||||
if (!expressions.empty()) {
|
||||
|
||||
Graph* graph = nullptr;
|
||||
graph = &DepList;
|
||||
if (CSsubgraphs) {
|
||||
auto group = GeoFeatureGroupExtension::getGroupOfObject(obj);
|
||||
if (group) {
|
||||
auto it = GraphList.find(group);
|
||||
if (it != GraphList.end())
|
||||
graph = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// If documentObject has an expression, create a subgraph for it
|
||||
if (graph && !GraphList[obj]) {
|
||||
GraphList[obj] = &graph->create_subgraph();
|
||||
setGraphAttributes(obj);
|
||||
}
|
||||
|
||||
// Create subgraphs for all document objects that it depends on; it will depend on some property there
|
||||
for (const auto &expr : expressions) {
|
||||
std::map<ObjectIdentifier, bool> deps;
|
||||
|
||||
expr.second->getIdentifiers(deps);
|
||||
|
||||
for (const auto &dep : deps) {
|
||||
if (dep.second)
|
||||
continue;
|
||||
DocumentObject * o = dep.first.getDocumentObject();
|
||||
|
||||
// Doesn't exist already?
|
||||
if (o && !GraphList[o]) {
|
||||
|
||||
if (CSsubgraphs) {
|
||||
auto group = GeoFeatureGroupExtension::getGroupOfObject(o);
|
||||
auto graph2 = group ? GraphList[group] : &DepList;
|
||||
if (graph2) {
|
||||
GraphList[o] = &graph2->create_subgraph();
|
||||
setGraphAttributes(o);
|
||||
}
|
||||
}
|
||||
else if (graph) {
|
||||
GraphList[o] = &graph->create_subgraph();
|
||||
setGraphAttributes(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief add Add @docObj to the graph, including all expressions (and dependencies) it includes.
|
||||
* @param docObj The document object to add.
|
||||
* @param name Name of node.
|
||||
*/
|
||||
|
||||
void add(DocumentObject *docObj, const std::string &name, const std::string &label, bool CSSubgraphs)
|
||||
{
|
||||
|
||||
//don't add objects twice
|
||||
if (std::find(objects.begin(), objects.end(), docObj) != objects.end())
|
||||
return;
|
||||
|
||||
//find the correct graph to add the vertex to. Check first expression graphs, afterwards
|
||||
//the parent CS and origin graphs
|
||||
Graph *sgraph = GraphList[docObj];
|
||||
if (CSSubgraphs) {
|
||||
if (!sgraph) {
|
||||
auto group = GeoFeatureGroupExtension::getGroupOfObject(docObj);
|
||||
if (group) {
|
||||
if (docObj->isDerivedFrom(App::OriginFeature::getClassTypeId()))
|
||||
sgraph = GraphList[group->getExtensionByType<OriginGroupExtension>()->Origin.getValue()];
|
||||
else
|
||||
sgraph = GraphList[group];
|
||||
}
|
||||
}
|
||||
if (!sgraph) {
|
||||
if (docObj->isDerivedFrom(OriginFeature::getClassTypeId()))
|
||||
sgraph = GraphList[static_cast<OriginFeature *>(docObj)->getOrigin()];
|
||||
}
|
||||
}
|
||||
if (!sgraph)
|
||||
sgraph = &DepList;
|
||||
|
||||
// Keep a list of all added document objects.
|
||||
objects.insert(docObj);
|
||||
|
||||
// Add vertex to graph. Track global and local index
|
||||
LocalVertexList[getId(docObj)] = add_vertex(*sgraph);
|
||||
GlobalVertexList[getId(docObj)] = vertex_no++;
|
||||
|
||||
// If node is in main graph, style it with rounded corners. If not, make it invisible.
|
||||
if (!GraphList[docObj]) {
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["style"] = "filled";
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["shape"] = "Mrecord";
|
||||
// Set node label
|
||||
if (name == label)
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["label"] = name;
|
||||
else
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["label"] = name + "\n(" + label + ")";
|
||||
}
|
||||
else {
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["style"] = "invis";
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["fixedsize"] = "true";
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["width"] = "0";
|
||||
get(vertex_attribute, *sgraph)[LocalVertexList[getId(docObj)]]["height"] = "0";
|
||||
}
|
||||
|
||||
// Add expressions and its dependencies
|
||||
auto expressions{docObj->ExpressionEngine.getExpressions()};
|
||||
for (const auto &expr : expressions) {
|
||||
auto found = std::as_const(GlobalVertexList).find(getId(expr.first));
|
||||
if (found == GlobalVertexList.end()) {
|
||||
int vid = LocalVertexList[getId(expr.first)] = add_vertex(*sgraph);
|
||||
GlobalVertexList[getId(expr.first)] = vertex_no++;
|
||||
setPropertyVertexAttributes(*sgraph, vid, expr.first.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Add all dependencies
|
||||
for (const auto &expression : expressions) {
|
||||
// Get dependencies
|
||||
std::map<ObjectIdentifier, bool> deps;
|
||||
expression.second->getIdentifiers(deps);
|
||||
|
||||
// Create subgraphs for all documentobjects that it depends on; it will depend on some property there
|
||||
for (const auto &dep : deps) {
|
||||
if (dep.second) {
|
||||
continue;
|
||||
}
|
||||
DocumentObject *depObjDoc = dep.first.getDocumentObject();
|
||||
auto found = GlobalVertexList.find(getId(dep.first));
|
||||
|
||||
if (found == GlobalVertexList.end()) {
|
||||
Graph *depSgraph = GraphList[depObjDoc] ? GraphList[depObjDoc] : &DepList;
|
||||
|
||||
LocalVertexList[getId(dep.first)] = add_vertex(*depSgraph);
|
||||
GlobalVertexList[getId(dep.first)] = vertex_no++;
|
||||
setPropertyVertexAttributes(*depSgraph, LocalVertexList[getId(dep.first)], dep.first.getPropertyName() + dep.first.getSubPathStr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recursiveCSSubgraphs(DocumentObject* cs, DocumentObject* parent) {
|
||||
|
||||
auto graph = parent ? GraphList[parent] : &DepList;
|
||||
// check if the value for the key 'parent' is null
|
||||
if (!graph)
|
||||
return;
|
||||
auto& sub = graph->create_subgraph();
|
||||
GraphList[cs] = ⊂
|
||||
get_property(sub, graph_name) = getClusterName(cs);
|
||||
|
||||
//build random color string
|
||||
std::stringstream stream;
|
||||
stream << "#" << std::setfill('0') << std::setw(2)<< std::hex << distribution(seed)
|
||||
<< std::setfill('0') << std::setw(2)<< std::hex << distribution(seed)
|
||||
<< std::setfill('0') << std::setw(2)<< std::hex << distribution(seed) << 80;
|
||||
std::string result(stream.str());
|
||||
|
||||
get_property(sub, graph_graph_attribute)["bgcolor"] = result;
|
||||
get_property(sub, graph_graph_attribute)["style"] = "rounded,filled";
|
||||
setGraphLabel(sub, cs);
|
||||
|
||||
for(auto obj : cs->getOutList()) {
|
||||
if (obj->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId())) {
|
||||
// in case of dependencies loops check if obj is already part of the
|
||||
// map to avoid infinite recursions
|
||||
auto it = GraphList.find(obj);
|
||||
if (it == GraphList.end())
|
||||
recursiveCSSubgraphs(obj, cs);
|
||||
}
|
||||
}
|
||||
|
||||
//setup the origin if available
|
||||
if(cs->hasExtension(App::OriginGroupExtension::getExtensionClassTypeId())) {
|
||||
auto origin = cs->getExtensionByType<OriginGroupExtension>()->Origin.getValue();
|
||||
if (!origin) {
|
||||
std::cerr << "Origin feature not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto& osub = sub.create_subgraph();
|
||||
GraphList[origin] = &osub;
|
||||
get_property(osub, graph_name) = getClusterName(origin);
|
||||
get_property(osub, graph_graph_attribute)["bgcolor"] = "none";
|
||||
setGraphLabel(osub, origin);
|
||||
}
|
||||
}
|
||||
|
||||
void addSubgraphs() {
|
||||
|
||||
ParameterGrp::handle depGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DependencyGraph");
|
||||
bool CSSubgraphs = depGrp->GetBool("GeoFeatureSubgraphs", true);
|
||||
|
||||
if(CSSubgraphs) {
|
||||
//first build up the coordinate system subgraphs
|
||||
for (auto objectIt : d->objectArray) {
|
||||
// do not require an empty inlist (#0003465: Groups breaking dependency graph)
|
||||
// App::Origin now has the GeoFeatureGroupExtension but it should not move its
|
||||
// group symbol outside its parent
|
||||
if (!objectIt->isDerivedFrom(Origin::getClassTypeId()) &&
|
||||
objectIt->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()))
|
||||
recursiveCSSubgraphs(objectIt, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal document objects
|
||||
for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It)
|
||||
addExpressionSubgraphIfNeeded(It->second, CSSubgraphs);
|
||||
|
||||
// Add external document objects
|
||||
for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) {
|
||||
std::vector<DocumentObject*> OutList = It->second->getOutList();
|
||||
for (std::vector<DocumentObject*>::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) {
|
||||
if (*It2) {
|
||||
std::map<std::string,Vertex>::const_iterator item = GlobalVertexList.find(getId(*It2));
|
||||
|
||||
if (item == GlobalVertexList.end())
|
||||
addExpressionSubgraphIfNeeded(*It2, CSSubgraphs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Filling up the adjacency List
|
||||
void buildAdjacencyList() {
|
||||
|
||||
ParameterGrp::handle depGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DependencyGraph");
|
||||
bool CSSubgraphs = depGrp->GetBool("GeoFeatureSubgraphs", true);
|
||||
|
||||
// Add internal document objects
|
||||
for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It)
|
||||
add(It->second, It->second->getNameInDocument(), It->second->Label.getValue(), CSSubgraphs);
|
||||
|
||||
// Add external document objects
|
||||
for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) {
|
||||
std::vector<DocumentObject*> OutList = It->second->getOutList();
|
||||
for (std::vector<DocumentObject*>::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) {
|
||||
if (*It2) {
|
||||
std::map<std::string,Vertex>::const_iterator item = GlobalVertexList.find(getId(*It2));
|
||||
|
||||
if (item == GlobalVertexList.end())
|
||||
add(*It2,
|
||||
std::string((*It2)->getDocument()->getName()) + "#" + (*It2)->getNameInDocument(),
|
||||
std::string((*It2)->getDocument()->getName()) + "#" + (*It2)->Label.getValue(),
|
||||
CSSubgraphs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addEdges() {
|
||||
// Get edge properties for main graph
|
||||
const boost::property_map<Graph, boost::edge_attribute_t>::type& edgeAttrMap = boost::get(boost::edge_attribute, DepList);
|
||||
|
||||
// Track edges between document objects connected by expression dependencies
|
||||
std::set<std::pair<const DocumentObject*, const DocumentObject*> > existingEdges;
|
||||
|
||||
// Add edges between properties
|
||||
for (const auto &docObj : objects) {
|
||||
|
||||
// Add expressions and its dependencies
|
||||
auto expressions = docObj->ExpressionEngine.getExpressions();
|
||||
for (const auto &expr : expressions) {
|
||||
std::map<ObjectIdentifier, bool> deps;
|
||||
expr.second->getIdentifiers(deps);
|
||||
|
||||
// Create subgraphs for all documentobjects that it depends on; it will depend on some property there
|
||||
for (const auto &dep : deps) {
|
||||
if (dep.second)
|
||||
continue;
|
||||
DocumentObject * depObjDoc = dep.first.getDocumentObject();
|
||||
Edge edge;
|
||||
bool inserted;
|
||||
|
||||
tie(edge, inserted) = add_edge(GlobalVertexList[getId(expr.first)], GlobalVertexList[getId(dep.first)], DepList);
|
||||
|
||||
// Add this edge to the set of all expression generated edges
|
||||
existingEdges.insert(std::make_pair(docObj, depObjDoc));
|
||||
|
||||
// Edges between properties should be a bit smaller, and dashed
|
||||
edgeAttrMap[edge]["arrowsize"] = "0.5";
|
||||
edgeAttrMap[edge]["style"] = "dashed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParameterGrp::handle depGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DependencyGraph");
|
||||
bool omitGeoFeatureGroups = depGrp->GetBool("GeoFeatureSubgraphs", true);
|
||||
|
||||
// Add edges between document objects
|
||||
for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) {
|
||||
|
||||
if(omitGeoFeatureGroups) {
|
||||
//coordinate systems are represented by subgraphs
|
||||
if(It->second->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()))
|
||||
continue;
|
||||
|
||||
//as well as origins
|
||||
if(It->second->isDerivedFrom(Origin::getClassTypeId()))
|
||||
continue;
|
||||
}
|
||||
|
||||
std::map<DocumentObject*, int> dups;
|
||||
std::vector<DocumentObject*> OutList = It->second->getOutList();
|
||||
const DocumentObject * docObj = It->second;
|
||||
|
||||
for (std::vector<DocumentObject*>::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) {
|
||||
if (*It2) {
|
||||
|
||||
// Count duplicate edges
|
||||
bool inserted = edge(GlobalVertexList[getId(docObj)], GlobalVertexList[getId(*It2)], DepList).second;
|
||||
if (inserted) {
|
||||
dups[*It2]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip edge if an expression edge already exists
|
||||
if (existingEdges.find(std::make_pair(docObj, *It2)) != existingEdges.end())
|
||||
continue;
|
||||
|
||||
// Add edge
|
||||
|
||||
Edge edge;
|
||||
|
||||
tie(edge, inserted) = add_edge(GlobalVertexList[getId(docObj)], GlobalVertexList[getId(*It2)], DepList);
|
||||
|
||||
// Set properties to make arrows go between subgraphs if needed
|
||||
if (GraphList[docObj])
|
||||
edgeAttrMap[edge]["ltail"] = getClusterName(docObj);
|
||||
if (GraphList[*It2])
|
||||
edgeAttrMap[edge]["lhead"] = getClusterName(*It2);
|
||||
}
|
||||
}
|
||||
|
||||
// Set labels for duplicate edges
|
||||
for (std::map<DocumentObject*, int>::const_iterator It2 = dups.begin(); It2 != dups.end(); ++It2) {
|
||||
Edge e(edge(GlobalVertexList[getId(It->second)], GlobalVertexList[getId(It2->first)], DepList).first);
|
||||
std::stringstream s;
|
||||
s << " " << (It2->second + 1) << "x";
|
||||
edgeAttrMap[e]["label"] = s.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using EdgeMap = std::unordered_multimap<Vertex, Edge>;
|
||||
|
||||
void removeEdges(EdgeMap & in_edges,
|
||||
EdgeMap & out_edges,
|
||||
std::pair<EdgeMap::iterator, EdgeMap::iterator > i_pair,
|
||||
std::function<Vertex (const Edge&)> select_vertex) {
|
||||
auto i = i_pair.first;
|
||||
|
||||
while (i != i_pair.second) {
|
||||
// Remove from in edges in other nodes
|
||||
auto in_i_pair = in_edges.equal_range(select_vertex(i->second));
|
||||
auto in_i = in_i_pair.first;
|
||||
|
||||
while (in_i != in_i_pair.second) {
|
||||
if (in_i->second == i->second)
|
||||
in_i = in_edges.erase(in_i);
|
||||
else
|
||||
++in_i;
|
||||
}
|
||||
|
||||
// Remove node from out_edges
|
||||
i = out_edges.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
#elif defined (__GNUC__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||
#endif
|
||||
|
||||
void markCycles() {
|
||||
bool changed = true;
|
||||
std::unordered_set<Vertex> in_use;
|
||||
EdgeMap in_edges;
|
||||
EdgeMap out_edges;
|
||||
|
||||
// Add all vertices to the in_use set
|
||||
graph_traits<Graph>::vertex_iterator vi, vi_end;
|
||||
tie(vi, vi_end) = vertices(DepList);
|
||||
for (; vi != vi_end; ++vi)
|
||||
in_use.insert(*vi);
|
||||
|
||||
// Add all edges to the in_edges and out_edges multimaps
|
||||
graph_traits<Graph>::edge_iterator ei, ei_end;
|
||||
tie(ei, ei_end) = edges(DepList);
|
||||
for (; ei != ei_end; ++ei) {
|
||||
in_edges.insert(std::make_pair(target(*ei, DepList), *ei));
|
||||
out_edges.insert(std::make_pair(source(*ei, DepList), *ei));
|
||||
}
|
||||
|
||||
// Go through dependency graph and remove nodes with either no input or output
|
||||
// A normal DAG without any cycles will get all its edges removed.
|
||||
// If one or more cycles exist in the graph, there will remain nodes with
|
||||
// both in and out edges.
|
||||
|
||||
while (changed) {
|
||||
auto uvi = in_use.begin();
|
||||
auto uvi_end = in_use.end();
|
||||
|
||||
// Flag that no changes has occurred so far. If the loop goes through
|
||||
// without this flag being set to true, we are done.
|
||||
changed = false;
|
||||
|
||||
while (uvi != uvi_end) {
|
||||
auto i_in_deg_pair = in_edges.equal_range(*uvi);
|
||||
auto i_out_deg_pair = out_edges.equal_range(*uvi);
|
||||
|
||||
if (i_in_deg_pair.first == in_edges.end() && i_out_deg_pair.first == out_edges.end()) {
|
||||
uvi = in_use.erase(uvi);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove out edges of nodes that don't have a single edge in
|
||||
if (i_in_deg_pair.first == in_edges.end()) {
|
||||
removeEdges(in_edges, out_edges, i_out_deg_pair, [&](Edge e) { return target(e, DepList); });
|
||||
changed = true;
|
||||
i_out_deg_pair = out_edges.equal_range(*uvi);
|
||||
}
|
||||
|
||||
// Remove in edges of nodes that don't have a single edge out
|
||||
if (i_out_deg_pair.first == out_edges.end()) {
|
||||
removeEdges(out_edges, in_edges, i_in_deg_pair, [&](Edge e) { return source(e, DepList); });
|
||||
changed = true;
|
||||
}
|
||||
|
||||
++uvi;
|
||||
}
|
||||
}
|
||||
|
||||
// Update colors in graph
|
||||
const boost::property_map<Graph, boost::edge_attribute_t>::type& edgeAttrMap = boost::get(boost::edge_attribute, DepList);
|
||||
for (auto ei = out_edges.begin(), ei_end = out_edges.end(); ei != ei_end; ++ei)
|
||||
edgeAttrMap[ei->second]["color"] = "red";
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
#elif defined (__GNUC__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
void markOutOfScopeLinks() {
|
||||
const boost::property_map<Graph, boost::edge_attribute_t>::type& edgeAttrMap = boost::get(boost::edge_attribute, DepList);
|
||||
|
||||
for( auto obj : objects) {
|
||||
|
||||
std::vector<App::DocumentObject*> invalids;
|
||||
GeoFeatureGroupExtension::getInvalidLinkObjects(obj, invalids);
|
||||
//isLinkValid returns true for non-link properties
|
||||
for(auto linkedObj : invalids) {
|
||||
|
||||
auto res = edge(GlobalVertexList[getId(obj)], GlobalVertexList[getId(linkedObj)], DepList);
|
||||
if(res.second)
|
||||
edgeAttrMap[res.first]["color"] = "orange";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct DocumentP* d;
|
||||
Graph DepList;
|
||||
int vertex_no;
|
||||
std::map<std::string, Vertex> LocalVertexList;
|
||||
std::map<std::string, Vertex> GlobalVertexList;
|
||||
std::set<const DocumentObject*> objects;
|
||||
std::map<const DocumentObject*, Graph*> GraphList;
|
||||
//random color generation
|
||||
std::mt19937 seed;
|
||||
std::uniform_int_distribution<int> distribution;
|
||||
};
|
||||
|
||||
GraphCreator g(d);
|
||||
|
||||
boost::write_graphviz(out, g.getGraph());
|
||||
}
|
||||
|
||||
//bool _has_cycle_dfs(const DependencyList & g, vertex_t u, default_color_type * color)
|
||||
//{
|
||||
// color[u] = gray_color;
|
||||
|
||||
Reference in New Issue
Block a user