deployment
This commit is contained in:
105
DEPLOYMENT.md
Normal file
105
DEPLOYMENT.md
Normal 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
|
||||
```
|
||||
50
README.md
50
README.md
@@ -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.
|
||||
|
||||
@@ -43,10 +43,10 @@ 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
|
||||
- **Seamless Blender Integration**: Automatic material node creation and assignment
|
||||
@@ -55,9 +55,10 @@ Cross-platform compatibility across Windows, macOS, and Linux environments, comb
|
||||
- **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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
src/operators/__pycache__/preset_ops.cpython-311.pyc
Normal file
BIN
src/operators/__pycache__/preset_ops.cpython-311.pyc
Normal file
Binary file not shown.
@@ -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,56 +257,16 @@ 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}")
|
||||
if self.preset_name not in all_presets:
|
||||
self.report({'ERROR'}, f"Preset '{self.preset_name}' not found")
|
||||
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'}
|
||||
preset_data, source = all_presets[self.preset_name]
|
||||
|
||||
print(f"[PRESET LOAD DEBUG] Loading preset '{self.preset_name}' from {source}")
|
||||
|
||||
props.is_updating = True
|
||||
|
||||
|
||||
BIN
src/presets/__pycache__/storage_system.cpython-311.pyc
Normal file
BIN
src/presets/__pycache__/storage_system.cpython-311.pyc
Normal file
Binary file not shown.
@@ -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}")
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
# ============================================================================
|
||||
|
||||
Binary file not shown.
@@ -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}")
|
||||
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
|
||||
# Set the preset name so it can be overwritten
|
||||
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:
|
||||
# Legend for storage indicators
|
||||
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
|
||||
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)")
|
||||
|
||||
# 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:
|
||||
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"""
|
||||
|
||||
Reference in New Issue
Block a user