config editor further progress
This commit is contained in:
+47
-6
@@ -2,16 +2,57 @@
|
|||||||
"individual": [
|
"individual": [
|
||||||
{ "name": "Start Node", "type": "begin" },
|
{ "name": "Start Node", "type": "begin" },
|
||||||
{
|
{
|
||||||
"name": "Hybrid Processor",
|
"name": "Average",
|
||||||
"type": "middle",
|
"type": "middle",
|
||||||
"fields": [
|
"fields": [
|
||||||
{"label": "Threshold", "type": "number"},
|
{"label": "Joint 1", "type": "slot"},
|
||||||
{"label": "Tag", "type": "string"},
|
{"label": "Joint 2", "type": "slot"}
|
||||||
{"label": "Sub-Logic", "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.parent_block = None
|
||||||
self.child_block = None
|
self.child_block = None
|
||||||
|
|
||||||
|
self.host_block = None
|
||||||
|
self.slots = {}
|
||||||
|
|
||||||
self.setFlags(
|
self.setFlags(
|
||||||
QGraphicsItem.GraphicsItemFlag.ItemIsMovable |
|
QGraphicsItem.GraphicsItemFlag.ItemIsMovable |
|
||||||
QGraphicsItem.GraphicsItemFlag.ItemIsSelectable |
|
QGraphicsItem.GraphicsItemFlag.ItemIsSelectable |
|
||||||
@@ -3245,6 +3248,8 @@ class PuzzleBlock(QGraphicsPathItem):
|
|||||||
self.inputs[key] = slot_label
|
self.inputs[key] = slot_label
|
||||||
self.original_inputs[key] = slot_label
|
self.original_inputs[key] = slot_label
|
||||||
|
|
||||||
|
self.slots[key] = None
|
||||||
|
|
||||||
self.proxy = QGraphicsProxyWidget(self)
|
self.proxy = QGraphicsProxyWidget(self)
|
||||||
self.proxy.setZValue(10)
|
self.proxy.setZValue(10)
|
||||||
self.proxy.setWidget(self.container)
|
self.proxy.setWidget(self.container)
|
||||||
@@ -3324,18 +3329,28 @@ class PuzzleBlock(QGraphicsPathItem):
|
|||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
# Only handle visual layering here. Do NOT detach links.
|
# Only handle visual layering here. Do NOT detach links.
|
||||||
|
self._press_offset = event.pos()
|
||||||
self.setZValue(100)
|
self.setZValue(100)
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, 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
|
# 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)
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
|
|
||||||
def itemChange(self, change, value):
|
def itemChange(self, change, value):
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
if self.scene() and self.scene().mouseGrabberItem() is self:
|
if self.scene() and self.scene().mouseGrabberItem() is self:
|
||||||
|
|
||||||
# 1. Sever Parent Link (Both ways)
|
# 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.parent_block = None # Tell child I'm gone
|
||||||
self.child_block = None # Forget my child
|
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)
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
|
|
||||||
@@ -3507,13 +3537,8 @@ class TopologyCanvas(QGraphicsView):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Failed to parse: {e}")
|
print(f"DEBUG: Failed to parse: {e}")
|
||||||
|
|
||||||
# Spawn selection
|
|
||||||
if block_type == "value":
|
new_block = PuzzleBlock(block_type, label, fields=fields)
|
||||||
# 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)
|
self.scene.addItem(new_block)
|
||||||
|
|
||||||
@@ -3540,11 +3565,48 @@ class TopologyCanvas(QGraphicsView):
|
|||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == Qt.Key.Key_F7:
|
if event.key() == Qt.Key.Key_F7:
|
||||||
self.diagnostic_dump()
|
self.diagnostic_dump()
|
||||||
|
if event.key() == Qt.Key.Key_F8:
|
||||||
|
self.print_logical_graphs()
|
||||||
super().keyPressEvent(event)
|
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):
|
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"]
|
blocks = [item for item in self.scene.items() if hasattr(item, 'data') and item.data(0) == "p_block"]
|
||||||
|
|
||||||
if not blocks:
|
if not blocks:
|
||||||
@@ -3554,18 +3616,29 @@ class TopologyCanvas(QGraphicsView):
|
|||||||
for i, block in enumerate(blocks):
|
for i, block in enumerate(blocks):
|
||||||
parent_name = block.parent_block.label_text if block.parent_block else "None"
|
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"
|
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)
|
# Check for 'Half-Broken' links (Asymmetry)
|
||||||
warning = ""
|
warning = ""
|
||||||
if block.parent_block and block.parent_block.child_block is not block:
|
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:
|
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" - Parent: {parent_name}")
|
||||||
print(f" - Child: {child_name}{warning}")
|
print(f" - Child: {child_name}")
|
||||||
print(f" - Selected: {block.isSelected()}")
|
|
||||||
|
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")
|
print(f"{'-'*60}\n")
|
||||||
|
|
||||||
|
|
||||||
@@ -3577,6 +3650,45 @@ class TopologyCanvas(QGraphicsView):
|
|||||||
potential_targets = self.scene.items(mouse_pos)
|
potential_targets = self.scene.items(mouse_pos)
|
||||||
target_block = None
|
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:
|
for item in potential_targets:
|
||||||
# Ignore the item we are dragging
|
# Ignore the item we are dragging
|
||||||
if item is dragged_item:
|
if item is dragged_item:
|
||||||
@@ -3660,6 +3772,45 @@ class TopologyCanvas(QGraphicsView):
|
|||||||
return False
|
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"):
|
def connect_blocks(self, parent, child, mode="downward"):
|
||||||
"""Links blocks and forces physical alignment based on a stable anchor."""
|
"""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}")
|
print(f"DEBUG: Entering connect_blocks | Parent: {parent.label_text} | Child: {child.label_text} | Mode: {mode}")
|
||||||
|
|||||||
Reference in New Issue
Block a user