Created combine_svg_vertical.py to vertically stack SVG files with centering
and configurable spacing. Enhanced svg_logo_to_stl.py with plate dimensions
and margin options for better nameplate creation.
combine_svg_vertical.py features:
- Vertically combines two SVG files (icon above text)
- Automatically centers smaller element
- Configurable spacing between elements (default: 20px)
- Configurable margin around composition (default: 10px)
- Preserves individual SVG structure in groups
svg_logo_to_stl.py enhancements:
- Added --plate-width and --plate-height options
- Logo scales to fit within plate dimensions (maintaining aspect ratio)
- Added --margin option for spacing around logo on plate
- Automatic centering of logo on plate
- Reports actual dimensions: plate, logo, margin, and offset
Example usage:
# Combine icon and text
python combine_svg_vertical.py um_grey.svg um_black.svg combined.svg --spacing 114
# Center on 256x256mm plate with 10mm margin
python svg_logo_to_stl.py combined.svg output \
--plate-width 256 --plate-height 256 --margin 10 \
--base-thickness 1.5 --feature-height 2.5
Files:
- combine_svg_vertical.py: Vertical SVG stacking tool
- README_combine_svg.md: Documentation for combine tool
- underground-magnetics-combined.svg: Example combined logo
- svg_logo_to_stl.py: Enhanced with plate dimensions
- command.txt: Updated with new parameters
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
146 lines
4.8 KiB
Python
Executable File
146 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Combine two SVG files vertically (top over bottom) with centering and spacing.
|
|
Useful for combining separated logo elements for 3D printing.
|
|
"""
|
|
|
|
import argparse
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
|
|
|
|
def load_svg(svg_path: Path):
|
|
"""Load SVG and extract its dimensions and content"""
|
|
tree = ET.parse(svg_path)
|
|
root = tree.getroot()
|
|
|
|
# Get dimensions from viewBox or width/height attributes
|
|
viewbox = root.get('viewBox')
|
|
if viewbox:
|
|
parts = viewbox.split()
|
|
x, y, width, height = map(float, parts)
|
|
else:
|
|
width = float(root.get('width', 0))
|
|
height = float(root.get('height', 0))
|
|
x, y = 0, 0
|
|
|
|
return {
|
|
'root': root,
|
|
'width': width,
|
|
'height': height,
|
|
'viewbox_x': x,
|
|
'viewbox_y': y
|
|
}
|
|
|
|
|
|
def combine_svgs_vertical(top_svg_path: Path, bottom_svg_path: Path, output_path: Path,
|
|
spacing: float = 20, margin: float = 10):
|
|
"""
|
|
Combine two SVGs vertically with the top one centered above the bottom one.
|
|
|
|
Args:
|
|
top_svg_path: Path to top SVG (e.g., icon)
|
|
bottom_svg_path: Path to bottom SVG (e.g., text)
|
|
output_path: Path for output combined SVG
|
|
spacing: Vertical space between top and bottom elements (default: 20)
|
|
margin: Margin around the entire composition (default: 10)
|
|
"""
|
|
# Load both SVGs
|
|
top_svg = load_svg(top_svg_path)
|
|
bottom_svg = load_svg(bottom_svg_path)
|
|
|
|
# Calculate combined dimensions
|
|
max_width = max(top_svg['width'], bottom_svg['width'])
|
|
combined_height = top_svg['height'] + spacing + bottom_svg['height']
|
|
|
|
# Add margins
|
|
canvas_width = max_width + (2 * margin)
|
|
canvas_height = combined_height + (2 * margin)
|
|
|
|
# Calculate centering offsets
|
|
top_x_offset = margin + (max_width - top_svg['width']) / 2
|
|
top_y_offset = margin
|
|
|
|
bottom_x_offset = margin + (max_width - bottom_svg['width']) / 2
|
|
bottom_y_offset = margin + top_svg['height'] + spacing
|
|
|
|
# Create new SVG root
|
|
combined = ET.Element('svg', {
|
|
'xmlns': 'http://www.w3.org/2000/svg',
|
|
'width': str(canvas_width),
|
|
'height': str(canvas_height),
|
|
'viewBox': f'0 0 {canvas_width} {canvas_height}'
|
|
})
|
|
|
|
# Add comment for clarity
|
|
comment = ET.Comment(f' Combined: {top_svg_path.name} (top) + {bottom_svg_path.name} (bottom) ')
|
|
combined.append(comment)
|
|
|
|
# Create group for top SVG with transform
|
|
top_group = ET.SubElement(combined, 'g', {
|
|
'id': 'top',
|
|
'transform': f'translate({top_x_offset}, {top_y_offset})'
|
|
})
|
|
|
|
# Copy all elements from top SVG
|
|
for child in top_svg['root']:
|
|
if child.tag != '{http://www.w3.org/2000/svg}metadata':
|
|
top_group.append(child)
|
|
|
|
# Create group for bottom SVG with transform
|
|
bottom_group = ET.SubElement(combined, 'g', {
|
|
'id': 'bottom',
|
|
'transform': f'translate({bottom_x_offset}, {bottom_y_offset})'
|
|
})
|
|
|
|
# Copy all elements from bottom SVG
|
|
for child in bottom_svg['root']:
|
|
if child.tag != '{http://www.w3.org/2000/svg}metadata':
|
|
bottom_group.append(child)
|
|
|
|
# Write output
|
|
tree = ET.ElementTree(combined)
|
|
ET.indent(tree, space=' ')
|
|
tree.write(output_path, encoding='utf-8', xml_declaration=True)
|
|
|
|
print(f"Created {output_path}")
|
|
print(f" Top ({top_svg_path.name}): {top_svg['width']:.0f}x{top_svg['height']:.0f} at ({top_x_offset:.1f}, {top_y_offset:.1f})")
|
|
print(f" Bottom ({bottom_svg_path.name}): {bottom_svg['width']:.0f}x{bottom_svg['height']:.0f} at ({bottom_x_offset:.1f}, {bottom_y_offset:.1f})")
|
|
print(f" Canvas: {canvas_width:.0f}x{canvas_height:.0f}")
|
|
print(f" Spacing: {spacing:.0f}, Margin: {margin:.0f}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Combine two SVG files vertically with centering and spacing'
|
|
)
|
|
parser.add_argument('top', help='Top SVG file (e.g., icon)')
|
|
parser.add_argument('bottom', help='Bottom SVG file (e.g., text)')
|
|
parser.add_argument('output', help='Output SVG file')
|
|
parser.add_argument('--spacing', '-s', type=float, default=20,
|
|
help='Vertical spacing between elements (default: 20)')
|
|
parser.add_argument('--margin', '-m', type=float, default=10,
|
|
help='Margin around entire composition (default: 10)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
top_path = Path(args.top)
|
|
bottom_path = Path(args.bottom)
|
|
output_path = Path(args.output)
|
|
|
|
if not top_path.exists():
|
|
print(f"ERROR: Top SVG file not found: {top_path}")
|
|
return 1
|
|
|
|
if not bottom_path.exists():
|
|
print(f"ERROR: Bottom SVG file not found: {bottom_path}")
|
|
return 1
|
|
|
|
combine_svgs_vertical(top_path, bottom_path, output_path, args.spacing, args.margin)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main())
|