diff --git a/vscode-antimony/all-requirements.txt b/vscode-antimony/all-requirements.txt
index a36641665..0fdd6d23c 100644
--- a/vscode-antimony/all-requirements.txt
+++ b/vscode-antimony/all-requirements.txt
@@ -19,3 +19,5 @@ bioservices==1.8.3
# ols_client==0.0.9
AMAS-sb==0.0.1
orjson==3.8.0
+SBMLDiagrams
+tellurium
\ No newline at end of file
diff --git a/vscode-antimony/package.json b/vscode-antimony/package.json
index 5188d797c..2f282c6e2 100644
--- a/vscode-antimony/package.json
+++ b/vscode-antimony/package.json
@@ -29,6 +29,7 @@
"onCommand:antimony.switchIndicationOff",
"onCommand:antimony.convertAntimonyToSBML",
"onCommand:antimony.convertSBMLToAntimony",
+ "onCommand:antimony.convertAntimonyToDiagram",
"onCommand:antimony.startSBMLWebview",
"onCommand:antimony.startAntimonyWebview",
"onCustomEditor:antimony.sbmlEditor",
@@ -122,6 +123,10 @@
"command": "antimony.convertSBMLToAntimony",
"title": "Convert to Antimony"
},
+ {
+ "command": "antimony.convertAntimonyToDiagram",
+ "title": "Convert to Diagram"
+ },
{
"command": "antimony.startSBMLWebview",
"title": "preview SBML",
@@ -189,6 +194,12 @@
"title": "Convert to Antimony",
"group": "1_modification",
"when": "editorLangId == xml"
+ },
+ {
+ "command": "antimony.convertAntimonyToDiagram",
+ "title": "Convert to Diagram",
+ "group": "1_modification",
+ "when": "editorLangId == antimony"
}
],
"editor/title": [
diff --git a/vscode-antimony/src/extension.ts b/vscode-antimony/src/extension.ts
index f81b69b22..fcad1dab8 100644
--- a/vscode-antimony/src/extension.ts
+++ b/vscode-antimony/src/extension.ts
@@ -130,6 +130,13 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('antimony.convertSBMLToAntimony',
(...args: any[]) => convertSBMLToAntimony(context, args)));
+ // SBMLDiagram
+ context.subscriptions.push(
+ vscode.commands.registerCommand('antimony.convertAntimonyToDiagram',
+ (...args: any[]) => {
+ convertAntimonyToDiagram(context, args);
+ }));
+
// custom editor
context.subscriptions.push(await SBMLEditorProvider.register(context, client));
context.subscriptions.push(await AntimonyEditorProvider.register(context, client));
@@ -281,6 +288,84 @@ async function checkConversionResult(result, type) {
}
}
+async function convertAntimonyToDiagram(context: vscode.ExtensionContext, args: any[]) {
+ if (!client) {
+ utils.pythonInterpreterError();
+ return;
+ }
+ await client.onReady();
+
+ await vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
+
+ const doc = vscode.window.activeTextEditor.document;
+ const uri = doc.uri.toString();
+
+ let speciesStr;
+ vscode.commands.executeCommand('antimony.getDiagramQuickpick', uri).then(async (result) => {
+ speciesStr = result;
+ let speciesList = speciesStr.species_list.split(' ');
+ let selectedSpeciesList = await vscode.window.showQuickPick(speciesList, {canPickMany: true, placeHolder: 'select species to include in your diagram'});
+ if (selectedSpeciesList && selectedSpeciesList.length === 0) {
+ vscode.window.showErrorMessage('Please select at least one species!');
+ } else {
+ const options: vscode.OpenDialogOptions = {
+ openLabel: "Select",
+ canSelectFolders: true,
+ canSelectFiles: false,
+ canSelectMany: false,
+ filters: {
+ 'Images': ['png']
+ },
+ title: "Select a location to save your SBML diagram"
+ };
+ vscode.window.showOpenDialog(options).then(fileUri => {
+ if (fileUri && fileUri[0]) {
+ let diagram;
+ vscode.commands.executeCommand('antimony.antFiletoDiagram', vscode.window.activeTextEditor.document,
+ fileUri[0].fsPath, selectedSpeciesList).then(async (result) => {
+ let error = await checkSBMLDiagramResult(result);
+ diagram = result;
+ if (!diagram.error) {
+ const panel = vscode.window.createWebviewPanel(
+ 'antimony',
+ 'SBMLDiagram',
+ vscode.ViewColumn.Two,
+ {
+ localResourceRoots: [vscode.Uri.file(path.dirname(diagram.file))]
+ }
+ );
+ const pngSrc = panel.webview.asWebviewUri(vscode.Uri.file(diagram.file));
+ panel.webview.html = getWebviewContent(pngSrc);
+ }
+ });
+ }
+ });
+ }
+ });
+}
+
+function getWebviewContent(uri: vscode.Uri) {
+ return `
+
+
+
+
+ SBMLDiagram
+
+
+
+
+ `;
+ }
+
+async function checkSBMLDiagramResult(result) {
+ if (result.error) {
+ vscode.window.showErrorMessage(`Could not convert file to diagram: ${result.error}`);
+ } else {
+ vscode.window.showInformationMessage(`${result.msg}`);
+ }
+}
+
async function createAnnotationDialog(context: vscode.ExtensionContext, args: any[]) {
// wait till client is ready, or the Python server might not have started yet.
// note: this is necessary for any command that might use the Python language server.
@@ -290,7 +375,6 @@ async function createAnnotationDialog(context: vscode.ExtensionContext, args: an
}
await client.onReady();
await vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
-
// dialog for annotation
const selection = vscode.window.activeTextEditor.selection;
diff --git a/vscode-antimony/src/server/main.py b/vscode-antimony/src/server/main.py
index 25b3a2862..78c29a8e0 100644
--- a/vscode-antimony/src/server/main.py
+++ b/vscode-antimony/src/server/main.py
@@ -36,6 +36,8 @@
import time
from AMAS import recommender, species_annotation
from bioservices import ChEBI
+import SBMLDiagrams
+import tellurium as te
# TODO remove this for production
logging.basicConfig(filename='vscode-antimony-dep.log', filemode='w', level=logging.DEBUG)
@@ -149,6 +151,47 @@ def sbml_file_to_ant_file(ls: LanguageServer, args):
'file': full_path_name
}
+@server.thread()
+@server.command('antimony.getDiagramQuickpick')
+def ant_file_to_sbml_file(ls: LanguageServer, args):
+ uri = args[0]
+ doc = server.workspace.get_document(uri)
+ antfile_cache = get_antfile(doc)
+ reaction_list = antfile_cache.analyzer.reaction_list
+ species_set = set()
+ for species_names, reaction_part_str in reaction_list:
+ species_set.update(species_names)
+ species_list = list(species_set)
+ species_list.sort()
+ return {
+ 'species_list': " ".join(species_list)
+ }
+
+@server.thread()
+@server.command('antimony.antFiletoDiagram')
+def ant_file_to_sbml_file(ls: LanguageServer, args):
+ ant = args[0].fileName
+ output_dir = args[1]
+ selected_species_list = args[2]
+ model_str = 'model *temp()\n'
+ reaction_list = antfile_cache.analyzer.reaction_list
+ for react_prod_list, reaction_str in reaction_list:
+ if any((match := item) in react_prod_list for item in selected_species_list):
+ model_str += reaction_str + '\n'
+ model_str += 'end'
+ r = te.loada(model_str)
+ sbmlStr = r.getSBML()
+ df = SBMLDiagrams.load(sbmlStr)
+ model_name = os.path.basename(ant)
+ full_path_name = os.path.join(output_dir, os.path.splitext(model_name)[0]+'_diagram.png')
+ df.autolayout()
+ df.draw(output_fileName=full_path_name,showReactionIds=True)
+ return {
+ 'msg': 'Diagram has been exported to {}'.format(output_dir),
+ 'file': full_path_name
+ }
+
+
@server.thread()
@server.command('antimony.sendType')
def get_type(ls: LanguageServer, args) -> dict[str, str]:
@@ -167,7 +210,6 @@ def get_type(ls: LanguageServer, args) -> dict[str, str]:
symbols= antfile_cache.symbols_at(position)[0]
symbol = symbols[0].type.__str__()
- vscode_logger.info("symbol: " + symbol)
return {
'symbol': symbol
}
diff --git a/vscode-antimony/src/server/stibium/stibium/analysis.py b/vscode-antimony/src/server/stibium/stibium/analysis.py
index c0ee2b59d..74ddf84f4 100644
--- a/vscode-antimony/src/server/stibium/stibium/analysis.py
+++ b/vscode-antimony/src/server/stibium/stibium/analysis.py
@@ -92,6 +92,7 @@ def __init__(self, root: FileNode, path: str):
self.unnamed_events_num = 0
base_scope = BaseScope()
self.reaction_item = set()
+ self.reaction_list = list()
for child in root.children:
if isinstance(child, ErrorToken):
continue
@@ -492,6 +493,7 @@ def handle_reaction(self, scope: AbstractScope, reaction: Reaction, insert: bool
else:
self.table.insert(QName(scope, name), SymbolType.Reaction, reaction, comp=comp)
+ species_names = set()
for species in chain(reaction.get_reactants(), reaction.get_products()):
if insert:
self.import_table.insert(QName(scope, species.get_name()), SymbolType.Species, comp=comp)
@@ -499,6 +501,11 @@ def handle_reaction(self, scope: AbstractScope, reaction: Reaction, insert: bool
else:
self.table.insert(QName(scope, species.get_name()), SymbolType.Species, comp=comp)
self.table.get(QName(scope, species.get_name()))[0].in_reaction = True
+ species_names.add(species.get_name_text())
+ reaction_str = reaction.to_string()
+ reaction_part_str = reaction_str.split(';')[0] + ';'
+ self.reaction_list.append((species_names, reaction_part_str))
+
rate_law = reaction.get_rate_law()
if rate_law is not None:
self.handle_arith_expr(scope, rate_law, insert)
diff --git a/vscode-antimony/src/server/stibium/stibium/ant_types.py b/vscode-antimony/src/server/stibium/stibium/ant_types.py
index 9449e268c..3968de0bb 100644
--- a/vscode-antimony/src/server/stibium/stibium/ant_types.py
+++ b/vscode-antimony/src/server/stibium/stibium/ant_types.py
@@ -417,6 +417,17 @@ def get_comp(self):
if self.children[6] is not None:
return self.children[6]
return None
+
+ def to_string(self) -> str:
+ ret = ''
+ if isinstance(self, LeafNode):
+ ret += self.text
+ else:
+ for node in self.descendants():
+ if isinstance(node, LeafNode):
+ ret += node.text
+ return ret
+
@dataclass
class InteractionName(TrunkNode):