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 OpenScadGraphEditor.Library;
using OpenScadGraphEditor.Library.IO;
using OpenScadGraphEditor.Utils;
namespace OpenScadGraphEditor.Nodes
{
public abstract class ScadNode
{
public abstract string NodeTitle { get; }
public abstract string NodeDescription { get; }
/// <summary>
/// The quick lookup code for the node.
/// </summary>
public abstract string NodeQuickLookup { get; }
public string Id { get; private set; } = Guid.NewGuid().ToString();
/// <summary>
/// The documentation of the port with the given Id.
/// </summary>
/// <param name="portId"></param>
/// <returns></returns>
public abstract string GetPortDocumentation(PortId portId);
public Vector2 Offset { get; set; }
public int InputPortCount => InputPorts.Count;
public int OutputPortCount => OutputPorts.Count;
protected readonly List<PortDefinition> InputPorts = new List<PortDefinition>();
protected readonly List<PortDefinition> OutputPorts = new List<PortDefinition>();
private readonly Dictionary<int, IScadLiteral> _inputLiterals =
new Dictionary<int, IScadLiteral>();
private readonly Dictionary<int, IScadLiteral> _outputLiterals =
new Dictionary<int, IScadLiteral>();
/// <summary>
/// Custom node attributes.
/// </summary>
private readonly Dictionary<string, string> _customAttributes
= new Dictionary<string, string>();
public IEnumerable<PortId> InputPortIds => InputPortCount.Range().Select(PortId.Input);
public IEnumerable<PortId> OutputPortIds => OutputPortCount.Range().Select(PortId.Output);
public bool TryGetLiteral(PortId port, out IScadLiteral result)
{
if (port.IsInput)
{
return _inputLiterals.TryGetValue(port.Port, out result);
}
else
{
return _outputLiterals.TryGetValue(port.Port, out result);
}
}
public PortDefinition GetPortDefinition(PortId port)
{
return port.IsInput ? InputPorts[port.Port] : OutputPorts[port.Port];
}
protected void SetPortDefinition(PortId port, PortDefinition portDefinition)
{
if (port.IsInput)
{
InputPorts[port.Port] = portDefinition;
}
else
{
OutputPorts[port.Port] = portDefinition;
}
}
public void DropPortLiteral(PortId port)
{
if (port.IsInput)
{
_inputLiterals.Remove(port.Port);
}
else
{
_outputLiterals.Remove(port.Port);
}
}
public void SwapInputLiterals(int index0, int index1)
{
var hasFirst = _inputLiterals.TryGetValue(index0, out var literal0);
var hasSecond = _inputLiterals.TryGetValue(index1, out var literal1);
if (hasFirst)
{
_inputLiterals[index1] = literal0;
}
else
{
_inputLiterals.Remove(index1);
}
if (hasSecond)
{
_inputLiterals[index0] = literal1;
}
else
{
_inputLiterals.Remove(index0);
}
}
public void SwapOutputLiterals(int index0, int index1)
{
var hasFirst = _outputLiterals.TryGetValue(index0, out var literal0);
var hasSecond = _outputLiterals.TryGetValue(index1, out var literal1);
if (hasFirst)
{
_outputLiterals[index1] = literal0;
}
else
{
_outputLiterals.Remove(index1);
}
if (hasSecond)
{
_outputLiterals[index0] = literal1;
}
else
{
_outputLiterals.Remove(index0);
}
}
public virtual void SaveInto(SavedNode node)
{
node.Id = Id;
node.Type = GetType().FullName;
node.Position = Offset;
InputPorts
.Indices()
.Where(i => _inputLiterals.ContainsKey(i))
.ForAll(i =>
{
node.SetData($"input_literal_value.{i}", _inputLiterals[i].SerializedValue);
node.SetData($"input_literal_set.{i}", _inputLiterals[i].IsSet);
});
OutputPorts
.Indices()
.Where(i => _outputLiterals.ContainsKey(i))
.ForAll(i =>
{
node.SetData($"output_literal_value.{i}", _outputLiterals[i].SerializedValue);
node.SetData($"output_literal_set.{i}", _outputLiterals[i].IsSet);
});
_customAttributes.ForAll(it => { node.SetData($"custom_attribute.{it.Key}", it.Value); });
}
public void PrepareLiteralsFromPortDefinitions()
{
var maxPorts = Mathf.Max(InputPorts.Count, OutputPorts.Count);
var idx = 0;
while (idx < maxPorts)
{
if (InputPorts.Count > idx)
{
BuildPortLiteral(PortId.Input(idx));
}
if (OutputPorts.Count > idx)
{
BuildPortLiteral(PortId.Output(idx));
}
idx++;
}
}
/// <summary>
/// Loading Phase 1: Called to restore the port definitions from the saved node.
/// </summary>
public virtual void RestorePortDefinitions(SavedNode node, IReferenceResolver resolver)
{
_customAttributes.Clear();
// restore custom attributes
node.StoredData
.Where(it => it.Key.StartsWith("custom_attribute."))
.ForAll(it => _customAttributes[it.Key.Substring("custom_attribute.".Length)] = it.Value);
}
/// <summary>
/// Loading Phase 2: Called to restore the literal structure from the saved node.
/// </summary>
/// <param name="node"></param>
/// <param name="resolver"></param>
public virtual void RestoreLiteralStructures(SavedNode node, IReferenceResolver resolver)
{
PrepareLiteralsFromPortDefinitions();
}
/// <summary>
/// Loading Phase 3: Called to restore the literal values from the saved node.
/// </summary>
public virtual void RestoreLiteralValues(SavedNode node, IReferenceResolver referenceResolver)
{
Id = node.Id;
Offset = node.Position;
InputPorts
.Indices()
.Where(i => _inputLiterals.ContainsKey(i))
.ForAll(i =>
{
if (!node.HasKey($"input_literal_value.{i}"))
{
// not saved, so keep as is.
return;
}
var serializedValue = node.GetDataString($"input_literal_value.{i}");
// broken literals still seem to happen quite often so we want additional debug output here.
try
{
_inputLiterals[i].SerializedValue = serializedValue;
}
catch (Exception e)
{
throw new BrokenFileException(
$"Broken literal value for INPUT port {i} in node {Id} ({serializedValue})", e);
}
_inputLiterals[i].IsSet = node.GetDataBool($"input_literal_set.{i}", true);
});
OutputPorts
.Indices()
.Where(i => _outputLiterals.ContainsKey(i))
.ForAll(i =>
{
var serializedValue = node.GetDataString($"output_literal_value.{i}");
// broken literals still seem to happen quite often so we want additional debug output here.
try
{
_outputLiterals[i].SerializedValue = serializedValue;
}
catch (Exception e)
{
throw new BrokenFileException(
$"Broken literal value for OUTPUT port {i} in node {Id}({serializedValue})", e);
}
_outputLiterals[i].IsSet = node.GetDataBool($"output_literal_set.{i}", true);
});
}
public PortType GetPortType(PortId port)
{
return port.IsInput ? InputPorts[port.Port].PortType : OutputPorts[port.Port].PortType;
}
public abstract string Render(ScadGraph context, int portIndex);
private string RenderPort(ScadGraph context, PortId port)
{
if (port.IsInput)
{
// render all output ports of all connected nodes and join them to a single string.
var connectedNodes = context.GetAllConnections()
.Where(it => it.IsTo(this, port.Port))
.ToList();
if (connectedNodes.Count > 0)
{
return connectedNodes
// 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
.OrderBy(it => it.From.Offset.y)
.ThenBy(it => it.From.Offset.x)
.Select(it =>
{
var rendered = it.From.Render(context, it.FromPort);
if (rendered.Empty())
{
return rendered; // if the node is empty, no point adding any modifiers to it.
}
var renderModifier = it.From.BuildRenderModifier();
if (!renderModifier.Empty())
{
return string.Format(renderModifier, rendered);
}
return rendered;
})
.JoinToString("\n");
}
}
// otherwise try rendering the literal
return RenderLiteral(port);
}
protected string RenderLiteral(PortId port)
{
if (TryGetLiteral(port, out var literal) && (literal.IsSet || GetPortDefinition(port).LiteralIsAutoSet))
{
return literal.RenderedValue;
}
return "";
}
internal string BuildRenderModifier()
{
var effectiveModifiers = this.GetModifiers();
this.TryGetColorModifier(out var effectiveColor);
// we also need to set a render modifier so the modifiers are rendered
var renderModifier = "";
if (effectiveModifiers.HasFlag(ScadNodeModifier.Debug))
{
renderModifier = "# {0}";
}
if (effectiveModifiers.HasFlag(ScadNodeModifier.Background))
{
renderModifier = "% {0}";
}
if (effectiveModifiers.HasFlag(ScadNodeModifier.Root))
{
renderModifier = "! {0}";
}
if (effectiveModifiers.HasFlag(ScadNodeModifier.Disable))
{
renderModifier = "* {0}";
}
if (effectiveModifiers.HasFlag(ScadNodeModifier.Color))
{
var colorModifier =
$"color([{effectiveColor.r.SafeToString()}, {effectiveColor.g.SafeToString()}, {effectiveColor.b.SafeToString()}, {effectiveColor.a.SafeToString()}]) {{0}}";
if (renderModifier.Empty())
{
renderModifier = colorModifier;
}
else
{
renderModifier = string.Format(renderModifier, colorModifier);
}
}
return renderModifier;
}
protected internal string RenderInput(ScadGraph context, int index)
{
return RenderPort(context, PortId.Input(index));
}
protected string RenderOutput(ScadGraph context, int index)
{
return RenderPort(context, PortId.Output(index));
}
public void BuildPortLiteral(PortId port)
{
var portDefinition = GetPortDefinition(port);
IScadLiteral literal;
switch (portDefinition.LiteralType)
{
case LiteralType.Boolean:
literal = new BooleanLiteral(portDefinition.DefaultValueAsBoolean);
break;
case LiteralType.Number:
literal = new NumberLiteral(portDefinition.DefaultValueAsDouble);
break;
case LiteralType.String:
literal = new StringLiteral(portDefinition.DefaultValueAsString);
break;
case LiteralType.Vector3:
literal = new Vector3Literal(0, 0, 0);
break;
case LiteralType.Vector2:
literal = new Vector2Literal(0, 0);
break;
case LiteralType.Name:
literal = new NameLiteral(portDefinition.DefaultValueAsString);
break;
case LiteralType.None:
// nothing to do
return;
default:
throw new ArgumentOutOfRangeException();
}
if (port.IsInput)
{
_inputLiterals[port.Port] = literal;
}
else
{
_outputLiterals[port.Port] = literal;
}
}
public void SetCustomAttribute(string key, string value)
{
_customAttributes[key] = value;
}
public bool TryGetCustomAttribute(string key, out string value)
{
return _customAttributes.TryGetValue(key, out value);
}
public void UnsetCustomAttribute(string key)
{
_customAttributes.Remove(key);
}
}
}7192f5216This file is pinned to the selected release. Download the original to open it in your design tool.