Parcourir la source

ported from DrawioConvert

rparedis il y a 1 an
commit
4ddb20a311

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 8 - 0
.idea/drawio2CBD.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 34 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,34 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ignoredPackages">
+        <value>
+          <list size="3">
+            <item index="0" class="java.lang.String" itemvalue="tqdm" />
+            <item index="1" class="java.lang.String" itemvalue="matplotlib" />
+            <item index="2" class="java.lang.String" itemvalue="bokeh" />
+          </list>
+        </value>
+      </option>
+    </inspection_tool>
+    <inspection_tool class="PyPep8Inspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
+      <option name="ignoredErrors">
+        <list>
+          <option value="E128" />
+          <option value="E701" />
+          <option value="E101" />
+        </list>
+      </option>
+    </inspection_tool>
+    <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoredErrors">
+        <list>
+          <option value="N802" />
+          <option value="N806" />
+          <option value="N803" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 4 - 0
.idea/misc.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/drawio2CBD.iml" filepath="$PROJECT_DIR$/.idea/drawio2CBD.iml" />
+    </modules>
+  </component>
+</project>

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
CBDLibrary.xml


BIN
__pycache__/transform.cpython-310.pyc


BIN
__pycache__/transform.cpython-311.pyc


BIN
__pycache__/util.cpython-310.pyc


BIN
__pycache__/util.cpython-311.pyc


+ 15 - 0
generated/SineGenerator-experiment.py

@@ -0,0 +1,15 @@
+#!/usr/bin/python3
+# This file was automatically generated from drawio2CBD from
+#   tests/SineGenerator.drawio
+
+from SineGenerator import *
+from pyCBD.simulator import Simulator
+
+cbd = SineGenerator("SineGenerator")
+
+# Run the Simulation
+sim = Simulator(cbd)
+sim.setDeltaT(0.1)
+sim.run(10.0)
+
+# TODO: Process Your Simulation Results

+ 38 - 0
generated/SineGenerator.py

@@ -0,0 +1,38 @@
+#!/usr/bin/python3
+# This file was automatically generated from drawio2CBD from
+#   .\tests\SineGenerator.drawio
+
+from pyCBD.Core import *
+from pyCBD.lib.std import *
+
+class SineGenerator(CBD):
+    def __init__(self, block_name):
+        super().__init__(block_name, input_ports=[], output_ports=['OUT1', 'OUT2'])
+
+        # Create the Blocks
+        self.addBlock(DeltaTBlock("time"))
+        self.addBlock(GenericBlock("sineWave", block_operator=("sin")))
+        self.addBlock(SineGenerator2("umpa"))
+        self.addBlock(AdderBlock("sum", numberOfInputs=(2)))
+
+        # Create the Connections
+        self.addConnection("time", "sineWave", output_port_name="OUT1", input_port_name="IN1")
+        self.addConnection("sineWave", "OUT1", output_port_name="OUT1")
+        self.addConnection("sum", "OUT2", output_port_name="OUT1")
+        self.addConnection("sineWave", "sum", output_port_name="OUT1", input_port_name="IN1")
+        self.addConnection("umpa", "sum", output_port_name="OUT", input_port_name="IN2")
+
+
+class SineGenerator2(CBD):
+    def __init__(self, block_name):
+        super().__init__(block_name, input_ports=[], output_ports=['OUT'])
+
+        # Create the Blocks
+        self.addBlock(DeltaTBlock("time"))
+        self.addBlock(GenericBlock("sineWave", block_operator=("sin")))
+
+        # Create the Connections
+        self.addConnection("time", "sineWave", output_port_name="OUT1", input_port_name="IN1")
+        self.addConnection("sineWave", "OUT", output_port_name="OUT1")
+
+

+ 78 - 0
main.py

@@ -0,0 +1,78 @@
+import os
+import argparse
+
+
+argprs = argparse.ArgumentParser(prog="main.py", description='Create model/simulation files from draw.io/diagrams.net XML files.')
+argprs.add_argument('input', type=str, nargs=1, help="The input file to convert.")
+argprs.add_argument("-e", "--entry", default='', help="The topmost class to use in the simulation. The 'entrace' for the simulation.")
+argprs.add_argument("-t", "--time", default=10.0, type=float, help="Total simulation time. (default: 10.0)")
+argprs.add_argument("-dt", "--delta", default=0.1, type=float, help="Time delta for the simulation. (default: 0.1)")
+argprs.add_argument('-s', '--skip', action='store_true',
+                    help="When set, empty formalism blocks will be skipped in the generation.")
+argprs.add_argument('-S', '--splitpages', action='store_false',
+                    help="When set, all pages are split into a multiple files.")
+argprs.add_argument('-r', '--reverse', action='store_true',
+                    help="When set, loops through the pages in reverse order.")
+argprs.add_argument('-d', '--directory', default='./',
+                    help="Directory of where the files must be generated. (default: ./)")
+args = argprs.parse_args()
+
+settings = {
+	"file": args.input[0],
+	"entry": args.entry,
+	"delta": args.delta,
+	"time": args.time
+}
+
+
+# === DRAWIO PARSING ===
+from drawio2py import parser
+from drawio2py.abstract_syntax import DrawIOFile
+from transform import collect_pages, Page
+
+abstract_syntax: DrawIOFile = parser.Parser.parse(settings["file"])
+
+IGNORE_PROPERTIES = ["id", "ID", "label", "placeholders", "symbol", "block_name", "class_name", "formalism", "__import__", "__docstring__"]
+INPUT = "InputPort"
+OUTPUT = "OutputPort"
+FORMALISM = "CBD"
+HERE = os.path.dirname(__file__)
+FNAME = abstract_syntax.filename.replace("\\", "/").split("/")[-1].rsplit(".", 1)[0]
+
+pages = collect_pages(abstract_syntax, FORMALISM, INPUT, OUTPUT, IGNORE_PROPERTIES, args.skip)
+if args.reverse:
+	pages = reversed(pages)
+if args.splitpages:
+	cs = [k for p in pages for k in p.classes]
+	ims = {}
+	for p in pages:
+		for im, l in p.imports.items():
+			ims.setdefault(im, []).extend(l)
+	page = Page("Merged", cs, ims)
+	pages = [page]
+
+
+
+# === CODE GENERATION ===
+from jinja2 import Template
+
+to_import = set()
+for page in pages:
+	with open(HERE + "/templates/model.py.jinja", 'r') as file:
+		template = Template(file.read(), trim_blocks=True, lstrip_blocks=True)
+	contents = template.render(classes=page.classes, file=settings['file'], imports=page.imports)
+	name = FNAME
+	if len(pages) > 1:
+		name += "." + page.name
+	with open("%s/%s.py" % (args.directory, name), 'w') as file:
+		file.write(contents)
+	to_import.add(name)
+
+if args.entry != "":
+	with open(HERE + "/templates/experiment.py.jinja", 'r') as file:
+		template = Template(file.read(), trim_blocks=True, lstrip_blocks=True)
+	contents = template.render(imports=to_import, **settings)
+	with open("%s/%s-experiment.py" % (args.directory, FNAME), 'w') as file:
+		file.write(contents)
+else:
+	print("[WARNING] Could not generate experiment file. -e/--entry flag is missing!")

+ 19 - 0
templates/experiment.py.jinja

@@ -0,0 +1,19 @@
+#!/usr/bin/python3
+# This file was automatically generated from drawio2CBD from
+#   {{ file }}
+
+{% for im in imports %}
+from {{ im }} import *
+{% endfor %}
+from pyCBD.simulator import Simulator
+
+cbd = {{entry}}("{{entry}}")
+
+# Run the Simulation
+sim = Simulator(cbd)
+{% if delta is defined %}
+sim.setDeltaT({{ delta }})
+{% endif %}
+sim.run({{ time }})
+
+# TODO: Process Your Simulation Results

+ 40 - 0
templates/model.py.jinja

@@ -0,0 +1,40 @@
+#!/usr/bin/python3
+# This file was automatically generated from drawio2CBD from
+#   {{ file }}
+
+from pyCBD.Core import *
+from pyCBD.lib.std import *
+{% for file, klss in imports.items() %}
+from {{ file }} import {{ klss|join(', ') }}
+{% endfor %}
+
+{% for block in classes %}
+class {{block.class_name}}(CBD):
+    {% if block.docs != "" %}
+    """
+    {{ block.docs }}
+    """
+    {% endif %}
+    def __init__(self, block_name{% if block.properties|length > 0 %}, {{block.get_properties()}}{% endif %}):
+        super().__init__(block_name, input_ports={{block.get_inputs()}}, output_ports={{ block.get_outputs() }})
+
+        # Create the Blocks
+        {% for child in block.blocks %}
+        self.addBlock({{child.class_name}}("{{child.block_name}}"{% if child.args|length > 0 %}, {{child.get_properties()}}{% endif %}))
+        {% endfor %}
+
+        # Create the Connections
+        {% for source, target in block.connections %}
+	        {% set send = "" %}
+	        {% if source[1] is not none %}
+	            {% set send = ", output_port_name=\"" + source[1] + '"' %}
+	        {% endif %}
+	        {% set tend = "" %}
+	        {% if target[1] is not none %}
+	            {% set tend = ", input_port_name=\"" + target[1] + '"' %}
+	        {% endif %}
+        self.addConnection("{{source[0]}}", "{{target[0]}}"{{ send }}{{ tend }})
+        {% endfor %}
+
+
+{% endfor %}

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
tests/SineGenerator.drawio


+ 102 - 0
transform.py

@@ -0,0 +1,102 @@
+from dataclasses import dataclass, field
+from util import *
+
+
+@dataclass
+class Class:
+	class_name: str
+	docs: str
+	properties: dict = field(default_factory=dict)
+	blocks: list = field(default_factory=list)
+	connections: list = field(default_factory=list)
+	ports: list = field(default_factory=list)
+
+	def get_properties(self):
+		return ", ".join(["%s=(%s)" % (k, v) for k, v in self.properties.items()])
+
+	def get_inputs(self):
+		return [p for d, p in self.ports if d == "IN"]
+
+	def get_outputs(self):
+		return [p for d, p in self.ports if d == "OUT"]
+
+	def is_empty(self):
+		return len(self.blocks) == 0 or len(self.connections) == 0
+
+
+@dataclass
+class Block:
+	class_name: str
+	block_name: str
+	args: dict
+
+	def get_properties(self):
+		return ", ".join(["%s=(%s)" % (k, v) for k, v in self.args.items()])
+
+
+@dataclass
+class Page:
+	name: str
+	classes: list = field(default_factory=list)
+	imports: dict = field(default_factory=dict)
+
+
+def collect_pages(drawio_file, formalism, inp, out, ignore, ignore_empty):
+	pages = []
+	for apage in drawio_file.pages:
+		page = Page(apage.name)
+
+		cbds_on_page = find_shapes_by_property(apage.root, "formalism", formalism)
+		for acbd in cbds_on_page:
+			cbd = Class(acbd.properties.get("class_name", ""), acbd.properties.get("__docstring__", ""))
+			for prop, val in acbd.properties.items():
+				if prop not in ignore:
+					cbd.properties[prop] = val
+
+			# Blocks
+			blocks = find_shapes(acbd, "class_name", False, False)
+			for ablock in blocks:
+				if ablock.properties.get("formalism", None) == formalism:
+					if ablock.properties.get("block_name", "") != "":
+						props = {k: v for k, v in ablock.properties.items() if k not in ignore}
+						block = Block(ablock.properties["class_name"], ablock.properties["block_name"], props)
+						cbd.blocks.append(block)
+				elif ablock.properties["class_name"] == inp:
+					cbd.ports.append(("IN", ablock.properties["name"]))
+				elif ablock.properties["class_name"] == out:
+					cbd.ports.append(("OUT", ablock.properties["name"]))
+				else:
+					props = {k: v for k, v in ablock.properties.items() if k not in ignore}
+					block = Block(ablock.properties["class_name"], ablock.properties["block_name"], props)
+					cbd.blocks.append(block)
+
+					if "__import__" in ablock.properties:
+						imp = ablock.properties["__import__"]
+						page.imports.setdefault(imp, []).append(ablock.properties["class_name"])
+
+			# Connections
+			edges = find_edges(acbd)
+			for edge in edges:
+				e = []
+				for el in [edge.source, edge.target]:
+					eblock = find_shapes_parent(el, "block_name", False)
+					if el.properties["class_name"] not in [inp, out]:
+						raise ConnectionError("Port %s is of invalid type %s in class %s" % \
+						                      (el.properties.get("name", "<<Undefined>>"),
+						                       el.properties.get("class_name", "<<Undefined>>"),
+						                       eblock.properties["class_name"]))
+
+					if eblock == acbd:
+						eblock = None
+					if eblock is not None:
+						src = eblock.properties["block_name"], el.properties["name"]
+					else:
+						src = el.properties["name"], None
+					e.append(src)
+
+				cbd.connections.append(e)
+
+			if not (ignore_empty and cbd.is_empty()):
+				page.classes.append(cbd)
+		pages.append(page)
+	return pages

+ 44 - 0
util.py

@@ -0,0 +1,44 @@
+"""
+Utility module, which contains functions to easily collect specific shapes of
+drawio models.
+"""
+
+from drawio2py.abstract_syntax import Edge
+
+
+def find_shapes_by_property(root, prop, value, continue_when_found=True):
+	if root.properties.get(prop, None) == value:
+		yield root
+		if continue_when_found:
+			for child in root.children:
+				yield from find_shapes_by_property(child, prop, value, continue_when_found)
+	else:
+		for child in root.children:
+			yield from find_shapes_by_property(child, prop, value, continue_when_found)
+
+
+def find_shapes(root, prop, include_self=True, continue_when_found=True):
+	if prop in root.properties and include_self:
+		yield root
+		if continue_when_found:
+			for child in root.children:
+				yield from find_shapes(child, prop, True, continue_when_found)
+	else:
+		for child in root.children:
+			yield from find_shapes(child, prop, True, continue_when_found)
+
+
+def find_edges(root, include_self=True):
+	if isinstance(root, Edge):
+		yield root
+	elif include_self or "class_name" not in root.properties:
+		for child in root.children:
+			yield from find_edges(child, False)
+
+
+def find_shapes_parent(root, prop, include_self=True):
+	if prop in root.properties and include_self:
+		return root
+	elif root.parent is not None:
+		return find_shapes_parent(root.parent, prop, True)
+	return None