deployment

This commit is contained in:
2025-09-11 17:35:33 +03:00
parent 9c2c879483
commit 7c40714d48
12 changed files with 387 additions and 284 deletions

105
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,105 @@
# Deployment Guide
Quick instructions for deploying a new version of the Text Texture Generator addon.
## Prerequisites
- Git repository with proper tags
- Python 3.10+
- All changes committed and pushed to main branch
## Deployment Steps
### 1. Create Version Tag
```bash
# Create and push a version tag
git tag v1.1.0
git push origin v1.1.0
```
### 2. Generate Changelog (Optional)
```bash
# Generate changelog for the new version
python3 build/generate_changelog.py --version 1.1.0 --output CHANGELOG_v1.1.0.md
# Or generate full changelog
python3 build/generate_changelog.py --full --output CHANGELOG.md
```
### 3. Build Packages
```bash
# Build both versions (recommended)
python3 build/build_addon.py --type all --version 1.1.0
# Or build individually:
# python3 build/build_addon.py --type full --version 1.1.0
# python3 build/build_addon.py --type free --version 1.1.0
```
### 4. Validate Packages
```bash
# Validate the generated packages
python3 build/validate_package.py dist/text_texture_generator_v1.1.0.zip --type full
python3 build/validate_package.py dist/text_texture_generator_v1.1.0_free.zip --type free
```
### 5. Deploy
Upload the validated packages from the `dist/` directory:
- `text_texture_generator_v1.1.0.zip` (Full version)
- `text_texture_generator_v1.1.0_free.zip` (Free version)
## Build System Overview
The deployment uses a sophisticated build-time file exclusion system:
- **Version Sync**: [`sync_version.py`](build/sync_version.py) automatically updates version information from git tags
- **Package Build**: [`build_addon.py`](build/build_addon.py) creates separate packages with different feature sets
- **Validation**: [`validate_package.py`](build/validate_package.py) ensures package integrity
- **Changelog**: [`generate_changelog.py`](build/generate_changelog.py) creates release notes from git history
## Configuration Files
- [`build/config.json`](build/config.json) - Project metadata and build settings
- [`build/file_exclusions.json`](build/file_exclusions.json) - Defines which files are excluded from free builds
## Troubleshooting
### No Git Tags Found
If version sync fails, ensure you have created and pushed the version tag:
```bash
git tag -l # List existing tags
git tag v1.1.0 # Create new tag
git push origin v1.1.0 # Push to remote
```
### Build Validation Fails
Check the validation output for specific errors:
```bash
python3 build/validate_package.py dist/your_package.zip --type full
```
### Version Mismatch
The build system automatically syncs versions from git tags. If you need to override:
```bash
python3 build/sync_version.py --version 1.1.0 --type full
```
## Quick Deploy Command
For experienced users, deploy in one command:
```bash
git tag v1.1.0 && git push origin v1.1.0 && python3 build/build_addon.py --type all --version 1.1.0 && python3 build/validate_package.py dist/text_texture_generator_v1.1.0.zip && python3 build/validate_package.py dist/text_texture_generator_v1.1.0_free.zip
```

View File

@@ -57,12 +57,12 @@ A modern Blender addon that generates image textures from text with extensive cu
- **Preset Management System**:
- Three-tier storage: Blend File, Persistent, Local presets
- All preset types now display properly in the interface
- Presets travel with .blend files (Blend File type)
- Simplified two-tier storage: Persistent File (default) + Blend File (optional)
- Unified preset list with clear storage indicators (🏠/📄)
- Presets travel with .blend files when "Save with .blend file" is checked
- Import/export functionality for backup and sharing
- Automatic migration system for addon updates
- Conflict resolution for preset imports
- Automatic migration from legacy three-tier system
- Conflict resolution with blend file presets taking priority
- **Preview System**:
@@ -254,33 +254,39 @@ The addon automatically installs required dependencies:
## Preset System
The addon features a sophisticated three-tier preset system:
The addon features a streamlined two-tier preset system designed for simplicity and reliability:
### Storage Locations (Priority Order)
1. **Blend File Storage** (Highest Priority)
1. **Blend File Storage** (Higher Priority)
- Presets stored in scene custom properties
- Travel with your .blend file
- Travel with your .blend file automatically
- Perfect for project-specific presets
- Display with 📄 indicator in UI
2. **Persistent File Storage** (Medium Priority)
2. **Persistent File Storage** (Default Storage)
- Survives addon updates and Blender upgrades
- Stored in Blender's user config directory
- Stored in Blender's user config directory (`~/.config/blender/text_texture_generator/presets/`)
- Shared across all blend files
- Display with 🏠 indicator in UI
3. **Local File Storage** (Lowest Priority)
- Local addon-specific presets
- Automatically migrated to persistent storage when needed
### Key Features
### Preset Features
- **Improved UI Display**: Recent bug fix ensures all three preset types (Blend File, Persistent, Local) display properly in the interface
- **Auto-Migration**: Seamlessly upgrades presets during addon updates
- **Simplified UI**: Single unified preset list with clear storage indicators
- **Easy Storage Selection**: Simple "Save with .blend file" checkbox controls destination
- **Priority System**: Blend file presets automatically override persistent ones with same name
- **Auto-Migration**: Seamlessly migrates old three-tier presets to new system
- **Import/Export**: Backup and share presets across installations
- **Conflict Resolution**: Smart handling of duplicate preset names
- **Migration Reports**: Detailed logs of preset migration operations
- **User-Friendly Messages**: Clear, non-technical confirmation messages
### How to Use
- **Default Behavior**: Presets save to persistent storage (🏠) - survives addon updates
- **Project-Specific**: Check "Save with .blend file" to save with your project (📄)
- **Loading**: All presets appear in one list, blend file presets take priority
- **Migration**: Old presets are automatically migrated on first use
## Build System
@@ -393,7 +399,7 @@ Marc Mintel <marc@mintel.me>
- Normal map generation from text alpha channels with strength/blur controls
- Advanced shader generation with Principled BSDF, Emission, and Transparent types
- Light path controls for shadows, reflections, and backfacing geometry
- Three-tier preset system (blend file → persistent → legacy) with auto-migration
- Simplified two-tier preset system (persistent + blend file) with priority handling
- Live preview with zoomable Image Editor integration and multiple backgrounds
- Prepend/append text support with horizontal/vertical layouts
- Custom font support with mixed-case validation and fallback systems
@@ -416,7 +422,7 @@ Marc Mintel <marc@mintel.me>
#### Full Version Features (Additional):
- Normal map generation from text alpha channels
- Complete preset management system with import/export
- Complete preset management system with simplified two-tier storage
- Advanced utility operations and batch processing
- Extended UI components and advanced styling options
- **File Count**: 23 files (60.1 KB package size)
@@ -433,6 +439,6 @@ Marc Mintel <marc@mintel.me>
- `core/normal_maps.py` - Normal map generation algorithms
- `operators/preset_ops.py` - Preset management operations
- `operators/utility_ops.py` - Batch processing and utility functions
- `presets/` - Entire preset storage and migration system
- `presets/` - Entire simplified preset storage and migration system
This approach ensures the free version provides genuine value while the full version offers substantially more capabilities through additional source code modules.

View File

@@ -43,21 +43,22 @@ Cross-platform compatibility across Windows, macOS, and Linux environments, comb
- **Memory Management**: Efficient processing for large textures without system overload
- **Batch Processing**: Multiple texture generation with consistent settings
### Version Strategy
#### **🆓 Free Version - Full Core Experience**
- **Complete Text Rendering Pipeline**: Professional typography with up to 1024×1024 resolution
- **Essential Positioning System**: 9-point anchor positioning with pixel-perfect alignment
- **Essential Positioning System**: 9-point anchor positioning with pixel-perfect alignment
- **Seamless Blender Integration**: Automatic material node creation and assignment
- **Professional Font Support**: System font detection and custom font file compatibility
- **Full Color Control**: Complete RGBA customization with transparency support
- **No Time Limits**: Use forever - perfect for testing and smaller projects
#### **⭐ Full Version - Unleash Professional Power**
- **Ultra-High Resolution**: Generate stunning 4096×4096 textures for commercial work
- **Advanced 3D Effects**: Automatic normal map generation with customizable depth and blur
- **Complete Preset Ecosystem**: Three-tier preset management with import/export for team workflows
- **Complete Preset Ecosystem**: Simplified two-tier preset management with import/export for team workflows
- **Professional Batch Processing**: Advanced utility operations and workflow automation tools
- **Extended Material Pipeline**: Advanced styling options and specialized production tools
- **Future-Proof Investment**: All upcoming premium features included with purchase
@@ -66,12 +67,14 @@ Cross-platform compatibility across Windows, macOS, and Linux environments, comb
### Smart Preset Management
- **Three-Tier Preset System**: Blend File, Persistent, and Local preset storage with proper UI display
- **Simplified Two-Tier System**: Streamlined Persistent (default) and Blend File storage with unified UI
- **Intuitive Storage Selection**: Simple "Save with .blend file" checkbox controls destination
- **Clear Visual Indicators**: 🏠 (persistent) and 📄 (blend file) icons for instant recognition
- **Priority System**: Blend file presets automatically override persistent ones with same name
- **User-Defined Presets**: Save and organize custom configurations with improved interface
- **Project-Specific Presets**: Maintain consistent styling across project assets
- **Preset Import/Export**: Share configurations between team members
- **Version Control**: Preset versioning for workflow tracking
- **Enhanced UI Display**: Recent improvements ensure all preset types display correctly in the interface
- **Auto-Migration**: Seamlessly migrates old three-tier presets to new streamlined system
### Material Integration

View File

@@ -44,7 +44,7 @@ From indie game developers to 3D artists and creative hobbyists, this tool saves
- **Extended Resolution**: Generate up to 4096×4096 high-resolution textures
- **Normal Map Generation**: Automatic height-to-normal conversion for realistic 3D depth and surface detail
- **Advanced Preset Management**: Complete preset system with import/export, sharing across three storage types (Blend File, Persistent, Local)
- **Advanced Preset Management**: Simplified two-tier preset system with import/export, easy storage selection with clear visual indicators
- **Batch Processing**: Process multiple textures with consistent settings and advanced utility operations
- **Professional Effects Suite**: Extended styling options and advanced material integration
- **Premium Modules**: Additional specialized tools for professional workflows

Binary file not shown.

View File

@@ -6,10 +6,11 @@ import json
import time
# Import utility functions from main module
from ..presets import (
get_preset_path, get_persistent_preset_path, get_addon_version,
get_blend_file_presets, save_blend_file_preset, delete_blend_file_preset,
refresh_presets_sync, ensure_presets_available
from ..presets.storage_system import (
save_preset_unified, get_unified_preset_list,
get_blend_file_presets, delete_blend_file_preset,
refresh_presets_sync, ensure_presets_available,
get_preset_path, get_persistent_preset_path
)
from ..utils import bl_info
@@ -161,46 +162,11 @@ class TEXT_TEXTURE_OT_save_preset(Operator):
preset_data['image_overlays'].append(overlay_data)
try:
# Primary: Save to blend file (new system)
print(f"[PRESET SAVE DEBUG] Attempting to save to blend file...")
blend_success = save_blend_file_preset(preset_name, preset_data)
print(f"[PRESET SAVE DEBUG] Blend file save result: {blend_success}")
# Save based on checkbox setting - single destination only
success = save_preset_unified(preset_data, preset_name, props.save_with_blend_file)
# ALWAYS save to persistent storage for cross-blend-file sharing and addon update survival
persistent_file = os.path.join(get_persistent_preset_path(), f"{preset_name}.json")
print(f"[PRESET SAVE DEBUG] Attempting to save to persistent file: {persistent_file}")
persistent_success = True
try:
with open(persistent_file, 'w') as f:
json.dump(preset_data, f, indent=2)
print(f"[PRESET SAVE DEBUG] Successfully saved preset '{preset_name}' to persistent storage")
# VERIFY: Read back the persistent file to confirm shader properties were saved
print(f"[PRESET SAVE DEBUG] Verifying persistent file contents...")
with open(persistent_file, 'r') as f:
saved_data = json.load(f)
print(f"[PRESET SAVE DEBUG] Persistent file shader properties verification:")
print(f"[PRESET SAVE DEBUG] disable_shadows: {saved_data.get('disable_shadows', 'NOT FOUND')}")
print(f"[PRESET SAVE DEBUG] disable_reflections: {saved_data.get('disable_reflections', 'NOT FOUND')}")
print(f"[PRESET SAVE DEBUG] disable_backfacing: {saved_data.get('disable_backfacing', 'NOT FOUND')}")
except Exception as persistent_error:
print(f"[PRESET SAVE DEBUG] Warning: Failed to save persistent preset file: {persistent_error}")
persistent_success = False
# Also save to legacy location for backward compatibility (optional)
legacy_success = True
try:
legacy_file = os.path.join(get_preset_path(), f"{preset_name}.json")
with open(legacy_file, 'w') as f:
json.dump(preset_data, f, indent=2)
print(f"[PRESET SAVE DEBUG] Also saved to legacy location for backward compatibility")
except Exception as legacy_error:
print(f"[PRESET SAVE DEBUG] Note: Could not save to legacy location: {legacy_error}")
legacy_success = False
if not blend_success and not persistent_success:
self.report({'ERROR'}, f"Failed to save preset to both blend file and persistent storage")
if not success:
self.report({'ERROR'}, f"Failed to save preset '{preset_name}'")
return {'CANCELLED'}
# Check if preset already exists in the collection and update it
@@ -213,28 +179,18 @@ class TEXT_TEXTURE_OT_save_preset(Operator):
if not existing_preset:
preset = props.presets.add()
preset.name = preset_name
# Priority: Blend file > Persistent file
preset.source = 'BLEND_FILE' if blend_success else 'PERSISTENT_FILE'
preset.source = 'BLEND_FILE' if props.save_with_blend_file else 'PERSISTENT_FILE'
else:
# Update source based on where it was successfully saved
existing_preset.source = 'BLEND_FILE' if blend_success else 'PERSISTENT_FILE'
# Don't clear the input field - keep the name for easy re-saving
# props.new_preset_name = "New Preset"
# Update source based on where it was saved
existing_preset.source = 'BLEND_FILE' if props.save_with_blend_file else 'PERSISTENT_FILE'
# Provide clear feedback
storage_info = ""
if blend_success and persistent_success:
storage_info = " (saved to blend file + persistent storage)"
elif blend_success:
storage_info = " (saved to blend file)"
elif persistent_success:
storage_info = " (saved to persistent storage)"
location = "with .blend file" if props.save_with_blend_file else "to persistent storage"
if self.overwrite:
self.report({'INFO'}, f"✅ Preset '{preset_name}' updated successfully{storage_info}")
self.report({'INFO'}, f"✅ Preset '{preset_name}' updated {location}")
else:
self.report({'INFO'}, f"✅ Preset '{preset_name}' saved successfully{storage_info}")
self.report({'INFO'}, f"✅ Preset '{preset_name}' saved {location}")
except (OSError, IOError, json.JSONEncodeError) as e:
self.report({'ERROR'}, f"Failed to save preset: {str(e)}")
@@ -301,57 +257,17 @@ class TEXT_TEXTURE_OT_load_preset(Operator):
print(f"[PRESET LOAD DEBUG] shader_connection: {props.shader_connection}")
try:
# Three-tier loading priority: Blend File > Persistent File > Legacy File
blend_presets = get_blend_file_presets()
preset_data = None
source = 'BLEND_FILE'
# Get unified preset list and find the one to load
all_presets = get_unified_preset_list()
print(f"[PRESET LOAD DEBUG] Available blend file presets: {list(blend_presets.keys())}")
if self.preset_name in blend_presets:
preset_data = blend_presets[self.preset_name]
source = 'BLEND_FILE'
print(f"[PRESET LOAD DEBUG] Loading preset '{self.preset_name}' from blend file")
print(f"[PRESET LOAD DEBUG] Blend file preset shader properties:")
print(f"[PRESET LOAD DEBUG] disable_shadows: {preset_data.get('disable_shadows', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_reflections: {preset_data.get('disable_reflections', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_backfacing: {preset_data.get('disable_backfacing', 'NOT FOUND')}")
else:
# Try persistent storage next
persistent_file = os.path.join(get_persistent_preset_path(), f"{self.preset_name}.json")
print(f"[PRESET LOAD DEBUG] Preset not in blend file, trying persistent file: {persistent_file}")
if os.path.exists(persistent_file):
with open(persistent_file, 'r') as f:
preset_data = json.load(f)
source = 'PERSISTENT_FILE'
print(f"[PRESET LOAD DEBUG] Loading preset '{self.preset_name}' from persistent file")
print(f"[PRESET LOAD DEBUG] Persistent file preset shader properties:")
print(f"[PRESET LOAD DEBUG] disable_shadows: {preset_data.get('disable_shadows', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_reflections: {preset_data.get('disable_reflections', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_backfacing: {preset_data.get('disable_backfacing', 'NOT FOUND')}")
else:
# Fallback to legacy local file
legacy_file = os.path.join(get_preset_path(), f"{self.preset_name}.json")
print(f"[PRESET LOAD DEBUG] Preset not in persistent storage, trying legacy file: {legacy_file}")
if os.path.exists(legacy_file):
with open(legacy_file, 'r') as f:
preset_data = json.load(f)
source = 'LOCAL_FILE'
print(f"[PRESET LOAD DEBUG] Loading preset '{self.preset_name}' from legacy file")
print(f"[PRESET LOAD DEBUG] Legacy file preset shader properties:")
print(f"[PRESET LOAD DEBUG] disable_shadows: {preset_data.get('disable_shadows', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_reflections: {preset_data.get('disable_reflections', 'NOT FOUND')}")
print(f"[PRESET LOAD DEBUG] disable_backfacing: {preset_data.get('disable_backfacing', 'NOT FOUND')}")
else:
print(f"[PRESET LOAD DEBUG] Legacy file does not exist: {legacy_file}")
self.report({'ERROR'}, f"Preset not found in any storage location: {self.preset_name}")
return {'CANCELLED'}
if not preset_data:
print(f"[PRESET LOAD DEBUG] No preset data found!")
self.report({'ERROR'}, f"No data found for preset: {self.preset_name}")
if self.preset_name not in all_presets:
self.report({'ERROR'}, f"Preset '{self.preset_name}' not found")
return {'CANCELLED'}
preset_data, source = all_presets[self.preset_name]
print(f"[PRESET LOAD DEBUG] Loading preset '{self.preset_name}' from {source}")
props.is_updating = True
# Skip loading text to prevent overwriting current text

Binary file not shown.

View File

@@ -5,37 +5,31 @@ import time
import platform
# ============================================================================
# PRESET STORAGE SYSTEM - V2.0 (PERSISTENT STORAGE)
# PRESET STORAGE SYSTEM - V3.0 (SIMPLIFIED TWO-TIER)
# ============================================================================
#
# The Text Texture Generator now uses a THREE-TIER preset storage system
# that ensures preset persistence across addon updates:
# The Text Texture Generator uses a SIMPLIFIED TWO-TIER preset storage system:
#
# 1. BLEND FILE STORAGE (Highest Priority)
# - Presets travel with the .blend file
# - Perfect for project-specific presets
# - Stored in scene custom properties
#
# 2. PERSISTENT FILE STORAGE (Medium Priority)
# 1. PERSISTENT FILE STORAGE (Default)
# - Survives addon updates and Blender upgrades
# - Stored in Blender's user config directory
# - Shared across all blend files
# - Default behavior when checkbox is unchecked
#
# 3. LEGACY FILE STORAGE (Lowest Priority)
# - Old system for backward compatibility
# - Vulnerable to addon updates
# - Will be automatically migrated to persistent storage
# 2. BLEND FILE STORAGE (Optional)
# - Presets travel with the .blend file
# - Perfect for project-specific presets
# - Stored in scene custom properties
# - Used when "Save with .blend file" checkbox is checked
#
# PRIORITY SYSTEM:
# - Blend file presets ALWAYS take priority over persistent ones
# - Name conflicts show only the blend file version
#
# MIGRATION SYSTEM:
# - Automatically detects addon version changes
# - Migrates legacy presets to persistent storage
# - Automatically migrates LOCAL_FILE presets to PERSISTENT_FILE storage
# - Creates backups before migration
# - Provides detailed migration reports
#
# IMPORT/EXPORT SYSTEM:
# - Export all presets for manual backup
# - Import presets with conflict resolution
# - Supports cross-machine preset sharing
# - One-time process with safety checks
#
# ============================================================================
@@ -298,8 +292,157 @@ def delete_blend_file_preset(preset_name):
print(f"Error deleting preset from blend file: {e}")
return False
def migrate_from_legacy_system():
"""
One-time migration from old three-tier system to simplified two-tier system.
Moves LOCAL_FILE presets to PERSISTENT_FILE and creates backup.
"""
try:
legacy_dir = get_preset_path()
persistent_dir = get_persistent_preset_path()
# Check if legacy directory exists and is different from persistent
if not os.path.exists(legacy_dir) or legacy_dir == persistent_dir:
print("[TTG] No separate legacy presets directory found, migration not needed")
return True
# Check for migration marker file to avoid duplicate migrations
migration_marker = os.path.join(persistent_dir, ".migration_complete")
if os.path.exists(migration_marker):
print("[TTG] Migration already completed previously")
return True
# Find legacy preset files
legacy_files = []
for filename in os.listdir(legacy_dir):
if filename.endswith('.json'):
legacy_files.append(filename)
if not legacy_files:
print("[TTG] No legacy presets to migrate")
# Create marker even if no files to migrate
with open(migration_marker, 'w') as f:
f.write("Migration completed - no legacy presets found")
return True
print(f"[TTG] Found {len(legacy_files)} legacy presets to migrate")
# Create backup directory in persistent location
backup_dir = os.path.join(persistent_dir, "legacy_backup")
os.makedirs(backup_dir, exist_ok=True)
# Load existing persistent presets to avoid conflicts
existing_persistent = set()
if os.path.exists(persistent_dir):
for filename in os.listdir(persistent_dir):
if filename.endswith('.json'):
existing_persistent.add(filename)
migrated_count = 0
skipped_count = 0
# Migrate each legacy preset
for filename in legacy_files:
preset_name = os.path.splitext(filename)[0]
legacy_file = os.path.join(legacy_dir, filename)
persistent_file = os.path.join(persistent_dir, filename)
backup_file = os.path.join(backup_dir, filename)
try:
# Create backup copy first
import shutil
shutil.copy2(legacy_file, backup_file)
# If preset doesn't exist in persistent storage, migrate it
if filename not in existing_persistent:
shutil.copy2(legacy_file, persistent_file)
migrated_count += 1
print(f"[TTG] Migrated preset '{preset_name}' to persistent storage")
else:
skipped_count += 1
print(f"[TTG] Skipped preset '{preset_name}' (already exists in persistent storage)")
except Exception as e:
print(f"[TTG] Error migrating preset '{preset_name}': {e}")
# Create migration completion marker
with open(migration_marker, 'w') as f:
migration_info = {
"migration_date": time.time(),
"migrated_count": migrated_count,
"skipped_count": skipped_count,
"backup_location": backup_dir
}
import json
json.dump(migration_info, f, indent=2)
print(f"[TTG] ✅ Migration completed: {migrated_count} migrated, {skipped_count} skipped")
print(f"[TTG] Backup created at: {backup_dir}")
return True
except Exception as e:
print(f"[TTG] ❌ Error during migration: {e}")
return False
def get_unified_preset_list():
"""
Get unified preset list with two-tier loading and priority rules.
Returns dict with preset names as keys and (preset_data, source) as values.
Priority order: BLEND_FILE > PERSISTENT_FILE
Blend file presets always take priority over persistent ones.
"""
all_presets = {}
try:
# Load persistent presets first (lower priority)
persistent_dir = get_persistent_preset_path()
if os.path.exists(persistent_dir):
for filename in os.listdir(persistent_dir):
if filename.endswith('.json') and not filename.startswith('.'):
preset_name = os.path.splitext(filename)[0]
try:
with open(os.path.join(persistent_dir, filename), 'r') as f:
preset_data = json.load(f)
all_presets[preset_name] = (preset_data, 'PERSISTENT_FILE')
except (json.JSONDecodeError, OSError) as e:
print(f"[TTG] Error reading persistent preset {preset_name}: {e}")
# Load blend file presets and override any conflicts (higher priority)
blend_presets = get_blend_file_presets()
for name, data in blend_presets.items():
all_presets[name] = (data, 'BLEND_FILE')
except Exception as e:
print(f"[TTG] Error in get_unified_preset_list: {e}")
return all_presets
def save_preset_unified(preset_data, name, save_to_blend_file=False):
"""
Save a preset to either persistent file or blend file based on user choice.
Default behavior saves to persistent file.
"""
if save_to_blend_file:
return save_blend_file_preset(name, preset_data)
else:
# Save to persistent file
try:
persistent_dir = get_persistent_preset_path()
persistent_file = os.path.join(persistent_dir, f"{name}.json")
with open(persistent_file, 'w') as f:
json.dump(preset_data, f, indent=2)
print(f"[TTG] Saved preset '{name}' to persistent storage")
return True
except Exception as e:
print(f"[TTG] Error saving preset to persistent storage: {e}")
return False
def initialize_presets():
"""Initialize presets with simplified, reliable synchronization"""
"""Initialize the simplified preset system and run migration if needed."""
try:
import bpy
if not hasattr(bpy.context, 'scene') or not bpy.context.scene:
@@ -311,80 +454,26 @@ def initialize_presets():
print(f"[TTG] Starting preset initialization...")
# Run one-time migration from old system
migrate_from_legacy_system()
# Clear existing presets to avoid duplicates
props.presets.clear()
# Collect all presets from all sources
all_presets = {}
# 1. Load from blend file (highest priority - travels with file)
try:
blend_presets = get_blend_file_presets()
for name, data in blend_presets.items():
all_presets[name] = {
'data': data,
'source': 'BLEND_FILE'
}
print(f"[TTG] Found {len(blend_presets)} presets in blend file")
except Exception as e:
print(f"[TTG] Error loading blend file presets: {e}")
# 2. Load from persistent storage (survives addon updates)
try:
persistent_dir = get_persistent_preset_path()
if os.path.exists(persistent_dir):
for filename in os.listdir(persistent_dir):
if filename.endswith('.json'):
preset_name = os.path.splitext(filename)[0]
# Only add if not already in blend file (blend file has priority)
if preset_name not in all_presets:
try:
with open(os.path.join(persistent_dir, filename), 'r') as f:
preset_data = json.load(f)
all_presets[preset_name] = {
'data': preset_data,
'source': 'PERSISTENT_FILE'
}
except (json.JSONDecodeError, OSError) as e:
print(f"[TTG] Error reading persistent preset {preset_name}: {e}")
print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'PERSISTENT_FILE'])} persistent presets")
except Exception as e:
print(f"[TTG] Error loading persistent presets: {e}")
# 3. Load from legacy storage (backward compatibility)
try:
legacy_dir = get_preset_path()
if os.path.exists(legacy_dir) and legacy_dir != get_persistent_preset_path():
for filename in os.listdir(legacy_dir):
if filename.endswith('.json'):
preset_name = os.path.splitext(filename)[0]
# Only add if not already found in higher priority sources
if preset_name not in all_presets:
try:
with open(os.path.join(legacy_dir, filename), 'r') as f:
preset_data = json.load(f)
all_presets[preset_name] = {
'data': preset_data,
'source': 'LOCAL_FILE'
}
except (json.JSONDecodeError, OSError) as e:
print(f"[TTG] Error reading legacy preset {preset_name}: {e}")
print(f"[TTG] Found {len([p for p in all_presets.values() if p['source'] == 'LOCAL_FILE'])} legacy presets")
except Exception as e:
print(f"[TTG] Error loading legacy presets: {e}")
# Get unified preset list
all_presets = get_unified_preset_list()
# Add all presets to UI list
for preset_name, preset_info in all_presets.items():
for preset_name, (preset_data, source) in all_presets.items():
preset = props.presets.add()
preset.name = preset_name
preset.source = preset_info['source']
preset.source = source
# Report results
blend_count = len([p for p in props.presets if p.source == 'BLEND_FILE'])
persistent_count = len([p for p in props.presets if p.source == 'PERSISTENT_FILE'])
legacy_count = len([p for p in props.presets if p.source == 'LOCAL_FILE'])
print(f"[TTG] ✅ Initialized {len(props.presets)} presets ({blend_count} blend file, {persistent_count} persistent, {legacy_count} legacy)")
print(f"[TTG] ✅ Initialized {len(props.presets)} presets ({blend_count} blend file, {persistent_count} persistent)")
except Exception as e:
print(f"[TTG] Critical error in initialize_presets: {e}")

View File

@@ -180,10 +180,9 @@ class TEXT_TEXTURE_Preset(PropertyGroup):
description="Where this preset is stored",
items=[
('BLEND_FILE', 'Blend File', 'Preset stored in the blend file (travels with the file)'),
('PERSISTENT_FILE', 'Persistent File', 'Preset stored in persistent location (survives addon updates)'),
('LOCAL_FILE', 'Local File', 'Preset stored locally (legacy system)')
('PERSISTENT_FILE', 'Persistent File', 'Preset stored in persistent location (survives addon updates)')
],
default='BLEND_FILE'
default='PERSISTENT_FILE'
)
class TEXT_TEXTURE_Properties(PropertyGroup):
@@ -628,6 +627,13 @@ class TEXT_TEXTURE_Properties(PropertyGroup):
default=False
)
# Save with blend file checkbox
save_with_blend_file: BoolProperty(
name="Save with .blend file",
description="Save this preset with the current .blend file instead of persistent storage",
default=False
)
# ============================================================================
# SHADER GENERATION PROPERTIES
# ============================================================================

View File

@@ -242,12 +242,15 @@ def draw_preview_options_section(layout, props):
layout.prop(props, "preview_bg_color2", text="Color 2")
def draw_presets_section(layout, props):
"""Draw presets save/load controls"""
"""Draw unified presets save/load controls"""
# Save preset section
save_row = layout.row(align=True)
save_row.prop(props, "new_preset_name", text="", placeholder="Preset Name")
save_row.operator("text_texture.save_preset", text="Save", icon='FILE_NEW')
# Save with blend file checkbox
layout.prop(props, "save_with_blend_file")
layout.separator()
# Import/Export and refresh buttons
@@ -261,76 +264,51 @@ def draw_presets_section(layout, props):
layout.separator()
# Load presets section
# Load presets section - unified list
if props.presets:
layout.label(text=f"📁 Saved Presets ({len(props.presets)}):", icon='PRESET')
layout.label(text=f"📁 Available Presets ({len(props.presets)}):", icon='PRESET')
# Separate presets by source
blend_presets = [p for p in props.presets if p.source == 'BLEND_FILE']
persistent_presets = [p for p in props.presets if p.source == 'PERSISTENT_FILE']
local_presets = [p for p in props.presets if p.source == 'LOCAL_FILE']
# Sort presets alphabetically for consistent display
sorted_presets = sorted(props.presets, key=lambda p: p.name.lower())
# Show blend file presets first
if blend_presets:
layout.label(text="🎯 Blend File Presets (travel with file):", icon='FILE_BLEND')
for preset in blend_presets:
row = layout.row()
load_btn = row.operator("text_texture.load_preset", text=f"📋 {preset.name}")
load_btn.preset_name = preset.name
# Quick overwrite button
overwrite_btn = row.operator("text_texture.save_preset", text="", icon='FILE_REFRESH')
overwrite_btn.overwrite = True
# Set the preset name so it can be overwritten
overwrite_btn.preset_name = preset.name
delete_btn = row.operator("text_texture.delete_preset", text="", icon='TRASH')
delete_btn.preset_name = preset.name
for preset in sorted_presets:
row = layout.row(align=True)
row.scale_y = 0.9
# Storage indicator icon
if preset.source == 'BLEND_FILE':
icon = '📄' # Blend file indicator
else:
icon = '🏠' # Persistent storage indicator
# Load button with indicator
load_btn = row.operator("text_texture.load_preset", text=f"{icon} {preset.name}")
load_btn.preset_name = preset.name
# Quick overwrite button
overwrite_btn = row.operator("text_texture.save_preset", text="", icon='FILE_REFRESH')
overwrite_btn.overwrite = True
overwrite_btn.preset_name = preset.name
# Delete button
delete_btn = row.operator("text_texture.delete_preset", text="", icon='TRASH')
delete_btn.preset_name = preset.name
# Show persistent presets second
if persistent_presets:
if blend_presets:
layout.separator()
layout.label(text="🛡️ Persistent Presets (survive addon updates):", icon='PREFERENCES')
for preset in persistent_presets:
row = layout.row()
load_btn = row.operator("text_texture.load_preset", text=f"📋 {preset.name}")
load_btn.preset_name = preset.name
# Quick overwrite button
overwrite_btn = row.operator("text_texture.save_preset", text="", icon='FILE_REFRESH')
overwrite_btn.overwrite = True
# Set the preset name so it can be overwritten
overwrite_btn.preset_name = preset.name
delete_btn = row.operator("text_texture.delete_preset", text="", icon='TRASH')
delete_btn.preset_name = preset.name
# Legend for storage indicators
layout.separator()
legend_box = layout.box()
legend_box.scale_y = 0.8
legend_box.label(text="Storage Indicators:", icon='INFO')
legend_box.label(text="🏠 Persistent (survives addon updates)")
legend_box.label(text="📄 Blend file (travels with .blend file)")
# Show local presets third
if local_presets:
if blend_presets or persistent_presets:
layout.separator()
layout.label(text="💻 Local Presets (this machine only):", icon='DISK_DRIVE')
for preset in local_presets:
row = layout.row()
load_btn = row.operator("text_texture.load_preset", text=f"📋 {preset.name}")
load_btn.preset_name = preset.name
# Quick overwrite button
overwrite_btn = row.operator("text_texture.save_preset", text="", icon='FILE_REFRESH')
overwrite_btn.overwrite = True
# Set the preset name so it can be overwritten
overwrite_btn.preset_name = preset.name
delete_btn = row.operator("text_texture.delete_preset", text="", icon='TRASH')
delete_btn.preset_name = preset.name
else:
layout.label(text="📂 No presets saved yet", icon='INFO')
layout.label(text="💡 Save your first preset above!", icon='LIGHT_DATA')
layout.separator()
info_box = layout.box()
info_box.label(text=" Presets are now saved in the blend file", icon='INFO')
info_box.label(text="They will travel with your .blend file!")
info_box.label(text=" Presets: Persistent by default, blend file optional", icon='INFO')
info_box.label(text="Check the box to save with your .blend file!")
def draw_shader_section(layout, props):
"""Draw shader generation controls"""