I was working on a tool to smoosh a directory of SVGs into an icon library and I just noticed that the icon browser picks up custom icon libraries
Very cool! Thanks IA team

Not the greatest SVGs to use for icons, but it proves the concept! The blank ones are extra useful
3 Likes
did you finish this tool? I’m having a hell of a time trying to get custom svg icons into ignition.
I definitely have a script working, but it needs some pretty… It was written in Python 3.9, I haven’t checked if it’ll work in Jython.
It only handles simple SVGs (which icons should be anyway) and doesn’t cope with and ignores SVGs with groups
# standard libraries
import xmltodict
from collections import OrderedDict
import copy
import os
from os import walk
ignitionIconLibraryName = 'lib-custom'
# NOT USED YET
# ignitionIconLibraryPath = r'C:\Program Files\Inductive Automation\Ignition\data\modules\com.inductiveautomation.perspective\icons'
svgFolderPath = r'G:\My Drive\Work\Resources\Symbols\Icons\material.io-custom'
# NOT USED YET
# ignitionIconLibraryFullpath = '{}\{}.svg'.format(ignitionIconLibraryPath, ignitionIconLibraryName)
# setup the SVG library "XML-as-a-dict" object
svgLibraryDict = OrderedDict()
svgLibraryDict['svg'] = OrderedDict()
svgLibraryDict['svg']['@xmlns'] = 'http://www.w3.org/2000/svg'
svgLibraryDict['svg']['@xmlns:xlink'] = 'http://www.w3.org/1999/xlink'
svgLibraryDict['svg']['defs'] = [OrderedDict()]
svgLibraryDict['svg']['defs'][0]['style'] = '.icon { display: none }\n\t\t\t.icon:target { display: inline }'
svgLibraryDict['svg']['svg'] = []
skeletonSVGDef = OrderedDict()
skeletonSVGDef['@viewBox'] = '<REPLACE ME>'
skeletonSVGDef['g'] = OrderedDict()
skeletonSVGDef['g']['@class'] = 'icon'
skeletonSVGDef['g']['@id'] = '<REPLACE ME>'
skeletonSVGDef['g']['path'] = OrderedDict()
skeletonSVGDef['g']['path']['@d'] = '<REPLACE ME>'
filenames = next(walk(svgFolderPath), ([], [], []))[2] # [] if no file
unprocessedFiles = []
processedFiles = []
for filename in filenames:
svg_viewBox = None
svg_g_path_d = None
filepath ='{}\{}'.format(svgFolderPath, filename)
if filepath.endswith('.svg') and filepath.split('.') != ignitionIconLibraryName:
with open(filepath) as fd:
new_svg = copy.deepcopy(skeletonSVGDef)
doc = xmltodict.parse(fd.read())
if doc.get('svg', None) is not None:
svg_viewBox = doc.get('svg', None).get('@viewBox', None)
if doc['svg'].get('path', None) is not None:
if isinstance(doc['svg']['path'], OrderedDict):
svg_g_path_d = doc['svg']['path'].get('@d', None)
processedFiles.append({'path': filepath,
'filename': filename,
'error': 'None'})
else:
unprocessedFiles.append({'path': filepath,
'filename': filename,
'error': 'None'})
elif doc['svg'].get('g', None) is not None:
if isinstance(doc['svg']['g'], OrderedDict):
if doc['svg']['g'].get('path', None) is not None:
if isinstance(doc['svg']['g']['path'], OrderedDict):
svg_g_path_d = doc['svg']['g']['path'].get('@d', None)
processedFiles.append({'path': filepath,
'filename': filename,
'error': 'None'})
else:
unprocessedFiles.append({'path': filepath,
'filename': filename,
'error': 'SVG.g.path object is a list and cannot be processed. SVG icons are required to be flat'})
else:
unprocessedFiles.append({'path': filepath,
'filename': filename,
'error': 'SVG.g object is a list and cannot be processed. SVG icons are required to be flat'})
if svg_g_path_d is not None and svg_viewBox is not None:
new_svg['@viewBox'] = svg_viewBox
new_svg['g']['path']['@d'] = svg_g_path_d
new_svg['g']['@id'] = filename.split('.')[0].replace('_', '-').replace(' ', '-').replace('-24px', '')
svgLibraryDict['svg']['svg'].append(new_svg)
print('SVGs added: {}'.format(len(svgLibraryDict['svg'])))
print('Successful Files:')
print('-----------------------')
for stuff in processedFiles:
print('{}'.format(stuff['filename']))
xml = xmltodict.unparse(svgLibraryDict)
with open('{}\\{}.svg'.format(svgFolderPath, ignitionIconLibraryName), 'w') as f:
f.write(xml)
print('Unprocessed Files:')
print('-----------------------')
for stuff in unprocessedFiles:
print('{}: {}'.format(stuff['filename'], stuff['error']))
# move the unprocessed files into an unprocessed folder
os.replace(stuff['path'], '{}\unprocessed\{}'.format(svgFolderPath, stuff['filename']))