Files
3dsign/combine_svg_vertical.py
Brent Perteet 859ce050a3 Add SVG combination tool and plate dimension options
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>
2026-05-31 12:23:24 -05:00

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())