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

201 lines
8.1 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 (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')
# 递归清理所有<g>的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)