From 27f423789006a2317674c27fa155c4d7a3826b15 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Fri, 10 Jul 2026 10:07:22 +0200 Subject: [PATCH] Adding JSON serialization UI --- Editor/ClusterJson_Drawer.cs | 72 +++++++++++++++++++++++++++++ Editor/ClusterJson_Drawer.cs.meta | 2 + Editor/Cluster_Drawer.cs | 28 ++++++----- Runtime/Scripts/ClusterJson.cs | 10 ++++ Runtime/Scripts/ClusterJson.cs.meta | 2 + Runtime/Scripts/Core/Cluster.cs | 29 +++++++++++- Runtime/Scripts/Core/Nucleus.cs | 3 -- 7 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 Editor/ClusterJson_Drawer.cs create mode 100644 Editor/ClusterJson_Drawer.cs.meta create mode 100644 Runtime/Scripts/ClusterJson.cs create mode 100644 Runtime/Scripts/ClusterJson.cs.meta diff --git a/Editor/ClusterJson_Drawer.cs b/Editor/ClusterJson_Drawer.cs new file mode 100644 index 0000000..25acfe8 --- /dev/null +++ b/Editor/ClusterJson_Drawer.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace NanoBrain.Unity { + + [CustomPropertyDrawer(typeof(ClusterJson))] + class ClusterJson_Drawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + EditorGUI.BeginProperty(position, label, property); + + ClusterJson clusterJson = property.objectReferenceValue as ClusterJson; + if (clusterJson == null) { + clusterJson = ScriptableObject.CreateInstance(); + + property.objectReferenceValue = clusterJson; + property.serializedObject.ApplyModifiedProperties(); + + EditorUtility.SetDirty(property.serializedObject.targetObject); + } + SerializedObject so = new(clusterJson); + SerializedProperty jsonFileProp = so.FindProperty(nameof(ClusterJson.jsonFile)); + + EditorGUI.BeginChangeCheck(); + + TextAsset current = jsonFileProp.objectReferenceValue as TextAsset; + Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + TextAsset newAsset = (TextAsset)EditorGUI.ObjectField(fieldRect, label, current, typeof(TextAsset), false); + + if (EditorGUI.EndChangeCheck()) { + jsonFileProp.objectReferenceValue = newAsset; + so.ApplyModifiedProperties(); + } + bool showWarning = IsInvalidJson(jsonFileProp); + if (showWarning) { + Rect helpRect = new( + position.x, + position.y + EditorGUIUtility.singleLineHeight + 2f, + position.width, + EditorGUIUtility.singleLineHeight * 2f + ); + propertyExtraHeight = 2 + 2 * EditorGUIUtility.singleLineHeight; + EditorGUI.HelpBox(helpRect, "Selected asset is not a .json file.", MessageType.Error); + } else + propertyExtraHeight = 0; + + EditorGUI.EndProperty(); + } + + private float propertyExtraHeight = 0; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return EditorGUIUtility.singleLineHeight + propertyExtraHeight; + } + + private readonly Dictionary _invalidCache = new(); + + private bool IsInvalidJson(SerializedProperty jsonFileProp) { + TextAsset current = jsonFileProp.objectReferenceValue as TextAsset; + if (current == null) return false; + + EntityId id = current.GetEntityId(); + if (_invalidCache.TryGetValue(id, out bool invalid)) + return invalid; + + string path = AssetDatabase.GetAssetPath(current); + invalid = !path.EndsWith(".json", System.StringComparison.OrdinalIgnoreCase); + _invalidCache[id] = invalid; + return invalid; + } + } +} \ No newline at end of file diff --git a/Editor/ClusterJson_Drawer.cs.meta b/Editor/ClusterJson_Drawer.cs.meta new file mode 100644 index 0000000..403c980 --- /dev/null +++ b/Editor/ClusterJson_Drawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 879b585246de16598959c21b724cf105 \ No newline at end of file diff --git a/Editor/Cluster_Drawer.cs b/Editor/Cluster_Drawer.cs index acc8eb3..abc7308 100644 --- a/Editor/Cluster_Drawer.cs +++ b/Editor/Cluster_Drawer.cs @@ -25,7 +25,8 @@ namespace NanoBrain.Unity { return height; SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab)); - if (prefabProp.objectReferenceValue != null && Cluster_Drawer.currentClusterView.isOpen) { + if (//prefabProp.objectReferenceValue != null && + Cluster_Drawer.currentClusterView.isOpen) { height = graphHeight; } else { @@ -92,18 +93,19 @@ namespace NanoBrain.Unity { } // If a brain has been selected - if (prefabProp.objectReferenceValue != null) { - // Graph is not shown when multi-editing - if (property.serializedObject.targetObjects.Length == 1) { - UnityEngine.Object targetObject = property.serializedObject.targetObject; - Cluster_Drawer.selectedTarget = targetObject; + //if (prefabProp.objectReferenceValue != null) { + // Graph is not shown when multi-editing + if (property.serializedObject.targetObjects.Length == 1) { + UnityEngine.Object targetObject = property.serializedObject.targetObject; + Cluster_Drawer.selectedTarget = targetObject; - Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster; - if (cluster.version != cluster.prefab.version) { - // Debug.Log($"prefab version: {cluster.prefab.version} cluster version: {cluster.version}"); - clusterView.initialized = false; - EditorApplication.delayCall += () => InstantiateCluster(property, clusterView); - } + Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster; + if (cluster != null) { + // if (cluster.version != cluster.prefab.version) { + // // Debug.Log($"prefab version: {cluster.prefab.version} cluster version: {cluster.version}"); + // clusterView.initialized = false; + // EditorApplication.delayCall += () => InstantiateCluster(property, clusterView); + // } // foldout header rect Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight); @@ -122,8 +124,10 @@ namespace NanoBrain.Unity { clusterView.Render(drawRect); //Debug.Log(prefab.cluster.defaultOutput.outputMagnitude); } + } } + //} EditorGUI.indentLevel = indent; EditorGUI.EndProperty(); diff --git a/Runtime/Scripts/ClusterJson.cs b/Runtime/Scripts/ClusterJson.cs new file mode 100644 index 0000000..2fa61b8 --- /dev/null +++ b/Runtime/Scripts/ClusterJson.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace NanoBrain.Unity { + + [System.Serializable] + public class ClusterJson : ScriptableObject { + public TextAsset jsonFile; + } + +} \ No newline at end of file diff --git a/Runtime/Scripts/ClusterJson.cs.meta b/Runtime/Scripts/ClusterJson.cs.meta new file mode 100644 index 0000000..5dd0437 --- /dev/null +++ b/Runtime/Scripts/ClusterJson.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8ea9c456ab9da37daaa610edaadc38bb \ No newline at end of file diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index e577fbf..f4e0e2c 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Collections.Generic; using UnityEngine; #if UNITY_MATHEMATICS @@ -112,6 +113,24 @@ namespace NanoBrain { ClonePrefab(); } + public Cluster(ClusterJson json) { + ClusterData clusterData = JsonUtility.FromJson(json.jsonFile.text); + this.name = clusterData.name; + // First create all neurons + foreach (NeuronData neuronData in clusterData.neurons) { + Neuron newNeuron = new(this, neuronData.name); + } + // Now create the synapses between them + foreach (NeuronData neuronData in clusterData.neurons) { + Neuron receiver = this.GetNeuron(neuronData.name); + foreach (SynapseData synapseData in neuronData.synapses) { + Neuron sender = this.GetNeuron(synapseData.neuronName); + sender?.AddReceiver(receiver, synapseData.weight); + } + } + } + + /// /// Clone a prefab. /// @@ -785,6 +804,11 @@ namespace NanoBrain { System.IO.File.WriteAllText(path, json); } + public ClusterData Import(string path) { + string json = File.ReadAllText(path); + return JsonUtility.FromJson(json); + } + #endregion } @@ -800,8 +824,9 @@ namespace NanoBrain { if (nucleus is Neuron neuron) { NeuronData neuronData = new(neuron); this.neurons.Add(neuronData); - } else if (nucleus is Cluster extCluster) { - ExternalClusterData clusterData = new (extCluster); + } + else if (nucleus is Cluster extCluster) { + ExternalClusterData clusterData = new(extCluster); this.clusters.Add(clusterData); } } diff --git a/Runtime/Scripts/Core/Nucleus.cs b/Runtime/Scripts/Core/Nucleus.cs index 9b7fda3..fe06919 100644 --- a/Runtime/Scripts/Core/Nucleus.cs +++ b/Runtime/Scripts/Core/Nucleus.cs @@ -39,9 +39,6 @@ namespace NanoBrain { Neuron, MemoryCell, Cluster, - //Receptor, - //ClusterReceptor, - //ClusterArray, } #region Update