bundle svg

This commit is contained in:
2025-10-13 19:08:28 +02:00
parent e3a8f0bcf0
commit fe2e81ecd0
43 changed files with 187 additions and 46 deletions

2
.gitignore vendored
View File

@@ -35,6 +35,8 @@ dist/
*.egg-info/
*.egg
*.whl
!src/wheels/
!src/wheels/**/*.whl
# IDE and Editor files
.vscode/

View File

@@ -225,13 +225,10 @@ The addon features intelligent font detection and validation:
## Dependencies
The addon automatically installs required dependencies:
The addon bundles and automatically installs its dependencies when needed:
- **Pillow** (>=10.0.0): For image generation and text rendering
Optional dependency for vector overlays:
- **CairoSVG** (>=2.7.0): Required to rasterize SVG overlays. Install it inside Blender's Python environment with `pip install cairosvg`.
- **Pillow** (>=10.0.0): Image generation and text rendering.
- **CairoSVG** (>=2.7.0): Rasterizes SVG overlays using the bundled wheel set for macOS and Linux (installs on first SVG use).
## Troubleshooting

View File

@@ -15,7 +15,7 @@ This guide keeps things short so you can start creating textures right away.
## Requirements
- Blender 4.0 or newer (Windows, macOS, Linux).
- Pillow installs automatically with the addon. _(SVG overlays need CairoSVG only install if you plan to use SVGs.)_
- Pillow and CairoSVG install automatically with the addon _(SVG overlays work out of the box)._
---
@@ -66,5 +66,5 @@ This guide keeps things short so you can start creating textures right away.
- Need multiple styles fast? Duplicate the blend file or save presets before you start experimenting.
- Keep the preview size smaller while fine-tuning; switch to full resolution for final renders.
- SVG logos? Add them as overlay components—the addon will rasterize them when CairoSVG is installed.
- SVG logos? Add them as overlay components—the addon bundles CairoSVG and rasterizes them automatically.
- For team projects, export presets (`Add-on Panel > Presets > Export`) so everyone can share the same look.

Binary file not shown.

Binary file not shown.

View File

@@ -28,6 +28,6 @@ Create polished text textures without leaving Blender. This addon drops a dedica
## Requirements
- Blender 4.0 or newer (Windows, macOS, or Linux).
- Pillow installs automatically; CairoSVG is only needed for SVG overlays.
- Pillow and CairoSVG install automatically—SVG overlays are ready out of the box.
Questions or feature requests? Message us on Gumroad or email `marc@mintel.me`—were happy to help.

View File

@@ -29,7 +29,7 @@ Bring typography directly into your Blender workflow. The Text Texture Generator
## Requirements & Setup
- Blender 4.0 or newer on Windows, macOS, or Linux.
- Pillow installs automatically; install CairoSVG only if you plan to use SVG overlays.
- Pillow and CairoSVG install automatically, so SVG overlays work immediately.
- Installation follows the standard “Install Add-on from File…” flow and includes demo `.blend` scenes to explore the toolset immediately.
Need a production license, classroom seats, or have a technical question? Send a DM on Superhive or drop an email to `marc@mintel.me`—we usually reply within one business day.

View File

@@ -201,7 +201,48 @@ def should_exclude_file(file_path: Path, source_dir: Path, exclude_patterns: Lis
return False
def copy_source_files(source_dir: Path, dest_dir: Path, exclude_patterns: List[str], exclude_files: List[str] = None, minify: bool = True):
def _format_version_tuple(version_tuple: tuple) -> str:
"""Format version tuple for insertion into bl_info."""
return "(" + ", ".join(str(part) for part in version_tuple) + ")"
def _edition_version_tuple(version: str, version_type: str) -> tuple:
"""Return edition-specific version tuple."""
try:
major_str, minor_str, patch_str = version.split(".", 2)
major, minor, patch = int(major_str), int(minor_str), int(patch_str)
except ValueError:
# Fallback if version string unexpected
return (1, 0, 0) if version_type == "free" else (1, 0, 1)
if version_type == "full":
return (major, minor, patch + 1)
return (major, minor, patch)
def _apply_edition_overrides(rel_path: Path, source_code: str, version_type: str, version: str) -> str:
"""Inject edition-specific metadata into copied source files."""
if rel_path.as_posix() == "__init__.py":
edition_name = "Text Texture Generator (Pro)" if version_type == "full" else "Text Texture Generator (Free)"
target_version_tuple = _edition_version_tuple(version, version_type)
source_code = re.sub(
r'"name":\s*"Text Texture Generator\s*\([^"]*\)"',
f'"name": "{edition_name}"',
source_code,
count=1
)
source_code = re.sub(
r'"version":\s*\([^\)]+\)',
f'"version": {_format_version_tuple(target_version_tuple)}',
source_code,
count=1
)
return source_code
def copy_source_files(source_dir: Path, dest_dir: Path, exclude_patterns: List[str], exclude_files: List[str] = None, minify: bool = True, version_type: str = "free", version: str = "1.0.0"):
"""Copy source files to destination, excluding specified patterns and files."""
if exclude_files is None:
exclude_files = []
@@ -232,16 +273,24 @@ def copy_source_files(source_dir: Path, dest_dir: Path, exclude_patterns: List[s
# Read, minify, and write Python files
try:
source_code = item.read_text(encoding='utf-8')
source_code = _apply_edition_overrides(rel_path, source_code, version_type, version)
minified_code = minify_python_code(source_code)
dest_file.write_text(minified_code, encoding='utf-8')
print(f" Copied (minified): {rel_path}")
except Exception as e:
# If minification fails, fall back to regular copy
print(f" Warning: Could not minify {rel_path}, copying as-is: {e}")
shutil.copy2(item, dest_file)
source_code = item.read_text(encoding='utf-8')
source_code = _apply_edition_overrides(rel_path, source_code, version_type, version)
dest_file.write_text(source_code, encoding='utf-8')
print(f" Copied: {rel_path}")
else:
# Copy non-Python files or when minification is disabled
if item.suffix == '.py':
source_code = item.read_text(encoding='utf-8')
source_code = _apply_edition_overrides(rel_path, source_code, version_type, version)
dest_file.write_text(source_code, encoding='utf-8')
else:
shutil.copy2(item, dest_file)
print(f" Copied: {rel_path}")
copied_count += 1
@@ -360,7 +409,7 @@ def build_addon(version_type: str, version: Optional[str] = None, output_dir: Op
# Copy source files to temp directory with version-specific exclusions
build_temp_dir = temp_dir / f"{config['project']['id']}_{version_type}"
copy_source_files(source_dir, build_temp_dir, exclude_patterns, exclude_files, minify)
copy_source_files(source_dir, build_temp_dir, exclude_patterns, exclude_files, minify, version_type=version_type, version=version)
# Create output filename
version_suffix = f"_{version_type}" if version_type == "free" else ""

View File

@@ -6,6 +6,8 @@ Core functionality for generating texture images with text overlays.
import math
import bpy
from array import array
from ..utils.constants import has_text_fitting
from ..utils.overlay_assets import (
SVGConversionError,
@@ -662,13 +664,13 @@ def generate_texture_image(props, width, height):
# Convert PIL image to Blender format (flip vertically to fix mirroring)
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue
pil_pixels = list(pil_img.getdata())
blender_pixels = []
for r, g, b, a in pil_pixels:
pass
blender_pixels.extend([r/255.0, g/255.0, b/255.0, a/255.0])
blender_img.pixels[:] = blender_pixels
raw_bytes = pil_img.tobytes()
normalized = array('f', (channel / 255.0 for channel in raw_bytes))
pixels_attr = getattr(blender_img, "pixels", None)
if hasattr(pixels_attr, "foreach_set"):
pixels_attr.foreach_set(normalized)
else:
blender_img.pixels[:] = normalized.tolist()
return blender_img, None
except ImportError as e:
pass

View File

@@ -46,7 +46,6 @@ except ImportError:
# preset_ops module not available (free version)
PRESET_OPERATORS = []
if is_full_version():
try:
pass
from .text_editor_ops import *
@@ -59,9 +58,22 @@ if is_full_version():
except ImportError as e:
import sys
print(f"WARNING: text_editor_ops import failed in operators/__init__.py: {e}", file=sys.stderr)
TEXT_EDITOR_OPERATORS = []
else:
TEXT_EDITOR_OPERATORS = []
class TEXT_TEXTURE_OT_edit_text:
"""Fallback stub when text editor operator is unavailable."""
bl_idname = "text_texture.edit_text"
bl_label = "Edit Text (Unavailable)"
bl_description = "Text editor feature unavailable in this build"
bl_options = {'REGISTER'}
@classmethod
def poll(cls, context):
return False
def execute(self, context):
raise RuntimeError("Text editor feature is unavailable in this build.")
TEXT_EDITOR_OPERATORS = ['TEXT_TEXTURE_OT_edit_text']
try:
pass

View File

@@ -162,8 +162,8 @@ class TEXT_TEXTURE_OT_preview(Operator):
# Get properties and generate preview with correct parameters
props = context.scene.text_texture_props
preview_size = props.preview_size if hasattr(props, 'preview_size') else 512
preview_width = getattr(props, 'texture_width', preview_size) or preview_size
preview_height = getattr(props, 'texture_height', preview_size) or preview_size
preview_width = preview_size
preview_height = preview_size
result = generate_preview(props, preview_width, preview_height)
if result:

View File

@@ -18,11 +18,18 @@ from .constants import (
is_free_version,
is_full_version
)
from .system import install_pillow, get_system_fonts, get_font_enum_items, find_default_truetype_font
from .system import (
install_pillow,
install_cairosvg_from_bundle,
get_system_fonts,
get_font_enum_items,
find_default_truetype_font
)
__all__ = [
'bl_info',
'install_pillow',
'install_cairosvg_from_bundle',
'get_system_fonts',
'get_font_enum_items',
'find_default_truetype_font',

View File

@@ -87,11 +87,16 @@ def ensure_svg_raster(image_path: str, scale: float) -> RasterizationResult:
try:
import cairosvg # type: ignore
except ImportError as import_error:
except ImportError:
try:
from .system import install_cairosvg_from_bundle
install_cairosvg_from_bundle()
import cairosvg # type: ignore # noqa: F401
except Exception as install_error:
raise SVGConversionError(
"SVG overlay support requires the 'cairosvg' package. "
"Install it via pip (pip install cairosvg) inside Blender's Python environment."
) from import_error
"SVG overlay support requires CairoSVG, but the bundled installation failed. "
"Please reinstall the addon or install CairoSVG manually inside Blender's Python environment."
) from install_error
# Guard against invalid scales (NaN, infinity, zero, negative)
if not isinstance(scale, (int, float)) or not math.isfinite(scale):

View File

@@ -6,9 +6,76 @@ import subprocess
import sys
import platform
import os
from pathlib import Path
from typing import Iterable
_SUPPORTED_PYTHON_TAG = "cp311"
_BUNDLED_WHEEL_DIRS = {
"darwin": "cp311-macosx_10_9_universal2",
"linux": "cp311-manylinux_2_28_x86_64",
}
_BUNDLED_CAIROSVG_PACKAGES = (
"cairosvg==2.7.1",
"cairocffi==1.7.1",
"cssselect2==0.7.0",
"tinycss2==1.2.1",
"defusedxml==0.7.1",
"cffi==1.17.1",
"pycparser==2.21",
)
_DEFAULT_FONT_CACHE = None
def _pip_install_from_bundle(packages: Iterable[str], bundle_dir: Path) -> None:
"""Install packages from a bundled wheel directory using pip."""
pass
command = [
sys.executable,
"-m",
"pip",
"install",
"--no-index",
"--no-cache-dir",
"--find-links",
str(bundle_dir),
*packages,
]
subprocess.check_call(command)
def _get_wheel_bundle_dir() -> Path:
"""Return the platform-specific wheel bundle directory for the current environment."""
pass
python_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
if python_tag != _SUPPORTED_PYTHON_TAG:
pass
raise RuntimeError(
f"Bundled dependencies target Python {_SUPPORTED_PYTHON_TAG}, "
f"but current interpreter reports {python_tag}."
)
platform_key = platform.system().lower()
bundle_name = _BUNDLED_WHEEL_DIRS.get(platform_key)
if not bundle_name:
pass
raise RuntimeError(f"No bundled wheel directory configured for platform '{platform_key}'.")
wheels_root = Path(__file__).resolve().parent.parent / "wheels"
bundle_dir = wheels_root / bundle_name
if not bundle_dir.exists():
pass
raise FileNotFoundError(f"Bundled wheel directory not found: {bundle_dir}")
return bundle_dir
def install_cairosvg_from_bundle() -> None:
"""Install CairoSVG and its dependencies using the bundled wheels."""
pass
bundle_dir = _get_wheel_bundle_dir()
_pip_install_from_bundle(_BUNDLED_CAIROSVG_PACKAGES, bundle_dir)
def install_pillow():
pass
"""Install Pillow if not available"""