I use this code generated via Claude.ai to fix the 0.12.0 for Blender 5.1.1
Put “gscatter-0_12_0.zip” and “fix_gscatter_blender51.py” in the same folder.
Open a terminal in that folder and run:
python fix_gscatter_blender51.py
#!/usr/bin/env python3
"""
fix_gscatter_blender51.py
=========================
Fixes the Gscatter 0.12.0 addon zip so it works with Blender 5.1 (Python 3.13).
Problem: The bundled Pillow wheels are compiled for Python 3.11 (cp311).
Blender 5.1 ships Python 3.13, which cannot load cp311 binaries.
Fix: 1. Downloads Pillow cp313 wheels for all supported platforms.
2. Updates blender_manifest.toml to reference the new wheels.
3. Repacks everything as gscatter-0_12_0-blender51.zip.
Usage:
python fix_gscatter_blender51.py [path/to/gscatter-0_12_0.zip]
If no path is given, the script looks for gscatter-0_12_0.zip in the
current directory.
Requirements: Python 3.8+, pip, internet access.
"""
import os
import sys
import shutil
import zipfile
import tempfile
import subprocess
import re
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
PILLOW_VERSION = "11.2.1"
PYTHON_TAG = "cp313"
# Platform tags: (pip --platform value, unique suffix to find the wheel file)
PLATFORMS = [
("win_amd64", "win_amd64"),
("win_arm64", "win_arm64"),
("macosx_11_0_x86_64", "macosx_10_13_x86_64"),
("macosx_11_0_arm64", "macosx_11_0_arm64"),
("manylinux_2_28_x86_64", "manylinux_2_28_x86_64"),
]
OUTPUT_ZIP = "gscatter-0_12_0-blender51.zip"
# ---------------------------------------------------------------------------
def run(cmd, **kw):
print(" $", " ".join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True, **kw)
if result.returncode != 0:
print("STDOUT:", result.stdout[-2000:])
print("STDERR:", result.stderr[-2000:])
raise RuntimeError(f"Command failed (exit {result.returncode})")
return result.stdout.strip()
def find_downloaded_wheel(directory, platform_suffix, used):
"""Find the wheel file for a given platform suffix, skipping already-used files."""
for f in sorted(os.listdir(directory)):
full = os.path.join(directory, f)
if full in used:
continue
if f.lower().startswith("pillow-") and platform_suffix.lower() in f.lower():
return full
return None
def download_pillow_wheels(dest_dir):
"""Download Pillow cp313 wheels for all target platforms."""
downloaded = []
used = set()
for pip_platform, suffix in PLATFORMS:
print(f"\n → Downloading Pillow {PILLOW_VERSION} for {pip_platform} ...")
run([
sys.executable, "-m", "pip", "download",
f"Pillow=={PILLOW_VERSION}",
"--only-binary=:all:",
"--python-version=3.13",
f"--platform={pip_platform}",
"--no-deps",
"-d", dest_dir,
])
whl = find_downloaded_wheel(dest_dir, suffix, used)
if whl is None:
files = os.listdir(dest_dir)
raise FileNotFoundError(
f"Could not find wheel for '{suffix}' in download dir.\n"
f"Files present: {files}"
)
used.add(whl)
downloaded.append((suffix, whl))
print(f" ✓ {os.path.basename(whl)}")
return downloaded
def fix_manifest(manifest_path, new_wheel_names):
"""Rewrite blender_manifest.toml with updated wheel list."""
with open(manifest_path, "r", encoding="utf-8") as f:
content = f.read()
non_pillow_wheels = [
"./wheels/attrs-24.2.0-py3-none-any.whl",
"./wheels/jsonschema-4.6.0-py3-none-any.whl",
"./wheels/pyrsistent-0.20.0-py3-none-any.whl",
"./wheels/t3dn_bip-1.0.9-py3-none-any.whl",
]
all_wheels = non_pillow_wheels + [f"./wheels/{n}" for n in new_wheel_names]
wheels_toml = "wheels = [\n" + "".join(f' "{w}",\n' for w in all_wheels) + "]"
content = re.sub(r'wheels\s*=\s*\[.*?\]', wheels_toml, content, flags=re.DOTALL)
# Remove blender_version_max if it would block Blender 5.x
content = re.sub(r'\nblender_version_max\s*=\s*"[^"]*"', '', content)
with open(manifest_path, "w", encoding="utf-8") as f:
f.write(content)
print("\n ✓ blender_manifest.toml updated.")
def main():
input_zip = sys.argv[1] if len(sys.argv) > 1 else "gscatter-0_12_0.zip"
if not os.path.exists(input_zip):
print(f"ERROR: Cannot find '{input_zip}'.")
print("Usage: python fix_gscatter_blender51.py [path/to/gscatter-0_12_0.zip]")
sys.exit(1)
print(f"Input zip : {input_zip}")
print(f"Output zip: {OUTPUT_ZIP}\n")
with tempfile.TemporaryDirectory() as tmpdir:
# 1. Extract
extract_dir = os.path.join(tmpdir, "addon")
print("[1/5] Extracting original zip ...")
with zipfile.ZipFile(input_zip, "r") as z:
z.extractall(extract_dir)
addon_dir = os.path.join(extract_dir, "gscatter")
wheels_dir = os.path.join(addon_dir, "wheels")
# 2. Remove old cp311 Pillow wheels
print("\n[2/5] Removing cp311 Pillow wheels ...")
removed = 0
for f in os.listdir(wheels_dir):
if f.lower().startswith("pillow-") and "cp311" in f:
os.remove(os.path.join(wheels_dir, f))
print(f" Removed: {f}")
removed += 1
print(f" {removed} wheel(s) removed.")
# 3. Download cp313 Pillow wheels
print("\n[3/5] Downloading Pillow cp313 wheels from PyPI ...")
wheel_dl_dir = os.path.join(tmpdir, "dl")
os.makedirs(wheel_dl_dir)
downloaded = download_pillow_wheels(wheel_dl_dir)
# 4. Copy new wheels in
print("\n[4/5] Installing new wheels ...")
new_wheel_names = []
for suffix, whl_path in downloaded:
name = os.path.basename(whl_path)
shutil.copy2(whl_path, os.path.join(wheels_dir, name))
new_wheel_names.append(name)
print(f" Added: {name}")
# 5. Patch manifest
fix_manifest(os.path.join(addon_dir, "blender_manifest.toml"), new_wheel_names)
# 6. Repack
output_path = os.path.abspath(OUTPUT_ZIP)
print(f"\n[5/5] Repacking → {output_path} ...")
with zipfile.ZipFile(output_path, "w", compression=zipfile.ZIP_DEFLATED) as zout:
for root, dirs, files in os.walk(extract_dir):
for file in files:
full_path = os.path.join(root, file)
arcname = os.path.relpath(full_path, extract_dir)
zout.write(full_path, arcname)
size_mb = os.path.getsize(output_path) / 1024 / 1024
print(f"\n✅ Done! {OUTPUT_ZIP} ({size_mb:.1f} MB)")
print("\nInstall in Blender 5.1:")
print(" Edit → Preferences → Add-ons → Install from Disk")
print(f" → select {OUTPUT_ZIP}")
if __name__ == "__main__":
main()```