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.
This commit is contained in:
Jacques Lucke 2022-12-29 15:09:52 +01:00
parent cc48610d2c
commit 7e4f988072
5 changed files with 117 additions and 44 deletions

View File

@ -184,10 +184,13 @@ class NodePort {
private:
Node *node_;
std::optional<std::string> port_name_;
std::optional<std::string> port_position_;
public:
NodePort(Node &node, std::optional<std::string> port_name = {})
: node_(&node), port_name_(std::move(port_name))
NodePort(Node &node,
std::optional<std::string> port_name = {},
std::optional<std::string> port_position = {})
: node_(&node), port_name_(std::move(port_name)), port_position_(std::move(port_position))
{
}
@ -248,15 +251,43 @@ class UndirectedEdge : public Edge {
std::string color_attr_from_hsv(float h, float s, float v);
struct NodeWithSockets {
struct Socket {
std::string name;
std::optional<std::string> fontcolor;
};
struct Input : public Socket {
};
struct Output : public Socket {
};
std::string node_name;
Vector<Input> inputs;
Vector<Output> outputs;
Input &add_input(std::string name)
{
this->inputs.append({});
Input &input = this->inputs.last();
input.name = std::move(name);
return input;
}
Output &add_output(std::string name)
{
this->outputs.append({});
Output &output = this->outputs.last();
output.name = std::move(name);
return output;
}
};
class NodeWithSocketsRef {
private:
Node *node_;
public:
NodeWithSocketsRef(Node &node,
StringRef name,
Span<std::string> input_names,
Span<std::string> output_names);
NodeWithSocketsRef(Node &node, const NodeWithSockets &data);
Node &node()
{
@ -266,13 +297,13 @@ class NodeWithSocketsRef {
NodePort input(int index) const
{
std::string port = "\"in" + std::to_string(index) + "\"";
return NodePort(*node_, port);
return NodePort(*node_, port, "w");
}
NodePort output(int index) const
{
std::string port = "\"out" + std::to_string(index) + "\"";
return NodePort(*node_, port);
return NodePort(*node_, port, "e");
}
};

View File

@ -244,6 +244,9 @@ void NodePort::to_dot_string(std::stringstream &ss) const
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)
@ -253,11 +256,7 @@ std::string color_attr_from_hsv(float h, float s, float v)
return ss.str();
}
NodeWithSocketsRef::NodeWithSocketsRef(Node &node,
StringRef name,
Span<std::string> input_names,
Span<std::string> output_names)
: node_(&node)
NodeWithSocketsRef::NodeWithSocketsRef(Node &node, const NodeWithSockets &data) : node_(&node)
{
std::stringstream ss;
@ -265,33 +264,39 @@ NodeWithSocketsRef::NodeWithSocketsRef(Node &node,
/* Header */
ss << R"(<tr><td colspan="3" align="center"><b>)";
ss << ((name.size() == 0) ? "No Name" : name);
ss << (data.node_name.empty() ? "No Name" : data.node_name);
ss << "</b></td></tr>";
/* Sockets */
int socket_max_amount = std::max(input_names.size(), output_names.size());
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 < input_names.size()) {
StringRef name = input_names[i];
if (name.size() == 0) {
name = "No Name";
}
if (i < data.inputs.size()) {
const NodeWithSockets::Input &input = data.inputs[i];
ss << R"(<td align="left" port="in)" << i << "\">";
ss << name;
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 < output_names.size()) {
StringRef name = output_names[i];
if (name.size() == 0) {
name = "No Name";
}
if (i < data.outputs.size()) {
const NodeWithSockets::Output &output = data.outputs[i];
ss << R"(<td align="right" port="out)" << i << "\">";
ss << name;
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 {

View File

@ -19,6 +19,10 @@
#include "FN_lazy_function.hh"
namespace blender::dot {
class DirectedEdge;
}
namespace blender::fn::lazy_function {
class Socket;
@ -238,10 +242,23 @@ class Graph : NonCopyable, NonMovable {
*/
bool node_indices_are_valid() const;
/**
* Optional configuration options for the dot graph generation. This allows creating
* visualizations for specific purposes.
*/
class ToDotOptions {
public:
virtual std::string socket_name(const Socket &socket) const;
virtual std::optional<std::string> socket_font_color(const Socket &socket) const;
virtual void add_edge_attributes(const OutputSocket &from,
const InputSocket &to,
dot::DirectedEdge &dot_edge) const;
};
/**
* Utility to generate a dot graph string for the graph. This can be used for debugging.
*/
std::string to_dot() const;
std::string to_dot(const ToDotOptions &options = {}) const;
};
/* -------------------------------------------------------------------- */

View File

@ -152,7 +152,23 @@ std::string DummyDebugInfo::output_name(const int /*i*/) const
return fallback_name;
}
std::string Graph::to_dot() const
std::string Graph::ToDotOptions::socket_name(const Socket &socket) const
{
return socket.name();
}
std::optional<std::string> Graph::ToDotOptions::socket_font_color(const Socket & /*socket*/) const
{
return std::nullopt;
}
void Graph::ToDotOptions::add_edge_attributes(const OutputSocket & /*from*/,
const InputSocket & /*to*/,
dot::DirectedEdge & /*dot_edge*/) const
{
}
std::string Graph::to_dot(const ToDotOptions &options) const
{
dot::DirectedGraph digraph;
digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
@ -168,17 +184,20 @@ std::string Graph::to_dot() const
dot_node.set_background_color("white");
}
Vector<std::string> input_names;
Vector<std::string> output_names;
dot::NodeWithSockets dot_node_with_sockets;
dot_node_with_sockets.node_name = node->name();
for (const InputSocket *socket : node->inputs()) {
input_names.append(socket->name());
dot::NodeWithSockets::Input &dot_input = dot_node_with_sockets.add_input(
options.socket_name(*socket));
dot_input.fontcolor = options.socket_font_color(*socket);
}
for (const OutputSocket *socket : node->outputs()) {
output_names.append(socket->name());
dot::NodeWithSockets::Output &dot_output = dot_node_with_sockets.add_output(
options.socket_name(*socket));
dot_output.fontcolor = options.socket_font_color(*socket);
}
dot_nodes.add_new(node,
dot::NodeWithSocketsRef(dot_node, node->name(), input_names, output_names));
dot_nodes.add_new(node, dot::NodeWithSocketsRef(dot_node, dot_node_with_sockets));
}
for (const Node *node : nodes_) {
@ -188,7 +207,9 @@ std::string Graph::to_dot() const
if (const OutputSocket *origin = socket->origin()) {
dot::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&origin->node());
digraph.new_edge(from_dot_node.output(origin->index()), to_dot_port);
dot::DirectedEdge &dot_edge = digraph.new_edge(from_dot_node.output(origin->index()),
to_dot_port);
options.add_edge_attributes(*origin, *socket, dot_edge);
}
else if (const void *default_value = socket->default_value()) {
const CPPType &type = socket->type();

View File

@ -344,27 +344,26 @@ std::string DerivedNodeTree::to_dot() const
dot_node.set_parent_cluster(cluster);
dot_node.set_background_color("white");
Vector<std::string> input_names;
Vector<std::string> output_names;
dot::NodeWithSockets dot_node_with_sockets;
for (const bNodeSocket *socket : node->input_sockets()) {
if (socket->is_available()) {
input_names.append(socket->name);
dot_node_with_sockets.add_input(socket->name);
}
}
for (const bNodeSocket *socket : node->output_sockets()) {
if (socket->is_available()) {
output_names.append(socket->name);
dot_node_with_sockets.add_output(socket->name);
}
}
dot::NodeWithSocketsRef dot_node_with_sockets = dot::NodeWithSocketsRef(
dot_node, node->name, input_names, output_names);
dot::NodeWithSocketsRef dot_node_with_sockets_ref = dot::NodeWithSocketsRef(
dot_node, dot_node_with_sockets);
int input_index = 0;
for (const bNodeSocket *socket : node->input_sockets()) {
if (socket->is_available()) {
dot_input_sockets.add_new(DInputSocket{node.context(), socket},
dot_node_with_sockets.input(input_index));
dot_node_with_sockets_ref.input(input_index));
input_index++;
}
}
@ -372,7 +371,7 @@ std::string DerivedNodeTree::to_dot() const
for (const bNodeSocket *socket : node->output_sockets()) {
if (socket->is_available()) {
dot_output_sockets.add_new(DOutputSocket{node.context(), socket},
dot_node_with_sockets.output(output_index));
dot_node_with_sockets_ref.output(output_index));
output_index++;
}
}