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 (assumes all units are converted to px)""" d = re.sub(r'(?<=\d)(mm|pt|cm|in|px)\b', '', d) 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 (assumes all coordinates are in px)""" 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): """Ensure stroke widths don't scale with the SVG""" for elem in root.iter(): if 'stroke' in elem.attrib and elem.attrib['stroke'] != 'none': elem.set('vector-effect', 'non-scaling-stroke') # 递归清理所有的transform def remove_all_transforms(root): for g in root.findall('.//{http://www.w3.org/2000/svg}g'): if 'transform' in g.attrib: del g.attrib['transform'] def scale_svg_file(input_path: str, output_path: str): # 手动移除ns0前缀(字符串替换) with open(input_path, 'r', encoding='utf-8') as f: content = f.read() content = content.replace('ns0:', '').replace(':ns0=', '=') tree = ET.ElementTree(ET.fromstring(content)) root = tree.getroot() # Calculate bounding box INCLUDING stroke width 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"Old dimensions: {width:.3f} × {height:.3f}") # Determine scale factor (larger dimension → 1000px) scale = 1000.0 / max(width, height) print(f"Scale factor: {scale:.6f}") remove_all_transforms(root) for path in root.findall('.//{http://www.w3.org/2000/svg}path'): d = path.get('d', '') d = re.sub(r'(?<=\d)(mm|px|cm|in)', '', d) # 匹配 "10mm" → "10" scaled_d = scale_svg_path(d, scale, (-x_min, -y_min)) path.set('d', scaled_d) for g in root.findall('.//{http://www.w3.org/2000/svg}g'): if 'transform' in g.attrib: del g.attrib['transform'] new_width = round(width * scale,3) new_height = round(height * scale) print(f"New dimensions: {new_width:.3f}px × {new_height:.3f}px") print(f"ViewBox: 0 0 {new_width:.3f} {new_height:.3f}") # apply_non_scaling_stroke(root) # normalize_stroke_widths2(root, scale) # Update SVG attributes root.set('viewBox', f'0 0 {new_width:.3f} {new_height:.3f}') root.set('width', f'{new_width:.3f}px') root.set('height', f'{new_height:.3f}px') # 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 ET.register_namespace('', 'http://www.w3.org/2000/svg') tree.write(output_path, encoding='utf-8', xml_declaration=True) print(f"Finished processing: {os.path.basename(input_path)}") def scale_svg_path(d: str, scale: float, offset: Tuple[float, float]) -> str: d = re.sub(r'(?<=\d)(mm|pt|cm|in|px)\b', '', d) commands = re.findall( r'([MmLlCcQqAaHhVvZz])([^MmLlCcQqAaHhVvZz]*)', d.strip().replace(',', ' ') ) scaled_parts = [] offset_x, offset_y = offset for cmd, args in commands: nums = list(map(float, re.findall(r'[-+]?\d*\.?\d+', args))) scaled_nums = [] i= 0 while i < len(nums): if cmd in ('A', 'a'): # 圆弧命令特殊处理 # rx ry x-axis-rotation large-arc-flag sweep-flag x y scaled_nums.extend([ f"{nums[i] * scale:.3f}", # rx f"{nums[i+1] * scale:.3f}", # ry f"{nums[i+2]:.3f}", # x-axis-rotation (不缩放) f"{int(nums[i+3])}", # large-arc-flag f"{int(nums[i+4])}", # sweep-flag f"{(nums[i+5] + (offset_x if cmd == 'a' else 0)) * scale:.3f}", # x f"{(nums[i+6] + (offset_y if cmd == 'a' else 0)) * scale:.3f}" # y ]) i += 7 else: # 其他命令 if i % 2 == 0: # x坐标 val = (nums[i] + (offset_x if cmd.islower() else 0)) * scale else: # y坐标 val = (nums[i] + (offset_y if cmd.islower() else 0)) * scale scaled_nums.append(f"{val:.3f}") i += 1 scaled_part = cmd + ' '.join(scaled_nums) scaled_parts.append(scaled_part.replace(' -', '-')) # 优化负号格式 return ' '.join(scaled_parts).replace(' ', ' ') 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)