This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenlib/intern/dot_export.cc
Jacques Lucke 7e4f988072 BLI: improve node graph export in dot format
This makes it bit easier to export node graphs and also allows for
more customization of links and sockets.
2022-12-29 15:09:52 +01:00

315 lines
7.1 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#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_;
}
if (port_position_.has_value()) {
ss << ":" << *port_position_;
}
}
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, const NodeWithSockets &data) : node_(&node)
{
std::stringstream ss;
ss << R"(<<table border="0" cellspacing="3">)";
/* Header */
ss << R"(<tr><td colspan="3" align="center"><b>)";
ss << (data.node_name.empty() ? "No Name" : data.node_name);
ss << "</b></td></tr>";
/* Sockets */
int socket_max_amount = std::max(data.inputs.size(), data.outputs.size());
for (int i = 0; i < socket_max_amount; i++) {
ss << "<tr>";
if (i < data.inputs.size()) {
const NodeWithSockets::Input &input = data.inputs[i];
ss << R"(<td align="left" port="in)" << i << "\">";
if (input.fontcolor) {
ss << R"(<font color=")" << *input.fontcolor << "\">";
}
ss << (input.name.empty() ? "No Name" : input.name);
if (input.fontcolor) {
ss << "</font>";
}
ss << "</td>";
}
else {
ss << "<td></td>";
}
ss << "<td></td>";
if (i < data.outputs.size()) {
const NodeWithSockets::Output &output = data.outputs[i];
ss << R"(<td align="right" port="out)" << i << "\">";
if (output.fontcolor) {
ss << R"(<font color=")" << *output.fontcolor << "\">";
}
ss << (output.name.empty() ? "No Name" : output.name);
if (output.fontcolor) {
ss << "</font>";
}
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