I asked ChatGPT to do a PDF merger for Rhino

It did it in less than a minute!
Now each time I have to place several images in one PDF or merge multiple PDFs I can simply call the script with an alias:
Alias PDFmerge -RunPythonScript (C:\RhinoPythonScripts\RhinoPDFmerge.py)
This is a good example of automating boring stuff.

#! python 3
# requirements: pypdf
# requirements: pillow
#!/usr/bin/env python3
# Combine PDFs and images into a single PDF (Rhino-safe, no CLI args)
# Requires: pypdf, Pillow (PIL). 

import os, io
import rhinoscriptsyntax as rs

# --- third-party libs ---
from pypdf import PdfReader, PdfWriter
from PIL import Image, ImageSequence

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff", ".webp"}
PDF_EXT = ".pdf"

def is_image(path):
    return os.path.splitext(path.lower())[1] in IMAGE_EXTS

def is_pdf(path):
    return path.lower().endswith(PDF_EXT)

def image_frames_to_pdf_bytes(img_path):
    """Return list of single-page PDF bytes (handles multi-page TIFFs)."""
    pages = []
    with Image.open(img_path) as im:
        try:
            frames = [frame.copy() for frame in ImageSequence.Iterator(im)]
            if not frames:
                frames = [im.copy()]
        except Exception:
            frames = [im.copy()]

        for frame in frames:
            # Flatten transparency, ensure RGB/L
            if frame.mode in ("RGBA", "LA"):
                bg = Image.new("RGB", frame.size, (255, 255, 255))
                bg.paste(frame, mask=frame.split()[-1])
                frame = bg
            elif frame.mode not in ("RGB", "L"):
                frame = frame.convert("RGB")

            bio = io.BytesIO()
            # Pillow embeds as a one-page PDF (size from pixels; set resolution for better print metadata)
            frame.save(bio, format="PDF", resolution=300)
            pages.append(bio.getvalue())
    return pages

def append_pdf_bytes(writer, pdf_bytes):
    reader = PdfReader(io.BytesIO(pdf_bytes))
    for page in reader.pages:
        writer.add_page(page)

def combine_files_to_pdf(files, output_pdf):
    if not files:
        rs.MessageBox("No files selected.", 48, "Combine to PDF")
        return False

    writer = PdfWriter()
    added = 0

    for path in files:
        ext = os.path.splitext(path)[1].lower()
        if is_pdf(path):
            try:
                reader = PdfReader(path)
                for page in reader.pages:
                    writer.add_page(page)
                added += len(reader.pages)
            except Exception as e:
                print("Skipped (PDF read error):", path, "-", e)
        elif is_image(path):
            try:
                for pdf_bytes in image_frames_to_pdf_bytes(path):
                    append_pdf_bytes(writer, pdf_bytes)
                    added += 1
            except Exception as e:
                print("Skipped (image error):", path, "-", e)
        else:
            print("Skipped (unsupported type):", path)

    if added == 0:
        rs.MessageBox("Nothing was added. Check file types.", 48, "Combine to PDF")
        return False

    # Write output
    with open(output_pdf, "wb") as f:
        writer.write(f)

    print("Done. Wrote:", output_pdf, "| pages:", added)
    rs.MessageBox("Created:\n{}".format(output_pdf), 64, "Combine to PDF")
    return True

def pick_files():
    # Multi-select files (PDFs + common image types)
    flt = "PDF and images (*.pdf;*.jpg;*.jpeg;*.png;*.bmp;*.tif;*.tiff;*.webp)|*.pdf;*.jpg;*.jpeg;*.png;*.bmp;*.tif;*.tiff;*.webp|All files (*.*)|*.*||"
    files = rs.OpenFileNames("Select PDFs and/or images (order is kept as selected folder view)", filter=flt)
    if not files: 
        return []
    # Optional: natural-sort by name; or keep dialog order. Here we keep dialog order.
    return list(files)

def pick_output(default_name="combined.pdf"):
    return rs.SaveFileName("Save combined PDF as", filter="PDF (*.pdf)|*.pdf||", filename=default_name)

# --- MAIN ---
if __name__ == "__main__":
    # Option A: set FILES manually and skip the picker
    FILES = []  # e.g. ["C:\\temp\\scan1.jpg", "C:\\temp\\doc.pdf"]

    if not FILES:
        FILES = pick_files()
        if not FILES:
            raise SystemExit

    out = pick_output()
    if not out:
        raise SystemExit
    if not out.lower().endswith(".pdf"):
        out += ".pdf"

    # Confirm overwrite (Rhino doesn't warn by default)
    if os.path.exists(out):
        if rs.MessageBox("File exists. Overwrite?\n{}".format(out), 33, "Confirm overwrite") != 1:
            raise SystemExit

    combine_files_to_pdf(FILES, out)

Check out other scripts here: