diff --git a/SVGs/Omniflo/bin/optimize_easy.bat b/SVGs/Omniflo/bin/optimize_easy.bat new file mode 100644 index 0000000..8a4d7fe --- /dev/null +++ b/SVGs/Omniflo/bin/optimize_easy.bat @@ -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 \ No newline at end of file diff --git a/SVGs/Omniflo/lib/3_svg_optimizer-Step1.py b/SVGs/Omniflo/lib/3_svg_optimizer-Step1.py index b22aded..3d7b9d3 100644 --- a/SVGs/Omniflo/lib/3_svg_optimizer-Step1.py +++ b/SVGs/Omniflo/lib/3_svg_optimizer-Step1.py @@ -12,7 +12,11 @@ def analyze_and_normalize_path(d, path_type, path_id=""): 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 @@ -98,7 +102,144 @@ def analyze_and_normalize_path(d, path_type, path_id=""): 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_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): """Process single SVG file with all optimizations""" try: @@ -233,7 +374,7 @@ def optimize_svg(input_path, output_path): if stroke == '#0ff' or stroke == 'rgb(0,255,255)': elem.set('stroke', '#ffe31b') 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') # Generate final XML diff --git a/SVGs/Omniflo/lib/3_svg_scaler-Step2.py b/SVGs/Omniflo/lib/3_svg_scaler-Step2.py index 1810af5..b3f3fe3 100644 --- a/SVGs/Omniflo/lib/3_svg_scaler-Step2.py +++ b/SVGs/Omniflo/lib/3_svg_scaler-Step2.py @@ -4,7 +4,8 @@ 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)""" + """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]*)', @@ -42,9 +43,11 @@ def parse_svg_path(d: str) -> List[Tuple[float, float]]: return points 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) root = tree.getroot() + + all_points = [] 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) + def normalize_stroke_widths2(root, scale): """Normalize stroke widths to ensure consistent appearance after scaling""" for elem in root.iter(): @@ -75,58 +79,99 @@ def apply_non_scaling_stroke(root): 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): - """Scale SVG file with consistent stroke widths""" - print(f"\nProcessing: {os.path.basename(input_path)}") - - tree = ET.parse(input_path) + # 手动移除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 original bounding box + # 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"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"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}") - normalize_stroke_widths2(root, scale) - apply_non_scaling_stroke(root) - # + # apply_non_scaling_stroke(root) + # normalize_stroke_widths2(root, scale) # Update SVG attributes - root.set('viewBox', f'0 0 {new_width} {new_height}') - root.set('width', f'{new_width}') - root.set('height', f'{new_height}') + 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) + # 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): @@ -150,6 +195,6 @@ def batch_process_svg(input_dir: str, output_dir: str): print("\nAll files processed successfully!") if __name__ == '__main__': - input_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\TEFBogen\NewSVG' - output_dir = r'C:\Users\y.wang\Documents\SSG-Ruledesigner-Konfigurator\SVGs\Omniflo\TEFBogen\NewSVG\test' + 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)