bundle svg
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,6 +35,8 @@ dist/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
*.egg
|
*.egg
|
||||||
*.whl
|
*.whl
|
||||||
|
!src/wheels/
|
||||||
|
!src/wheels/**/*.whl
|
||||||
|
|
||||||
# IDE and Editor files
|
# IDE and Editor files
|
||||||
.vscode/
|
.vscode/
|
||||||
@@ -77,4 +79,4 @@ marketing/*.psd
|
|||||||
*.temp
|
*.temp
|
||||||
*.bak
|
*.bak
|
||||||
*.swp
|
*.swp
|
||||||
*~.nib
|
*~.nib
|
||||||
|
|||||||
@@ -225,13 +225,10 @@ The addon features intelligent font detection and validation:
|
|||||||
|
|
||||||
## Dependencies
|
## 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
|
- **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).
|
||||||
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`.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ This guide keeps things short so you can start creating textures right away.
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Blender 4.0 or newer (Windows, macOS, Linux).
|
- 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.
|
- 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.
|
- 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.
|
- 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
|
## Requirements
|
||||||
- Blender 4.0 or newer (Windows, macOS, or Linux).
|
- 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.
|
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
|
## Requirements & Setup
|
||||||
- Blender 4.0 or newer on Windows, macOS, or Linux.
|
- 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.
|
- 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.
|
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
|
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."""
|
"""Copy source files to destination, excluding specified patterns and files."""
|
||||||
if exclude_files is None:
|
if exclude_files is None:
|
||||||
exclude_files = []
|
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
|
# Read, minify, and write Python files
|
||||||
try:
|
try:
|
||||||
source_code = item.read_text(encoding='utf-8')
|
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)
|
minified_code = minify_python_code(source_code)
|
||||||
dest_file.write_text(minified_code, encoding='utf-8')
|
dest_file.write_text(minified_code, encoding='utf-8')
|
||||||
print(f" Copied (minified): {rel_path}")
|
print(f" Copied (minified): {rel_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If minification fails, fall back to regular copy
|
# If minification fails, fall back to regular copy
|
||||||
print(f" Warning: Could not minify {rel_path}, copying as-is: {e}")
|
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}")
|
print(f" Copied: {rel_path}")
|
||||||
else:
|
else:
|
||||||
# Copy non-Python files or when minification is disabled
|
# 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}")
|
print(f" Copied: {rel_path}")
|
||||||
copied_count += 1
|
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
|
# Copy source files to temp directory with version-specific exclusions
|
||||||
build_temp_dir = temp_dir / f"{config['project']['id']}_{version_type}"
|
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
|
# Create output filename
|
||||||
version_suffix = f"_{version_type}" if version_type == "free" else ""
|
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 math
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from array import array
|
||||||
|
|
||||||
from ..utils.constants import has_text_fitting
|
from ..utils.constants import has_text_fitting
|
||||||
from ..utils.overlay_assets import (
|
from ..utils.overlay_assets import (
|
||||||
SVGConversionError,
|
SVGConversionError,
|
||||||
@@ -662,13 +664,13 @@ def generate_texture_image(props, width, height):
|
|||||||
|
|
||||||
# Convert PIL image to Blender format (flip vertically to fix mirroring)
|
# Convert PIL image to Blender format (flip vertically to fix mirroring)
|
||||||
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue
|
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue
|
||||||
pil_pixels = list(pil_img.getdata())
|
raw_bytes = pil_img.tobytes()
|
||||||
blender_pixels = []
|
normalized = array('f', (channel / 255.0 for channel in raw_bytes))
|
||||||
for r, g, b, a in pil_pixels:
|
pixels_attr = getattr(blender_img, "pixels", None)
|
||||||
pass
|
if hasattr(pixels_attr, "foreach_set"):
|
||||||
blender_pixels.extend([r/255.0, g/255.0, b/255.0, a/255.0])
|
pixels_attr.foreach_set(normalized)
|
||||||
|
else:
|
||||||
blender_img.pixels[:] = blender_pixels
|
blender_img.pixels[:] = normalized.tolist()
|
||||||
return blender_img, None
|
return blender_img, None
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -46,22 +46,34 @@ except ImportError:
|
|||||||
# preset_ops module not available (free version)
|
# preset_ops module not available (free version)
|
||||||
PRESET_OPERATORS = []
|
PRESET_OPERATORS = []
|
||||||
|
|
||||||
if is_full_version():
|
try:
|
||||||
try:
|
pass
|
||||||
pass
|
from .text_editor_ops import *
|
||||||
from .text_editor_ops import *
|
from .text_editor_ops import (
|
||||||
from .text_editor_ops import (
|
TEXT_TEXTURE_OT_edit_text,
|
||||||
TEXT_TEXTURE_OT_edit_text,
|
)
|
||||||
)
|
TEXT_EDITOR_OPERATORS = [
|
||||||
TEXT_EDITOR_OPERATORS = [
|
'TEXT_TEXTURE_OT_edit_text',
|
||||||
'TEXT_TEXTURE_OT_edit_text',
|
]
|
||||||
]
|
except ImportError as e:
|
||||||
except ImportError as e:
|
import sys
|
||||||
import sys
|
print(f"WARNING: text_editor_ops import failed in operators/__init__.py: {e}", file=sys.stderr)
|
||||||
print(f"WARNING: text_editor_ops import failed in operators/__init__.py: {e}", file=sys.stderr)
|
|
||||||
TEXT_EDITOR_OPERATORS = []
|
class TEXT_TEXTURE_OT_edit_text:
|
||||||
else:
|
"""Fallback stub when text editor operator is unavailable."""
|
||||||
TEXT_EDITOR_OPERATORS = []
|
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:
|
try:
|
||||||
pass
|
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
|
# Get properties and generate preview with correct parameters
|
||||||
props = context.scene.text_texture_props
|
props = context.scene.text_texture_props
|
||||||
preview_size = props.preview_size if hasattr(props, 'preview_size') else 512
|
preview_size = props.preview_size if hasattr(props, 'preview_size') else 512
|
||||||
preview_width = getattr(props, 'texture_width', preview_size) or preview_size
|
preview_width = preview_size
|
||||||
preview_height = getattr(props, 'texture_height', preview_size) or preview_size
|
preview_height = preview_size
|
||||||
result = generate_preview(props, preview_width, preview_height)
|
result = generate_preview(props, preview_width, preview_height)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -18,11 +18,18 @@ from .constants import (
|
|||||||
is_free_version,
|
is_free_version,
|
||||||
is_full_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__ = [
|
__all__ = [
|
||||||
'bl_info',
|
'bl_info',
|
||||||
'install_pillow',
|
'install_pillow',
|
||||||
|
'install_cairosvg_from_bundle',
|
||||||
'get_system_fonts',
|
'get_system_fonts',
|
||||||
'get_font_enum_items',
|
'get_font_enum_items',
|
||||||
'find_default_truetype_font',
|
'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:
|
try:
|
||||||
import cairosvg # type: ignore
|
import cairosvg # type: ignore
|
||||||
except ImportError as import_error:
|
except ImportError:
|
||||||
raise SVGConversionError(
|
try:
|
||||||
"SVG overlay support requires the 'cairosvg' package. "
|
from .system import install_cairosvg_from_bundle
|
||||||
"Install it via pip (pip install cairosvg) inside Blender's Python environment."
|
install_cairosvg_from_bundle()
|
||||||
) from import_error
|
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)
|
# Guard against invalid scales (NaN, infinity, zero, negative)
|
||||||
if not isinstance(scale, (int, float)) or not math.isfinite(scale):
|
if not isinstance(scale, (int, float)) or not math.isfinite(scale):
|
||||||
|
|||||||
@@ -6,9 +6,76 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import os
|
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
|
_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():
|
def install_pillow():
|
||||||
pass
|
pass
|
||||||
"""Install Pillow if not available"""
|
"""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