From 2f52d06c01682fe421af9ffd227aad0080c1f138 Mon Sep 17 00:00:00 2001 From: yum Date: Thu, 8 Jan 2026 18:39:56 -0800 Subject: Fold: fuck --- Scripts/Fold/Editor/FoldGraph.cs | 107 ++++++++++++++-- Scripts/Fold/Editor/FoldGraphView.cs | 239 ++++++++++++++++++++++++++++++----- 2 files changed, 297 insertions(+), 49 deletions(-) (limited to 'Scripts/Fold') diff --git a/Scripts/Fold/Editor/FoldGraph.cs b/Scripts/Fold/Editor/FoldGraph.cs index 5782dc7..c0719d9 100644 --- a/Scripts/Fold/Editor/FoldGraph.cs +++ b/Scripts/Fold/Editor/FoldGraph.cs @@ -8,6 +8,59 @@ public class FoldEdge public string outputNodeGuid, outputPortName, inputNodeGuid, inputPortName; } +[Serializable] +public readonly struct FoldOperationDefinition +{ + public readonly string title; + public readonly int opcode; + public readonly (string name, Type type)[] inputs; + + public FoldOperationDefinition(string title, int opcode, params (string name, Type type)[] inputs) + { + this.title = title; + this.opcode = opcode; + this.inputs = inputs; + } + + public FoldNodeSerialized Serialize(FoldGraphView graphView, string guid) + { + var serialized = new FoldNodeSerialized { opcode = opcode }; + int floatIndex = 0, vectorIndex = 0; + + foreach (var (name, type) in inputs) + { + if (type == typeof(float)) + SetFloat(ref serialized, floatIndex++, graphView.GetInputValue(guid, name, 0f)); + else if (type == typeof(Vector4)) + SetVector(ref serialized, vectorIndex++, graphView.GetInputValue(guid, name, Vector4.zero)); + } + + return serialized; + } + + static void SetFloat(ref FoldNodeSerialized node, int index, float value) + { + switch (index) + { + case 0: node.float0 = value; break; + case 1: node.float1 = value; break; + case 2: node.float2 = value; break; + case 3: node.float3 = value; break; + } + } + + static void SetVector(ref FoldNodeSerialized node, int index, Vector4 value) + { + switch (index) + { + case 0: node.vec0 = value; break; + case 1: node.vec1 = value; break; + case 2: node.vec2 = value; break; + case 3: node.vec3 = value; break; + } + } +} + [Serializable] public abstract class FoldNodeData { @@ -19,8 +72,10 @@ public abstract class FoldNodeData [Serializable] public abstract class FoldOperationData : FoldNodeData { - public abstract IEnumerable<(string name, Type type)> GetInputPorts(); - public abstract FoldNodeSerialized Serialize(FoldGraphView graphView); + protected abstract FoldOperationDefinition Definition { get; } + public override string Title => Definition.title; + public virtual IEnumerable<(string name, Type type)> GetInputPorts() => Definition.inputs; + public virtual FoldNodeSerialized Serialize(FoldGraphView graphView) => Definition.Serialize(graphView, guid); } [Serializable] public class FloatValueNodeData : FoldNodeData { public float value; public override string Title => "Float"; } @@ -31,33 +86,57 @@ public abstract class FoldOperationData : FoldNodeData [Serializable] public class AxisAlignNodeData : FoldOperationData { - public override string Title => "Axis Align"; - public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("po", typeof(Vector4)); yield return ("pp", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("t", typeof(float)); } - public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 4, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "po", Vector4.zero), vec1 = gv.GetInputValue(guid, "pp", Vector4.zero), vec2 = gv.GetInputValue(guid, "r", Vector4.zero) }; + static readonly FoldOperationDefinition DefinitionInfo = new( + "Axis Align", + 4, + ("po", typeof(Vector4)), + ("pp", typeof(Vector4)), + ("r", typeof(Vector4)), + ("t", typeof(float))); + + protected override FoldOperationDefinition Definition => DefinitionInfo; } [Serializable] public class PlaneToTubeNodeData : FoldOperationData { - public override string Title => "Plane to Tube"; - public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("p", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("s", typeof(Vector4)); yield return ("t", typeof(float)); } - public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 2, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "p", Vector4.zero), vec1 = gv.GetInputValue(guid, "r", Vector4.zero), vec2 = gv.GetInputValue(guid, "s", Vector4.zero) }; + static readonly FoldOperationDefinition DefinitionInfo = new( + "Plane to Tube", + 2, + ("p", typeof(Vector4)), + ("r", typeof(Vector4)), + ("s", typeof(Vector4)), + ("t", typeof(float))); + + protected override FoldOperationDefinition Definition => DefinitionInfo; } [Serializable] public class PointAlignNodeData : FoldOperationData { - public override string Title => "Point Align"; - public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("po", typeof(Vector4)); yield return ("pp", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("t", typeof(float)); } - public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 3, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "po", Vector4.zero), vec1 = gv.GetInputValue(guid, "pp", Vector4.zero), vec2 = gv.GetInputValue(guid, "r", Vector4.zero) }; + static readonly FoldOperationDefinition DefinitionInfo = new( + "Point Align", + 3, + ("po", typeof(Vector4)), + ("pp", typeof(Vector4)), + ("r", typeof(Vector4)), + ("t", typeof(float))); + + protected override FoldOperationDefinition Definition => DefinitionInfo; } [Serializable] public class TubeToPlaneNodeData : FoldOperationData { - public override string Title => "Tube to Plane"; - public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("p", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("s", typeof(Vector4)); yield return ("t", typeof(float)); } - public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 1, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "p", Vector4.zero), vec1 = gv.GetInputValue(guid, "r", Vector4.zero), vec2 = gv.GetInputValue(guid, "s", Vector4.zero) }; + static readonly FoldOperationDefinition DefinitionInfo = new( + "Tube to Plane", + 1, + ("p", typeof(Vector4)), + ("r", typeof(Vector4)), + ("s", typeof(Vector4)), + ("t", typeof(float))); + + protected override FoldOperationDefinition Definition => DefinitionInfo; } public class FoldGraph : ScriptableObject diff --git a/Scripts/Fold/Editor/FoldGraphView.cs b/Scripts/Fold/Editor/FoldGraphView.cs index 2c9e933..e01bf4f 100644 --- a/Scripts/Fold/Editor/FoldGraphView.cs +++ b/Scripts/Fold/Editor/FoldGraphView.cs @@ -210,6 +210,15 @@ public class FoldGraphView : GraphView readonly Dictionary nodeLookup = new(); readonly FoldEdgeConnectorListener edgeConnectorListener; bool suppressGraphChanges; + readonly HashSet pendingAutoComplete = new(); + + void WithGraphChangesSuppressed(Action action) + { + var previous = suppressGraphChanges; + suppressGraphChanges = true; + try { action(); } + finally { suppressGraphChanges = previous; } + } public FoldGraphView(FoldWindow owner, FoldGraph graph) { @@ -275,9 +284,8 @@ public class FoldGraphView : GraphView MarkDirty(); } - public void Reload() + public void Reload() => WithGraphChangesSuppressed(() => { - suppressGraphChanges = true; nodeLookup.Clear(); DeleteElements(graphElements.Where(e => e is Node or Edge).ToList()); @@ -288,8 +296,7 @@ public class FoldGraphView : GraphView if (o.GetPort(e.outputPortName) is { } op && i.GetPort(e.inputPortName) is { } ip) AddElement(op.ConnectTo(ip)); } - suppressGraphChanges = false; - } + }); void OnKeyDown(KeyDownEvent evt) { @@ -301,29 +308,29 @@ public class FoldGraphView : GraphView var sel = selection.OfType().ToList(); if (sel.Count == 0) return; - suppressGraphChanges = true; RecordUndo("Duplicate Nodes"); var map = new Dictionary(); - foreach (var orig in sel) + WithGraphChangesSuppressed(() => { - var clone = JsonUtility.FromJson(JsonUtility.ToJson(orig.Data)); - clone = (FoldNodeData)Activator.CreateInstance(orig.Data.GetType()); - JsonUtility.FromJsonOverwrite(JsonUtility.ToJson(orig.Data), clone); - clone.guid = Guid.NewGuid().ToString(); - clone.position += new Vector2(30f, 30f); - graphAsset.nodes.Add(clone); - var v = CreateNode(clone); - map[orig.Data.guid] = v; - nodeLookup[clone.guid] = v; - AddElement(v); - } - - foreach (var e in graphAsset.edges.Where(e => map.ContainsKey(e.outputNodeGuid) && map.ContainsKey(e.inputNodeGuid))) - if (map[e.outputNodeGuid].GetPort(e.outputPortName) is { } op && map[e.inputNodeGuid].GetPort(e.inputPortName) is { } ip) - { var edge = op.ConnectTo(ip); AddElement(edge); AddEdgeData(edge); } + foreach (var orig in sel) + { + var clone = (FoldNodeData)Activator.CreateInstance(orig.Data.GetType()); + JsonUtility.FromJsonOverwrite(JsonUtility.ToJson(orig.Data), clone); + clone.guid = Guid.NewGuid().ToString(); + clone.position += new Vector2(30f, 30f); + graphAsset.nodes.Add(clone); + var v = CreateNode(clone); + map[orig.Data.guid] = v; + nodeLookup[clone.guid] = v; + AddElement(v); + } + + foreach (var e in graphAsset.edges.Where(e => map.ContainsKey(e.outputNodeGuid) && map.ContainsKey(e.inputNodeGuid))) + if (map[e.outputNodeGuid].GetPort(e.outputPortName) is { } op && map[e.inputNodeGuid].GetPort(e.inputPortName) is { } ip) + { var edge = op.ConnectTo(ip); AddElement(edge); AddEdgeData(edge); } + }); - suppressGraphChanges = false; ClearSelection(); foreach (var n in map.Values) AddToSelection(n); MarkDirty(); @@ -342,7 +349,7 @@ public class FoldGraphView : GraphView return view; } - internal FoldNodeView CreateNode(Type dataType, Vector2 position) + internal FoldNodeView CreateNode(Type dataType, Vector2 position, bool autoComplete = false) { if (!suppressGraphChanges) RecordUndo("Create Node"); var data = (FoldNodeData)Activator.CreateInstance(dataType); @@ -353,6 +360,7 @@ public class FoldGraphView : GraphView AddElement(view); view.RefreshExpandedState(); view.RefreshPorts(); + if (autoComplete) RegisterGeometryOnce(view, () => AutoCreateSingleOptionNodes(view)); MarkDirty(); return view; } @@ -366,17 +374,30 @@ public class FoldGraphView : GraphView public FoldNodeView CreateNodeAndConnect(Port startPort, Type dataType, Vector2 pos) { - suppressGraphChanges = true; - var view = CreateNode(dataType, pos); - var compat = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); - if (compat != null) + RecordUndo("Create Node"); + FoldNodeView view = null; + WithGraphChangesSuppressed(() => { - var (o, i) = startPort.direction == Direction.Output ? (startPort, compat) : (compat, startPort); - var edge = o.ConnectTo(i); - AddElement(edge); - AddEdgeData(edge); - } - suppressGraphChanges = false; + view = CreateNode(dataType, pos); + + // If dragging from input port, offset so the new node's right-center is at cursor once layout is computed + if (startPort.direction == Direction.Input) + RegisterGeometryOnce(view, () => + { + var size = view.layout.size; + view.SetPosition(new Rect(pos - new Vector2(size.x, size.y / 2), size)); + }); + + var compat = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); + if (compat != null) + { + var (o, i) = startPort.direction == Direction.Output ? (startPort, compat) : (compat, startPort); + var edge = o.ConnectTo(i); + AddElement(edge); + AddEdgeData(edge); + } + }); + RegisterGeometryOnce(view, () => AutoCreateSingleOptionNodes(view)); MarkDirty(); return view; } @@ -401,6 +422,155 @@ public class FoldGraphView : GraphView p.Initialize(this, port, pos); if (p.HasEntries()) SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(pos)), p); } + + void AutoCreateSingleOptionNodes(FoldNodeView node) + { + if (node == null) return; + pendingAutoComplete.Remove(node.Data.guid); + var debug = new List(); + var targetRect = EstimateNodeRect(node); + if (targetRect.size == Vector2.zero) + { + if (pendingAutoComplete.Add(node.Data.guid)) + RegisterGeometryOnce(node, () => AutoCreateSingleOptionNodes(node)); + Debug.Log("Fold AutoComplete: defer (no size); layout=" + node.layout.size + " resolved=(" + node.resolvedStyle.width + "," + node.resolvedStyle.height + ")"); + return; + } + + var ports = node.inputContainer.Children().OfType() + .Where(p => !p.connections.Any()) + .Select((p, i) => (port: p, index: i)) + .ToList(); + + var candidates = ports + .Select(p => (p.port, p.index, options: GetNodeOptions(p.port).ToList())) + .Where(p => p.options.Count == 1) + .ToList(); + + if (candidates.Count == 0) return; + + var created = new List(); + var horizontalSpacing = targetRect.width + GetHorizontalMargins(node); + var verticalSpacing = targetRect.height; + var occupied = new List { targetRect }; + + RecordUndo("Auto-complete Ports"); + WithGraphChangesSuppressed(() => + { + foreach (var (port, index, options) in candidates) + { + var dataType = options[0].dataType; + var targetPos = targetRect.position; + var desired = new Vector2( + targetPos.x - horizontalSpacing, + targetPos.y + (index - (candidates.Count - 1) * 0.5f) * verticalSpacing); + + var rect = FindFreePosition(new Rect(desired, targetRect.size), occupied, debug); + if (rect == Rect.zero) + { + debug.Add($"fail place idx={index} type={dataType.Name} desired={desired}"); + continue; + } + + var newNode = CreateNode(dataType, rect.position); + created.Add(newNode); + + var compat = GetCompatiblePorts(port, null).FirstOrDefault(p => p.node == newNode); + if (compat != null) + { + var (o, i) = port.direction == Direction.Output ? (port, compat) : (compat, port); + var edge = o.ConnectTo(i); + AddElement(edge); + AddEdgeData(edge); + } + } + }); + + foreach (var child in created) + AutoCreateSingleOptionNodes(child); + + Debug.Log($"Fold AutoComplete: node='{node.title}' size={targetRect.size} spacing=({horizontalSpacing},{verticalSpacing}) candidates={candidates.Count} created={created.Count} notes=[{string.Join("; ", debug)}]"); + MarkDirty(); + } + + Rect FindFreePosition(Rect desired, List occupied, List debug) + { + if (desired.size == Vector2.zero) return Rect.zero; + + Rect rect = desired; + int safety = 0; + while ((occupied?.Any(o => RectsOverlap(o, rect)) == true || OverlapsExisting(rect)) && safety++ < 50) + rect.position += new Vector2(0f, rect.height); + if (safety >= 50) + { + debug?.Add($"exceeded iterations({safety}) starting={desired.position}"); + return Rect.zero; + } + occupied?.Add(rect); + return rect; + } + + bool OverlapsExisting(Rect rect) + { + return nodeLookup.Values.Any(n => + { + var existing = EstimateNodeRect(n); + return existing.size != Vector2.zero && RectsOverlap(existing, rect); + }); + } + + static Rect EstimateNodeRect(FoldNodeView node) + { + var size = node.layout.size; + if (float.IsNaN(size.x) || float.IsNaN(size.y)) + size = Vector2.zero; + if (size == Vector2.zero) + { + var rs = node.resolvedStyle; + size = new Vector2(float.IsNaN(rs.width) ? 0f : rs.width, float.IsNaN(rs.height) ? 0f : rs.height); + } + if (float.IsNaN(size.x) || float.IsNaN(size.y)) + size = Vector2.zero; + return size == Vector2.zero ? Rect.zero : new Rect(node.GetPosition().position, size); + } + + static float GetHorizontalMargins(FoldNodeView node) + { + var rs = node.resolvedStyle; + float left = float.IsNaN(rs.marginLeft) ? 0f : rs.marginLeft; + float right = float.IsNaN(rs.marginRight) ? 0f : rs.marginRight; + return left + right; + } + + static bool RectsOverlap(Rect a, Rect b) => + a.xMin < b.xMax && a.xMax > b.xMin && a.yMin < b.yMax && a.yMax > b.yMin; + + static void RegisterGeometryOnce(VisualElement element, Action action) + { + bool TryInvoke() + { + var size = element.layout.size; + if (float.IsNaN(size.x) || float.IsNaN(size.y) || size == Vector2.zero) + { + var rs = element.resolvedStyle; + size = new Vector2(float.IsNaN(rs.width) ? 0f : rs.width, float.IsNaN(rs.height) ? 0f : rs.height); + } + + if (size == Vector2.zero) return false; + action?.Invoke(); + return true; + }; + + if (TryInvoke()) return; + + EventCallback handler = null; + handler = evt => + { + if (TryInvoke()) + element.UnregisterCallback(handler); + }; + element.RegisterCallback(handler); + } } class FoldEdgeConnectorListener : IEdgeConnectorListener @@ -447,8 +617,7 @@ class FoldSearchProvider : ScriptableObject, ISearchWindowProvider if (entry.userData is not Type dataType) return false; var pos = graphView.contentViewContainer.WorldToLocal(worldPosition); if (startPort != null) graphView.CreateNodeAndConnect(startPort, dataType, pos); - else graphView.CreateNode(dataType, pos); + else graphView.CreateNode(dataType, pos, true); return true; } } - -- cgit v1.2.3