Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DrawIO Capability #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions DrawIO/PiicoDevSymbols.xml

Large diffs are not rendered by default.

160 changes: 160 additions & 0 deletions DrawIO/create_symbol_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""
A quick script in pure python (3.10 or above) to generate a DrawIO Symbol Library
from the main SVG library.

Draw.IO library support is limited. In terms of customising the styles, see
the last comment here:

https://stackoverflow.com/questions/73309145/edit-svg-color-in-draw-io

For details on custom library:

https://www.drawio.com/blog/custom-libraries

For details on library file format:

https://github.com/jgraph/drawio-libs


It can also help to tweak symbols in DrawIO, drag into a library, export the library, and inspect.
"""
import json
import pathlib
import base64
from xml.etree import ElementTree as Et
from xml.etree.ElementTree import Element, SubElement, tostring

Et.register_namespace("xlink", "http://www.w3.org/1999/xlink")
Et.register_namespace("", "http://www.w3.org/2000/svg") # Default namespace


BASE_PATH = pathlib.Path(__file__).parent.parent
SVG_DIR = BASE_PATH / "SVG"
OVERRIDE_PATH = BASE_PATH / "DrawIO" / "override"
LIB_FILE = BASE_PATH / "DrawIO" / "PiicoDevSymbols.xml"


def process_symbol(svg_path: pathlib.Path, as_xml=False) -> tuple[int, int, str]:
# Read the SVG file
tree = Et.parse(svg_path)
root = tree.getroot()

# Extract height and width of the SVG element
width = root.get("width").replace("px", "")
height = root.get("height").replace("px", "")

# Convert back to string
modified_svg = Et.tostring(root, encoding="unicode")

# Encode to BASE64
encoded_svg = base64.b64encode(modified_svg.encode("utf-8")).decode("utf-8")

if as_xml:
# Create the root element
mx_graph_model = Element("mxGraphModel")
# Create 'root' element
root = SubElement(mx_graph_model, "root")

# Create 'mxCell' elements
# noinspection PyUnusedLocal
mx_cell0 = SubElement(root, "mxCell", {"id": "0"})
# noinspection PyUnusedLocal
mx_cell1 = SubElement(root, "mxCell", {"id": "1", "parent": "0"})

style = (
f"editableCssRules=.*;"
f"shape=image;"
f"verticalLabelPosition=bottom;"
f"verticalAlign=top;"
f"imageAspect=0;"
f"aspect=fixed;"
f"image=data:image/svg+xml,{encoded_svg}"
)

mx_cell2 = SubElement(
root,
"mxCell",
{"id": "2", "value": "", "style": style, "vertex": "1", "parent": "1"},
)

# Create 'mxGeometry' element as a child of mxCell2
SubElement(
mx_cell2,
"mxGeometry",
{
"y": "2.842170943040401e-14",
"width": width,
"height": height,
"as": "geometry",
},
)

# Convert the XML tree to a string
xml_string = tostring(mx_graph_model, encoding="unicode")
# data = base64.b64encode(xml_string.encode("utf-8")).decode("utf-8")

# {
# "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><mxCell id=\"2\" value=\"\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"1\"><mxGeometry y=\"2.842170943040401e-14\" width=\"120\" height=\"60\" as=\"geometry\"/></mxCell></root></mxGraphModel>",
# "w": 120,
# "h": 60,
# "aspect": "fixed"
# }

data = xml_string.replace("<", "&lt;").replace(">", "&gt;")
# data = xml_string

else:
data = f"data:image/svg+xml;base64,{encoded_svg}"

return int(height), int(width), data


def unescape_export_str(xml_str):
"""
A utility for dev / debugging which unescapes a string as per
https://github.com/jgraph/drawio-libs
"""
return xml_str.replace("&lt;", "<").replace("&gt;", ">").replace('\\"', '"')


if __name__ == "__main__":

def _main():
library = []
overrides = {p.stem: p for p in OVERRIDE_PATH.glob("*.svg")}

for path in SVG_DIR.glob("*.svg"):
title = path.stem
lib_entry = {
"title": title,
"aspect": "fixed",
}

if title in overrides:
h, w, data = process_symbol(overrides[title], as_xml=True)
lib_entry.update(
{
"xml": data,
"w": w,
"h": h,
}
)
else:
h, w, data = process_symbol(path)
lib_entry.update(
{
"data": data,
"w": w,
"h": h,
}
)

library.append(lib_entry)

library.sort(key=lambda item: item["title"])

with open(LIB_FILE, "w") as f:
# noinspection SpellCheckingInspection
f.write(f"<mxlibrary>{json.dumps(library, indent=4)}</mxlibrary>")

_main()
74 changes: 74 additions & 0 deletions DrawIO/generate_overrides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
A quick script that generates an override file for certain SVGs so that they
work better with DrawIO.

The main changes are to the cables. A single path is applied to keep a 40x40 icon
and then Draw.IO connectors can be used instead. Changes to the file:

* Bounding rectangle removed
* A single line (which can be copied to each file)
* line width 2 set in styles
* "stroke-width:45" manually removed.
* Shift the text up by adding transform="translate(0, -2)" to each path


"""
import re
import pathlib
from xml.etree import ElementTree as Et

Et.register_namespace("xlink", "http://www.w3.org/1999/xlink")
Et.register_namespace("", "http://www.w3.org/2000/svg") # Default namespace


BASE_PATH = pathlib.Path(__file__).parent.parent
SVG_DIR = BASE_PATH / "SVG"
OVERRIDE_PATH = BASE_PATH / "DrawIO" / "override"
OVERRIDE_FILES = [
"50mm-Cable",
"100mm-Cable",
"200mm-Cable",
"500mm-Cable",
]


def process_svg(svg_path: pathlib.Path) -> str:
# Read the SVG file
tree = Et.parse(svg_path)
root = tree.getroot()

# Add style element
style = Et.Element("style", type="text/css")
style.text = ".line{stroke:rgb(0%,0%,0%);stroke-width:2}\n.text{fill:rgb(0%,0%,0%)}"
root.insert(0, style)

# Apply class attribute based on style definitions: Assumes RGB defined for all colours.
for element in root.findall(".//*[@style]"):
style = element.get("style")
if "stroke:r" in style:
element.set("class", "line")
element.set("style", re.sub(r"stroke:[^;]+;?", "", style))

if "fill:r" in style:
element.set("class", (element.get("class", "") + " text").strip())
element.set("style", re.sub(r"fill:[^;]+;?", "", style))

# Convert back to string
modified_svg = Et.tostring(root, encoding="unicode")

return modified_svg


if __name__ == "__main__":

def _main():
for title in OVERRIDE_FILES:
src = SVG_DIR / f"{title}.svg"
new_svg = process_svg(src)
dest_file = OVERRIDE_PATH / f"{title}.svg"

with dest_file.open("w") as f:
# noinspection SpellCheckingInspection
f.write(new_svg)

_main()
11 changes: 11 additions & 0 deletions DrawIO/override/100mm-Cable.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading