config editor further progress
This commit is contained in:
+47
-6
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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,12 +3537,7 @@ 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)
|
||||
|
||||
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}")
|
||||
|
||||
Reference in New Issue
Block a user