Skip to content

Commit

Permalink
RFC: Edge properties
Browse files Browse the repository at this point in the history
This is a bunch of modifications, in no particular order, that would
allow creating edge properties. These properties are not known by the
runtime (4.0 style), only by GRC for rendering.

How to try:
- Install this branch
- Open an RFNoC flow graph (e.g., rfnoc_radio_loopback.grc). Then
  right-click on one of the green arrows. You will be able to set
  a Boolean property of this connection.
- When you render the flow graph, it'll have access to the params. It's
  not pretty, and doesn't even work yet.

Open issues:

- There is a problem where the edge properties don't stick. When I exit
  the properties dialog, the values seem fine, but when I render, or
  when I re-open the properties dialog box, then values are back to
  default.
- I would like to be able to use variable notation in the connection
  template, just like in the 'make' templates.

Questions for GRC folks:
- Is this approach sensible in principle?
- If yes, any ideas how I can fix the remaining issues (see above)?
  • Loading branch information
mbr0wn committed Oct 4, 2023
1 parent e2f1477 commit 17aa951
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 12 deletions.
2 changes: 1 addition & 1 deletion gr-uhd/examples/grc/rfnoc_radio_loopback.grc
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,4 @@ connections:

metadata:
file_format: 1
grc_version: v3.11.0.0git-549-g9a9a82e3
grc_version: v3.11.0.0git-551-ge2f14775
8 changes: 7 additions & 1 deletion gr-uhd/grc/rfnoc.domain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ multiple_connections_per_output: false

templates:
- type: [rfnoc, rfnoc]
connect: self.rfnoc_graph.connect(self.${ source.parent_block.name }.get_unique_id(), ${ source.key }, self.${ sink.parent_block.name }.get_unique_id(), ${ sink.key }, False)
connect: self.rfnoc_graph.connect(self.${ source.parent_block.name }.get_unique_id(), ${ source.key }, self.${ sink.parent_block.name }.get_unique_id(), ${ sink.key }, False) \# ${not(params['is_back_edge'].get_value())}
parameters:
- id: is_back_edge
label: 'Back-edge'
dtype: bool
options: ['True', 'False']
default: 'False'
30 changes: 29 additions & 1 deletion grc/core/Connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@
"""

import collections

from .base import Element
from .Constants import ALIASES_OF
from .utils.descriptors import lazy_property


class Connection(Element):
"""
Stores information about a connection between two block ports. This class
knows:
- Where the source and sink ports are (on which blocks)
- The domain (message, stream, ...)
- Which parameters are associated with this connection
"""

is_connection = True
documentation = {'': ''}

def __init__(self, parent, source, sink):
"""
Make a new connection given the parent and 2 ports.
Args:
flow_graph: the parent of this element
parent: the parent of this element (a flow graph)
source: a port (any direction)
sink: a port (any direction)
@throws Error cannot make connection
Expand All @@ -41,6 +50,16 @@ def __init__(self, parent, source, sink):
self.source_port = source
self.sink_port = sink

# Unlike the blocks, connection parameters are defined in the connection
# domain definition files, as all connections within the same domain
# share the same properties.
param_factory = self.parent_platform.make_param
conn_parameters = self.parent_platform.connection_params.get(self.type, {})
self.params = collections.OrderedDict(
(data['id'], param_factory(parent=self, **data))
for data in conn_parameters
)

def __str__(self):
return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
self.source_block, self.source_port, self.sink_block, self.sink_port,
Expand All @@ -57,6 +76,10 @@ def __hash__(self):
def __iter__(self):
return iter((self.source_port, self.sink_port))

def children(self):
""" This includes the connection parameters """
return self.params.values()

@lazy_property
def source_block(self):
return self.source_port.parent_block
Expand All @@ -79,6 +102,11 @@ def enabled(self):
"""
return self.source_block.enabled and self.sink_block.enabled

@property
def label(self):
""" Returns a label for dialogs """
return 'Connection'

def validate(self):
"""
Validate the connections.
Expand Down
11 changes: 10 additions & 1 deletion grc/core/generator/top_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ def is_duplicate(l):
return output

def _blocks(self):
"""
Returns a list of tuples: (block, block_make)
'block' contains a reference to the block object.
'block_make' contains the pre-rendered string for the 'make' part of the
block.
"""
fg = self._flow_graph
parameters = fg.get_parameters()

Expand Down Expand Up @@ -318,8 +325,10 @@ def by_domain_and_blocks(c):
for con in sorted(connections, key=by_domain_and_blocks):
template = templates[con.type]
if con.source_port.dtype != 'bus':
print(con.params)
print([p.get_value() for p in con.params.values()])
code = template.render(
make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port, params=con.params)
rendered.append(code)
else:
# Bus ports need to iterate over the underlying connections and then render
Expand Down
2 changes: 2 additions & 0 deletions grc/core/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(self, *args, **kwargs):
self.domains = {}
self.connection_templates = {}
self.cpp_connection_templates = {}
self.connection_params = {}

self._block_categories = {}
self._auto_hier_block_generate_chain = set()
Expand Down Expand Up @@ -283,6 +284,7 @@ def load_domain_description(self, data, file_path):
'connect', '')
self.cpp_connection_templates[connection_id] = connection.get(
'cpp_connect', '')
self.connection_params[connection_id] = connection.get('parameters', {})

def load_category_tree_description(self, data, file_path):
"""Parse category tree file and add it to list"""
Expand Down
32 changes: 30 additions & 2 deletions grc/gui/Application.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from .PropsDialog import PropsDialog

from ..core import Messages
from ..core.Connection import Connection
from ..core.blocks import Block


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -551,7 +553,8 @@ def flow_graph_update(fg=flow_graph):
##################################################
elif action == Actions.BLOCK_PARAM_MODIFY:
selected_block = args[0] if args[0] else flow_graph.selected_block
if selected_block:
selected_conn = args[0] if args[0] else flow_graph.selected_connection
if selected_block and isinstance(selected_block, Block):
self.dialog = PropsDialog(self.main_window, selected_block)
response = Gtk.ResponseType.APPLY
while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit
Expand All @@ -571,6 +574,27 @@ def flow_graph_update(fg=flow_graph):
Actions.ELEMENT_SELECT()
self.dialog.destroy()
self.dialog = None
elif selected_conn and isinstance(selected_conn, Connection):
self.dialog = PropsDialog(self.main_window, selected_conn)
response = Gtk.ResponseType.APPLY
while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit
response = self.dialog.run()
if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
page.state_cache.save_new_state(
flow_graph.export_data())
# Following line forces a complete update of io ports
flow_graph_update()
page.saved = False
if response in (Gtk.ResponseType.REJECT, Gtk.ResponseType.ACCEPT):
n = page.state_cache.get_current_state()
flow_graph.import_data(n)
flow_graph_update()
if response == Gtk.ResponseType.APPLY:
# null action, that updates the main window
Actions.ELEMENT_SELECT()
self.dialog.destroy()
self.dialog = None
log.info(f"con params: {[p.get_value() for p in selected_conn.params.values()]}")
elif action == Actions.EXTERNAL_UPDATE:
page.state_cache.save_new_state(flow_graph.export_data())
flow_graph_update()
Expand Down Expand Up @@ -811,11 +835,15 @@ def flow_graph_update(fg=flow_graph):

selected_blocks = list(flow_graph.selected_blocks())
selected_block = selected_blocks[0] if selected_blocks else None
selected_connections = list(flow_graph.selected_connections())
selected_connection = selected_connections[0] if selected_connections else None
log.debug(f"selected block: {selected_block}")
log.debug(f"selected conns: {selected_connection}")

# update general buttons
Actions.ERRORS_WINDOW_DISPLAY.set_enabled(not flow_graph.is_valid())
Actions.ELEMENT_DELETE.set_enabled(bool(flow_graph.selected_elements))
Actions.BLOCK_PARAM_MODIFY.set_enabled(bool(selected_block))
Actions.BLOCK_PARAM_MODIFY.set_enabled(bool(selected_block) or bool(selected_connection))
Actions.BLOCK_ROTATE_CCW.set_enabled(bool(selected_blocks))
Actions.BLOCK_ROTATE_CW.set_enabled(bool(selected_blocks))
# update alignment options
Expand Down
18 changes: 12 additions & 6 deletions grc/gui/PropsDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def __init__(self, parent, block):
(Constants.MIN_DIALOG_WIDTH, Constants.MIN_DIALOG_HEIGHT)
))

# Careful: 'block' can also be a connection! The naming is because
# property dialogs for connections were added much later.
self._block = block
self._hash = 0
self._config = parent.config
Expand Down Expand Up @@ -212,7 +214,9 @@ def _update_docs_page(self):
pos = buf.get_end_iter()

# Add link to wiki page for this block, at the top, as long as it's not an OOT block
if self._block.category and self._block.category[0] == "Core":
if self._block.is_connection:
self._docs_link.set_markup('Connection')
elif self._block.category and self._block.category[0] == "Core":
note = "Wiki Page for this Block: "
prefix = self._config.wiki_block_docs_url_prefix
suffix = self._block.label.replace(" ", "_")
Expand All @@ -236,11 +240,13 @@ def _update_docs_page(self):
buf.insert(pos, '\n')

# if given the current parameters an exact match can be made
block_constructor = self._block.templates.render(
'make').rsplit('.', 2)[-1]
block_class = block_constructor.partition('(')[0].strip()
if block_class in docstrings:
docstrings = {block_class: docstrings[block_class]}
block_templates = getattr(self._block, 'templates', None)
if block_templates:
block_constructor = block_templates.render(
'make').rsplit('.', 2)[-1]
block_class = block_constructor.partition('(')[0].strip()
if block_class in docstrings:
docstrings = {block_class: docstrings[block_class]}

# show docstring(s) extracted from python sources
for cls_name, docstring in docstrings.items():
Expand Down
19 changes: 19 additions & 0 deletions grc/gui/canvas/flowgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,15 @@ def selected_blocks(self):
"""
return (e for e in self.selected_elements.copy() if e.is_block)

def selected_connections(self):
"""
Get a group of selected connections.
Returns:
sub set of connections in this flow graph
"""
return (e for e in self.selected_elements.copy() if e.is_connection)

@property
def selected_block(self):
"""
Expand All @@ -689,6 +698,16 @@ def selected_block(self):
"""
return next(self.selected_blocks(), None)

@property
def selected_connection(self):
"""
Get the selected connection
Returns:
a connection or None
"""
return next(self.selected_connections(), None)

def get_selected_elements(self):
"""
Get the group of selected elements.
Expand Down

0 comments on commit 17aa951

Please sign in to comment.