From 945b0f69fd1f40de5df4ce70b77b3a7aa40293c1 Mon Sep 17 00:00:00 2001 From: RoboDoig Date: Thu, 19 Mar 2026 17:21:34 +0000 Subject: [PATCH 1/4] Jinja template for schema deserialization operator --- .gitignore | 1 + copier.yml | 5 ++- template/src/Extensions/LoadSchemas.cs.jinja | 37 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 template/src/Extensions/LoadSchemas.cs.jinja diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/copier.yml b/copier.yml index 091a81d..8e0ca3d 100644 --- a/copier.yml +++ b/copier.yml @@ -52,13 +52,12 @@ dotnet_name: when: false dotnet_full_name: - # Eg: Aind.Behavior.NewProject - default: '{{ (prefix | replace("-", " ") | title | replace(" ", ".")) }}.{{ (dotnet_name) }}' + # Eg: UclOpenNewProject + default: '{{ (prefix | replace("-", " ") | title | replace(" ", "")) }}{{ (dotnet_name) }}' help: Internal .NET module name (auto-generated) when: false python_class_prefix: - # Eg: AindBehaviorNewProject default: '{{ dotnet_full_name | replace(".", "") }}' help: Internal Python class prefix (auto-generated) type: str diff --git a/template/src/Extensions/LoadSchemas.cs.jinja b/template/src/Extensions/LoadSchemas.cs.jinja new file mode 100644 index 0000000..470e255 --- /dev/null +++ b/template/src/Extensions/LoadSchemas.cs.jinja @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reactive.Linq; +using Bonsai; +using {{ dotnet_full_name }}DataSchema; + +public class LoadSchemas : Source> +{ + [Description("The relative or absolute path of the session file to open for reading.")] + [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string SessionSettingsPath {get; set;} + + [Description("The relative or absolute path of the rig file to open for reading.")] + [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string RigSettingsPath {get; set;} + + [Description("The relative or absolute path of the task file to open for reading.")] + [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string TaskLogicSettingsPath {get; set;} + + public override IObservable> Generate() + { + string sessionSettingsRaw = File.ReadAllText(SessionSettingsPath); + string rigSettingsRaw = File.ReadAllText(RigSettingsPath); + string taskLogicSettingsRaw = File.ReadAllText(TaskLogicSettingsPath); + + var sessionSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject(sessionSettingsRaw)); + var rigSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject<{{ dotnet_full_name }}Rig>(rigSettingsRaw)); + var taskLogicSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject<{{ dotnet_full_name }}TaskLogic>(taskLogicSettingsRaw)); + + return sessionSource.Zip(rigSource, taskLogicSource, (s1, s2, s3) => + { + return Tuple.Create(s1, s2, s3); + }); + } +} \ No newline at end of file From 9eb552236f47cb9b4ff164621e8cf46494a0aaaf Mon Sep 17 00:00:00 2001 From: RoboDoig Date: Thu, 19 Mar 2026 17:28:59 +0000 Subject: [PATCH 2/4] Fix typo in template --- template/src/Extensions/LoadSchemas.cs.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/src/Extensions/LoadSchemas.cs.jinja b/template/src/Extensions/LoadSchemas.cs.jinja index 470e255..691d0e2 100644 --- a/template/src/Extensions/LoadSchemas.cs.jinja +++ b/template/src/Extensions/LoadSchemas.cs.jinja @@ -5,7 +5,7 @@ using System.Reactive.Linq; using Bonsai; using {{ dotnet_full_name }}DataSchema; -public class LoadSchemas : Source> +public class LoadSchemas : Source> { [Description("The relative or absolute path of the session file to open for reading.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] From 74804b6acd9664506f0b0d065bea9a66ce842eed Mon Sep 17 00:00:00 2001 From: RoboDoig Date: Thu, 19 Mar 2026 17:30:58 +0000 Subject: [PATCH 3/4] Add experiment to generated models --- template/src/{{ python_folder_name }}/regenerate.py.jinja | 1 + 1 file changed, 1 insertion(+) diff --git a/template/src/{{ python_folder_name }}/regenerate.py.jinja b/template/src/{{ python_folder_name }}/regenerate.py.jinja index 8f2754a..bbb0a80 100644 --- a/template/src/{{ python_folder_name }}/regenerate.py.jinja +++ b/template/src/{{ python_folder_name }}/regenerate.py.jinja @@ -17,6 +17,7 @@ def main(): models = [ {{ python_folder_name }}.task.{{ python_class_prefix }}TaskLogic, {{ python_folder_name }}.rig.{{ python_class_prefix }}Rig, + Experiment ] model = pydantic.RootModel[Union[tuple(models)]] From f2737f4047cf472f45a7aca12e160e18786b4b2a Mon Sep 17 00:00:00 2001 From: RoboDoig Date: Thu, 19 Mar 2026 17:41:52 +0000 Subject: [PATCH 4/4] Update to reference experiment class rather than session --- template/examples/session.py.jinja | 2 +- template/scripts/{deploy.ps1 => deploy.ps1.jinja} | 13 ++++++++----- template/src/Extensions/LoadSchemas.cs.jinja | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) rename template/scripts/{deploy.ps1 => deploy.ps1.jinja} (86%) diff --git a/template/examples/session.py.jinja b/template/examples/session.py.jinja index e98d7dd..e442ffa 100644 --- a/template/examples/session.py.jinja +++ b/template/examples/session.py.jinja @@ -1,7 +1,7 @@ import datetime import os -from swc.aeon.schema import Experiment +from ucl_open.rigs.experiment import Experiment # TODO - autofill experiment fields session = Experiment( diff --git a/template/scripts/deploy.ps1 b/template/scripts/deploy.ps1.jinja similarity index 86% rename from template/scripts/deploy.ps1 rename to template/scripts/deploy.ps1.jinja index 42f5b67..fc41560 100644 --- a/template/scripts/deploy.ps1 +++ b/template/scripts/deploy.ps1.jinja @@ -17,7 +17,13 @@ if (Test-Path -Path ./.venv) { &uv venv .\.venv\Scripts\Activate.ps1 Write-Output "Synchronizing environment..." -&uv sync +&uv sync + +Write-Output "Creating sgen output folder" +mkdir src\DataSchemas + +Write-Output "Generate placeholder extension classes..." +&uv run .\src\{{ python_folder_name }}\regenerate.py Write-Output "Creating a Bonsai environment and installing packages..." if (Test-Path -Path "bonsai") { @@ -32,7 +38,4 @@ if (Test-Path -Path "bonsai") { Set-Location .. Write-Output "Creating bonsai extensions folder..." -mkdir src\Extensions - -Write-Output "Creating sgen output folder" -mkdir src\DataSchemas \ No newline at end of file +mkdir src\Extensions \ No newline at end of file diff --git a/template/src/Extensions/LoadSchemas.cs.jinja b/template/src/Extensions/LoadSchemas.cs.jinja index 691d0e2..bff108f 100644 --- a/template/src/Extensions/LoadSchemas.cs.jinja +++ b/template/src/Extensions/LoadSchemas.cs.jinja @@ -5,7 +5,7 @@ using System.Reactive.Linq; using Bonsai; using {{ dotnet_full_name }}DataSchema; -public class LoadSchemas : Source> +public class LoadSchemas : Source> { [Description("The relative or absolute path of the session file to open for reading.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] @@ -19,13 +19,13 @@ public class LoadSchemas : Source> Generate() + public override IObservable> Generate() { string sessionSettingsRaw = File.ReadAllText(SessionSettingsPath); string rigSettingsRaw = File.ReadAllText(RigSettingsPath); string taskLogicSettingsRaw = File.ReadAllText(TaskLogicSettingsPath); - var sessionSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject(sessionSettingsRaw)); + var sessionSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject(sessionSettingsRaw)); var rigSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject<{{ dotnet_full_name }}Rig>(rigSettingsRaw)); var taskLogicSource = Observable.Return(Newtonsoft.Json.JsonConvert.DeserializeObject<{{ dotnet_full_name }}TaskLogic>(taskLogicSettingsRaw));