Files
SSG-Ruledesigner-Konfigurator/SVGs/Omniflo/lib/3_svg_scaler-Step2.py
T

159 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)