config editor further progress

This commit is contained in:
2026-05-04 16:10:37 -07:00
parent 5b062611c6
commit 67243649ef
2 changed files with 213 additions and 21 deletions
+47 -6
View File
@@ -2,16 +2,57 @@
"individual": [
{ "name": "Start Node", "type": "begin" },
{
"name": "Hybrid Processor",
"name": "Average",
"type": "middle",
"fields": [
{"label": "Threshold", "type": "number"},
{"label": "Tag", "type": "string"},
{"label": "Sub-Logic", "type": "slot"}
{"label": "Joint 1", "type": "slot"},
{"label": "Joint 2", "type": "slot"}
]
},
{
"name": "Distance",
"type": "middle",
"fields": [
{"label": "Joint 1", "type": "slot"},
{"label": "Joint 2", "type": "slot"}
]
},
{
"name": "Z-Diff",
"type": "middle",
"fields": [
{"label": "Joint 1", "type": "slot"},
{"label": "Joint 2", "type": "slot"}
]
},
{
"name": "Angle",
"type": "middle",
"fields": [
{"label": "Joint 1", "type": "slot"},
{"label": "Joint 2", "type": "slot"},
{"label": "Joint 3", "type": "slot"}
]
},
{ "name": "Export Node", "type": "end" },
{ "name": "like this", "type": "logic" }
{ "name": "End", "type": "end" },
{ "name": "Joint 1", "type": "logic" },
{ "name": "Joint 2", "type": "logic" },
{ "name": "Joint 3", "type": "logic" },
{ "name": "Joint 4", "type": "logic" },
{ "name": "Joint 5", "type": "logic" },
{ "name": "Joint 6", "type": "logic" },
{ "name": "Joint 7", "type": "logic" },
{ "name": "Joint 8", "type": "logic" },
{ "name": "Joint 9", "type": "logic" },
{ "name": "Joint 10", "type": "logic" },
{ "name": "Joint 11", "type": "logic" },
{ "name": "Joint 12", "type": "logic" },
{ "name": "Joint 13", "type": "logic" },
{ "name": "Joint 14", "type": "logic" },
{ "name": "Joint 15", "type": "logic" },
{ "name": "Joint 16", "type": "logic" },
{ "name": "Joint 17", "type": "logic" }
]
}
+166 -15
View File
@@ -3175,6 +3175,9 @@ class PuzzleBlock(QGraphicsPathItem):
self.parent_block = None
self.child_block = None
self.host_block = None
self.slots = {}
self.setFlags(
QGraphicsItem.GraphicsItemFlag.ItemIsMovable |
QGraphicsItem.GraphicsItemFlag.ItemIsSelectable |
@@ -3245,6 +3248,8 @@ class PuzzleBlock(QGraphicsPathItem):
self.inputs[key] = slot_label
self.original_inputs[key] = slot_label
self.slots[key] = None
self.proxy = QGraphicsProxyWidget(self)
self.proxy.setZValue(10)
self.proxy.setWidget(self.container)
@@ -3324,18 +3329,28 @@ class PuzzleBlock(QGraphicsPathItem):
def mousePressEvent(self, event):
# Only handle visual layering here. Do NOT detach links.
self._press_offset = event.pos()
self.setZValue(100)
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
if getattr(self, '_detach_host', None):
grab_offset = self._detach_grab_offset
self.setParentItem(None)
self.setPos(event.scenePos() - grab_offset)
self._detach_host = None
self._detach_grab_offset = None
# Drop it back down to standard Z-level
self.setZValue(0)
self.setZValue(20 if getattr(self, 'host_block', None) else 0)
super().mouseReleaseEvent(event)
def itemChange(self, change, value):
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
if self.scene() and self.scene().mouseGrabberItem() is self:
# 1. Sever Parent Link (Both ways)
@@ -3350,6 +3365,21 @@ class PuzzleBlock(QGraphicsPathItem):
self.child_block.parent_block = None # Tell child I'm gone
self.child_block = None # Forget my child
if getattr(self, 'host_block', None):
host = self.host_block
if not getattr(self, '_detach_host', None):
self._detach_host = host
self._detach_grab_offset = self._press_offset
for k, v in host.slots.items():
if v == self:
host.slots[k] = None
host.inputs[k].show()
break
self.host_block = None
return super().itemChange(change, value)
@@ -3507,13 +3537,8 @@ class TopologyCanvas(QGraphicsView):
except Exception as e:
print(f"DEBUG: Failed to parse: {e}")
# Spawn selection
if block_type == "value":
# new_block = ValueBlock(value="0")
print("Spawned ValueBlock (Placeholder)")
return # Exit early if not implemented to avoid setPos error
else:
new_block = PuzzleBlock(block_type, label, fields=fields)
new_block = PuzzleBlock(block_type, label, fields=fields)
self.scene.addItem(new_block)
@@ -3540,11 +3565,48 @@ class TopologyCanvas(QGraphicsView):
def keyPressEvent(self, event):
if event.key() == Qt.Key.Key_F7:
self.diagnostic_dump()
if event.key() == Qt.Key.Key_F8:
self.print_logical_graphs()
super().keyPressEvent(event)
def print_logical_graphs(self):
"""Crawls the logical links and prints all current stacks."""
print(f"\n{'='*60}\nCURRENT LOGICAL GRAPHS (F8 Triggered)")
blocks = [item for item in self.scene.items() if hasattr(item, 'b_type')]
# Heads are blocks with no parent AND no host (filtering out embedded logic blocks)
heads = [b for b in blocks if getattr(b, 'parent_block', None) is None and getattr(b, 'host_block', None) is None]
if not heads:
print("No standalone blocks or stacks found on canvas.")
print(f"{'='*60}\n")
return
for i, head in enumerate(heads):
chain = []
curr = head
while curr:
# Format: [Label (type)]
node_str = f"[{curr.label_text} ({curr.b_type})]"
# Check for embedded logic blocks and append them visually
if getattr(curr, 'slots', None):
embedded = [f"{k}: {v.label_text}" for k, v in curr.slots.items() if v]
if embedded:
node_str += f" [Slots -> {', '.join(embedded)}]"
chain.append(node_str)
curr = getattr(curr, 'child_block', None)
print(f"Stack {i + 1}: " + " -> ".join(chain))
print(f"{'='*60}\n")
def diagnostic_dump(self):
print(f"\n{'-'*20} LOGICAL STATE DUMP {'-'*20}")
print(f"\n{'-'*20} LOGICAL STATE DUMP (F7 Triggered) {'-'*20}")
blocks = [item for item in self.scene.items() if hasattr(item, 'data') and item.data(0) == "p_block"]
if not blocks:
@@ -3554,18 +3616,29 @@ class TopologyCanvas(QGraphicsView):
for i, block in enumerate(blocks):
parent_name = block.parent_block.label_text if block.parent_block else "None"
child_name = block.child_block.label_text if block.child_block else "None"
host_name = block.host_block.label_text if getattr(block, 'host_block', None) else "None"
# Check for 'Half-Broken' links (Asymmetry)
warning = ""
if block.parent_block and block.parent_block.child_block is not block:
warning = " [!] ASYMMETRY: Parent doesn't recognize this child"
warning += " [!] ASYMMETRY: Parent doesn't recognize this child."
if block.child_block and block.child_block.parent_block is not block:
warning = " [!] ASYMMETRY: Child doesn't recognize this parent"
warning += " [!] ASYMMETRY: Child doesn't recognize this parent."
if getattr(block, 'host_block', None) and block not in block.host_block.slots.values():
warning += " [!] ASYMMETRY: Host doesn't recognize this logic block in its slots."
print(f"[{i}] BLOCK: {block.label_text}")
print(f"[{i}] BLOCK: {block.label_text} ({block.b_type})")
print(f" - Parent: {parent_name}")
print(f" - Child: {child_name}{warning}")
print(f" - Selected: {block.isSelected()}")
print(f" - Child: {child_name}")
if block.b_type == "logic":
print(f" - Host: {host_name}")
elif hasattr(block, 'slots') and block.slots:
slots_info = {k: (v.label_text if v else "Empty") for k, v in block.slots.items()}
print(f" - Slots: {slots_info}")
if warning:
print(f" - WARNING:{warning}")
print(f"{'-'*60}\n")
@@ -3577,6 +3650,45 @@ class TopologyCanvas(QGraphicsView):
potential_targets = self.scene.items(mouse_pos)
target_block = None
if dragged_item.b_type == "logic":
for item in potential_targets:
if item is dragged_item: continue
# Safely locate the block object behind whatever was hit
host_candidate = None
if item.data(0) == "p_block":
host_candidate = item
else:
p = item.parentItem()
if p and p.data(0) == "p_block":
host_candidate = p
if host_candidate and hasattr(host_candidate, 'slots'):
for slot_key, slot_label in host_candidate.inputs.items():
# Check if this specific slot widget contains the mouse release point
proxy = host_candidate.proxy
# 2. Map from Scene -> Proxy -> Widget
# mapFromScene gives us the position relative to the proxy
point_in_proxy = proxy.mapFromScene(mouse_pos)
# mapFromParent translates from proxy space to the slot_label's internal space
local_mouse_pos = slot_label.mapFromParent(point_in_proxy)
# 3. Perform the hit test
if slot_label.rect().contains(local_mouse_pos.toPoint()):
# STRICTURE: Only proceed if the slot dictionary explicitly shows None
if host_candidate.slots.get(slot_key) is None:
return self.embed_logic_block(dragged_item, host_candidate, slot_key)
else:
print(f"[DEBUG] Slot {slot_key} is already occupied!")
return None
print("[DEBUG] SNAP FAILED: No empty slot found for logic block.")
return False
for item in potential_targets:
# Ignore the item we are dragging
if item is dragged_item:
@@ -3660,6 +3772,45 @@ class TopologyCanvas(QGraphicsView):
return False
def embed_logic_block(self, logic_block, host_block, slot_key):
# 1. Logical Link
host_block.slots[slot_key] = logic_block
logic_block.host_block = host_block
logic_block.current_slot = slot_key
# 2. Re-parent
logic_block.setParentItem(host_block)
# 3. Precise Coordinate Mapping
target_widget = host_block.inputs[slot_key]
proxy = host_block.proxy
# Get the rect of the QLineEdit relative to the proxy widget
widget_rect = proxy.subWidgetRect(target_widget)
# Map that rectangle's top-left point from "Proxy Space" to "Host Block Space"
# This accounts for title bars, margins, and internal layout shifts
slot_origin_in_host = proxy.mapToItem(host_block, widget_rect.topLeft())
# 4. Alignment Calculation
# X: Set a fixed margin from the left edge of the host block
final_x = 10
# Y: Center the orange block vertically within the slot's actual height
# Use boundingRect().height() to ensure we have the real rendered size
logic_height = logic_block.boundingRect().height()
y_center_offset = (target_widget.height() - logic_height) / 2
final_y = slot_origin_in_host.y() + y_center_offset
# 5. Apply Position
logic_block.setPos(final_x, final_y)
logic_block.setZValue(host_block.zValue() + 1)
return True
def connect_blocks(self, parent, child, mode="downward"):
"""Links blocks and forces physical alignment based on a stable anchor."""
print(f"DEBUG: Entering connect_blocks | Parent: {parent.label_text} | Child: {child.label_text} | Mode: {mode}")