This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/intern/dot_export.cc

324 lines
7.6 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <iomanip>
#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>(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>(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<UndirectedEdge>(edge));
return *edge;
}
DirectedEdge &DirectedGraph::new_edge(NodePort from, NodePort to)
{
DirectedEdge *edge = new DirectedEdge(from, to);
edges_.append(std::unique_ptr<DirectedEdge>(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<DirectedEdge> &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<UndirectedEdge> &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<std::string> input_names,
Span<std::string> output_names)
: node_(&node)
{
std::stringstream ss;
ss << "<<table border=\"0\" cellspacing=\"3\">";
/* Header */
ss << "<tr><td colspan=\"3\" align=\"center\"><b>";
ss << ((name.size() == 0) ? "No Name" : name);
ss << "</b></td></tr>";
/* Sockets */
int socket_max_amount = std::max(input_names.size(), output_names.size());
for (int i = 0; i < socket_max_amount; i++) {
ss << "<tr>";
if (i < input_names.size()) {
StringRef name = input_names[i];
if (name.size() == 0) {
name = "No Name";
}
ss << "<td align=\"left\" port=\"in" << i << "\">";
ss << name;
ss << "</td>";
}
else {
ss << "<td></td>";
}
ss << "<td></td>";
if (i < output_names.size()) {
StringRef name = output_names[i];
if (name.size() == 0) {
name = "No Name";
}
ss << "<td align=\"right\" port=\"out" << i << "\">";
ss << name;
ss << "</td>";
}
else {
ss << "<td></td>";
}
ss << "</tr>";
}
ss << "</table>>";
node_->attributes.set("label", ss.str());
node_->set_shape(Attr_shape::Rectangle);
}
} // namespace blender::dot