import os import re import xml.etree.ElementTree as ET from typing import Tuple, List def parse_svg_path(d: str) -> List[Tuple[float, float]]: """Extract all coordinates from SVG path data (including curve control points)""" points = [] commands = re.findall( r'([MmLlCcQqAaHhVvZz])([^MmLlCcQqAaHhVvZz]*)', d.strip().replace(',', ' ') ) current_pos = (0.0, 0.0) for cmd, args in commands: args = list(map(float, re.findall(r'[-+]?\d*\.?\d+', args))) if cmd in ('M', 'm', 'L', 'l'): # Move/line commands for i in range(0, len(args), 2): x, y = args[i], args[i+1] if cmd.islower(): # Relative coordinates x += current_pos[0] y += current_pos[1] points.append((x, y)) current_pos = (x, y) elif cmd in ('C', 'c'): # Cubic Bezier curves for i in range(0, len(args), 6): x1, y1, x2, y2, x, y = args[i:i+6] if cmd.islower(): x1 += current_pos[0]; y1 += current_pos[1] x2 += current_pos[0]; y2 += current_pos[1] x += current_pos[0]; y += current_pos[1] points.extend([(x1, y1), (x2, y2), (x, y)]) current_pos = (x, y) elif cmd in ('A', 'a'): # Arc commands (start/end points only) for i in range(0, len(args), 7): x, y = args[i+5], args[i+6] if cmd.islower(): x += current_pos[0] y += current_pos[1] points.append((x, y)) current_pos = (x, y) return points def calculate_bounding_box(svg_file: str) -> Tuple[float, float, float, float]: """Calculate true bounding box of all paths in SVG""" tree = ET.parse(svg_file) root = tree.getroot() all_points = [] for path in root.findall('.//{http://www.w3.org/2000/svg}path'): d = path.get('d', '') all_points.extend(parse_svg_path(d)) if not all_points: return (0, 0, 0, 0) x_min = min(p[0] for p in all_points) x_max = max(p[0] for p in all_points) y_min = min(p[1] for p in all_points) y_max = max(p[1] for p in all_points) return (x_min, y_min, x_max, y_max) def normalize_stroke_widths2(root, scale): """Normalize stroke widths to ensure consistent appearance after scaling""" for elem in root.iter(): if 'stroke-width' in elem.attrib: # Remove existing stroke-width del elem.attrib['stroke-width'] # Apply scaled stroke width (e.g., 1px → 1/scale) elem.set('stroke-width', f'{1/scale:.6f}mm') def apply_non_scaling_stroke(root): """确保所有有描边的元素的描边宽度不随缩放变化""" for elem in root.iter(): if 'stroke' in elem.attrib and elem.attrib['stroke'] != 'none': # 强制设置 stroke-width=1(无单位) elem.set('stroke-width', '1') # 确保 vector-effect 生效 elem.set('vector-effect', 'non-scaling-stroke') def scale_svg_file(input_path: str, output_path: str): """Scale SVG file with consistent stroke widths""" print(f"\nProcessing: {os.path.basename(input_path)}") tree = ET.parse(input_path) root = tree.getroot() # Calculate original bounding box x_min, y_min, x_max, y_max = calculate_bounding_box(input_path) width = x_max - x_min height = y_max - y_min print(f"Original bounding box: ({x_min:.3f}, {y_min:.3f}) to ({x_max:.3f}, {y_max:.3f})") print(f"Original dimensions: {width:.3f} (w) × {height:.3f} (h)") # Determine scaling base (larger dimension → 1000px) if width > height: scale = 1000.0 / width new_width = 1000.0 new_height = round(height * scale) print(f"Scaling base: Width (larger dimension)") else: scale = 1000.0 / height new_height = 1000.0 new_width = round(width * scale,3) print(f"Scaling base: Height (larger dimension)") print(f"Scale factor: {scale:.6f}") print(f"New dimensions: {new_width:.3f}px × {new_height:.3f}px") print(f"ViewBox: 0 0 {new_width:.3f} {new_height:.3f}") # normalize_stroke_widths2(root, scale) apply_non_scaling_stroke(root) # # Update SVG attributes root.set('viewBox', f'0 0 {new_width:.3f} {new_height:.3f}') root.set('width', f'{new_width:.3f}') root.set('height', f'{new_height:.3f}') # Apply translation and scaling for g in root.findall('{http://www.w3.org/2000/svg}g'): transform = g.get('transform', '') new_transform = f'translate({-x_min*scale},{-y_min*scale}) scale({scale})' if transform: new_transform = f'{transform} {new_transform}' g.set('transform', new_transform) # Save modified SVG tree.write(output_path, encoding='utf-8', xml_declaration=True) print(f"Finished processing: {os.path.basename(input_path)}") def batch_process_svg(input_dir: str, output_dir: str): """Batch process SVG files in directory (non-recursive)""" if not os.path.exists(output_dir): os.makedirs(output_dir) svg_files = [ f for f in os.listdir(input_dir) if f.lower().endswith('.svg') and os.path.isfile(os.path.join(input_dir, f)) ] if not svg_files: print("No SVG files found in the input directory!") return print(f"\nFound {len(svg_files)} SVG files to process") for filename in svg_files: input_path = os.path.join(input_dir, filename) output_path = os.path.join(output_dir, filename) scale_svg_file(input_path, output_path) print("\nAll files processed successfully!") if __name__ == '__main__': input_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\work' output_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\Weichen' batch_process_svg(input_dir, output_dir)