Flatten SOX slot registry through full base-type chain

Types with intermediate parents (e.g. ontrolOx::PushButtonImage ->
Widget -> Service -> Component) only got Component + own slots, so
config/runtime streams were decoded against the wrong slot list and
x/y/imageFileName came out wrong.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 09:01:59 +03:00
co-authored by Claude Opus 5.5
parent fd951f5b82
commit b0e769075f
2 changed files with 29 additions and 45 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
import configparser import configparser
import os import os
VERSION = "0.0.0.123" VERSION = "0.0.0.124"
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
DEFAULT_GRID_SIZE = 10 DEFAULT_GRID_SIZE = 10
DEFAULT_ADD_TIMESTAMP = True DEFAULT_ADD_TIMESTAMP = True
+28 -44
View File
@@ -87,54 +87,39 @@ class DeviceSession:
def _load_slot_registry(self, parts): def _load_slot_registry(self, parts):
"""Builds slot_registry from kit manifests for the device's schema.""" """Builds slot_registry from kit manifests for the device's schema."""
registry = {} registry = {}
base_slots = [] own_slots = {} # "kit::Type" -> declared slots
bases = {} # "kit::Type" -> "kit::Base" or None
for kit_id, part in enumerate(parts): for kit_id, part in enumerate(parts):
kit_name = part.name kit_name = part.name
checksum = part.checksum checksum = part.checksum
manifest = schema.find_manifest(kit_name, checksum) manifest = schema.find_manifest(kit_name, checksum)
if manifest is None: if manifest is None:
continue continue
if kit_name == "sys": self._register_kit_types(registry, kit_id, kit_name, manifest,
base_slots = self._extract_component_base_slots(manifest) own_slots, bases)
self._register_kit_types(registry, kit_id, kit_name, manifest) # Flatten each type's slots through its whole base chain (e.g.
# Prepend inherited Component base slots (meta, etc.) to every type, # Component -> Service -> ontrolOx::Widget -> PushButtonImage), then
# then renumber IDs to match the wire slot index (positional in the # renumber IDs to match the wire slot index (positional in the
# flattened inherited + own list — see sedona Type.java resolveSlots). # flattened inherited + own list — see sedona Type.java resolveSlots).
if base_slots: for key in registry:
for key in registry: qname = self.sox_client.type_name_map[key]
existing_names = {s["name"] for s in registry[key]} chain = []
for bs in base_slots: seen = set()
if bs["name"] not in existing_names: t = qname
registry[key].insert(0, dict(bs)) while t and t not in seen and t in own_slots:
for i, slot in enumerate(registry[key]): seen.add(t)
slot["id"] = i chain.append(t)
t = bases.get(t)
flat = []
for t in reversed(chain):
flat.extend(dict(s) for s in own_slots[t])
for i, slot in enumerate(flat):
slot["id"] = i
registry[key] = flat
self.sox_client.slot_registry = registry self.sox_client.slot_registry = registry
def _extract_component_base_slots(self, manifest): def _register_kit_types(self, registry, kit_id, kit_name, manifest,
"""Extracts base slots from sys::Component that all types inherit.""" own_slots, bases):
for type_elem in manifest.iter("type"):
if type_elem.get("name") == "Component":
slots = []
for slot_elem in type_elem.findall("slot"):
slot_name = slot_elem.get("name", "")
slot_id = int(slot_elem.get("id", "-1"))
slot_type = slot_elem.get("type", "int")
flags_str = slot_elem.get("flags", "")
slots.append({
"name": slot_name,
"id": slot_id,
"type_name": slot_type,
"is_config": "c" in flags_str or "p" in flags_str,
"is_runtime": "r" in flags_str,
"is_action": "a" in flags_str,
"is_operator": "o" in flags_str,
"is_as_str": slot_type == "Str",
"is_prop": "a" not in flags_str,
})
return slots
return []
def _register_kit_types(self, registry, kit_id, kit_name, manifest):
root = manifest root = manifest
for type_elem in root.iter("type"): for type_elem in root.iter("type"):
type_name = type_elem.get("name", "") type_name = type_elem.get("name", "")
@@ -165,6 +150,8 @@ class DeviceSession:
}) })
registry[(kit_id, type_id_val)] = slots registry[(kit_id, type_id_val)] = slots
full_name = f"{kit_name}::{type_name}" full_name = f"{kit_name}::{type_name}"
own_slots[full_name] = slots
bases[full_name] = type_elem.get("base") or None
self.sox_client.type_name_map[(kit_id, type_id_val)] = full_name self.sox_client.type_name_map[(kit_id, type_id_val)] = full_name
# update type_name on any cached component of this type # update type_name on any cached component of this type
for c in self.sox_client.cache: for c in self.sox_client.cache:
@@ -368,16 +355,13 @@ class DeviceSession:
def _slot_name_by_id(self, kit_id, type_id_val, slot_id): def _slot_name_by_id(self, kit_id, type_id_val, slot_id):
# SOX links use absolute slot IDs across the full inheritance chain. # SOX links use absolute slot IDs across the full inheritance chain.
# final_components_registry has correctly resolved absolute IDs;
# the per-type slot_registry only stores local (per-type) IDs
# which are wrong for types with intermediate parents.
type_name = self.sox_client.type_name_map.get((kit_id, type_id_val)) type_name = self.sox_client.type_name_map.get((kit_id, type_id_val))
if type_name and type_name in app_state.final_components_registry: if type_name and type_name in app_state.final_components_registry:
for s in app_state.final_components_registry[type_name]: for s in app_state.final_components_registry[type_name]:
if s["slot_id"] == slot_id: if s["slot_id"] == slot_id:
return s["name"] return s["name"]
# Fallback to slot_registry (local IDs — wrong for inherited types, # Fallback to slot_registry (flattened through the base chain, so
# but keeps things working when the schema registry is not yet loaded) # its IDs are absolute too)
slots = self.sox_client.slot_registry.get((kit_id, type_id_val)) slots = self.sox_client.slot_registry.get((kit_id, type_id_val))
if slots: if slots:
for s in slots: for s in slots: