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**: - **Preset Management System**:
- Three-tier storage: Blend File, Persistent, Local presets - Simplified two-tier storage: Persistent File (default) + Blend File (optional)
- All preset types now display properly in the interface - Unified preset list with clear storage indicators (🏠/📄)
- Presets travel with .blend files (Blend File type) - Presets travel with .blend files when "Save with .blend file" is checked
- Import/export functionality for backup and sharing - Import/export functionality for backup and sharing
- Automatic migration system for addon updates - Automatic migration from legacy three-tier system
- Conflict resolution for preset imports - Conflict resolution with blend file presets taking priority
- **Preview System**: - **Preview System**:
@@ -254,33 +254,39 @@ The addon automatically installs required dependencies:
## Preset System ## 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) ### Storage Locations (Priority Order)
1. **Blend File Storage** (Highest Priority) 1. **Blend File Storage** (Higher Priority)
- Presets stored in scene custom properties - Presets stored in scene custom properties
- Travel with your .blend file - Travel with your .blend file automatically
- Perfect for project-specific presets - 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 - 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 - Shared across all blend files
- Display with 🏠 indicator in UI
3. **Local File Storage** (Lowest Priority) ### Key Features
- Local addon-specific presets
- Automatically migrated to persistent storage when needed
### Preset Features - **Simplified UI**: Single unified preset list with clear storage indicators
- **Easy Storage Selection**: Simple "Save with .blend file" checkbox controls destination
- **Improved UI Display**: Recent bug fix ensures all three preset types (Blend File, Persistent, Local) display properly in the interface - **Priority System**: Blend file presets automatically override persistent ones with same name
- **Auto-Migration**: Seamlessly upgrades presets during addon updates - **Auto-Migration**: Seamlessly migrates old three-tier presets to new system
- **Import/Export**: Backup and share presets across installations - **Import/Export**: Backup and share presets across installations
- **Conflict Resolution**: Smart handling of duplicate preset names - **User-Friendly Messages**: Clear, non-technical confirmation messages
- **Migration Reports**: Detailed logs of preset migration operations
### 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 ## Build System
@@ -393,7 +399,7 @@ Marc Mintel <marc@mintel.me>
- Normal map generation from text alpha channels with strength/blur controls - Normal map generation from text alpha channels with strength/blur controls
- Advanced shader generation with Principled BSDF, Emission, and Transparent types - Advanced shader generation with Principled BSDF, Emission, and Transparent types
- Light path controls for shadows, reflections, and backfacing geometry - 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 - Live preview with zoomable Image Editor integration and multiple backgrounds
- Prepend/append text support with horizontal/vertical layouts - Prepend/append text support with horizontal/vertical layouts
- Custom font support with mixed-case validation and fallback systems - Custom font support with mixed-case validation and fallback systems
@@ -416,7 +422,7 @@ Marc Mintel <marc@mintel.me>
#### Full Version Features (Additional): #### Full Version Features (Additional):
- Normal map generation from text alpha channels - 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 - Advanced utility operations and batch processing
- Extended UI components and advanced styling options - Extended UI components and advanced styling options
- **File Count**: 23 files (60.1 KB package size) - **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 - `core/normal_maps.py` - Normal map generation algorithms
- `operators/preset_ops.py` - Preset management operations - `operators/preset_ops.py` - Preset management operations
- `operators/utility_ops.py` - Batch processing and utility functions - `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. 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 - **Memory Management**: Efficient processing for large textures without system overload
- **Batch Processing**: Multiple texture generation with consistent settings - **Batch Processing**: Multiple texture generation with consistent settings
### Version Strategy ### Version Strategy
#### **🆓 Free Version - Full Core Experience** #### **🆓 Free Version - Full Core Experience**
- **Complete Text Rendering Pipeline**: Professional typography with up to 1024×1024 resolution - **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 - **Seamless Blender Integration**: Automatic material node creation and assignment
- **Professional Font Support**: System font detection and custom font file compatibility - **Professional Font Support**: System font detection and custom font file compatibility
- **Full Color Control**: Complete RGBA customization with transparency support - **Full Color Control**: Complete RGBA customization with transparency support
- **No Time Limits**: Use forever - perfect for testing and smaller projects - **No Time Limits**: Use forever - perfect for testing and smaller projects
#### **⭐ Full Version - Unleash Professional Power** #### **⭐ Full Version - Unleash Professional Power**
- **Ultra-High Resolution**: Generate stunning 4096×4096 textures for commercial work - **Ultra-High Resolution**: Generate stunning 4096×4096 textures for commercial work
- **Advanced 3D Effects**: Automatic normal map generation with customizable depth and blur - **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 - **Professional Batch Processing**: Advanced utility operations and workflow automation tools
- **Extended Material Pipeline**: Advanced styling options and specialized production tools - **Extended Material Pipeline**: Advanced styling options and specialized production tools
- **Future-Proof Investment**: All upcoming premium features included with purchase - **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 ### 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 - **User-Defined Presets**: Save and organize custom configurations with improved interface
- **Project-Specific Presets**: Maintain consistent styling across project assets - **Project-Specific Presets**: Maintain consistent styling across project assets
- **Preset Import/Export**: Share configurations between team members - **Preset Import/Export**: Share configurations between team members
- **Version Control**: Preset versioning for workflow tracking - **Auto-Migration**: Seamlessly migrates old three-tier presets to new streamlined system
- **Enhanced UI Display**: Recent improvements ensure all preset types display correctly in the interface
### Material Integration ### 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 - **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 - **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 - **Batch Processing**: Process multiple textures with consistent settings and advanced utility operations
- **Professional Effects Suite**: Extended styling options and advanced material integration - **Professional Effects Suite**: Extended styling options and advanced material integration
- **Premium Modules**: Additional specialized tools for professional workflows - **Premium Modules**: Additional specialized tools for professional workflows

Binary file not shown.

View File

@@ -6,10 +6,11 @@ import json
import time import time
# Import utility functions from main module # Import utility functions from main module
from ..presets import ( from ..presets.storage_system import (
get_preset_path, get_persistent_preset_path, get_addon_version, save_preset_unified, get_unified_preset_list,
get_blend_file_presets, save_blend_file_preset, delete_blend_file_preset, get_blend_file_presets, delete_blend_file_preset,
refresh_presets_sync, ensure_presets_available refresh_presets_sync, ensure_presets_available,
get_preset_path, get_persistent_preset_path
) )
from ..utils import bl_info from ..utils import bl_info
@@ -161,46 +162,11 @@ class TEXT_TEXTURE_OT_save_preset(Operator):
preset_data['image_overlays'].append(overlay_data) preset_data['image_overlays'].append(overlay_data)
try: try:
# Primary: Save to blend file (new system) # Save based on checkbox setting - single destination only
print(f"[PRESET SAVE DEBUG] Attempting to save to blend file...") success = save_preset_unified(preset_data, preset_name, props.save_with_blend_file)
blend_success = save_blend_file_preset(preset_name, preset_data)
print(f"[PRESET SAVE DEBUG] Blend file save result: {blend_success}")
# ALWAYS save to persistent storage for cross-blend-file sharing and addon update survival if not success:
persistent_file = os.path.join(get_persistent_preset_path(), f"{preset_name}.json") self.report({'ERROR'}, f"Failed to save preset '{preset_name}'")
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")
return {'CANCELLED'} return {'CANCELLED'}
# Check if preset already exists in the collection and update it # 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: if not existing_preset:
preset = props.presets.add() preset = props.presets.add()
preset.name = preset_name preset.name = preset_name
# Priority: Blend file > Persistent file preset.source = 'BLEND_FILE' if props.save_with_blend_file else 'PERSISTENT_FILE'
preset.source = 'BLEND_FILE' if blend_success else 'PERSISTENT_FILE'
else: else:
# Update source based on where it was successfully saved # Update source based on where it was saved
existing_preset.source = 'BLEND_FILE' if blend_success else 'PERSISTENT_FILE' existing_preset.source = 'BLEND_FILE' if props.save_with_blend_file else 'PERSISTENT_FILE'
# Don't clear the input field - keep the name for easy re-saving
# props.new_preset_name = "New Preset"
# Provide clear feedback # Provide clear feedback
storage_info = "" location = "with .blend file" if props.save_with_blend_file else "to persistent storage"
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)"
if self.overwrite: if self.overwrite:
self.report({'INFO'}, f"✅ Preset '{preset_name}' updated successfully{storage_info}") self.report({'INFO'}, f"✅ Preset '{preset_name}' updated {location}")
else: 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: except (OSError, IOError, json.JSONEncodeError) as e:
self.report({'ERROR'}, f"Failed to save preset: {str(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}") print(f"[PRESET LOAD DEBUG] shader_connection: {props.shader_connection}")
try: try:
# Three-tier loading priority: Blend File > Persistent File > Legacy File # Get unified preset list and find the one to load
blend_presets = get_blend_file_presets() all_presets = get_unified_preset_list()
preset_data = None
source = 'BLEND_FILE'
print(f"[PRESET LOAD DEBUG] Available blend file presets: {list(blend_presets.keys())}") if self.preset_name not in all_presets:
self.report({'ERROR'}, f"Preset '{self.preset_name}' not found")
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}")
return {'CANCELLED'} 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 props.is_updating = True
# Skip loading text to prevent overwriting current text # Skip loading text to prevent overwriting current text

Binary file not shown.

View File

@@ -5,37 +5,31 @@ import time
import platform 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 # The Text Texture Generator uses a SIMPLIFIED TWO-TIER preset storage system:
# that ensures preset persistence across addon updates:
# #
# 1. BLEND FILE STORAGE (Highest Priority) # 1. PERSISTENT FILE STORAGE (Default)
# - Presets travel with the .blend file
# - Perfect for project-specific presets
# - Stored in scene custom properties
#
# 2. PERSISTENT FILE STORAGE (Medium Priority)
# - Survives addon updates and Blender upgrades # - Survives addon updates and Blender upgrades
# - Stored in Blender's user config directory # - Stored in Blender's user config directory
# - Shared across all blend files # - Shared across all blend files
# - Default behavior when checkbox is unchecked
# #
# 3. LEGACY FILE STORAGE (Lowest Priority) # 2. BLEND FILE STORAGE (Optional)
# - Old system for backward compatibility # - Presets travel with the .blend file
# - Vulnerable to addon updates # - Perfect for project-specific presets
# - Will be automatically migrated to persistent storage # - 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: # MIGRATION SYSTEM:
# - Automatically detects addon version changes # - Automatically migrates LOCAL_FILE presets to PERSISTENT_FILE storage
# - Migrates legacy presets to persistent storage
# - Creates backups before migration # - Creates backups before migration
# - Provides detailed migration reports # - One-time process with safety checks
#
# IMPORT/EXPORT SYSTEM:
# - Export all presets for manual backup
# - Import presets with conflict resolution
# - Supports cross-machine preset sharing
# #
# ============================================================================ # ============================================================================
@@ -298,8 +292,157 @@ def delete_blend_file_preset(preset_name):
print(f"Error deleting preset from blend file: {e}") print(f"Error deleting preset from blend file: {e}")
return False 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(): def initialize_presets():
"""Initialize presets with simplified, reliable synchronization""" """Initialize the simplified preset system and run migration if needed."""
try: try:
import bpy import bpy
if not hasattr(bpy.context, 'scene') or not bpy.context.scene: if not hasattr(bpy.context, 'scene') or not bpy.context.scene:
@@ -311,80 +454,26 @@ def initialize_presets():
print(f"[TTG] Starting preset initialization...") print(f"[TTG] Starting preset initialization...")
# Run one-time migration from old system
migrate_from_legacy_system()
# Clear existing presets to avoid duplicates # Clear existing presets to avoid duplicates
props.presets.clear() props.presets.clear()
# Collect all presets from all sources # Get unified preset list
all_presets = {} all_presets = get_unified_preset_list()
# 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}")
# Add all presets to UI 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 = props.presets.add()
preset.name = preset_name preset.name = preset_name
preset.source = preset_info['source'] preset.source = source
# Report results # Report results
blend_count = len([p for p in props.presets if p.source == 'BLEND_FILE']) 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']) 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: except Exception as e:
print(f"[TTG] Critical error in initialize_presets: {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", description="Where this preset is stored",
items=[ items=[
('BLEND_FILE', 'Blend File', 'Preset stored in the blend file (travels with the file)'), ('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)'), ('PERSISTENT_FILE', 'Persistent File', 'Preset stored in persistent location (survives addon updates)')
('LOCAL_FILE', 'Local File', 'Preset stored locally (legacy system)')
], ],
default='BLEND_FILE' default='PERSISTENT_FILE'
) )
class TEXT_TEXTURE_Properties(PropertyGroup): class TEXT_TEXTURE_Properties(PropertyGroup):
@@ -628,6 +627,13 @@ class TEXT_TEXTURE_Properties(PropertyGroup):
default=False 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 # 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") layout.prop(props, "preview_bg_color2", text="Color 2")
def draw_presets_section(layout, props): def draw_presets_section(layout, props):
"""Draw presets save/load controls""" """Draw unified presets save/load controls"""
# Save preset section # Save preset section
save_row = layout.row(align=True) save_row = layout.row(align=True)
save_row.prop(props, "new_preset_name", text="", placeholder="Preset Name") save_row.prop(props, "new_preset_name", text="", placeholder="Preset Name")
save_row.operator("text_texture.save_preset", text="Save", icon='FILE_NEW') 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() layout.separator()
# Import/Export and refresh buttons # Import/Export and refresh buttons
@@ -261,76 +264,51 @@ def draw_presets_section(layout, props):
layout.separator() layout.separator()
# Load presets section # Load presets section - unified list
if props.presets: 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 # Sort presets alphabetically for consistent display
blend_presets = [p for p in props.presets if p.source == 'BLEND_FILE'] sorted_presets = sorted(props.presets, key=lambda p: p.name.lower())
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']
# Show blend file presets first for preset in sorted_presets:
if blend_presets: row = layout.row(align=True)
layout.label(text="🎯 Blend File Presets (travel with file):", icon='FILE_BLEND') row.scale_y = 0.9
for preset in blend_presets:
row = layout.row() # Storage indicator icon
load_btn = row.operator("text_texture.load_preset", text=f"📋 {preset.name}") if preset.source == 'BLEND_FILE':
load_btn.preset_name = preset.name icon = '📄' # Blend file indicator
else:
# Quick overwrite button icon = '🏠' # Persistent storage indicator
overwrite_btn = row.operator("text_texture.save_preset", text="", icon='FILE_REFRESH')
overwrite_btn.overwrite = True # Load button with indicator
# Set the preset name so it can be overwritten load_btn = row.operator("text_texture.load_preset", text=f"{icon} {preset.name}")
overwrite_btn.preset_name = preset.name load_btn.preset_name = preset.name
delete_btn = row.operator("text_texture.delete_preset", text="", icon='TRASH') # Quick overwrite button
delete_btn.preset_name = preset.name 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 # Legend for storage indicators
if persistent_presets: layout.separator()
if blend_presets: legend_box = layout.box()
layout.separator() legend_box.scale_y = 0.8
layout.label(text="🛡️ Persistent Presets (survive addon updates):", icon='PREFERENCES') legend_box.label(text="Storage Indicators:", icon='INFO')
for preset in persistent_presets: legend_box.label(text="🏠 Persistent (survives addon updates)")
row = layout.row() legend_box.label(text="📄 Blend file (travels with .blend file)")
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
# 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: else:
layout.label(text="📂 No presets saved yet", icon='INFO') layout.label(text="📂 No presets saved yet", icon='INFO')
layout.label(text="💡 Save your first preset above!", icon='LIGHT_DATA') layout.label(text="💡 Save your first preset above!", icon='LIGHT_DATA')
layout.separator() layout.separator()
info_box = layout.box() info_box = layout.box()
info_box.label(text=" Presets are now saved in the blend file", icon='INFO') info_box.label(text=" Presets: Persistent by default, blend file optional", icon='INFO')
info_box.label(text="They will travel with your .blend file!") info_box.label(text="Check the box to save with your .blend file!")
def draw_shader_section(layout, props): def draw_shader_section(layout, props):
"""Draw shader generation controls""" """Draw shader generation controls"""