diff --git a/app_state.py b/app_state.py
index 1c51f57..9211312 100644
--- a/app_state.py
+++ b/app_state.py
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
import configparser
import os
-VERSION = "0.0.0.083"
+VERSION = "0.0.0.084"
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
DEFAULT_GRID_SIZE = 10
DEFAULT_ADD_TIMESTAMP = True
diff --git a/device_session.py b/device_session.py
index 618d84f..79172ce 100644
--- a/device_session.py
+++ b/device_session.py
@@ -68,15 +68,49 @@ class DeviceSession:
def _load_slot_registry(self, parts):
"""Builds slot_registry from kit manifests for the device's schema."""
registry = {}
+ base_slots = []
for kit_id, part in enumerate(parts):
kit_name = part.name
checksum = part.checksum
manifest = schema.find_manifest(kit_name, checksum)
if manifest is None:
continue
+ if kit_name == "sys":
+ base_slots = self._extract_component_base_slots(manifest)
self._register_kit_types(registry, kit_id, kit_name, manifest)
+ # Prepend inherited Component base slots (meta, etc.) to every type
+ if base_slots:
+ for key in registry:
+ existing_names = {s["name"] for s in registry[key]}
+ for bs in base_slots:
+ if bs["name"] not in existing_names:
+ registry[key].insert(0, bs)
self.sox_client.slot_registry = registry
+ def _extract_component_base_slots(self, manifest):
+ """Extracts base slots from sys::Component that all types inherit."""
+ for type_elem in manifest.iter("type"):
+ if type_elem.get("name") == "Component":
+ slots = []
+ for slot_elem in type_elem.iter("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
for type_elem in root.iter("type"):
@@ -239,14 +273,22 @@ class DeviceSession:
return comp_elem
def build_links_elements(self, sax_root):
- """Adds elements under the app element from cached link data."""
- app_elem = sax_root.find("app")
- if app_elem is None:
- return
+ """Adds elements under a container, matching .sax structure."""
+ links_elem = sax_root.find("links")
+ if links_elem is None:
+ links_elem = ET.SubElement(sax_root, "links")
+
+ seen = set()
for c in self.sox_client.cache:
if c is None:
continue
for lnk in c.links:
+ link_key = (lnk.from_comp_id, lnk.from_slot_id,
+ lnk.to_comp_id, lnk.to_slot_id)
+ if link_key in seen:
+ continue
+ seen.add(link_key)
+
from_comp = self.sox_client.cache_get(lnk.from_comp_id)
to_comp = self.sox_client.cache_get(lnk.to_comp_id)
if from_comp is None or to_comp is None:
@@ -261,7 +303,7 @@ class DeviceSession:
to_path = self._comp_path(lnk.to_comp_id)
if from_path and to_path and from_slot_name and to_slot_name:
- ET.SubElement(app_elem, "link",
+ ET.SubElement(links_elem, "link",
**{"from": f"{from_path}.{from_slot_name}",
"to": f"{to_path}.{to_slot_name}"})