Perspective - Icon browser picks up custom icon libraries :)

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 :slight_smile: Very cool! Thanks IA team

image

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']))