I have embedded an SVG in a Perspective view and edited its elements. Is there a way to export this back to a .svg file?
Yep, but you'll have to do it yourself for now in script by generating the xml.. I can post my script when I'm back at my laptop, but it doesn't export more complex styles
Sorry, time got away from me.
Script below.
obj
is a copy of the "SVG" in the View, converted into a Py Object with json.loads
/ system.util.jsonDecode
The ATTR_LOOKUP
is a conversion table (dictionary) of Ignition styles to its corresponding SVG style.
EDIT: Tested and working in the script console, although you'll need the clipboard functions. I've posted them elsewhere on the forum
import xml.etree.cElementTree as et
import json
import copy
def extract_svg_from_view(obj):
#json_str = clipboard.getClipboard()
#'[{ "type": "ia.shapes.svg", "version": 0, "props": { "viewBox": "0 0 185 527.99", "elements": [{ "type": "path", "name": "rect933", "d": "m92.5 5c-43.75 0-87.5 30.69-87.5 92.07v425.92h175v-425.92c0-61.38-43.75-92.07-87.5-92.07z", "fill": { "paint": "#fff" }, "stroke": { "paint": "#000", "linecap": "round", "width": "10" } }, { "type": "path", "name": "rect933-3", "d": "m92.5 5c-43.75 0-87.5 30.69-87.5 92.07v425.92h175v-425.92c0-61.38-43.75-92.07-87.5-92.07z", "fill": { "paint": "#fff" }, "stroke": { "paint": "#000", "linecap": "round", "width": "10" } }, { "type": "group", "name": "g7299", "fill": { "paint": "#ff2a2a" }, "stroke": { "paint": "#000", "linecap": "round", "width": "10" }, "elements": [{ "type": "circle", "name": "circle7295", "cx": "69.758", "cy": "157.43", "r": "64.685" }, { "type": "circle", "name": "path7213", "cx": "92.5", "cy": "263.99", "r": "64.685" } ] } ] }, "meta": { "name": "TEST_Export" }, "position": { "basis": "20px" }, "custom": {} } ] '
#svg_obj = json.loads(json_str)
svg_obj = obj[0] # assume that the obj passed in is a copied "SVG" from a Perspective View which adds the SVG definition into a single item array. We want the item
viewbox = svg_obj['props']['viewBox']
elements = svg_obj['props']['elements']
ATTR_LOOKUP = {}
ATTR_LOOKUP['name'] = 'id'
ATTR_LOOKUP['fill.paint'] = 'fill'
ATTR_LOOKUP['fill.opacity'] = 'opacity'
ATTR_LOOKUP['stroke.paint'] = 'stroke'
ATTR_LOOKUP['style.stroke'] = 'stroke'
ATTR_LOOKUP['stroke.linecap'] = 'stroke-linecap'
ATTR_LOOKUP['stroke.width'] = 'stroke-width'
ATTR_LOOKUP['style.strokeWidth'] = 'stroke-width'
ATTR_LOOKUP['style.vector-effect'] = 'vector-effect'
doc = et.Element('svg', viewBox=viewbox, version='1.1', xmlns='http://www.w3.org/2000/svg')
not_included_SVG_elements = []
def addElements(xmlobj, elements):
for element in elements:
if 'type' in element:
attrs_dict = {}
# collect the attributes for the current SVG element are store them in a dictionary
for attr_name in element:
# exclude any nested elements as they need to be added later. These are not added as attributes
if attr_name != 'elements':
attr = element[attr_name]
# if the attribute is a dict in Ignition, then the dict will contain the actual attribute names
if isinstance(attr, dict):
for prop_name in attr:
svg_prop_name = ATTR_LOOKUP.get('{}.{}'.format(attr_name, prop_name), 'UNSET')
if svg_prop_name == 'UNSET':
not_included_SVG_elements.append('{}.{}'.format(attr_name, prop_name))
svg_prop_val = attr[prop_name]
attrs_dict[svg_prop_name] = svg_prop_val
else:
attrs_dict[ATTR_LOOKUP.get(attr_name, attr_name)] = attr
element_name = attrs_dict['type']
if element_name == 'group':
element_name = 'g'
xmlobj_attr = et.SubElement(xmlobj, element_name, **attrs_dict)
if 'elements' in element:
attr_elements = copy.deepcopy(element['elements'])
addElements(xmlobj_attr, attr_elements)
addElements(doc, elements)
#print ET.tostring(doc, encoding='utf8', method='xml')
if not_included_SVG_elements != []:
print "Copied to clipboard, but some SVG attributes are missing mapping. These have not been extracted {}".format(not_included_SVG_elements)
return et.tostring(doc)
json = shared.util.clipboard.readText()
obj = system.util.jsonDecode(json)
svg = extract_svg_from_view(obj)
shared.util.clipboard.writeText(svg)
Thank you very much!
Hello @nminchin. First I would like to thank you for effort.
I know it's been a while, but I just tried your script and got this error below:
Copied to clipboard, but some SVG attributes are missing mapping. These have not been extracted ['stroke.miterlimit', 'stroke.dasharray', 'stroke.opacity', 'stroke.miterlimit', 'stroke.dasharray', 'stroke.opacity', 'stroke.miterlimit', 'stroke.dasharray', 'stroke.opacity', 'stroke.miterlimit', 'stroke.dasharray', 'stroke.opacity']
Traceback (most recent call last):
File "<input>", line 3, in <module>
File "<module:SVGtoView>", line 69, in extract_svg_from_view
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 1128, in tostring
ElementTree(element).write(file, encoding, method=method)
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 821, in write
serialize(write, self._root, encoding, qnames, namespaces)
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 941, in _serialize_xml
_serialize_xml(write, e, encoding, qnames, None)
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 934, in _serialize_xml
v = _escape_attrib(v, encoding)
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 1094, in _escape_attrib
_raise_serialization_error(text)
File "C:\Users\ali.elboraay\.ignition\cache\gwlocalhost_8088\C1\pylib\xml\etree\ElementTree.py", line 1053, in _raise_serialization_error
raise TypeError(
TypeError: cannot serialize 1 (type int)
and the svg I copied is:
[
{
"type": "ia.shapes.svg",
"version": 0,
"props": {
"viewBox": "0 0 15.87501 30.956209",
"elements": [
{
"d": "M 1.6955795,-5.5e-5 C 0.7562625,-5.5e-5 0,0.756209 0,1.695527 v 16.592928 l 10.095088,5.125915 5.627073,-4.269909 V 1.695527 c 0,-0.939318 -0.756262,-1.695582 -1.695582,-1.695582 z M 0,18.768089 v 10.471828 c 0,0.939312 0.7562625,1.69557 1.6955795,1.69557 H 14.026579 c 0.93932,0 1.695582,-0.756258 1.695582,-1.69557 V 19.68647 l -5.563431,4.220177 z",
"fill": {
"opacity": 1
},
"name": "driver_body",
"stroke": {
"dasharray": "none",
"linecap": "round",
"miterlimit": "4",
"opacity": "1",
"paint": "",
"width": 0
},
"style": {},
"type": "path"
},
{
"elements": [
{
"d": "m 5.2853724,1.536714 h 7.2516916 c 0.842235,0 1.52028,0.598107 1.52028,1.341048 v 10.325979 c 0,0.786693 -4.31169,2.462549 -5.16104,2.462549 -0.842235,0 -5.1312101,-1.719608 -5.1312101,-2.462549 V 2.877762 c 0,-0.742941 0.6780437,-1.341048 1.5202785,-1.341048 z",
"fill": {
"opacity": "1",
"paint": "#f2f2f2"
},
"name": "panel",
"stroke": {
"dasharray": "none",
"linecap": "round",
"miterlimit": "4",
"opacity": "1",
"paint": "none",
"width": "0.529"
},
"style": {},
"type": "path"
},
{
"d": "m 31.664062,37.642578 v 3.533203 h 4.00586 v -3.533203 z m -6.355468,4.953125 v 3.53125 H 29.3125 v -3.53125 z m 12.814453,0 v 3.53125 h 4.003906 v -3.53125 z m -6.458985,4.9375 v 3.533203 h 4.00586 v -3.533203 z",
"fill": {
"opacity": "1",
"paint": "#b3b3b3"
},
"name": "rect881",
"stroke": {
"dasharray": "none",
"linecap": "round",
"miterlimit": "4",
"opacity": "1",
"paint": "none",
"width": "2"
},
"style": {},
"transform": "matrix(0.2645835,0,0,0.2645835,0,-3.025e-5)",
"type": "path"
},
{
"d": "m 5.4915642,2.7496701 h 6.9566448 c 0.489159,0 0.882959,0.4246184 0.882959,0.9520602 v 4.4054356 c 0,0.5274417 -0.3938,0.9520601 -0.882959,0.9520601 H 5.4915642 C 5.0024053,9.059226 4.608606,8.6346076 4.608606,8.1071659 V 3.7017303 c 0,-0.5274418 0.3937993,-0.9520602 0.8829582,-0.9520602 z",
"fill": {
"opacity": "1",
"paint": "#333333"
},
"name": "rect889",
"stroke": {
"dasharray": "none",
"linecap": "round",
"miterlimit": "4",
"opacity": "1",
"paint": "none",
"width": "0.529167"
},
"style": {},
"type": "path"
}
],
"name": "g860",
"transform": "translate(0,0.21612603)",
"type": "group"
}
],
"style": {
"cursor": "pointer"
}
},
"meta": {
"name": "VFD"
},
"position": {
"x": -1,
"height": 95,
"width": 50
},
"custom": {},
"propConfig": {
"props.elements[0].fill.paint": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"tagPath": "{view.params.tagPath}"
},
"tagPath": "{tagPath}/Run"
},
"overlayOptOut": true,
"transforms": [
{
"expression": "coalesce(if({value}, \"var(--color-en)\", \"var(--color-de)\") , \"var(--color-de)\")",
"type": "expression"
}
],
"type": "tag"
}
}
},
"events": {
"dom": {
"onClick": {
"config": {
"script": "\tPID.OpenPopup(self, event, \"Lib/SLD/MD/VFD/popup\", 350, 650, self.view.params.tagPath, \"righDock\", \"Lib/SLD/MD/VFD/popup\")"
},
"scope": "G",
"type": "script"
}
}
}
}
]
TypeError: cannot serialize 1 (type int)
I got the error. Somehow Ignition css property become int or float instead of string.
e.g.
"opacity": 1
so either I delete the value and leave the property empty, or I copy it from another normal property
"width": 0
here I just added "px" after "0"
and so on in each property that throw error.