Add ad hoc syncvar editor publishing
This commit is contained in:
parent
49263e223f
commit
bb192a28c0
8 changed files with 288 additions and 4 deletions
|
|
@ -103,6 +103,8 @@ Derived State:
|
|||
- Blender JSON debug exports are inspection/import probes, not mirror authority.
|
||||
- Unity and Blender sync UI fields are command/edit affordances; the
|
||||
`brokkr.sync.*` documents are the shared sync policy surface.
|
||||
- Ad hoc syncvar editors write `brokkr.sync.var.v0` policy records only; they
|
||||
do not make an unsupported kind executable inside the daemon.
|
||||
- Editor selection and scene summaries are observations until command receipts
|
||||
confirm an accepted mutation.
|
||||
- Daemon stdout reports are telemetry, not durable sync state. The durable state
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ Primary sync documents:
|
|||
- `brokkr.sync.var.v0`: per-lane sync options such as transform, material,
|
||||
component property, custom property, timeline frame, camera lens, or
|
||||
Cinemachine virtual camera.
|
||||
Editor surfaces may also publish ad hoc sync vars with an explicit kind,
|
||||
paths, authority, enabled flag, and interpolation. The daemon executes
|
||||
implemented kinds and preserves unsupported kinds as typed sync policy for
|
||||
external consumers.
|
||||
- `brokkr.sync.timeline_binding.v0`: Blender scene/action timeline to Unity
|
||||
Timeline/Cinemachine correspondence.
|
||||
- `brokkr.sync.receipt.v0`: daemon sync pass receipts.
|
||||
|
|
|
|||
|
|
@ -190,6 +190,54 @@ class BrokkrPreferences(bpy.types.AddonPreferences):
|
|||
description="Synchronize Blender camera animation into Unity Cinemachine lanes",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_binding_id: bpy.props.StringProperty(
|
||||
name="Sync Var Binding",
|
||||
default="",
|
||||
description="Optional binding id for an ad hoc Brokkr sync var",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_kind: bpy.props.StringProperty(
|
||||
name="Sync Var Kind",
|
||||
default="custom-property",
|
||||
description="Brokkr sync var kind",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_display_name: bpy.props.StringProperty(
|
||||
name="Sync Var Name",
|
||||
default="Custom Property",
|
||||
description="Human-readable sync var label",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_unity_path: bpy.props.StringProperty(
|
||||
name="Unity Path",
|
||||
default="",
|
||||
description="Unity property path for the ad hoc sync var",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_blender_path: bpy.props.StringProperty(
|
||||
name="Blender Path",
|
||||
default="",
|
||||
description="Blender property path for the ad hoc sync var",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_authority: bpy.props.StringProperty(
|
||||
name="Authority",
|
||||
default="blender-to-unity",
|
||||
description="Sync authority, for example blender-to-unity or unity-to-blender",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_interpolation: bpy.props.StringProperty(
|
||||
name="Interpolation",
|
||||
default="step",
|
||||
description="Sync interpolation mode",
|
||||
)
|
||||
|
||||
ad_hoc_sync_var_enabled: bpy.props.BoolProperty(
|
||||
name="Sync Var Enabled",
|
||||
default=True,
|
||||
description="Whether the ad hoc sync var is enabled",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "broker_uri")
|
||||
|
|
@ -217,6 +265,14 @@ class BrokkrPreferences(bpy.types.AddonPreferences):
|
|||
layout.prop(self, "blender_action_name")
|
||||
layout.prop(self, "sync_timeline_frame")
|
||||
layout.prop(self, "sync_cinemachine_camera")
|
||||
layout.prop(self, "ad_hoc_sync_var_binding_id")
|
||||
layout.prop(self, "ad_hoc_sync_var_kind")
|
||||
layout.prop(self, "ad_hoc_sync_var_display_name")
|
||||
layout.prop(self, "ad_hoc_sync_var_unity_path")
|
||||
layout.prop(self, "ad_hoc_sync_var_blender_path")
|
||||
layout.prop(self, "ad_hoc_sync_var_authority")
|
||||
layout.prop(self, "ad_hoc_sync_var_interpolation")
|
||||
layout.prop(self, "ad_hoc_sync_var_enabled")
|
||||
|
||||
|
||||
class BROKKR_PT_status(bpy.types.Panel):
|
||||
|
|
@ -251,6 +307,18 @@ class BROKKR_PT_status(bpy.types.Panel):
|
|||
sync_row.operator("brokkr.publish_timeline_sync", icon="TIME")
|
||||
sync_row.operator("brokkr.refresh_sync_receipt", icon="FILE_REFRESH")
|
||||
|
||||
var_box = layout.box()
|
||||
var_box.label(text="Ad Hoc Sync Var")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_binding_id")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_kind")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_display_name")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_unity_path")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_blender_path")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_authority")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_interpolation")
|
||||
var_box.prop(prefs, "ad_hoc_sync_var_enabled")
|
||||
var_box.operator("brokkr.publish_sync_var", icon="DRIVER")
|
||||
|
||||
if adapter.last_snapshot:
|
||||
layout.separator()
|
||||
layout.label(text=f"Objects: {adapter.last_snapshot.get('objectCount', 0)}")
|
||||
|
|
@ -401,6 +469,31 @@ class BROKKR_OT_refresh_sync_receipt(bpy.types.Operator):
|
|||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BROKKR_OT_publish_sync_var(bpy.types.Operator):
|
||||
bl_idname = "brokkr.publish_sync_var"
|
||||
bl_label = "Publish Sync Var"
|
||||
bl_description = "Publish an ad hoc Brokkr sync var through the Blender mirror"
|
||||
|
||||
def execute(self, context):
|
||||
prefs = context.preferences.addons[__name__].preferences
|
||||
sync_var = target().publish_sync_var(
|
||||
prefs.cultmesh_cache_path,
|
||||
prefs.cultlib_py_src,
|
||||
prefs.sync_session_id,
|
||||
prefs.sync_display_name,
|
||||
prefs.ad_hoc_sync_var_display_name,
|
||||
prefs.ad_hoc_sync_var_binding_id,
|
||||
prefs.ad_hoc_sync_var_kind,
|
||||
prefs.ad_hoc_sync_var_unity_path,
|
||||
prefs.ad_hoc_sync_var_blender_path,
|
||||
prefs.ad_hoc_sync_var_authority,
|
||||
prefs.ad_hoc_sync_var_enabled,
|
||||
prefs.ad_hoc_sync_var_interpolation,
|
||||
)
|
||||
self.report({"INFO"}, f"Brokkr sync var: {sync_var['displayName']}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def _auto_capture(scene, depsgraph):
|
||||
context = bpy.context
|
||||
prefs = context.preferences.addons.get(__name__)
|
||||
|
|
@ -424,6 +517,7 @@ classes = (
|
|||
BROKKR_OT_publish_object_sync,
|
||||
BROKKR_OT_publish_timeline_sync,
|
||||
BROKKR_OT_refresh_sync_receipt,
|
||||
BROKKR_OT_publish_sync_var,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -232,6 +232,52 @@ class BrokkrBlenderTarget:
|
|||
self._put_sync_var(node, documents, normalized_session_id, binding_id, "cinemachine-virtual-camera", "Cinemachine Camera", "CinemachineVirtualCamera", "camera", "blender-to-unity", sync_camera, "linear", now)
|
||||
return binding
|
||||
|
||||
def publish_sync_var(
|
||||
self,
|
||||
cache_path: str,
|
||||
cultlib_py_src: str,
|
||||
session_id: str,
|
||||
session_display_name: str,
|
||||
sync_var_display_name: str,
|
||||
binding_id: str,
|
||||
kind: str,
|
||||
unity_property_path: str,
|
||||
blender_property_path: str,
|
||||
authority: str,
|
||||
enabled: bool,
|
||||
interpolation: str,
|
||||
) -> dict[str, Any]:
|
||||
now = _now()
|
||||
node, documents = self._open_node(cache_path, cultlib_py_src)
|
||||
normalized_session_id = session_id or "default"
|
||||
normalized_kind = kind or "custom-property"
|
||||
normalized_binding_id = binding_id or _stable_id(
|
||||
"adhoc-binding",
|
||||
normalized_session_id,
|
||||
normalized_kind,
|
||||
unity_property_path,
|
||||
blender_property_path,
|
||||
)
|
||||
session = _sync_session(normalized_session_id, session_display_name, now)
|
||||
sync_var_id = _stable_id("var", normalized_session_id, normalized_binding_id, normalized_kind)
|
||||
sync_var = {
|
||||
"schema": SYNC_VAR_SCHEMA,
|
||||
"syncVarId": sync_var_id,
|
||||
"sessionId": normalized_session_id,
|
||||
"bindingId": normalized_binding_id,
|
||||
"displayName": sync_var_display_name or normalized_kind,
|
||||
"kind": normalized_kind,
|
||||
"unityPropertyPath": unity_property_path,
|
||||
"blenderPropertyPath": blender_property_path,
|
||||
"authority": authority or "blender-to-unity",
|
||||
"enabled": bool(enabled),
|
||||
"interpolation": interpolation or "step",
|
||||
"updatedAt": now,
|
||||
}
|
||||
node.database.put(documents["sync_session"], f"sync/sessions/{normalized_session_id}", session)
|
||||
node.database.put(documents["sync_var"], f"sync/vars/{sync_var_id}", sync_var)
|
||||
return sync_var
|
||||
|
||||
def start_server(
|
||||
self,
|
||||
cache_path: str,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ owns the mirror state.
|
|||
12. After running `brokkr-daemon sync-once` or `sync-loop`, click
|
||||
`Refresh Sync Receipt` to show the latest daemon sync pass observed in the
|
||||
Blender mirror.
|
||||
13. To publish an ad hoc sync variable, fill the `Ad Hoc Sync Var` fields in
|
||||
the sidebar or add-on preferences and click `Publish Sync Var`.
|
||||
|
||||
Admitted command actions:
|
||||
|
||||
|
|
|
|||
|
|
@ -151,5 +151,53 @@ namespace GameCult.Brokkr.Editor
|
|||
get => EditorPrefs.GetString("GameCult.Brokkr.PrefabVariantPath", "Assets/BrokkrPrefabVariant.prefab");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.PrefabVariantPath", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarBindingId
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarBindingId", "");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarBindingId", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarKind
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarKind", "custom-property");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarKind", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarDisplayName
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarDisplayName", "Custom Property");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarDisplayName", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarUnityPath
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarUnityPath", "");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarUnityPath", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarBlenderPath
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarBlenderPath", "");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarBlenderPath", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarAuthority
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarAuthority", "blender-to-unity");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarAuthority", value);
|
||||
}
|
||||
|
||||
internal static string AdHocSyncVarInterpolation
|
||||
{
|
||||
get => EditorPrefs.GetString("GameCult.Brokkr.AdHocSyncVarInterpolation", "step");
|
||||
set => EditorPrefs.SetString("GameCult.Brokkr.AdHocSyncVarInterpolation", value);
|
||||
}
|
||||
|
||||
internal static bool AdHocSyncVarEnabled
|
||||
{
|
||||
get => EditorPrefs.GetBool("GameCult.Brokkr.AdHocSyncVarEnabled", true);
|
||||
set => EditorPrefs.SetBool("GameCult.Brokkr.AdHocSyncVarEnabled", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,14 @@ namespace GameCult.Brokkr.Editor
|
|||
private string prefabAssetPath = "";
|
||||
private string prefabInstanceName = "";
|
||||
private string prefabVariantPath = "Assets/BrokkrPrefabVariant.prefab";
|
||||
private string adHocSyncVarBindingId = "";
|
||||
private string adHocSyncVarKind = "custom-property";
|
||||
private string adHocSyncVarDisplayName = "Custom Property";
|
||||
private string adHocSyncVarUnityPath = "";
|
||||
private string adHocSyncVarBlenderPath = "";
|
||||
private string adHocSyncVarAuthority = "blender-to-unity";
|
||||
private string adHocSyncVarInterpolation = "step";
|
||||
private bool adHocSyncVarEnabled = true;
|
||||
|
||||
[MenuItem("GameCult/Brokkr")]
|
||||
public static void Open()
|
||||
|
|
@ -79,6 +87,14 @@ namespace GameCult.Brokkr.Editor
|
|||
prefabAssetPath = BrokkrSettings.PrefabAssetPath;
|
||||
prefabInstanceName = BrokkrSettings.PrefabInstanceName;
|
||||
prefabVariantPath = BrokkrSettings.PrefabVariantPath;
|
||||
adHocSyncVarBindingId = BrokkrSettings.AdHocSyncVarBindingId;
|
||||
adHocSyncVarKind = BrokkrSettings.AdHocSyncVarKind;
|
||||
adHocSyncVarDisplayName = BrokkrSettings.AdHocSyncVarDisplayName;
|
||||
adHocSyncVarUnityPath = BrokkrSettings.AdHocSyncVarUnityPath;
|
||||
adHocSyncVarBlenderPath = BrokkrSettings.AdHocSyncVarBlenderPath;
|
||||
adHocSyncVarAuthority = BrokkrSettings.AdHocSyncVarAuthority;
|
||||
adHocSyncVarInterpolation = BrokkrSettings.AdHocSyncVarInterpolation;
|
||||
adHocSyncVarEnabled = BrokkrSettings.AdHocSyncVarEnabled;
|
||||
Selection.selectionChanged += Repaint;
|
||||
EditorSceneManagerBridge.SceneDirtied += OnEditorSignal;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
||||
|
|
@ -130,6 +146,14 @@ namespace GameCult.Brokkr.Editor
|
|||
BrokkrSettings.PrefabAssetPath = prefabAssetPath;
|
||||
BrokkrSettings.PrefabInstanceName = prefabInstanceName;
|
||||
BrokkrSettings.PrefabVariantPath = prefabVariantPath;
|
||||
BrokkrSettings.AdHocSyncVarBindingId = adHocSyncVarBindingId;
|
||||
BrokkrSettings.AdHocSyncVarKind = adHocSyncVarKind;
|
||||
BrokkrSettings.AdHocSyncVarDisplayName = adHocSyncVarDisplayName;
|
||||
BrokkrSettings.AdHocSyncVarUnityPath = adHocSyncVarUnityPath;
|
||||
BrokkrSettings.AdHocSyncVarBlenderPath = adHocSyncVarBlenderPath;
|
||||
BrokkrSettings.AdHocSyncVarAuthority = adHocSyncVarAuthority;
|
||||
BrokkrSettings.AdHocSyncVarInterpolation = adHocSyncVarInterpolation;
|
||||
BrokkrSettings.AdHocSyncVarEnabled = adHocSyncVarEnabled;
|
||||
SetStatus("Settings saved.", MessageType.Info);
|
||||
}
|
||||
|
||||
|
|
@ -291,6 +315,22 @@ namespace GameCult.Brokkr.Editor
|
|||
unityCinemachineObjectId = selectedId;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Ad Hoc Sync Var", EditorStyles.boldLabel);
|
||||
adHocSyncVarBindingId = EditorGUILayout.TextField("Binding Id", adHocSyncVarBindingId);
|
||||
adHocSyncVarKind = EditorGUILayout.TextField("Kind", adHocSyncVarKind);
|
||||
adHocSyncVarDisplayName = EditorGUILayout.TextField("Display Name", adHocSyncVarDisplayName);
|
||||
adHocSyncVarUnityPath = EditorGUILayout.TextField("Unity Path", adHocSyncVarUnityPath);
|
||||
adHocSyncVarBlenderPath = EditorGUILayout.TextField("Blender Path", adHocSyncVarBlenderPath);
|
||||
adHocSyncVarAuthority = EditorGUILayout.TextField("Authority", adHocSyncVarAuthority);
|
||||
adHocSyncVarInterpolation = EditorGUILayout.TextField("Interpolation", adHocSyncVarInterpolation);
|
||||
adHocSyncVarEnabled = EditorGUILayout.Toggle("Enabled", adHocSyncVarEnabled);
|
||||
|
||||
if (GUILayout.Button("Publish Sync Var"))
|
||||
{
|
||||
PublishAdHocSyncVar();
|
||||
}
|
||||
}
|
||||
|
||||
private void CaptureSnapshot()
|
||||
|
|
@ -580,6 +620,36 @@ namespace GameCult.Brokkr.Editor
|
|||
}
|
||||
}
|
||||
|
||||
private void PublishAdHocSyncVar()
|
||||
{
|
||||
try
|
||||
{
|
||||
RequireMirror();
|
||||
var now = System.DateTimeOffset.UtcNow.ToString("O");
|
||||
var bindingId = string.IsNullOrWhiteSpace(adHocSyncVarBindingId)
|
||||
? StableId("adhoc-binding", NormalizedSyncSessionId(), adHocSyncVarKind, adHocSyncVarUnityPath, adHocSyncVarBlenderPath)
|
||||
: adHocSyncVarBindingId.Trim();
|
||||
var session = BuildSyncSession(now);
|
||||
|
||||
mirror.PublishSyncSessionAsync(session).GetAwaiter().GetResult();
|
||||
PublishSyncVar(
|
||||
bindingId,
|
||||
string.IsNullOrWhiteSpace(adHocSyncVarKind) ? "custom-property" : adHocSyncVarKind.Trim(),
|
||||
string.IsNullOrWhiteSpace(adHocSyncVarDisplayName) ? "Custom Property" : adHocSyncVarDisplayName.Trim(),
|
||||
adHocSyncVarUnityPath.Trim(),
|
||||
adHocSyncVarBlenderPath.Trim(),
|
||||
adHocSyncVarEnabled,
|
||||
string.IsNullOrWhiteSpace(adHocSyncVarInterpolation) ? "step" : adHocSyncVarInterpolation.Trim(),
|
||||
now,
|
||||
string.IsNullOrWhiteSpace(adHocSyncVarAuthority) ? "blender-to-unity" : adHocSyncVarAuthority.Trim());
|
||||
SetStatus($"Published Brokkr sync var: {adHocSyncVarDisplayName}", MessageType.Info);
|
||||
}
|
||||
catch (System.Exception error)
|
||||
{
|
||||
SetStatus(error.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private BrokkrSyncSession BuildSyncSession(string observedAt)
|
||||
{
|
||||
return new BrokkrSyncSession
|
||||
|
|
@ -601,8 +671,12 @@ namespace GameCult.Brokkr.Editor
|
|||
string blenderPropertyPath,
|
||||
bool enabled,
|
||||
string interpolation,
|
||||
string updatedAt)
|
||||
string updatedAt,
|
||||
string authority = "")
|
||||
{
|
||||
var resolvedAuthority = string.IsNullOrWhiteSpace(authority)
|
||||
? DefaultAuthorityForKind(kind)
|
||||
: authority;
|
||||
var syncVar = new BrokkrSyncVar
|
||||
{
|
||||
syncVarId = StableId("var", NormalizedSyncSessionId(), bindingId, kind),
|
||||
|
|
@ -612,9 +686,7 @@ namespace GameCult.Brokkr.Editor
|
|||
kind = kind,
|
||||
unityPropertyPath = unityPropertyPath,
|
||||
blenderPropertyPath = blenderPropertyPath,
|
||||
authority = kind.StartsWith("timeline", StringComparison.Ordinal) || kind == "custom-property"
|
||||
? "blender-to-unity"
|
||||
: "unity-to-blender",
|
||||
authority = resolvedAuthority,
|
||||
enabled = enabled,
|
||||
interpolation = interpolation,
|
||||
updatedAt = updatedAt
|
||||
|
|
@ -622,6 +694,13 @@ namespace GameCult.Brokkr.Editor
|
|||
mirror.PublishSyncVarAsync(syncVar).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static string DefaultAuthorityForKind(string kind)
|
||||
{
|
||||
return kind.StartsWith("timeline", StringComparison.Ordinal) || kind == "custom-property"
|
||||
? "blender-to-unity"
|
||||
: "unity-to-blender";
|
||||
}
|
||||
|
||||
private static string BuildTransformPath(Transform transform)
|
||||
{
|
||||
var path = transform.name;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ In Unity:
|
|||
9. With the mirror running, run `brokkr-daemon sync-once` or `sync-loop`, then
|
||||
click `Poll Sync Receipt` or leave the window open to show the latest daemon
|
||||
sync pass receipt.
|
||||
10. To publish an ad hoc sync variable, fill `Ad Hoc Sync Var` with a binding id
|
||||
or leave it blank for a generated id, set kind/paths/authority/interpolation,
|
||||
then click `Publish Sync Var`.
|
||||
|
||||
The Unity adapter writes the latest editor snapshot to:
|
||||
|
||||
|
|
@ -51,6 +54,12 @@ Brokkr daemon sync receipts are read from:
|
|||
sync/receipts/{receiptId}
|
||||
```
|
||||
|
||||
Ad hoc sync variables are written to:
|
||||
|
||||
```text
|
||||
sync/vars/{syncVarId}
|
||||
```
|
||||
|
||||
Verse-side command clients write `brokkr.unity.command_intent.v0` documents to:
|
||||
|
||||
```text
|
||||
|
|
|
|||
Loading…
Reference in a new issue