This commit is contained in:
2025-09-17 19:43:11 +02:00
parent 7c041e2a26
commit 0e9a655ea1
58 changed files with 2821 additions and 892 deletions

View File

@@ -2,26 +2,39 @@
Constants and metadata for the Text Texture Generator addon.
"""
# Version information
VERSION_TYPE = "free" # "free" or "full"
bl_info = {
"name": "Text Texture Generator",
"name": "Text Texture Generator" + (" Free" if VERSION_TYPE == "free" else " Full"),
"author": "Marc Mintel <marc@mintel.me>",
"version": (1, 0, 0),
"blender": (4, 0, 0),
"location": "3D View > Sidebar (N) > Tool Tab | Shader Editor > Sidebar > Tool Tab",
"description": "Generate image textures from text with accurate dimensions and instant live preview",
"description": "Generate image textures from text with accurate dimensions and instant live preview" + (" - Free Version (Limited Features)" if VERSION_TYPE == "free" else " - Full Version"),
"category": "Material",
}
# Version information
VERSION_TYPE = "full" # "free" or "full"
# Feature configuration based on version type
ENABLED_FEATURES = ["basic", "preview"] if VERSION_TYPE == "free" else [
"basic", "preview", "presets", "normal_maps", "batch_processing",
"image_overlays", "basic_utilities", "advanced_utilities", "advanced_styling",
"stroke_effects", "blur_effects", "shadow_glow_effects", "shader_generation"
]
# Version-specific limits (applied at build time through file exclusions)
def get_version_limits():
"""Get version-specific limits."""
return {
"max_texture_size": 4096,
"max_concurrent_operations": 4
}
if VERSION_TYPE == "free":
return {
"max_texture_size": 1024,
"max_concurrent_operations": 1
}
else:
return {
"max_texture_size": 4096,
"max_concurrent_operations": 4
}
def is_free_version():
"""Check if this is the free version."""
@@ -31,37 +44,116 @@ def is_full_version():
"""Check if this is the full version."""
return VERSION_TYPE == "full"
def sync_margin_values(props, margin_type='all'):
"""Synchronize margin values across properties
# UI Helper Functions for Version Display
def get_version_badge_text():
"""Get version badge text for UI display."""
return "FREE" if is_free_version() else "PRO"
def get_version_badge_icon():
"""Get appropriate icon for version badge."""
return 'GIFT' if is_free_version() else 'STAR'
def get_upgrade_hint_text(feature_name):
"""Get contextual upgrade hint text for specific features."""
if not is_free_version():
return ""
return f"Upgrade to Pro version to access {feature_name}"
# Feature availability functions
def has_feature(feature_name):
"""Check if a specific feature is available."""
return feature_name in ENABLED_FEATURES
def has_image_overlays():
"""Check if image overlay features are available"""
return has_feature("image_overlays")
def has_basic_utilities():
"""Check if basic utility operations are available"""
return has_feature("basic_utilities")
def has_presets():
"""Check if preset features are available"""
return has_feature("presets")
def has_normal_maps():
"""Check if normal map generation is available"""
return has_feature("normal_maps")
def has_batch_processing():
"""Check if batch processing is available"""
return has_feature("batch_processing")
def has_advanced_utilities():
"""Check if advanced utility operations are available"""
return has_feature("advanced_utilities")
def has_advanced_styling():
"""Check if advanced styling options are available"""
return has_feature("advanced_styling")
def has_stroke_effects():
"""Check if stroke effects are available"""
return has_feature("stroke_effects")
def has_blur_effects():
"""Check if blur effects are available"""
return has_feature("blur_effects")
def has_shadow_glow_effects():
"""Check if shadow and glow effects are available"""
return has_feature("shadow_glow_effects")
def has_shader_generation():
"""Check if shader generation is available"""
return has_feature("shader_generation")
def get_max_resolution():
"""Get maximum texture resolution based on version"""
limits = get_version_limits()
return limits.get("max_texture_size", 1024)
def should_show_feature_lock(feature_name):
"""Check if feature lock indicator should be shown."""
# Map feature names to their availability functions
feature_checks = {
'presets': has_presets,
'normal_maps': has_normal_maps,
'batch_processing': has_batch_processing,
'advanced_utilities': has_advanced_utilities,
'advanced_styling': has_advanced_styling,
'image_overlays': has_image_overlays,
'basic_utilities': has_basic_utilities,
'stroke_effects': has_stroke_effects,
'blur_effects': has_blur_effects,
'shadow_glow_effects': has_shadow_glow_effects,
'shader_generation': has_shader_generation
}
Args:
props: Properties object containing margin values
margin_type: Type of margin sync ('all', 'horizontal', 'vertical')
"""
if not props:
return
try:
# Get base margin value
base_margin = getattr(props, 'text_fitting_margin', 10.0)
if margin_type == 'all':
# Sync all margins to base margin
if hasattr(props, 'prepend_margin'):
props.prepend_margin = base_margin
if hasattr(props, 'append_margin'):
props.append_margin = base_margin
elif margin_type == 'horizontal':
# Sync horizontal margins
if hasattr(props, 'prepend_margin') and hasattr(props, 'append_margin'):
avg_margin = (props.prepend_margin + props.append_margin) / 2
props.prepend_margin = avg_margin
props.append_margin = avg_margin
elif margin_type == 'vertical':
# Sync vertical margins (if any exist in future)
pass
except Exception as e:
print(f"Warning: Failed to sync margin values: {e}")
if is_full_version():
return False
# For free version, show lock if feature is not available
if feature_name in feature_checks:
return not feature_checks[feature_name]()
return True # Show lock for unknown features
def get_feature_availability_text():
"""Get text describing current feature availability."""
if is_free_version():
return "Free version - Basic features available"
return "Pro version - All features available"
def get_version_info_details():
"""Get detailed version information for display."""
limits = get_version_limits()
info = {
'version_type': VERSION_TYPE.upper(),
'max_texture_size': limits.get('max_texture_size', 'Unlimited'),
'max_concurrent_operations': limits.get('max_concurrent_operations', 'Unlimited'),
'features_enabled': len(ENABLED_FEATURES),
'upgrade_available': is_free_version()
}
return info

View File

@@ -1,150 +0,0 @@
"""
Feature flag system for Text Texture Generator.
Provides runtime checking of enabled features based on build configuration.
"""
import json
import os
from pathlib import Path
from typing import List, Dict, Any, Optional
# Import the build-time injected constants
try:
from .constants import ENABLED_FEATURES, VERSION_TYPE, get_version_limits
except ImportError:
# Fallback for development/testing
ENABLED_FEATURES = ["basic", "preview", "advanced_styling", "normal_maps", "presets"]
VERSION_TYPE = "full"
def get_version_limits():
return {"max_texture_size": 4096, "max_concurrent_operations": 4}
class FeatureManager:
"""Manages feature flags and version-specific limitations."""
def __init__(self):
self._enabled_features = set(ENABLED_FEATURES)
self._version_type = VERSION_TYPE
self._limits = get_version_limits()
self._features_config = self._load_features_config()
def _load_features_config(self) -> Optional[Dict]:
"""Load the features configuration file if available."""
try:
# Try to load from build directory (development)
config_path = Path(__file__).parent.parent.parent / "build" / "features.json"
if config_path.exists():
with open(config_path, 'r') as f:
return json.load(f)
except Exception:
pass
return None
def is_feature_enabled(self, feature_name: str) -> bool:
"""Check if a feature is enabled in the current build."""
return feature_name in self._enabled_features
def get_enabled_features(self) -> List[str]:
"""Get list of all enabled features."""
return list(self._enabled_features)
def get_version_type(self) -> str:
"""Get the version type (free or full)."""
return self._version_type
def get_limit(self, limit_name: str, default: Any = None) -> Any:
"""Get a version-specific limit."""
return self._limits.get(limit_name, default)
def get_max_texture_size(self) -> int:
"""Get the maximum texture size for this version."""
return self.get_limit("max_texture_size", 1024)
def get_max_concurrent_operations(self) -> int:
"""Get the maximum concurrent operations for this version."""
return self.get_limit("max_concurrent_operations", 1)
def require_feature(self, feature_name: str) -> bool:
"""Check if feature is enabled, raise exception if not."""
if not self.is_feature_enabled(feature_name):
raise FeatureNotAvailableError(
f"Feature '{feature_name}' is not available in {self._version_type} version"
)
return True
def get_feature_info(self, feature_name: str) -> Optional[Dict]:
"""Get detailed information about a feature."""
if not self._features_config:
return None
return self._features_config.get("features", {}).get(feature_name)
def is_component_enabled(self, component_path: str) -> bool:
"""Check if a specific component is enabled based on feature flags."""
if not self._features_config:
return True # Assume enabled if no config
features = self._features_config.get("features", {})
for feature_name, feature_config in features.items():
if self.is_feature_enabled(feature_name):
components = feature_config.get("components", [])
if component_path in components:
return True
return False
class FeatureNotAvailableError(Exception):
"""Raised when trying to use a feature that's not available in the current version."""
pass
# Global feature manager instance
feature_manager = FeatureManager()
# Convenience functions
def is_feature_enabled(feature_name: str) -> bool:
"""Check if a feature is enabled."""
return feature_manager.is_feature_enabled(feature_name)
def require_feature(feature_name: str) -> bool:
"""Require a feature to be enabled."""
return feature_manager.require_feature(feature_name)
def get_version_type() -> str:
"""Get the current version type."""
return feature_manager.get_version_type()
def get_max_texture_size() -> int:
"""Get maximum texture size for this version."""
return feature_manager.get_max_texture_size()
def get_max_concurrent_operations() -> int:
"""Get maximum concurrent operations for this version."""
return feature_manager.get_max_concurrent_operations()
# Decorators for feature-gated functionality
def feature_required(feature_name: str):
"""Decorator to require a feature for a function or method."""
def decorator(func):
def wrapper(*args, **kwargs):
require_feature(feature_name)
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator
def feature_gated(feature_name: str, fallback=None):
"""Decorator to conditionally execute a function based on feature availability."""
def decorator(func):
def wrapper(*args, **kwargs):
if is_feature_enabled(feature_name):
return func(*args, **kwargs)
elif fallback is not None:
return fallback(*args, **kwargs) if callable(fallback) else fallback
else:
raise FeatureNotAvailableError(
f"Feature '{feature_name}' is not available in {get_version_type()} version"
)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator

View File

@@ -8,16 +8,21 @@ import platform
import os
def install_pillow():
pass
"""Install Pillow if not available"""
try:
pass
import PIL
except ImportError:
pass
print("Installing Pillow...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "pillow>=10.0.0"])
def get_system_fonts():
pass
"""Get detailed list of available system fonts with paths"""
try:
pass
from PIL import ImageFont
import glob
@@ -25,6 +30,7 @@ def get_system_fonts():
system = platform.system()
if system == "Windows":
pass
font_dirs = [
"C:/Windows/Fonts/",
os.path.expanduser("~/AppData/Local/Microsoft/Windows/Fonts/")
@@ -47,35 +53,46 @@ def get_system_fonts():
extensions = ["*.ttf", "*.ttc", "*.otf"]
for font_dir in font_dirs:
pass
if os.path.exists(font_dir):
pass
for ext in extensions:
pass
for font_path in glob.glob(os.path.join(font_dir, "**", ext), recursive=True):
pass
try:
pass
font_name = os.path.splitext(os.path.basename(font_path))[0]
font_name = font_name.replace("-", " ").replace("_", " ")
fonts[font_name] = font_path
except Exception:
pass
continue
return fonts
except ImportError:
pass
return {}
def get_font_enum_items(self, context):
pass
"""Dynamic enum items for font selection"""
items = [("default", "Default Font", "Use system default font", 0)]
if not hasattr(get_font_enum_items, 'cached_fonts'):
pass
get_font_enum_items.cached_fonts = get_system_fonts()
fonts = get_font_enum_items.cached_fonts
for i, (font_name, font_path) in enumerate(sorted(fonts.items())[:50]):
pass
items.append((font_path, font_name, f"Font: {font_name}", i + 1))
return items
def get_anchor_point_matrix():
pass
"""Return a 3x3 matrix of anchor point identifiers for UI positioning grid"""
return [
['TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT'],