/* SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "BLI_dot_export.hh" namespace blender::dot { /* Graph Building ************************************************/ Node &Graph::new_node(StringRef label) { Node *node = new Node(*this); nodes_.append(std::unique_ptr(node)); top_level_nodes_.add_new(node); node->attributes.set("label", label); return *node; } Cluster &Graph::new_cluster(StringRef label) { Cluster *cluster = new Cluster(*this); clusters_.append(std::unique_ptr(cluster)); top_level_clusters_.add_new(cluster); cluster->attributes.set("label", label); return *cluster; } UndirectedEdge &UndirectedGraph::new_edge(NodePort a, NodePort b) { UndirectedEdge *edge = new UndirectedEdge(a, b); edges_.append(std::unique_ptr(edge)); return *edge; } DirectedEdge &DirectedGraph::new_edge(NodePort from, NodePort to) { DirectedEdge *edge = new DirectedEdge(from, to); edges_.append(std::unique_ptr(edge)); return *edge; } void Cluster::set_parent_cluster(Cluster *new_parent) { if (parent_ == new_parent) { return; } if (parent_ == nullptr) { graph_.top_level_clusters_.remove(this); new_parent->children_.add_new(this); } else if (new_parent == nullptr) { parent_->children_.remove(this); graph_.top_level_clusters_.add_new(this); } else { parent_->children_.remove(this); new_parent->children_.add_new(this); } parent_ = new_parent; } void Node::set_parent_cluster(Cluster *cluster) { if (cluster_ == cluster) { return; } if (cluster_ == nullptr) { graph_.top_level_nodes_.remove(this); cluster->nodes_.add_new(this); } else if (cluster == nullptr) { cluster_->nodes_.remove(this); graph_.top_level_nodes_.add_new(this); } else { cluster_->nodes_.remove(this); cluster->nodes_.add_new(this); } cluster_ = cluster; } /* Utility methods **********************************************/ void Graph::set_random_cluster_bgcolors() { for (Cluster *cluster : top_level_clusters_) { cluster->set_random_cluster_bgcolors(); } } void Cluster::set_random_cluster_bgcolors() { float hue = rand() / float(RAND_MAX); float staturation = 0.3f; float value = 0.8f; this->attributes.set("bgcolor", color_attr_from_hsv(hue, staturation, value)); for (Cluster *cluster : children_) { cluster->set_random_cluster_bgcolors(); } } bool Cluster::contains(Node &node) const { Cluster *current = node.parent_cluster(); while (current != nullptr) { if (current == this) { return true; } current = current->parent_; } return false; } /* Dot Generation **********************************************/ std::string DirectedGraph::to_dot_string() const { std::stringstream ss; ss << "digraph {\n"; this->export__declare_nodes_and_clusters(ss); ss << "\n"; for (const std::unique_ptr &edge : edges_) { edge->export__as_edge_statement(ss); ss << "\n"; } ss << "}\n"; return ss.str(); } std::string UndirectedGraph::to_dot_string() const { std::stringstream ss; ss << "graph {\n"; this->export__declare_nodes_and_clusters(ss); ss << "\n"; for (const std::unique_ptr &edge : edges_) { edge->export__as_edge_statement(ss); ss << "\n"; } ss << "}\n"; return ss.str(); } void Graph::export__declare_nodes_and_clusters(std::stringstream &ss) const { ss << "graph "; attributes.export__as_bracket_list(ss); ss << "\n\n"; for (Node *node : top_level_nodes_) { node->export__as_declaration(ss); } for (Cluster *cluster : top_level_clusters_) { cluster->export__declare_nodes_and_clusters(ss); } } void Cluster::export__declare_nodes_and_clusters(std::stringstream &ss) const { ss << "subgraph " << this->name() << " {\n"; ss << "graph "; attributes.export__as_bracket_list(ss); ss << "\n\n"; for (Node *node : nodes_) { node->export__as_declaration(ss); } for (Cluster *cluster : children_) { cluster->export__declare_nodes_and_clusters(ss); } ss << "}\n"; } void DirectedEdge::export__as_edge_statement(std::stringstream &ss) const { a_.to_dot_string(ss); ss << " -> "; b_.to_dot_string(ss); ss << " "; attributes.export__as_bracket_list(ss); } void UndirectedEdge::export__as_edge_statement(std::stringstream &ss) const { a_.to_dot_string(ss); ss << " -- "; b_.to_dot_string(ss); ss << " "; attributes.export__as_bracket_list(ss); } void Attributes::export__as_bracket_list(std::stringstream &ss) const { ss << "["; attributes_.foreach_item([&](StringRef key, StringRef value) { if (StringRef(value).startswith("<")) { /* Don't draw the quotes, this is an HTML-like value. */ ss << key << "=" << value << ", "; } else { ss << key << "=\""; for (char c : value) { if (c == '\"') { /* Escape double quotes. */ ss << '\\'; } ss << c; } ss << "\", "; } }); ss << "]"; } void Node::export__as_id(std::stringstream &ss) const { ss << '"' << uintptr_t(this) << '"'; } void Node::export__as_declaration(std::stringstream &ss) const { this->export__as_id(ss); ss << " "; attributes.export__as_bracket_list(ss); ss << "\n"; } void NodePort::to_dot_string(std::stringstream &ss) const { node_->export__as_id(ss); if (port_name_.has_value()) { ss << ":" << *port_name_; } } std::string color_attr_from_hsv(float h, float s, float v) { std::stringstream ss; ss << std::setprecision(4) << h << ' ' << s << ' ' << v; return ss.str(); } NodeWithSocketsRef::NodeWithSocketsRef(Node &node, StringRef name, Span input_names, Span output_names) : node_(&node) { std::stringstream ss; ss << R"(<)"; /* Header */ ss << R"("; /* Sockets */ int socket_max_amount = std::max(input_names.size(), output_names.size()); for (int i = 0; i < socket_max_amount; i++) { ss << ""; if (i < input_names.size()) { StringRef name = input_names[i]; if (name.size() == 0) { name = "No Name"; } ss << R"("; } else { ss << ""; } ss << ""; if (i < output_names.size()) { StringRef name = output_names[i]; if (name.size() == 0) { name = "No Name"; } ss << R"("; } else { ss << ""; } ss << ""; } ss << "
)"; ss << ((name.size() == 0) ? "No Name" : name); ss << "
"; ss << name; ss << ""; ss << name; ss << "
>"; node_->attributes.set("label", ss.str()); node_->set_shape(Attr_shape::Rectangle); } } // namespace blender::dot