From fc8caa8a293f37ef30aaf538e0f67e129417464a Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 28 Apr 2026 11:30:40 +0200 Subject: [PATCH] Fix adding/removing cluster outputs --- Editor/ClusterEditor.cs | 162 +++++++++++++++----------------- Runtime/Scripts/Core/Cluster.cs | 23 ++++- Runtime/Scripts/Core/Neuron.cs | 4 +- 3 files changed, 100 insertions(+), 89 deletions(-) diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index 96f1b75..b3b3aad 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -70,24 +70,7 @@ namespace NanoBrain { // In a Prefab editor, no instance exists but we need it for the ClusterViewer. // So we create a temporary instance - Cluster cluster = new(prefab); - this.currentCluster = cluster; - - Button addButton = new(() => OnAddClusterOutput()) { - text = "Add" - }; - topMenuContainer?.Add(addButton); - - Add(topMenuContainer); - } - - void OnAddClusterOutput() { - Nucleus newOutput = new Neuron(this.prefab, "New Output"); - this.prefab.RefreshOutputs(); - // outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList(); - // outputsPopup.value = newOutput.name; - - this.currentNucleus = newOutput; + this.currentCluster = new(prefab); } public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) { @@ -151,69 +134,74 @@ namespace NanoBrain { if (serializedObject == null || serializedObject.targetObject == null) return; - if (this.currentNucleus == null) - return; serializedObject.Update(); - GUIStyle headerStyle = new(EditorStyles.boldLabel) { - alignment = TextAnchor.MiddleLeft, - margin = new RectOffset(10, 0, 4, 4) - }; GUIStyle boldTextFieldStyle = new(EditorStyles.textField) { fontStyle = FontStyle.Bold }; - // Nucleus type - string nucleusType = this.currentNucleus.GetType().Name; - GUILayout.Label(nucleusType, headerStyle); - - // Nucleus name - if (this.currentNucleus is Cluster parentCluster) { - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button(this.currentNucleus.parent.name)) - OnClusterClick(parentCluster); - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - EditorGUI.EndDisabledGroup(); - if (GUILayout.Button("Reimport")) - ReimportCluster(parentCluster); - EditorGUILayout.EndHorizontal(); + if (this.currentNucleus == null) { + OutputsInspector(ref anythingChanged); + return; } else { - string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - if (newName != this.currentNucleus.name) { - this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); - // outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList(); - anythingChanged = true; - } - } + GUIStyle headerStyle = new(EditorStyles.boldLabel) { + alignment = TextAnchor.MiddleLeft, + margin = new RectOffset(10, 0, 4, 4) + }; + // Nucleus type + string nucleusType = this.currentNucleus.GetType().Name; + GUILayout.Label(nucleusType, headerStyle); - // Current output value - if (Application.isPlaying) { - if (currentNucleus is Neuron currentNeuron1) { - GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString()); - EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude); + // Nucleus name + Cluster cluster = this.currentNucleus as Cluster; + if (cluster != null) { + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button(this.currentNucleus.parent.name)) + OnClusterClick(cluster); + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); + EditorGUI.EndDisabledGroup(); + if (GUILayout.Button("Reimport")) + ReimportCluster(cluster); + EditorGUILayout.EndHorizontal(); + } + else { + string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); + if (newName != this.currentNucleus.name) { + this.currentNucleus.name = newName; + this.prefab.RefreshOutputs(); + // outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList(); + anythingChanged = true; + } + } + + // Current output value + if (Application.isPlaying) { + if (currentNucleus is Neuron currentNeuron1) { + GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString()); + EditorGUILayout.FloatField(nameLabel, currentNeuron1.outputMagnitude); + } + else + EditorGUILayout.LabelField(" "); } else EditorGUILayout.LabelField(" "); + + // Memory cell + if (this.currentNucleus is MemoryCell memory) + MemoryCellInspector(memory, ref anythingChanged); + // Cluster + else if (cluster != null) //(this.currentNucleus is Cluster cluster) + ClusterInspector(cluster, ref anythingChanged); + // Other + else + NucleusInspector(this.currentNucleus, ref anythingChanged); + + if (GUILayout.Button("Delete")) + DeleteNucleus(this.currentNucleus); } - else - EditorGUILayout.LabelField(" "); - - // Memory cell - if (this.currentNucleus is MemoryCell memory) - MemoryCellInspector(memory, ref anythingChanged); - // Cluster - else if (this.currentNucleus is Cluster cluster) - ClusterInspector(cluster, ref anythingChanged); - // Other - else - NucleusInspector(this.currentNucleus, ref anythingChanged); - - if (GUILayout.Button("Delete")) - DeleteNucleus(this.currentNucleus); serializedObject.ApplyModifiedProperties(); if (anythingChanged) { @@ -222,6 +210,24 @@ namespace NanoBrain { } } + protected void OutputsInspector(ref bool anythingChanged) { + GUIStyle headerStyle = new(EditorStyles.boldLabel) { + alignment = TextAnchor.MiddleLeft, + margin = new RectOffset(10, 0, 4, 4) + }; + GUILayout.Label("Outputs", headerStyle); + + bool connecting = GUILayout.Button("Add Output Neuron"); + if (connecting) { + Nucleus newOutput = new Neuron(this.prefab, "New Output"); + // Regenerate the temporary clsuter instance + // See also the constructor + this.currentCluster = new (this.prefab); + this.currentNucleus = newOutput; + this.selectedOutput = this.currentNucleus; + } + } + protected void MemoryCellInspector(MemoryCell memoryCell, ref bool anythingChanged) { memoryCell.staticMemory = EditorGUILayout.Toggle("Static Memory", memoryCell.staticMemory); NucleusInspector(memoryCell, ref anythingChanged); @@ -430,15 +436,6 @@ namespace NanoBrain { case Nucleus.Type.Cluster: AddClusterInput(nucleus); break; - // case Nucleus.Type.Receptor: - // AddReceptorInput(nucleus); - // break; - // case Nucleus.Type.ClusterReceptor: - // AddClusterReceptorInput(nucleus); - // break; - // case Nucleus.Type.ClusterArray: - // AddClusterArrayInput(nucleus); - // break; default: break; } @@ -535,15 +532,12 @@ namespace NanoBrain { } } } - this.prefab.nuclei.Remove(nucleus); + this.currentCluster.DeleteNucleus(nucleus);//clusterNuclei.Remove(nucleus); - // if (outputsPopup.value == nucleus.name) { - // this.prefab.RefreshOutputs(); - // // outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList(); - // // outputsPopup.index = 0; - // } - - Neuron.Delete(nucleus); + // this.prefab.nuclei.Remove(nucleus); + // Neuron.Delete(nucleus); + this.prefab.RefreshOutputs(); + this.currentNucleus = this.prefab.output; this.selectedOutput = this.currentNucleus; diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index 0b89721..9e905f9 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -528,10 +528,6 @@ namespace NanoBrain { #endregion ClusterArray - - - //public Dictionary nucleiDict = new(); - public List _inputs = null; public virtual List inputs { get { @@ -643,6 +639,9 @@ namespace NanoBrain { return this._outputs; } } + public void RefreshOutputs() { + this._outputs = null; + } public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) { foreach (Nucleus receptor in this.clusterNuclei) { @@ -685,6 +684,22 @@ namespace NanoBrain { } } + public bool DeleteNucleus(Nucleus nucleus) { + if (this.clusterNuclei.Contains(nucleus) == false) { + // Try to find the nucleus by name + if (TryGetNucleus(nucleus.name, out nucleus) == false) + return false; + } + + Neuron.Delete(nucleus); + int nucleusIx = this.clusterNuclei.IndexOf(nucleus); + this.clusterNuclei.Remove(nucleus); + this.prefab.nuclei.RemoveAt(nucleusIx); + RefreshOutputs(); + + return true; + } + #region Receivers public virtual List CollectReceivers() { diff --git a/Runtime/Scripts/Core/Neuron.cs b/Runtime/Scripts/Core/Neuron.cs index d0f9473..934fb63 100644 --- a/Runtime/Scripts/Core/Neuron.cs +++ b/Runtime/Scripts/Core/Neuron.cs @@ -33,8 +33,10 @@ namespace NanoBrain { public Neuron(ClusterPrefab prefab, string name) { this.clusterPrefab = prefab; this.name = name; - if (this.clusterPrefab != null) + if (this.clusterPrefab != null) { this.clusterPrefab.nuclei.Add(this); + this.clusterPrefab.RefreshOutputs(); + } else Debug.LogError("No prefab when adding neuron to prefab"); }