bundle svg
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,6 +35,8 @@ dist/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
*.whl
|
||||
!src/wheels/
|
||||
!src/wheels/**/*.whl
|
||||
|
||||
# IDE and Editor files
|
||||
.vscode/
|
||||
@@ -77,4 +79,4 @@ marketing/*.psd
|
||||
*.temp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
*~.nib
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
BIN
dist/text_texture_generator_v1.0.0.zip
vendored
Binary file not shown.
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
BIN
dist/text_texture_generator_v1.0.0_free.zip
vendored
Binary file not shown.
@@ -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`—we’re happy to help.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,17 +273,25 @@ 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
|
||||
shutil.copy2(item, dest_file)
|
||||
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 ""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -46,22 +46,34 @@ except ImportError:
|
||||
# preset_ops module not available (free version)
|
||||
PRESET_OPERATORS = []
|
||||
|
||||
if is_full_version():
|
||||
try:
|
||||
pass
|
||||
from .text_editor_ops import *
|
||||
from .text_editor_ops import (
|
||||
TEXT_TEXTURE_OT_edit_text,
|
||||
)
|
||||
TEXT_EDITOR_OPERATORS = [
|
||||
'TEXT_TEXTURE_OT_edit_text',
|
||||
]
|
||||
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 = []
|
||||
try:
|
||||
pass
|
||||
from .text_editor_ops import *
|
||||
from .text_editor_ops import (
|
||||
TEXT_TEXTURE_OT_edit_text,
|
||||
)
|
||||
TEXT_EDITOR_OPERATORS = [
|
||||
'TEXT_TEXTURE_OT_edit_text',
|
||||
]
|
||||
except ImportError as e:
|
||||
import sys
|
||||
print(f"WARNING: text_editor_ops import failed in operators/__init__.py: {e}", file=sys.stderr)
|
||||
|
||||
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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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',
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -87,11 +87,16 @@ def ensure_svg_raster(image_path: str, scale: float) -> RasterizationResult:
|
||||
|
||||
try:
|
||||
import cairosvg # type: ignore
|
||||
except ImportError as import_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
|
||||
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 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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user