Apply offline wiresheet rules to live mode: meta positions and links

Three fixes so live wiresheets render identically to offline .sax files:

1. Inherit sys::Component base slots (meta) into every type's slot
   registry so component x/y positions are read from the device.

2. Place <link> elements under a <links> container element instead of
   directly under <app>, matching the .sax structure that draw_links
   and build_linked_slot_index expect.

3. Deduplicate links (each link appears on both endpoints in Sox) so
   wires are not drawn twice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 13:14:02 +03:00
co-authored by Claude Opus 4.6
parent 599370f60c
commit 4c90c1c241
2 changed files with 48 additions and 6 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.083" VERSION = "0.0.0.084"
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
+47 -5
View File
@@ -68,15 +68,49 @@ 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 = []
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":
base_slots = self._extract_component_base_slots(manifest)
self._register_kit_types(registry, kit_id, kit_name, 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 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): 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"):
@@ -239,14 +273,22 @@ class DeviceSession:
return comp_elem return comp_elem
def build_links_elements(self, sax_root): def build_links_elements(self, sax_root):
"""Adds <link> elements under the app element from cached link data.""" """Adds <link> elements under a <links> container, matching .sax structure."""
app_elem = sax_root.find("app") links_elem = sax_root.find("links")
if app_elem is None: if links_elem is None:
return links_elem = ET.SubElement(sax_root, "links")
seen = set()
for c in self.sox_client.cache: for c in self.sox_client.cache:
if c is None: if c is None:
continue continue
for lnk in c.links: 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) from_comp = self.sox_client.cache_get(lnk.from_comp_id)
to_comp = self.sox_client.cache_get(lnk.to_comp_id) to_comp = self.sox_client.cache_get(lnk.to_comp_id)
if from_comp is None or to_comp is None: 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) to_path = self._comp_path(lnk.to_comp_id)
if from_path and to_path and from_slot_name and to_slot_name: 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}", **{"from": f"{from_path}.{from_slot_name}",
"to": f"{to_path}.{to_slot_name}"}) "to": f"{to_path}.{to_slot_name}"})