Skip to content

Commit

Permalink
Merge branch 'development' into main
Browse files Browse the repository at this point in the history
Alpha Release Candidate: v0.0.2-alpha
  • Loading branch information
jgladwig committed Feb 14, 2022
2 parents c6e98c7 + 934be52 commit 4a2dd2e
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 53 deletions.
164 changes: 128 additions & 36 deletions backend/editor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ class Protocol(AuthorizedModel):
rdf_file = models.FileField(upload_to='editor/protocols/', null=True)

def to_rdf_string(self, format="nt"):
return Protocol.to_paml(self.graph).document.write_string(format)
return Protocol.to_paml(self.name, self.graph).document.write_string(format)

@classmethod
def to_paml(cls, graph):
return PAMLMapping.graph_to_protocol(graph)
def to_paml(cls, name, graph):
return PAMLMapping.graph_to_protocol(name, graph)


class Primitive(models.Model):
Expand Down Expand Up @@ -67,35 +67,43 @@ def _load_paml():
paml.import_library(lib)
return paml, uml

def graph_to_protocol(graph):
def graph_to_protocol(name, graph, doc=None):
"""
Convert an editor graph to a PAML protocol.
"""
paml, uml = PAMLMapping._load_paml()

doc = sbol3.Document()
if not doc:
doc = sbol3.Document()
sbol3.set_namespace('https://bbn.com/scratch/')

name = "".join([c for c in graph['id'] if c.isalpha() or c.isdecimal()])
protocol_name = "".join([c for c in name if c.isalpha() or c.isdecimal()])

protocol: paml.Protocol = paml.Protocol(name)
protocol.name = name
protocol: paml.Protocol = paml.Protocol(protocol_name)
protocol.name = protocol_name
# FIXME protocol.description = DOCSTRING
doc.add(protocol)

# Create protcool nodes

# Collect parameters for call behaviors
parameters = PAMLMapping.map_parameters(graph)


# Create protcol nodes
node_to_call_behavior = {}
for _, node in graph["nodes"].items():
node_to_call_behavior[node['id']] = PAMLMapping.node_to_call_behavior(protocol, graph, node)
node_to_call_behavior[node['id']] = PAMLMapping.node_to_call_behavior(protocol, graph, node, parameters)

# Create protocol edges
for _, node in graph["nodes"].items():
PAMLMapping.make_incoming_edges(protocol, graph, node, node_to_call_behavior)

# protocol.to_dot().render(view=True)

return protocol

def make_incoming_edges(protocol, graph, node, node_to_call_behavior,
skipped_nodes=["Input"]):
skipped_nodes=["Input", "Parameter"]):
"""
Add incoming edges to protocol node from graph edges
"""
Expand All @@ -108,26 +116,49 @@ def make_incoming_edges(protocol, graph, node, node_to_call_behavior,
for source in input_pin["connections"]:
source_node_id = source['node']
source_output_id = source["output"]
source_call_behavior = node_to_call_behavior[source_node_id]
source_pin = source_call_behavior.output_pin(source_output_id)
cba_input_pin = protocol_node.input_pin(input_pin_id)
protocol.use_value(source_pin, cba_input_pin)

if input_pin_id == "Start":
# Control Flow for Ordering
protocol.order(node_to_call_behavior[source_node_id], protocol_node)
else:
# Data Flow
cba_input_pin = protocol_node.input_pin(input_pin_id)
source_call_behavior = node_to_call_behavior[source_node_id]

if isinstance(source_call_behavior, uml.ActivityParameterNode):
protocol.use_value(source_call_behavior, cba_input_pin)
elif isinstance(source_call_behavior, uml.CallBehaviorAction):
# ActivityNode pin to pin connection
source_pin = source_call_behavior.output_pin(source_output_id)
protocol.use_value(source_pin, cba_input_pin)
else: # Passthrough and Failure cases
if source_node_id not in node_to_call_behavior or \
not node_to_call_behavior[source_node_id]:
pass # This is a parameter that was already made into a value pin
else:
raise PAMLMappingException(f"Unable to process ObjectFlow for pin: {input_pin_id}")

elif node["name"] == "Output":
#source = graph["nodes"][str(node['id'])]['inputs']['input']['connections'][0] #FIXME assumes that the source is present
for input_pin_id, input_pin in node['inputs'].items():
for source in input_pin["connections"]:
source_node_id = source['node']
source_output_id = source["output"]
# TODO ignoring source["data"] for now
source_call_behavior = node_to_call_behavior[str(source_node_id)]
source_pin = source_call_behavior.get_output(source_output_id)
node.use_value(source_pin, node)
if len(input_pin["connections"]) == 0:
## Protocol doesn't connect to output, but can still make the output
output = protocol.add_output(node["data"]["name"], PAMLMapping.map_type(node["data"]["type"]))
else:
for source in input_pin["connections"]:
source_node_id = source['node']
source_output_id = source["output"]
# TODO ignoring source["data"] for now
source_call_behavior = node_to_call_behavior[source_node_id]
source_pin = source_call_behavior.output_pin(source_output_id)
output = protocol.designate_output(node["data"]["name"], PAMLMapping.map_type(node["data"]["type"]), source_pin)
protocol.order(source_call_behavior, output)
elif node["name"] in skipped_nodes:
pass
else:
raise PAMLMappingException(f"Do not know how to make incomeing edges for node: {node}")
raise PAMLMappingException(f"Do not know how to make incoming edges for node: {node}")

def node_to_call_behavior(protocol, graph, node):
def node_to_call_behavior(protocol, graph, node, parameters):
"""
Convert a node representing a protocol activity into a call behavior
"""
Expand All @@ -140,10 +171,30 @@ def node_to_call_behavior(protocol, graph, node):
except Exception as e:
pass

subprotocol = None
if not primitive:
try:
subprotocol = Protocol.objects.get(name=node["name"])
except Exception as e:
pass

if primitive:
return protocol.execute_primitive(primitive)
input_pin_map = {}
if node["id"] in parameters:
input_pin_map = parameters[node["id"]]

return protocol.execute_primitive(primitive, **input_pin_map)
elif "isModule" in node["data"] and node["data"]["isModule"]:
# Subprotocol
input_pin_map = {}
if node["id"] in parameters:
input_pin_map = parameters[node["id"]]
paml_subprotocol = protocol.document.find(f"{sbol3.get_namespace()}{node['name']}")
if not paml_subprotocol:
paml_subprotocol = PAMLMapping.graph_to_protocol(node["name"], subprotocol.graph, doc=protocol.document)
return protocol.execute_primitive(paml_subprotocol, **input_pin_map)
elif node["name"] == "Input":
name = "foo" # node["data"]['name']
name = node["data"]['name']
node_type = sbol3.Identified # eval(node["data"]['type'])
optional = True # node["data"]['optional']
default_value = None # eval(node["data"]['default'])
Expand All @@ -155,21 +206,62 @@ def node_to_call_behavior(protocol, graph, node):
)
return param
elif node["name"] == "Output":
source = graph["nodes"][str(node['id'])]['inputs']['input']['connections'][0] #FIXME assumes that the source is present

param = protocol.add_output(
node["data"]['name'],
node["data"]['type']
)
param_node = uml.ActivityParameterNode(parameter=param)
protocol.nodes.append(param_node)

return param
sources = graph["nodes"][str(node['id'])]['inputs']['input']['connections']

if len(sources) > 1:
raise PAMLMappingException(f"There is more than one Activity linked to the Output node: {node}")
# elif len(sources) == 1:
# param = protocol.add_output(
# node["data"]['name'],
# node["data"]['type']
# )
# # param_node = uml.ActivityParameterNode(parameter=param)
# # protocol.nodes.append(param_node)

# return param
# Do not create the protocol output here, handle when looking edges. The designate_output function will create an output.
else:
return None
elif node["name"] == "Parameter":
return None
else:
raise PAMLMappingException(f"Do not know how to covert node: {node}")

def map_parameters(graph):
## Find the Parameter nodes and make a mapping from Activity nodes to parameter, value pairs
parameters = {}
for _, node in graph["nodes"].items():
if node["name"] == "Parameter":
parameters = PAMLMapping.map_parameter(node, graph, parameters) # side effect setting parameters
return parameters

def map_parameter(node, graph, parameters):
## Need to create a parameter object and save for populating the Activities it connects with
for activity_node in node["outputs"]["output"]["connections"]:
# This parameter node is a parameter for the activity_node
parameter_value = PAMLMapping.map_value(node["data"]["value"], node["data"]["type"])
activity_graph_node = graph["nodes"][str(activity_node["node"])]

if activity_graph_node["id"] not in parameters:
parameters[activity_graph_node["id"]] = {}

parameters[activity_graph_node["id"]][activity_node["input"]] = parameter_value

return parameters


def map_value(value, value_type):
## Take the string value and the string type, and convert it into paramaterValuePair
import uml

return uml.literal(value)

def map_type(string_type):
"""
Map the type specified in the string to a PAML type
"""
return sbol3.Identified


def reload_models():
paml, _ = PAMLMapping._load_paml()
Expand Down
30 changes: 19 additions & 11 deletions frontend/web/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,61 @@
height: 100%;
}

.node {
.editor .node {
background: #CAEBF2;
border-color: #A9A9A9;
}

.node .title {
.editor .node .title {
color: black;
}

.editor .node:focus {
color: yellow;
}

.connection .main-path {
stroke: #A9A9A9;
}

.node .input {
.editor .node .input {
color: black;
}

.node .input-title {
.editor .node .input-title {
color: black;
}

.node .output-title {
.editor .node .output-title {
color: black;
}

.node[name=Input] {
.editor .node[name=Input] {
background: #EFEFEF;

}

.node[name=Output] {
.editor .node[name=Output] {
/* background: #A9A9A9; */
background: #EFEFEF;
}


.node[name=Parameter] {
.editor .node[name=Parameter] {
/* background: #A9A9A9; */
background: #FF3B3F;
}

.node[ismodule="true"] {
.editor .node[ismodule="true"] {
background: #EFEFEF;
}


.node .socket.timepoint {
.editor .node .socket.timepoint {
background: #49d30f;
}

.node .socket.number {
.editor .node .socket.number {
background: gray;
}

Expand Down
8 changes: 4 additions & 4 deletions frontend/web/src/editor/components/Control.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class InputComponent extends Rete.Component {
var out1 = new Rete.Output("output", "", numSocket);

var ctrlName = Object.keys(node.data).find(k => k === "name")
var value = ctrlName ? node.data[ctrlName] : "New Input";
var value = ctrlName ? node.data[ctrlName] : "<New Input>";
var ctrl = new TextControl(this.editor, "name", value);

var typeName = Object.keys(node.data).find(k => k === "type")
Expand Down Expand Up @@ -119,14 +119,14 @@ export class ParameterComponent extends Rete.Component {
var out1 = new Rete.Output("output", "", numSocket);

var ctrlName = Object.keys(node.data).find(k => k === "name")
var value = ctrlName ? node.data[ctrlName] : "New Parameter";
var value = ctrlName ? node.data[ctrlName] : "<New Parameter>";
var ctrl = new TextControl(this.editor, "name", value);

var typeValue = "type" in node.data ? node.data["type"] : "";
var typectrl = new ListControl(this.editor, "type", typeValue, this.dataTypes);


var pvalue = "value" in node.data ? node.data["value"] : "";
var pvalue = "value" in node.data ? node.data["value"] : "<My Value>";
var parameterValue = new TextControl(this.editor, "value", pvalue)

return node
Expand Down Expand Up @@ -160,7 +160,7 @@ export class OutputComponent extends Rete.Component {
var inp = new Rete.Input("input", "Value", numSocket);

var ctrlName = Object.keys(node.data).find(k => k === "name")
var value = ctrlName ? node.data[ctrlName] : "New Output";
var value = ctrlName ? node.data[ctrlName] : "<New Output>";
var ctrl = new TextControl(this.editor, "name", value);

var typeValue = "type" in node.data ? node.data["type"] : "";
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/src/editor/components/ProtocolInspector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class ProtocolInspectorGroup extends React.Component {
let emptyTab = (
<Nav.Item>
<SplitButton size="sm" variant="outline-primary" title={emptyProtocol} onClick={() => this.props.editor.displayNewProtocol()}>
<Dropdown.Item key={"load-rete-graph-file"} onClick={() => this.fileInput.current.click()}>Load Rete Graph</Dropdown.Item>
<Dropdown.Item key={"load-rete-graph-file"} onClick={() => this.fileInput.current.click()}>Import from Rete Graph</Dropdown.Item>
</SplitButton>
</Nav.Item>
)
Expand Down
8 changes: 7 additions & 1 deletion frontend/web/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,18 @@

#editor-protocol-tabs .btn-sm {
color: black;
}
border-color: black;

}

#editor-protocol-tabs .dropdown.current-protocol {
background-color: #FF3B3F;
}
#editor-protocol-tabs .dropdown.current-protocol::selection {
background-color: #FF3B3F;
}


#editor-protocol-tabs .dropdown.current-protocol button {
font-weight: 900;
}
Expand Down

0 comments on commit 4a2dd2e

Please sign in to comment.