Skip to content
Snippets Groups Projects
vector_layer_split.py 4.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Pregmodder's avatar
    Pregmodder committed
    #!/usr/bin/env python3
    
    '''
    Application for splitting groups from one SVG file into separate files
    
    Usage:
    python3 vector_layer_split.py infile format outdir
    
    Usage Example:
    python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
    '''
    
    import lxml.etree as etree
    import sys
    import os
    import copy
    import re
    
    import normalize_svg
    
    ezsh's avatar
    ezsh committed
    import argparse
    
    parser = argparse.ArgumentParser(
    	description='Application for splitting groups from one SVG file into separate files.')
    parser.add_argument('-o', '--output', dest='output_dir', required=True,
    
    klorpa's avatar
    klorpa committed
    					help='output directory')
    
    ezsh's avatar
    ezsh committed
    parser.add_argument('-f', '--format', dest='output_format',
    
    klorpa's avatar
    klorpa committed
    					choices=['svg', 'tw'], default='svg', help='output format.')
    
    ezsh's avatar
    ezsh committed
    parser.add_argument('-p', '--prefix', dest='prefix', default='',
    
    klorpa's avatar
    klorpa committed
    					help='Prepend this string to result file names')
    
    ezsh's avatar
    ezsh committed
    parser.add_argument('input_file', metavar='FILENAME', nargs='+',
    
    klorpa's avatar
    klorpa committed
    					help='Input SVG file with layers')
    
    ezsh's avatar
    ezsh committed
    
    args = parser.parse_args()
    output_format = args.output_format
    output_directory = args.output_dir
    
    
    def splitFile(inputFile):
    	tree = etree.parse(inputFile)
    	normalize_svg.fix(tree)
    
    	# prepare output template
    	template = copy.deepcopy(tree)
    	root = template.getroot()
    
    	# remove all svg root attributes except document size
    	for a in root.attrib:
    		if (a != "viewBox"):
    			del root.attrib[a]
    
    	# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
    	# if output_format == 'tw':
    		# root.attrib["class"] = "'+_art_display_class+'"
    	ns = {
    		'svg': 'http://www.w3.org/2000/svg',
    		'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
    		'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
    	}
    
    	# remove all content, including metadata
    	# for twine output, style definitions are removed, too
    	defs = None
    	for e in root:
    		if (e.tag == etree.QName(ns['svg'], 'defs')):
    			defs = e
    		if (e.tag == etree.QName(ns['svg'], 'g') or
    			e.tag == etree.QName(ns['svg'], 'metadata') or
    			e.tag == etree.QName(ns['svg'], 'defs') or
    			e.tag == etree.QName(ns['sodipodi'], 'namedview') or
    			(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
    			):
    			root.remove(e)
    	# template preparation finished
    
    	# prepare regex for later use
    	regex_xmlns = re.compile(' xmlns[^ ]+')
    	regex_space = re.compile(r'[>]\s+[<]')
    
    	# find all groups
    	layers = tree.xpath('//svg:g', namespaces=ns)
    	for layer in layers:
    		i = layer.get('id')
    		if (  # disregard non-content groups
    			i.endswith("_") or  # manually suppressed with underscore
    			i.startswith("XMLID") or  # Illustrator generated group
    			i.startswith("g")  # Inkscape generated group
    		):
    			continue
    	# create new canvas
    		output = copy.deepcopy(template)
    	# copy all shapes into template
    		canvas = output.getroot()
    		for e in layer:
    			canvas.append(e)
    		# represent template as SVG (binary string)
    		svg = etree.tostring(output, pretty_print=False)
    		# poor man's conditional defs insertion
    		# TODO: extract only referenced defs (filters, gradients, ...)
    		# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
    		if ("filter:" in svg.decode('utf-8')):
    			# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
    			canvas.insert(0, defs)
    			# re-generate output
    			svg = etree.tostring(output, pretty_print=False)
    
    		if (output_format == 'tw'):
    			# remove unnecessary attributes
    			# TODO: never generate unnecessary attributes in the first place
    			svg = svg.decode('utf-8')
    			svg = regex_xmlns.sub('', svg)
    			svg = svg.replace(' inkscape:connector-curvature="0"', '')  # this just saves space
    			svg = svg.replace('\n', '').replace('\r', '')  # print cannot be multi-line
    			svg = regex_space.sub('><', svg)  # remove indentation
    			svg = svg.replace('svg:', '')  # svg namespace was removed
    			if ("Boob" in i):  # internal groups are used for scaling
    				svg = svg.replace('<g ', '<g data-transform="boob" ')  # boob art uses the boob scaling
    			elif ("Belly" in i):
    				svg = svg.replace('<g ', '<g data-transform="belly" ')  # belly art uses the belly scaling
    
    ezsh's avatar
    ezsh committed
    			elif ("Balls" in i):
    				svg = svg.replace('<g ', '<g data-transform="balls" ')  # balls art uses the balls scaling
    
    ezsh's avatar
    ezsh committed
    			else:
    				svg = svg.replace('<g ', '<g data-transform="art" ')  # otherwise use default scaling
    		if (not svg.endswith('\n')):
    			svg += '\n'
    		svg = svg.encode('utf-8')
    
    		# save SVG string to file
    		i = layer.get('id')
    		output_path = os.path.join(output_directory, "{0}{1}.svg".format(args.prefix, i))
    		with open(output_path, 'wb') as f:
    			if (output_format == 'svg'):
    				# Header for normal SVG (XML)
    				f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
    				f.write(svg)
    			elif (output_format == 'tw'):
    				f.write(svg)
    
    
    Pregmodder's avatar
    Pregmodder committed
    
    if not os.path.exists(output_directory):
    
    klorpa's avatar
    klorpa committed
    	os.makedirs(output_directory)
    
    Pregmodder's avatar
    Pregmodder committed
    
    
    ezsh's avatar
    ezsh committed
    for f in args.input_file:
    	splitFile(f)