160 lines
5.7 KiB
Python
160 lines
5.7 KiB
Python
import re
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
from typing import List, Dict, Tuple
|
|
|
|
def process_svg_file(input_path: str, output_path: str) -> bool:
|
|
"""Process SVG file while preserving original dimensions and viewBox"""
|
|
try:
|
|
# Parse SVG file
|
|
tree = ET.parse(input_path)
|
|
root = tree.getroot()
|
|
|
|
# Store original dimensions and viewBox
|
|
original_attrs = {
|
|
'width': root.attrib.get('width', '100%'),
|
|
'height': root.attrib.get('height', '100%'),
|
|
'viewBox': root.attrib.get('viewBox', '')
|
|
}
|
|
|
|
# Remove namespace prefixes
|
|
for elem in root.iter():
|
|
if '}' in elem.tag:
|
|
elem.tag = elem.tag.split('}', 1)[1]
|
|
for attr in list(elem.attrib):
|
|
if '}' in attr:
|
|
new_attr = attr.split('}', 1)[1]
|
|
elem.attrib[new_attr] = elem.attrib[attr]
|
|
del elem.attrib[attr]
|
|
|
|
# Process all groups with transforms
|
|
for g in root.findall('g'):
|
|
if 'transform' in g.attrib:
|
|
# Parse transform and convert to matrix
|
|
transform = g.attrib['transform']
|
|
matrix_str = convert_transform_to_matrix(transform)
|
|
|
|
# Update group attributes
|
|
g.attrib['transform'] = matrix_str
|
|
g.attrib['stroke'] = '#ffe31b' # Force stroke color
|
|
g.attrib['fill'] = 'none'
|
|
g.attrib['stroke-linecap'] = 'square'
|
|
g.attrib['vector-effect'] = 'non-scaling-stroke'
|
|
|
|
# Format paths within group
|
|
for path in g.findall('path'):
|
|
path.attrib['d'] = simplify_path_data(path.attrib.get('d', ''))
|
|
path.tail = '\n ' # Maintain consistent indentation
|
|
|
|
# Set root attributes while preserving original dimensions
|
|
root.attrib.update({
|
|
'version': '1.1',
|
|
'xmlns': 'http://www.w3.org/2000/svg',
|
|
'width': original_attrs['width'],
|
|
'height': original_attrs['height'],
|
|
'viewBox': original_attrs['viewBox'],
|
|
'fill-rule': 'evenodd',
|
|
'stroke-linecap': 'round',
|
|
'stroke-linejoin': 'round',
|
|
'space': 'preserve'
|
|
})
|
|
|
|
# Format XML with proper indentation
|
|
indent(root)
|
|
|
|
# Generate XML string
|
|
xml_str = ET.tostring(root, encoding='unicode')
|
|
xml_str = '<?xml version="1.0" encoding="UTF-8"?>\n' + xml_str
|
|
|
|
# Clean up formatting
|
|
xml_str = re.sub(r'\n\s*\n', '\n', xml_str)
|
|
xml_str = re.sub(r'>\s+<', '>\n<', xml_str)
|
|
|
|
# Write to file
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write(xml_str)
|
|
|
|
print(f"Processed: {os.path.basename(input_path)}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"Failed {os.path.basename(input_path)}: {str(e)}")
|
|
return False
|
|
|
|
def convert_transform_to_matrix(transform: str) -> str:
|
|
"""Convert transform string to matrix notation with 4 decimal places"""
|
|
tx = ty = 0.0
|
|
translate_match = re.search(r'translate\(([^,]+),\s*([^)]+)\)', transform)
|
|
if translate_match:
|
|
tx = float(translate_match.group(1))
|
|
ty = float(translate_match.group(2))
|
|
|
|
scale = 1.0
|
|
scale_match = re.search(r'scale\(([^)]+)\)', transform)
|
|
if scale_match:
|
|
scale = float(scale_match.group(1))
|
|
|
|
return f"matrix({scale:.4f} 0 0 {scale:.4f} {tx:.2f} {ty:.2f})"
|
|
|
|
def simplify_path_data(d: str) -> str:
|
|
"""Simplify path data while maintaining relative commands"""
|
|
d = re.sub(r'\s+', ' ', d.strip())
|
|
|
|
def format_number(num_str: str) -> str:
|
|
try:
|
|
num = float(num_str)
|
|
if abs(num) < 0.001 and num != 0:
|
|
return f"{num:.0e}".replace('e-0', 'e-')
|
|
if abs(num - 1000) < 0.1:
|
|
return '1e3'
|
|
if abs(num - round(num, 3)) < 0.0001:
|
|
return f"{round(num, 3):g}"
|
|
return num_str
|
|
except ValueError:
|
|
return num_str
|
|
|
|
parts = []
|
|
for part in re.split(r'([ ,])', d):
|
|
if re.match(r'^[-+]?\d*\.?\d+$', part):
|
|
parts.append(format_number(part))
|
|
else:
|
|
parts.append(part)
|
|
|
|
return ''.join(parts).replace(' ,', ',')
|
|
|
|
def indent(elem: ET.Element, level: int = 0):
|
|
"""Properly indent XML elements"""
|
|
indent_str = "\n" + level * " "
|
|
if len(elem):
|
|
if not elem.text or not elem.text.strip():
|
|
elem.text = indent_str + " "
|
|
if not elem.tail or not elem.tail.strip():
|
|
elem.tail = indent_str
|
|
for child in elem:
|
|
indent(child, level + 1)
|
|
if not elem.tail or not elem.tail.strip():
|
|
elem.tail = indent_str
|
|
else:
|
|
if level and (not elem.tail or not elem.tail.strip()):
|
|
elem.tail = indent_str
|
|
|
|
def batch_process_svgs(input_dir: str, output_dir: str):
|
|
"""Process all SVG files in a directory"""
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
success_count = 0
|
|
for filename in sorted(os.listdir(input_dir)):
|
|
if filename.lower().endswith('.svg'):
|
|
input_path = os.path.join(input_dir, filename)
|
|
output_path = os.path.join(output_dir, filename)
|
|
if process_svg_file(input_path, output_path):
|
|
success_count += 1
|
|
|
|
print(f"\nCompleted: {success_count} files processed")
|
|
|
|
if __name__ == '__main__':
|
|
input_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\Weichen'
|
|
output_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\Weichen\outputdir'
|
|
# Example usage:
|
|
batch_process_svgs(input_dir, output_dir) |