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:
parent
cc48610d2c
commit
7e4f988072
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue