cherry-pick #1: initial Kindred branding + assembly joint fix
Cherry-picked 316d4f4b52 with conflict resolution:
- CMakeLists.txt: merged Kindred version vars with upstream 1.2.0-dev base
- src/Main/*.cpp: applied Kindred branding (banner, copyright, license)
- Resolved add/add conflicts for files already copied in Phase 1
- Includes assembly joint flip overconstrain fix
This commit is contained in:
214
resources/branding/generate-splash.py
Executable file
214
resources/branding/generate-splash.py
Executable file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate Kindred Create splash screen and about images.
|
||||
|
||||
This script creates branded splash screens with:
|
||||
- Rounded dark rectangle background (Catppuccin Mocha base #1e1e2e)
|
||||
- Kindred logo centered
|
||||
- "Kindred Create" title text
|
||||
- Version string
|
||||
|
||||
Requirements:
|
||||
pip install Pillow cairosvg
|
||||
|
||||
Usage:
|
||||
python generate-splash.py [--version VERSION] [--freecad-version FREECAD_VERSION]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
except ImportError:
|
||||
print("Error: Pillow is required. Install with: pip install Pillow")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import cairosvg
|
||||
except ImportError:
|
||||
print("Warning: cairosvg not found. Will try alternative SVG conversion.")
|
||||
cairosvg = None
|
||||
|
||||
# Catppuccin Mocha colors
|
||||
COLORS = {
|
||||
'base': '#1e1e2e',
|
||||
'surface0': '#313244',
|
||||
'text': '#cdd6f4',
|
||||
'subtext0': '#a6adc8',
|
||||
'blue': '#89b4fa',
|
||||
'lavender': '#b4befe',
|
||||
}
|
||||
|
||||
def hex_to_rgb(hex_color):
|
||||
"""Convert hex color to RGB tuple."""
|
||||
hex_color = hex_color.lstrip('#')
|
||||
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||
|
||||
def create_rounded_rectangle(draw, bbox, radius, fill):
|
||||
"""Draw a rounded rectangle."""
|
||||
x1, y1, x2, y2 = bbox
|
||||
draw.rounded_rectangle(bbox, radius=radius, fill=fill)
|
||||
|
||||
def load_svg_as_image(svg_path, width, height):
|
||||
"""Load an SVG file and convert to PIL Image at specified size."""
|
||||
if cairosvg:
|
||||
import io
|
||||
png_data = cairosvg.svg2png(url=str(svg_path), output_width=width, output_height=height)
|
||||
return Image.open(io.BytesIO(png_data)).convert('RGBA')
|
||||
else:
|
||||
# Fallback: try using inkscape command line
|
||||
import subprocess
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
try:
|
||||
subprocess.run([
|
||||
'inkscape', '-w', str(width), '-h', str(height),
|
||||
str(svg_path), '-o', tmp_path
|
||||
], check=True, capture_output=True)
|
||||
img = Image.open(tmp_path).convert('RGBA')
|
||||
return img
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.unlink(tmp_path)
|
||||
|
||||
def get_font(size, bold=False):
|
||||
"""Get a font, falling back to default if not available."""
|
||||
font_names = [
|
||||
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
|
||||
'/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf' if bold else '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
|
||||
'/usr/share/fonts/TTF/DejaVuSans-Bold.ttf' if bold else '/usr/share/fonts/TTF/DejaVuSans.ttf',
|
||||
]
|
||||
for font_name in font_names:
|
||||
if os.path.exists(font_name):
|
||||
return ImageFont.truetype(font_name, size)
|
||||
# Fallback to default
|
||||
return ImageFont.load_default()
|
||||
|
||||
def create_splash(output_path, logo_path, width, height, version, freecad_version, scale=1):
|
||||
"""Create a splash screen image."""
|
||||
# Scale dimensions
|
||||
w = int(width * scale)
|
||||
h = int(height * scale)
|
||||
radius = int(16 * scale)
|
||||
padding = int(30 * scale)
|
||||
|
||||
# Create image with transparent background
|
||||
img = Image.new('RGBA', (w, h), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Draw rounded rectangle background
|
||||
bg_color = hex_to_rgb(COLORS['base'])
|
||||
create_rounded_rectangle(draw, (padding, padding, w - padding, h - padding), radius, bg_color)
|
||||
|
||||
# Load and place logo
|
||||
logo_max_width = int(300 * scale)
|
||||
logo_max_height = int(150 * scale)
|
||||
|
||||
try:
|
||||
logo = load_svg_as_image(logo_path, logo_max_width, logo_max_height)
|
||||
# Center logo horizontally, place in upper portion
|
||||
logo_x = (w - logo.width) // 2
|
||||
logo_y = int(80 * scale)
|
||||
img.paste(logo, (logo_x, logo_y), logo)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load logo: {e}")
|
||||
logo_y = int(80 * scale)
|
||||
|
||||
# Draw "Kindred Create" title
|
||||
title_font = get_font(int(24 * scale), bold=True)
|
||||
title = "Kindred Create"
|
||||
title_bbox = draw.textbbox((0, 0), title, font=title_font)
|
||||
title_width = title_bbox[2] - title_bbox[0]
|
||||
title_x = (w - title_width) // 2
|
||||
title_y = int(250 * scale)
|
||||
draw.text((title_x, title_y), title, fill=hex_to_rgb(COLORS['text']), font=title_font)
|
||||
|
||||
# Draw version string
|
||||
version_font = get_font(int(12 * scale))
|
||||
version_str = f"v{version} (FreeCAD {freecad_version})"
|
||||
version_bbox = draw.textbbox((0, 0), version_str, font=version_font)
|
||||
version_width = version_bbox[2] - version_bbox[0]
|
||||
version_x = (w - version_width) // 2
|
||||
version_y = title_y + int(35 * scale)
|
||||
draw.text((version_x, version_y), version_str, fill=hex_to_rgb(COLORS['subtext0']), font=version_font)
|
||||
|
||||
# Save
|
||||
img.save(output_path, 'PNG')
|
||||
print(f"Created: {output_path}")
|
||||
|
||||
def create_about(output_path, logo_path, width, height, scale=1):
|
||||
"""Create an about dialog image."""
|
||||
# Scale dimensions
|
||||
w = int(width * scale)
|
||||
h = int(height * scale)
|
||||
|
||||
# Create image
|
||||
img = Image.new('RGBA', (w, h), hex_to_rgb(COLORS['base']) + (255,))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Load and place logo
|
||||
logo_max_width = int(200 * scale)
|
||||
logo_max_height = int(100 * scale)
|
||||
|
||||
try:
|
||||
logo = load_svg_as_image(logo_path, logo_max_width, logo_max_height)
|
||||
logo_x = (w - logo.width) // 2
|
||||
logo_y = int(30 * scale)
|
||||
img.paste(logo, (logo_x, logo_y), logo)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load logo: {e}")
|
||||
|
||||
# Save
|
||||
img.save(output_path, 'PNG')
|
||||
print(f"Created: {output_path}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate Kindred Create splash screens')
|
||||
parser.add_argument('--version', default='0.1.0', help='Kindred Create version')
|
||||
parser.add_argument('--freecad-version', default='1.0.0', help='FreeCAD version')
|
||||
args = parser.parse_args()
|
||||
|
||||
script_dir = Path(__file__).parent
|
||||
logo_path = script_dir / 'kindred-logo.svg'
|
||||
icons_dir = script_dir.parent.parent / 'src' / 'Gui' / 'Icons'
|
||||
|
||||
if not logo_path.exists():
|
||||
print(f"Error: Logo not found at {logo_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Create splash screens (600x400 as per spec)
|
||||
create_splash(
|
||||
icons_dir / 'kindredcreatesplash.png',
|
||||
logo_path,
|
||||
600, 400,
|
||||
args.version,
|
||||
args.freecad_version,
|
||||
scale=1
|
||||
)
|
||||
|
||||
# Create 2x version for HiDPI
|
||||
create_splash(
|
||||
icons_dir / 'kindredcreatesplash_2x.png',
|
||||
logo_path,
|
||||
600, 400,
|
||||
args.version,
|
||||
args.freecad_version,
|
||||
scale=2
|
||||
)
|
||||
|
||||
# Create about image
|
||||
create_about(
|
||||
icons_dir / 'kindredcreateabout.png',
|
||||
logo_path,
|
||||
400, 200,
|
||||
scale=1
|
||||
)
|
||||
|
||||
print("\nSplash screen generation complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user