bat Datei für einfachen Aufruf der verschiedenen Verzeichnisse dazu
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
@echo off
|
||||||
|
REM ================================================
|
||||||
|
REM SVG Optimizer Batch Script (Interactive Menu)
|
||||||
|
REM Allows users to select SVG optimization options
|
||||||
|
REM ================================================
|
||||||
|
|
||||||
|
REM Load environment variables
|
||||||
|
call setenv.bat
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
REM Check if Python is available in PATH
|
||||||
|
where python >nul 2>&1
|
||||||
|
if %ERRORLEVEL% neq 0 (
|
||||||
|
echo Error: Python is not found in your PATH
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Display menu options
|
||||||
|
echo SVG Optimization Options:
|
||||||
|
echo 1. Bogen (--bogen)
|
||||||
|
echo 2. Tefbogen (--tefbogen)
|
||||||
|
echo 3. Weichen (--weichen)
|
||||||
|
echo 4. Tefweichen (--tefweichen)
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Prompt user for selection
|
||||||
|
choice /c 1234 /m "Enter your choice (1-4): "
|
||||||
|
|
||||||
|
REM Set corresponding argument based on user choice
|
||||||
|
set "arg="
|
||||||
|
if %ERRORLEVEL% equ 1 set "arg=--bogen"
|
||||||
|
if %ERRORLEVEL% equ 2 set "arg=--tefbogen"
|
||||||
|
if %ERRORLEVEL% equ 3 set "arg=--weichen"
|
||||||
|
if %ERRORLEVEL% equ 4 set "arg=--tefweichen"
|
||||||
|
|
||||||
|
REM Execute Python script with selected argument
|
||||||
|
python "%RD_CONF_LIB%\3_svg_optimizer-Step1.py" %arg%
|
||||||
|
|
||||||
|
REM Check execution status
|
||||||
|
if %ERRORLEVEL% equ 0 (
|
||||||
|
echo SVG optimization completed successfully!
|
||||||
|
) else (
|
||||||
|
echo SVG optimization failed with error code: %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Keep window open to view results
|
||||||
|
pause
|
||||||
@@ -12,7 +12,11 @@ def analyze_and_normalize_path(d, path_type, path_id=""):
|
|||||||
report = []
|
report = []
|
||||||
normalized = []
|
normalized = []
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
|
# First check if path contains any arc commands
|
||||||
|
if 'A' in d or 'a' in d:
|
||||||
|
report.append(" Path contains arc commands - leaving unchanged")
|
||||||
|
return original_d, report, False
|
||||||
# Ensure path starts with M command
|
# Ensure path starts with M command
|
||||||
if not d.strip().startswith('M'):
|
if not d.strip().startswith('M'):
|
||||||
d = 'M' + d[1:] if d.startswith('L') else 'M ' + d
|
d = 'M' + d[1:] if d.startswith('L') else 'M ' + d
|
||||||
@@ -98,7 +102,144 @@ def analyze_and_normalize_path(d, path_type, path_id=""):
|
|||||||
full_report.append("Path not modified")
|
full_report.append("Path not modified")
|
||||||
|
|
||||||
return new_d, '\n'.join(full_report)
|
return new_d, '\n'.join(full_report)
|
||||||
|
def analyze_and_normalize_path(d, path_type, path_id=""):
|
||||||
|
"""Strictly normalize path direction and generate detailed report"""
|
||||||
|
original_d = d
|
||||||
|
report = []
|
||||||
|
normalized = []
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
# First check if path contains any arc commands
|
||||||
|
if 'A' in d or 'a' in d:
|
||||||
|
report_header = f"[{path_id}-Analysis] Type: {path_type} (contains arcs)"
|
||||||
|
full_report = [report_header,
|
||||||
|
f"Original path: {original_d}",
|
||||||
|
" Path contains arc commands - leaving unchanged",
|
||||||
|
"Path not modified"]
|
||||||
|
return original_d, '\n'.join(full_report)
|
||||||
|
|
||||||
|
# Ensure path starts with M command
|
||||||
|
if not d.strip().startswith('M'):
|
||||||
|
d = 'M' + d[1:] if d.startswith('L') else 'M ' + d
|
||||||
|
report.append(" Fix: Added M command at path start")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Parse path commands
|
||||||
|
commands = []
|
||||||
|
current_cmd = None
|
||||||
|
for token in re.split('([A-Za-z])', d):
|
||||||
|
if not token:
|
||||||
|
continue
|
||||||
|
if token in 'MLAZHVCSQTa-z':
|
||||||
|
current_cmd = token
|
||||||
|
else:
|
||||||
|
if current_cmd:
|
||||||
|
commands.append((current_cmd, token.strip()))
|
||||||
|
|
||||||
|
prev_x, prev_y = None, None
|
||||||
|
|
||||||
|
for cmd, params in commands:
|
||||||
|
params = [float(p) for p in re.split('[, ]+', params.strip()) if p]
|
||||||
|
|
||||||
|
if cmd == 'M':
|
||||||
|
if len(params) >= 2:
|
||||||
|
x, y = params[0], params[1]
|
||||||
|
normalized.append(f"M {x} {y}")
|
||||||
|
prev_x, prev_y = x, y
|
||||||
|
continue
|
||||||
|
|
||||||
|
if path_type == 'Line segment' and cmd == 'L':
|
||||||
|
if len(params) >= 2 and prev_x is not None:
|
||||||
|
x, y = params[0], params[1]
|
||||||
|
need_swap = x < prev_x or (x == prev_x and y < prev_y)
|
||||||
|
|
||||||
|
if need_swap:
|
||||||
|
new_segment = [f"M {x} {y}", f"L {prev_x} {prev_y}"]
|
||||||
|
report.append(f" Need to swap start/end → New path: {' '.join(new_segment)}")
|
||||||
|
normalized = new_segment
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
normalized.append(f"L {x} {y}")
|
||||||
|
report.append(" Path direction already correct, no changes made")
|
||||||
|
|
||||||
|
prev_x, prev_y = x, y
|
||||||
|
continue
|
||||||
|
|
||||||
|
normalized.append(f"{cmd} {' '.join(map(str, params))}")
|
||||||
|
|
||||||
|
new_d = ' '.join(normalized)
|
||||||
|
report_header = f"[{path_id}-Analysis] Type: {path_type}"
|
||||||
|
full_report = [report_header, f"Original path: {original_d}"] + report
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
full_report.append(f"Modified path: {new_d}")
|
||||||
|
else:
|
||||||
|
full_report.append("Path not modified")
|
||||||
|
|
||||||
|
return new_d, '\n'.join(full_report)
|
||||||
|
''' def analyze_and_normalize_path(d, path_type, path_id=""):
|
||||||
|
"""Strictly normalize path direction and generate detailed report"""
|
||||||
|
original_d = d
|
||||||
|
report = []
|
||||||
|
normalized = []
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
# First check if path contains any arc commands
|
||||||
|
if 'A' in d or 'a' in d:
|
||||||
|
report.append(" Path contains arc commands - leaving unchanged")
|
||||||
|
return original_d, report, False
|
||||||
|
|
||||||
|
# Ensure path starts with M command
|
||||||
|
if not d.strip().startswith('M'):
|
||||||
|
d = 'M' + d[1:] if d.startswith('L') else 'M ' + d
|
||||||
|
report.append(" Fix: Added M command at path start")
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Parse path commands
|
||||||
|
commands = []
|
||||||
|
current_cmd = None
|
||||||
|
for token in re.split('([A-Za-z])', d):
|
||||||
|
if not token:
|
||||||
|
continue
|
||||||
|
if token in 'MLAZHVCSQTa-z':
|
||||||
|
current_cmd = token
|
||||||
|
else:
|
||||||
|
if current_cmd:
|
||||||
|
commands.append((current_cmd, token.strip()))
|
||||||
|
|
||||||
|
prev_x, prev_y = None, None
|
||||||
|
|
||||||
|
for cmd, params in commands:
|
||||||
|
params = [float(p) for p in re.split('[, ]+', params.strip()) if p]
|
||||||
|
|
||||||
|
if cmd == 'M':
|
||||||
|
if len(params) >= 2:
|
||||||
|
x, y = params[0], params[1]
|
||||||
|
normalized.append(f"M {x} {y}")
|
||||||
|
prev_x, prev_y = x, y
|
||||||
|
continue
|
||||||
|
|
||||||
|
if path_type == 'Line segment' and cmd == 'L':
|
||||||
|
if len(params) >= 2 and prev_x is not None:
|
||||||
|
x, y = params[0], params[1]
|
||||||
|
need_swap = x < prev_x or (x == prev_x and y < prev_y)
|
||||||
|
|
||||||
|
if need_swap:
|
||||||
|
new_segment = [f"M {x} {y}", f"L {prev_x} {prev_y}"]
|
||||||
|
report.append(f" Need to swap start/end → New path: {' '.join(new_segment)}")
|
||||||
|
normalized = new_segment
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
normalized.append(f"L {x} {y}")
|
||||||
|
report.append(" Path direction already correct, no changes made")
|
||||||
|
|
||||||
|
prev_x, prev_y = x, y
|
||||||
|
continue
|
||||||
|
|
||||||
|
normalized.append(f"{cmd} {' '.join(map(str, params))}")
|
||||||
|
|
||||||
|
normalized_path = ' '.join(normalized)
|
||||||
|
return normalized_path if changed else original_d, report, changed '''
|
||||||
def optimize_svg(input_path, output_path):
|
def optimize_svg(input_path, output_path):
|
||||||
"""Process single SVG file with all optimizations"""
|
"""Process single SVG file with all optimizations"""
|
||||||
try:
|
try:
|
||||||
@@ -233,7 +374,7 @@ def optimize_svg(input_path, output_path):
|
|||||||
if stroke == '#0ff' or stroke == 'rgb(0,255,255)':
|
if stroke == '#0ff' or stroke == 'rgb(0,255,255)':
|
||||||
elem.set('stroke', '#ffe31b')
|
elem.set('stroke', '#ffe31b')
|
||||||
elif stroke == '#000' or stroke == 'rgb(255,0,0)'or stroke == 'rgb(0,0,0)':
|
elif stroke == '#000' or stroke == 'rgb(255,0,0)'or stroke == 'rgb(0,0,0)':
|
||||||
elem.set('stroke', '#ec2525')
|
elem.set('stroke', '#ffe31b')
|
||||||
elem.set('stroke-width', '1')
|
elem.set('stroke-width', '1')
|
||||||
|
|
||||||
# Generate final XML
|
# Generate final XML
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import xml.etree.ElementTree as ET
|
|||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
|
|
||||||
def parse_svg_path(d: str) -> List[Tuple[float, float]]:
|
def parse_svg_path(d: str) -> List[Tuple[float, float]]:
|
||||||
"""Extract all coordinates from SVG path data (including curve control points)"""
|
"""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 = []
|
points = []
|
||||||
commands = re.findall(
|
commands = re.findall(
|
||||||
r'([MmLlCcQqAaHhVvZz])([^MmLlCcQqAaHhVvZz]*)',
|
r'([MmLlCcQqAaHhVvZz])([^MmLlCcQqAaHhVvZz]*)',
|
||||||
@@ -42,9 +43,11 @@ def parse_svg_path(d: str) -> List[Tuple[float, float]]:
|
|||||||
return points
|
return points
|
||||||
|
|
||||||
def calculate_bounding_box(svg_file: str) -> Tuple[float, float, float, float]:
|
def calculate_bounding_box(svg_file: str) -> Tuple[float, float, float, float]:
|
||||||
"""Calculate true bounding box of all paths in SVG"""
|
"""Calculate true bounding box of all paths in SVG (assumes all coordinates are in px)"""
|
||||||
tree = ET.parse(svg_file)
|
tree = ET.parse(svg_file)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
|
|
||||||
all_points = []
|
all_points = []
|
||||||
|
|
||||||
for path in root.findall('.//{http://www.w3.org/2000/svg}path'):
|
for path in root.findall('.//{http://www.w3.org/2000/svg}path'):
|
||||||
@@ -61,6 +64,7 @@ def calculate_bounding_box(svg_file: str) -> Tuple[float, float, float, float]:
|
|||||||
return (x_min, y_min, x_max, y_max)
|
return (x_min, y_min, x_max, y_max)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_stroke_widths2(root, scale):
|
def normalize_stroke_widths2(root, scale):
|
||||||
"""Normalize stroke widths to ensure consistent appearance after scaling"""
|
"""Normalize stroke widths to ensure consistent appearance after scaling"""
|
||||||
for elem in root.iter():
|
for elem in root.iter():
|
||||||
@@ -75,58 +79,99 @@ def apply_non_scaling_stroke(root):
|
|||||||
for elem in root.iter():
|
for elem in root.iter():
|
||||||
if 'stroke' in elem.attrib and elem.attrib['stroke'] != 'none':
|
if 'stroke' in elem.attrib and elem.attrib['stroke'] != 'none':
|
||||||
elem.set('vector-effect', 'non-scaling-stroke')
|
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):
|
def scale_svg_file(input_path: str, output_path: str):
|
||||||
"""Scale SVG file with consistent stroke widths"""
|
# 手动移除ns0前缀(字符串替换)
|
||||||
print(f"\nProcessing: {os.path.basename(input_path)}")
|
with open(input_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
tree = ET.parse(input_path)
|
content = content.replace('ns0:', '').replace(':ns0=', '=')
|
||||||
|
tree = ET.ElementTree(ET.fromstring(content))
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
# Calculate original bounding box
|
# Calculate bounding box INCLUDING stroke width
|
||||||
x_min, y_min, x_max, y_max = calculate_bounding_box(input_path)
|
x_min, y_min, x_max, y_max = calculate_bounding_box(input_path)
|
||||||
width = x_max - x_min
|
width = x_max - x_min
|
||||||
height = y_max - y_min
|
height = y_max - y_min
|
||||||
|
print(f"Old dimensions: {width:.3f} × {height:.3f}")
|
||||||
print(f"Original bounding box: ({x_min:.3f}, {y_min:.3f}) to ({x_max:.3f}, {y_max:.3f})")
|
# Determine scale factor (larger dimension → 1000px)
|
||||||
print(f"Original dimensions: {width:.3f} (w) × {height:.3f} (h)")
|
scale = 1000.0 / max(width, height)
|
||||||
|
|
||||||
|
|
||||||
# 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"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"New dimensions: {new_width:.3f}px × {new_height:.3f}px")
|
||||||
print(f"ViewBox: 0 0 {new_width:.3f} {new_height:.3f}")
|
print(f"ViewBox: 0 0 {new_width:.3f} {new_height:.3f}")
|
||||||
normalize_stroke_widths2(root, scale)
|
# apply_non_scaling_stroke(root)
|
||||||
apply_non_scaling_stroke(root)
|
# normalize_stroke_widths2(root, scale)
|
||||||
#
|
|
||||||
# Update SVG attributes
|
# Update SVG attributes
|
||||||
root.set('viewBox', f'0 0 {new_width} {new_height}')
|
root.set('viewBox', f'0 0 {new_width:.3f} {new_height:.3f}')
|
||||||
root.set('width', f'{new_width}')
|
root.set('width', f'{new_width:.3f}px')
|
||||||
root.set('height', f'{new_height}')
|
root.set('height', f'{new_height:.3f}px')
|
||||||
|
|
||||||
# Apply translation and scaling
|
# Apply translation and scaling
|
||||||
for g in root.findall('{http://www.w3.org/2000/svg}g'):
|
# for g in root.findall('{http://www.w3.org/2000/svg}g'):
|
||||||
transform = g.get('transform', '')
|
# transform = g.get('transform', '')
|
||||||
new_transform = f'translate({-x_min*scale},{-y_min*scale}) scale({scale})'
|
# new_transform = f'translate({-x_min*scale},{-y_min*scale}) scale({scale})'
|
||||||
if transform:
|
# if transform:
|
||||||
new_transform = f'{transform} {new_transform}'
|
# new_transform = f'{transform} {new_transform}'
|
||||||
g.set('transform', new_transform)
|
# g.set('transform', new_transform)
|
||||||
|
|
||||||
# Save modified SVG
|
# Save modified SVG
|
||||||
|
ET.register_namespace('', 'http://www.w3.org/2000/svg')
|
||||||
tree.write(output_path, encoding='utf-8', xml_declaration=True)
|
tree.write(output_path, encoding='utf-8', xml_declaration=True)
|
||||||
print(f"Finished processing: {os.path.basename(input_path)}")
|
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):
|
def batch_process_svg(input_dir: str, output_dir: str):
|
||||||
"""Batch process SVG files in directory (non-recursive)"""
|
"""Batch process SVG files in directory (non-recursive)"""
|
||||||
if not os.path.exists(output_dir):
|
if not os.path.exists(output_dir):
|
||||||
@@ -150,6 +195,6 @@ def batch_process_svg(input_dir: str, output_dir: str):
|
|||||||
print("\nAll files processed successfully!")
|
print("\nAll files processed successfully!")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
input_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\TEFBogen\NewSVG'
|
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\TEFBogen\NewSVG\test'
|
output_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\Weichen'
|
||||||
batch_process_svg(input_dir, output_dir)
|
batch_process_svg(input_dir, output_dir)
|
||||||
|
|||||||
Reference in New Issue
Block a user