Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.Linq;
using Godot;
using GodotExt;
using OpenScadGraphEditor.Library.IO;
using OpenScadGraphEditor.Nodes;
using OpenScadGraphEditor.Nodes.Reroute;
using OpenScadGraphEditor.Utils;
namespace OpenScadGraphEditor.Library
{
/// <summary>
/// A lightweight graph representation, that can simply render to OpenScad but is not user-editable and has
/// no visible UI.
/// </summary>
public class ScadGraph
{
private readonly List<ScadConnection> _connections = new List<ScadConnection>();
private readonly List<ScadNode> _nodes = new List<ScadNode>();
public InvokableDescription Description { get; private set; }
public string Render()
{
// we render all nodes which have either no output at all or a single disconnected flow output
var candidates = GetOutputRoots();
// sort the candidates by their position in the graph
// top to bottom, then left to right; e.g.
// nodes with a lower y value will be rendered first
// nodes with the same y value will be sorted by their x value
var sortedCandidates = candidates
.OrderBy(it => it.Offset.y)
.ThenBy(it => it.Offset.x)
.ToList();
var content = sortedCandidates.Select(it =>
{
var nodeContent = it.Render(this, 0);
var renderModifier = it.BuildRenderModifier();
return !renderModifier.Empty() ? string.Format(renderModifier, nodeContent) : nodeContent;
}).JoinToString("\n");
// now check if the graph has an entrypoint if, so render the entry point with the given content
if (TryGetEntryPoint(out var entryPoint))
{
content = entryPoint.RenderEntryPoint( content);
}
return content;
}
/// <summary>
/// Returns all nodes of the graph which are the starting point of a renderable path. A node is a starting
/// point, when it has no output at all or a single disconnected flow output. Comment and Reroute nodes are
/// ignored.
/// </summary>
/// <returns></returns>
public IEnumerable<ScadNode> GetOutputRoots()
{
return _nodes.Where(it =>
{
// helper nodes will not be used as render target.
if (it is Comment || it is RerouteNode)
{
return false;
}
if (it.OutputPortCount == 0)
{
return true;
}
var hasFlowOutput = false;
for (var i = 0; i < it.OutputPortCount; i++)
{
var portId = PortId.Output(i);
if (it.GetPortType(portId) == PortType.Geometry)
{
hasFlowOutput = true;
if (IsPortConnected(it, portId))
{
return false;
}
}
}
return hasFlowOutput;
});
}
/// <summary>
/// Tries to get the entry point of this graph if it has any.
/// </summary>
public bool TryGetEntryPoint(out EntryPoint entryPoint)
{
entryPoint = _nodes.FirstOrDefault(it => it is EntryPoint) as EntryPoint;
return entryPoint != null;
}
public IEnumerable<ScadNode> GetAllNodes()
{
return _nodes.ToList();
}
public void Main()
{
Clear();
Description = new MainModuleDescription();
}
public ScadNode ById(string id)
{
return _nodes.First(it => it.Id == id);
}
public bool TryById(string id, out ScadNode node)
{
var result = _nodes.FirstOrDefault(it => it.Id == id);
node = result;
return result != null;
}
public void NewFromDescription(InvokableDescription description)
{
Clear();
Description = description;
switch (description)
{
case FunctionDescription functionDescription:
var entryPoint = NodeFactory.Build<FunctionEntryPoint>(functionDescription);
var returnNode = NodeFactory.Build<FunctionReturn>(functionDescription);
// don't let the nodes overlap
returnNode.Offset = entryPoint.Offset + new Vector2(800, 0);
_nodes.Add(entryPoint);
_nodes.Add(returnNode);
break;
case ModuleDescription moduleDescription:
entryPoint = NodeFactory.Build<ModuleEntryPoint>(moduleDescription);
_nodes.Add(entryPoint);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void Clear()
{
_connections.Clear();
_nodes.Clear();
}
private ScadNode Lookup(string id)
{
return _nodes.First(it => it.Id == id);
}
public void LoadFrom(SavedGraph graph, InvokableDescription description, IReferenceResolver resolver)
{
Clear();
Description = description;
foreach (var savedNode in graph.Nodes)
{
var node = NodeFactory.FromSavedNode(savedNode, resolver);
_nodes.Add(node);
}
foreach (var connection in graph.Connections)
{
var target = Lookup(connection.ToId);
_connections.Add(new ScadConnection(this, Lookup(connection.FromId), connection.FromPort, target,
connection.ToPort));
}
}
public void SaveInto(SavedGraph graph)
{
graph.Description = Description.ToSavedState();
foreach (var node in (IEnumerable<ScadNode>) _nodes)
{
graph.Nodes.Add(node.ToSavedState());
}
foreach (var connection in GetAllConnections())
{
var savedConnection = Prefabs.New<SavedConnection>();
savedConnection.FromId = connection.From.Id;
savedConnection.FromPort = connection.FromPort;
savedConnection.ToId = connection.To.Id;
savedConnection.ToPort = connection.ToPort;
graph.Connections.Add(savedConnection);
}
}
public IEnumerable<ScadConnection> GetAllConnections()
{
return _connections;
}
public void RemoveConnection(ScadConnection connection)
{
var removed = _connections.Remove(connection);
GdAssert.That(removed, "Tried to remove non-existing connection.");
}
public void AddConnection(string fromId, int fromPort, string toId, int toPort)
{
_connections.Add(new ScadConnection(this, ById(fromId), fromPort, ById(toId), toPort));
}
public void RemoveNode(ScadNode node)
{
GdAssert.That(_nodes.Contains(node), "Tried to remove non-existing node.");
GdAssert.That(!_connections.Any(it => it.InvolvesNode(node)), "Tried to remove node with connections.");
_nodes.Remove(node);
}
public void AddNode(ScadNode node)
{
GdAssert.That(_nodes.All(it => it.Id != node.Id), "Tried to add node which already exists.");
_nodes.Add(node);
}
public bool IsPortConnected(ScadNode node, PortId port)
{
return GetAllConnections().Any(it => it.InvolvesPort(node, port));
}
}
}7192f5216This file is pinned to the selected release. Download the original to open it in your design tool.