multicluster visualization

This commit is contained in:
Pascal Serrarens 2026-05-20 15:08:42 +02:00
parent a87118fc23
commit 92bfb29ed0
5 changed files with 84 additions and 37 deletions

View File

@ -76,8 +76,15 @@ namespace NanoBrain.Unity {
margin = new RectOffset(10, 0, 4, 4) margin = new RectOffset(10, 0, 4, 4)
}; };
// Nucleus type // Nucleus type
string nucleusType = this.view.currentNucleus.GetType().Name; System.Type nucleusType = this.view.currentNucleus.GetType();
GUILayout.Label(nucleusType, headerStyle); if (nucleusType == typeof(Cluster)) {
Cluster cluster = this.view.currentNucleus as Cluster;
GUILayout.Label($"{cluster.prefab.name} {nucleusType.Name}", headerStyle);
}
else
GUILayout.Label(nucleusType.Name, headerStyle);
// string nucleusType = this.view.currentNucleus.GetType().Name;
// GUILayout.Label(nucleusType, headerStyle);
// Nucleus name // Nucleus name
string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle); string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle);

View File

@ -7,6 +7,8 @@ namespace NanoBrain.Unity {
public class ClusterView { public class ClusterView {
public static ClusterPrefab previousPrefab;
private static readonly float discRadius = 20; private static readonly float discRadius = 20;
private float viewWidth; private float viewWidth;
private float contentWidth = 1000; private float contentWidth = 1000;
@ -199,7 +201,7 @@ namespace NanoBrain.Unity {
DrawOutputs(position); DrawOutputs(position);
return; return;
} }
Dag dag = GenerateGraph(this.selectedOutput); Dag dag = GenerateGraph(this.selectedOutput);
Dag.ComputeLayout(dag); Dag.ComputeLayout(dag);
@ -269,7 +271,7 @@ namespace NanoBrain.Unity {
Neuron receiverNeuron = receiver.nucleus as Neuron; Neuron receiverNeuron = receiver.nucleus as Neuron;
if (receiverNeuron == null) if (receiverNeuron == null)
return; return;
foreach (Synapse synapse in receiverNeuron.synapses) { foreach (Synapse synapse in receiverNeuron.synapses) {
Nucleus nucleus = synapse.neuron; Nucleus nucleus = synapse.neuron;
if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) { if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) {
@ -310,7 +312,7 @@ namespace NanoBrain.Unity {
if (nucleus == this.selectedOutput) { if (nucleus == this.selectedOutput) {
// Add link to 'Outpus' // Add link to 'Outpus'
nodeCount++; nodeCount++;
if (ClusterViewer.previousPrefab != null) if (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab)
// Add link to previous editor // Add link to previous editor
nodeCount++; nodeCount++;
} }
@ -344,9 +346,9 @@ namespace NanoBrain.Unity {
} }
if (nucleus == this.selectedOutput) { if (nucleus == this.selectedOutput) {
Vector3 pos = new(50, margin + row * spacing, 0); Vector3 pos = new(50, margin + row * spacing, 0);
if (ClusterViewer.previousPrefab != null) { if (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab) {
DrawEdge(parentPos, pos); DrawEdge(parentPos, pos);
DrawClusterPrefab(ClusterViewer.previousPrefab, pos); DrawClusterPrefab(ClusterView.previousPrefab, pos);
row++; row++;
} }
pos = new(50, margin + row * spacing, 0); pos = new(50, margin + row * spacing, 0);
@ -447,9 +449,10 @@ namespace NanoBrain.Unity {
float margin = 10 + spacing / 2; float margin = 10 + spacing / 2;
int row = 0; int row = 0;
Vector3 position = Vector3.zero;
foreach (Cluster sibling in nucleus.parent.instances) { foreach (Cluster sibling in nucleus.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron; Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
Vector3 position = new(250, margin + row * spacing, 0.0f); position = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, position); DrawEdge(parentPos, position);
Color color = Color.black; Color color = Color.black;
if (Application.isPlaying) { if (Application.isPlaying) {
@ -459,16 +462,16 @@ namespace NanoBrain.Unity {
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} }
DrawNucleus(siblingNeuron, position, color); DrawNucleus(siblingNeuron, position, color);
row++;
}
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron
string name = $"{nucleus.parent.instances[0].baseName}\n{nucleus.name}";
GUIStyle style = new(EditorStyles.label) { GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter, alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white }, normal = { textColor = Color.white },
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
}; };
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron Handles.Label(labelPos, name, style);
string name = $"{sibling.baseName}\n{nucleus.name}";
Handles.Label(labelPos, name, style);
row++;
}
expandArray = false; expandArray = false;
} }
@ -526,7 +529,6 @@ namespace NanoBrain.Unity {
} }
} }
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) { protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) {
maxValue = 1; maxValue = 1;
Color color; Color color;
@ -541,7 +543,6 @@ namespace NanoBrain.Unity {
DrawNucleus(nucleus, position, color); DrawNucleus(nucleus, position, color);
} }
protected void DrawNucleus(Nucleus nucleus, Vector2 position, Color color) { protected void DrawNucleus(Nucleus nucleus, Vector2 position, Color color) {
if (nucleus == null) if (nucleus == null)
return; return;
@ -699,7 +700,7 @@ namespace NanoBrain.Unity {
e.Use(); e.Use();
Selection.activeObject = prefab; Selection.activeObject = prefab;
EditorGUIUtility.PingObject(prefab); EditorGUIUtility.PingObject(prefab);
ClusterViewer.previousPrefab = null; ClusterView.previousPrefab = null;
Editor.CreateEditor(prefab); Editor.CreateEditor(prefab);
} }
} }
@ -765,16 +766,16 @@ namespace NanoBrain.Unity {
protected void OnNeuronClick(Nucleus nucleus) { protected void OnNeuronClick(Nucleus nucleus) {
if (nucleus == this.currentNucleus) { if (nucleus == this.currentNucleus) {
this.selectedSynapseNeuron = null; this.selectedSynapseNeuron = null;
// if (Application.isPlaying) { if (Application.isPlaying) {
// if (nucleus is Cluster) if (nucleus is Cluster)
// expandArray = !expandArray; expandArray = !expandArray;
// else else
// expandArray = false; expandArray = false;
// } }
// else { else {
if (nucleus is Cluster cluster) if (nucleus is Cluster cluster)
OnClusterClick(cluster); OnClusterClick(cluster);
// } }
} }
else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) { else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) {
// We go to a different cluster // We go to a different cluster
@ -795,6 +796,7 @@ namespace NanoBrain.Unity {
else { else {
// select the cluster, not the neuron in the cluster // select the cluster, not the neuron in the cluster
this.currentNucleus = nucleus.parent; this.currentNucleus = nucleus.parent;
this.selectedSynapseNeuron = null;
this.expandArray = false; this.expandArray = false;
} }
} }
@ -802,6 +804,7 @@ namespace NanoBrain.Unity {
this.currentNucleus = nucleus; this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0) if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus; this.selectedOutput = this.currentNucleus;
this.selectedSynapseNeuron = null;
this.expandArray = false; this.expandArray = false;
} }
} }
@ -810,7 +813,7 @@ namespace NanoBrain.Unity {
// May be used with storedPrefab... // May be used with storedPrefab...
Selection.activeObject = subCluster.prefab; Selection.activeObject = subCluster.prefab;
EditorGUIUtility.PingObject(subCluster.prefab); EditorGUIUtility.PingObject(subCluster.prefab);
ClusterViewer.previousPrefab = this.currentCluster.prefab; ClusterView.previousPrefab = this.currentCluster.prefab;
Editor.CreateEditor(subCluster.prefab); Editor.CreateEditor(subCluster.prefab);
} }

View File

@ -9,7 +9,7 @@ namespace NanoBrain.Unity {
public class ClusterViewer : Editor { public class ClusterViewer : Editor {
public static ClusterPrefab previousPrefab; //public static ClusterPrefab previousPrefab;
public class GraphView : VisualElement { public class GraphView : VisualElement {
protected Cluster currentCluster; protected Cluster currentCluster;

View File

@ -12,12 +12,19 @@ namespace NanoBrain.Unity {
[CustomPropertyDrawer(typeof(Cluster))] [CustomPropertyDrawer(typeof(Cluster))]
class Cluster_Drawer : PropertyDrawer { class Cluster_Drawer : PropertyDrawer {
static Cluster_Drawer() {
SceneView.duringSceneGui += OnSceneGUI;
}
public static void Insepctor(SerializedObject serializedObject, string propertyName) { public static void Insepctor(SerializedObject serializedObject, string propertyName) {
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName)); EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
} }
const float padding = 4f; const float padding = 4f;
const float elementHeight = 64f; // height reserved for the VisualElement const float elementHeight = 64f; // height reserved for the VisualElement
private static ClusterView clusterView;
private static UnityEngine.Object selectedTarget;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
float height = EditorGUIUtility.singleLineHeight + padding; float height = EditorGUIUtility.singleLineHeight + padding;
@ -69,7 +76,8 @@ namespace NanoBrain.Unity {
if (property.serializedObject.targetObjects.Length == 1) { if (property.serializedObject.targetObjects.Length == 1) {
// Graph is not shown when multi-editing // Graph is not shown when multi-editing
UnityEngine.Object targetObject = property.serializedObject.targetObject; UnityEngine.Object targetObject = property.serializedObject.targetObject;
Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster; Cluster_Drawer.selectedTarget = targetObject;
Cluster cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster;
// key per field instance // key per field instance
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetInstanceID();//GetEntityId(); string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetInstanceID();//GetEntityId();
@ -85,18 +93,40 @@ namespace NanoBrain.Unity {
// content rect below header // content rect below header
Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f); Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f);
Cluster_Drawer.clusterView = ClusterView.GetClusterView(property);
ClusterView.Render(drawRect, cluster, property); ClusterView.Render(drawRect, cluster, property);
//Debug.Log(prefab.cluster.defaultOutput.outputMagnitude); //Debug.Log(prefab.cluster.defaultOutput.outputMagnitude);
} }
} }
} }
else {
}
EditorGUI.indentLevel = indent; EditorGUI.indentLevel = indent;
EditorGUI.EndProperty(); EditorGUI.EndProperty();
} }
private static void OnSceneGUI(SceneView sceneView) {
GameObject gameObject = null;
if (selectedTarget is Component c)
gameObject = c.gameObject;
else if (selectedTarget is GameObject g)
gameObject = g;
Handles.color = Color.yellow;
if (clusterView.selectedSynapseNeuron != null) {
foreach (Cluster sibling in clusterView.selectedSynapseNeuron.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(clusterView.selectedSynapseNeuron.name) as Neuron;
Vector3 worldVector = gameObject.transform.TransformVector(siblingNeuron.outputValue);
Handles.DrawLine(gameObject.transform.position, gameObject.transform.position + worldVector);
}
}
else {
if (clusterView.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.DrawLine(gameObject.transform.position, gameObject.transform.position + worldVector);
}
}
}
} }
public static class SerializedPropertyUtility { public static class SerializedPropertyUtility {

View File

@ -42,7 +42,8 @@ namespace NanoBrain {
/// A cluster is a multi-cluster when there is more than one instance. /// A cluster is a multi-cluster when there is more than one instance.
/// The actual instances are only created at runtime. /// The actual instances are only created at runtime.
/// The value instanceCount determines how many instances will be present at runtime. /// The value instanceCount determines how many instances will be present at runtime.
[NonSerialized] //[NonSerialized]
[SerializeReference]
public Cluster[] instances; public Cluster[] instances;
/// <summary> /// <summary>
@ -164,7 +165,7 @@ namespace NanoBrain {
} }
} }
if (Application.isPlaying) { //if (Application.isPlaying) {
// Only create cluster siblings at runtime // Only create cluster siblings at runtime
foreach (Nucleus clonedNucleus in clonedNuclei) { foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster clonedCluster) if (clonedNucleus is not Cluster clonedCluster)
@ -194,7 +195,7 @@ namespace NanoBrain {
if (clonedNucleus is not Cluster) if (clonedNucleus is not Cluster)
clonedNucleus.UpdateStateIsolated(); clonedNucleus.UpdateStateIsolated();
} }
} //}
} }
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo /// \copydoc NanoBrain::Nucleus::ShallowCloneTo
@ -503,6 +504,8 @@ namespace NanoBrain {
foreach (Nucleus receptor in this.nuclei) { foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus) if (receptor is Nucleus nucleus)
if (nucleus.name == nucleusName) { if (nucleus.name == nucleusName) {
// if (nucleus is Cluster cluster)
// cluster.CheckInstances();
foundNucleus = nucleus; foundNucleus = nucleus;
return true; return true;
} }
@ -524,6 +527,7 @@ namespace NanoBrain {
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster cluster) { if (nucleus is Cluster cluster) {
if (cluster.name == clusterName || cluster.name == clusterName0) { if (cluster.name == clusterName || cluster.name == clusterName0) {
// cluster.CheckInstances();
string subNucleusName = nucleusName[(dotPosition + 1)..]; string subNucleusName = nucleusName[(dotPosition + 1)..];
return cluster.GetNucleus(subNucleusName); return cluster.GetNucleus(subNucleusName);
} }
@ -534,9 +538,11 @@ namespace NanoBrain {
else { else {
string nucleusName0 = nucleusName + ": 0"; string nucleusName0 = nucleusName + ": 0";
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster) { if (nucleus is Cluster cluster) {
if (nucleus.name == nucleusName || nucleus.name == nucleusName0) if (nucleus.name == nucleusName || nucleus.name == nucleusName0) {
// cluster.CheckInstances();
return nucleus; return nucleus;
}
} }
else if (nucleus.name == nucleusName) else if (nucleus.name == nucleusName)
return nucleus; return nucleus;
@ -574,6 +580,7 @@ namespace NanoBrain {
return this.GetNeuron(neuronName); return this.GetNeuron(neuronName);
// See if we are already using a cluster for thingId // See if we are already using a cluster for thingId
thingClusters ??= new();
if (thingClusters.TryGetValue(thingId, out Cluster cluster)) if (thingClusters.TryGetValue(thingId, out Cluster cluster))
return cluster.GetNeuron(neuronName); return cluster.GetNeuron(neuronName);