324 lines
7.5 KiB
C++
324 lines
7.5 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 << R"(<<table border="0" cellspacing="3">)";
|
|
|
|
/* Header */
|
|
ss << R"(<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 << R"(<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 << R"(<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
|