diff --git a/.specstory/history/2025-12-17_23-00Z-compressor-access-violations.md b/.specstory/history/2025-12-17_23-00Z-compressor-access-violations.md new file mode 100644 index 0000000..1b244f4 --- /dev/null +++ b/.specstory/history/2025-12-17_23-00Z-compressor-access-violations.md @@ -0,0 +1,22886 @@ + + + + +# Compressor access violations (2025-12-17 23:00Z) + +_**User**_ + +why is the compressor getting exception access violations? + +00:00.718 blend | Read blend: "A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_6.blend" +Blender 5.0.1 (hash a3db93c5b259 built 2025-12-16 01:32:30) +Writing: C:\Users\Nathan\AppData\Local\Temp\blender.crash.txt + + +--- + +_**Agent (model default, mode Agent)**_ + + +Investigating the access violation. Reviewing compressor-related code and Blender file handling. + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How does the Blender file compression work and what causes access violations?", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 67 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\n\necho Starting Blender file compression...\n\nREM Check if files were dragged onto the batch file\nif \"%~1\"==\"\" (\n echo.\n echo Usage: Drag .blend files onto this batch file to compress them.\n echo Output will be saved to the \\output folder.\n echo.\n pause\n exit /b 1\n)\n\nREM Create output directory if it doesn't exist\nif not exist \"output\" mkdir \"output\"\n\nREM Initialize counters and tracking\nset \"processed=0\"\nset \"crashed=0\"\nset \"skipped=0\"\nset \"deleted_similar=0\"\nset \"threshold_percent=10\"\n\nREM Create a temporary Python script for compression\nset \"TEMP_SCRIPT=compress_blend_temp.py\"\necho Creating Python script: !TEMP_SCRIPT!\n\n(\necho import bpy\necho import os\necho import sys\necho.\necho # Enable compression globally\necho bpy.context.preferences.filepaths.save_version = 0\necho bpy.context.preferences.filepaths.use_file_compression = True\necho.\necho # Create output directory if it doesn't exist\necho if not os.path.exists^(\"output\"^):\necho os.makedirs^(\"output\"^)\necho.\necho # Get command line arguments ^(the dragged files^)\necho blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []\necho.\necho print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)\necho.\necho def is_compressed_file^(path^):\necho try:\necho with open^(path, 'rb'^) as f:\necho header = f.read^(4^)\necho if header[:2] == b'\\x1f\\x8b': # gzip\necho return True\necho if header == b'\\x28\\xb5\\x2f\\xfd': # zstd\necho return True\necho return False\necho except Exception:\necho return False\necho.\necho for blend_file in blend_files:\necho if blend_file.lower^(^).endswith^(\".blend\"^):\necho try:\necho # Create output path in output folder\necho filename = os.path.basename^(blend_file^)\necho output_file = os.path.join^(\"output\", filename^)\necho # Check if output file already exists\necho if os.path.exists^(output_file^):", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "echo Starting Blender file compression...", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "REM Check if files were dragged onto the batch file", + "lineNumber": 6 + }, + { + "text": "if \"%~1\"==\"\" (", + "lineNumber": 7 + }, + { + "text": " echo.", + "lineNumber": 8 + }, + { + "text": " echo Usage: Drag .blend files onto this batch file to compress them.", + "lineNumber": 9 + }, + { + "text": " echo Output will be saved to the \\output folder.", + "lineNumber": 10 + }, + { + "text": " echo.", + "lineNumber": 11 + }, + { + "text": " pause", + "lineNumber": 12 + }, + { + "text": " exit /b 1", + "lineNumber": 13 + }, + { + "text": ")", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "REM Create output directory if it doesn't exist", + "lineNumber": 16 + }, + { + "text": "if not exist \"output\" mkdir \"output\"", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "REM Initialize counters and tracking", + "lineNumber": 19 + }, + { + "text": "set \"processed=0\"", + "lineNumber": 20 + }, + { + "text": "set \"crashed=0\"", + "lineNumber": 21 + }, + { + "text": "set \"skipped=0\"", + "lineNumber": 22 + }, + { + "text": "set \"deleted_similar=0\"", + "lineNumber": 23 + }, + { + "text": "set \"threshold_percent=10\"", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "REM Create a temporary Python script for compression", + "lineNumber": 26 + }, + { + "text": "set \"TEMP_SCRIPT=compress_blend_temp.py\"", + "lineNumber": 27 + }, + { + "text": "echo Creating Python script: !TEMP_SCRIPT!", + "lineNumber": 28 + }, + { + "lineNumber": 29 + }, + { + "text": "(", + "lineNumber": 30 + }, + { + "text": "echo import bpy", + "lineNumber": 31 + }, + { + "text": "echo import os", + "lineNumber": 32 + }, + { + "text": "echo import sys", + "lineNumber": 33 + }, + { + "text": "echo.", + "lineNumber": 34 + }, + { + "text": "echo # Enable compression globally", + "lineNumber": 35 + }, + { + "text": "echo bpy.context.preferences.filepaths.save_version = 0", + "lineNumber": 36 + }, + { + "text": "echo bpy.context.preferences.filepaths.use_file_compression = True", + "lineNumber": 37 + }, + { + "text": "echo.", + "lineNumber": 38 + }, + { + "text": "echo # Create output directory if it doesn't exist", + "lineNumber": 39 + }, + { + "text": "echo if not os.path.exists^(\"output\"^):", + "lineNumber": 40 + }, + { + "text": "echo os.makedirs^(\"output\"^)", + "lineNumber": 41 + }, + { + "text": "echo.", + "lineNumber": 42 + }, + { + "text": "echo # Get command line arguments ^(the dragged files^)", + "lineNumber": 43 + }, + { + "text": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []", + "lineNumber": 44 + }, + { + "text": "echo.", + "lineNumber": 45 + }, + { + "text": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)", + "lineNumber": 46 + }, + { + "text": "echo.", + "lineNumber": 47 + }, + { + "text": "echo def is_compressed_file^(path^):", + "lineNumber": 48 + }, + { + "text": "echo try:", + "lineNumber": 49 + }, + { + "text": "echo with open^(path, 'rb'^) as f:", + "lineNumber": 50 + }, + { + "text": "echo header = f.read^(4^)", + "lineNumber": 51 + }, + { + "text": "echo if header[:2] == b'\\x1f\\x8b': # gzip", + "lineNumber": 52 + }, + { + "text": "echo return True", + "lineNumber": 53 + }, + { + "text": "echo if header == b'\\x28\\xb5\\x2f\\xfd': # zstd", + "lineNumber": 54 + }, + { + "text": "echo return True", + "lineNumber": 55 + }, + { + "text": "echo return False", + "lineNumber": 56 + }, + { + "text": "echo except Exception:", + "lineNumber": 57 + }, + { + "text": "echo return False", + "lineNumber": 58 + }, + { + "text": "echo.", + "lineNumber": 59 + }, + { + "text": "echo for blend_file in blend_files:", + "lineNumber": 60 + }, + { + "text": "echo if blend_file.lower^(^).endswith^(\".blend\"^):", + "lineNumber": 61 + }, + { + "text": "echo try:", + "lineNumber": 62 + }, + { + "text": "echo # Create output path in output folder", + "lineNumber": 63 + }, + { + "text": "echo filename = os.path.basename^(blend_file^)", + "lineNumber": 64 + }, + { + "text": "echo output_file = os.path.join^(\"output\", filename^)", + "lineNumber": 65 + }, + { + "text": "echo # Check if output file already exists", + "lineNumber": 66 + }, + { + "text": "echo if os.path.exists^(output_file^):", + "lineNumber": 67 + } + ] + }, + "score": 0.38407179713249207 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 65 + }, + "endPosition": { + "line": 118 + } + }, + "contents": "echo # Check if output file already exists\necho if os.path.exists^(output_file^):\necho print^(\"SKIP_EXISTING:\" + blend_file^)\necho continue\necho # Skip if source is already compressed\necho if is_compressed_file^(blend_file^):\necho print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)\necho continue\necho print^(\"PROCESSING:\" + blend_file^)\necho # Load the blend file\necho bpy.ops.wm.open_mainfile^(filepath=blend_file^)\necho # Save with compression to output folder\necho bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho except Exception as e:\necho print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho else:\necho print^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"COMPRESSION_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n echo Script created successfully: !TEMP_SCRIPT!\n) else (\n echo ERROR: Failed to create script file!\n pause\n exit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho COMPRESSION REPORT\necho ========================================\necho.\n\nREM Count processed files and get file info\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1", + "signatures": {}, + "detailedLines": [ + { + "text": "echo # Check if output file already exists", + "lineNumber": 66 + }, + { + "text": "echo if os.path.exists^(output_file^):", + "lineNumber": 67 + }, + { + "text": "echo print^(\"SKIP_EXISTING:\" + blend_file^)", + "lineNumber": 68 + }, + { + "text": "echo continue", + "lineNumber": 69 + }, + { + "text": "echo # Skip if source is already compressed", + "lineNumber": 70 + }, + { + "text": "echo if is_compressed_file^(blend_file^):", + "lineNumber": 71 + }, + { + "text": "echo print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)", + "lineNumber": 72 + }, + { + "text": "echo continue", + "lineNumber": 73 + }, + { + "text": "echo print^(\"PROCESSING:\" + blend_file^)", + "lineNumber": 74 + }, + { + "text": "echo # Load the blend file", + "lineNumber": 75 + }, + { + "text": "echo bpy.ops.wm.open_mainfile^(filepath=blend_file^)", + "lineNumber": 76 + }, + { + "text": "echo # Save with compression to output folder", + "lineNumber": 77 + }, + { + "text": "echo bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 78 + }, + { + "text": "echo print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 79 + }, + { + "text": "echo except Exception as e:", + "lineNumber": 80 + }, + { + "text": "echo print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 81 + }, + { + "text": "echo else:", + "lineNumber": 82 + }, + { + "text": "echo print^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 83 + }, + { + "text": "echo.", + "lineNumber": 84 + }, + { + "text": "echo print^(\"COMPRESSION_COMPLETE\"^)", + "lineNumber": 85 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 86 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 89 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 90 + }, + { + "text": " echo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 91 + }, + { + "text": ") else (", + "lineNumber": 92 + }, + { + "text": " echo ERROR: Failed to create script file!", + "lineNumber": 93 + }, + { + "text": " pause", + "lineNumber": 94 + }, + { + "text": " exit /b 1", + "lineNumber": 95 + }, + { + "text": ")", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 98 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 99 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 100 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 101 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 104 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 105 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 106 + }, + { + "lineNumber": 107 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 108 + }, + { + "text": "echo.", + "lineNumber": 109 + }, + { + "text": "echo ========================================", + "lineNumber": 110 + }, + { + "text": "echo COMPRESSION REPORT", + "lineNumber": 111 + }, + { + "text": "echo ========================================", + "lineNumber": 112 + }, + { + "text": "echo.", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": "REM Count processed files and get file info", + "lineNumber": 115 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 116 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 117 + }, + { + "text": " set /a processed+=1", + "lineNumber": 118 + } + ] + }, + "score": 0.3633953332901001 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n try:\n # Create temporary archive file path (but don't create the file - let 7z create it)\n temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)\n temp_zip = Path(temp_zip_path)\n \n # Create list file with absolute path\n fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)\n list_file_path = Path(temp_path)\n with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:\n for rel_path in file_list:\n list_file.write(rel_path + \"\\n\")\n list_file.flush()\n os.fsync(list_file.fileno()) # Ensure data is written to disk\n # File is closed here by context manager, small delay to ensure OS releases handle\n time.sleep(0.1)\n \n # Use absolute paths for both list file and temp zip\n list_file_abs = list_file_path.resolve()\n temp_zip_abs = temp_zip.resolve()\n # Create archive in temp location first (7z will create it fresh)\n cmd = [\n SEVEN_Z_EXE,\n \"a\",\n \"-y\",\n \"-bb0\", # Suppress progress output\n f\"-mx={COMPRESSION_LEVEL}\",\n \"-t7z\", # Use 7z format, not zip\n ]\n \n # Set compression method and memory/dictionary size based on method\n # At compression level 0, use Copy (store) method for maximum speed\n FIXED_DICT_SIZE_MB = 1024\n if COMPRESSION_LEVEL == 0:\n # Level 0 = no compression, just store files (fastest)\n cmd.append(\"-m0=Copy\")", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 697 + }, + { + "text": " try:", + "lineNumber": 698 + }, + { + "text": " # Create temporary archive file path (but don't create the file - let 7z create it)", + "lineNumber": 699 + }, + { + "text": " temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)", + "lineNumber": 700 + }, + { + "text": " temp_zip = Path(temp_zip_path)", + "lineNumber": 701 + }, + { + "text": " ", + "lineNumber": 702 + }, + { + "text": " # Create list file with absolute path", + "lineNumber": 703 + }, + { + "text": " fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)", + "lineNumber": 704 + }, + { + "text": " list_file_path = Path(temp_path)", + "lineNumber": 705 + }, + { + "text": " with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:", + "lineNumber": 706 + }, + { + "text": " for rel_path in file_list:", + "lineNumber": 707 + }, + { + "text": " list_file.write(rel_path + \"\\n\")", + "lineNumber": 708 + }, + { + "text": " list_file.flush()", + "lineNumber": 709 + }, + { + "text": " os.fsync(list_file.fileno()) # Ensure data is written to disk", + "lineNumber": 710 + }, + { + "text": " # File is closed here by context manager, small delay to ensure OS releases handle", + "lineNumber": 711 + }, + { + "text": " time.sleep(0.1)", + "lineNumber": 712 + }, + { + "text": " ", + "lineNumber": 713 + }, + { + "text": " # Use absolute paths for both list file and temp zip", + "lineNumber": 714 + }, + { + "text": " list_file_abs = list_file_path.resolve()", + "lineNumber": 715 + }, + { + "text": " temp_zip_abs = temp_zip.resolve()", + "lineNumber": 716 + }, + { + "text": " # Create archive in temp location first (7z will create it fresh)", + "lineNumber": 717 + }, + { + "text": " cmd = [", + "lineNumber": 718 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 719 + }, + { + "text": " \"a\",", + "lineNumber": 720 + }, + { + "text": " \"-y\",", + "lineNumber": 721 + }, + { + "text": " \"-bb0\", # Suppress progress output", + "lineNumber": 722 + }, + { + "text": " f\"-mx={COMPRESSION_LEVEL}\",", + "lineNumber": 723 + }, + { + "text": " \"-t7z\", # Use 7z format, not zip", + "lineNumber": 724 + }, + { + "text": " ]", + "lineNumber": 725 + }, + { + "text": " ", + "lineNumber": 726 + }, + { + "text": " # Set compression method and memory/dictionary size based on method", + "lineNumber": 727 + }, + { + "text": " # At compression level 0, use Copy (store) method for maximum speed", + "lineNumber": 728 + }, + { + "text": " FIXED_DICT_SIZE_MB = 1024", + "lineNumber": 729 + }, + { + "text": " if COMPRESSION_LEVEL == 0:", + "lineNumber": 730 + }, + { + "text": " # Level 0 = no compression, just store files (fastest)", + "lineNumber": 731 + }, + { + "text": " cmd.append(\"-m0=Copy\")", + "lineNumber": 732 + } + ] + }, + "score": 0.33957433700561523 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n # Mark as moved so we don't delete it\n finally:\n # Clean up temp zip if it wasn't moved\n if temp_zip and temp_zip.exists():\n try:\n temp_zip.unlink(missing_ok=True)\n except OSError:\n pass\n # Clean up list file, with retry in case 7z still has it open\n if list_file_path and list_file_path.exists():\n for attempt in range(3):\n try:\n list_file_path.unlink(missing_ok=True)\n break\n except PermissionError:\n if attempt < 2:\n time.sleep(0.1) # Wait 100ms before retry\n else:\n # Last attempt failed, just log and continue\n # The temp file will be cleaned up by the OS eventually\n pass\n return\n\n # Use zipfile (only if ZIPPER_TYPE == \"zip\")\n if ZIPPER_TYPE == \"zip\":\n from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile\n\n zip_path.parent.mkdir(parents=True, exist_ok=True)\n if COMPRESSION_LEVEL <= 0:\n compression = ZIP_STORED\n zip_kwargs = {}\n else:\n compression = ZIP_DEFLATED\n zip_kwargs = {\"compresslevel\": COMPRESSION_LEVEL}\n\n with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:\n # Sort files to ensure consistent archive ordering (matches compute_state)\n files = sorted(\n iter_sequence_files(seq_dir),\n key=lambda p: p.relative_to(seq_dir).as_posix(),\n )\n for file_path in files:\n archive.write(file_path, arcname=file_path.relative_to(seq_dir).as_posix())\n return\n\n # Unknown ZIPPER_TYPE - fail with clear error\n raise", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "text": " # Mark as moved so we don't delete it", + "lineNumber": 800 + }, + { + "text": " finally:", + "lineNumber": 801 + }, + { + "text": " # Clean up temp zip if it wasn't moved", + "lineNumber": 802 + }, + { + "text": " if temp_zip and temp_zip.exists():", + "lineNumber": 803 + }, + { + "text": " try:", + "lineNumber": 804 + }, + { + "text": " temp_zip.unlink(missing_ok=True)", + "lineNumber": 805 + }, + { + "text": " except OSError:", + "lineNumber": 806 + }, + { + "text": " pass", + "lineNumber": 807 + }, + { + "text": " # Clean up list file, with retry in case 7z still has it open", + "lineNumber": 808 + }, + { + "text": " if list_file_path and list_file_path.exists():", + "lineNumber": 809 + }, + { + "text": " for attempt in range(3):", + "lineNumber": 810 + }, + { + "text": " try:", + "lineNumber": 811 + }, + { + "text": " list_file_path.unlink(missing_ok=True)", + "lineNumber": 812 + }, + { + "text": " break", + "lineNumber": 813 + }, + { + "text": " except PermissionError:", + "lineNumber": 814 + }, + { + "text": " if attempt < 2:", + "lineNumber": 815 + }, + { + "text": " time.sleep(0.1) # Wait 100ms before retry", + "lineNumber": 816 + }, + { + "text": " else:", + "lineNumber": 817 + }, + { + "text": " # Last attempt failed, just log and continue", + "lineNumber": 818 + }, + { + "text": " # The temp file will be cleaned up by the OS eventually", + "lineNumber": 819 + }, + { + "text": " pass", + "lineNumber": 820 + }, + { + "text": " return", + "lineNumber": 821 + }, + { + "lineNumber": 822 + }, + { + "text": " # Use zipfile (only if ZIPPER_TYPE == \"zip\")", + "lineNumber": 823 + }, + { + "text": " if ZIPPER_TYPE == \"zip\":", + "lineNumber": 824 + }, + { + "text": " from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile", + "lineNumber": 825 + }, + { + "lineNumber": 826 + }, + { + "text": " zip_path.parent.mkdir(parents=True, exist_ok=True)", + "lineNumber": 827 + }, + { + "text": " if COMPRESSION_LEVEL <= 0:", + "lineNumber": 828 + }, + { + "text": " compression = ZIP_STORED", + "lineNumber": 829 + }, + { + "text": " zip_kwargs = {}", + "lineNumber": 830 + }, + { + "text": " else:", + "lineNumber": 831 + }, + { + "text": " compression = ZIP_DEFLATED", + "lineNumber": 832 + }, + { + "text": " zip_kwargs = {\"compresslevel\": COMPRESSION_LEVEL}", + "lineNumber": 833 + }, + { + "lineNumber": 834 + }, + { + "text": " with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:", + "lineNumber": 835 + }, + { + "text": " # Sort files to ensure consistent archive ordering (matches compute_state)", + "lineNumber": 836 + }, + { + "text": " files = sorted(", + "lineNumber": 837 + }, + { + "text": " iter_sequence_files(seq_dir),", + "lineNumber": 838 + }, + { + "text": " key=lambda p: p.relative_to(seq_dir).as_posix(),", + "lineNumber": 839 + }, + { + "text": " )", + "lineNumber": 840 + }, + { + "text": " for file_path in files:", + "lineNumber": 841 + }, + { + "text": " archive.write(file_path, arcname=file_path.relative_to(seq_dir).as_posix())", + "lineNumber": 842 + }, + { + "text": " return", + "lineNumber": 843 + }, + { + "lineNumber": 844 + }, + { + "text": " # Unknown ZIPPER_TYPE - fail with clear error", + "lineNumber": 845 + }, + { + "text": " raise", + "lineNumber": 846 + } + ] + }, + "score": 0.31317469477653503 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n if COMPRESSION_METHOD == \"PPMd\":\n # PPMd is single-threaded, so -mmt won't help\n # But we can still set it for consistency\n cmd.append(\"-mmt=on\")\n elif worker_count == 1:\n # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)\n cmd.append(f\"-mmt={cpu_cores}\")\n else:\n # Multiple workers: use auto mode (7z will manage threads)\n cmd.append(\"-mmt=on\")\n \n cmd.extend([\n str(temp_zip_abs),\n f\"@{list_file_abs}\",\n ])\n \n # Log the command in verbose mode for debugging\n if verbose:\n cmd_str = \" \".join(cmd)\n log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)\n \n result = subprocess.run(\n cmd,\n cwd=seq_dir,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z compression failed: {error_msg}\")\n \n # Move temp zip to final location, replacing any existing file\n if zip_path.exists():\n zip_path.unlink()\n temp_zip.replace(zip_path)\n temp_zip = None # Mark as moved so we don't delete it\n finally:\n # Clean up temp zip if it wasn't moved", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 760 + }, + { + "text": " if COMPRESSION_METHOD == \"PPMd\":", + "lineNumber": 761 + }, + { + "text": " # PPMd is single-threaded, so -mmt won't help", + "lineNumber": 762 + }, + { + "text": " # But we can still set it for consistency", + "lineNumber": 763 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 764 + }, + { + "text": " elif worker_count == 1:", + "lineNumber": 765 + }, + { + "text": " # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)", + "lineNumber": 766 + }, + { + "text": " cmd.append(f\"-mmt={cpu_cores}\")", + "lineNumber": 767 + }, + { + "text": " else:", + "lineNumber": 768 + }, + { + "text": " # Multiple workers: use auto mode (7z will manage threads)", + "lineNumber": 769 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 770 + }, + { + "text": " ", + "lineNumber": 771 + }, + { + "text": " cmd.extend([", + "lineNumber": 772 + }, + { + "text": " str(temp_zip_abs),", + "lineNumber": 773 + }, + { + "text": " f\"@{list_file_abs}\",", + "lineNumber": 774 + }, + { + "text": " ])", + "lineNumber": 775 + }, + { + "text": " ", + "lineNumber": 776 + }, + { + "text": " # Log the command in verbose mode for debugging", + "lineNumber": 777 + }, + { + "text": " if verbose:", + "lineNumber": 778 + }, + { + "text": " cmd_str = \" \".join(cmd)", + "lineNumber": 779 + }, + { + "text": " log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)", + "lineNumber": 780 + }, + { + "text": " ", + "lineNumber": 781 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 782 + }, + { + "text": " cmd,", + "lineNumber": 783 + }, + { + "text": " cwd=seq_dir,", + "lineNumber": 784 + }, + { + "text": " check=False,", + "lineNumber": 785 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 786 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 787 + }, + { + "text": " text=True,", + "lineNumber": 788 + }, + { + "text": " )", + "lineNumber": 789 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 790 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 791 + }, + { + "text": " if result.stdout:", + "lineNumber": 792 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 793 + }, + { + "text": " raise RuntimeError(f\"7z compression failed: {error_msg}\")", + "lineNumber": 794 + }, + { + "text": " ", + "lineNumber": 795 + }, + { + "text": " # Move temp zip to final location, replacing any existing file", + "lineNumber": 796 + }, + { + "text": " if zip_path.exists():", + "lineNumber": 797 + }, + { + "text": " zip_path.unlink()", + "lineNumber": 798 + }, + { + "text": " temp_zip.replace(zip_path)", + "lineNumber": 799 + }, + { + "text": " temp_zip = None # Mark as moved so we don't delete it", + "lineNumber": 800 + }, + { + "text": " finally:", + "lineNumber": 801 + }, + { + "text": " # Clean up temp zip if it wasn't moved", + "lineNumber": 802 + } + ] + }, + "score": 0.3123857080936432 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n FIXED_DICT_SIZE_MB = 1024\n if COMPRESSION_LEVEL == 0:\n # Level 0 = no compression, just store files (fastest)\n cmd.append(\"-m0=Copy\")\n # Copy method doesn't need threading, but enable it anyway for consistency\n cmd.append(\"-mmt=on\")\n else:\n # Compression levels 1-9: use configured compression method\n if COMPRESSION_METHOD == \"PPMd\":\n # PPMd: specify memory as part of method string\n cmd.append(f\"-m0=PPMd:mem={FIXED_DICT_SIZE_MB}m\")\n elif COMPRESSION_METHOD == \"LZMA2\":\n # LZMA2: use -md for dictionary size\n cmd.append(\"-m0=LZMA2\")\n cmd.append(f\"-md={FIXED_DICT_SIZE_MB}m\")\n elif COMPRESSION_METHOD == \"BZip2\":\n # BZip2: use -md for dictionary size (smaller max: 900KB)\n max_bzip2_dict = min(FIXED_DICT_SIZE_MB, 900) # BZip2 max is 900KB\n cmd.append(\"-m0=BZip2\")\n cmd.append(f\"-md={max_bzip2_dict}k\")\n elif COMPRESSION_METHOD == \"Deflate\":\n # Deflate: doesn't use dictionary size parameter\n cmd.append(\"-m0=Deflate\")\n else:\n # Fallback: use LZMA2\n cmd.append(\"-m0=LZMA2\")\n cmd.append(f\"-md={FIXED_DICT_SIZE_MB}m\")\n \n # CPU thread allocation: when there's only 1 worker, use all CPU cores\n # When there are multiple workers, use auto mode to let 7z decide\n # Note: PPMd is single-threaded and won't benefit from -mmt\n cpu_cores = os.cpu_count() or 1\n if COMPRESSION_METHOD == \"PPMd\":\n # PPMd is single-threaded, so -mmt won't help", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 728 + }, + { + "text": " FIXED_DICT_SIZE_MB = 1024", + "lineNumber": 729 + }, + { + "text": " if COMPRESSION_LEVEL == 0:", + "lineNumber": 730 + }, + { + "text": " # Level 0 = no compression, just store files (fastest)", + "lineNumber": 731 + }, + { + "text": " cmd.append(\"-m0=Copy\")", + "lineNumber": 732 + }, + { + "text": " # Copy method doesn't need threading, but enable it anyway for consistency", + "lineNumber": 733 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 734 + }, + { + "text": " else:", + "lineNumber": 735 + }, + { + "text": " # Compression levels 1-9: use configured compression method", + "lineNumber": 736 + }, + { + "text": " if COMPRESSION_METHOD == \"PPMd\":", + "lineNumber": 737 + }, + { + "text": " # PPMd: specify memory as part of method string", + "lineNumber": 738 + }, + { + "text": " cmd.append(f\"-m0=PPMd:mem={FIXED_DICT_SIZE_MB}m\")", + "lineNumber": 739 + }, + { + "text": " elif COMPRESSION_METHOD == \"LZMA2\":", + "lineNumber": 740 + }, + { + "text": " # LZMA2: use -md for dictionary size", + "lineNumber": 741 + }, + { + "text": " cmd.append(\"-m0=LZMA2\")", + "lineNumber": 742 + }, + { + "text": " cmd.append(f\"-md={FIXED_DICT_SIZE_MB}m\")", + "lineNumber": 743 + }, + { + "text": " elif COMPRESSION_METHOD == \"BZip2\":", + "lineNumber": 744 + }, + { + "text": " # BZip2: use -md for dictionary size (smaller max: 900KB)", + "lineNumber": 745 + }, + { + "text": " max_bzip2_dict = min(FIXED_DICT_SIZE_MB, 900) # BZip2 max is 900KB", + "lineNumber": 746 + }, + { + "text": " cmd.append(\"-m0=BZip2\")", + "lineNumber": 747 + }, + { + "text": " cmd.append(f\"-md={max_bzip2_dict}k\")", + "lineNumber": 748 + }, + { + "text": " elif COMPRESSION_METHOD == \"Deflate\":", + "lineNumber": 749 + }, + { + "text": " # Deflate: doesn't use dictionary size parameter", + "lineNumber": 750 + }, + { + "text": " cmd.append(\"-m0=Deflate\")", + "lineNumber": 751 + }, + { + "text": " else:", + "lineNumber": 752 + }, + { + "text": " # Fallback: use LZMA2", + "lineNumber": 753 + }, + { + "text": " cmd.append(\"-m0=LZMA2\")", + "lineNumber": 754 + }, + { + "text": " cmd.append(f\"-md={FIXED_DICT_SIZE_MB}m\")", + "lineNumber": 755 + }, + { + "text": " ", + "lineNumber": 756 + }, + { + "text": " # CPU thread allocation: when there's only 1 worker, use all CPU cores", + "lineNumber": 757 + }, + { + "text": " # When there are multiple workers, use auto mode to let 7z decide", + "lineNumber": 758 + }, + { + "text": " # Note: PPMd is single-threaded and won't benefit from -mmt", + "lineNumber": 759 + }, + { + "text": " cpu_cores = os.cpu_count() or 1", + "lineNumber": 760 + }, + { + "text": " if COMPRESSION_METHOD == \"PPMd\":", + "lineNumber": 761 + }, + { + "text": " # PPMd is single-threaded, so -mmt won't help", + "lineNumber": 762 + } + ] + }, + "score": 0.3013975918292999 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 115 + }, + "endPosition": { + "line": 172, + "column": 6 + } + }, + "contents": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1\n set \"original_file=%%b\"\n set \"compressed_file=%%c\"\n \n REM Get file sizes\n for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"\n for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"\n \n echo [!processed!] !original_file!\n echo Original: !original_size! bytes\n echo Compressed: !compressed_size! bytes\n set /a \"savings=!original_size! - !compressed_size!\"\n if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)\n echo Savings: !savings! bytes\n echo Reduction: !percent!%%\n if !percent! lss !threshold_percent! (\n del \"!compressed_file!\" 2>nul\n set /a deleted_similar+=1\n echo Deleted: too similar to original\n )\n echo.\n ) else if \"%%a\"==\"CRASH\" (\n set /a crashed+=1\n echo [!crashed!] CRASHED: %%b\n echo Error: %%c\n echo.\n ) else if \"%%a\"==\"SKIP_EXISTING\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already exists^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(not .blend^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already compressed^): %%b\n echo.\n )\n)\n\nREM Clean up output file\ndel /f /q \"blender_output.txt\" 2>nul\n\necho ========================================\necho SUMMARY\necho ========================================\necho Processed: !processed! files\necho Crashed: !crashed! files\necho Skipped: !skipped! files\necho Deleted ^(too similar^): !deleted_similar! files\necho Total: %* files\necho ========================================\necho.\necho Done!\npause ", + "signatures": {}, + "detailedLines": [ + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 116 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 117 + }, + { + "text": " set /a processed+=1", + "lineNumber": 118 + }, + { + "text": " set \"original_file=%%b\"", + "lineNumber": 119 + }, + { + "text": " set \"compressed_file=%%c\"", + "lineNumber": 120 + }, + { + "text": " ", + "lineNumber": 121 + }, + { + "text": " REM Get file sizes", + "lineNumber": 122 + }, + { + "text": " for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"", + "lineNumber": 123 + }, + { + "text": " for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"", + "lineNumber": 124 + }, + { + "text": " ", + "lineNumber": 125 + }, + { + "text": " echo [!processed!] !original_file!", + "lineNumber": 126 + }, + { + "text": " echo Original: !original_size! bytes", + "lineNumber": 127 + }, + { + "text": " echo Compressed: !compressed_size! bytes", + "lineNumber": 128 + }, + { + "text": " set /a \"savings=!original_size! - !compressed_size!\"", + "lineNumber": 129 + }, + { + "text": " if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)", + "lineNumber": 130 + }, + { + "text": " echo Savings: !savings! bytes", + "lineNumber": 131 + }, + { + "text": " echo Reduction: !percent!%%", + "lineNumber": 132 + }, + { + "text": " if !percent! lss !threshold_percent! (", + "lineNumber": 133 + }, + { + "text": " del \"!compressed_file!\" 2>nul", + "lineNumber": 134 + }, + { + "text": " set /a deleted_similar+=1", + "lineNumber": 135 + }, + { + "text": " echo Deleted: too similar to original", + "lineNumber": 136 + }, + { + "text": " )", + "lineNumber": 137 + }, + { + "text": " echo.", + "lineNumber": 138 + }, + { + "text": " ) else if \"%%a\"==\"CRASH\" (", + "lineNumber": 139 + }, + { + "text": " set /a crashed+=1", + "lineNumber": 140 + }, + { + "text": " echo [!crashed!] CRASHED: %%b", + "lineNumber": 141 + }, + { + "text": " echo Error: %%c", + "lineNumber": 142 + }, + { + "text": " echo.", + "lineNumber": 143 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_EXISTING\" (", + "lineNumber": 144 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 145 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already exists^): %%b", + "lineNumber": 146 + }, + { + "text": " echo.", + "lineNumber": 147 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (", + "lineNumber": 148 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 149 + }, + { + "text": " echo [!skipped!] SKIPPED ^(not .blend^): %%b", + "lineNumber": 150 + }, + { + "text": " echo.", + "lineNumber": 151 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (", + "lineNumber": 152 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 153 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already compressed^): %%b", + "lineNumber": 154 + }, + { + "text": " echo.", + "lineNumber": 155 + }, + { + "text": " )", + "lineNumber": 156 + }, + { + "text": ")", + "lineNumber": 157 + }, + { + "lineNumber": 158 + }, + { + "text": "REM Clean up output file", + "lineNumber": 159 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 160 + }, + { + "lineNumber": 161 + }, + { + "text": "echo ========================================", + "lineNumber": 162 + }, + { + "text": "echo SUMMARY", + "lineNumber": 163 + }, + { + "text": "echo ========================================", + "lineNumber": 164 + }, + { + "text": "echo Processed: !processed! files", + "lineNumber": 165 + }, + { + "text": "echo Crashed: !crashed! files", + "lineNumber": 166 + }, + { + "text": "echo Skipped: !skipped! files", + "lineNumber": 167 + }, + { + "text": "echo Deleted ^(too similar^): !deleted_similar! files", + "lineNumber": 168 + }, + { + "text": "echo Total: %* files", + "lineNumber": 169 + }, + { + "text": "echo ========================================", + "lineNumber": 170 + }, + { + "text": "echo.", + "lineNumber": 171 + }, + { + "text": "echo Done!", + "lineNumber": 172 + }, + { + "text": "pause ", + "lineNumber": 173 + } + ] + }, + "score": 0.2850058674812317 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 661, + "column": 63 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n if ZIPPER_TYPE == \"7z\":\n if SEVEN_Z_EXE is None:\n raise RuntimeError(\n \"7z compression requested but 7z executable not found in PATH. \"\n \"Please install 7z (e.g., via Chocolatey: choco install 7zip) \"\n \"or set zipper to 'zip' in config.json\"\n )\n zip_path.parent.mkdir(parents=True, exist_ok=True)\n \n # If creating a .7z file, remove any existing .zip file for the same sequence\n if zip_path.suffix == \".7z\":\n old_zip_path = zip_path.with_suffix(\".zip\")\n if old_zip_path.exists():\n old_zip_path.unlink(missing_ok=True)\n old_state_path = state_path_for(old_zip_path)\n if old_state_path.exists():\n old_state_path.unlink(missing_ok=True)\n \n # Build list of files to archive with relative paths\n # Sort files to ensure consistent archive ordering (matches compute_state)\n files = sorted(\n iter_sequence_files(seq_dir),\n key=lambda p: p.relative_to(seq_dir).as_posix(),\n )\n file_list = [file_path.relative_to(seq_dir).as_posix() for file_path in files]\n \n if not file_list:\n raise RuntimeError(f\"No files found to archive in {seq_dir}\")\n \n # Create zip in temporary location first to avoid issues with corrupted existing files\n temp_zip = None\n list_file_path = None\n try:\n # Create temporary archive file path (but don't create the file - let 7z create it)", + "signatures": {}, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 666 + }, + { + "text": " if SEVEN_Z_EXE is None:", + "lineNumber": 667 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 668 + }, + { + "text": " \"7z compression requested but 7z executable not found in PATH. \"", + "lineNumber": 669 + }, + { + "text": " \"Please install 7z (e.g., via Chocolatey: choco install 7zip) \"", + "lineNumber": 670 + }, + { + "text": " \"or set zipper to 'zip' in config.json\"", + "lineNumber": 671 + }, + { + "text": " )", + "lineNumber": 672 + }, + { + "text": " zip_path.parent.mkdir(parents=True, exist_ok=True)", + "lineNumber": 673 + }, + { + "text": " ", + "lineNumber": 674 + }, + { + "text": " # If creating a .7z file, remove any existing .zip file for the same sequence", + "lineNumber": 675 + }, + { + "text": " if zip_path.suffix == \".7z\":", + "lineNumber": 676 + }, + { + "text": " old_zip_path = zip_path.with_suffix(\".zip\")", + "lineNumber": 677 + }, + { + "text": " if old_zip_path.exists():", + "lineNumber": 678 + }, + { + "text": " old_zip_path.unlink(missing_ok=True)", + "lineNumber": 679 + }, + { + "text": " old_state_path = state_path_for(old_zip_path)", + "lineNumber": 680 + }, + { + "text": " if old_state_path.exists():", + "lineNumber": 681 + }, + { + "text": " old_state_path.unlink(missing_ok=True)", + "lineNumber": 682 + }, + { + "text": " ", + "lineNumber": 683 + }, + { + "text": " # Build list of files to archive with relative paths", + "lineNumber": 684 + }, + { + "text": " # Sort files to ensure consistent archive ordering (matches compute_state)", + "lineNumber": 685 + }, + { + "text": " files = sorted(", + "lineNumber": 686 + }, + { + "text": " iter_sequence_files(seq_dir),", + "lineNumber": 687 + }, + { + "text": " key=lambda p: p.relative_to(seq_dir).as_posix(),", + "lineNumber": 688 + }, + { + "text": " )", + "lineNumber": 689 + }, + { + "text": " file_list = [file_path.relative_to(seq_dir).as_posix() for file_path in files]", + "lineNumber": 690 + }, + { + "text": " ", + "lineNumber": 691 + }, + { + "text": " if not file_list:", + "lineNumber": 692 + }, + { + "text": " raise RuntimeError(f\"No files found to archive in {seq_dir}\")", + "lineNumber": 693 + }, + { + "text": " ", + "lineNumber": 694 + }, + { + "text": " # Create zip in temporary location first to avoid issues with corrupted existing files", + "lineNumber": 695 + }, + { + "text": " temp_zip = None", + "lineNumber": 696 + }, + { + "text": " list_file_path = None", + "lineNumber": 697 + }, + { + "text": " try:", + "lineNumber": 698 + }, + { + "text": " # Create temporary archive file path (but don't create the file - let 7z create it)", + "lineNumber": 699 + } + ] + }, + "score": 0.2817677855491638 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": {}, + "endPosition": { + "line": 102 + } + }, + "contents": "#!/usr/bin/env python3\n\"\"\"Maintain zipped render sequences for Git hooks.\n\nDefault mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,\nand stages any updated archives so commits only track compact files. Switch to\n`--mode expand` to inflate the tracked archives back into the ignored working\ndirectories after checkouts or pulls.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport platform\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport traceback\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom pathlib import Path\nfrom typing import Iterator, Sequence\n\n# Try to import psutil for cross-platform RAM detection\ntry:\n import psutil\n HAS_PSUTIL = True\nexcept ImportError:\n HAS_PSUTIL = False\n # For Windows fallback\n if platform.system() == \"Windows\":\n try:\n import ctypes\n HAS_CTYPES = True\n except ImportError:\n HAS_CTYPES = False\n else:\n HAS_CTYPES = False\n\n\nRENDER_ROOT = Path(\"Renders\")\nARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"\nSEQUENCE_EXTENSIONS = {\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".tif\",\n \".tiff\",\n \".exr\",\n}\nSTATE_SUFFIX = \".meta.json\"\nDEFAULT_CONFIG = {\n \"zipper\": \"7z\",\n \"compression\": 9,\n \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate\n \"dailyFormat\": \"daily_YYMMDD\",\n \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)\n}\n\n\ndef log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:\n if verbose_only and not verbose:\n return\n print(f\"[{mode}] {message}\", flush=True)\n\n\ndef load_config() -> dict:\n # First try to load from project's .config folder (current working directory)\n # Then fall back to ProjectStructure repo config (next to zip_sequences.py)\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "#!/usr/bin/env python3", + "lineNumber": 1 + }, + { + "text": "\"\"\"Maintain zipped render sequences for Git hooks.", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "Default mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,", + "lineNumber": 4 + }, + { + "text": "and stages any updated archives so commits only track compact files. Switch to", + "lineNumber": 5 + }, + { + "text": "`--mode expand` to inflate the tracked archives back into the ignored working", + "lineNumber": 6 + }, + { + "text": "directories after checkouts or pulls.", + "lineNumber": 7 + }, + { + "text": "\"\"\"", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": "from __future__ import annotations", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "import argparse", + "lineNumber": 12 + }, + { + "text": "import json", + "lineNumber": 13 + }, + { + "text": "import os", + "lineNumber": 14 + }, + { + "text": "import platform", + "lineNumber": 15 + }, + { + "text": "import shutil", + "lineNumber": 16 + }, + { + "text": "import subprocess", + "lineNumber": 17 + }, + { + "text": "import sys", + "lineNumber": 18 + }, + { + "text": "import tempfile", + "lineNumber": 19 + }, + { + "text": "import time", + "lineNumber": 20 + }, + { + "text": "import traceback", + "lineNumber": 21 + }, + { + "text": "from concurrent.futures import ThreadPoolExecutor, as_completed", + "lineNumber": 22 + }, + { + "text": "from pathlib import Path", + "lineNumber": 23 + }, + { + "text": "from typing import Iterator, Sequence", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "# Try to import psutil for cross-platform RAM detection", + "lineNumber": 26 + }, + { + "text": "try:", + "lineNumber": 27 + }, + { + "text": " import psutil", + "lineNumber": 28 + }, + { + "text": " HAS_PSUTIL = True", + "lineNumber": 29 + }, + { + "text": "except ImportError:", + "lineNumber": 30 + }, + { + "text": " HAS_PSUTIL = False", + "lineNumber": 31 + }, + { + "text": " # For Windows fallback", + "lineNumber": 32 + }, + { + "text": " if platform.system() == \"Windows\":", + "lineNumber": 33 + }, + { + "text": " try:", + "lineNumber": 34 + }, + { + "text": " import ctypes", + "lineNumber": 35 + }, + { + "text": " HAS_CTYPES = True", + "lineNumber": 36 + }, + { + "text": " except ImportError:", + "lineNumber": 37 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 38 + }, + { + "text": " else:", + "lineNumber": 39 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": "RENDER_ROOT = Path(\"Renders\")", + "lineNumber": 43 + }, + { + "text": "ARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"", + "lineNumber": 44 + }, + { + "text": "SEQUENCE_EXTENSIONS = {", + "lineNumber": 45 + }, + { + "text": " \".png\",", + "lineNumber": 46 + }, + { + "text": " \".jpg\",", + "lineNumber": 47 + }, + { + "text": " \".jpeg\",", + "lineNumber": 48 + }, + { + "text": " \".tif\",", + "lineNumber": 49 + }, + { + "text": " \".tiff\",", + "lineNumber": 50 + }, + { + "text": " \".exr\",", + "lineNumber": 51 + }, + { + "text": "}", + "lineNumber": 52 + }, + { + "text": "STATE_SUFFIX = \".meta.json\"", + "lineNumber": 53 + }, + { + "text": "DEFAULT_CONFIG = {", + "lineNumber": 54 + }, + { + "text": " \"zipper\": \"7z\",", + "lineNumber": 55 + }, + { + "text": " \"compression\": 9,", + "lineNumber": 56 + }, + { + "text": " \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate", + "lineNumber": 57 + }, + { + "text": " \"dailyFormat\": \"daily_YYMMDD\",", + "lineNumber": 58 + }, + { + "text": " \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)", + "lineNumber": 59 + }, + { + "text": "}", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "lineNumber": 62 + }, + { + "text": "def log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:", + "lineNumber": 63, + "isSignature": true + }, + { + "text": " if verbose_only and not verbose:", + "lineNumber": 64 + }, + { + "text": " return", + "lineNumber": 65 + }, + { + "text": " print(f\"[{mode}] {message}\", flush=True)", + "lineNumber": 66, + "isSignature": true + }, + { + "lineNumber": 67 + }, + { + "lineNumber": 68 + }, + { + "text": "def load_config() -> dict:", + "lineNumber": 69, + "isSignature": true + }, + { + "text": " # First try to load from project's .config folder (current working directory)", + "lineNumber": 70, + "isSignature": true + }, + { + "text": " # Then fall back to ProjectStructure repo config (next to zip_sequences.py)", + "lineNumber": 71, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 72 + } + ] + }, + "score": 0.2665804624557495 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 849, + "column": 5 + }, + "endPosition": { + "line": 957 + } + }, + "contents": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:\n target_dir = sequence_dir_for(zip_path)\n if target_dir.exists():\n shutil.rmtree(target_dir)\n target_dir.mkdir(parents=True, exist_ok=True)\n\n if ZIPPER_TYPE == \"7z\":\n if SEVEN_Z_EXE is None:\n raise RuntimeError(\n \"7z extraction requested but 7z executable not found in PATH. \"\n \"Please install 7z or set zipper to 'zip' in config.json\"\n )\n cmd = [\n SEVEN_Z_EXE,\n \"x\",\n \"-y\",\n \"-mtc=on\", # Preserve timestamps during extraction\n str(zip_path),\n f\"-o{target_dir}\",\n ]\n result = subprocess.run(\n cmd,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z extraction failed: {error_msg}\")\n elif ZIPPER_TYPE == \"zip\":\n from zipfile import ZipFile\n\n with ZipFile(zip_path, \"r\") as archive:\n archive.extractall(target_dir)\n else:\n raise RuntimeError(\n f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"\n f\"Expected '7z' or 'zip'. \"\n f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"\n )\n\n # Restore timestamps from stored state\n # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding\n is_windows = platform.system() == \"Windows\"\n restored_count = 0\n failed_count = 0\n mismatch_count = 0\n \n # Process files in sorted order to match state ordering\n files_to_restore = sorted(\n seq_state.get(\"files\", []),\n key=lambda e: e[\"path\"]\n )\n \n for entry in files_to_restore:", + "signatures": {}, + "detailedLines": [ + { + "text": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:", + "lineNumber": 853, + "isSignature": true + }, + { + "text": " target_dir = sequence_dir_for(zip_path)", + "lineNumber": 854 + }, + { + "text": " if target_dir.exists():", + "lineNumber": 855 + }, + { + "text": " shutil.rmtree(target_dir)", + "lineNumber": 856 + }, + { + "text": " target_dir.mkdir(parents=True, exist_ok=True)", + "lineNumber": 857 + }, + { + "lineNumber": 858 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 859 + }, + { + "text": " if SEVEN_Z_EXE is None:", + "lineNumber": 860 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 861 + }, + { + "text": " \"7z extraction requested but 7z executable not found in PATH. \"", + "lineNumber": 862 + }, + { + "text": " \"Please install 7z or set zipper to 'zip' in config.json\"", + "lineNumber": 863 + }, + { + "text": " )", + "lineNumber": 864 + }, + { + "text": " cmd = [", + "lineNumber": 865 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 866 + }, + { + "text": " \"x\",", + "lineNumber": 867 + }, + { + "text": " \"-y\",", + "lineNumber": 868 + }, + { + "text": " \"-mtc=on\", # Preserve timestamps during extraction", + "lineNumber": 869 + }, + { + "text": " str(zip_path),", + "lineNumber": 870 + }, + { + "text": " f\"-o{target_dir}\",", + "lineNumber": 871 + }, + { + "text": " ]", + "lineNumber": 872 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 873 + }, + { + "text": " cmd,", + "lineNumber": 874 + }, + { + "text": " check=False,", + "lineNumber": 875 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 876 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 877 + }, + { + "text": " text=True,", + "lineNumber": 878 + }, + { + "text": " )", + "lineNumber": 879 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 880 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 881 + }, + { + "text": " if result.stdout:", + "lineNumber": 882 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 883 + }, + { + "text": " raise RuntimeError(f\"7z extraction failed: {error_msg}\")", + "lineNumber": 884 + }, + { + "text": " elif ZIPPER_TYPE == \"zip\":", + "lineNumber": 885 + }, + { + "text": " from zipfile import ZipFile", + "lineNumber": 886 + }, + { + "lineNumber": 887 + }, + { + "text": " with ZipFile(zip_path, \"r\") as archive:", + "lineNumber": 888 + }, + { + "text": " archive.extractall(target_dir)", + "lineNumber": 889 + }, + { + "text": " else:", + "lineNumber": 890 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 891 + }, + { + "text": " f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"", + "lineNumber": 892 + }, + { + "text": " f\"Expected '7z' or 'zip'. \"", + "lineNumber": 893 + }, + { + "text": " f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"", + "lineNumber": 894 + }, + { + "text": " )", + "lineNumber": 895 + }, + { + "lineNumber": 896 + }, + { + "text": " # Restore timestamps from stored state", + "lineNumber": 897 + }, + { + "text": " # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding", + "lineNumber": 898 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 899 + }, + { + "text": " restored_count = 0", + "lineNumber": 900 + }, + { + "text": " failed_count = 0", + "lineNumber": 901 + }, + { + "text": " mismatch_count = 0", + "lineNumber": 902 + }, + { + "text": " ", + "lineNumber": 903 + }, + { + "text": " # Process files in sorted order to match state ordering", + "lineNumber": 904 + }, + { + "text": " files_to_restore = sorted(", + "lineNumber": 905 + }, + { + "text": " seq_state.get(\"files\", []),", + "lineNumber": 906 + }, + { + "text": " key=lambda e: e[\"path\"]", + "lineNumber": 907 + }, + { + "text": " )", + "lineNumber": 908 + }, + { + "text": " ", + "lineNumber": 909 + }, + { + "text": " for entry in files_to_restore:", + "lineNumber": 910 + } + ] + }, + "score": 0.266065388917923 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitattributes", + "range": { + "startPosition": {}, + "endPosition": { + "line": 55 + } + }, + "contents": "# Git LFS attributes configuration\n# All listed file types will be tracked by Git LFS\n\n# Video files\n*.avi filter=lfs diff=lfs merge=lfs -text\n*.mkv filter=lfs diff=lfs merge=lfs -text\n*.mov filter=lfs diff=lfs merge=lfs -text\n*.MOV filter=lfs diff=lfs merge=lfs -text\n*.mp4 filter=lfs diff=lfs merge=lfs -text\n*.webm filter=lfs diff=lfs merge=lfs -text\n\n# Image files\n*.bmp filter=lfs diff=lfs merge=lfs -text\n*.exr filter=lfs diff=lfs merge=lfs -text\n*.gif filter=lfs diff=lfs merge=lfs -text\n*.hdr filter=lfs diff=lfs merge=lfs -text\n*.jpg filter=lfs diff=lfs merge=lfs -text\n*.jpeg filter=lfs diff=lfs merge=lfs -text\n*.png filter=lfs diff=lfs merge=lfs -text\n*.tga filter=lfs diff=lfs merge=lfs -text\n*.tif filter=lfs diff=lfs merge=lfs -text\n*.tiff filter=lfs diff=lfs merge=lfs -text\n*.webp filter=lfs diff=lfs merge=lfs -text\n\n# 3D/CG files\n*.3ds filter=lfs diff=lfs merge=lfs -text\n*.abc filter=lfs diff=lfs merge=lfs -text\n*.blend filter=lfs diff=lfs merge=lfs -text\n*.blend1 filter=lfs diff=lfs merge=lfs -text\n*.blend2 filter=lfs diff=lfs merge=lfs -text\n*.bvh filter=lfs diff=lfs merge=lfs -text\n*.bvh.gz filter=lfs diff=lfs merge=lfs -text\n*.c4d filter=lfs diff=lfs merge=lfs -text\n*.dae filter=lfs diff=lfs merge=lfs -text\n*.fbx filter=lfs diff=lfs merge=lfs -text\n*.fbxkey filter=lfs diff=lfs merge=lfs -text\n*.ma filter=lfs diff=lfs merge=lfs -text\n*.max filter=lfs diff=lfs merge=lfs -text\n*.mb filter=lfs diff=lfs merge=lfs -text\n*.obj filter=lfs diff=lfs merge=lfs -text\n*.usd filter=lfs diff=lfs merge=lfs -text\n*.vdb filter=lfs diff=lfs merge=lfs -text\n*.bphys filter=lfs diff=lfs merge=lfs -text\n*.bobj filter=lfs diff=lfs merge=lfs -text\n*.bvel filter=lfs diff=lfs merge=lfs -text\n*.bpointcache filter=lfs diff=lfs merge=lfs -text\n*.blob filter=lfs diff=lfs merge=lfs -text\n*.unitypackage filter=lfs diff=lfs merge=lfs -text\n\n# Houdini files\n*.hiplc filter=lfs diff=lfs merge=lfs -text\n*.bgeo filter=lfs diff=lfs merge=lfs -text\n*.bgeo.sc filter=lfs diff=lfs merge=lfs -text\n\n# Flip Fluids cache files", + "signatures": {}, + "detailedLines": [ + { + "text": "# Git LFS attributes configuration", + "lineNumber": 1 + }, + { + "text": "# All listed file types will be tracked by Git LFS", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "# Video files", + "lineNumber": 4 + }, + { + "text": "*.avi filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 5 + }, + { + "text": "*.mkv filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 6 + }, + { + "text": "*.mov filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 7 + }, + { + "text": "*.MOV filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 8 + }, + { + "text": "*.mp4 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 9 + }, + { + "text": "*.webm filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "# Image files", + "lineNumber": 12 + }, + { + "text": "*.bmp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 13 + }, + { + "text": "*.exr filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 14 + }, + { + "text": "*.gif filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 15 + }, + { + "text": "*.hdr filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 16 + }, + { + "text": "*.jpg filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 17 + }, + { + "text": "*.jpeg filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 18 + }, + { + "text": "*.png filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 19 + }, + { + "text": "*.tga filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 20 + }, + { + "text": "*.tif filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 21 + }, + { + "text": "*.tiff filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 22 + }, + { + "text": "*.webp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": "# 3D/CG files", + "lineNumber": 25 + }, + { + "text": "*.3ds filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 26 + }, + { + "text": "*.abc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 27 + }, + { + "text": "*.blend filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 28 + }, + { + "text": "*.blend1 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 29 + }, + { + "text": "*.blend2 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 30 + }, + { + "text": "*.bvh filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 31 + }, + { + "text": "*.bvh.gz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 32 + }, + { + "text": "*.c4d filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 33 + }, + { + "text": "*.dae filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 34 + }, + { + "text": "*.fbx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 35 + }, + { + "text": "*.fbxkey filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 36 + }, + { + "text": "*.ma filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 37 + }, + { + "text": "*.max filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 38 + }, + { + "text": "*.mb filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 39 + }, + { + "text": "*.obj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 40 + }, + { + "text": "*.usd filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 41 + }, + { + "text": "*.vdb filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 42 + }, + { + "text": "*.bphys filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 43 + }, + { + "text": "*.bobj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 44 + }, + { + "text": "*.bvel filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 45 + }, + { + "text": "*.bpointcache filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 46 + }, + { + "text": "*.blob filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 47 + }, + { + "text": "*.unitypackage filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 48 + }, + { + "lineNumber": 49 + }, + { + "text": "# Houdini files", + "lineNumber": 50 + }, + { + "text": "*.hiplc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 51 + }, + { + "text": "*.bgeo filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 52 + }, + { + "text": "*.bgeo.sc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": "# Flip Fluids cache files", + "lineNumber": 55 + } + ] + }, + "score": 0.24383710324764252 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 157, + "column": 30 + }, + "endPosition": { + "line": 236 + } + }, + "contents": "def get_available_ram() -> int | None:\n \"\"\"Get available RAM in bytes, reserving 20% for system.\n \n Returns:\n Available RAM in bytes, or None if detection fails.\n \"\"\"\n try:\n if HAS_PSUTIL:\n # Use psutil for cross-platform RAM detection\n mem = psutil.virtual_memory()\n # Reserve 20% for system, use 80% for compression jobs\n available = int(mem.total * 0.8)\n return available\n elif HAS_CTYPES and platform.system() == \"Windows\":\n # Windows fallback using ctypes\n class MEMORYSTATUSEX(ctypes.Structure):\n _fields_ = [\n (\"dwLength\", ctypes.c_ulong),\n (\"dwMemoryLoad\", ctypes.c_ulong),\n (\"ullTotalPhys\", ctypes.c_ulonglong),\n (\"ullAvailPhys\", ctypes.c_ulonglong),\n (\"ullTotalPageFile\", ctypes.c_ulonglong),\n (\"ullAvailPageFile\", ctypes.c_ulonglong),\n (\"ullTotalVirtual\", ctypes.c_ulonglong),\n (\"ullAvailVirtual\", ctypes.c_ulonglong),\n (\"ullAvailExtendedVirtual\", ctypes.c_ulonglong),\n ]\n \n kernel32 = ctypes.windll.kernel32\n kernel32.GlobalMemoryStatusEx.argtypes = [ctypes.POINTER(MEMORYSTATUSEX)]\n kernel32.GlobalMemoryStatusEx.restype = ctypes.c_bool\n \n mem_status = MEMORYSTATUSEX()\n mem_status.dwLength = ctypes.sizeof(MEMORYSTATUSEX)\n \n if kernel32.GlobalMemoryStatusEx(ctypes.byref(mem_status)):\n # Reserve 20% for system, use 80% for compression jobs\n available = int(mem_status.ullTotalPhys * 0.8)\n return available\n except Exception:\n pass\n \n return None\n\n\ndef estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:\n \"\"\"", + "signatures": {}, + "detailedLines": [ + { + "text": "def get_available_ram() -> int | None:", + "lineNumber": 161, + "isSignature": true + }, + { + "text": " \"\"\"Get available RAM in bytes, reserving 20% for system.", + "lineNumber": 162 + }, + { + "text": " ", + "lineNumber": 163 + }, + { + "text": " Returns:", + "lineNumber": 164 + }, + { + "text": " Available RAM in bytes, or None if detection fails.", + "lineNumber": 165 + }, + { + "text": " \"\"\"", + "lineNumber": 166 + }, + { + "text": " try:", + "lineNumber": 167 + }, + { + "text": " if HAS_PSUTIL:", + "lineNumber": 168 + }, + { + "text": " # Use psutil for cross-platform RAM detection", + "lineNumber": 169 + }, + { + "text": " mem = psutil.virtual_memory()", + "lineNumber": 170 + }, + { + "text": " # Reserve 20% for system, use 80% for compression jobs", + "lineNumber": 171 + }, + { + "text": " available = int(mem.total * 0.8)", + "lineNumber": 172 + }, + { + "text": " return available", + "lineNumber": 173 + }, + { + "text": " elif HAS_CTYPES and platform.system() == \"Windows\":", + "lineNumber": 174 + }, + { + "text": " # Windows fallback using ctypes", + "lineNumber": 175 + }, + { + "text": " class MEMORYSTATUSEX(ctypes.Structure):", + "lineNumber": 176 + }, + { + "text": " _fields_ = [", + "lineNumber": 177 + }, + { + "text": " (\"dwLength\", ctypes.c_ulong),", + "lineNumber": 178 + }, + { + "text": " (\"dwMemoryLoad\", ctypes.c_ulong),", + "lineNumber": 179 + }, + { + "text": " (\"ullTotalPhys\", ctypes.c_ulonglong),", + "lineNumber": 180 + }, + { + "text": " (\"ullAvailPhys\", ctypes.c_ulonglong),", + "lineNumber": 181 + }, + { + "text": " (\"ullTotalPageFile\", ctypes.c_ulonglong),", + "lineNumber": 182 + }, + { + "text": " (\"ullAvailPageFile\", ctypes.c_ulonglong),", + "lineNumber": 183 + }, + { + "text": " (\"ullTotalVirtual\", ctypes.c_ulonglong),", + "lineNumber": 184 + }, + { + "text": " (\"ullAvailVirtual\", ctypes.c_ulonglong),", + "lineNumber": 185 + }, + { + "text": " (\"ullAvailExtendedVirtual\", ctypes.c_ulonglong),", + "lineNumber": 186 + }, + { + "text": " ]", + "lineNumber": 187 + }, + { + "text": " ", + "lineNumber": 188 + }, + { + "text": " kernel32 = ctypes.windll.kernel32", + "lineNumber": 189 + }, + { + "text": " kernel32.GlobalMemoryStatusEx.argtypes = [ctypes.POINTER(MEMORYSTATUSEX)]", + "lineNumber": 190 + }, + { + "text": " kernel32.GlobalMemoryStatusEx.restype = ctypes.c_bool", + "lineNumber": 191 + }, + { + "text": " ", + "lineNumber": 192 + }, + { + "text": " mem_status = MEMORYSTATUSEX()", + "lineNumber": 193 + }, + { + "text": " mem_status.dwLength = ctypes.sizeof(MEMORYSTATUSEX)", + "lineNumber": 194 + }, + { + "text": " ", + "lineNumber": 195 + }, + { + "text": " if kernel32.GlobalMemoryStatusEx(ctypes.byref(mem_status)):", + "lineNumber": 196 + }, + { + "text": " # Reserve 20% for system, use 80% for compression jobs", + "lineNumber": 197 + }, + { + "text": " available = int(mem_status.ullTotalPhys * 0.8)", + "lineNumber": 198 + }, + { + "text": " return available", + "lineNumber": 199 + }, + { + "text": " except Exception:", + "lineNumber": 200 + }, + { + "text": " pass", + "lineNumber": 201 + }, + { + "text": " ", + "lineNumber": 202 + }, + { + "text": " return None", + "lineNumber": 203, + "isSignature": true + }, + { + "lineNumber": 204 + }, + { + "lineNumber": 205 + }, + { + "text": "def estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:", + "lineNumber": 206, + "isSignature": true + }, + { + "text": " \"\"\"", + "lineNumber": 207 + } + ] + }, + "score": 0.2415280044078827 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 523, + "column": 33 + }, + "endPosition": { + "line": 557 + } + }, + "contents": "def compute_state(seq_dir: Path) -> dict:\n entries = []\n files = sorted(\n iter_sequence_files(seq_dir),\n key=lambda p: p.relative_to(seq_dir).as_posix(),\n )\n # On Windows, normalize timestamps to 100-nanosecond boundaries (NTFS precision)\n # to match what we do when restoring timestamps\n is_windows = platform.system() == \"Windows\"\n \n for file_path in files:\n stat = file_path.stat()\n mtime_ns = stat.st_mtime_ns\n \n # Normalize to filesystem precision (Windows NTFS uses 100-nanosecond intervals)\n if is_windows:\n mtime_ns = (mtime_ns // 100) * 100\n \n entries.append(\n {\n \"path\": file_path.relative_to(seq_dir).as_posix(),\n \"size\": stat.st_size,\n \"mtime_ns\": mtime_ns,\n }\n )\n return {\"files\": entries}\n\n\ndef current_state(seq_dir: Path) -> dict:\n if not seq_dir.exists() or not seq_dir.is_dir():\n return {\"files\": []}", + "signatures": {}, + "detailedLines": [ + { + "text": "def compute_state(seq_dir: Path) -> dict:", + "lineNumber": 527, + "isSignature": true + }, + { + "text": " entries = []", + "lineNumber": 528 + }, + { + "text": " files = sorted(", + "lineNumber": 529 + }, + { + "text": " iter_sequence_files(seq_dir),", + "lineNumber": 530 + }, + { + "text": " key=lambda p: p.relative_to(seq_dir).as_posix(),", + "lineNumber": 531 + }, + { + "text": " )", + "lineNumber": 532 + }, + { + "text": " # On Windows, normalize timestamps to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 533 + }, + { + "text": " # to match what we do when restoring timestamps", + "lineNumber": 534 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 535 + }, + { + "text": " ", + "lineNumber": 536 + }, + { + "text": " for file_path in files:", + "lineNumber": 537 + }, + { + "text": " stat = file_path.stat()", + "lineNumber": 538 + }, + { + "text": " mtime_ns = stat.st_mtime_ns", + "lineNumber": 539 + }, + { + "text": " ", + "lineNumber": 540 + }, + { + "text": " # Normalize to filesystem precision (Windows NTFS uses 100-nanosecond intervals)", + "lineNumber": 541 + }, + { + "text": " if is_windows:", + "lineNumber": 542 + }, + { + "text": " mtime_ns = (mtime_ns // 100) * 100", + "lineNumber": 543 + }, + { + "text": " ", + "lineNumber": 544 + }, + { + "text": " entries.append(", + "lineNumber": 545 + }, + { + "text": " {", + "lineNumber": 546 + }, + { + "text": " \"path\": file_path.relative_to(seq_dir).as_posix(),", + "lineNumber": 547 + }, + { + "text": " \"size\": stat.st_size,", + "lineNumber": 548 + }, + { + "text": " \"mtime_ns\": mtime_ns,", + "lineNumber": 549 + }, + { + "text": " }", + "lineNumber": 550 + }, + { + "text": " )", + "lineNumber": 551 + }, + { + "text": " return {\"files\": entries}", + "lineNumber": 552, + "isSignature": true + }, + { + "lineNumber": 553 + }, + { + "lineNumber": 554 + }, + { + "text": "def current_state(seq_dir: Path) -> dict:", + "lineNumber": 555, + "isSignature": true + }, + { + "text": " if not seq_dir.exists() or not seq_dir.is_dir():", + "lineNumber": 556 + }, + { + "text": " return {\"files\": []}", + "lineNumber": 557 + } + ] + }, + "score": 0.23620319366455078 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitattributes", + "range": { + "startPosition": { + "line": 51 + }, + "endPosition": { + "line": 110, + "column": 40 + } + }, + "contents": "*.bgeo filter=lfs diff=lfs merge=lfs -text\n*.bgeo.sc filter=lfs diff=lfs merge=lfs -text\n\n# Flip Fluids cache files\n*.data filter=lfs diff=lfs merge=lfs -text\n*.sqlite3 filter=lfs diff=lfs merge=lfs -text\n*.ffp3 filter=lfs diff=lfs merge=lfs -text\n\n# Substance files\n*.sbs filter=lfs diff=lfs merge=lfs -text\n*.sbsar filter=lfs diff=lfs merge=lfs -text\n*.spp filter=lfs diff=lfs merge=lfs -text\n\n# Audio files\n*.mp3 filter=lfs diff=lfs merge=lfs -text\n*.wav filter=lfs diff=lfs merge=lfs -text\n\n# Archive/Compression files\n*.7z filter=lfs diff=lfs merge=lfs -text\n*.bz2 filter=lfs diff=lfs merge=lfs -text\n*.gz filter=lfs diff=lfs merge=lfs -text\n*.rar filter=lfs diff=lfs merge=lfs -text\n*.tar filter=lfs diff=lfs merge=lfs -text\n*.zip filter=lfs diff=lfs merge=lfs -text\n\n# Documents\n*.docx filter=lfs diff=lfs merge=lfs -text\n*.xlsx filter=lfs diff=lfs merge=lfs -text\n*.eps filter=lfs diff=lfs merge=lfs -text\n*.pdf filter=lfs diff=lfs merge=lfs -text\n\n# Adobe files\n*.aegraphic filter=lfs diff=lfs merge=lfs -text\n*.aep filter=lfs diff=lfs merge=lfs -text\n*.prel filter=lfs diff=lfs merge=lfs -text\n*.prin filter=lfs diff=lfs merge=lfs -text\n*.prmf filter=lfs diff=lfs merge=lfs -text\n*.prproj filter=lfs diff=lfs merge=lfs -text\n*.ai filter=lfs diff=lfs merge=lfs -text\n*.psd filter=lfs diff=lfs merge=lfs -text\n\n# Davinci Resolve files\n*.dpx filter=lfs diff=lfs merge=lfs -text\n*.drp filter=lfs diff=lfs merge=lfs -text\n\n# Data files\n*.h5 filter=lfs diff=lfs merge=lfs -text\n*.hdf5 filter=lfs diff=lfs merge=lfs -text\n\n# Other\n*.uni filter=lfs diff=lfs merge=lfs -text\n*.pdn filter=lfs diff=lfs merge=lfs -text\n*.pur filter=lfs diff=lfs merge=lfs -text\n\n# Python files\n*.npz filter=lfs diff=lfs merge=lfs -text\n\n# Adobe Alternatives\n*.xcf filter=lfs diff=lfs merge=lfs -text\n*.af filter=lfs diff=lfs merge=lfs -text", + "signatures": {}, + "detailedLines": [ + { + "text": "*.bgeo filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 52 + }, + { + "text": "*.bgeo.sc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": "# Flip Fluids cache files", + "lineNumber": 55 + }, + { + "text": "*.data filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 56 + }, + { + "text": "*.sqlite3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 57 + }, + { + "text": "*.ffp3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 58 + }, + { + "lineNumber": 59 + }, + { + "text": "# Substance files", + "lineNumber": 60 + }, + { + "text": "*.sbs filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 61 + }, + { + "text": "*.sbsar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 62 + }, + { + "text": "*.spp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 63 + }, + { + "lineNumber": 64 + }, + { + "text": "# Audio files", + "lineNumber": 65 + }, + { + "text": "*.mp3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 66 + }, + { + "text": "*.wav filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 67 + }, + { + "lineNumber": 68 + }, + { + "text": "# Archive/Compression files", + "lineNumber": 69 + }, + { + "text": "*.7z filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 70 + }, + { + "text": "*.bz2 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 71 + }, + { + "text": "*.gz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 72 + }, + { + "text": "*.rar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 73 + }, + { + "text": "*.tar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 74 + }, + { + "text": "*.zip filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "# Documents", + "lineNumber": 77 + }, + { + "text": "*.docx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 78 + }, + { + "text": "*.xlsx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 79 + }, + { + "text": "*.eps filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 80 + }, + { + "text": "*.pdf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 81 + }, + { + "lineNumber": 82 + }, + { + "text": "# Adobe files", + "lineNumber": 83 + }, + { + "text": "*.aegraphic filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 84 + }, + { + "text": "*.aep filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 85 + }, + { + "text": "*.prel filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 86 + }, + { + "text": "*.prin filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 87 + }, + { + "text": "*.prmf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 88 + }, + { + "text": "*.prproj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 89 + }, + { + "text": "*.ai filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 90 + }, + { + "text": "*.psd filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 91 + }, + { + "lineNumber": 92 + }, + { + "text": "# Davinci Resolve files", + "lineNumber": 93 + }, + { + "text": "*.dpx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 94 + }, + { + "text": "*.drp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 95 + }, + { + "lineNumber": 96 + }, + { + "text": "# Data files", + "lineNumber": 97 + }, + { + "text": "*.h5 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 98 + }, + { + "text": "*.hdf5 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 99 + }, + { + "lineNumber": 100 + }, + { + "text": "# Other", + "lineNumber": 101 + }, + { + "text": "*.uni filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 102 + }, + { + "text": "*.pdn filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 103 + }, + { + "text": "*.pur filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": "# Python files", + "lineNumber": 106 + }, + { + "text": "*.npz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": "# Adobe Alternatives", + "lineNumber": 109 + }, + { + "text": "*.xcf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 110 + }, + { + "text": "*.af filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 111 + } + ] + }, + "score": 0.2347303032875061 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 202, + "column": 15 + }, + "endPosition": { + "line": 471 + } + }, + "contents": "def estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:\n \"\"\"Estimate RAM usage per compression job based on folder size.\n \n Args:\n seq_dir: Path to the sequence directory\n seq_state: State dictionary containing file information\n \n Returns:\n Estimated RAM usage in bytes\n \"\"\"\n # Calculate total folder size from seq_state\n total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))\n \n if ZIPPER_TYPE == \"7z\":\n # Fixed dictionary size: 1GB (1024MB)\n # Actual observed usage: ~2GB per process regardless of sequence size\n # Use a tight estimate to allow maximum concurrency\n # Estimate 3GB per job (50% safety margin over observed ~2GB)\n base_ram = 3 * 1024 * 1024 * 1024 # 3GB base estimate\n \n # For very large sequences (>50GB), add small buffer\n if total_bytes > 50 * 1024 * 1024 * 1024: # >50GB\n # Add 1GB for very large sequences\n estimated_ram = base_ram + (1 * 1024 * 1024 * 1024)\n else:\n estimated_ram = base_ram\n \n return estimated_ram\n else:\n # zip compression is more memory-efficient\n # Conservative estimate: 1GB per job\n return 1024 * 1024 * 1024 # 1 GB\n\n\ndef max_workers(\n requested: int | None,\n work_items: list[tuple[Path, Path, Path, dict]] | None = None,\n *,\n verbose: bool = False\n) -> tuple[int, int | None]:\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "def estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:", + "lineNumber": 206, + "isSignature": true + }, + { + "text": " \"\"\"Estimate RAM usage per compression job based on folder size.", + "lineNumber": 207 + }, + { + "text": " ", + "lineNumber": 208 + }, + { + "text": " Args:", + "lineNumber": 209 + }, + { + "text": " seq_dir: Path to the sequence directory", + "lineNumber": 210 + }, + { + "text": " seq_state: State dictionary containing file information", + "lineNumber": 211 + }, + { + "text": " ", + "lineNumber": 212 + }, + { + "text": " Returns:", + "lineNumber": 213 + }, + { + "text": " Estimated RAM usage in bytes", + "lineNumber": 214 + }, + { + "text": " \"\"\"", + "lineNumber": 215 + }, + { + "text": " # Calculate total folder size from seq_state", + "lineNumber": 216 + }, + { + "text": " total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))", + "lineNumber": 217 + }, + { + "text": " ", + "lineNumber": 218 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 219 + }, + { + "text": " # Fixed dictionary size: 1GB (1024MB)", + "lineNumber": 220 + }, + { + "text": " # Actual observed usage: ~2GB per process regardless of sequence size", + "lineNumber": 221 + }, + { + "text": " # Use a tight estimate to allow maximum concurrency", + "lineNumber": 222 + }, + { + "text": " # Estimate 3GB per job (50% safety margin over observed ~2GB)", + "lineNumber": 223 + }, + { + "text": " base_ram = 3 * 1024 * 1024 * 1024 # 3GB base estimate", + "lineNumber": 224 + }, + { + "text": " ", + "lineNumber": 225 + }, + { + "text": " # For very large sequences (>50GB), add small buffer", + "lineNumber": 226 + }, + { + "text": " if total_bytes > 50 * 1024 * 1024 * 1024: # >50GB", + "lineNumber": 227 + }, + { + "text": " # Add 1GB for very large sequences", + "lineNumber": 228 + }, + { + "text": " estimated_ram = base_ram + (1 * 1024 * 1024 * 1024)", + "lineNumber": 229 + }, + { + "text": " else:", + "lineNumber": 230 + }, + { + "text": " estimated_ram = base_ram", + "lineNumber": 231 + }, + { + "text": " ", + "lineNumber": 232 + }, + { + "text": " return estimated_ram", + "lineNumber": 233 + }, + { + "text": " else:", + "lineNumber": 234 + }, + { + "text": " # zip compression is more memory-efficient", + "lineNumber": 235 + }, + { + "text": " # Conservative estimate: 1GB per job", + "lineNumber": 236 + }, + { + "text": " return 1024 * 1024 * 1024 # 1 GB", + "lineNumber": 237, + "isSignature": true + }, + { + "lineNumber": 238 + }, + { + "lineNumber": 239 + }, + { + "text": "def max_workers(", + "lineNumber": 240, + "isSignature": true + }, + { + "text": " requested: int | None,", + "lineNumber": 241, + "isSignature": true + }, + { + "text": " work_items: list[tuple[Path, Path, Path, dict]] | None = None,", + "lineNumber": 242, + "isSignature": true + }, + { + "text": " *,", + "lineNumber": 243, + "isSignature": true + }, + { + "text": " verbose: bool = False", + "lineNumber": 244, + "isSignature": true + }, + { + "text": ") -> tuple[int, int | None]:", + "lineNumber": 245, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 246 + } + ] + }, + "score": 0.23333029448986053 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 972 + }, + "endPosition": { + "line": 1144 + } + }, + "contents": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:\n\n\n work_items.append((seq_dir, zip_path, state_path, seq_state))\n queued += 1\n if queued <= 5 or queued % 5 == 0:\n total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))\n size_gb = total_bytes / (1024 ** 3)\n log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")\n\n if not work_items:\n if not RENDER_ROOT.exists():\n log(\"zip\", \"Render root 'Renders' not found; nothing to zip.\")\n else:\n log(\"zip\", \"Archives already up to date; no sequences needed zipping.\")\n log(\n \"scan\",\n f\"Summary: scanned {total_scanned}, quick-skipped {quick_skipped}, \"\n f\"state-skipped {state_skipped}, empty {empty_dirs}, queued {queued}\",\n )\n removed = cleanup_orphan_archives(verbose=verbose)\n if removed:\n log(\"zip\", f\"Removed {removed} orphan archive(s).\", verbose=verbose)\n return 0\n\n # Calculate RAM-aware worker count based on work items\n worker_count, ram_limits_dict = max_workers(requested_workers, work_items, verbose=verbose)\n log(\n \"init\",\n f\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\",\n )\n\n updated_paths: list[Path] = []\n\n total = len(work_items)\n completed = 0\n\n with ThreadPoolExecutor(max_workers=worker_count) as executor:\n future_map = {\n executor.submit(process_zip, seq_dir, zip_path, state_path, seq_state, ram_limits_dict.get(seq_dir), worker_count, verbose=verbose): seq_dir\n for seq_dir, zip_path, state_path, seq_state in work_items\n }\n\n for future in as_completed(future_map):\n updated_paths.extend(future.result())\n completed += 1\n seq_dir = future_map[future]\n rel = seq_dir.relative_to(RENDER_ROOT)\n log(\"zip\", f\"{completed}/{total} {rel}\")\n\n updated_count = len(updated_paths) // 2", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 973, + "column": 1 + }, + "endPosition": { + "line": 974, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:", + "lineNumber": 973, + "isSignature": true + }, + { + "lineNumber": 1077 + }, + { + "lineNumber": 1078 + }, + { + "text": " work_items.append((seq_dir, zip_path, state_path, seq_state))", + "lineNumber": 1079 + }, + { + "text": " queued += 1", + "lineNumber": 1080 + }, + { + "text": " if queued <= 5 or queued % 5 == 0:", + "lineNumber": 1081 + }, + { + "text": " total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))", + "lineNumber": 1082 + }, + { + "text": " size_gb = total_bytes / (1024 ** 3)", + "lineNumber": 1083 + }, + { + "text": " log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")", + "lineNumber": 1084 + }, + { + "lineNumber": 1085 + }, + { + "text": " if not work_items:", + "lineNumber": 1086 + }, + { + "text": " if not RENDER_ROOT.exists():", + "lineNumber": 1087 + }, + { + "text": " log(\"zip\", \"Render root 'Renders' not found; nothing to zip.\")", + "lineNumber": 1088 + }, + { + "text": " else:", + "lineNumber": 1089 + }, + { + "text": " log(\"zip\", \"Archives already up to date; no sequences needed zipping.\")", + "lineNumber": 1090 + }, + { + "text": " log(", + "lineNumber": 1091 + }, + { + "text": " \"scan\",", + "lineNumber": 1092 + }, + { + "text": " f\"Summary: scanned {total_scanned}, quick-skipped {quick_skipped}, \"", + "lineNumber": 1093 + }, + { + "text": " f\"state-skipped {state_skipped}, empty {empty_dirs}, queued {queued}\",", + "lineNumber": 1094 + }, + { + "text": " )", + "lineNumber": 1095 + }, + { + "text": " removed = cleanup_orphan_archives(verbose=verbose)", + "lineNumber": 1096 + }, + { + "text": " if removed:", + "lineNumber": 1097 + }, + { + "text": " log(\"zip\", f\"Removed {removed} orphan archive(s).\", verbose=verbose)", + "lineNumber": 1098 + }, + { + "text": " return 0", + "lineNumber": 1099 + }, + { + "lineNumber": 1100 + }, + { + "text": " # Calculate RAM-aware worker count based on work items", + "lineNumber": 1101 + }, + { + "text": " worker_count, ram_limits_dict = max_workers(requested_workers, work_items, verbose=verbose)", + "lineNumber": 1102 + }, + { + "text": " log(", + "lineNumber": 1103 + }, + { + "text": " \"init\",", + "lineNumber": 1104 + }, + { + "text": " f\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\",", + "lineNumber": 1105 + }, + { + "text": " )", + "lineNumber": 1106 + }, + { + "lineNumber": 1107 + }, + { + "text": " updated_paths: list[Path] = []", + "lineNumber": 1108 + }, + { + "lineNumber": 1109 + }, + { + "text": " total = len(work_items)", + "lineNumber": 1110 + }, + { + "text": " completed = 0", + "lineNumber": 1111 + }, + { + "lineNumber": 1112 + }, + { + "text": " with ThreadPoolExecutor(max_workers=worker_count) as executor:", + "lineNumber": 1113 + }, + { + "text": " future_map = {", + "lineNumber": 1114 + }, + { + "text": " executor.submit(process_zip, seq_dir, zip_path, state_path, seq_state, ram_limits_dict.get(seq_dir), worker_count, verbose=verbose): seq_dir", + "lineNumber": 1115 + }, + { + "text": " for seq_dir, zip_path, state_path, seq_state in work_items", + "lineNumber": 1116 + }, + { + "text": " }", + "lineNumber": 1117 + }, + { + "lineNumber": 1118 + }, + { + "text": " for future in as_completed(future_map):", + "lineNumber": 1119 + }, + { + "text": " updated_paths.extend(future.result())", + "lineNumber": 1120 + }, + { + "text": " completed += 1", + "lineNumber": 1121 + }, + { + "text": " seq_dir = future_map[future]", + "lineNumber": 1122 + }, + { + "text": " rel = seq_dir.relative_to(RENDER_ROOT)", + "lineNumber": 1123 + }, + { + "text": " log(\"zip\", f\"{completed}/{total} {rel}\")", + "lineNumber": 1124 + }, + { + "lineNumber": 1125 + }, + { + "text": " updated_count = len(updated_paths) // 2", + "lineNumber": 1126 + } + ] + }, + "score": 0.22207355499267578 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 852 + }, + "endPosition": { + "line": 957 + } + }, + "contents": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:\n\n key=lambda e: e[\"path\"]\n )\n \n for entry in files_to_restore:\n file_path = target_dir / entry[\"path\"]\n if not file_path.exists():\n if verbose:\n log(\"expand\", f\"Warning: File not found after extraction: {entry['path']}\", verbose_only=True, verbose=verbose)\n failed_count += 1\n continue\n \n stored_mtime_ns = entry[\"mtime_ns\"]\n \n # On Windows, round to 100-nanosecond boundaries (NTFS precision)\n if is_windows:\n # NTFS FileTime uses 100-nanosecond intervals\n stored_mtime_ns = (stored_mtime_ns // 100) * 100\n \n try:\n # Restore timestamp\n os.utime(file_path, ns=(stored_mtime_ns, stored_mtime_ns))\n \n # Verify the timestamp was set correctly (within filesystem precision tolerance)\n actual_stat = file_path.stat()\n actual_mtime_ns = actual_stat.st_mtime_ns\n \n # On Windows, compare at 100-nanosecond precision\n if", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 853, + "column": 1 + }, + "endPosition": { + "line": 854, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:", + "lineNumber": 853, + "isSignature": true + }, + { + "lineNumber": 906 + }, + { + "text": " key=lambda e: e[\"path\"]", + "lineNumber": 907 + }, + { + "text": " )", + "lineNumber": 908 + }, + { + "text": " ", + "lineNumber": 909 + }, + { + "text": " for entry in files_to_restore:", + "lineNumber": 910 + }, + { + "text": " file_path = target_dir / entry[\"path\"]", + "lineNumber": 911 + }, + { + "text": " if not file_path.exists():", + "lineNumber": 912 + }, + { + "text": " if verbose:", + "lineNumber": 913 + }, + { + "text": " log(\"expand\", f\"Warning: File not found after extraction: {entry['path']}\", verbose_only=True, verbose=verbose)", + "lineNumber": 914 + }, + { + "text": " failed_count += 1", + "lineNumber": 915 + }, + { + "text": " continue", + "lineNumber": 916 + }, + { + "text": " ", + "lineNumber": 917 + }, + { + "text": " stored_mtime_ns = entry[\"mtime_ns\"]", + "lineNumber": 918 + }, + { + "text": " ", + "lineNumber": 919 + }, + { + "text": " # On Windows, round to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 920 + }, + { + "text": " if is_windows:", + "lineNumber": 921 + }, + { + "text": " # NTFS FileTime uses 100-nanosecond intervals", + "lineNumber": 922 + }, + { + "text": " stored_mtime_ns = (stored_mtime_ns // 100) * 100", + "lineNumber": 923 + }, + { + "text": " ", + "lineNumber": 924 + }, + { + "text": " try:", + "lineNumber": 925 + }, + { + "text": " # Restore timestamp", + "lineNumber": 926 + }, + { + "text": " os.utime(file_path, ns=(stored_mtime_ns, stored_mtime_ns))", + "lineNumber": 927 + }, + { + "text": " ", + "lineNumber": 928 + }, + { + "text": " # Verify the timestamp was set correctly (within filesystem precision tolerance)", + "lineNumber": 929 + }, + { + "text": " actual_stat = file_path.stat()", + "lineNumber": 930 + }, + { + "text": " actual_mtime_ns = actual_stat.st_mtime_ns", + "lineNumber": 931 + }, + { + "text": " ", + "lineNumber": 932 + }, + { + "text": " # On Windows, compare at 100-nanosecond precision", + "lineNumber": 933 + }, + { + "text": " if", + "lineNumber": 934 + } + ] + }, + "score": 0.21984310448169708 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": { + "line": 64 + }, + "endPosition": { + "line": 107 + } + }, + "contents": "echo ^\t\t\t# Save to output folder\necho ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho ^\t\texcept Exception as e:\necho ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho ^\telse:\necho ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n\techo Script created successfully: !TEMP_SCRIPT!\n) else (\n\techo ERROR: Failed to create script file!\n\tpause\n\texit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho ACTION FAKE USER REMOVAL REPORT\necho ========================================\necho.\n\nREM Track updated counts per file during parse\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n\tif \"%%a\"==\"SUCCESS\" (\n\t\tset /a processed+=1\n\t\techo [!processed!] SUCCESS: %%b", + "signatures": {}, + "detailedLines": [ + { + "text": "echo ^\t\t\t# Save to output folder", + "lineNumber": 65 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 66 + }, + { + "text": "echo ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 67 + }, + { + "text": "echo ^\t\texcept Exception as e:", + "lineNumber": 68 + }, + { + "text": "echo ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 69 + }, + { + "text": "echo ^\telse:", + "lineNumber": 70 + }, + { + "text": "echo ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 71 + }, + { + "text": "echo.", + "lineNumber": 72 + }, + { + "text": "echo print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)", + "lineNumber": 73 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 74 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 77 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 78 + }, + { + "text": "\techo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 79 + }, + { + "text": ") else (", + "lineNumber": 80 + }, + { + "text": "\techo ERROR: Failed to create script file!", + "lineNumber": 81 + }, + { + "text": "\tpause", + "lineNumber": 82 + }, + { + "text": "\texit /b 1", + "lineNumber": 83 + }, + { + "text": ")", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 86 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 87 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 88 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 89 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 92 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 93 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 94 + }, + { + "lineNumber": 95 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 96 + }, + { + "text": "echo.", + "lineNumber": 97 + }, + { + "text": "echo ========================================", + "lineNumber": 98 + }, + { + "text": "echo ACTION FAKE USER REMOVAL REPORT", + "lineNumber": 99 + }, + { + "text": "echo ========================================", + "lineNumber": 100 + }, + { + "text": "echo.", + "lineNumber": 101 + }, + { + "lineNumber": 102 + }, + { + "text": "REM Track updated counts per file during parse", + "lineNumber": 103 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 104 + }, + { + "text": "\tif \"%%a\"==\"SUCCESS\" (", + "lineNumber": 105 + }, + { + "text": "\t\tset /a processed+=1", + "lineNumber": 106 + }, + { + "text": "\t\techo [!processed!] SUCCESS: %%b", + "lineNumber": 107 + } + ] + }, + "score": 0.2114613950252533 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 972 + }, + "endPosition": { + "line": 1144 + } + }, + "contents": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:\n\n old_stored_state = None\n if ZIPPER_TYPE == \"7z\":\n old_zip_path = zip_path.with_suffix(\".zip\")\n if old_zip_path.exists():\n old_state_path = state_path_for(old_zip_path)\n old_stored_state = load_state(old_state_path)\n # If old .zip exists and .7z doesn't, use old .zip's state for comparison\n if not zip_path.exists() and old_stored_state is not None:\n stored_state = old_stored_state\n \n # If .7z archive exists and we have stored state, do quick check before computing full state\n if zip_path.exists() and stored_state is not None:\n # Quick check: if directory mtime is older than archive, likely unchanged\n try:\n dir_mtime = seq_dir.stat().st_mtime_ns\n archive_mtime = zip_path.stat().st_mtime_ns\n # If directory wasn't modified since archive was created, skip state computation\n if dir_mtime <= archive_mtime:\n quick_skipped += 1\n if quick_skipped <= 5:\n log(\"scan\", f\"Skipping {rel} (unchanged since archive)\")\n # Still need to check for old .zip cleanup (we have .7z, so .zip is obsolete)\n if old_zip_path and old_zip_path.exists():\n old_zip_path.unlink(missing_ok=True)\n old_state_path = state_path_for(old_zip_path)\n if old_state_path.exists():\n old_state_path.unlink(missing_ok=True)\n continue\n except OSError:\n # If stat fails, fall through to full state computation\n pass\n \n # Compute current state only if we need to\n seq_state = compute_state(seq_dir)\n if not seq_state[\"files\"]:\n empty_dirs += 1", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 973, + "column": 1 + }, + "endPosition": { + "line": 974, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:", + "lineNumber": 973, + "isSignature": true + }, + { + "lineNumber": 1006 + }, + { + "text": " old_stored_state = None", + "lineNumber": 1007 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 1008 + }, + { + "text": " old_zip_path = zip_path.with_suffix(\".zip\")", + "lineNumber": 1009 + }, + { + "text": " if old_zip_path.exists():", + "lineNumber": 1010 + }, + { + "text": " old_state_path = state_path_for(old_zip_path)", + "lineNumber": 1011 + }, + { + "text": " old_stored_state = load_state(old_state_path)", + "lineNumber": 1012 + }, + { + "text": " # If old .zip exists and .7z doesn't, use old .zip's state for comparison", + "lineNumber": 1013 + }, + { + "text": " if not zip_path.exists() and old_stored_state is not None:", + "lineNumber": 1014 + }, + { + "text": " stored_state = old_stored_state", + "lineNumber": 1015 + }, + { + "text": " ", + "lineNumber": 1016 + }, + { + "text": " # If .7z archive exists and we have stored state, do quick check before computing full state", + "lineNumber": 1017 + }, + { + "text": " if zip_path.exists() and stored_state is not None:", + "lineNumber": 1018 + }, + { + "text": " # Quick check: if directory mtime is older than archive, likely unchanged", + "lineNumber": 1019 + }, + { + "text": " try:", + "lineNumber": 1020 + }, + { + "text": " dir_mtime = seq_dir.stat().st_mtime_ns", + "lineNumber": 1021 + }, + { + "text": " archive_mtime = zip_path.stat().st_mtime_ns", + "lineNumber": 1022 + }, + { + "text": " # If directory wasn't modified since archive was created, skip state computation", + "lineNumber": 1023 + }, + { + "text": " if dir_mtime <= archive_mtime:", + "lineNumber": 1024 + }, + { + "text": " quick_skipped += 1", + "lineNumber": 1025 + }, + { + "text": " if quick_skipped <= 5:", + "lineNumber": 1026 + }, + { + "text": " log(\"scan\", f\"Skipping {rel} (unchanged since archive)\")", + "lineNumber": 1027 + }, + { + "text": " # Still need to check for old .zip cleanup (we have .7z, so .zip is obsolete)", + "lineNumber": 1028 + }, + { + "text": " if old_zip_path and old_zip_path.exists():", + "lineNumber": 1029 + }, + { + "text": " old_zip_path.unlink(missing_ok=True)", + "lineNumber": 1030 + }, + { + "text": " old_state_path = state_path_for(old_zip_path)", + "lineNumber": 1031 + }, + { + "text": " if old_state_path.exists():", + "lineNumber": 1032 + }, + { + "text": " old_state_path.unlink(missing_ok=True)", + "lineNumber": 1033 + }, + { + "text": " continue", + "lineNumber": 1034 + }, + { + "text": " except OSError:", + "lineNumber": 1035 + }, + { + "text": " # If stat fails, fall through to full state computation", + "lineNumber": 1036 + }, + { + "text": " pass", + "lineNumber": 1037 + }, + { + "text": " ", + "lineNumber": 1038 + }, + { + "text": " # Compute current state only if we need to", + "lineNumber": 1039 + }, + { + "text": " seq_state = compute_state(seq_dir)", + "lineNumber": 1040 + }, + { + "text": " if not seq_state[\"files\"]:", + "lineNumber": 1041 + }, + { + "text": " empty_dirs += 1", + "lineNumber": 1042 + } + ] + }, + "score": 0.20994879305362701 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": { + "line": 103 + }, + "endPosition": { + "line": 161 + } + }, + "contents": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n\tif \"%%a\"==\"SUCCESS\" (\n\t\tset /a processed+=1\n\t\techo [!processed!] SUCCESS: %%b\n\t\techo Saved to: %%c\n\t\tREM Compare sizes and delete if reduction < threshold\n\t\tfor %%f in (\"%%b\") do set \"original_size=%%~zf\"\n\t\tfor %%f in (\"%%c\") do set \"compressed_size=%%~zf\"\n\t\tset /a savings=original_size-compressed_size\n\t\tif !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)\n\t\techo Original: !original_size! bytes\n\t\techo Output: !compressed_size! bytes\n\t\techo Reduction: !percent!%%\n\t\tif !percent! lss !threshold_percent! (\n\t\t\tdel \"%%c\" 2>nul\n\t\t\tset /a deleted_similar+=1\n\t\t\techo Deleted: too similar to original\n\t\t)\n\t\techo.\n\t) else if \"%%a\"==\"CRASH\" (\n\t\tset /a crashed+=1\n\t\techo [!crashed!] CRASHED: %%b\n\t\techo Error: %%c\n\t\techo.\n\t) else if \"%%a\"==\"SKIP_EXISTING\" (\n\t\tset /a skipped+=1\n\t\techo [!skipped!] SKIPPED ^(already exists^): %%b\n\t\techo.\n\t) else if \"%%a\"==\"SKIP_NOT_BLEND\" (\n\t\tset /a skipped+=1\n\t\techo [!skipped!] SKIPPED ^(not .blend^): %%b\n\t\techo.\n\t) else if \"%%a\"==\"UPDATED\" (\n\t\tREM Accumulate total updated actions\n\t\tfor /f \"delims= tokens=1\" %%x in (\"%%c\") do set \"updated_in_file=%%x\"\n\t\tset /a updated_total+=updated_in_file\n\t\techo Updated actions: !updated_in_file!\n\t)\n)\n\nREM Clean up output file\ndel /f /q \"blender_output.txt\" 2>nul\n\necho ========================================\necho SUMMARY\necho ========================================\necho Processed: !processed! files\necho Crashed: !crashed! files\necho Skipped: !skipped! files\necho Deleted ^(too similar^): !deleted_similar! files\necho Total actions updated: !updated_total!\necho Total: %* files\necho ========================================\necho.\necho Done!\npause", + "signatures": {}, + "detailedLines": [ + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 104 + }, + { + "text": "\tif \"%%a\"==\"SUCCESS\" (", + "lineNumber": 105 + }, + { + "text": "\t\tset /a processed+=1", + "lineNumber": 106 + }, + { + "text": "\t\techo [!processed!] SUCCESS: %%b", + "lineNumber": 107 + }, + { + "text": "\t\techo Saved to: %%c", + "lineNumber": 108 + }, + { + "text": "\t\tREM Compare sizes and delete if reduction < threshold", + "lineNumber": 109 + }, + { + "text": "\t\tfor %%f in (\"%%b\") do set \"original_size=%%~zf\"", + "lineNumber": 110 + }, + { + "text": "\t\tfor %%f in (\"%%c\") do set \"compressed_size=%%~zf\"", + "lineNumber": 111 + }, + { + "text": "\t\tset /a savings=original_size-compressed_size", + "lineNumber": 112 + }, + { + "text": "\t\tif !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)", + "lineNumber": 113 + }, + { + "text": "\t\techo Original: !original_size! bytes", + "lineNumber": 114 + }, + { + "text": "\t\techo Output: !compressed_size! bytes", + "lineNumber": 115 + }, + { + "text": "\t\techo Reduction: !percent!%%", + "lineNumber": 116 + }, + { + "text": "\t\tif !percent! lss !threshold_percent! (", + "lineNumber": 117 + }, + { + "text": "\t\t\tdel \"%%c\" 2>nul", + "lineNumber": 118 + }, + { + "text": "\t\t\tset /a deleted_similar+=1", + "lineNumber": 119 + }, + { + "text": "\t\t\techo Deleted: too similar to original", + "lineNumber": 120 + }, + { + "text": "\t\t)", + "lineNumber": 121 + }, + { + "text": "\t\techo.", + "lineNumber": 122 + }, + { + "text": "\t) else if \"%%a\"==\"CRASH\" (", + "lineNumber": 123 + }, + { + "text": "\t\tset /a crashed+=1", + "lineNumber": 124 + }, + { + "text": "\t\techo [!crashed!] CRASHED: %%b", + "lineNumber": 125 + }, + { + "text": "\t\techo Error: %%c", + "lineNumber": 126 + }, + { + "text": "\t\techo.", + "lineNumber": 127 + }, + { + "text": "\t) else if \"%%a\"==\"SKIP_EXISTING\" (", + "lineNumber": 128 + }, + { + "text": "\t\tset /a skipped+=1", + "lineNumber": 129 + }, + { + "text": "\t\techo [!skipped!] SKIPPED ^(already exists^): %%b", + "lineNumber": 130 + }, + { + "text": "\t\techo.", + "lineNumber": 131 + }, + { + "text": "\t) else if \"%%a\"==\"SKIP_NOT_BLEND\" (", + "lineNumber": 132 + }, + { + "text": "\t\tset /a skipped+=1", + "lineNumber": 133 + }, + { + "text": "\t\techo [!skipped!] SKIPPED ^(not .blend^): %%b", + "lineNumber": 134 + }, + { + "text": "\t\techo.", + "lineNumber": 135 + }, + { + "text": "\t) else if \"%%a\"==\"UPDATED\" (", + "lineNumber": 136 + }, + { + "text": "\t\tREM Accumulate total updated actions", + "lineNumber": 137 + }, + { + "text": "\t\tfor /f \"delims= tokens=1\" %%x in (\"%%c\") do set \"updated_in_file=%%x\"", + "lineNumber": 138 + }, + { + "text": "\t\tset /a updated_total+=updated_in_file", + "lineNumber": 139 + }, + { + "text": "\t\techo Updated actions: !updated_in_file!", + "lineNumber": 140 + }, + { + "text": "\t)", + "lineNumber": 141 + }, + { + "text": ")", + "lineNumber": 142 + }, + { + "lineNumber": 143 + }, + { + "text": "REM Clean up output file", + "lineNumber": 144 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 145 + }, + { + "lineNumber": 146 + }, + { + "text": "echo ========================================", + "lineNumber": 147 + }, + { + "text": "echo SUMMARY", + "lineNumber": 148 + }, + { + "text": "echo ========================================", + "lineNumber": 149 + }, + { + "text": "echo Processed: !processed! files", + "lineNumber": 150 + }, + { + "text": "echo Crashed: !crashed! files", + "lineNumber": 151 + }, + { + "text": "echo Skipped: !skipped! files", + "lineNumber": 152 + }, + { + "text": "echo Deleted ^(too similar^): !deleted_similar! files", + "lineNumber": 153 + }, + { + "text": "echo Total actions updated: !updated_total!", + "lineNumber": 154 + }, + { + "text": "echo Total: %* files", + "lineNumber": 155 + }, + { + "text": "echo ========================================", + "lineNumber": 156 + }, + { + "text": "echo.", + "lineNumber": 157 + }, + { + "text": "echo Done!", + "lineNumber": 158 + }, + { + "text": "pause", + "lineNumber": 159 + } + ] + }, + "score": 0.20691069960594177 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 102, + "column": 32 + }, + "endPosition": { + "line": 202 + } + }, + "contents": "CONFIG = load_config()\nzipper_val = CONFIG.get(\"zipper\", \"7z\")\n# Handle both old boolean format and new string format\nif isinstance(zipper_val, bool):\n ZIPPER_TYPE = \"7z\" if zipper_val else \"zip\"\nelse:\n ZIPPER_TYPE = str(zipper_val).lower()\n\nCOMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)\nif isinstance(COMPRESSION_LEVEL, str):\n try:\n COMPRESSION_LEVEL = int(COMPRESSION_LEVEL)\n except ValueError:\n COMPRESSION_LEVEL = 9\nif not isinstance(COMPRESSION_LEVEL, int):\n COMPRESSION_LEVEL = 9\nCOMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))\n\nCOMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"LZMA2\")\n# Validate compression method\nvalid_methods = {\"LZMA2\", \"PPMd\", \"BZip2\", \"Deflate\"}\nif COMPRESSION_METHOD not in valid_methods:\n COMPRESSION_METHOD = \"LZMA2\" # Default to LZMA2 for multi-threading support\n\nMAX_7Z_INSTANCES = CONFIG.get(\"Max7zInst\", 0)\nif MAX_7Z_INSTANCES is not None:\n if isinstance(MAX_7Z_INSTANCES, str):\n try:\n MAX_7Z_INSTANCES = int(MAX_7Z_INSTANCES)\n except ValueError:\n MAX_7Z_INSTANCES = 0\n if not isinstance(MAX_7Z_INSTANCES, int) or MAX_7Z_INSTANCES < 1:\n MAX_7Z_INSTANCES = 0\n# Treat 0 as None (auto-calculate)\nif MAX_7Z_INSTANCES == 0:\n MAX_7Z_INSTANCES = None\n\nSEVEN_Z_EXE: str | None = None\nif ZIPPER_TYPE == \"7z\":\n SEVEN_Z_EXE = shutil.which(\"7z\") or shutil.which(\"7za\")\n\n\ndef parse_args() -> argparse.Namespace:\n parser = argparse.ArgumentParser(description=\"Sync render sequences with zipped archives.\")\n parser.add_argument(\n \"--mode\",\n choices=(\"zip\", \"expand\"),\n default=\"zip\",\n help=\"zip sequences for commit (default) or expand tracked archives\",\n )\n parser.add_argument(\"--jobs\", type=int, help=\"max parallel workers\")\n parser.add_argument(\"--verbose\", action=\"store_true\", help=\"print extra progress details\")\n return parser.parse_args()\n\n\ndef get_available_ram() -> int | None:\n \"\"\"", + "signatures": {}, + "detailedLines": [ + { + "text": "CONFIG = load_config()", + "lineNumber": 106 + }, + { + "text": "zipper_val = CONFIG.get(\"zipper\", \"7z\")", + "lineNumber": 107 + }, + { + "text": "# Handle both old boolean format and new string format", + "lineNumber": 108 + }, + { + "text": "if isinstance(zipper_val, bool):", + "lineNumber": 109 + }, + { + "text": " ZIPPER_TYPE = \"7z\" if zipper_val else \"zip\"", + "lineNumber": 110 + }, + { + "text": "else:", + "lineNumber": 111 + }, + { + "text": " ZIPPER_TYPE = str(zipper_val).lower()", + "lineNumber": 112 + }, + { + "lineNumber": 113 + }, + { + "text": "COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)", + "lineNumber": 114 + }, + { + "text": "if isinstance(COMPRESSION_LEVEL, str):", + "lineNumber": 115 + }, + { + "text": " try:", + "lineNumber": 116 + }, + { + "text": " COMPRESSION_LEVEL = int(COMPRESSION_LEVEL)", + "lineNumber": 117 + }, + { + "text": " except ValueError:", + "lineNumber": 118 + }, + { + "text": " COMPRESSION_LEVEL = 9", + "lineNumber": 119 + }, + { + "text": "if not isinstance(COMPRESSION_LEVEL, int):", + "lineNumber": 120 + }, + { + "text": " COMPRESSION_LEVEL = 9", + "lineNumber": 121 + }, + { + "text": "COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))", + "lineNumber": 122 + }, + { + "lineNumber": 123 + }, + { + "text": "COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"LZMA2\")", + "lineNumber": 124 + }, + { + "text": "# Validate compression method", + "lineNumber": 125 + }, + { + "text": "valid_methods = {\"LZMA2\", \"PPMd\", \"BZip2\", \"Deflate\"}", + "lineNumber": 126 + }, + { + "text": "if COMPRESSION_METHOD not in valid_methods:", + "lineNumber": 127 + }, + { + "text": " COMPRESSION_METHOD = \"LZMA2\" # Default to LZMA2 for multi-threading support", + "lineNumber": 128 + }, + { + "lineNumber": 129 + }, + { + "text": "MAX_7Z_INSTANCES = CONFIG.get(\"Max7zInst\", 0)", + "lineNumber": 130 + }, + { + "text": "if MAX_7Z_INSTANCES is not None:", + "lineNumber": 131 + }, + { + "text": " if isinstance(MAX_7Z_INSTANCES, str):", + "lineNumber": 132 + }, + { + "text": " try:", + "lineNumber": 133 + }, + { + "text": " MAX_7Z_INSTANCES = int(MAX_7Z_INSTANCES)", + "lineNumber": 134 + }, + { + "text": " except ValueError:", + "lineNumber": 135 + }, + { + "text": " MAX_7Z_INSTANCES = 0", + "lineNumber": 136 + }, + { + "text": " if not isinstance(MAX_7Z_INSTANCES, int) or MAX_7Z_INSTANCES < 1:", + "lineNumber": 137 + }, + { + "text": " MAX_7Z_INSTANCES = 0", + "lineNumber": 138 + }, + { + "text": "# Treat 0 as None (auto-calculate)", + "lineNumber": 139 + }, + { + "text": "if MAX_7Z_INSTANCES == 0:", + "lineNumber": 140 + }, + { + "text": " MAX_7Z_INSTANCES = None", + "lineNumber": 141 + }, + { + "lineNumber": 142 + }, + { + "text": "SEVEN_Z_EXE: str | None = None", + "lineNumber": 143 + }, + { + "text": "if ZIPPER_TYPE == \"7z\":", + "lineNumber": 144 + }, + { + "text": " SEVEN_Z_EXE = shutil.which(\"7z\") or shutil.which(\"7za\")", + "lineNumber": 145 + }, + { + "lineNumber": 146 + }, + { + "lineNumber": 147 + }, + { + "text": "def parse_args() -> argparse.Namespace:", + "lineNumber": 148, + "isSignature": true + }, + { + "text": " parser = argparse.ArgumentParser(description=\"Sync render sequences with zipped archives.\")", + "lineNumber": 149 + }, + { + "text": " parser.add_argument(", + "lineNumber": 150 + }, + { + "text": " \"--mode\",", + "lineNumber": 151 + }, + { + "text": " choices=(\"zip\", \"expand\"),", + "lineNumber": 152 + }, + { + "text": " default=\"zip\",", + "lineNumber": 153 + }, + { + "text": " help=\"zip sequences for commit (default) or expand tracked archives\",", + "lineNumber": 154 + }, + { + "text": " )", + "lineNumber": 155 + }, + { + "text": " parser.add_argument(\"--jobs\", type=int, help=\"max parallel workers\")", + "lineNumber": 156 + }, + { + "text": " parser.add_argument(\"--verbose\", action=\"store_true\", help=\"print extra progress details\")", + "lineNumber": 157 + }, + { + "text": " return parser.parse_args()", + "lineNumber": 158, + "isSignature": true + }, + { + "lineNumber": 159 + }, + { + "lineNumber": 160 + }, + { + "text": "def get_available_ram() -> int | None:", + "lineNumber": 161, + "isSignature": true + }, + { + "text": " \"\"\"", + "lineNumber": 162 + } + ] + }, + "score": 0.20644600689411163 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitignore", + "range": { + "startPosition": {}, + "endPosition": { + "line": 123, + "column": 7 + } + }, + "contents": "# Blender Studio Git Management - .gitignore\n\n# Blender auto-save files\n*.blend1\n*.blend2\n*.blend3\n*.blend4\n*.blend5\n*.blend6\n*.blend7\n*.blend8\n*.blend9\n*.blend10\n*.blend11\n*.blend12\n*.blend13\n*.blend14\n*.blend15\n*.blend16\n*.blend17\n*.blend18\n*.blend19\n*.blend20\n*.blend21\n*.blend22\n*.blend23\n*.blend24\n*.blend25\n*.blend26\n*.blend27\n*.blend28\n*.blend29\n*.blend30\n*.blend31\n*.blend32\n\n# Blender cache and temporary files\n*.swp\n*.swo\n*~\n*.tmp\n*.temp\n\n# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\ndesktop.ini\n\n# Editor files\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n\n# Large media files (uncomment if you want to exclude rendered outputs)\n# *.mp4\n# *.avi\n# *.mov\n# *.png\n# *.jpg\n# *.jpeg\n# *.exr\n# *.hdr\n# *.tiff\n# *.tga\n\n# Audio files (uncomment if excluding)\n# *.wav\n# *.mp3\n# *.aac\n# *.ogg\n\n# Log files\n*.log\n\n# Python cache\n__pycache__/\n*.pyc\n*.pyo\n*.pyd\n.Python\n\n# Node modules (if using any web tools)\nnode_modules/\n\n# Environment files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Backup files\n*.bak\n*.backup\n*.old\n\n# Temporary files\ntemp/\ntmp/\ncache/ \n\nRenders/**\n!Renders/_zipped/\n!Renders/_zipped/**\n!Renders/**/\n!Renders/**/*.bat\n!Renders/**/*.log\nseq/\n\n# Premiere\n*.prlock\nAdobe Premiere Pro Video Previews/\nAdobe Premiere Pro Audio Previews/\nAdobe Premiere Pro (Beta) Video Previews/\nAdobe Premiere Pro (Beta) Audio Previews/\n\nPr/RnR/", + "signatures": {}, + "detailedLines": [ + { + "text": "# Blender Studio Git Management - .gitignore", + "lineNumber": 1 + }, + { + "lineNumber": 2 + }, + { + "text": "# Blender auto-save files", + "lineNumber": 3 + }, + { + "text": "*.blend1", + "lineNumber": 4 + }, + { + "text": "*.blend2", + "lineNumber": 5 + }, + { + "text": "*.blend3", + "lineNumber": 6 + }, + { + "text": "*.blend4", + "lineNumber": 7 + }, + { + "text": "*.blend5", + "lineNumber": 8 + }, + { + "text": "*.blend6", + "lineNumber": 9 + }, + { + "text": "*.blend7", + "lineNumber": 10 + }, + { + "text": "*.blend8", + "lineNumber": 11 + }, + { + "text": "*.blend9", + "lineNumber": 12 + }, + { + "text": "*.blend10", + "lineNumber": 13 + }, + { + "text": "*.blend11", + "lineNumber": 14 + }, + { + "text": "*.blend12", + "lineNumber": 15 + }, + { + "text": "*.blend13", + "lineNumber": 16 + }, + { + "text": "*.blend14", + "lineNumber": 17 + }, + { + "text": "*.blend15", + "lineNumber": 18 + }, + { + "text": "*.blend16", + "lineNumber": 19 + }, + { + "text": "*.blend17", + "lineNumber": 20 + }, + { + "text": "*.blend18", + "lineNumber": 21 + }, + { + "text": "*.blend19", + "lineNumber": 22 + }, + { + "text": "*.blend20", + "lineNumber": 23 + }, + { + "text": "*.blend21", + "lineNumber": 24 + }, + { + "text": "*.blend22", + "lineNumber": 25 + }, + { + "text": "*.blend23", + "lineNumber": 26 + }, + { + "text": "*.blend24", + "lineNumber": 27 + }, + { + "text": "*.blend25", + "lineNumber": 28 + }, + { + "text": "*.blend26", + "lineNumber": 29 + }, + { + "text": "*.blend27", + "lineNumber": 30 + }, + { + "text": "*.blend28", + "lineNumber": 31 + }, + { + "text": "*.blend29", + "lineNumber": 32 + }, + { + "text": "*.blend30", + "lineNumber": 33 + }, + { + "text": "*.blend31", + "lineNumber": 34 + }, + { + "text": "*.blend32", + "lineNumber": 35 + }, + { + "lineNumber": 36 + }, + { + "text": "# Blender cache and temporary files", + "lineNumber": 37 + }, + { + "text": "*.swp", + "lineNumber": 38 + }, + { + "text": "*.swo", + "lineNumber": 39 + }, + { + "text": "*~", + "lineNumber": 40 + }, + { + "text": "*.tmp", + "lineNumber": 41 + }, + { + "text": "*.temp", + "lineNumber": 42 + }, + { + "lineNumber": 43 + }, + { + "text": "# OS generated files", + "lineNumber": 44 + }, + { + "text": ".DS_Store", + "lineNumber": 45 + }, + { + "text": ".DS_Store?", + "lineNumber": 46 + }, + { + "text": "._*", + "lineNumber": 47 + }, + { + "text": ".Spotlight-V100", + "lineNumber": 48 + }, + { + "text": ".Trashes", + "lineNumber": 49 + }, + { + "text": "ehthumbs.db", + "lineNumber": 50 + }, + { + "text": "Thumbs.db", + "lineNumber": 51 + }, + { + "text": "desktop.ini", + "lineNumber": 52 + }, + { + "lineNumber": 53 + }, + { + "text": "# Editor files", + "lineNumber": 54 + }, + { + "text": ".vscode/", + "lineNumber": 55 + }, + { + "text": ".idea/", + "lineNumber": 56 + }, + { + "text": "*.swp", + "lineNumber": 57 + }, + { + "text": "*.swo", + "lineNumber": 58 + }, + { + "text": "*~", + "lineNumber": 59 + }, + { + "lineNumber": 60 + }, + { + "text": "# Large media files (uncomment if you want to exclude rendered outputs)", + "lineNumber": 61 + }, + { + "text": "# *.mp4", + "lineNumber": 62 + }, + { + "text": "# *.avi", + "lineNumber": 63 + }, + { + "text": "# *.mov", + "lineNumber": 64 + }, + { + "text": "# *.png", + "lineNumber": 65 + }, + { + "text": "# *.jpg", + "lineNumber": 66 + }, + { + "text": "# *.jpeg", + "lineNumber": 67 + }, + { + "text": "# *.exr", + "lineNumber": 68 + }, + { + "text": "# *.hdr", + "lineNumber": 69 + }, + { + "text": "# *.tiff", + "lineNumber": 70 + }, + { + "text": "# *.tga", + "lineNumber": 71 + }, + { + "lineNumber": 72 + }, + { + "text": "# Audio files (uncomment if excluding)", + "lineNumber": 73 + }, + { + "text": "# *.wav", + "lineNumber": 74 + }, + { + "text": "# *.mp3", + "lineNumber": 75 + }, + { + "text": "# *.aac", + "lineNumber": 76 + }, + { + "text": "# *.ogg", + "lineNumber": 77 + }, + { + "lineNumber": 78 + }, + { + "text": "# Log files", + "lineNumber": 79 + }, + { + "text": "*.log", + "lineNumber": 80 + }, + { + "lineNumber": 81 + }, + { + "text": "# Python cache", + "lineNumber": 82 + }, + { + "text": "__pycache__/", + "lineNumber": 83 + }, + { + "text": "*.pyc", + "lineNumber": 84 + }, + { + "text": "*.pyo", + "lineNumber": 85 + }, + { + "text": "*.pyd", + "lineNumber": 86 + }, + { + "text": ".Python", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": "# Node modules (if using any web tools)", + "lineNumber": 89 + }, + { + "text": "node_modules/", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": "# Environment files", + "lineNumber": 92 + }, + { + "text": ".env", + "lineNumber": 93 + }, + { + "text": ".env.local", + "lineNumber": 94 + }, + { + "text": ".env.development.local", + "lineNumber": 95 + }, + { + "text": ".env.test.local", + "lineNumber": 96 + }, + { + "text": ".env.production.local", + "lineNumber": 97 + }, + { + "lineNumber": 98 + }, + { + "text": "# Backup files", + "lineNumber": 99 + }, + { + "text": "*.bak", + "lineNumber": 100 + }, + { + "text": "*.backup", + "lineNumber": 101 + }, + { + "text": "*.old", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": "# Temporary files", + "lineNumber": 104 + }, + { + "text": "temp/", + "lineNumber": 105 + }, + { + "text": "tmp/", + "lineNumber": 106 + }, + { + "text": "cache/ ", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": "Renders/**", + "lineNumber": 109 + }, + { + "text": "!Renders/_zipped/", + "lineNumber": 110 + }, + { + "text": "!Renders/_zipped/**", + "lineNumber": 111 + }, + { + "text": "!Renders/**/", + "lineNumber": 112 + }, + { + "text": "!Renders/**/*.bat", + "lineNumber": 113 + }, + { + "text": "!Renders/**/*.log", + "lineNumber": 114 + }, + { + "text": "seq/", + "lineNumber": 115 + }, + { + "lineNumber": 116 + }, + { + "text": "# Premiere", + "lineNumber": 117 + }, + { + "text": "*.prlock", + "lineNumber": 118 + }, + { + "text": "Adobe Premiere Pro Video Previews/", + "lineNumber": 119 + }, + { + "text": "Adobe Premiere Pro Audio Previews/", + "lineNumber": 120 + }, + { + "text": "Adobe Premiere Pro (Beta) Video Previews/", + "lineNumber": 121 + }, + { + "text": "Adobe Premiere Pro (Beta) Audio Previews/", + "lineNumber": 122 + }, + { + "lineNumber": 123 + }, + { + "text": "Pr/RnR/", + "lineNumber": 124 + } + ] + }, + "score": 0.204682394862175 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 969, + "column": 53 + }, + "endPosition": { + "line": 1144 + } + }, + "contents": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:\n work_items: list[tuple[Path, Path, Path, dict]] = []\n log(\"init\", f\"Scanning sequences under {RENDER_ROOT.resolve()}\")\n total_scanned = 0\n quick_skipped = 0\n state_skipped = 0\n empty_dirs = 0\n queued = 0\n\n if RENDER_ROOT.exists():\n for seq_dir in find_sequence_dirs(RENDER_ROOT, verbose=verbose):\n total_scanned += 1\n rel = seq_dir.relative_to(RENDER_ROOT)\n if total_scanned <= 5 or total_scanned % 10 == 0:\n log(\"scan\", f\"[{total_scanned}] Inspecting {rel}\")\n\n # Get the target archive path (will be .7z if ZIPPER_TYPE is \"7z\")\n zip_path = archive_path_for(seq_dir)\n state_path = state_path_for(zip_path)\n \n # Quick check: if archive exists, load stored state first (fast)\n stored_state = load_state(state_path)\n \n # Debug: log if stored state is missing\n if stored_state is None:\n if zip_path.exists():\n if verbose:\n log(\"scan\", f\"Warning: {rel} archive exists but no stored state file found at {state_path}\", verbose_only=True, verbose=verbose)\n else:\n if verbose:\n log(\"scan\", f\"Info: {rel} no archive or state file (will create new)\", verbose_only=True, verbose=verbose)\n \n # Check if we need to upgrade from .zip to .7z\n old_zip_path = None\n old_stored_state = None\n if ZIPPER_TYPE == \"7z\":\n old_zip_path = zip_path.with_suffix(\".zip\")", + "signatures": {}, + "detailedLines": [ + { + "text": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:", + "lineNumber": 973, + "isSignature": true + }, + { + "text": " work_items: list[tuple[Path, Path, Path, dict]] = []", + "lineNumber": 974 + }, + { + "text": " log(\"init\", f\"Scanning sequences under {RENDER_ROOT.resolve()}\")", + "lineNumber": 975 + }, + { + "text": " total_scanned = 0", + "lineNumber": 976 + }, + { + "text": " quick_skipped = 0", + "lineNumber": 977 + }, + { + "text": " state_skipped = 0", + "lineNumber": 978 + }, + { + "text": " empty_dirs = 0", + "lineNumber": 979 + }, + { + "text": " queued = 0", + "lineNumber": 980 + }, + { + "lineNumber": 981 + }, + { + "text": " if RENDER_ROOT.exists():", + "lineNumber": 982 + }, + { + "text": " for seq_dir in find_sequence_dirs(RENDER_ROOT, verbose=verbose):", + "lineNumber": 983 + }, + { + "text": " total_scanned += 1", + "lineNumber": 984 + }, + { + "text": " rel = seq_dir.relative_to(RENDER_ROOT)", + "lineNumber": 985 + }, + { + "text": " if total_scanned <= 5 or total_scanned % 10 == 0:", + "lineNumber": 986 + }, + { + "text": " log(\"scan\", f\"[{total_scanned}] Inspecting {rel}\")", + "lineNumber": 987 + }, + { + "lineNumber": 988 + }, + { + "text": " # Get the target archive path (will be .7z if ZIPPER_TYPE is \"7z\")", + "lineNumber": 989 + }, + { + "text": " zip_path = archive_path_for(seq_dir)", + "lineNumber": 990 + }, + { + "text": " state_path = state_path_for(zip_path)", + "lineNumber": 991 + }, + { + "text": " ", + "lineNumber": 992 + }, + { + "text": " # Quick check: if archive exists, load stored state first (fast)", + "lineNumber": 993 + }, + { + "text": " stored_state = load_state(state_path)", + "lineNumber": 994 + }, + { + "text": " ", + "lineNumber": 995 + }, + { + "text": " # Debug: log if stored state is missing", + "lineNumber": 996 + }, + { + "text": " if stored_state is None:", + "lineNumber": 997 + }, + { + "text": " if zip_path.exists():", + "lineNumber": 998 + }, + { + "text": " if verbose:", + "lineNumber": 999 + }, + { + "text": " log(\"scan\", f\"Warning: {rel} archive exists but no stored state file found at {state_path}\", verbose_only=True, verbose=verbose)", + "lineNumber": 1000 + }, + { + "text": " else:", + "lineNumber": 1001 + }, + { + "text": " if verbose:", + "lineNumber": 1002 + }, + { + "text": " log(\"scan\", f\"Info: {rel} no archive or state file (will create new)\", verbose_only=True, verbose=verbose)", + "lineNumber": 1003 + }, + { + "text": " ", + "lineNumber": 1004 + }, + { + "text": " # Check if we need to upgrade from .zip to .7z", + "lineNumber": 1005 + }, + { + "text": " old_zip_path = None", + "lineNumber": 1006 + }, + { + "text": " old_stored_state = None", + "lineNumber": 1007 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 1008 + }, + { + "text": " old_zip_path = zip_path.with_suffix(\".zip\")", + "lineNumber": 1009 + } + ] + }, + "score": 0.1996699571609497 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": {}, + "endPosition": { + "line": 106 + } + }, + "contents": "import bpy\nimport re\nimport os\n\ndef link_bsdf_materials():\n \"\"\"Link all materials from the BSDF library file\"\"\"\n \n library_path = r\"R:\\Creative\\artsy\\maya\\0 ProjectStructure\\1 BlenderAssets\\Amazon\\MATERIALS_BSDF_pallette_v1.0.blend\"\n \n if not os.path.exists(library_path):\n print(f\"Warning: Library file not found at {library_path}\")\n return []\n \n print(f\"Linking materials from: {library_path}\")\n \n # Get list of materials before linking\n materials_before = set(bpy.data.materials.keys())\n \n # Link all materials from the library file\n with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):\n # Link all materials\n data_to.materials = data_from.materials\n \n # Get list of newly linked materials\n materials_after = set(bpy.data.materials.keys())\n newly_linked = materials_after - materials_before\n \n print(f\"Linked {len(newly_linked)} materials from library\")\n for mat_name in sorted(newly_linked):\n print(f\" - {mat_name}\")\n \n return list(newly_linked)\n\ndef remap_appended_to_linked():\n \"\"\"Remap any appended BSDF materials to their linked counterparts\"\"\"", + "signatures": {}, + "detailedLines": [ + { + "text": "import bpy", + "lineNumber": 1 + }, + { + "text": "import re", + "lineNumber": 2 + }, + { + "text": "import os", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": "def link_bsdf_materials():", + "lineNumber": 5, + "isSignature": true + }, + { + "text": " \"\"\"Link all materials from the BSDF library file\"\"\"", + "lineNumber": 6 + }, + { + "text": " ", + "lineNumber": 7 + }, + { + "text": " library_path = r\"R:\\Creative\\artsy\\maya\\0 ProjectStructure\\1 BlenderAssets\\Amazon\\MATERIALS_BSDF_pallette_v1.0.blend\"", + "lineNumber": 8 + }, + { + "text": " ", + "lineNumber": 9 + }, + { + "text": " if not os.path.exists(library_path):", + "lineNumber": 10 + }, + { + "text": " print(f\"Warning: Library file not found at {library_path}\")", + "lineNumber": 11 + }, + { + "text": " return []", + "lineNumber": 12 + }, + { + "text": " ", + "lineNumber": 13 + }, + { + "text": " print(f\"Linking materials from: {library_path}\")", + "lineNumber": 14 + }, + { + "text": " ", + "lineNumber": 15 + }, + { + "text": " # Get list of materials before linking", + "lineNumber": 16 + }, + { + "text": " materials_before = set(bpy.data.materials.keys())", + "lineNumber": 17 + }, + { + "text": " ", + "lineNumber": 18 + }, + { + "text": " # Link all materials from the library file", + "lineNumber": 19 + }, + { + "text": " with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):", + "lineNumber": 20 + }, + { + "text": " # Link all materials", + "lineNumber": 21 + }, + { + "text": " data_to.materials = data_from.materials", + "lineNumber": 22 + }, + { + "text": " ", + "lineNumber": 23 + }, + { + "text": " # Get list of newly linked materials", + "lineNumber": 24 + }, + { + "text": " materials_after = set(bpy.data.materials.keys())", + "lineNumber": 25 + }, + { + "text": " newly_linked = materials_after - materials_before", + "lineNumber": 26 + }, + { + "text": " ", + "lineNumber": 27 + }, + { + "text": " print(f\"Linked {len(newly_linked)} materials from library\")", + "lineNumber": 28 + }, + { + "text": " for mat_name in sorted(newly_linked):", + "lineNumber": 29 + }, + { + "text": " print(f\" - {mat_name}\")", + "lineNumber": 30 + }, + { + "text": " ", + "lineNumber": 31 + }, + { + "text": " return list(newly_linked)", + "lineNumber": 32, + "isSignature": true + }, + { + "lineNumber": 33 + }, + { + "text": "def remap_appended_to_linked():", + "lineNumber": 34, + "isSignature": true + }, + { + "text": " \"\"\"Remap any appended BSDF materials to their linked counterparts\"\"\"", + "lineNumber": 35 + } + ] + }, + "score": 0.19845426082611084 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 557, + "column": 33 + }, + "endPosition": { + "line": 638 + } + }, + "contents": "def load_state(state_path: Path) -> dict | None:\n if not state_path.exists():\n return None\n try:\n state = json.loads(state_path.read_text())\n # Normalize timestamps in loaded state to ensure consistency\n # This handles state files created before normalization was added\n is_windows = platform.system() == \"Windows\"\n if is_windows and \"files\" in state:\n for entry in state.get(\"files\", []):\n if \"mtime_ns\" in entry:\n # Normalize to 100-nanosecond boundaries (NTFS precision)\n entry[\"mtime_ns\"] = (entry[\"mtime_ns\"] // 100) * 100\n return state\n except json.JSONDecodeError:\n return None\n\n\ndef state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "def load_state(state_path: Path) -> dict | None:", + "lineNumber": 561, + "isSignature": true + }, + { + "text": " if not state_path.exists():", + "lineNumber": 562 + }, + { + "text": " return None", + "lineNumber": 563 + }, + { + "text": " try:", + "lineNumber": 564 + }, + { + "text": " state = json.loads(state_path.read_text())", + "lineNumber": 565 + }, + { + "text": " # Normalize timestamps in loaded state to ensure consistency", + "lineNumber": 566 + }, + { + "text": " # This handles state files created before normalization was added", + "lineNumber": 567 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 568 + }, + { + "text": " if is_windows and \"files\" in state:", + "lineNumber": 569 + }, + { + "text": " for entry in state.get(\"files\", []):", + "lineNumber": 570 + }, + { + "text": " if \"mtime_ns\" in entry:", + "lineNumber": 571 + }, + { + "text": " # Normalize to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 572 + }, + { + "text": " entry[\"mtime_ns\"] = (entry[\"mtime_ns\"] // 100) * 100", + "lineNumber": 573 + }, + { + "text": " return state", + "lineNumber": 574 + }, + { + "text": " except json.JSONDecodeError:", + "lineNumber": 575 + }, + { + "text": " return None", + "lineNumber": 576, + "isSignature": true + }, + { + "lineNumber": 577 + }, + { + "lineNumber": 578 + }, + { + "text": "def state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:", + "lineNumber": 579, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 580 + } + ] + }, + "score": 0.19832873344421387 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "compress|compressor|blend", + "caseInsensitive": true +} +``` +Result: + +```json +{ + "success": { + "pattern": "compress|compressor|blend", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": { + "matches": [ + { + "file": "zip_sequences.py", + "matches": [ + { + "lineNumber": 56, + "content": " \"compression\": 9," + }, + { + "lineNumber": 57, + "content": " \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate" + }, + { + "lineNumber": 114, + "content": "COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 115, + "content": "if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 117, + "content": " COMPRESSION_LEVEL = int(COMPRESSION_LEVEL)" + }, + { + "lineNumber": 119, + "content": " COMPRESSION_LEVEL = 9" + }, + { + "lineNumber": 120, + "content": "if not isinstance(COMPRESSION_LEVEL, int):" + }, + { + "lineNumber": 121, + "content": " COMPRESSION_LEVEL = 9" + }, + { + "lineNumber": 122, + "content": "COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))" + }, + { + "lineNumber": 124, + "content": "COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"LZMA2\")" + }, + { + "lineNumber": 125, + "content": "# Validate compression method" + }, + { + "lineNumber": 127, + "content": "if COMPRESSION_METHOD not in valid_methods:" + }, + { + "lineNumber": 128, + "content": " COMPRESSION_METHOD = \"LZMA2\" # Default to LZMA2 for multi-threading support" + }, + { + "lineNumber": 171, + "content": " # Reserve 20% for system, use 80% for compression jobs" + }, + { + "lineNumber": 197, + "content": " # Reserve 20% for system, use 80% for compression jobs" + }, + { + "lineNumber": 207, + "content": " \"\"\"Estimate RAM usage per compression job based on folder size." + }, + { + "lineNumber": 235, + "content": " # zip compression is more memory-efficient" + }, + { + "lineNumber": 312, + "content": " # Use 95% of available RAM for compression jobs" + }, + { + "lineNumber": 313, + "content": " compression_ram = int(available_ram * 0.95)" + }, + { + "lineNumber": 336, + "content": " if total_estimated_ram <= compression_ram:" + }, + { + "lineNumber": 360, + "content": " bin_remaining.append(compression_ram - estimated_ram)" + }, + { + "lineNumber": 375, + "content": " compression_ram_gb = compression_ram / (1024 ** 3)" + }, + { + "lineNumber": 379, + "content": " f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (95%)\"," + }, + { + "lineNumber": 389, + "content": " if total_estimated_ram <= compression_ram:" + }, + { + "lineNumber": 438, + "content": " # For zip compression, use existing estimation-based approach" + }, + { + "lineNumber": 669, + "content": " \"7z compression requested but 7z executable not found in PATH. \"" + }, + { + "lineNumber": 723, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 727, + "content": " # Set compression method and memory/dictionary size based on method" + }, + { + "lineNumber": 728, + "content": " # At compression level 0, use Copy (store) method for maximum speed" + }, + { + "lineNumber": 730, + "content": " if COMPRESSION_LEVEL == 0:" + }, + { + "lineNumber": 731, + "content": " # Level 0 = no compression, just store files (fastest)" + }, + { + "lineNumber": 736, + "content": " # Compression levels 1-9: use configured compression method" + }, + { + "lineNumber": 737, + "content": " if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 740, + "content": " elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 744, + "content": " elif COMPRESSION_METHOD == \"BZip2\":" + }, + { + "lineNumber": 749, + "content": " elif COMPRESSION_METHOD == \"Deflate\":" + }, + { + "lineNumber": 761, + "content": " if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 794, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 828, + "content": " if COMPRESSION_LEVEL <= 0:" + }, + { + "lineNumber": 829, + "content": " compression = ZIP_STORED" + }, + { + "lineNumber": 832, + "content": " compression = ZIP_DEFLATED" + }, + { + "lineNumber": 833, + "content": " zip_kwargs = {\"compresslevel\": COMPRESSION_LEVEL}" + }, + { + "lineNumber": 835, + "content": " with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:" + }, + { + "lineNumber": 1084, + "content": " log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")" + }, + { + "lineNumber": 1105, + "content": " f\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\"," + }, + { + "lineNumber": 1286, + "content": " log(\"init\", f\"7z executable: {exe}, Max7zInst: {max_inst}, method: {COMPRESSION_METHOD}, level: {COMPRESSION_LEVEL}\")" + } + ] + }, + { + "file": ".specstory/history/2025-10-30_00-40Z-update-gitignore-files-in-project-repos.md", + "matches": [ + { + "lineNumber": 9, + "content": "@2025-08-08_19-32Z-setting-remote-for-local-git-repo.md @2025-08-15_18-13Z-make-file-input-draggable.md @2025-08-15_18-50Z-change-default-git-branch-to-main.md @2025-08-17_20-18Z-change-date-format-in-workflow-scripts.md @2025-08-18_21-09Z-ignore-folders-named-archive.md @2025-08-19_18-08Z-rename-images-to-match-folder-names.md @2025-08-19_15-59Z-update-latest-gitignore-file.md @2025-08-20_20-39Z-stage-all-and-commit-changes.md @2025-08-21_00-14Z-apply-changes-to-home-version.md @2025-08-21_00-15Z-implement-changes-while-respecting-home-pathing.md @2025-09-05_21-38Z-remap-non-working-material-names.md @2025-09-09_21-49Z-create-script-to-remove-fake-users.md @2025-09-11_21-52Z-move-mocap-folder-to-assets.md @2025-08-26_22-03Z-questioning-the-reason-why.md @2025-09-15_16-30Z-take-the-most-recent-changes.md @2025-09-17_19-23Z-check-blend-compression-before-saving.md @2025-10-13_17-59Z-check-git-repositories-and-uncommitted-changes.md @2025-10-07_15-36Z-check-for-uncommitted-changes-in-git" + } + ] + }, + { + "file": ".specstory/history/2025-11-25_20-03Z-fix-archive-removal-issue-in-zipseqarchv.md", + "matches": [ + { + "lineNumber": 1413, + "content": "----- 9/17/2025 1:52 PM 5757 compress_blend_files.bat" + }, + { + "lineNumber": 1483, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nimport traceback\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.sys" + }, + { + "lineNumber": 1693, + "content": "[zip] Archive 04 Securing Your Seam\\08_smooth blend.zip has matching sequence directory; keeping" + }, + { + "lineNumber": 1814, + "content": "The cleanup function now runs in all cases, not just when sequences are queued for compression. This ensures that when you delete sequences, their corresponding archives and metadata files are automatically cleaned up on the next run of `ZipSeqArchv.bat`." + }, + { + "lineNumber": 2194, + "content": "[scan] Queued scab_v2.1 for compression (~1.31GB) [1 total]" + }, + { + "lineNumber": 2198, + "content": "[scan] Queued tatt2away_process_v3 for compression (~0.67GB) [2 total]" + }, + { + "lineNumber": 2204, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 2212, + "content": "[init] Preparing to compress 2 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 2460, + "content": "is the compression lvl 0 working on this? it doesn't seem to be going very fast" + }, + { + "lineNumber": 2486, + "content": "[scan] Queued scab_v2.1 for compression (~1.31GB) [1 total]" + }, + { + "lineNumber": 2496, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 2504, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 2519, + "content": "At compression level 0, the script still uses `-m0=LZMA2`, which compresses. At level 0, we should use `-m0=Copy` (store). Updating the code to use Copy when level is 0:" + }, + { + "lineNumber": 2534, + "content": "<summary>Tool use: **grep** • Grep for \"COMPRESSION_LEVEL\" in \"zip_sequences.py\" • 11 matches</summary>" + }, + { + "lineNumber": 2541, + "content": "| `zip_sequences.py` | `COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)` | L114 |" + }, + { + "lineNumber": 2542, + "content": "| `zip_sequences.py` | `if isinstance(COMPRESSION_LEVEL, str):` | L115 |" + }, + { + "lineNumber": 2543, + "content": "| `zip_sequences.py` | `COMPRESSION_LEVEL = int(COMPRESSION_LEVEL)` | L117 |" + }, + { + "lineNumber": 2544, + "content": "| `zip_sequences.py` | `COMPRESSION_LEVEL = 9` | L119 |" + }, + { + "lineNumber": 2545, + "content": "| `zip_sequences.py` | `if not isinstance(COMPRESSION_LEVEL, int):` | L120 |" + }, + { + "lineNumber": 2546, + "content": "| `zip_sequences.py` | `COMPRESSION_LEVEL = 9` | L121 |" + }, + { + "lineNumber": 2547, + "content": "| `zip_sequences.py` | `COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))` | L122 |" + }, + { + "lineNumber": 2548, + "content": "| `zip_sequences.py` | `f\"-mx=\\{COMPRESSION_LEVEL\\}\",` | L645 |" + }, + { + "lineNumber": 2549, + "content": "| `zip_sequences.py` | `if COMPRESSION_LEVEL <= 0:` | L743 |" + }, + { + "lineNumber": 2550, + "content": "| `zip_sequences.py` | `zip_kwargs = \\{\"compresslevel\": COMPRESSION_LEVEL\\}` | L748 |" + }, + { + "lineNumber": 2551, + "content": "| `zip_sequences.py` | `log(\"init\", f\"7z executable: \\{exe\\}, Max7zInst: \\{max_inst\\}, method: \\{COMPRESSION_METHOD\\}, level: \\{COMPRESSION_LEVEL\\}\")` | L1117 |" + }, + { + "lineNumber": 2582, + "content": "Updating the code to use Copy (store) when compression level is 0:" + }, + { + "lineNumber": 2597, + "content": " # Set compression method and memory/dictionary size based on method" + }, + { + "lineNumber": 2599, + "content": "+ # At compression level 0, use Copy (store) method for maximum speed" + }, + { + "lineNumber": 2601, + "content": "- if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 2604, + "content": "- elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 2608, + "content": "- elif COMPRESSION_METHOD == \"BZip2\":" + }, + { + "lineNumber": 2613, + "content": "- elif COMPRESSION_METHOD == \"Deflate\":" + }, + { + "lineNumber": 2625, + "content": "- if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 2635, + "content": "+ if COMPRESSION_LEVEL == 0:" + }, + { + "lineNumber": 2636, + "content": "+ # Level 0 = no compression, just store files (fastest)" + }, + { + "lineNumber": 2641, + "content": "+ # Compression levels 1-9: use configured compression method" + }, + { + "lineNumber": 2642, + "content": "+ if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 2645, + "content": "+ elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 2649, + "content": "+ elif COMPRESSION_METHOD == \"BZip2\":" + }, + { + "lineNumber": 2654, + "content": "+ elif COMPRESSION_METHOD == \"Deflate\":" + }, + { + "lineNumber": 2666, + "content": "+ if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 2708, + "content": "Fixed: at compression level 0, the script was still using LZMA2, which compresses. At level 0, it now uses `-m0=Copy` (store), which packages files without compression." + }, + { + "lineNumber": 2711, + "content": "- When `compression: 0` is set, the script now uses `-m0=Copy` instead of the configured compression method" + }, + { + "lineNumber": 2712, + "content": "- Copy mode stores files without compression, so it's limited mainly by disk I/O speed" + }, + { + "lineNumber": 2713, + "content": "- Compression levels 1-9 continue to use the configured method (LZMA2, PPMd, etc.)" + }, + { + "lineNumber": 2715, + "content": "When you run `ZipSeqArchv.bat` with `compression: 0`, it should be much faster since it's just copying files into the archive without compression. The 7z command will now show `-m0=Copy` instead of `-m0=LZMA2` when compression level is 0." + }, + { + "lineNumber": 2747, + "content": "[scan] Queued scab_v2.1 for compression (~1.31GB) [1 total]" + }, + { + "lineNumber": 2757, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 2765, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 2817, + "content": "1. Compression level 0 is working: the 7z command shows `-m0=Copy` instead of `-m0=LZMA2`, so it's using Copy (store) with no compression, which should be faster." + }, + { + "lineNumber": 2823, + "content": "Everything looks good. With compression level 0, archiving should be much faster since it's just copying files into the archive without compression." + } + ] + }, + { + "file": ".specstory/history/2025-12-15_17-34Z-updatesequences-bat-script-folder-ignore.md", + "matches": [ + { + "lineNumber": 428, + "content": " \"contents\": \"# ProjectStructure Configuration\\n\\nThe repository reads user preferences from &#96;config.json&#96; in the ProjectStructure\\nroot (copied alongside helper batches when they are deployed to projects).\\n\\n## Keys\\n\\n| Key | Type | Meaning |\\n| --- | --- | --- |\\n| &#96;dailyFormat&#96; | bool | &#96;true&#96; → daily folders named &#96;YYYY-MM-DD&#96;; &#96;false&#96; → &#96;daily_*&#96; style. |\\n| &#96;structDir&#96; | string | Absolute or relative path to the canonical ProjectStructure directory. Blank values default to the folder containing &#96;config.json&#96;. |\\n| &#96;projectsRoot&#96; | string (optional) | Override for the root directory scanned by &#96;UpgradeSeqBatches.ps1&#96;. Defaults to the parent of &#96;structDir&#96;. |\\n| &#96;zipper&#96; | bool | &#96;true&#96; → use 7‑Zip (if available); &#96;false&#96; → use Python’s built-in zipfile module. |\\n| &#96;compression&#96; | int | Compression level &#96;0-9&#96; (0 = store only, 9 = max" + }, + { + "lineNumber": 481, + "content": " \"text\": \"| &#96;compression&#96; | int | Compression level &#96;0-9&#96; (0 = store only, 9 = max). Applies to both zipfile and 7‑Zip. |\"," + }, + { + "lineNumber": 1411, + "content": " \"contents\": \"{\\n \\\"dailyFormat\\\": \\\"daily_YYMMDD\\\",\\n \\\"structDir\\\": \\\"A:\\\\\\\\1 Amazon_Active_Projects\\\\\\\\3 ProjectStructure\\\",\\n \\\"zipper\\\": \\\"7z\\\",\\n \\\"compression\\\": 0,\\n \\\"Max7zInst\\\": 0\\n}\"," + }, + { + "lineNumber": 1431, + "content": " \"text\": \" \\\"compression\\\": 0,\"," + }, + { + "lineNumber": 1455, + "content": " \"contents\": \"@echo off\\nsetlocal EnableExtensions\\n\\nset \\\"SCRIPT_DIR=%~dp0\\\"\\nset \\\"CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1\\\"\\nset \\\"CONFIG_PATH=%SCRIPT_DIR%config.json\\\"\\n\\nif not exist \\\"%CONFIG_LOADER%\\\" (\\n echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.\\n exit /b 1\\n)\\n\\nif not exist \\\"%CONFIG_PATH%\\\" (\\n echo [ERROR] config.json not found next to NewProject.bat.\\n exit /b 1\\n)\\n\\nfor /f \\\"usebackq delims=\\\" %%I in (&#96;powershell -NoProfile -ExecutionPolicy Bypass -Command ^\\n \\\"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)\\\"&#96;) do set \\\"STRUCT_DIR=%%I\\\"\\n\\nif not defined STRUCT_DIR (\\n echo [ERROR] Unable to resolve ProjectStructure directory from config.\\n exit /b 1\\n)\\n\\n:: Get current date in YYYY-MM-DD format\\nfor /f \\\"tokens=2-4 delims=/ \\\" %%a in ('date /t') do (\\n set mm=%%a\\n set dd=%%b\\n set yy=%%c\\n)" + }, + { + "lineNumber": 1630, + "content": " \"text\": \"mkdir \\\"%projectRoot%\\\\Assets\\\\Blends\\\"\"," + }, + { + "lineNumber": 1649, + "content": " \"text\": \":: Create Blends structure\"," + }, + { + "lineNumber": 1653, + "content": " \"text\": \"mkdir \\\"%projectRoot%\\\\Blends\\\\animations\\\\\\\"\"," + }, + { + "lineNumber": 1657, + "content": " \"text\": \"mkdir \\\"%projectRoot%\\\\Blends\\\\stills\\\\img-BG\\\"\"," + }, + { + "lineNumber": 2451, + "content": " \"contents\": \"function Get-ZipCompressionLevel {\\n $value = Get-ConfigValue -Name 'compression' -Default 9\\n if ($value -is [string]) {\\n $parsed = 0\\n if ([int]::TryParse($value, [ref]$parsed)) {\\n $value = $parsed\\n }\\n }\\n\\n if ($value -isnot [int]) {\\n return 9\\n }\\n\\n return [Math]::Min(9, [Math]::Max(0, $value))\\n}\\n\\n# If script is run directly (not dot-sourced), prompt for project path and deploy\\n# When dot-sourced, InvocationName is '.'; when run directly, it's the script path or name\\nif ($MyInvocation.InvocationName -ne '.') {\\n $projectPath = Get-ProjectPathFromUser\\n if ($null -ne $projectPath) {\\n # Deploy batch files and config to the project\\n $structDir = Get-StructDirectory\\n if (-not (Test-Path -LiteralPath $structDir -PathType Container)) {\\n Write-Error \\\"Configured structDir not found: $structDir\\\"\\n exit 1\\n }\\n\\n try {\\n $res" + }, + { + "lineNumber": 2455, + "content": " \"text\": \"function Get-ZipCompressionLevel {\"," + }, + { + "lineNumber": 2459, + "content": " \"text\": \" $value = Get-ConfigValue -Name 'compression' -Default 9\"," + }, + { + "lineNumber": 2958, + "content": " \"contents\": \"# Blender Studio Git Management - .gitignore\\n\\n# Blender auto-save files\\n*.blend1\\n*.blend2\\n*.blend3\\n*.blend4\\n*.blend5\\n*.blend6\\n*.blend7\\n*.blend8\\n*.blend9\\n*.blend10\\n*.blend11\\n*.blend12\\n*.blend13\\n*.blend14\\n*.blend15\\n*.blend16\\n*.blend17\\n*.blend18\\n*.blend19\\n*.blend20\\n*.blend21\\n*.blend22\\n*.blend23\\n*.blend24\\n*.blend25\\n*.blend26\\n*.blend27\\n*.blend28\\n*.blend29\\n*.blend30\\n*.blend31\\n*.blend32\\n\\n# Blender cache and temporary files\\n*.swp\\n*.swo\\n*~\\n*.tmp\\n*.temp\\n\\n# OS generated files\\n.DS_Store\\n.DS_Store?\\n._*\\n.Spotlight-V100\\n.Trashes\\nehthumbs.db\\nThumbs.db\\ndesktop.ini\\n\\n# Editor files\\n.vscode/\\n.idea/\\n*.swp\\n*.swo\\n*~\\n\\n# Large media files (uncomment if you want to exclude rendered outputs)\\n# *.mp4\\n# *.avi\\n# *.mov\\n# *.png\\n# *.jpg\\n# *.jpeg\\n# *.exr\\n# *.hdr\\n# *.tiff\\n# *.tga\\n\\n# Audio files (uncomment if excluding)\\n# *.wav\\n# *.mp3\\n# *.aac\\n# *.ogg\\n\\n# Log files\\n*.log\\n\\n# Python cache\\n__pycache__/\\n*.pyc\\n*.pyo\\n*.pyd\\" + }, + { + "lineNumber": 2962, + "content": " \"text\": \"# Blender Studio Git Management - .gitignore\"," + }, + { + "lineNumber": 2969, + "content": " \"text\": \"# Blender auto-save files\"," + }, + { + "lineNumber": 2973, + "content": " \"text\": \"*.blend1\"," + }, + { + "lineNumber": 2977, + "content": " \"text\": \"*.blend2\"," + }, + { + "lineNumber": 2981, + "content": " \"text\": \"*.blend3\"," + }, + { + "lineNumber": 2985, + "content": " \"text\": \"*.blend4\"," + }, + { + "lineNumber": 2989, + "content": " \"text\": \"*.blend5\"," + }, + { + "lineNumber": 2993, + "content": " \"text\": \"*.blend6\"," + }, + { + "lineNumber": 2997, + "content": " \"text\": \"*.blend7\"," + }, + { + "lineNumber": 3001, + "content": " \"text\": \"*.blend8\"," + }, + { + "lineNumber": 3005, + "content": " \"text\": \"*.blend9\"," + }, + { + "lineNumber": 3009, + "content": " \"text\": \"*.blend10\"," + }, + { + "lineNumber": 3013, + "content": " \"text\": \"*.blend11\"," + }, + { + "lineNumber": 3017, + "content": " \"text\": \"*.blend12\"," + }, + { + "lineNumber": 3021, + "content": " \"text\": \"*.blend13\"," + }, + { + "lineNumber": 3025, + "content": " \"text\": \"*.blend14\"," + }, + { + "lineNumber": 3029, + "content": " \"text\": \"*.blend15\"," + }, + { + "lineNumber": 3033, + "content": " \"text\": \"*.blend16\"," + }, + { + "lineNumber": 3037, + "content": " \"text\": \"*.blend17\"," + }, + { + "lineNumber": 3041, + "content": " \"text\": \"*.blend18\"," + }, + { + "lineNumber": 3045, + "content": " \"text\": \"*.blend19\"," + }, + { + "lineNumber": 3049, + "content": " \"text\": \"*.blend20\"," + }, + { + "lineNumber": 3053, + "content": " \"text\": \"*.blend21\"," + }, + { + "lineNumber": 3057, + "content": " \"text\": \"*.blend22\"," + }, + { + "lineNumber": 3061, + "content": " \"text\": \"*.blend23\"," + }, + { + "lineNumber": 3065, + "content": " \"text\": \"*.blend24\"," + }, + { + "lineNumber": 3069, + "content": " \"text\": \"*.blend25\"," + }, + { + "lineNumber": 3073, + "content": " \"text\": \"*.blend26\"," + }, + { + "lineNumber": 3077, + "content": " \"text\": \"*.blend27\"," + }, + { + "lineNumber": 3081, + "content": " \"text\": \"*.blend28\"," + }, + { + "lineNumber": 3085, + "content": " \"text\": \"*.blend29\"," + }, + { + "lineNumber": 3089, + "content": " \"text\": \"*.blend30\"," + }, + { + "lineNumber": 3093, + "content": " \"text\": \"*.blend31\"," + }, + { + "lineNumber": 3097, + "content": " \"text\": \"*.blend32\"," + }, + { + "lineNumber": 3104, + "content": " \"text\": \"# Blender cache and temporary files\"," + }, + { + "lineNumber": 4261, + "content": " \"contents\": \"#!/usr/bin/env python3\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\n\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\nand stages any updated archives so commits only track compact files. Switch to\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\ndirectories after checkouts or pulls.\\n\\\"\\\"\\\"\\n\\nfrom __future__ import annotations\\n\\nimport argparse\\nimport json\\nimport os\\nimport platform\\nimport shutil\\nimport subprocess\\nimport sys\\nimport tempfile\\nimport time\\nimport traceback\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\nfrom pathlib import Path\\nfrom typing import Iterator, Sequence\\n\\n# Try to import psutil for cross-platform RAM detection\\ntry:\\n import psutil\\n HAS_PSUTIL = True\\nexcept ImportError:\\n HAS_PSUTIL = False\\n # For Windows fallback\\n if platform.system() == \\\"Windows\\\":\\n try:\\n import ctypes\\n " + }, + { + "lineNumber": 4479, + "content": " \"text\": \" \\\"compression\\\": 9,\"," + }, + { + "lineNumber": 4483, + "content": " \"text\": \" \\\"compressionMethod\\\": \\\"LZMA2\\\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate\"," + }, + { + "lineNumber": 4563, + "content": " \"contents\": \"for %%F in (ConfigLoader.ps1 config.json) do (\\n if exist \\\"%scriptDir%%%F\\\" (\\n if \\\"%DRY%\\\"==\\\"1\\\" (\\n echo [DRY] copy \\\"%scriptDir%%%F\\\" \\\"%rendersDir%\\\\%%F\\\"\\n ) else (\\n copy /Y \\\"%scriptDir%%%F\\\" \\\"%rendersDir%\\\\%%F\\\" &gt;nul\\n )\\n ) else (\\n echo [WARN] Missing template: \\\"%scriptDir%%%F\\\"\\n )\\n)\\n\\n:: -----------------------------\\n:: Merge .gitignore and .gitattributes from templates\\n:: -----------------------------\\nset \\\"tplGitIgnore=%scriptDir%components\\\\gitignore\\\"\\nset \\\"dstGitIgnore=%projectDir%\\\\.gitignore\\\"\\nset \\\"tplGitAttr=%scriptDir%components\\\\gitattributes\\\"\\nset \\\"dstGitAttr=%projectDir%\\\\.gitattributes\\\"\\n\\ncall :MergeTemplate \\\"%tplGitIgnore%\\\" \\\"%dstGitIgnore%\\\"\\ncall :MergeTemplate \\\"%tplGitAttr%\\\" \\\"%dstGitAttr%\\\"\\n\\n:: -----------------------------\\n:: Initialize git and Git LFS\\n:: -----------------------------\\nif not exist \\\"%projectDir%\\\\.git\\\" (\\n if \\\"%DRY%\\\"==\\\"1\\\" (\\n echo [DRY] git init\\n ) els" + }, + { + "lineNumber": 4750, + "content": " \"text\": \"set \\\"animDir=%projectDir%\\\\Blends\\\\animations\\\"\"," + }, + { + "lineNumber": 4966, + "content": " \"contents\": \"@echo off\\nsetlocal EnableExtensions EnableDelayedExpansion\\n\\n:: UpgradeToGitProj.bat\\n:: Run this inside an existing (pre-git) project root.\\n:: - Merges .gitignore and .gitattributes from components/ templates\\n:: - Initializes git and installs Git LFS\\n:: - Creates Renders, copies helper scripts\\n:: - Collects seq/ outputs from daily_* folders under Blends\\\\animations (with submodule support)\\n\\n:: -----------------------------\\n:: Arguments\\n:: -----------------------------\\nset \\\"DRY=0\\\"\\nif /I \\\"%~1\\\"==\\\"/dry-run\\\" set \\\"DRY=1\\\"\\nif /I \\\"%~1\\\"==\\\"--dry-run\\\" set \\\"DRY=1\\\"\\nif /I \\\"%~1\\\"==\\\"-n\\\" set \\\"DRY=1\\\"\\nif /I \\\"%~1\\\"==\\\"/n\\\" set \\\"DRY=1\\\"\\n\\nset \\\"projectDir=%CD%\\\"\\nset \\\"scriptDir=%~dp0\\\"\\nset \\\"RUN_ID=%RANDOM%_%RANDOM%\\\"\\nset \\\"DELETE_LIST=%TEMP%\\\\upg_delete_%RUN_ID%.lst\\\"\\n\\nsetlocal EnableDelayedExpansion\\nset /a debugTotal=0\\nset /a debugPlanned=0\\nset /a debugMissing=0\\n\\necho ==============================================\\necho UpgradeToGitProj " + }, + { + "lineNumber": 5001, + "content": " \"text\": \":: - Collects seq/ outputs from daily_* folders under Blends\\\\animations (with submodule support)\"," + } + ] + }, + { + "file": "compress_blend_files.bat", + "matches": [ + { + "lineNumber": 4, + "content": "echo Starting Blender file compression..." + }, + { + "lineNumber": 9, + "content": " echo Usage: Drag .blend files onto this batch file to compress them." + }, + { + "lineNumber": 26, + "content": "REM Create a temporary Python script for compression" + }, + { + "lineNumber": 27, + "content": "set \"TEMP_SCRIPT=compress_blend_temp.py\"" + }, + { + "lineNumber": 35, + "content": "echo # Enable compression globally" + }, + { + "lineNumber": 37, + "content": "echo bpy.context.preferences.filepaths.use_file_compression = True" + }, + { + "lineNumber": 44, + "content": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []" + }, + { + "lineNumber": 46, + "content": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)" + }, + { + "lineNumber": 48, + "content": "echo def is_compressed_file^(path^):" + }, + { + "lineNumber": 60, + "content": "echo for blend_file in blend_files:" + }, + { + "lineNumber": 61, + "content": "echo if blend_file.lower^(^).endswith^(\".blend\"^):" + }, + { + "lineNumber": 64, + "content": "echo filename = os.path.basename^(blend_file^)" + }, + { + "lineNumber": 68, + "content": "echo print^(\"SKIP_EXISTING:\" + blend_file^)" + }, + { + "lineNumber": 70, + "content": "echo # Skip if source is already compressed" + }, + { + "lineNumber": 71, + "content": "echo if is_compressed_file^(blend_file^):" + }, + { + "lineNumber": 72, + "content": "echo print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)" + }, + { + "lineNumber": 74, + "content": "echo print^(\"PROCESSING:\" + blend_file^)" + }, + { + "lineNumber": 75, + "content": "echo # Load the blend file" + }, + { + "lineNumber": 76, + "content": "echo bpy.ops.wm.open_mainfile^(filepath=blend_file^)" + }, + { + "lineNumber": 77, + "content": "echo # Save with compression to output folder" + }, + { + "lineNumber": 78, + "content": "echo bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)" + }, + { + "lineNumber": 79, + "content": "echo print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)" + }, + { + "lineNumber": 81, + "content": "echo print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)" + }, + { + "lineNumber": 83, + "content": "echo print^(\"SKIP_NOT_BLEND:\" + blend_file^)" + }, + { + "lineNumber": 85, + "content": "echo print^(\"COMPRESSION_COMPLETE\"^)" + }, + { + "lineNumber": 86, + "content": "echo bpy.ops.wm.quit_blender^(^)" + }, + { + "lineNumber": 98, + "content": "REM Run Blender to process the dragged files" + }, + { + "lineNumber": 99, + "content": "echo Processing dragged .blend files..." + }, + { + "lineNumber": 101, + "content": "del /f /q \"blender_output.txt\" 2>nul" + }, + { + "lineNumber": 102, + "content": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"" + }, + { + "lineNumber": 111, + "content": "echo COMPRESSION REPORT" + }, + { + "lineNumber": 116, + "content": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (" + }, + { + "lineNumber": 120, + "content": " set \"compressed_file=%%c\"" + }, + { + "lineNumber": 124, + "content": " for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"" + }, + { + "lineNumber": 128, + "content": " echo Compressed: !compressed_size! bytes" + }, + { + "lineNumber": 129, + "content": " set /a \"savings=!original_size! - !compressed_size!\"" + }, + { + "lineNumber": 134, + "content": " del \"!compressed_file!\" 2>nul" + }, + { + "lineNumber": 148, + "content": " ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (" + }, + { + "lineNumber": 150, + "content": " echo [!skipped!] SKIPPED ^(not .blend^): %%b" + }, + { + "lineNumber": 152, + "content": " ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (" + }, + { + "lineNumber": 154, + "content": " echo [!skipped!] SKIPPED ^(already compressed^): %%b" + }, + { + "lineNumber": 160, + "content": "del /f /q \"blender_output.txt\" 2>nul" + } + ] + }, + { + "file": ".specstory/history/2025-11-17_19-23Z-sort-and-comment-gitattributes-file.md", + "matches": [ + { + "lineNumber": 78, + "content": " *.blend filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 86, + "content": "+ *.blend1 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 87, + "content": "+ *.blend2 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 94, + "content": "- *.blend1 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 102, + "content": "- *.blend2 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 116, + "content": "+ # Archive/Compression files" + }, + { + "lineNumber": 217, + "content": " - Archive/Compression files" + } + ] + }, + { + "file": "components/gitattributes", + "matches": [ + { + "lineNumber": 28, + "content": "*.blend filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 29, + "content": "*.blend1 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 30, + "content": "*.blend2 filter=lfs diff=lfs merge=lfs -text" + }, + { + "lineNumber": 69, + "content": "# Archive/Compression files" + } + ] + }, + { + "file": "config.json", + "matches": [ + { + "lineNumber": 5, + "content": " \"compression\": 0," + } + ] + }, + { + "file": ".specstory/history/2025-11-12_01-02Z-compression-failure-and-memory-allocation-issues.md", + "matches": [ + { + "lineNumber": 5, + "content": "# Compression failure and memory allocation issues (2025-11-12 01:02Z)" + }, + { + "lineNumber": 11, + "content": "why did it fail? also, I don't want a setting of 0 to cause 32 instances @ 3gb. It should account for the number of sequences to zip and the size of each sequence to determine the number of instances and how much RAM to allocate each, given how many can feasibly compress alongside each other." + }, + { + "lineNumber": 17, + "content": "[zip] RAM: 76.7GB available (80% of total), 72.9GB for compression (95%), 3.0GB per job (dict: 1024MB) → RAM limit: 22, work items: 22, requested: None, final: 22" + }, + { + "lineNumber": 63, + "content": "Sequence sync failed: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 115, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 117, + "content": "RuntimeError: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 220, + "content": "1. **Why it failed**: The code uses a fixed 3GB per job estimate and doesn't account for actual sequence sizes. A 3.6GB sequence with a 1GB dictionary needs more RAM (dictionary + input buffers + compression overhead ≈ 5–6GB+), but the code allocated only 3GB." + }, + { + "lineNumber": 227, + "content": " - Keep dictionary at 1GB (for best compression)" + }, + { + "lineNumber": 275, + "content": " <summary>Tool use: **web_search** • Searched web `7z compression memory usage RAM allocation large files dictionary size` • **1** results</summary>" + }, + { + "lineNumber": 279, + "content": "| Web Search Results | | The failure occurred because the compression process attempted to allocate more memory than was available, leading to the error: \"7z compression failed: ERROR: Can't allocate required memory!\" This issue arises from the current logic, which uses a fixed 3GB per job estimate without considering the actual size of each sequence.<br/><br/>**Understanding Memory Requirements:**<br/><br/>7-Zip's LZMA compression algorithm requires memory based on several factors:<br/><br/>1. **Dictionary Size:** This is a key parameter in LZMA compression. Larger dictionary sizes can improve compression ratios but also increase memory usage. For instance, a 64MB dictionary size requires approximately 709MB of RAM for compression. ([documentation.help](https://documentation.help/7-Zip/documentation.pdf?utm_source=openai))<br/><br/>2. **Input Buffer:** The size of the data being compressed also impacts memory usage. Compressing a 25GB sequence doesn't necessarily require 25GB of RAM," + }, + { + "lineNumber": 305, + "content": " \"plan\": \"# Implement Size-Aware RAM Estimation and Bin-Packing for 7z Workers\\n\\n## Overview\\nReplace the fixed 3GB per-job RAM estimate with dynamic estimation based on actual sequence sizes. Use bin-packing (largest-first) to determine optimal concurrent workers when &#96;Max7zInst&#96; is 0.\\n\\n## Problem Analysis\\n\\n**Current Issues:**\\n1. Fixed 3GB per-job estimate doesn't account for actual sequence sizes\\n2. A 3.6GB sequence with 1GB dictionary needs ~12-13GB RAM (not 3GB)\\n3. 7z dictionary overhead: ~11x dictionary size (1GB dict ≈ 11GB RAM for dictionary operations)\\n4. No consideration of sequence sizes when calculating workers\\n\\n**Failure Case:**\\n- 22 workers × 3GB = 66GB allocated\\n- But a 3.6GB sequence actually needs ~12GB RAM\\n- Result: \\\"Can't allocate required memory!\\\" error\\n\\n## Implementation Plan\\n\\n### 1. Fix &#96;estimate_ram_per_job()&#96; function\\n**File:** &#96;zip_sequences.py&#96; (lines 186-220)\\n\\nUpdate the RAM estimation formula for 7z:\\n- **Dic" + }, + { + "lineNumber": 307, + "content": " \"overview\": \"Replace the fixed 3GB per-job estimate with size-aware RAM estimation that accounts for dictionary overhead (~11x dictionary size), input buffers, and compression overhead. Implement bin-packing algorithm (largest-first) to determine optimal concurrent workers when Max7zInst is 0.\"" + }, + { + "lineNumber": 430, + "content": "- # Compression factor: 7z can use significant RAM, especially for large files" + }, + { + "lineNumber": 432, + "content": "- compression_factor = 0.15" + }, + { + "lineNumber": 434, + "content": "+ # This is the main memory consumer for 7z LZMA compression" + }, + { + "lineNumber": 447, + "content": "- estimated_ram = max(base_ram, int(total_bytes * compression_factor))" + }, + { + "lineNumber": 449, + "content": "+ # Compression overhead: additional memory for compression process" + }, + { + "lineNumber": 507, + "content": "- # Use 95% of available RAM for compression jobs (aggressive mode)" + }, + { + "lineNumber": 508, + "content": "+ # Use 95% of available RAM for compression jobs" + }, + { + "lineNumber": 509, + "content": " compression_ram = int(available_ram * 0.95)" + }, + { + "lineNumber": 512, + "content": "- ram_limit = max(1, compression_ram // FIXED_RAM_PER_JOB)" + }, + { + "lineNumber": 548, + "content": "+ bin_remaining.append(compression_ram - estimated_ram)" + }, + { + "lineNumber": 568, + "content": " compression_ram_gb = compression_ram / (1024 ** 3)" + }, + { + "lineNumber": 573, + "content": "- f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (95%), {ram_per_job_gb:.1f}GB per job (dict: {FIXED_DICT_SIZE_MB}MB) → \"" + }, + { + "lineNumber": 575, + "content": "+ f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (95%)\"," + }, + { + "lineNumber": 696, + "content": " # For zip compression, use existing estimation-based approach" + }, + { + "lineNumber": 805, + "content": "+ # We keep dictionary at 1GB for best compression regardless of RAM estimate" + }, + { + "lineNumber": 837, + "content": "Checking the zip compression path in `max_workers()` and updating it to return a dict:" + }, + { + "lineNumber": 855, + "content": "Updating the zip compression path to return a dict:" + }, + { + "lineNumber": 1213, + "content": "- Updated zip compression path, Max7zInst path, and error paths" + }, + { + "lineNumber": 1219, + "content": "- Keeps dictionary at 1GB for best compression" + }, + { + "lineNumber": 1386, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1441, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1505, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1561, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1659, + "content": " \"diffString\": \" \\r\\n work_items.append((seq_dir, zip_path, state_path, seq_state))\\r\\n+ queued += 1\\r\\n+ if queued &lt;= 5 or queued % 5 == 0:\\r\\n+ total_bytes = sum(entry.get(\\\"size\\\", 0) for entry in seq_state.get(\\\"files\\\", []))\\r\\n+ size_gb = total_bytes / (1024 ** 3)\\r\\n+ log(\\\"scan\\\", f\\\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\\\")\\r\\n \\r\\n if not work_items:\\r\"," + }, + { + "lineNumber": 1667, + "content": " \"diffString\": \" else:\\r\\n log(\\\"zip\\\", \\\"Archives already up to date; no sequences needed zipping.\\\")\\r\\n+ log(\\r\\n+ \\\"scan\\\",\\r\\n+ f\\\"Summary: scanned {total_scanned}, quick-skipped {quick_skipped}, \\\"\\r\\n+ f\\\"state-skipped {state_skipped}, empty {empty_dirs}, queued {queued}\\\",\\r\\n+ )\\r\\n return 0\\r\\n \\r\\n # Calculate RAM-aware worker count based on work items\\r\\n worker_count, ram_limits_dict = max_workers(requested_workers, work_items, verbose=verbose)\\r\\n+ log(\\r\\n+ \\\"init\\\",\\r\\n+ f\\\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\\\",\\r\\n+ )\\r\\n \\r\\n updated_paths: list[Path] = []\\r\"," + }, + { + "lineNumber": 1679, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1765, + "content": " \"contentsAfterEdit\": \"#!/usr/bin/env python3\\r\\n\\\"\\\"\\\"Maintain zipped render sequences for Git hooks.\\r\\n\\r\\nDefault mode scans &#96;Renders/&#96;, produces ZIP archives under &#96;Renders/_zipped/&#96;,\\r\\nand stages any updated archives so commits only track compact files. Switch to\\r\\n&#96;--mode expand&#96; to inflate the tracked archives back into the ignored working\\r\\ndirectories after checkouts or pulls.\\r\\n\\\"\\\"\\\"\\r\\n\\r\\nfrom __future__ import annotations\\r\\n\\r\\nimport argparse\\r\\nimport json\\r\\nimport os\\r\\nimport platform\\r\\nimport shutil\\r\\nimport subprocess\\r\\nimport sys\\r\\nimport tempfile\\r\\nimport time\\r\\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\\r\\nfrom pathlib import Path\\r\\nfrom typing import Iterator, Sequence\\r\\n\\r\\n# Try to import psutil for cross-platform RAM detection\\r\\ntry:\\r\\n import psutil\\r\\n HAS_PSUTIL = True\\r\\nexcept ImportError:\\r\\n HAS_PSUTIL = False\\r\\n # For Windows fallback\\r\\n if platform.system() == \\\"Windows\\\"" + }, + { + "lineNumber": 1811, + "content": " \"compression\": 9," + }, + { + "lineNumber": 1843, + "content": " log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")" + }, + { + "lineNumber": 1852, + "content": "- Logged compression setup details (worker count, job totals) and ensured the main entry point always prints an initial banner so the window never sits blank before heavy work begins." + }, + { + "lineNumber": 1889, + "content": "[scan] Queued 3_background\\2025-10-26\\3_background_dark for compression (~667.98GB) [1 total]" + }, + { + "lineNumber": 1890, + "content": "[scan] Queued 3_background\\2025-10-30\\3_background_dark for compression (~527.10GB) [2 total]" + }, + { + "lineNumber": 1892, + "content": "[scan] Queued 4_StartupScreen\\2025-02-05\\4_StartupScreen_evensrenumbered for compression (~20.66GB) [3 total]" + }, + { + "lineNumber": 1894, + "content": "[scan] Queued 4_StartupScreen\\2025-02-08\\Shot2Redo_1024 for compression (~8.83GB) [4 total]" + }, + { + "lineNumber": 1895, + "content": "[scan] Queued 4_StartupScreen\\2025-02-08\\Shot3Redo_128 for compression (~8.44GB) [5 total]" + }, + { + "lineNumber": 1896, + "content": "[scan] Queued 4_StartupScreen\\2025-10-21\\Shot4 for compression (~2.36GB) [10 total]" + }, + { + "lineNumber": 1898, + "content": "[scan] Queued 4_StartupScreen\\2025-11-02\\Shot1_dark for compression (~4.48GB) [15 total]" + }, + { + "lineNumber": 1901, + "content": "[zip] RAM: 76.7GB available (80% of total), 72.9GB for compression (95%)" + }, + { + "lineNumber": 1905, + "content": "[init] Preparing to compress 19 sequence(s) with 4 worker(s)" + }, + { + "lineNumber": 1925, + "content": "Sequence sync failed: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 1954, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 1955, + "content": "RuntimeError: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 2011, + "content": " # Compression overhead: additional memory for compression process" + }, + { + "lineNumber": 2114, + "content": "- # Compression overhead: additional memory for compression process" + }, + { + "lineNumber": 2193, + "content": "If you move those sequences into `_archive` folders, they'll be skipped automatically. The RAM estimation updates will still help if you do need to compress very large sequences in the future." + }, + { + "lineNumber": 2211, + "content": "[scan] Queued 0_GaslightRender\\2024-11-23\\GaslightRender_smol for compression (~0.19GB) [1 total]" + }, + { + "lineNumber": 2213, + "content": "[scan] Queued 1_PonderingOrb\\2021-01-10\\rendertest for compression (~2.33GB) [2 total]" + }, + { + "lineNumber": 2215, + "content": "[scan] Queued 1_PonderingOrb\\2024-11-23\\PonderingOrb for compression (~19.76GB) [3 total]" + }, + { + "lineNumber": 2217, + "content": "[scan] Queued 3_background\\2025-01-13\\background for compression (~16.73GB) [4 total]" + }, + { + "lineNumber": 2219, + "content": "[scan] Queued 3_background\\2025-10-30\\3_background_dark_4k for compression (~27.57GB) [5 total]" + }, + { + "lineNumber": 2221, + "content": "[scan] Queued 4_StartupScreen\\2025-02-08\\Shot3Redo_128 for compression (~8.44GB) [10 total]" + }, + { + "lineNumber": 2222, + "content": "[scan] Queued 4_StartupScreen\\2025-02-14\\Shot8 for compression (~0.65GB) [15 total]" + }, + { + "lineNumber": 2224, + "content": "[scan] Queued 4_StartupScreen\\2025-10-24\\Shot2_dark for compression (~2.36GB) [20 total]" + }, + { + "lineNumber": 2225, + "content": "[scan] Queued 4_StartupScreen\\2025-11-03\\Shot1_dark for compression (~0.78GB) [25 total]" + }, + { + "lineNumber": 2227, + "content": "[scan] Queued 4_StartupScreen\\2025-11-07\\Shot2_dark for compression (~2.37GB) [30 total]" + }, + { + "lineNumber": 2228, + "content": "[scan] Queued 5_goodbye\\2025-02-23\\219_goodbye_RR2_evensrenamed for compression (~0.16GB) [35 total]" + }, + { + "lineNumber": 2229, + "content": "[zip] RAM: 76.7GB available (80% of total), 72.9GB for compression (95%)" + }, + { + "lineNumber": 2233, + "content": "[init] Preparing to compress 38 sequence(s) with 8 worker(s)" + }, + { + "lineNumber": 2273, + "content": "Sequence sync failed: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 2302, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 2303, + "content": "RuntimeError: 7z compression failed: ERROR: Can't allocate required memory!" + }, + { + "lineNumber": 2372, + "content": "[scan] Queued 1_PonderingOrb\\2024-11-23\\PonderingOrb for compression (~19.76GB) [1 total]" + }, + { + "lineNumber": 2380, + "content": "[scan] Queued 3_background\\2025-10-30\\3_background_dark_4k for compression (~27.57GB) [2 total]" + }, + { + "lineNumber": 2382, + "content": "[scan] Queued 3_background\\2025-11-02\\3_background_dark for compression (~7.40GB) [3 total]" + }, + { + "lineNumber": 2384, + "content": "[scan] Queued 4_StartupScreen\\2025-02-05\\4_StartupScreen_evensrenumbered for compression (~20.66GB) [4 total]" + }, + { + "lineNumber": 2386, + "content": "[scan] Queued 4_StartupScreen\\2025-02-08\\Shot1Redo_1024 for compression (~9.26GB) [5 total]" + }, + { + "lineNumber": 2390, + "content": "[scan] Queued 4_StartupScreen\\2025-02-12\\Shot3 for compression (~4.04GB) [10 total]" + }, + { + "lineNumber": 2398, + "content": "[scan] Queued 4_StartupScreen\\2025-10-24\\Shot2_dark for compression (~2.36GB) [15 total]" + }, + { + "lineNumber": 2400, + "content": "[scan] Queued 4_StartupScreen\\2025-11-03\\Shot1_dark for compression (~0.78GB) [20 total]" + }, + { + "lineNumber": 2404, + "content": "[scan] Queued 4_StartupScreen\\2025-11-07\\Shot2_dark for compression (~2.37GB) [25 total]" + }, + { + "lineNumber": 2406, + "content": "[scan] Queued 5_goodbye\\2025-02-23\\goodbye for compression (~49.80GB) [30 total]" + }, + { + "lineNumber": 2408, + "content": "[zip] RAM: 76.7GB available (80% of total), 72.9GB for compression (95%)" + }, + { + "lineNumber": 2416, + "content": "[init] Preparing to compress 31 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 2561, + "content": "- All 31 sequences compressed (1/31 through 31/31)" + }, + { + "lineNumber": 2570, + "content": "The compression is working correctly with the new size-aware RAM estimation and bin-packing algorithm." + } + ] + }, + { + "file": "ConfigLoader.ps1", + "matches": [ + { + "lineNumber": 173, + "content": "function Get-ZipCompressionLevel {" + }, + { + "lineNumber": 174, + "content": " $value = Get-ConfigValue -Name 'compression' -Default 9" + } + ] + }, + { + "file": "replace_cel_with_bsdf.py", + "matches": [ + { + "lineNumber": 8, + "content": " library_path = r\"R:\\Creative\\artsy\\maya\\0 ProjectStructure\\1 BlenderAssets\\Amazon\\MATERIALS_BSDF_pallette_v1.0.blend\"" + }, + { + "lineNumber": 49, + "content": " base_name = mat.name.split(\".blend\")[0] if \".blend\" in mat.name else mat.name" + }, + { + "lineNumber": 84, + "content": " base_name = mat.name.split(\".blend\")[0] if \".blend\" in mat.name else mat.name" + }, + { + "lineNumber": 130, + "content": " base_name = missing_mat.name.split(\".blend\")[0] if \".blend\" in missing_mat.name else missing_mat.name" + }, + { + "lineNumber": 190, + "content": " \"\"\"Replace all CEL materials with their BSDF counterparts using Blender's user remapping\"\"\"" + }, + { + "lineNumber": 245, + "content": " base_name = name.split(\".blend\")[0] if \".blend\" in name else name" + }, + { + "lineNumber": 309, + "content": " # Use Blender's built-in user remapping to replace ALL users" + } + ] + }, + { + "file": "remove_action_fake_users.bat", + "matches": [ + { + "lineNumber": 9, + "content": "\techo Usage: Drag .blend files onto this batch file to remove fake users from actions." + }, + { + "lineNumber": 41, + "content": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []" + }, + { + "lineNumber": 43, + "content": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to process\"^)" + }, + { + "lineNumber": 45, + "content": "echo for blend_file in blend_files:" + }, + { + "lineNumber": 46, + "content": "echo ^\tif blend_file.lower^(^).endswith^(\".blend\"^):" + }, + { + "lineNumber": 49, + "content": "echo ^\t\t\tfilename = os.path.basename^(blend_file^)" + }, + { + "lineNumber": 53, + "content": "echo ^\t\t\t\tprint^(\"SKIP_EXISTING:\" + blend_file^)" + }, + { + "lineNumber": 55, + "content": "echo ^\t\t\tprint^(\"PROCESSING:\" + blend_file^)" + }, + { + "lineNumber": 56, + "content": "echo ^\t\t\t# Load the blend file" + }, + { + "lineNumber": 57, + "content": "echo ^\t\t\tbpy.ops.wm.open_mainfile^(filepath=blend_file^)" + }, + { + "lineNumber": 64, + "content": "echo ^\t\t\tprint^(\"UPDATED:\" + blend_file + \":\" + str^(changed^)^)" + }, + { + "lineNumber": 66, + "content": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)" + }, + { + "lineNumber": 67, + "content": "echo ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)" + }, + { + "lineNumber": 69, + "content": "echo ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)" + }, + { + "lineNumber": 71, + "content": "echo ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)" + }, + { + "lineNumber": 74, + "content": "echo bpy.ops.wm.quit_blender^(^)" + }, + { + "lineNumber": 86, + "content": "REM Run Blender to process the dragged files" + }, + { + "lineNumber": 87, + "content": "echo Processing dragged .blend files..." + }, + { + "lineNumber": 89, + "content": "del /f /q \"blender_output.txt\" 2>nul" + }, + { + "lineNumber": 90, + "content": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"" + }, + { + "lineNumber": 104, + "content": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (" + }, + { + "lineNumber": 111, + "content": "\t\tfor %%f in (\"%%c\") do set \"compressed_size=%%~zf\"" + }, + { + "lineNumber": 112, + "content": "\t\tset /a savings=original_size-compressed_size" + }, + { + "lineNumber": 115, + "content": "\t\techo Output: !compressed_size! bytes" + }, + { + "lineNumber": 132, + "content": "\t) else if \"%%a\"==\"SKIP_NOT_BLEND\" (" + }, + { + "lineNumber": 134, + "content": "\t\techo [!skipped!] SKIPPED ^(not .blend^): %%b" + }, + { + "lineNumber": 145, + "content": "del /f /q \"blender_output.txt\" 2>nul" + } + ] + }, + { + "file": ".specstory/history/2025-11-26_20-08Z-configure-configloader-for-zipseq-and-unzipseq.md", + "matches": [ + { + "lineNumber": 257, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 9 for compression (~0.33GB) [1 total]" + }, + { + "lineNumber": 261, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4A_2 for compression (~0.20GB) [2 total]" + }, + { + "lineNumber": 263, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4B for compression (~0.23GB) [3 total]" + }, + { + "lineNumber": 265, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5 for compression (~1.62GB) [4 total]" + }, + { + "lineNumber": 269, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 6 for compression (~1.42GB) [5 total]" + }, + { + "lineNumber": 271, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_2_broom for compression (~1.59GB) [10 total]" + }, + { + "lineNumber": 275, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_5 for compression (~1.34GB) [15 total]" + }, + { + "lineNumber": 279, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 287, + "content": "[init] Preparing to compress 18 sequence(s) with 18 worker(s)" + }, + { + "lineNumber": 587, + "content": " with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:" + }, + { + "lineNumber": 645, + "content": "**The fix:** Both the 7z and zip compression paths now sort files using the same key as `compute_state`: `key=lambda p: p.relative_to(seq_dir).as_posix()`. This ensures:" + }, + { + "lineNumber": 704, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking for compression (~1.03GB) [1 total]" + }, + { + "lineNumber": 706, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking_Pt2 for compression (~1.07GB) [2 total]" + }, + { + "lineNumber": 710, + "content": "[scan] Queued 04 Securing Your Seam\\04_holding nails for compression (~1.14GB) [3 total]" + }, + { + "lineNumber": 712, + "content": "[scan] Queued 04 Securing Your Seam\\09_ending for compression (~0.91GB) [4 total]" + }, + { + "lineNumber": 716, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [5 total]" + }, + { + "lineNumber": 718, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.59GB) [10 total]" + }, + { + "lineNumber": 722, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.14GB) [15 total]" + }, + { + "lineNumber": 724, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 8 insert for compression (~0.66GB) [20 total]" + }, + { + "lineNumber": 728, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 2B for compression (~1.32GB) [25 total]" + }, + { + "lineNumber": 730, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4A for compression (~1.17GB) [30 total]" + }, + { + "lineNumber": 734, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5B_insert for compression (~0.14GB) [35 total]" + }, + { + "lineNumber": 736, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_1_respray for compression (~0.73GB) [40 total]" + }, + { + "lineNumber": 740, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_3_PE_spread for compression (~1.04GB) [45 total]" + }, + { + "lineNumber": 742, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_7 for compression (~1.25GB) [50 total]" + }, + { + "lineNumber": 746, + "content": "[scan] Queued blooper_reel\\ft_cutting_turf_shot_1_face for compression (~0.29GB) [55 total]" + }, + { + "lineNumber": 748, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 756, + "content": "[init] Preparing to compress 59 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 971, + "content": "| `zip_sequences.py` | `\"7z compression requested but 7z executable not found in PATH. \"` | L593 |" + }, + { + "lineNumber": 978, + "content": "| `zip_sequences.py` | `log(\"init\", f\"7z executable: \\{exe\\}, Max7zInst: \\{max_inst\\}, method: \\{COMPRESSION_METHOD\\}, level: \\{COMPRESSION_LEVEL\\}\")` | L1131 |" + }, + { + "lineNumber": 1630, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking for compression (~1.03GB) [1 total]" + }, + { + "lineNumber": 1632, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking_Pt2 for compression (~1.07GB) [2 total]" + }, + { + "lineNumber": 1636, + "content": "[scan] Queued 04 Securing Your Seam\\04_holding nails for compression (~1.14GB) [3 total]" + }, + { + "lineNumber": 1638, + "content": "[scan] Queued 04 Securing Your Seam\\09_ending for compression (~0.91GB) [4 total]" + }, + { + "lineNumber": 1642, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [5 total]" + }, + { + "lineNumber": 1644, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.59GB) [10 total]" + }, + { + "lineNumber": 1648, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.14GB) [15 total]" + }, + { + "lineNumber": 1650, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 8 insert for compression (~0.66GB) [20 total]" + }, + { + "lineNumber": 1654, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 2B for compression (~1.32GB) [25 total]" + }, + { + "lineNumber": 1656, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4A for compression (~1.17GB) [30 total]" + }, + { + "lineNumber": 1660, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5B_insert for compression (~0.14GB) [35 total]" + }, + { + "lineNumber": 1662, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_1_respray for compression (~0.73GB) [40 total]" + }, + { + "lineNumber": 1666, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_3_PE_spread for compression (~1.04GB) [45 total]" + }, + { + "lineNumber": 1668, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_7 for compression (~1.25GB) [50 total]" + }, + { + "lineNumber": 1672, + "content": "[scan] Queued blooper_reel\\ft_cutting_turf_shot_1_face for compression (~0.29GB) [55 total]" + }, + { + "lineNumber": 1674, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 1682, + "content": "[init] Preparing to compress 59 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 2060, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking for compression (~1.03GB) [1 total]" + }, + { + "lineNumber": 2062, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking_Pt2 for compression (~1.07GB) [2 total]" + }, + { + "lineNumber": 2066, + "content": "[scan] Queued 04 Securing Your Seam\\04_holding nails for compression (~1.14GB) [3 total]" + }, + { + "lineNumber": 2068, + "content": "[scan] Queued 04 Securing Your Seam\\09_ending for compression (~0.91GB) [4 total]" + }, + { + "lineNumber": 2072, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [5 total]" + }, + { + "lineNumber": 2074, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.59GB) [10 total]" + }, + { + "lineNumber": 2078, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.14GB) [15 total]" + }, + { + "lineNumber": 2080, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 8 insert for compression (~0.66GB) [20 total]" + }, + { + "lineNumber": 2084, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 2B for compression (~1.32GB) [25 total]" + }, + { + "lineNumber": 2086, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4A for compression (~1.17GB) [30 total]" + }, + { + "lineNumber": 2090, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5B_insert for compression (~0.14GB) [35 total]" + }, + { + "lineNumber": 2092, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_1_respray for compression (~0.73GB) [40 total]" + }, + { + "lineNumber": 2096, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_3_PE_spread for compression (~1.04GB) [45 total]" + }, + { + "lineNumber": 2098, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_7 for compression (~1.25GB) [50 total]" + }, + { + "lineNumber": 2102, + "content": "[scan] Queued blooper_reel\\ft_cutting_turf_shot_1_face for compression (~0.29GB) [55 total]" + }, + { + "lineNumber": 2104, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 2112, + "content": "[init] Preparing to compress 59 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 2901, + "content": "[expand] Renders\\_zipped\\04 Securing Your Seam\\08_smooth blend.zip -> Renders\\04 Securing Your Seam\\08_smooth blend" + }, + { + "lineNumber": 2935, + "content": "[expand] 45/105 04 Securing Your Seam\\08_smooth blend.zip" + }, + { + "lineNumber": 3213, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking for compression (~1.03GB) [1 total]" + }, + { + "lineNumber": 3215, + "content": "[scan] Queued 04 Securing Your Seam\\02_FT talking_Pt2 for compression (~1.07GB) [2 total]" + }, + { + "lineNumber": 3219, + "content": "[scan] Queued 04 Securing Your Seam\\04_holding nails for compression (~1.14GB) [3 total]" + }, + { + "lineNumber": 3221, + "content": "[scan] Queued 04 Securing Your Seam\\09_ending for compression (~0.91GB) [4 total]" + }, + { + "lineNumber": 3225, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [5 total]" + }, + { + "lineNumber": 3227, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.59GB) [10 total]" + }, + { + "lineNumber": 3231, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.14GB) [15 total]" + }, + { + "lineNumber": 3233, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 8 insert for compression (~0.66GB) [20 total]" + }, + { + "lineNumber": 3237, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 2B for compression (~1.32GB) [25 total]" + }, + { + "lineNumber": 3239, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 4A for compression (~1.17GB) [30 total]" + }, + { + "lineNumber": 3243, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5B_insert for compression (~0.14GB) [35 total]" + }, + { + "lineNumber": 3245, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_1_respray for compression (~0.73GB) [40 total]" + }, + { + "lineNumber": 3249, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_3_PE_spread for compression (~1.04GB) [45 total]" + }, + { + "lineNumber": 3251, + "content": "[scan] Queued 07 Final Touches And Maintenance\\Visual_7 for compression (~1.25GB) [50 total]" + }, + { + "lineNumber": 3255, + "content": "[scan] Queued blooper_reel\\ft_cutting_turf_shot_1_face for compression (~0.29GB) [55 total]" + }, + { + "lineNumber": 3257, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 3265, + "content": "[init] Preparing to compress 59 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 3933, + "content": " - If changed → fall through to queue for compression" + }, + { + "lineNumber": 4117, + "content": "[expand] Renders\\_zipped\\04 Securing Your Seam\\08_smooth blend.zip -> Renders\\04 Securing Your Seam\\08_smooth blend" + }, + { + "lineNumber": 4151, + "content": "[expand] 45/105 04 Securing Your Seam\\08_smooth blend.zip" + }, + { + "lineNumber": 4515, + "content": "[scan] Info: 04 Securing Your Seam\\08_smooth blend no archive or state file (will create new)" + }, + { + "lineNumber": 4625, + "content": "[zip] Archive 04 Securing Your Seam\\08_smooth blend.zip has matching sequence directory; keeping" + } + ] + }, + { + "file": "CONFIG.md", + "matches": [ + { + "lineNumber": 14, + "content": "| `compression` | int | Compression level `0-9` (0 = store only, 9 = max). Applies to both zipfile and 7‑Zip. |" + } + ] + }, + { + "file": "components/gitignore", + "matches": [ + { + "lineNumber": 1, + "content": "# Blender Studio Git Management - .gitignore" + }, + { + "lineNumber": 3, + "content": "# Blender auto-save files" + }, + { + "lineNumber": 4, + "content": "*.blend1" + }, + { + "lineNumber": 5, + "content": "*.blend2" + }, + { + "lineNumber": 6, + "content": "*.blend3" + }, + { + "lineNumber": 7, + "content": "*.blend4" + }, + { + "lineNumber": 8, + "content": "*.blend5" + }, + { + "lineNumber": 9, + "content": "*.blend6" + }, + { + "lineNumber": 10, + "content": "*.blend7" + }, + { + "lineNumber": 11, + "content": "*.blend8" + }, + { + "lineNumber": 12, + "content": "*.blend9" + }, + { + "lineNumber": 13, + "content": "*.blend10" + }, + { + "lineNumber": 14, + "content": "*.blend11" + }, + { + "lineNumber": 15, + "content": "*.blend12" + }, + { + "lineNumber": 16, + "content": "*.blend13" + }, + { + "lineNumber": 17, + "content": "*.blend14" + }, + { + "lineNumber": 18, + "content": "*.blend15" + }, + { + "lineNumber": 19, + "content": "*.blend16" + }, + { + "lineNumber": 20, + "content": "*.blend17" + }, + { + "lineNumber": 21, + "content": "*.blend18" + }, + { + "lineNumber": 22, + "content": "*.blend19" + }, + { + "lineNumber": 23, + "content": "*.blend20" + }, + { + "lineNumber": 24, + "content": "*.blend21" + }, + { + "lineNumber": 25, + "content": "*.blend22" + }, + { + "lineNumber": 26, + "content": "*.blend23" + }, + { + "lineNumber": 27, + "content": "*.blend24" + }, + { + "lineNumber": 28, + "content": "*.blend25" + }, + { + "lineNumber": 29, + "content": "*.blend26" + }, + { + "lineNumber": 30, + "content": "*.blend27" + }, + { + "lineNumber": 31, + "content": "*.blend28" + }, + { + "lineNumber": 32, + "content": "*.blend29" + }, + { + "lineNumber": 33, + "content": "*.blend30" + }, + { + "lineNumber": 34, + "content": "*.blend31" + }, + { + "lineNumber": 35, + "content": "*.blend32" + }, + { + "lineNumber": 37, + "content": "# Blender cache and temporary files" + } + ] + }, + { + "file": "UpgradeToGitProj.bat", + "matches": [ + { + "lineNumber": 9, + "content": ":: - Collects seq/ outputs from daily_* folders under Blends\\animations (with submodule support)" + }, + { + "lineNumber": 106, + "content": "set \"animDir=%projectDir%\\Blends\\animations\"" + }, + { + "lineNumber": 127, + "content": " if \"%DRY%\"==\"1\" echo [DRY] Using submodules under Blends\\animations" + } + ] + }, + { + "file": ".specstory/history/2025-11-10_16-59Z-change-compression-method-to-7z.md", + "matches": [ + { + "lineNumber": 5, + "content": "# Change compression method to 7z (2025-11-10 16:59Z)" + }, + { + "lineNumber": 9, + "content": "@zip_sequences.py (1-334) let's have it use 7z at compression lvl 9 instead of zip at 0 (store) I don't mind it taking a few minutes per zip if they're more compressed." + }, + { + "lineNumber": 20, + "content": "Switching to 7z with compression level 9. Updating the code to use 7z instead of Python's zipfile." + }, + { + "lineNumber": 38, + "content": "Updating the code to use 7z with compression level 9 instead of Python's zipfile with no compression." + }, + { + "lineNumber": 102, + "content": "- with ZipFile(zip_path, \"w\", compression=ZIP_STORED) as archive:" + }, + { + "lineNumber": 112, + "content": "+ # Use 7z with maximum compression (level 9)" + }, + { + "lineNumber": 113, + "content": "+ # -mx=9: maximum compression" + }, + { + "lineNumber": 134, + "content": "+ raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 251, + "content": "+ \"\"\"Maintain compressed render sequences for Git hooks using 7z." + }, + { + "lineNumber": 257, + "content": "+ Default mode scans &#96;Renders/&#96;, produces 7z archives (compression level 9) under" + }, + { + "lineNumber": 293, + "content": "Updated the script to use 7z with compression level 9 instead of Python's zipfile with no compression. Changes:" + }, + { + "lineNumber": 298, + "content": " - `-mx=9`: maximum compression" + }, + { + "lineNumber": 305, + "content": "The script now creates 7z archives with maximum compression. Compression may take longer, but archives will be smaller. Ensure 7z is installed and available in your PATH (typically `7z.exe` on Windows)." + }, + { + "lineNumber": 311, + "content": "Will this remove old zip versions, or will I have to delete the _zipped folder in repo and re-compress?" + }, + { + "lineNumber": 433, + "content": "No need to manually delete the `_zipped` folder. The next run will remove old `.zip` files and create new `.7z` archives with maximum compression. The cleanup runs automatically after the zip operation completes." + }, + { + "lineNumber": 518, + "content": " \"compression\": 9," + }, + { + "lineNumber": 544, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 545, + "content": " if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 600, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 663, + "content": " \"compression\": 9" + }, + { + "lineNumber": 689, + "content": " - The code now checks if `zipper` is `\"7z\"` (case-insensitive) to determine whether to use 7z compression" + }, + { + "lineNumber": 696, + "content": "- `\"zipper\": \"7z\"` or `\"zipper\": \"zip\"` to choose the compression tool" + }, + { + "lineNumber": 716, + "content": "Checking the compression logic to see why it's not using 7z when configured:" + }, + { + "lineNumber": 789, + "content": "+ raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 905, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 906, + "content": " if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 920, + "content": "- print(\"[zip] Requested 7z compression but no 7z executable was found; falling back to zipfile.\", file=sys.stderr)" + }, + { + "lineNumber": 922, + "content": "+ print(\"[zip] Warning: 7z compression requested but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 963, + "content": "- If `\"zipper\": \"7z\"` → uses 7z compression (or errors if 7z not found)" + }, + { + "lineNumber": 966, + "content": "It will use the compression method specified in your config." + }, + { + "lineNumber": 1045, + "content": " raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 1237, + "content": "- f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 1254, + "content": "+ f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 1268, + "content": "+ raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 1413, + "content": "Sequence sync failed: 7z compression failed:" + }, + { + "lineNumber": 1443, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 1444, + "content": "RuntimeError: 7z compression failed:" + }, + { + "lineNumber": 3038, + "content": "Sequence sync failed: 7z compression failed:" + }, + { + "lineNumber": 3098, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 3100, + "content": "RuntimeError: 7z compression failed:" + }, + { + "lineNumber": 3129, + "content": "Checking the 7z compression implementation to diagnose the issue:" + }, + { + "lineNumber": 3201, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 3252, + "content": " raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 3298, + "content": "- No corrupted zip files interfere with the compression" + }, + { + "lineNumber": 3316, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 3318, + "content": "RuntimeError: 7z compression failed:" + }, + { + "lineNumber": 3431, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 4728, + "content": "Sequence sync failed: 7z compression failed:" + }, + { + "lineNumber": 4780, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 4782, + "content": "RuntimeError: 7z compression failed:" + }, + { + "lineNumber": 4835, + "content": "| `compress_blend_files.bat` | L66 |" + }, + { + "lineNumber": 4836, + "content": "| `compress_blend_files.bat` | L64-116 |" + }, + { + "lineNumber": 4987, + "content": "The 7z compression should work now with the file locking fix." + }, + { + "lineNumber": 5209, + "content": "Sequence sync failed: 7z compression failed:" + }, + { + "lineNumber": 5269, + "content": " raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 5271, + "content": "RuntimeError: 7z compression failed:" + }, + { + "lineNumber": 5378, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 5389, + "content": "- raise RuntimeError(f\"7z compression failed: {result.stderr}\")" + }, + { + "lineNumber": 5393, + "content": "+ raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 5445, + "content": "Sequence sync failed: 7z compression failed: ERRORS:" + }, + { + "lineNumber": 5535, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 5537, + "content": "RuntimeError: 7z compression failed: ERRORS:" + }, + { + "lineNumber": 5628, + "content": " raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 5677, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 5691, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 5762, + "content": "3. **Atomic move** - After successful compression, moves the temp zip to the final location, replacing any existing file" + }, + { + "lineNumber": 5884, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 6162, + "content": "The script will now use 7z compression as configured." + }, + { + "lineNumber": 6259, + "content": "| `zip_sequences.py` | `print(\"[zip] Warning: 7z compression requested but no 7z executable was found in PATH.\", file=sys.stderr)` | L91 |" + }, + { + "lineNumber": 6347, + "content": "- print(\"[zip] Warning: 7z compression requested but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 6348, + "content": "+ print(f\"[zip] Warning: 7z compression requested (ZIPPER_TYPE='{ZIPPER_TYPE}') but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 6415, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 6416, + "content": " if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 6599, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 6600, + "content": " if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 6636, + "content": "[zip] Debug: zipper_val='7z' (type: str), ZIPPER_TYPE='7z', CONFIG keys: ['zipper', 'compression', 'dailyFormat', 'structDir']" + }, + { + "lineNumber": 6669, + "content": " print(f\"[zip] Warning: 7z compression requested (ZIPPER_TYPE='{ZIPPER_TYPE}') but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 6710, + "content": " raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 6711, + "content": "+ print(f\"[zip] Debug: Using 7z compression with {SEVEN_Z_EXE}\", file=sys.stderr)" + }, + { + "lineNumber": 6733, + "content": "+ print(f\"[zip] Debug: Using zipfile compression (ZIPPER_TYPE={ZIPPER_TYPE!r})\", file=sys.stderr)" + }, + { + "lineNumber": 6772, + "content": "2. Which compression method `zip_sequence` uses (7z or zipfile)" + }, + { + "lineNumber": 6852, + "content": "- print(f\"[zip] Debug: Using zipfile compression (ZIPPER_TYPE={ZIPPER_TYPE!r})\", file=sys.stderr)" + }, + { + "lineNumber": 6856, + "content": "+ print(f\"[zip] Debug: Using zipfile compression (ZIPPER_TYPE={ZIPPER_TYPE!r})\", file=sys.stderr)" + }, + { + "lineNumber": 6860, + "content": "- if COMPRESSION_LEVEL &lt;= 0:" + }, + { + "lineNumber": 6861, + "content": "- compression = ZIP_STORED" + }, + { + "lineNumber": 6864, + "content": "- compression = ZIP_DEFLATED" + }, + { + "lineNumber": 6865, + "content": "- zip_kwargs = {\"compresslevel\": COMPRESSION_LEVEL}" + }, + { + "lineNumber": 6867, + "content": "+ if COMPRESSION_LEVEL &lt;= 0:" + }, + { + "lineNumber": 6868, + "content": "+ compression = ZIP_STORED" + }, + { + "lineNumber": 6871, + "content": "+ compression = ZIP_DEFLATED" + }, + { + "lineNumber": 6872, + "content": "+ zip_kwargs = {\"compresslevel\": COMPRESSION_LEVEL}" + }, + { + "lineNumber": 6874, + "content": "- with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:" + }, + { + "lineNumber": 6877, + "content": "+ with ZipFile(zip_path, \"w\", compression=compression, **zip_kwargs) as archive:" + }, + { + "lineNumber": 6971, + "content": "- raise RuntimeError(\"7z compression requested but 7z executable not found in PATH\")" + }, + { + "lineNumber": 6973, + "content": "+ \"7z compression requested but 7z executable not found in PATH. \"" + }, + { + "lineNumber": 6977, + "content": " print(f\"[zip] Debug: Using 7z compression with {SEVEN_Z_EXE}\", file=sys.stderr)" + }, + { + "lineNumber": 7033, + "content": "[zip] Debug: zipper_val='7z' (type: str), ZIPPER_TYPE='7z', CONFIG keys: ['zipper', 'compression', 'dailyFormat', 'structDir']" + }, + { + "lineNumber": 7041, + "content": "[zip] Debug: Using 7z compression with C:\\ProgramData\\chocolatey\\bin\\7z.EXE" + }, + { + "lineNumber": 7052, + "content": "Confirmed: config loads correctly, 7z is found, and the script uses 7z compression. Removing the debug output since it's working:" + }, + { + "lineNumber": 7070, + "content": " COMPRESSION_LEVEL = CONFIG.get(\"compression\", 9)" + }, + { + "lineNumber": 7071, + "content": " if isinstance(COMPRESSION_LEVEL, str):" + }, + { + "lineNumber": 7093, + "content": " print(f\"[zip] Warning: 7z compression requested (ZIPPER_TYPE='{ZIPPER_TYPE}') but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 7125, + "content": "- print(f\"[zip] Debug: Using 7z compression with {SEVEN_Z_EXE}\", file=sys.stderr)" + }, + { + "lineNumber": 7147, + "content": "- print(f\"[zip] Debug: Using zipfile compression (ZIPPER_TYPE={ZIPPER_TYPE!r})\", file=sys.stderr)" + }, + { + "lineNumber": 7197, + "content": "- print(f\"[zip] Warning: 7z compression requested (ZIPPER_TYPE='{ZIPPER_TYPE}') but no 7z executable was found in PATH.\", file=sys.stderr)" + }, + { + "lineNumber": 7233, + "content": "- Using 7z compression as configured" + }, + { + "lineNumber": 7237, + "content": "The script will now use 7z compression at level 9 as specified in your config, and if there are any issues (like 7z not being found), it will fail immediately with a clear error message explaining what's wrong." + }, + { + "lineNumber": 7249, + "content": "[zip] Debug: zipper_val='7z' (type: str), ZIPPER_TYPE='7z', CONFIG keys: ['zipper', 'compression', 'dailyFormat', 'structDir']" + }, + { + "lineNumber": 7257, + "content": "[zip] Debug: Using 7z compression with C:\\ProgramData\\chocolatey\\bin\\7z.EXE" + }, + { + "lineNumber": 7425, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 7747, + "content": "When you run the script, it will create `.7z` files instead of `.zip` files. The 7z format typically provides better compression than zip, especially at compression level 9." + }, + { + "lineNumber": 8190, + "content": "How do I view the compression method and other properties of the 7z archive?" + }, + { + "lineNumber": 8235, + "content": "**1. List files with technical details (compression method, size, etc.):**" + }, + { + "lineNumber": 8256, + "content": "- Compression method (e.g., LZMA2, LZMA)" + }, + { + "lineNumber": 8257, + "content": "- Compression level" + }, + { + "lineNumber": 8258, + "content": "- File sizes (original, compressed)" + }, + { + "lineNumber": 8268, + "content": "This lists the compression method, level, and sizes for all `.7z` files in your `_zipped` folder." + }, + { + "lineNumber": 8274, + "content": "Is it compression lvl 9?" + }, + { + "lineNumber": 16793, + "content": "Yes, it's using compression level 9." + }, + { + "lineNumber": 16802, + "content": "The script passes `-mx=9` to 7z (from your config's `\"compression\": 9`), so the archive is compressed at maximum level." + }, + { + "lineNumber": 18379, + "content": "- **Compression Method**: `LZMA2:28` (compression level 9, 256 MB dictionary)" + }, + { + "lineNumber": 18380, + "content": "- **Solid Archive**: Yes (all files compressed together for better ratio)" + }, + { + "lineNumber": 18384, + "content": "- 1 MP4 file: `01_Blueprint-1-150.mp4` (2.27 MB uncompressed)" + }, + { + "lineNumber": 18385, + "content": "- 150 PNG files: `01_Blueprint_000001.png` through `01_Blueprint_000150.png` (~197–199 KB each, uncompressed)" + }, + { + "lineNumber": 18387, + "content": "All files are compressed with `LZMA2:28`, confirming compression level 9. The archive is solid, which improves compression for similar files (PNG frames)." + }, + { + "lineNumber": 18393, + "content": "Let's see if we can make the 7z compression go faster. What's causing my bottleneck? I see low CPU util and low disk util." + }, + { + "lineNumber": 18419, + "content": " <summary>Tool use: **codebase_search** • Searched codebase \"How does the script parallelize compression of multiple sequences?\" • **14** results</summary>" + }, + { + "lineNumber": 18426, + "content": "| `compress_blend_files.bat` | L64-116 |" + }, + { + "lineNumber": 18430, + "content": "| `compress_blend_files.bat` | L66 |" + }, + { + "lineNumber": 18448, + "content": "LZMA2 compression is single-threaded by default, which explains the low CPU usage. Enabling multi-threading and optimizing the 7z command:" + }, + { + "lineNumber": 18502, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 18593, + "content": "1. **Using a different compression method** — Some methods (like PPMd) may be faster with multi-threading, though they may compress less" + }, + { + "lineNumber": 18594, + "content": "2. **Reducing compression level** — Level 9 is slowest; level 7 or 8 may be faster with similar ratios" + }, + { + "lineNumber": 18597, + "content": "Test it and let me know if CPU usage improves. If it's still low, we can explore other compression methods or adjust the level." + }, + { + "lineNumber": 18614, + "content": "LZMA2 is largely single-threaded; `-mmt=on` only helps with some operations. Checking alternative compression methods with better multi-threading:" + }, + { + "lineNumber": 18629, + "content": " <summary>Tool use: **web_search** • Searched web `7z compression methods multi-threading performance LZMA2 vs PPMd vs BZip2` • **1** results</summary>" + }, + { + "lineNumber": 18633, + "content": "| Web Search Results | | It appears that despite enabling multi-threading with the `-mmt=on` option, your CPU usage during 7z compression remains low, especially for smaller sequences. This behavior is typical due to the nature of the LZMA2 compression algorithm used by 7z.<br/><br/>**Understanding LZMA2 Compression and CPU Utilization:**<br/><br/>LZMA2, the default compression method in 7z, is designed for high compression ratios but can be resource-intensive. While it supports multi-threading, the efficiency of CPU utilization depends on several factors:<br/><br/>1. **File Size and Complexity:** Smaller files or sequences may not fully benefit from multi-threading, leading to lower CPU usage. Larger files provide more data to process concurrently, resulting in higher CPU utilization.<br/><br/>2. **Compression Level:** Higher compression levels (e.g., `-mx=9`) require more CPU resources and time. Lowering the compression level (e.g., `-mx=5` or `-mx=6`) can speed up the process with " + }, + { + "lineNumber": 18645, + "content": "LZMA2 has limited multi-threading. Adding support for compression methods with better multi-threading (e.g., PPMd) and making it configurable:" + }, + { + "lineNumber": 18671, + "content": "Adding support for compression methods with better multi-threading. PPMd typically performs better than LZMA2 for multi-threading:" + }, + { + "lineNumber": 18686, + "content": " \"compression\": 9," + }, + { + "lineNumber": 18687, + "content": "+ \"compressionMethod\": \"LZMA2\", # Options: LZMA2, PPMd, BZip2, Deflate" + }, + { + "lineNumber": 18707, + "content": " COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))" + }, + { + "lineNumber": 18709, + "content": "+ # Compression method: LZMA2 (default, best ratio but slower), PPMd (faster, better multi-threading), BZip2, Deflate" + }, + { + "lineNumber": 18710, + "content": "+ COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"LZMA2\").upper()" + }, + { + "lineNumber": 18712, + "content": "+ if COMPRESSION_METHOD not in (\"LZMA2\", \"PPMD\", \"BZIP2\", \"DEFLATE\"):" + }, + { + "lineNumber": 18713, + "content": "+ COMPRESSION_METHOD = \"LZMA2\"" + }, + { + "lineNumber": 18736, + "content": "+ # Build compression method parameter" + }, + { + "lineNumber": 18737, + "content": "+ if COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 18739, + "content": "+ elif COMPRESSION_METHOD == \"PPMD\":" + }, + { + "lineNumber": 18741, + "content": "+ elif COMPRESSION_METHOD == \"BZIP2\":" + }, + { + "lineNumber": 18743, + "content": "+ elif COMPRESSION_METHOD == \"DEFLATE\":" + }, + { + "lineNumber": 18758, + "content": " f\"-mx={COMPRESSION_LEVEL}\"," + }, + { + "lineNumber": 18759, + "content": "+ method_param, # Compression method" + }, + { + "lineNumber": 18793, + "content": "Added support for different compression methods. PPMd typically has better multi-threading than LZMA2." + }, + { + "lineNumber": 18796, + "content": "1. **Configurable compression method** — You can now choose: `LZMA2`, `PPMd`, `BZip2`, or `Deflate`" + }, + { + "lineNumber": 18806, + "content": " \"compression\": 9," + }, + { + "lineNumber": 18807, + "content": " \"compressionMethod\": \"PPMd\"" + }, + { + "lineNumber": 18811, + "content": "**Compression method comparison:**" + }, + { + "lineNumber": 18812, + "content": "- **LZMA2** (default): Best compression ratio, limited multi-threading (your current bottleneck)" + }, + { + "lineNumber": 18825, + "content": "what's the highest compression with highest CPU util?" + }, + { + "lineNumber": 18835, + "content": "PPMd offers the best balance: high compression and better multi-threading than LZMA2." + }, + { + "lineNumber": 18842, + "content": "For maximum compression with high CPU utilization, use PPMd. Updating the default config to use PPMd:" + }, + { + "lineNumber": 18865, + "content": " \"compression\": 9," + }, + { + "lineNumber": 18866, + "content": "- \"compressionMethod\": \"LZMA2\", # Options: LZMA2, PPMd, BZip2, Deflate" + }, + { + "lineNumber": 18867, + "content": "+ \"compressionMethod\": \"PPMd\", # Options: LZMA2 (best ratio, limited threading), PPMd (high ratio, good threading), BZip2, Deflate" + }, + { + "lineNumber": 18882, + "content": "**PPMd** provides the best balance: high compression with better multi-threading." + }, + { + "lineNumber": 18884, + "content": "**Compression methods ranked by ratio + CPU utilization:**" + }, + { + "lineNumber": 18885, + "content": "1. **PPMd** — Very high compression (often 90–95% of LZMA2) with good multi-threading" + }, + { + "lineNumber": 18886, + "content": "2. **LZMA2** — Highest compression, but limited multi-threading (core algorithm is single-threaded)" + }, + { + "lineNumber": 18887, + "content": "3. **BZip2** — Good compression, decent multi-threading" + }, + { + "lineNumber": 18888, + "content": "4. **Deflate** — Lower compression, good multi-threading" + }, + { + "lineNumber": 18890, + "content": "**Recommendation:** Use **PPMd** for the best ratio with high CPU utilization. It should use more CPU cores than LZMA2 while maintaining a high compression ratio." + }, + { + "lineNumber": 18899, + "content": " \"compression\": 9," + }, + { + "lineNumber": 18900, + "content": " \"compressionMethod\": \"PPMd\"" + }, + { + "lineNumber": 18904, + "content": "If you want the absolute highest compression regardless of CPU usage, use `\"compressionMethod\": \"LZMA2\"`, but expect lower CPU utilization." + }, + { + "lineNumber": 18906, + "content": "Test with PPMd and you should see higher CPU usage while still getting strong compression." + }, + { + "lineNumber": 19408, + "content": "[zip] RAM: 102.2GB available (80% of total), 40.9GB for compression (40%), 8.0GB per job (dict: 1024MB) → RAM limit: 5, requested: None, final: 5" + }, + { + "lineNumber": 19477, + "content": "- # Use only 40% of that for compression jobs (very conservative to prevent swapping)" + }, + { + "lineNumber": 19478, + "content": "- compression_ram = int(available_ram * 0.4)" + }, + { + "lineNumber": 19479, + "content": "+ # Use 95% of available RAM for compression jobs (aggressive mode)" + }, + { + "lineNumber": 19480, + "content": "+ compression_ram = int(available_ram * 0.95)" + }, + { + "lineNumber": 19492, + "content": "- f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (40%), {ram_per_job_gb:.1f}GB per job (dict: {FIXED_DICT_SIZE_MB}MB) → \"" + }, + { + "lineNumber": 19493, + "content": "+ f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (95%), {ram_per_job_gb:.1f}GB per job (dict: {FIXED_DICT_SIZE_MB}MB) → \"" + }, + { + "lineNumber": 19536, + "content": "1. **Increased RAM usage**: From 40% to 95% of available RAM for compression" + }, + { + "lineNumber": 19540, + "content": "- **Before**: 40.9GB for compression ÷ 8GB per job = **5 workers**" + }, + { + "lineNumber": 19541, + "content": "- **After**: ~97GB for compression ÷ 3GB per job = **~32 workers**" + }, + { + "lineNumber": 19543, + "content": "This should use more CPU and RAM. The dictionary size remains 1GB, so compression ratio is unchanged." + }, + { + "lineNumber": 19654, + "content": " ram_limit = max(1, compression_ram // FIXED_RAM_PER_JOB)" + }, + { + "lineNumber": 19671, + "content": " f\"RAM: {ram_gb:.1f}GB available (80% of total), {compression_ram_gb:.1f}GB for compression (95%), {ram_per_job_gb:.1f}GB per job (dict: {FIXED_DICT_SIZE_MB}MB) → \"" + }, + { + "lineNumber": 19825, + "content": "pulled from HOME again.@2025-11-12_01-02Z-compression-failure-and-memory-allocation-issues.md the RAM estimate is far higher than the actual result." + }, + { + "lineNumber": 19885, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [1 total]" + }, + { + "lineNumber": 19889, + "content": "[scan] Queued 05 Stretch Cut Nail\\02_talking for compression (~0.81GB) [2 total]" + }, + { + "lineNumber": 19891, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle for compression (~0.70GB) [3 total]" + }, + { + "lineNumber": 19893, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_dance_only for compression (~0.38GB) [4 total]" + }, + { + "lineNumber": 19895, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.46GB) [5 total]" + }, + { + "lineNumber": 19899, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.17GB) [10 total]" + }, + { + "lineNumber": 19901, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 9 for compression (~0.37GB) [15 total]" + }, + { + "lineNumber": 19903, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 19911, + "content": "[init] Preparing to compress 15 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 19983, + "content": "- # This is the main memory consumer for 7z LZMA compression" + }, + { + "lineNumber": 20125, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [1 total]" + }, + { + "lineNumber": 20127, + "content": "[scan] Queued 05 Stretch Cut Nail\\02_kicker insert for compression (~0.24GB) [2 total]" + }, + { + "lineNumber": 20129, + "content": "[scan] Queued 05 Stretch Cut Nail\\02_talking for compression (~0.81GB) [3 total]" + }, + { + "lineNumber": 20131, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle for compression (~0.70GB) [4 total]" + }, + { + "lineNumber": 20133, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_dance_only for compression (~0.38GB) [5 total]" + }, + { + "lineNumber": 20137, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 10 for compression (~1.28GB) [10 total]" + }, + { + "lineNumber": 20139, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 8 for compression (~0.94GB) [15 total]" + }, + { + "lineNumber": 20141, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 20149, + "content": "[init] Preparing to compress 16 sequence(s) with 2 worker(s)" + }, + { + "lineNumber": 20285, + "content": "With 97GB available for compression and 3GB per job, bin-packing should allow many more concurrent workers. The estimate is tighter while still leaving headroom for actual usage variations." + }, + { + "lineNumber": 20347, + "content": "[scan] Queued 05 Stretch Cut Nail\\01_intro for compression (~1.10GB) [1 total]" + }, + { + "lineNumber": 20351, + "content": "[scan] Queued 05 Stretch Cut Nail\\02_talking for compression (~0.81GB) [2 total]" + }, + { + "lineNumber": 20353, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle for compression (~0.70GB) [3 total]" + }, + { + "lineNumber": 20355, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_dance_only for compression (~0.38GB) [4 total]" + }, + { + "lineNumber": 20357, + "content": "[scan] Queued 05 Stretch Cut Nail\\03_FT Shuffle_Intro for compression (~0.46GB) [5 total]" + }, + { + "lineNumber": 20361, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 11 for compression (~1.17GB) [10 total]" + }, + { + "lineNumber": 20363, + "content": "[scan] Queued 05 Stretch Cut Nail\\Visual 9 for compression (~0.37GB) [15 total]" + }, + { + "lineNumber": 20365, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 20373, + "content": "[init] Preparing to compress 15 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 20424, + "content": " # Use 95% of available RAM for compression jobs" + }, + { + "lineNumber": 20425, + "content": " compression_ram = int(available_ram * 0.95)" + }, + { + "lineNumber": 20467, + "content": "- bin_remaining.append(compression_ram - estimated_ram)" + }, + { + "lineNumber": 20477, + "content": "+ if total_estimated_ram &lt;= compression_ram:" + }, + { + "lineNumber": 20501, + "content": "+ bin_remaining.append(compression_ram - estimated_ram)" + }, + { + "lineNumber": 20518, + "content": " compression_ram_gb = compression_ram / (1024 ** 3)" + }, + { + "lineNumber": 20534, + "content": "+ if total_estimated_ram &lt;= compression_ram:" + }, + { + "lineNumber": 20593, + "content": "1. **Check if all sequences fit**: If total estimated RAM (45GB) ≤ available compression RAM (97GB), use one worker per sequence." + }, + { + "lineNumber": 20620, + "content": "In the compression script, bin packing is used to:" + }, + { + "lineNumber": 20621, + "content": "- **Items**: Sequences to compress (each estimated at ~3GB RAM)" + }, + { + "lineNumber": 20717, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5 for compression (~1.61GB) [1 total]" + }, + { + "lineNumber": 20719, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 20727, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 20783, + "content": "<summary>Tool use: **grep** • Grep for \"compressionMethod\" in \"zip_sequences.py\" • 0 matches</summary>" + }, + { + "lineNumber": 20968, + "content": "When there's only one sequence to compress, the single 7z process will use all available CPU cores, improving compression speed. With multiple concurrent workers, 7z uses auto mode to balance threads." + }, + { + "lineNumber": 20987, + "content": "Checking if the compression method is configured and used. LZMA2 (default) is largely single-threaded, which could explain the low CPU usage. Verifying the compression method setting:" + }, + { + "lineNumber": 20994, + "content": "<summary>Tool use: **grep** • Grep for \"compressionMethod|COMPRESSION_METHOD\" in \"zip_sequences.py\" • 0 matches</summary>" + }, + { + "lineNumber": 21016, + "content": " <summary>Tool use: **codebase_search** • Searched codebase \"What compression method is configured for 7z compression?\" • **13** results</summary>" + }, + { + "lineNumber": 21029, + "content": "| `compress_blend_files.bat` | L66 |" + }, + { + "lineNumber": 21044, + "content": "The code doesn't use a compression method setting, so it defaults to LZMA2, which is largely single-threaded. Adding support for a configurable compression method (e.g., PPMd) for better multi-threading:" + }, + { + "lineNumber": 21077, + "content": "Adding compression method support. LZMA2 (default) is largely single-threaded, so we'll use PPMd for better multi-threading:" + }, + { + "lineNumber": 21100, + "content": " \"compression\": 9," + }, + { + "lineNumber": 21101, + "content": "+ \"compressionMethod\": \"PPMd\", # Compression method: LZMA2, PPMd, BZip2, Deflate" + }, + { + "lineNumber": 21121, + "content": " COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))" + }, + { + "lineNumber": 21123, + "content": "+ COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"PPMd\")" + }, + { + "lineNumber": 21124, + "content": "+ # Validate compression method" + }, + { + "lineNumber": 21126, + "content": "+ if COMPRESSION_METHOD not in valid_methods:" + }, + { + "lineNumber": 21127, + "content": "+ COMPRESSION_METHOD = \"PPMd\" # Default to PPMd for better multi-threading" + }, + { + "lineNumber": 21151, + "content": "+ # Set compression method (PPMd is better for multi-threading than default LZMA2)" + }, + { + "lineNumber": 21152, + "content": "+ cmd.append(f\"-m0={COMPRESSION_METHOD}\")" + }, + { + "lineNumber": 21176, + "content": "Updating logging to show the compression method and checking for errors:" + }, + { + "lineNumber": 21240, + "content": "+ log(\"init\", f\"7z executable: {exe}, Max7zInst: {max_inst}, method: {COMPRESSION_METHOD}, level: {COMPRESSION_LEVEL}\")" + }, + { + "lineNumber": 21256, + "content": "Added compression method support. The issue is that **LZMA2** (the default 7z method) is largely single-threaded, so even with `-mmt={cpu_cores}`, it won't use all cores effectively." + }, + { + "lineNumber": 21260, + "content": "1. **Added `compressionMethod` to `DEFAULT_CONFIG`** — defaults to `\"PPMd\"` for better multi-threading" + }, + { + "lineNumber": 21261, + "content": "2. **Added compression method loading and validation** — reads from config, validates against supported methods (LZMA2, PPMd, BZip2, Deflate)" + }, + { + "lineNumber": 21262, + "content": "3. **Added `-m0={COMPRESSION_METHOD}` to the 7z command** — uses the specified method instead of default LZMA2" + }, + { + "lineNumber": 21263, + "content": "4. **Updated logging** — shows the compression method being used" + }, + { + "lineNumber": 21269, + "content": "Since your `config.json` doesn't have `compressionMethod` set, it will use the default `\"PPMd\"` from `DEFAULT_CONFIG`. You can explicitly add it to your config if you want:" + }, + { + "lineNumber": 21273, + "content": " \"compressionMethod\": \"PPMd\"" + }, + { + "lineNumber": 21345, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5 for compression (~1.61GB) [1 total]" + }, + { + "lineNumber": 21347, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 21355, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 21359, + "content": "Sequence sync failed: 7z compression failed: System ERROR:" + }, + { + "lineNumber": 21421, + "content": " raise RuntimeError(f\"7z compression failed: {error_msg}\")" + }, + { + "lineNumber": 21423, + "content": "RuntimeError: 7z compression failed: System ERROR:" + }, + { + "lineNumber": 21454, + "content": "The error is that `-md` (dictionary size) is for LZMA/LZMA2. PPMd uses a different parameter. Updating the code to set memory/dictionary based on the compression method:" + }, + { + "lineNumber": 21469, + "content": " <summary>Tool use: **web_search** • Searched web `7z PPMd compression method parameters memory dictionary size` • **1** results</summary>" + }, + { + "lineNumber": 21473, + "content": "| Web Search Results | | It appears you're encountering two primary issues:<br/><br/>1. **Low CPU Utilization During Compression**: Despite configuring 7-Zip to use multiple threads, you're observing only about 15% CPU usage.<br/><br/>2. **Batch Script Error**: The error message `\". was unexpected at this time.\"` suggests a syntax issue in your batch script.<br/><br/>**Addressing Low CPU Utilization:**<br/><br/>The LZMA2 compression method, which is the default for 7z archives, supports multi-threading. However, its effectiveness can vary based on the data being compressed and the system's hardware. To enhance CPU utilization, consider the following:<br/><br/>- **Verify Multi-Threading Configuration**: Ensure that the `-mmt` switch is correctly set in your 7z command. This switch enables multi-threading, and specifying a number (e.g., `-mmt=4`) directs 7-Zip to use that many threads. ([jaymze.org](https://jaymze.org/proglang/7zip/7z_command-line_guide.pdf?utm_source=openai))<br/><br/>" + }, + { + "lineNumber": 21485, + "content": "Updating the code to set memory/dictionary parameters based on the compression method. `-md` is for LZMA/LZMA2; PPMd uses a different format:" + }, + { + "lineNumber": 21501, + "content": "- # Set compression method (PPMd is better for multi-threading than default LZMA2)" + }, + { + "lineNumber": 21502, + "content": "- cmd.append(f\"-m0={COMPRESSION_METHOD}\")" + }, + { + "lineNumber": 21506, + "content": "- # We keep dictionary at 1GB for best compression regardless of RAM estimate" + }, + { + "lineNumber": 21507, + "content": "+ # Set compression method and memory/dictionary size based on method" + }, + { + "lineNumber": 21511, + "content": "+ if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 21514, + "content": "+ elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 21518, + "content": "+ elif COMPRESSION_METHOD == \"BZip2\":" + }, + { + "lineNumber": 21523, + "content": "+ elif COMPRESSION_METHOD == \"Deflate\":" + }, + { + "lineNumber": 21643, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5 for compression (~1.61GB) [1 total]" + }, + { + "lineNumber": 21645, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 21653, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 21685, + "content": "| Web Search Results | | I understand you're experiencing low CPU utilization (~3%) during compression with 7-Zip, even after configuring it to use the PPMd method with 1GB of memory. Let's explore potential reasons and solutions:<br/><br/>**1. Compression Method Characteristics:**<br/><br/>- **PPMd Method:** PPMd is optimized for compressing text data and is inherently single-threaded, meaning it utilizes only one CPU core during compression. This can lead to low overall CPU usage on multi-core systems. ([documentation.help](https://documentation.help/7-Zip/7z.htm?utm_source=openai))<br/><br/>**2. Alternative Compression Methods:**<br/><br/>- **LZMA2 Method:** LZMA2 supports multi-threading, allowing it to utilize multiple CPU cores simultaneously, which can significantly increase CPU usage and compression speed. ([documentation.help](https://documentation.help/7-Zip/7z.htm?utm_source=openai))<br/><br/>**3. Recommendations:**<br/><br/>- **Switch to LZMA2:** If your primary goal is to" + }, + { + "lineNumber": 21712, + "content": " \"compression\": 9," + }, + { + "lineNumber": 21713, + "content": "- \"compressionMethod\": \"PPMd\", # Compression method: LZMA2, PPMd, BZip2, Deflate" + }, + { + "lineNumber": 21714, + "content": "+ \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate" + }, + { + "lineNumber": 21734, + "content": " COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))" + }, + { + "lineNumber": 21736, + "content": "- COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"PPMd\")" + }, + { + "lineNumber": 21737, + "content": "+ COMPRESSION_METHOD = CONFIG.get(\"compressionMethod\", \"LZMA2\")" + }, + { + "lineNumber": 21738, + "content": " # Validate compression method" + }, + { + "lineNumber": 21740, + "content": " if COMPRESSION_METHOD not in valid_methods:" + }, + { + "lineNumber": 21741, + "content": "- COMPRESSION_METHOD = \"PPMd\" # Default to PPMd for better multi-threading" + }, + { + "lineNumber": 21742, + "content": "+ COMPRESSION_METHOD = \"LZMA2\" # Default to LZMA2 for multi-threading support" + }, + { + "lineNumber": 21768, + "content": "+ if COMPRESSION_METHOD == \"PPMd\":" + }, + { + "lineNumber": 21975, + "content": "1. **Changed default compression method to LZMA2** — LZMA2 supports multi-threading" + }, + { + "lineNumber": 21983, + "content": "Since your `config.json` doesn't have `compressionMethod` set, it will now default to `LZMA2` instead of `PPMd`. With LZMA2 and `-mmt={cpu_cores}` when there's only 1 worker, you should see much higher CPU utilization." + }, + { + "lineNumber": 22057, + "content": "[scan] Queued 06 Infill And Powerbrooming\\Visual 5 for compression (~1.61GB) [1 total]" + }, + { + "lineNumber": 22059, + "content": "[zip] RAM: 102.2GB available (80% of total), 97.1GB for compression (95%)" + }, + { + "lineNumber": 22067, + "content": "[init] Preparing to compress 1 sequence(s) with 1 worker(s)" + }, + { + "lineNumber": 22085, + "content": "3. Data characteristics — already-compressed images may not benefit much" + }, + { + "lineNumber": 22106, + "content": "| Web Search Results | | I'm sorry to hear that you're still experiencing low CPU utilization during the compression process, even after adjusting the settings. Let's explore some potential reasons and solutions:<br/><br/>1. **Compression Method and Multi-threading**:<br/> - **LZMA2** is designed to support multi-threading, which can significantly improve compression speed by utilizing multiple CPU cores.<br/> - Ensure that you're explicitly specifying LZMA2 as the compression method in your command:<br/> ```bash<br/> 7z a -m0=lzma2 -mmt=on archive.7z files<br/> ```<br/> Here, `-m0=lzma2` sets the compression method to LZMA2, and `-mmt=on` enables multi-threading. ([superuser.com](https://superuser.com/questions/408631/how-do-i-enable-multi-threaded-lzma2-using-7-zip-9-20-on-the-command-line?utm_source=openai))<br/><br/>2. **Dictionary Size and Memory Allocation**:<br/> - The dictionary size (`-md`) can impact both compression ratio and CPU usage. Larger dictiona" + }, + { + "lineNumber": 22120, + "content": "2. Already-compressed data — image files (PNG/JPG) compress poorly, so CPU stays low" + }, + { + "lineNumber": 22145, + "content": " elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 22148, + "content": "+ # Smaller blocks = more parallelism, but slightly worse compression" + }, + { + "lineNumber": 22155, + "content": " elif COMPRESSION_METHOD == \"BZip2\":" + }, + { + "lineNumber": 22212, + "content": " elif COMPRESSION_METHOD == \"LZMA2\":" + }, + { + "lineNumber": 22215, + "content": "- # Smaller blocks = more parallelism, but slightly worse compression" + }, + { + "lineNumber": 22222, + "content": " elif COMPRESSION_METHOD == \"BZip2\":" + } + ] + }, + { + "file": "NewProject.bat", + "matches": [ + { + "lineNumber": 46, + "content": "mkdir \"%projectRoot%\\Assets\\Blends\"" + }, + { + "lineNumber": 51, + "content": ":: Create Blends structure" + }, + { + "lineNumber": 52, + "content": "mkdir \"%projectRoot%\\Blends\\animations\\\"" + }, + { + "lineNumber": 53, + "content": "mkdir \"%projectRoot%\\Blends\\stills\\img-BG\"" + } + ] + } + ], + "totalLines": 766, + "totalMatchedLines": 766 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +Checking the crash log location and reviewing the script for potential issues: + + +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "What causes Blender access violations when opening or saving blend files in background mode?", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 65 + }, + "endPosition": { + "line": 118 + } + }, + "contents": "echo # Check if output file already exists\necho if os.path.exists^(output_file^):\necho print^(\"SKIP_EXISTING:\" + blend_file^)\necho continue\necho # Skip if source is already compressed\necho if is_compressed_file^(blend_file^):\necho print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)\necho continue\necho print^(\"PROCESSING:\" + blend_file^)\necho # Load the blend file\necho bpy.ops.wm.open_mainfile^(filepath=blend_file^)\necho # Save with compression to output folder\necho bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho except Exception as e:\necho print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho else:\necho print^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"COMPRESSION_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n echo Script created successfully: !TEMP_SCRIPT!\n) else (\n echo ERROR: Failed to create script file!\n pause\n exit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho COMPRESSION REPORT\necho ========================================\necho.\n\nREM Count processed files and get file info\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1", + "signatures": {}, + "detailedLines": [ + { + "text": "echo # Check if output file already exists", + "lineNumber": 66 + }, + { + "text": "echo if os.path.exists^(output_file^):", + "lineNumber": 67 + }, + { + "text": "echo print^(\"SKIP_EXISTING:\" + blend_file^)", + "lineNumber": 68 + }, + { + "text": "echo continue", + "lineNumber": 69 + }, + { + "text": "echo # Skip if source is already compressed", + "lineNumber": 70 + }, + { + "text": "echo if is_compressed_file^(blend_file^):", + "lineNumber": 71 + }, + { + "text": "echo print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)", + "lineNumber": 72 + }, + { + "text": "echo continue", + "lineNumber": 73 + }, + { + "text": "echo print^(\"PROCESSING:\" + blend_file^)", + "lineNumber": 74 + }, + { + "text": "echo # Load the blend file", + "lineNumber": 75 + }, + { + "text": "echo bpy.ops.wm.open_mainfile^(filepath=blend_file^)", + "lineNumber": 76 + }, + { + "text": "echo # Save with compression to output folder", + "lineNumber": 77 + }, + { + "text": "echo bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 78 + }, + { + "text": "echo print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 79 + }, + { + "text": "echo except Exception as e:", + "lineNumber": 80 + }, + { + "text": "echo print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 81 + }, + { + "text": "echo else:", + "lineNumber": 82 + }, + { + "text": "echo print^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 83 + }, + { + "text": "echo.", + "lineNumber": 84 + }, + { + "text": "echo print^(\"COMPRESSION_COMPLETE\"^)", + "lineNumber": 85 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 86 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 89 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 90 + }, + { + "text": " echo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 91 + }, + { + "text": ") else (", + "lineNumber": 92 + }, + { + "text": " echo ERROR: Failed to create script file!", + "lineNumber": 93 + }, + { + "text": " pause", + "lineNumber": 94 + }, + { + "text": " exit /b 1", + "lineNumber": 95 + }, + { + "text": ")", + "lineNumber": 96 + }, + { + "lineNumber": 97 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 98 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 99 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 100 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 101 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 104 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 105 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 106 + }, + { + "lineNumber": 107 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 108 + }, + { + "text": "echo.", + "lineNumber": 109 + }, + { + "text": "echo ========================================", + "lineNumber": 110 + }, + { + "text": "echo COMPRESSION REPORT", + "lineNumber": 111 + }, + { + "text": "echo ========================================", + "lineNumber": 112 + }, + { + "text": "echo.", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": "REM Count processed files and get file info", + "lineNumber": 115 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 116 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 117 + }, + { + "text": " set /a processed+=1", + "lineNumber": 118 + } + ] + }, + "score": 0.3222891688346863 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 67 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\n\necho Starting Blender file compression...\n\nREM Check if files were dragged onto the batch file\nif \"%~1\"==\"\" (\n echo.\n echo Usage: Drag .blend files onto this batch file to compress them.\n echo Output will be saved to the \\output folder.\n echo.\n pause\n exit /b 1\n)\n\nREM Create output directory if it doesn't exist\nif not exist \"output\" mkdir \"output\"\n\nREM Initialize counters and tracking\nset \"processed=0\"\nset \"crashed=0\"\nset \"skipped=0\"\nset \"deleted_similar=0\"\nset \"threshold_percent=10\"\n\nREM Create a temporary Python script for compression\nset \"TEMP_SCRIPT=compress_blend_temp.py\"\necho Creating Python script: !TEMP_SCRIPT!\n\n(\necho import bpy\necho import os\necho import sys\necho.\necho # Enable compression globally\necho bpy.context.preferences.filepaths.save_version = 0\necho bpy.context.preferences.filepaths.use_file_compression = True\necho.\necho # Create output directory if it doesn't exist\necho if not os.path.exists^(\"output\"^):\necho os.makedirs^(\"output\"^)\necho.\necho # Get command line arguments ^(the dragged files^)\necho blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []\necho.\necho print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)\necho.\necho def is_compressed_file^(path^):\necho try:\necho with open^(path, 'rb'^) as f:\necho header = f.read^(4^)\necho if header[:2] == b'\\x1f\\x8b': # gzip\necho return True\necho if header == b'\\x28\\xb5\\x2f\\xfd': # zstd\necho return True\necho return False\necho except Exception:\necho return False\necho.\necho for blend_file in blend_files:\necho if blend_file.lower^(^).endswith^(\".blend\"^):\necho try:\necho # Create output path in output folder\necho filename = os.path.basename^(blend_file^)\necho output_file = os.path.join^(\"output\", filename^)\necho # Check if output file already exists\necho if os.path.exists^(output_file^):", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "echo Starting Blender file compression...", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "REM Check if files were dragged onto the batch file", + "lineNumber": 6 + }, + { + "text": "if \"%~1\"==\"\" (", + "lineNumber": 7 + }, + { + "text": " echo.", + "lineNumber": 8 + }, + { + "text": " echo Usage: Drag .blend files onto this batch file to compress them.", + "lineNumber": 9 + }, + { + "text": " echo Output will be saved to the \\output folder.", + "lineNumber": 10 + }, + { + "text": " echo.", + "lineNumber": 11 + }, + { + "text": " pause", + "lineNumber": 12 + }, + { + "text": " exit /b 1", + "lineNumber": 13 + }, + { + "text": ")", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "REM Create output directory if it doesn't exist", + "lineNumber": 16 + }, + { + "text": "if not exist \"output\" mkdir \"output\"", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "REM Initialize counters and tracking", + "lineNumber": 19 + }, + { + "text": "set \"processed=0\"", + "lineNumber": 20 + }, + { + "text": "set \"crashed=0\"", + "lineNumber": 21 + }, + { + "text": "set \"skipped=0\"", + "lineNumber": 22 + }, + { + "text": "set \"deleted_similar=0\"", + "lineNumber": 23 + }, + { + "text": "set \"threshold_percent=10\"", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "REM Create a temporary Python script for compression", + "lineNumber": 26 + }, + { + "text": "set \"TEMP_SCRIPT=compress_blend_temp.py\"", + "lineNumber": 27 + }, + { + "text": "echo Creating Python script: !TEMP_SCRIPT!", + "lineNumber": 28 + }, + { + "lineNumber": 29 + }, + { + "text": "(", + "lineNumber": 30 + }, + { + "text": "echo import bpy", + "lineNumber": 31 + }, + { + "text": "echo import os", + "lineNumber": 32 + }, + { + "text": "echo import sys", + "lineNumber": 33 + }, + { + "text": "echo.", + "lineNumber": 34 + }, + { + "text": "echo # Enable compression globally", + "lineNumber": 35 + }, + { + "text": "echo bpy.context.preferences.filepaths.save_version = 0", + "lineNumber": 36 + }, + { + "text": "echo bpy.context.preferences.filepaths.use_file_compression = True", + "lineNumber": 37 + }, + { + "text": "echo.", + "lineNumber": 38 + }, + { + "text": "echo # Create output directory if it doesn't exist", + "lineNumber": 39 + }, + { + "text": "echo if not os.path.exists^(\"output\"^):", + "lineNumber": 40 + }, + { + "text": "echo os.makedirs^(\"output\"^)", + "lineNumber": 41 + }, + { + "text": "echo.", + "lineNumber": 42 + }, + { + "text": "echo # Get command line arguments ^(the dragged files^)", + "lineNumber": 43 + }, + { + "text": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []", + "lineNumber": 44 + }, + { + "text": "echo.", + "lineNumber": 45 + }, + { + "text": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)", + "lineNumber": 46 + }, + { + "text": "echo.", + "lineNumber": 47 + }, + { + "text": "echo def is_compressed_file^(path^):", + "lineNumber": 48 + }, + { + "text": "echo try:", + "lineNumber": 49 + }, + { + "text": "echo with open^(path, 'rb'^) as f:", + "lineNumber": 50 + }, + { + "text": "echo header = f.read^(4^)", + "lineNumber": 51 + }, + { + "text": "echo if header[:2] == b'\\x1f\\x8b': # gzip", + "lineNumber": 52 + }, + { + "text": "echo return True", + "lineNumber": 53 + }, + { + "text": "echo if header == b'\\x28\\xb5\\x2f\\xfd': # zstd", + "lineNumber": 54 + }, + { + "text": "echo return True", + "lineNumber": 55 + }, + { + "text": "echo return False", + "lineNumber": 56 + }, + { + "text": "echo except Exception:", + "lineNumber": 57 + }, + { + "text": "echo return False", + "lineNumber": 58 + }, + { + "text": "echo.", + "lineNumber": 59 + }, + { + "text": "echo for blend_file in blend_files:", + "lineNumber": 60 + }, + { + "text": "echo if blend_file.lower^(^).endswith^(\".blend\"^):", + "lineNumber": 61 + }, + { + "text": "echo try:", + "lineNumber": 62 + }, + { + "text": "echo # Create output path in output folder", + "lineNumber": 63 + }, + { + "text": "echo filename = os.path.basename^(blend_file^)", + "lineNumber": 64 + }, + { + "text": "echo output_file = os.path.join^(\"output\", filename^)", + "lineNumber": 65 + }, + { + "text": "echo # Check if output file already exists", + "lineNumber": 66 + }, + { + "text": "echo if os.path.exists^(output_file^):", + "lineNumber": 67 + } + ] + }, + "score": 0.26401469111442566 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": { + "line": 64 + }, + "endPosition": { + "line": 107 + } + }, + "contents": "echo ^\t\t\t# Save to output folder\necho ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho ^\t\texcept Exception as e:\necho ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho ^\telse:\necho ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n\techo Script created successfully: !TEMP_SCRIPT!\n) else (\n\techo ERROR: Failed to create script file!\n\tpause\n\texit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho ACTION FAKE USER REMOVAL REPORT\necho ========================================\necho.\n\nREM Track updated counts per file during parse\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n\tif \"%%a\"==\"SUCCESS\" (\n\t\tset /a processed+=1\n\t\techo [!processed!] SUCCESS: %%b", + "signatures": {}, + "detailedLines": [ + { + "text": "echo ^\t\t\t# Save to output folder", + "lineNumber": 65 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 66 + }, + { + "text": "echo ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 67 + }, + { + "text": "echo ^\t\texcept Exception as e:", + "lineNumber": 68 + }, + { + "text": "echo ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 69 + }, + { + "text": "echo ^\telse:", + "lineNumber": 70 + }, + { + "text": "echo ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 71 + }, + { + "text": "echo.", + "lineNumber": 72 + }, + { + "text": "echo print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)", + "lineNumber": 73 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 74 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 77 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 78 + }, + { + "text": "\techo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 79 + }, + { + "text": ") else (", + "lineNumber": 80 + }, + { + "text": "\techo ERROR: Failed to create script file!", + "lineNumber": 81 + }, + { + "text": "\tpause", + "lineNumber": 82 + }, + { + "text": "\texit /b 1", + "lineNumber": 83 + }, + { + "text": ")", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 86 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 87 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 88 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 89 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 92 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 93 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 94 + }, + { + "lineNumber": 95 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 96 + }, + { + "text": "echo.", + "lineNumber": 97 + }, + { + "text": "echo ========================================", + "lineNumber": 98 + }, + { + "text": "echo ACTION FAKE USER REMOVAL REPORT", + "lineNumber": 99 + }, + { + "text": "echo ========================================", + "lineNumber": 100 + }, + { + "text": "echo.", + "lineNumber": 101 + }, + { + "lineNumber": 102 + }, + { + "text": "REM Track updated counts per file during parse", + "lineNumber": 103 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 104 + }, + { + "text": "\tif \"%%a\"==\"SUCCESS\" (", + "lineNumber": 105 + }, + { + "text": "\t\tset /a processed+=1", + "lineNumber": 106 + }, + { + "text": "\t\techo [!processed!] SUCCESS: %%b", + "lineNumber": 107 + } + ] + }, + "score": 0.2552241086959839 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitignore", + "range": { + "startPosition": {}, + "endPosition": { + "line": 123, + "column": 7 + } + }, + "contents": "# Blender Studio Git Management - .gitignore\n\n# Blender auto-save files\n*.blend1\n*.blend2\n*.blend3\n*.blend4\n*.blend5\n*.blend6\n*.blend7\n*.blend8\n*.blend9\n*.blend10\n*.blend11\n*.blend12\n*.blend13\n*.blend14\n*.blend15\n*.blend16\n*.blend17\n*.blend18\n*.blend19\n*.blend20\n*.blend21\n*.blend22\n*.blend23\n*.blend24\n*.blend25\n*.blend26\n*.blend27\n*.blend28\n*.blend29\n*.blend30\n*.blend31\n*.blend32\n\n# Blender cache and temporary files\n*.swp\n*.swo\n*~\n*.tmp\n*.temp\n\n# OS generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\ndesktop.ini\n\n# Editor files\n.vscode/\n.idea/\n*.swp\n*.swo\n*~\n\n# Large media files (uncomment if you want to exclude rendered outputs)\n# *.mp4\n# *.avi\n# *.mov\n# *.png\n# *.jpg\n# *.jpeg\n# *.exr\n# *.hdr\n# *.tiff\n# *.tga\n\n# Audio files (uncomment if excluding)\n# *.wav\n# *.mp3\n# *.aac\n# *.ogg\n\n# Log files\n*.log\n\n# Python cache\n__pycache__/\n*.pyc\n*.pyo\n*.pyd\n.Python\n\n# Node modules (if using any web tools)\nnode_modules/\n\n# Environment files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Backup files\n*.bak\n*.backup\n*.old\n\n# Temporary files\ntemp/\ntmp/\ncache/ \n\nRenders/**\n!Renders/_zipped/\n!Renders/_zipped/**\n!Renders/**/\n!Renders/**/*.bat\n!Renders/**/*.log\nseq/\n\n# Premiere\n*.prlock\nAdobe Premiere Pro Video Previews/\nAdobe Premiere Pro Audio Previews/\nAdobe Premiere Pro (Beta) Video Previews/\nAdobe Premiere Pro (Beta) Audio Previews/\n\nPr/RnR/", + "signatures": {}, + "detailedLines": [ + { + "text": "# Blender Studio Git Management - .gitignore", + "lineNumber": 1 + }, + { + "lineNumber": 2 + }, + { + "text": "# Blender auto-save files", + "lineNumber": 3 + }, + { + "text": "*.blend1", + "lineNumber": 4 + }, + { + "text": "*.blend2", + "lineNumber": 5 + }, + { + "text": "*.blend3", + "lineNumber": 6 + }, + { + "text": "*.blend4", + "lineNumber": 7 + }, + { + "text": "*.blend5", + "lineNumber": 8 + }, + { + "text": "*.blend6", + "lineNumber": 9 + }, + { + "text": "*.blend7", + "lineNumber": 10 + }, + { + "text": "*.blend8", + "lineNumber": 11 + }, + { + "text": "*.blend9", + "lineNumber": 12 + }, + { + "text": "*.blend10", + "lineNumber": 13 + }, + { + "text": "*.blend11", + "lineNumber": 14 + }, + { + "text": "*.blend12", + "lineNumber": 15 + }, + { + "text": "*.blend13", + "lineNumber": 16 + }, + { + "text": "*.blend14", + "lineNumber": 17 + }, + { + "text": "*.blend15", + "lineNumber": 18 + }, + { + "text": "*.blend16", + "lineNumber": 19 + }, + { + "text": "*.blend17", + "lineNumber": 20 + }, + { + "text": "*.blend18", + "lineNumber": 21 + }, + { + "text": "*.blend19", + "lineNumber": 22 + }, + { + "text": "*.blend20", + "lineNumber": 23 + }, + { + "text": "*.blend21", + "lineNumber": 24 + }, + { + "text": "*.blend22", + "lineNumber": 25 + }, + { + "text": "*.blend23", + "lineNumber": 26 + }, + { + "text": "*.blend24", + "lineNumber": 27 + }, + { + "text": "*.blend25", + "lineNumber": 28 + }, + { + "text": "*.blend26", + "lineNumber": 29 + }, + { + "text": "*.blend27", + "lineNumber": 30 + }, + { + "text": "*.blend28", + "lineNumber": 31 + }, + { + "text": "*.blend29", + "lineNumber": 32 + }, + { + "text": "*.blend30", + "lineNumber": 33 + }, + { + "text": "*.blend31", + "lineNumber": 34 + }, + { + "text": "*.blend32", + "lineNumber": 35 + }, + { + "lineNumber": 36 + }, + { + "text": "# Blender cache and temporary files", + "lineNumber": 37 + }, + { + "text": "*.swp", + "lineNumber": 38 + }, + { + "text": "*.swo", + "lineNumber": 39 + }, + { + "text": "*~", + "lineNumber": 40 + }, + { + "text": "*.tmp", + "lineNumber": 41 + }, + { + "text": "*.temp", + "lineNumber": 42 + }, + { + "lineNumber": 43 + }, + { + "text": "# OS generated files", + "lineNumber": 44 + }, + { + "text": ".DS_Store", + "lineNumber": 45 + }, + { + "text": ".DS_Store?", + "lineNumber": 46 + }, + { + "text": "._*", + "lineNumber": 47 + }, + { + "text": ".Spotlight-V100", + "lineNumber": 48 + }, + { + "text": ".Trashes", + "lineNumber": 49 + }, + { + "text": "ehthumbs.db", + "lineNumber": 50 + }, + { + "text": "Thumbs.db", + "lineNumber": 51 + }, + { + "text": "desktop.ini", + "lineNumber": 52 + }, + { + "lineNumber": 53 + }, + { + "text": "# Editor files", + "lineNumber": 54 + }, + { + "text": ".vscode/", + "lineNumber": 55 + }, + { + "text": ".idea/", + "lineNumber": 56 + }, + { + "text": "*.swp", + "lineNumber": 57 + }, + { + "text": "*.swo", + "lineNumber": 58 + }, + { + "text": "*~", + "lineNumber": 59 + }, + { + "lineNumber": 60 + }, + { + "text": "# Large media files (uncomment if you want to exclude rendered outputs)", + "lineNumber": 61 + }, + { + "text": "# *.mp4", + "lineNumber": 62 + }, + { + "text": "# *.avi", + "lineNumber": 63 + }, + { + "text": "# *.mov", + "lineNumber": 64 + }, + { + "text": "# *.png", + "lineNumber": 65 + }, + { + "text": "# *.jpg", + "lineNumber": 66 + }, + { + "text": "# *.jpeg", + "lineNumber": 67 + }, + { + "text": "# *.exr", + "lineNumber": 68 + }, + { + "text": "# *.hdr", + "lineNumber": 69 + }, + { + "text": "# *.tiff", + "lineNumber": 70 + }, + { + "text": "# *.tga", + "lineNumber": 71 + }, + { + "lineNumber": 72 + }, + { + "text": "# Audio files (uncomment if excluding)", + "lineNumber": 73 + }, + { + "text": "# *.wav", + "lineNumber": 74 + }, + { + "text": "# *.mp3", + "lineNumber": 75 + }, + { + "text": "# *.aac", + "lineNumber": 76 + }, + { + "text": "# *.ogg", + "lineNumber": 77 + }, + { + "lineNumber": 78 + }, + { + "text": "# Log files", + "lineNumber": 79 + }, + { + "text": "*.log", + "lineNumber": 80 + }, + { + "lineNumber": 81 + }, + { + "text": "# Python cache", + "lineNumber": 82 + }, + { + "text": "__pycache__/", + "lineNumber": 83 + }, + { + "text": "*.pyc", + "lineNumber": 84 + }, + { + "text": "*.pyo", + "lineNumber": 85 + }, + { + "text": "*.pyd", + "lineNumber": 86 + }, + { + "text": ".Python", + "lineNumber": 87 + }, + { + "lineNumber": 88 + }, + { + "text": "# Node modules (if using any web tools)", + "lineNumber": 89 + }, + { + "text": "node_modules/", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": "# Environment files", + "lineNumber": 92 + }, + { + "text": ".env", + "lineNumber": 93 + }, + { + "text": ".env.local", + "lineNumber": 94 + }, + { + "text": ".env.development.local", + "lineNumber": 95 + }, + { + "text": ".env.test.local", + "lineNumber": 96 + }, + { + "text": ".env.production.local", + "lineNumber": 97 + }, + { + "lineNumber": 98 + }, + { + "text": "# Backup files", + "lineNumber": 99 + }, + { + "text": "*.bak", + "lineNumber": 100 + }, + { + "text": "*.backup", + "lineNumber": 101 + }, + { + "text": "*.old", + "lineNumber": 102 + }, + { + "lineNumber": 103 + }, + { + "text": "# Temporary files", + "lineNumber": 104 + }, + { + "text": "temp/", + "lineNumber": 105 + }, + { + "text": "tmp/", + "lineNumber": 106 + }, + { + "text": "cache/ ", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": "Renders/**", + "lineNumber": 109 + }, + { + "text": "!Renders/_zipped/", + "lineNumber": 110 + }, + { + "text": "!Renders/_zipped/**", + "lineNumber": 111 + }, + { + "text": "!Renders/**/", + "lineNumber": 112 + }, + { + "text": "!Renders/**/*.bat", + "lineNumber": 113 + }, + { + "text": "!Renders/**/*.log", + "lineNumber": 114 + }, + { + "text": "seq/", + "lineNumber": 115 + }, + { + "lineNumber": 116 + }, + { + "text": "# Premiere", + "lineNumber": 117 + }, + { + "text": "*.prlock", + "lineNumber": 118 + }, + { + "text": "Adobe Premiere Pro Video Previews/", + "lineNumber": 119 + }, + { + "text": "Adobe Premiere Pro Audio Previews/", + "lineNumber": 120 + }, + { + "text": "Adobe Premiere Pro (Beta) Video Previews/", + "lineNumber": 121 + }, + { + "text": "Adobe Premiere Pro (Beta) Audio Previews/", + "lineNumber": 122 + }, + { + "lineNumber": 123 + }, + { + "text": "Pr/RnR/", + "lineNumber": 124 + } + ] + }, + "score": 0.24031829833984375 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 66 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\n\necho Starting fake user removal for actions...\n\nREM Check if files were dragged onto the batch file\nif \"%~1\"==\"\" (\n\techo.\n\techo Usage: Drag .blend files onto this batch file to remove fake users from actions.\n\techo Output will be saved to the \\output folder.\n\techo.\n\tpause\n\texit /b 1\n)\n\nREM Create output directory if it doesn't exist\nif not exist \"output\" mkdir \"output\"\n\nREM Initialize counters and tracking\nset \"processed=0\"\nset \"crashed=0\"\nset \"skipped=0\"\nset \"deleted_similar=0\"\nset \"threshold_percent=10\"\nset \"updated_total=0\"\n\nREM Create a temporary Python script for processing\nset \"TEMP_SCRIPT=remove_action_fake_users_temp.py\"\necho Creating Python script: !TEMP_SCRIPT!\n\n(\necho import bpy\necho import os\necho import sys\necho.\necho # Create output directory if it doesn't exist\necho if not os.path.exists^(\"output\"^):\necho \tos.makedirs^(\"output\"^)\necho.\necho # Get command line arguments ^(the dragged files^)\necho blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []\necho.\necho print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to process\"^)\necho.\necho for blend_file in blend_files:\necho ^\tif blend_file.lower^(^).endswith^(\".blend\"^):\necho ^\t\ttry:\necho ^\t\t\t# Create output path in output folder\necho ^\t\t\tfilename = os.path.basename^(blend_file^)\necho ^\t\t\toutput_file = os.path.join^(\"output\", filename^)\necho ^\t\t\t# Check if output file already exists\necho ^\t\t\tif os.path.exists^(output_file^):\necho ^\t\t\t\tprint^(\"SKIP_EXISTING:\" + blend_file^)\necho ^\t\t\t\tcontinue\necho ^\t\t\tprint^(\"PROCESSING:\" + blend_file^)\necho ^\t\t\t# Load the blend file\necho ^\t\t\tbpy.ops.wm.open_mainfile^(filepath=blend_file^)\necho ^\t\t\t# Remove fake users from all actions\necho ^\t\t\tchanged = 0\necho ^\t\t\tfor action in bpy.data.actions:\necho ^\t\t\t\tif getattr^(action, \"use_fake_user\", False^):\necho ^\t\t\t\t\taction.use_fake_user = False\necho ^\t\t\t\t\tchanged += 1\necho ^\t\t\tprint^(\"UPDATED:\" + blend_file + \":\" + str^(changed^)^)\necho ^\t\t\t# Save to output folder\necho ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "echo Starting fake user removal for actions...", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "REM Check if files were dragged onto the batch file", + "lineNumber": 6 + }, + { + "text": "if \"%~1\"==\"\" (", + "lineNumber": 7 + }, + { + "text": "\techo.", + "lineNumber": 8 + }, + { + "text": "\techo Usage: Drag .blend files onto this batch file to remove fake users from actions.", + "lineNumber": 9 + }, + { + "text": "\techo Output will be saved to the \\output folder.", + "lineNumber": 10 + }, + { + "text": "\techo.", + "lineNumber": 11 + }, + { + "text": "\tpause", + "lineNumber": 12 + }, + { + "text": "\texit /b 1", + "lineNumber": 13 + }, + { + "text": ")", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "REM Create output directory if it doesn't exist", + "lineNumber": 16 + }, + { + "text": "if not exist \"output\" mkdir \"output\"", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "REM Initialize counters and tracking", + "lineNumber": 19 + }, + { + "text": "set \"processed=0\"", + "lineNumber": 20 + }, + { + "text": "set \"crashed=0\"", + "lineNumber": 21 + }, + { + "text": "set \"skipped=0\"", + "lineNumber": 22 + }, + { + "text": "set \"deleted_similar=0\"", + "lineNumber": 23 + }, + { + "text": "set \"threshold_percent=10\"", + "lineNumber": 24 + }, + { + "text": "set \"updated_total=0\"", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": "REM Create a temporary Python script for processing", + "lineNumber": 27 + }, + { + "text": "set \"TEMP_SCRIPT=remove_action_fake_users_temp.py\"", + "lineNumber": 28 + }, + { + "text": "echo Creating Python script: !TEMP_SCRIPT!", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": "(", + "lineNumber": 31 + }, + { + "text": "echo import bpy", + "lineNumber": 32 + }, + { + "text": "echo import os", + "lineNumber": 33 + }, + { + "text": "echo import sys", + "lineNumber": 34 + }, + { + "text": "echo.", + "lineNumber": 35 + }, + { + "text": "echo # Create output directory if it doesn't exist", + "lineNumber": 36 + }, + { + "text": "echo if not os.path.exists^(\"output\"^):", + "lineNumber": 37 + }, + { + "text": "echo \tos.makedirs^(\"output\"^)", + "lineNumber": 38 + }, + { + "text": "echo.", + "lineNumber": 39 + }, + { + "text": "echo # Get command line arguments ^(the dragged files^)", + "lineNumber": 40 + }, + { + "text": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []", + "lineNumber": 41 + }, + { + "text": "echo.", + "lineNumber": 42 + }, + { + "text": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to process\"^)", + "lineNumber": 43 + }, + { + "text": "echo.", + "lineNumber": 44 + }, + { + "text": "echo for blend_file in blend_files:", + "lineNumber": 45 + }, + { + "text": "echo ^\tif blend_file.lower^(^).endswith^(\".blend\"^):", + "lineNumber": 46 + }, + { + "text": "echo ^\t\ttry:", + "lineNumber": 47 + }, + { + "text": "echo ^\t\t\t# Create output path in output folder", + "lineNumber": 48 + }, + { + "text": "echo ^\t\t\tfilename = os.path.basename^(blend_file^)", + "lineNumber": 49 + }, + { + "text": "echo ^\t\t\toutput_file = os.path.join^(\"output\", filename^)", + "lineNumber": 50 + }, + { + "text": "echo ^\t\t\t# Check if output file already exists", + "lineNumber": 51 + }, + { + "text": "echo ^\t\t\tif os.path.exists^(output_file^):", + "lineNumber": 52 + }, + { + "text": "echo ^\t\t\t\tprint^(\"SKIP_EXISTING:\" + blend_file^)", + "lineNumber": 53 + }, + { + "text": "echo ^\t\t\t\tcontinue", + "lineNumber": 54 + }, + { + "text": "echo ^\t\t\tprint^(\"PROCESSING:\" + blend_file^)", + "lineNumber": 55 + }, + { + "text": "echo ^\t\t\t# Load the blend file", + "lineNumber": 56 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.open_mainfile^(filepath=blend_file^)", + "lineNumber": 57 + }, + { + "text": "echo ^\t\t\t# Remove fake users from all actions", + "lineNumber": 58 + }, + { + "text": "echo ^\t\t\tchanged = 0", + "lineNumber": 59 + }, + { + "text": "echo ^\t\t\tfor action in bpy.data.actions:", + "lineNumber": 60 + }, + { + "text": "echo ^\t\t\t\tif getattr^(action, \"use_fake_user\", False^):", + "lineNumber": 61 + }, + { + "text": "echo ^\t\t\t\t\taction.use_fake_user = False", + "lineNumber": 62 + }, + { + "text": "echo ^\t\t\t\t\tchanged += 1", + "lineNumber": 63 + }, + { + "text": "echo ^\t\t\tprint^(\"UPDATED:\" + blend_file + \":\" + str^(changed^)^)", + "lineNumber": 64 + }, + { + "text": "echo ^\t\t\t# Save to output folder", + "lineNumber": 65 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 66 + } + ] + }, + "score": 0.21328389644622803 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitattributes", + "range": { + "startPosition": {}, + "endPosition": { + "line": 55 + } + }, + "contents": "# Git LFS attributes configuration\n# All listed file types will be tracked by Git LFS\n\n# Video files\n*.avi filter=lfs diff=lfs merge=lfs -text\n*.mkv filter=lfs diff=lfs merge=lfs -text\n*.mov filter=lfs diff=lfs merge=lfs -text\n*.MOV filter=lfs diff=lfs merge=lfs -text\n*.mp4 filter=lfs diff=lfs merge=lfs -text\n*.webm filter=lfs diff=lfs merge=lfs -text\n\n# Image files\n*.bmp filter=lfs diff=lfs merge=lfs -text\n*.exr filter=lfs diff=lfs merge=lfs -text\n*.gif filter=lfs diff=lfs merge=lfs -text\n*.hdr filter=lfs diff=lfs merge=lfs -text\n*.jpg filter=lfs diff=lfs merge=lfs -text\n*.jpeg filter=lfs diff=lfs merge=lfs -text\n*.png filter=lfs diff=lfs merge=lfs -text\n*.tga filter=lfs diff=lfs merge=lfs -text\n*.tif filter=lfs diff=lfs merge=lfs -text\n*.tiff filter=lfs diff=lfs merge=lfs -text\n*.webp filter=lfs diff=lfs merge=lfs -text\n\n# 3D/CG files\n*.3ds filter=lfs diff=lfs merge=lfs -text\n*.abc filter=lfs diff=lfs merge=lfs -text\n*.blend filter=lfs diff=lfs merge=lfs -text\n*.blend1 filter=lfs diff=lfs merge=lfs -text\n*.blend2 filter=lfs diff=lfs merge=lfs -text\n*.bvh filter=lfs diff=lfs merge=lfs -text\n*.bvh.gz filter=lfs diff=lfs merge=lfs -text\n*.c4d filter=lfs diff=lfs merge=lfs -text\n*.dae filter=lfs diff=lfs merge=lfs -text\n*.fbx filter=lfs diff=lfs merge=lfs -text\n*.fbxkey filter=lfs diff=lfs merge=lfs -text\n*.ma filter=lfs diff=lfs merge=lfs -text\n*.max filter=lfs diff=lfs merge=lfs -text\n*.mb filter=lfs diff=lfs merge=lfs -text\n*.obj filter=lfs diff=lfs merge=lfs -text\n*.usd filter=lfs diff=lfs merge=lfs -text\n*.vdb filter=lfs diff=lfs merge=lfs -text\n*.bphys filter=lfs diff=lfs merge=lfs -text\n*.bobj filter=lfs diff=lfs merge=lfs -text\n*.bvel filter=lfs diff=lfs merge=lfs -text\n*.bpointcache filter=lfs diff=lfs merge=lfs -text\n*.blob filter=lfs diff=lfs merge=lfs -text\n*.unitypackage filter=lfs diff=lfs merge=lfs -text\n\n# Houdini files\n*.hiplc filter=lfs diff=lfs merge=lfs -text\n*.bgeo filter=lfs diff=lfs merge=lfs -text\n*.bgeo.sc filter=lfs diff=lfs merge=lfs -text\n\n# Flip Fluids cache files", + "signatures": {}, + "detailedLines": [ + { + "text": "# Git LFS attributes configuration", + "lineNumber": 1 + }, + { + "text": "# All listed file types will be tracked by Git LFS", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "# Video files", + "lineNumber": 4 + }, + { + "text": "*.avi filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 5 + }, + { + "text": "*.mkv filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 6 + }, + { + "text": "*.mov filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 7 + }, + { + "text": "*.MOV filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 8 + }, + { + "text": "*.mp4 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 9 + }, + { + "text": "*.webm filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "# Image files", + "lineNumber": 12 + }, + { + "text": "*.bmp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 13 + }, + { + "text": "*.exr filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 14 + }, + { + "text": "*.gif filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 15 + }, + { + "text": "*.hdr filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 16 + }, + { + "text": "*.jpg filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 17 + }, + { + "text": "*.jpeg filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 18 + }, + { + "text": "*.png filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 19 + }, + { + "text": "*.tga filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 20 + }, + { + "text": "*.tif filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 21 + }, + { + "text": "*.tiff filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 22 + }, + { + "text": "*.webp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": "# 3D/CG files", + "lineNumber": 25 + }, + { + "text": "*.3ds filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 26 + }, + { + "text": "*.abc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 27 + }, + { + "text": "*.blend filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 28 + }, + { + "text": "*.blend1 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 29 + }, + { + "text": "*.blend2 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 30 + }, + { + "text": "*.bvh filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 31 + }, + { + "text": "*.bvh.gz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 32 + }, + { + "text": "*.c4d filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 33 + }, + { + "text": "*.dae filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 34 + }, + { + "text": "*.fbx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 35 + }, + { + "text": "*.fbxkey filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 36 + }, + { + "text": "*.ma filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 37 + }, + { + "text": "*.max filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 38 + }, + { + "text": "*.mb filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 39 + }, + { + "text": "*.obj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 40 + }, + { + "text": "*.usd filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 41 + }, + { + "text": "*.vdb filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 42 + }, + { + "text": "*.bphys filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 43 + }, + { + "text": "*.bobj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 44 + }, + { + "text": "*.bvel filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 45 + }, + { + "text": "*.bpointcache filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 46 + }, + { + "text": "*.blob filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 47 + }, + { + "text": "*.unitypackage filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 48 + }, + { + "lineNumber": 49 + }, + { + "text": "# Houdini files", + "lineNumber": 50 + }, + { + "text": "*.hiplc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 51 + }, + { + "text": "*.bgeo filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 52 + }, + { + "text": "*.bgeo.sc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": "# Flip Fluids cache files", + "lineNumber": 55 + } + ] + }, + "score": 0.20849290490150452 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": {}, + "endPosition": { + "line": 106 + } + }, + "contents": "import bpy\nimport re\nimport os\n\ndef link_bsdf_materials():\n \"\"\"Link all materials from the BSDF library file\"\"\"\n \n library_path = r\"R:\\Creative\\artsy\\maya\\0 ProjectStructure\\1 BlenderAssets\\Amazon\\MATERIALS_BSDF_pallette_v1.0.blend\"\n \n if not os.path.exists(library_path):\n print(f\"Warning: Library file not found at {library_path}\")\n return []\n \n print(f\"Linking materials from: {library_path}\")\n \n # Get list of materials before linking\n materials_before = set(bpy.data.materials.keys())\n \n # Link all materials from the library file\n with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):\n # Link all materials\n data_to.materials = data_from.materials\n \n # Get list of newly linked materials\n materials_after = set(bpy.data.materials.keys())\n newly_linked = materials_after - materials_before\n \n print(f\"Linked {len(newly_linked)} materials from library\")\n for mat_name in sorted(newly_linked):\n print(f\" - {mat_name}\")\n \n return list(newly_linked)\n\ndef remap_appended_to_linked():\n \"\"\"Remap any appended BSDF materials to their linked counterparts\"\"\"", + "signatures": {}, + "detailedLines": [ + { + "text": "import bpy", + "lineNumber": 1 + }, + { + "text": "import re", + "lineNumber": 2 + }, + { + "text": "import os", + "lineNumber": 3 + }, + { + "lineNumber": 4 + }, + { + "text": "def link_bsdf_materials():", + "lineNumber": 5, + "isSignature": true + }, + { + "text": " \"\"\"Link all materials from the BSDF library file\"\"\"", + "lineNumber": 6 + }, + { + "text": " ", + "lineNumber": 7 + }, + { + "text": " library_path = r\"R:\\Creative\\artsy\\maya\\0 ProjectStructure\\1 BlenderAssets\\Amazon\\MATERIALS_BSDF_pallette_v1.0.blend\"", + "lineNumber": 8 + }, + { + "text": " ", + "lineNumber": 9 + }, + { + "text": " if not os.path.exists(library_path):", + "lineNumber": 10 + }, + { + "text": " print(f\"Warning: Library file not found at {library_path}\")", + "lineNumber": 11 + }, + { + "text": " return []", + "lineNumber": 12 + }, + { + "text": " ", + "lineNumber": 13 + }, + { + "text": " print(f\"Linking materials from: {library_path}\")", + "lineNumber": 14 + }, + { + "text": " ", + "lineNumber": 15 + }, + { + "text": " # Get list of materials before linking", + "lineNumber": 16 + }, + { + "text": " materials_before = set(bpy.data.materials.keys())", + "lineNumber": 17 + }, + { + "text": " ", + "lineNumber": 18 + }, + { + "text": " # Link all materials from the library file", + "lineNumber": 19 + }, + { + "text": " with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):", + "lineNumber": 20 + }, + { + "text": " # Link all materials", + "lineNumber": 21 + }, + { + "text": " data_to.materials = data_from.materials", + "lineNumber": 22 + }, + { + "text": " ", + "lineNumber": 23 + }, + { + "text": " # Get list of newly linked materials", + "lineNumber": 24 + }, + { + "text": " materials_after = set(bpy.data.materials.keys())", + "lineNumber": 25 + }, + { + "text": " newly_linked = materials_after - materials_before", + "lineNumber": 26 + }, + { + "text": " ", + "lineNumber": 27 + }, + { + "text": " print(f\"Linked {len(newly_linked)} materials from library\")", + "lineNumber": 28 + }, + { + "text": " for mat_name in sorted(newly_linked):", + "lineNumber": 29 + }, + { + "text": " print(f\" - {mat_name}\")", + "lineNumber": 30 + }, + { + "text": " ", + "lineNumber": 31 + }, + { + "text": " return list(newly_linked)", + "lineNumber": 32, + "isSignature": true + }, + { + "lineNumber": 33 + }, + { + "text": "def remap_appended_to_linked():", + "lineNumber": 34, + "isSignature": true + }, + { + "text": " \"\"\"Remap any appended BSDF materials to their linked counterparts\"\"\"", + "lineNumber": 35 + } + ] + }, + "score": 0.18834909796714783 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 115 + }, + "endPosition": { + "line": 172, + "column": 6 + } + }, + "contents": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1\n set \"original_file=%%b\"\n set \"compressed_file=%%c\"\n \n REM Get file sizes\n for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"\n for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"\n \n echo [!processed!] !original_file!\n echo Original: !original_size! bytes\n echo Compressed: !compressed_size! bytes\n set /a \"savings=!original_size! - !compressed_size!\"\n if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)\n echo Savings: !savings! bytes\n echo Reduction: !percent!%%\n if !percent! lss !threshold_percent! (\n del \"!compressed_file!\" 2>nul\n set /a deleted_similar+=1\n echo Deleted: too similar to original\n )\n echo.\n ) else if \"%%a\"==\"CRASH\" (\n set /a crashed+=1\n echo [!crashed!] CRASHED: %%b\n echo Error: %%c\n echo.\n ) else if \"%%a\"==\"SKIP_EXISTING\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already exists^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(not .blend^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already compressed^): %%b\n echo.\n )\n)\n\nREM Clean up output file\ndel /f /q \"blender_output.txt\" 2>nul\n\necho ========================================\necho SUMMARY\necho ========================================\necho Processed: !processed! files\necho Crashed: !crashed! files\necho Skipped: !skipped! files\necho Deleted ^(too similar^): !deleted_similar! files\necho Total: %* files\necho ========================================\necho.\necho Done!\npause ", + "signatures": {}, + "detailedLines": [ + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 116 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 117 + }, + { + "text": " set /a processed+=1", + "lineNumber": 118 + }, + { + "text": " set \"original_file=%%b\"", + "lineNumber": 119 + }, + { + "text": " set \"compressed_file=%%c\"", + "lineNumber": 120 + }, + { + "text": " ", + "lineNumber": 121 + }, + { + "text": " REM Get file sizes", + "lineNumber": 122 + }, + { + "text": " for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"", + "lineNumber": 123 + }, + { + "text": " for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"", + "lineNumber": 124 + }, + { + "text": " ", + "lineNumber": 125 + }, + { + "text": " echo [!processed!] !original_file!", + "lineNumber": 126 + }, + { + "text": " echo Original: !original_size! bytes", + "lineNumber": 127 + }, + { + "text": " echo Compressed: !compressed_size! bytes", + "lineNumber": 128 + }, + { + "text": " set /a \"savings=!original_size! - !compressed_size!\"", + "lineNumber": 129 + }, + { + "text": " if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)", + "lineNumber": 130 + }, + { + "text": " echo Savings: !savings! bytes", + "lineNumber": 131 + }, + { + "text": " echo Reduction: !percent!%%", + "lineNumber": 132 + }, + { + "text": " if !percent! lss !threshold_percent! (", + "lineNumber": 133 + }, + { + "text": " del \"!compressed_file!\" 2>nul", + "lineNumber": 134 + }, + { + "text": " set /a deleted_similar+=1", + "lineNumber": 135 + }, + { + "text": " echo Deleted: too similar to original", + "lineNumber": 136 + }, + { + "text": " )", + "lineNumber": 137 + }, + { + "text": " echo.", + "lineNumber": 138 + }, + { + "text": " ) else if \"%%a\"==\"CRASH\" (", + "lineNumber": 139 + }, + { + "text": " set /a crashed+=1", + "lineNumber": 140 + }, + { + "text": " echo [!crashed!] CRASHED: %%b", + "lineNumber": 141 + }, + { + "text": " echo Error: %%c", + "lineNumber": 142 + }, + { + "text": " echo.", + "lineNumber": 143 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_EXISTING\" (", + "lineNumber": 144 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 145 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already exists^): %%b", + "lineNumber": 146 + }, + { + "text": " echo.", + "lineNumber": 147 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (", + "lineNumber": 148 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 149 + }, + { + "text": " echo [!skipped!] SKIPPED ^(not .blend^): %%b", + "lineNumber": 150 + }, + { + "text": " echo.", + "lineNumber": 151 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (", + "lineNumber": 152 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 153 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already compressed^): %%b", + "lineNumber": 154 + }, + { + "text": " echo.", + "lineNumber": 155 + }, + { + "text": " )", + "lineNumber": 156 + }, + { + "text": ")", + "lineNumber": 157 + }, + { + "lineNumber": 158 + }, + { + "text": "REM Clean up output file", + "lineNumber": 159 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 160 + }, + { + "lineNumber": 161 + }, + { + "text": "echo ========================================", + "lineNumber": 162 + }, + { + "text": "echo SUMMARY", + "lineNumber": 163 + }, + { + "text": "echo ========================================", + "lineNumber": 164 + }, + { + "text": "echo Processed: !processed! files", + "lineNumber": 165 + }, + { + "text": "echo Crashed: !crashed! files", + "lineNumber": 166 + }, + { + "text": "echo Skipped: !skipped! files", + "lineNumber": 167 + }, + { + "text": "echo Deleted ^(too similar^): !deleted_similar! files", + "lineNumber": 168 + }, + { + "text": "echo Total: %* files", + "lineNumber": 169 + }, + { + "text": "echo ========================================", + "lineNumber": 170 + }, + { + "text": "echo.", + "lineNumber": 171 + }, + { + "text": "echo Done!", + "lineNumber": 172 + }, + { + "text": "pause ", + "lineNumber": 173 + } + ] + }, + "score": 0.18653225898742676 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": {}, + "endPosition": { + "line": 102 + } + }, + "contents": "#!/usr/bin/env python3\n\"\"\"Maintain zipped render sequences for Git hooks.\n\nDefault mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,\nand stages any updated archives so commits only track compact files. Switch to\n`--mode expand` to inflate the tracked archives back into the ignored working\ndirectories after checkouts or pulls.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport platform\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport traceback\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom pathlib import Path\nfrom typing import Iterator, Sequence\n\n# Try to import psutil for cross-platform RAM detection\ntry:\n import psutil\n HAS_PSUTIL = True\nexcept ImportError:\n HAS_PSUTIL = False\n # For Windows fallback\n if platform.system() == \"Windows\":\n try:\n import ctypes\n HAS_CTYPES = True\n except ImportError:\n HAS_CTYPES = False\n else:\n HAS_CTYPES = False\n\n\nRENDER_ROOT = Path(\"Renders\")\nARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"\nSEQUENCE_EXTENSIONS = {\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".tif\",\n \".tiff\",\n \".exr\",\n}\nSTATE_SUFFIX = \".meta.json\"\nDEFAULT_CONFIG = {\n \"zipper\": \"7z\",\n \"compression\": 9,\n \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate\n \"dailyFormat\": \"daily_YYMMDD\",\n \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)\n}\n\n\ndef log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:\n if verbose_only and not verbose:\n return\n print(f\"[{mode}] {message}\", flush=True)\n\n\ndef load_config() -> dict:\n # First try to load from project's .config folder (current working directory)\n # Then fall back to ProjectStructure repo config (next to zip_sequences.py)\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "#!/usr/bin/env python3", + "lineNumber": 1 + }, + { + "text": "\"\"\"Maintain zipped render sequences for Git hooks.", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "Default mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,", + "lineNumber": 4 + }, + { + "text": "and stages any updated archives so commits only track compact files. Switch to", + "lineNumber": 5 + }, + { + "text": "`--mode expand` to inflate the tracked archives back into the ignored working", + "lineNumber": 6 + }, + { + "text": "directories after checkouts or pulls.", + "lineNumber": 7 + }, + { + "text": "\"\"\"", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": "from __future__ import annotations", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "import argparse", + "lineNumber": 12 + }, + { + "text": "import json", + "lineNumber": 13 + }, + { + "text": "import os", + "lineNumber": 14 + }, + { + "text": "import platform", + "lineNumber": 15 + }, + { + "text": "import shutil", + "lineNumber": 16 + }, + { + "text": "import subprocess", + "lineNumber": 17 + }, + { + "text": "import sys", + "lineNumber": 18 + }, + { + "text": "import tempfile", + "lineNumber": 19 + }, + { + "text": "import time", + "lineNumber": 20 + }, + { + "text": "import traceback", + "lineNumber": 21 + }, + { + "text": "from concurrent.futures import ThreadPoolExecutor, as_completed", + "lineNumber": 22 + }, + { + "text": "from pathlib import Path", + "lineNumber": 23 + }, + { + "text": "from typing import Iterator, Sequence", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "# Try to import psutil for cross-platform RAM detection", + "lineNumber": 26 + }, + { + "text": "try:", + "lineNumber": 27 + }, + { + "text": " import psutil", + "lineNumber": 28 + }, + { + "text": " HAS_PSUTIL = True", + "lineNumber": 29 + }, + { + "text": "except ImportError:", + "lineNumber": 30 + }, + { + "text": " HAS_PSUTIL = False", + "lineNumber": 31 + }, + { + "text": " # For Windows fallback", + "lineNumber": 32 + }, + { + "text": " if platform.system() == \"Windows\":", + "lineNumber": 33 + }, + { + "text": " try:", + "lineNumber": 34 + }, + { + "text": " import ctypes", + "lineNumber": 35 + }, + { + "text": " HAS_CTYPES = True", + "lineNumber": 36 + }, + { + "text": " except ImportError:", + "lineNumber": 37 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 38 + }, + { + "text": " else:", + "lineNumber": 39 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": "RENDER_ROOT = Path(\"Renders\")", + "lineNumber": 43 + }, + { + "text": "ARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"", + "lineNumber": 44 + }, + { + "text": "SEQUENCE_EXTENSIONS = {", + "lineNumber": 45 + }, + { + "text": " \".png\",", + "lineNumber": 46 + }, + { + "text": " \".jpg\",", + "lineNumber": 47 + }, + { + "text": " \".jpeg\",", + "lineNumber": 48 + }, + { + "text": " \".tif\",", + "lineNumber": 49 + }, + { + "text": " \".tiff\",", + "lineNumber": 50 + }, + { + "text": " \".exr\",", + "lineNumber": 51 + }, + { + "text": "}", + "lineNumber": 52 + }, + { + "text": "STATE_SUFFIX = \".meta.json\"", + "lineNumber": 53 + }, + { + "text": "DEFAULT_CONFIG = {", + "lineNumber": 54 + }, + { + "text": " \"zipper\": \"7z\",", + "lineNumber": 55 + }, + { + "text": " \"compression\": 9,", + "lineNumber": 56 + }, + { + "text": " \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate", + "lineNumber": 57 + }, + { + "text": " \"dailyFormat\": \"daily_YYMMDD\",", + "lineNumber": 58 + }, + { + "text": " \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)", + "lineNumber": 59 + }, + { + "text": "}", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "lineNumber": 62 + }, + { + "text": "def log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:", + "lineNumber": 63, + "isSignature": true + }, + { + "text": " if verbose_only and not verbose:", + "lineNumber": 64 + }, + { + "text": " return", + "lineNumber": 65 + }, + { + "text": " print(f\"[{mode}] {message}\", flush=True)", + "lineNumber": 66, + "isSignature": true + }, + { + "lineNumber": 67 + }, + { + "lineNumber": 68 + }, + { + "text": "def load_config() -> dict:", + "lineNumber": 69, + "isSignature": true + }, + { + "text": " # First try to load from project's .config folder (current working directory)", + "lineNumber": 70, + "isSignature": true + }, + { + "text": " # Then fall back to ProjectStructure repo config (next to zip_sequences.py)", + "lineNumber": 71, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 72 + } + ] + }, + "score": 0.17492960393428802 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 557, + "column": 33 + }, + "endPosition": { + "line": 638 + } + }, + "contents": "def load_state(state_path: Path) -> dict | None:\n if not state_path.exists():\n return None\n try:\n state = json.loads(state_path.read_text())\n # Normalize timestamps in loaded state to ensure consistency\n # This handles state files created before normalization was added\n is_windows = platform.system() == \"Windows\"\n if is_windows and \"files\" in state:\n for entry in state.get(\"files\", []):\n if \"mtime_ns\" in entry:\n # Normalize to 100-nanosecond boundaries (NTFS precision)\n entry[\"mtime_ns\"] = (entry[\"mtime_ns\"] // 100) * 100\n return state\n except json.JSONDecodeError:\n return None\n\n\ndef state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "def load_state(state_path: Path) -> dict | None:", + "lineNumber": 561, + "isSignature": true + }, + { + "text": " if not state_path.exists():", + "lineNumber": 562 + }, + { + "text": " return None", + "lineNumber": 563 + }, + { + "text": " try:", + "lineNumber": 564 + }, + { + "text": " state = json.loads(state_path.read_text())", + "lineNumber": 565 + }, + { + "text": " # Normalize timestamps in loaded state to ensure consistency", + "lineNumber": 566 + }, + { + "text": " # This handles state files created before normalization was added", + "lineNumber": 567 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 568 + }, + { + "text": " if is_windows and \"files\" in state:", + "lineNumber": 569 + }, + { + "text": " for entry in state.get(\"files\", []):", + "lineNumber": 570 + }, + { + "text": " if \"mtime_ns\" in entry:", + "lineNumber": 571 + }, + { + "text": " # Normalize to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 572 + }, + { + "text": " entry[\"mtime_ns\"] = (entry[\"mtime_ns\"] // 100) * 100", + "lineNumber": 573 + }, + { + "text": " return state", + "lineNumber": 574 + }, + { + "text": " except json.JSONDecodeError:", + "lineNumber": 575 + }, + { + "text": " return None", + "lineNumber": 576, + "isSignature": true + }, + { + "lineNumber": 577 + }, + { + "lineNumber": 578 + }, + { + "text": "def state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:", + "lineNumber": 579, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 580 + } + ] + }, + "score": 0.17217624187469482 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\components\\gitattributes", + "range": { + "startPosition": { + "line": 51 + }, + "endPosition": { + "line": 110, + "column": 40 + } + }, + "contents": "*.bgeo filter=lfs diff=lfs merge=lfs -text\n*.bgeo.sc filter=lfs diff=lfs merge=lfs -text\n\n# Flip Fluids cache files\n*.data filter=lfs diff=lfs merge=lfs -text\n*.sqlite3 filter=lfs diff=lfs merge=lfs -text\n*.ffp3 filter=lfs diff=lfs merge=lfs -text\n\n# Substance files\n*.sbs filter=lfs diff=lfs merge=lfs -text\n*.sbsar filter=lfs diff=lfs merge=lfs -text\n*.spp filter=lfs diff=lfs merge=lfs -text\n\n# Audio files\n*.mp3 filter=lfs diff=lfs merge=lfs -text\n*.wav filter=lfs diff=lfs merge=lfs -text\n\n# Archive/Compression files\n*.7z filter=lfs diff=lfs merge=lfs -text\n*.bz2 filter=lfs diff=lfs merge=lfs -text\n*.gz filter=lfs diff=lfs merge=lfs -text\n*.rar filter=lfs diff=lfs merge=lfs -text\n*.tar filter=lfs diff=lfs merge=lfs -text\n*.zip filter=lfs diff=lfs merge=lfs -text\n\n# Documents\n*.docx filter=lfs diff=lfs merge=lfs -text\n*.xlsx filter=lfs diff=lfs merge=lfs -text\n*.eps filter=lfs diff=lfs merge=lfs -text\n*.pdf filter=lfs diff=lfs merge=lfs -text\n\n# Adobe files\n*.aegraphic filter=lfs diff=lfs merge=lfs -text\n*.aep filter=lfs diff=lfs merge=lfs -text\n*.prel filter=lfs diff=lfs merge=lfs -text\n*.prin filter=lfs diff=lfs merge=lfs -text\n*.prmf filter=lfs diff=lfs merge=lfs -text\n*.prproj filter=lfs diff=lfs merge=lfs -text\n*.ai filter=lfs diff=lfs merge=lfs -text\n*.psd filter=lfs diff=lfs merge=lfs -text\n\n# Davinci Resolve files\n*.dpx filter=lfs diff=lfs merge=lfs -text\n*.drp filter=lfs diff=lfs merge=lfs -text\n\n# Data files\n*.h5 filter=lfs diff=lfs merge=lfs -text\n*.hdf5 filter=lfs diff=lfs merge=lfs -text\n\n# Other\n*.uni filter=lfs diff=lfs merge=lfs -text\n*.pdn filter=lfs diff=lfs merge=lfs -text\n*.pur filter=lfs diff=lfs merge=lfs -text\n\n# Python files\n*.npz filter=lfs diff=lfs merge=lfs -text\n\n# Adobe Alternatives\n*.xcf filter=lfs diff=lfs merge=lfs -text\n*.af filter=lfs diff=lfs merge=lfs -text", + "signatures": {}, + "detailedLines": [ + { + "text": "*.bgeo filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 52 + }, + { + "text": "*.bgeo.sc filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": "# Flip Fluids cache files", + "lineNumber": 55 + }, + { + "text": "*.data filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 56 + }, + { + "text": "*.sqlite3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 57 + }, + { + "text": "*.ffp3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 58 + }, + { + "lineNumber": 59 + }, + { + "text": "# Substance files", + "lineNumber": 60 + }, + { + "text": "*.sbs filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 61 + }, + { + "text": "*.sbsar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 62 + }, + { + "text": "*.spp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 63 + }, + { + "lineNumber": 64 + }, + { + "text": "# Audio files", + "lineNumber": 65 + }, + { + "text": "*.mp3 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 66 + }, + { + "text": "*.wav filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 67 + }, + { + "lineNumber": 68 + }, + { + "text": "# Archive/Compression files", + "lineNumber": 69 + }, + { + "text": "*.7z filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 70 + }, + { + "text": "*.bz2 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 71 + }, + { + "text": "*.gz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 72 + }, + { + "text": "*.rar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 73 + }, + { + "text": "*.tar filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 74 + }, + { + "text": "*.zip filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "# Documents", + "lineNumber": 77 + }, + { + "text": "*.docx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 78 + }, + { + "text": "*.xlsx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 79 + }, + { + "text": "*.eps filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 80 + }, + { + "text": "*.pdf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 81 + }, + { + "lineNumber": 82 + }, + { + "text": "# Adobe files", + "lineNumber": 83 + }, + { + "text": "*.aegraphic filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 84 + }, + { + "text": "*.aep filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 85 + }, + { + "text": "*.prel filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 86 + }, + { + "text": "*.prin filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 87 + }, + { + "text": "*.prmf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 88 + }, + { + "text": "*.prproj filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 89 + }, + { + "text": "*.ai filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 90 + }, + { + "text": "*.psd filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 91 + }, + { + "lineNumber": 92 + }, + { + "text": "# Davinci Resolve files", + "lineNumber": 93 + }, + { + "text": "*.dpx filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 94 + }, + { + "text": "*.drp filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 95 + }, + { + "lineNumber": 96 + }, + { + "text": "# Data files", + "lineNumber": 97 + }, + { + "text": "*.h5 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 98 + }, + { + "text": "*.hdf5 filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 99 + }, + { + "lineNumber": 100 + }, + { + "text": "# Other", + "lineNumber": 101 + }, + { + "text": "*.uni filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 102 + }, + { + "text": "*.pdn filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 103 + }, + { + "text": "*.pur filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 104 + }, + { + "lineNumber": 105 + }, + { + "text": "# Python files", + "lineNumber": 106 + }, + { + "text": "*.npz filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 107 + }, + { + "lineNumber": 108 + }, + { + "text": "# Adobe Alternatives", + "lineNumber": 109 + }, + { + "text": "*.xcf filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 110 + }, + { + "text": "*.af filter=lfs diff=lfs merge=lfs -text", + "lineNumber": 111 + } + ] + }, + "score": 0.16746710240840912 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": { + "line": 108 + }, + "endPosition": { + "line": 186 + } + }, + "contents": "def remap_missing_datablocks():\n missing_mat in missing_materials:\n base_name = missing_mat.name.split(\".blend\")[0] if \".blend\" in missing_mat.name else missing_mat.name\n base_name = base_name.split(\".\")[0] if \".\" in base_name else base_name\n \n # Look for a replacement material\n replacement_found = False\n \n # First, try to find a linked material with the same name from the current library\n for mat in materials:\n if (mat.library is not None and \n mat.library.filepath and \n os.path.exists(bpy.path.abspath(mat.library.filepath)) and\n mat.name == missing_mat.name):\n \n if missing_mat.users > 0:\n print(f\"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}\")\n missing_mat.user_remap(mat)\n remapping_count += 1\n replacement_found = True\n break\n \n # If no exact match, try to find a BSDF material with similar name\n if not replacement_found and (missing_mat.name.startswith(\"BSDF_\") or \"BSDF_\" in missing_mat.name):\n for mat in materials:\n if (mat.library is not None and \n mat.library.filepath and \n os.path.exists(bpy.path.abspath(mat.library.filepath)) and\n mat.name.startswith(\"BSDF_\") and\n (mat.name == base_name or \n mat.name.startswith(base_name + \".\") or\n base_name in mat.name)):\n \n if missing_mat.users > 0:\n print(f\"Remapping missing BSDF {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}\")\n missing_mat.user_remap(mat)\n remapping_count += 1\n replacement_found = True\n break\n \n # If still no replacement, try to find any valid linked material with the same base name", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 109, + "column": 1 + }, + "endPosition": { + "line": 110, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def remap_missing_datablocks():", + "lineNumber": 109, + "isSignature": true + }, + { + "text": " missing_mat in missing_materials:", + "lineNumber": 129 + }, + { + "text": " base_name = missing_mat.name.split(\".blend\")[0] if \".blend\" in missing_mat.name else missing_mat.name", + "lineNumber": 130 + }, + { + "text": " base_name = base_name.split(\".\")[0] if \".\" in base_name else base_name", + "lineNumber": 131 + }, + { + "text": " ", + "lineNumber": 132 + }, + { + "text": " # Look for a replacement material", + "lineNumber": 133 + }, + { + "text": " replacement_found = False", + "lineNumber": 134 + }, + { + "text": " ", + "lineNumber": 135 + }, + { + "text": " # First, try to find a linked material with the same name from the current library", + "lineNumber": 136 + }, + { + "text": " for mat in materials:", + "lineNumber": 137 + }, + { + "text": " if (mat.library is not None and ", + "lineNumber": 138 + }, + { + "text": " mat.library.filepath and ", + "lineNumber": 139 + }, + { + "text": " os.path.exists(bpy.path.abspath(mat.library.filepath)) and", + "lineNumber": 140 + }, + { + "text": " mat.name == missing_mat.name):", + "lineNumber": 141 + }, + { + "text": " ", + "lineNumber": 142 + }, + { + "text": " if missing_mat.users > 0:", + "lineNumber": 143 + }, + { + "text": " print(f\"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}\")", + "lineNumber": 144 + }, + { + "text": " missing_mat.user_remap(mat)", + "lineNumber": 145 + }, + { + "text": " remapping_count += 1", + "lineNumber": 146 + }, + { + "text": " replacement_found = True", + "lineNumber": 147 + }, + { + "text": " break", + "lineNumber": 148 + }, + { + "text": " ", + "lineNumber": 149 + }, + { + "text": " # If no exact match, try to find a BSDF material with similar name", + "lineNumber": 150 + }, + { + "text": " if not replacement_found and (missing_mat.name.startswith(\"BSDF_\") or \"BSDF_\" in missing_mat.name):", + "lineNumber": 151 + }, + { + "text": " for mat in materials:", + "lineNumber": 152 + }, + { + "text": " if (mat.library is not None and ", + "lineNumber": 153 + }, + { + "text": " mat.library.filepath and ", + "lineNumber": 154 + }, + { + "text": " os.path.exists(bpy.path.abspath(mat.library.filepath)) and", + "lineNumber": 155 + }, + { + "text": " mat.name.startswith(\"BSDF_\") and", + "lineNumber": 156 + }, + { + "text": " (mat.name == base_name or ", + "lineNumber": 157 + }, + { + "text": " mat.name.startswith(base_name + \".\") or", + "lineNumber": 158 + }, + { + "text": " base_name in mat.name)):", + "lineNumber": 159 + }, + { + "text": " ", + "lineNumber": 160 + }, + { + "text": " if missing_mat.users > 0:", + "lineNumber": 161 + }, + { + "text": " print(f\"Remapping missing BSDF {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}\")", + "lineNumber": 162 + }, + { + "text": " missing_mat.user_remap(mat)", + "lineNumber": 163 + }, + { + "text": " remapping_count += 1", + "lineNumber": 164 + }, + { + "text": " replacement_found = True", + "lineNumber": 165 + }, + { + "text": " break", + "lineNumber": 166 + }, + { + "text": " ", + "lineNumber": 167 + }, + { + "text": " # If still no replacement, try to find any valid linked material with the same base name", + "lineNumber": 168 + } + ] + }, + "score": 0.16543453931808472 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n if COMPRESSION_METHOD == \"PPMd\":\n # PPMd is single-threaded, so -mmt won't help\n # But we can still set it for consistency\n cmd.append(\"-mmt=on\")\n elif worker_count == 1:\n # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)\n cmd.append(f\"-mmt={cpu_cores}\")\n else:\n # Multiple workers: use auto mode (7z will manage threads)\n cmd.append(\"-mmt=on\")\n \n cmd.extend([\n str(temp_zip_abs),\n f\"@{list_file_abs}\",\n ])\n \n # Log the command in verbose mode for debugging\n if verbose:\n cmd_str = \" \".join(cmd)\n log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)\n \n result = subprocess.run(\n cmd,\n cwd=seq_dir,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z compression failed: {error_msg}\")\n \n # Move temp zip to final location, replacing any existing file\n if zip_path.exists():\n zip_path.unlink()\n temp_zip.replace(zip_path)\n temp_zip = None # Mark as moved so we don't delete it\n finally:\n # Clean up temp zip if it wasn't moved", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 760 + }, + { + "text": " if COMPRESSION_METHOD == \"PPMd\":", + "lineNumber": 761 + }, + { + "text": " # PPMd is single-threaded, so -mmt won't help", + "lineNumber": 762 + }, + { + "text": " # But we can still set it for consistency", + "lineNumber": 763 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 764 + }, + { + "text": " elif worker_count == 1:", + "lineNumber": 765 + }, + { + "text": " # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)", + "lineNumber": 766 + }, + { + "text": " cmd.append(f\"-mmt={cpu_cores}\")", + "lineNumber": 767 + }, + { + "text": " else:", + "lineNumber": 768 + }, + { + "text": " # Multiple workers: use auto mode (7z will manage threads)", + "lineNumber": 769 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 770 + }, + { + "text": " ", + "lineNumber": 771 + }, + { + "text": " cmd.extend([", + "lineNumber": 772 + }, + { + "text": " str(temp_zip_abs),", + "lineNumber": 773 + }, + { + "text": " f\"@{list_file_abs}\",", + "lineNumber": 774 + }, + { + "text": " ])", + "lineNumber": 775 + }, + { + "text": " ", + "lineNumber": 776 + }, + { + "text": " # Log the command in verbose mode for debugging", + "lineNumber": 777 + }, + { + "text": " if verbose:", + "lineNumber": 778 + }, + { + "text": " cmd_str = \" \".join(cmd)", + "lineNumber": 779 + }, + { + "text": " log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)", + "lineNumber": 780 + }, + { + "text": " ", + "lineNumber": 781 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 782 + }, + { + "text": " cmd,", + "lineNumber": 783 + }, + { + "text": " cwd=seq_dir,", + "lineNumber": 784 + }, + { + "text": " check=False,", + "lineNumber": 785 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 786 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 787 + }, + { + "text": " text=True,", + "lineNumber": 788 + }, + { + "text": " )", + "lineNumber": 789 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 790 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 791 + }, + { + "text": " if result.stdout:", + "lineNumber": 792 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 793 + }, + { + "text": " raise RuntimeError(f\"7z compression failed: {error_msg}\")", + "lineNumber": 794 + }, + { + "text": " ", + "lineNumber": 795 + }, + { + "text": " # Move temp zip to final location, replacing any existing file", + "lineNumber": 796 + }, + { + "text": " if zip_path.exists():", + "lineNumber": 797 + }, + { + "text": " zip_path.unlink()", + "lineNumber": 798 + }, + { + "text": " temp_zip.replace(zip_path)", + "lineNumber": 799 + }, + { + "text": " temp_zip = None # Mark as moved so we don't delete it", + "lineNumber": 800 + }, + { + "text": " finally:", + "lineNumber": 801 + }, + { + "text": " # Clean up temp zip if it wasn't moved", + "lineNumber": 802 + } + ] + }, + "score": 0.164682999253273 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n try:\n # Create temporary archive file path (but don't create the file - let 7z create it)\n temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)\n temp_zip = Path(temp_zip_path)\n \n # Create list file with absolute path\n fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)\n list_file_path = Path(temp_path)\n with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:\n for rel_path in file_list:\n list_file.write(rel_path + \"\\n\")\n list_file.flush()\n os.fsync(list_file.fileno()) # Ensure data is written to disk\n # File is closed here by context manager, small delay to ensure OS releases handle\n time.sleep(0.1)\n \n # Use absolute paths for both list file and temp zip\n list_file_abs = list_file_path.resolve()\n temp_zip_abs = temp_zip.resolve()\n # Create archive in temp location first (7z will create it fresh)\n cmd = [\n SEVEN_Z_EXE,\n \"a\",\n \"-y\",\n \"-bb0\", # Suppress progress output\n f\"-mx={COMPRESSION_LEVEL}\",\n \"-t7z\", # Use 7z format, not zip\n ]\n \n # Set compression method and memory/dictionary size based on method\n # At compression level 0, use Copy (store) method for maximum speed\n FIXED_DICT_SIZE_MB = 1024\n if COMPRESSION_LEVEL == 0:\n # Level 0 = no compression, just store files (fastest)\n cmd.append(\"-m0=Copy\")", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 697 + }, + { + "text": " try:", + "lineNumber": 698 + }, + { + "text": " # Create temporary archive file path (but don't create the file - let 7z create it)", + "lineNumber": 699 + }, + { + "text": " temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)", + "lineNumber": 700 + }, + { + "text": " temp_zip = Path(temp_zip_path)", + "lineNumber": 701 + }, + { + "text": " ", + "lineNumber": 702 + }, + { + "text": " # Create list file with absolute path", + "lineNumber": 703 + }, + { + "text": " fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)", + "lineNumber": 704 + }, + { + "text": " list_file_path = Path(temp_path)", + "lineNumber": 705 + }, + { + "text": " with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:", + "lineNumber": 706 + }, + { + "text": " for rel_path in file_list:", + "lineNumber": 707 + }, + { + "text": " list_file.write(rel_path + \"\\n\")", + "lineNumber": 708 + }, + { + "text": " list_file.flush()", + "lineNumber": 709 + }, + { + "text": " os.fsync(list_file.fileno()) # Ensure data is written to disk", + "lineNumber": 710 + }, + { + "text": " # File is closed here by context manager, small delay to ensure OS releases handle", + "lineNumber": 711 + }, + { + "text": " time.sleep(0.1)", + "lineNumber": 712 + }, + { + "text": " ", + "lineNumber": 713 + }, + { + "text": " # Use absolute paths for both list file and temp zip", + "lineNumber": 714 + }, + { + "text": " list_file_abs = list_file_path.resolve()", + "lineNumber": 715 + }, + { + "text": " temp_zip_abs = temp_zip.resolve()", + "lineNumber": 716 + }, + { + "text": " # Create archive in temp location first (7z will create it fresh)", + "lineNumber": 717 + }, + { + "text": " cmd = [", + "lineNumber": 718 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 719 + }, + { + "text": " \"a\",", + "lineNumber": 720 + }, + { + "text": " \"-y\",", + "lineNumber": 721 + }, + { + "text": " \"-bb0\", # Suppress progress output", + "lineNumber": 722 + }, + { + "text": " f\"-mx={COMPRESSION_LEVEL}\",", + "lineNumber": 723 + }, + { + "text": " \"-t7z\", # Use 7z format, not zip", + "lineNumber": 724 + }, + { + "text": " ]", + "lineNumber": 725 + }, + { + "text": " ", + "lineNumber": 726 + }, + { + "text": " # Set compression method and memory/dictionary size based on method", + "lineNumber": 727 + }, + { + "text": " # At compression level 0, use Copy (store) method for maximum speed", + "lineNumber": 728 + }, + { + "text": " FIXED_DICT_SIZE_MB = 1024", + "lineNumber": 729 + }, + { + "text": " if COMPRESSION_LEVEL == 0:", + "lineNumber": 730 + }, + { + "text": " # Level 0 = no compression, just store files (fastest)", + "lineNumber": 731 + }, + { + "text": " cmd.append(\"-m0=Copy\")", + "lineNumber": 732 + } + ] + }, + "score": 0.15954655408859253 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 852 + }, + "endPosition": { + "line": 957 + } + }, + "contents": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:\n\n key=lambda e: e[\"path\"]\n )\n \n for entry in files_to_restore:\n file_path = target_dir / entry[\"path\"]\n if not file_path.exists():\n if verbose:\n log(\"expand\", f\"Warning: File not found after extraction: {entry['path']}\", verbose_only=True, verbose=verbose)\n failed_count += 1\n continue\n \n stored_mtime_ns = entry[\"mtime_ns\"]\n \n # On Windows, round to 100-nanosecond boundaries (NTFS precision)\n if is_windows:\n # NTFS FileTime uses 100-nanosecond intervals\n stored_mtime_ns = (stored_mtime_ns // 100) * 100\n \n try:\n # Restore timestamp\n os.utime(file_path, ns=(stored_mtime_ns, stored_mtime_ns))\n \n # Verify the timestamp was set correctly (within filesystem precision tolerance)\n actual_stat = file_path.stat()\n actual_mtime_ns = actual_stat.st_mtime_ns\n \n # On Windows, compare at 100-nanosecond precision\n if", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 853, + "column": 1 + }, + "endPosition": { + "line": 854, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:", + "lineNumber": 853, + "isSignature": true + }, + { + "lineNumber": 906 + }, + { + "text": " key=lambda e: e[\"path\"]", + "lineNumber": 907 + }, + { + "text": " )", + "lineNumber": 908 + }, + { + "text": " ", + "lineNumber": 909 + }, + { + "text": " for entry in files_to_restore:", + "lineNumber": 910 + }, + { + "text": " file_path = target_dir / entry[\"path\"]", + "lineNumber": 911 + }, + { + "text": " if not file_path.exists():", + "lineNumber": 912 + }, + { + "text": " if verbose:", + "lineNumber": 913 + }, + { + "text": " log(\"expand\", f\"Warning: File not found after extraction: {entry['path']}\", verbose_only=True, verbose=verbose)", + "lineNumber": 914 + }, + { + "text": " failed_count += 1", + "lineNumber": 915 + }, + { + "text": " continue", + "lineNumber": 916 + }, + { + "text": " ", + "lineNumber": 917 + }, + { + "text": " stored_mtime_ns = entry[\"mtime_ns\"]", + "lineNumber": 918 + }, + { + "text": " ", + "lineNumber": 919 + }, + { + "text": " # On Windows, round to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 920 + }, + { + "text": " if is_windows:", + "lineNumber": 921 + }, + { + "text": " # NTFS FileTime uses 100-nanosecond intervals", + "lineNumber": 922 + }, + { + "text": " stored_mtime_ns = (stored_mtime_ns // 100) * 100", + "lineNumber": 923 + }, + { + "text": " ", + "lineNumber": 924 + }, + { + "text": " try:", + "lineNumber": 925 + }, + { + "text": " # Restore timestamp", + "lineNumber": 926 + }, + { + "text": " os.utime(file_path, ns=(stored_mtime_ns, stored_mtime_ns))", + "lineNumber": 927 + }, + { + "text": " ", + "lineNumber": 928 + }, + { + "text": " # Verify the timestamp was set correctly (within filesystem precision tolerance)", + "lineNumber": 929 + }, + { + "text": " actual_stat = file_path.stat()", + "lineNumber": 930 + }, + { + "text": " actual_mtime_ns = actual_stat.st_mtime_ns", + "lineNumber": 931 + }, + { + "text": " ", + "lineNumber": 932 + }, + { + "text": " # On Windows, compare at 100-nanosecond precision", + "lineNumber": 933 + }, + { + "text": " if", + "lineNumber": 934 + } + ] + }, + "score": 0.15477000176906586 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 849, + "column": 5 + }, + "endPosition": { + "line": 957 + } + }, + "contents": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:\n target_dir = sequence_dir_for(zip_path)\n if target_dir.exists():\n shutil.rmtree(target_dir)\n target_dir.mkdir(parents=True, exist_ok=True)\n\n if ZIPPER_TYPE == \"7z\":\n if SEVEN_Z_EXE is None:\n raise RuntimeError(\n \"7z extraction requested but 7z executable not found in PATH. \"\n \"Please install 7z or set zipper to 'zip' in config.json\"\n )\n cmd = [\n SEVEN_Z_EXE,\n \"x\",\n \"-y\",\n \"-mtc=on\", # Preserve timestamps during extraction\n str(zip_path),\n f\"-o{target_dir}\",\n ]\n result = subprocess.run(\n cmd,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z extraction failed: {error_msg}\")\n elif ZIPPER_TYPE == \"zip\":\n from zipfile import ZipFile\n\n with ZipFile(zip_path, \"r\") as archive:\n archive.extractall(target_dir)\n else:\n raise RuntimeError(\n f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"\n f\"Expected '7z' or 'zip'. \"\n f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"\n )\n\n # Restore timestamps from stored state\n # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding\n is_windows = platform.system() == \"Windows\"\n restored_count = 0\n failed_count = 0\n mismatch_count = 0\n \n # Process files in sorted order to match state ordering\n files_to_restore = sorted(\n seq_state.get(\"files\", []),\n key=lambda e: e[\"path\"]\n )\n \n for entry in files_to_restore:", + "signatures": {}, + "detailedLines": [ + { + "text": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:", + "lineNumber": 853, + "isSignature": true + }, + { + "text": " target_dir = sequence_dir_for(zip_path)", + "lineNumber": 854 + }, + { + "text": " if target_dir.exists():", + "lineNumber": 855 + }, + { + "text": " shutil.rmtree(target_dir)", + "lineNumber": 856 + }, + { + "text": " target_dir.mkdir(parents=True, exist_ok=True)", + "lineNumber": 857 + }, + { + "lineNumber": 858 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 859 + }, + { + "text": " if SEVEN_Z_EXE is None:", + "lineNumber": 860 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 861 + }, + { + "text": " \"7z extraction requested but 7z executable not found in PATH. \"", + "lineNumber": 862 + }, + { + "text": " \"Please install 7z or set zipper to 'zip' in config.json\"", + "lineNumber": 863 + }, + { + "text": " )", + "lineNumber": 864 + }, + { + "text": " cmd = [", + "lineNumber": 865 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 866 + }, + { + "text": " \"x\",", + "lineNumber": 867 + }, + { + "text": " \"-y\",", + "lineNumber": 868 + }, + { + "text": " \"-mtc=on\", # Preserve timestamps during extraction", + "lineNumber": 869 + }, + { + "text": " str(zip_path),", + "lineNumber": 870 + }, + { + "text": " f\"-o{target_dir}\",", + "lineNumber": 871 + }, + { + "text": " ]", + "lineNumber": 872 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 873 + }, + { + "text": " cmd,", + "lineNumber": 874 + }, + { + "text": " check=False,", + "lineNumber": 875 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 876 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 877 + }, + { + "text": " text=True,", + "lineNumber": 878 + }, + { + "text": " )", + "lineNumber": 879 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 880 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 881 + }, + { + "text": " if result.stdout:", + "lineNumber": 882 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 883 + }, + { + "text": " raise RuntimeError(f\"7z extraction failed: {error_msg}\")", + "lineNumber": 884 + }, + { + "text": " elif ZIPPER_TYPE == \"zip\":", + "lineNumber": 885 + }, + { + "text": " from zipfile import ZipFile", + "lineNumber": 886 + }, + { + "lineNumber": 887 + }, + { + "text": " with ZipFile(zip_path, \"r\") as archive:", + "lineNumber": 888 + }, + { + "text": " archive.extractall(target_dir)", + "lineNumber": 889 + }, + { + "text": " else:", + "lineNumber": 890 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 891 + }, + { + "text": " f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"", + "lineNumber": 892 + }, + { + "text": " f\"Expected '7z' or 'zip'. \"", + "lineNumber": 893 + }, + { + "text": " f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"", + "lineNumber": 894 + }, + { + "text": " )", + "lineNumber": 895 + }, + { + "lineNumber": 896 + }, + { + "text": " # Restore timestamps from stored state", + "lineNumber": 897 + }, + { + "text": " # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding", + "lineNumber": 898 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 899 + }, + { + "text": " restored_count = 0", + "lineNumber": 900 + }, + { + "text": " failed_count = 0", + "lineNumber": 901 + }, + { + "text": " mismatch_count = 0", + "lineNumber": 902 + }, + { + "text": " ", + "lineNumber": 903 + }, + { + "text": " # Process files in sorted order to match state ordering", + "lineNumber": 904 + }, + { + "text": " files_to_restore = sorted(", + "lineNumber": 905 + }, + { + "text": " seq_state.get(\"files\", []),", + "lineNumber": 906 + }, + { + "text": " key=lambda e: e[\"path\"]", + "lineNumber": 907 + }, + { + "text": " )", + "lineNumber": 908 + }, + { + "text": " ", + "lineNumber": 909 + }, + { + "text": " for entry in files_to_restore:", + "lineNumber": 910 + } + ] + }, + "score": 0.15313979983329773 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 575, + "column": 19 + }, + "endPosition": { + "line": 638 + } + }, + "contents": "def state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:\n if stored_state is None:\n if verbose:\n log(\"scan\", \"State changed: no stored state found\", verbose_only=True, verbose=verbose)\n return True\n \n # Normalize timestamps in both states for comparison\n # On Windows, normalize to 100-nanosecond boundaries (NTFS precision)\n is_windows = platform.system() == \"Windows\"\n \n def normalize_state(state: dict) -> dict:\n \"\"\"Normalize timestamps in state to filesystem precision.\"\"\"\n normalized = {\"files\": []}\n for entry in state.get(\"files\", []):\n mtime_ns = entry[\"mtime_ns\"]\n if is_windows:\n mtime_ns = (mtime_ns // 100) * 100\n normalized[\"files\"].append({\n \"path\": entry[\"path\"],\n \"size\": entry[\"size\"],\n \"mtime_ns\": mtime_ns,\n })\n return normalized\n \n # Compare normalized states\n normalized_seq = normalize_state(seq_state)\n normalized_stored = normalize_state(stored_state)\n \n if normalized_seq == normalized_stored:\n return False\n \n # States differ - log diagnostic info if verbose\n if verbose:", + "signatures": {}, + "detailedLines": [ + { + "text": "def state_changed(seq_state: dict, stored_state: dict | None, *, verbose: bool = False) -> bool:", + "lineNumber": 579, + "isSignature": true + }, + { + "text": " if stored_state is None:", + "lineNumber": 580 + }, + { + "text": " if verbose:", + "lineNumber": 581 + }, + { + "text": " log(\"scan\", \"State changed: no stored state found\", verbose_only=True, verbose=verbose)", + "lineNumber": 582 + }, + { + "text": " return True", + "lineNumber": 583 + }, + { + "text": " ", + "lineNumber": 584 + }, + { + "text": " # Normalize timestamps in both states for comparison", + "lineNumber": 585 + }, + { + "text": " # On Windows, normalize to 100-nanosecond boundaries (NTFS precision)", + "lineNumber": 586 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 587 + }, + { + "text": " ", + "lineNumber": 588 + }, + { + "text": " def normalize_state(state: dict) -> dict:", + "lineNumber": 589 + }, + { + "text": " \"\"\"Normalize timestamps in state to filesystem precision.\"\"\"", + "lineNumber": 590 + }, + { + "text": " normalized = {\"files\": []}", + "lineNumber": 591 + }, + { + "text": " for entry in state.get(\"files\", []):", + "lineNumber": 592 + }, + { + "text": " mtime_ns = entry[\"mtime_ns\"]", + "lineNumber": 593 + }, + { + "text": " if is_windows:", + "lineNumber": 594 + }, + { + "text": " mtime_ns = (mtime_ns // 100) * 100", + "lineNumber": 595 + }, + { + "text": " normalized[\"files\"].append({", + "lineNumber": 596 + }, + { + "text": " \"path\": entry[\"path\"],", + "lineNumber": 597 + }, + { + "text": " \"size\": entry[\"size\"],", + "lineNumber": 598 + }, + { + "text": " \"mtime_ns\": mtime_ns,", + "lineNumber": 599 + }, + { + "text": " })", + "lineNumber": 600 + }, + { + "text": " return normalized", + "lineNumber": 601 + }, + { + "text": " ", + "lineNumber": 602 + }, + { + "text": " # Compare normalized states", + "lineNumber": 603 + }, + { + "text": " normalized_seq = normalize_state(seq_state)", + "lineNumber": 604 + }, + { + "text": " normalized_stored = normalize_state(stored_state)", + "lineNumber": 605 + }, + { + "text": " ", + "lineNumber": 606 + }, + { + "text": " if normalized_seq == normalized_stored:", + "lineNumber": 607 + }, + { + "text": " return False", + "lineNumber": 608 + }, + { + "text": " ", + "lineNumber": 609 + }, + { + "text": " # States differ - log diagnostic info if verbose", + "lineNumber": 610 + }, + { + "text": " if verbose:", + "lineNumber": 611 + } + ] + }, + "score": 0.1464795023202896 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": { + "line": 33 + }, + "endPosition": { + "line": 186 + } + }, + "contents": "def remap_appended_to_linked():\n\n \n # Also check for any BSDF materials that might be from old paths or different files\n # and try to find matching linked materials\n for mat in materials:\n if mat.library is None and (mat.name.startswith(\"BSDF_\") or \"BSDF_\" in mat.name):\n # This is an appended BSDF material - look for a linked version\n base_name = mat.name.split(\".blend\")[0] if \".blend\" in mat.name else mat.name\n base_name = base_name.split(\".\")[0] if \".\" in base_name else base_name\n \n # Look for any linked material with the same base name\n for linked_mat in materials:\n if (linked_mat.library is not None and \n linked_mat.name.startswith(\"BSDF_\") and\n (linked_mat.name == base_name or \n linked_mat.name.startswith(base_name + \".\") or\n linked_mat.name == mat.name)):\n \n if mat.users > 0:\n print(f\"Remapping old BSDF {mat.name} ({mat.users} users) to linked {linked_mat.name}\")\n mat.user_remap(linked_mat)\n remapping_count += 1\n \n # Remove the unused material\n if mat.users == 0:\n print(f\"Removing unused old BSDF material: {mat.name}\")\n bpy.data.materials.remove(mat)\n break\n \n print(f\"Remapped {remapping_count} appended/old BSDF materials to linked versions\")\n return remapping_count\n\ndef remap_missing_datablocks():\n \"\"\"Remap materials that have missing/broken library links\"\"\"", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 34, + "column": 1 + }, + "endPosition": { + "line": 35, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def remap_appended_to_linked():", + "lineNumber": 34, + "isSignature": true + }, + { + "lineNumber": 77 + }, + { + "text": " ", + "lineNumber": 78 + }, + { + "text": " # Also check for any BSDF materials that might be from old paths or different files", + "lineNumber": 79 + }, + { + "text": " # and try to find matching linked materials", + "lineNumber": 80 + }, + { + "text": " for mat in materials:", + "lineNumber": 81 + }, + { + "text": " if mat.library is None and (mat.name.startswith(\"BSDF_\") or \"BSDF_\" in mat.name):", + "lineNumber": 82 + }, + { + "text": " # This is an appended BSDF material - look for a linked version", + "lineNumber": 83 + }, + { + "text": " base_name = mat.name.split(\".blend\")[0] if \".blend\" in mat.name else mat.name", + "lineNumber": 84 + }, + { + "text": " base_name = base_name.split(\".\")[0] if \".\" in base_name else base_name", + "lineNumber": 85 + }, + { + "text": " ", + "lineNumber": 86 + }, + { + "text": " # Look for any linked material with the same base name", + "lineNumber": 87 + }, + { + "text": " for linked_mat in materials:", + "lineNumber": 88 + }, + { + "text": " if (linked_mat.library is not None and ", + "lineNumber": 89 + }, + { + "text": " linked_mat.name.startswith(\"BSDF_\") and", + "lineNumber": 90 + }, + { + "text": " (linked_mat.name == base_name or ", + "lineNumber": 91 + }, + { + "text": " linked_mat.name.startswith(base_name + \".\") or", + "lineNumber": 92 + }, + { + "text": " linked_mat.name == mat.name)):", + "lineNumber": 93 + }, + { + "text": " ", + "lineNumber": 94 + }, + { + "text": " if mat.users > 0:", + "lineNumber": 95 + }, + { + "text": " print(f\"Remapping old BSDF {mat.name} ({mat.users} users) to linked {linked_mat.name}\")", + "lineNumber": 96 + }, + { + "text": " mat.user_remap(linked_mat)", + "lineNumber": 97 + }, + { + "text": " remapping_count += 1", + "lineNumber": 98 + }, + { + "text": " ", + "lineNumber": 99 + }, + { + "text": " # Remove the unused material", + "lineNumber": 100 + }, + { + "text": " if mat.users == 0:", + "lineNumber": 101 + }, + { + "text": " print(f\"Removing unused old BSDF material: {mat.name}\")", + "lineNumber": 102 + }, + { + "text": " bpy.data.materials.remove(mat)", + "lineNumber": 103 + }, + { + "text": " break", + "lineNumber": 104 + }, + { + "text": " ", + "lineNumber": 105 + }, + { + "text": " print(f\"Remapped {remapping_count} appended/old BSDF materials to linked versions\")", + "lineNumber": 106 + }, + { + "text": " return remapping_count", + "lineNumber": 107, + "isSignature": true + }, + { + "lineNumber": 108 + }, + { + "text": "def remap_missing_datablocks():", + "lineNumber": 109, + "isSignature": true + }, + { + "text": " \"\"\"Remap materials that have missing/broken library links\"\"\"", + "lineNumber": 110 + } + ] + }, + "score": 0.14241009950637817 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpdateSequences.ps1", + "range": { + "startPosition": {}, + "endPosition": { + "line": 60 + } + }, + "contents": "[CmdletBinding()]\nparam(\n [switch]$DebugMode\n)\n\nSet-StrictMode -Version Latest\n$ErrorActionPreference = 'Stop'\n\nif (-not $PSScriptRoot) {\n $PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path\n}\n\n$configLoader = Join-Path -Path $PSScriptRoot -ChildPath 'ConfigLoader.ps1'\nif (-not (Test-Path -LiteralPath $configLoader)) {\n throw \"Missing ConfigLoader.ps1 in $PSScriptRoot\"\n}\n. $configLoader\n\n$useIsoDailyFormat = Use-IsoDailyFormat\n\nfunction Sync-SequenceFilenames {\n param(\n [Parameter(Mandatory)] [string]$SequenceFolderPath,\n [Parameter(Mandatory)] [string]$SequenceName,\n [string]$LogFile,\n [string[]]$Extensions = @('.png','.jpg','.jpeg','.exr','.tif','.tiff','.bmp','.tga')\n )\n $renamed = 0\n $collisions = 0\n $errors = 0\n $checked = 0\n $minFrame = [int]::MaxValue\n $maxFrame = -1\n $frameCount = 0\n\n if ($LogFile) { \"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME CHECK in '$SequenceFolderPath' (seq='$SequenceName')\" | Add-Content -LiteralPath $LogFile }\n\n $files = Get-ChildItem -LiteralPath $SequenceFolderPath -File -Recurse -ErrorAction SilentlyContinue |\n Where-Object { $_.FullName -notlike '*\\_archive\\*' -and ($Extensions -contains $_.Extension.ToLower()) }\n foreach ($f in $files) {\n $checked++\n $base = [System.IO.Path]::GetFileNameWithoutExtension($f.Name)\n $ext = $f.Extension\n $digits = $null\n\n if ($base -match '_(\\d{6})$') {\n $digits = $Matches[1]\n }\n elseif ($base -match '(?<!_)\\b(\\d{6})$') {\n $digits = $Matches[1]\n }\n elseif ($base -match '(\\d{4})$') {\n $digits = ('00' + $Matches[1])\n }\n else {\n continue\n }\n\n try {\n $n = [int]$digits", + "signatures": {}, + "detailedLines": [ + { + "text": "[CmdletBinding()]", + "lineNumber": 1 + }, + { + "text": "param(", + "lineNumber": 2 + }, + { + "text": " [switch]$DebugMode", + "lineNumber": 3 + }, + { + "text": ")", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "Set-StrictMode -Version Latest", + "lineNumber": 6 + }, + { + "text": "$ErrorActionPreference = 'Stop'", + "lineNumber": 7 + }, + { + "lineNumber": 8 + }, + { + "text": "if (-not $PSScriptRoot) {", + "lineNumber": 9 + }, + { + "text": " $PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path", + "lineNumber": 10 + }, + { + "text": "}", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": "$configLoader = Join-Path -Path $PSScriptRoot -ChildPath 'ConfigLoader.ps1'", + "lineNumber": 13 + }, + { + "text": "if (-not (Test-Path -LiteralPath $configLoader)) {", + "lineNumber": 14 + }, + { + "text": " throw \"Missing ConfigLoader.ps1 in $PSScriptRoot\"", + "lineNumber": 15 + }, + { + "text": "}", + "lineNumber": 16 + }, + { + "text": ". $configLoader", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "$useIsoDailyFormat = Use-IsoDailyFormat", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": "function Sync-SequenceFilenames {", + "lineNumber": 21 + }, + { + "text": " param(", + "lineNumber": 22 + }, + { + "text": " [Parameter(Mandatory)] [string]$SequenceFolderPath,", + "lineNumber": 23 + }, + { + "text": " [Parameter(Mandatory)] [string]$SequenceName,", + "lineNumber": 24 + }, + { + "text": " [string]$LogFile,", + "lineNumber": 25 + }, + { + "text": " [string[]]$Extensions = @('.png','.jpg','.jpeg','.exr','.tif','.tiff','.bmp','.tga')", + "lineNumber": 26 + }, + { + "text": " )", + "lineNumber": 27 + }, + { + "text": " $renamed = 0", + "lineNumber": 28 + }, + { + "text": " $collisions = 0", + "lineNumber": 29 + }, + { + "text": " $errors = 0", + "lineNumber": 30 + }, + { + "text": " $checked = 0", + "lineNumber": 31 + }, + { + "text": " $minFrame = [int]::MaxValue", + "lineNumber": 32 + }, + { + "text": " $maxFrame = -1", + "lineNumber": 33 + }, + { + "text": " $frameCount = 0", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": " if ($LogFile) { \"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME CHECK in '$SequenceFolderPath' (seq='$SequenceName')\" | Add-Content -LiteralPath $LogFile }", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": " $files = Get-ChildItem -LiteralPath $SequenceFolderPath -File -Recurse -ErrorAction SilentlyContinue |", + "lineNumber": 38 + }, + { + "text": " Where-Object { $_.FullName -notlike '*\\_archive\\*' -and ($Extensions -contains $_.Extension.ToLower()) }", + "lineNumber": 39 + }, + { + "text": " foreach ($f in $files) {", + "lineNumber": 40 + }, + { + "text": " $checked++", + "lineNumber": 41 + }, + { + "text": " $base = [System.IO.Path]::GetFileNameWithoutExtension($f.Name)", + "lineNumber": 42 + }, + { + "text": " $ext = $f.Extension", + "lineNumber": 43 + }, + { + "text": " $digits = $null", + "lineNumber": 44 + }, + { + "lineNumber": 45 + }, + { + "text": " if ($base -match '_(\\d{6})$') {", + "lineNumber": 46 + }, + { + "text": " $digits = $Matches[1]", + "lineNumber": 47 + }, + { + "text": " }", + "lineNumber": 48 + }, + { + "text": " elseif ($base -match '(?<!_)\\b(\\d{6})$') {", + "lineNumber": 49 + }, + { + "text": " $digits = $Matches[1]", + "lineNumber": 50 + }, + { + "text": " }", + "lineNumber": 51 + }, + { + "text": " elseif ($base -match '(\\d{4})$') {", + "lineNumber": 52 + }, + { + "text": " $digits = ('00' + $Matches[1])", + "lineNumber": 53 + }, + { + "text": " }", + "lineNumber": 54 + }, + { + "text": " else {", + "lineNumber": 55 + }, + { + "text": " continue", + "lineNumber": 56 + }, + { + "text": " }", + "lineNumber": 57 + }, + { + "lineNumber": 58 + }, + { + "text": " try {", + "lineNumber": 59 + }, + { + "text": " $n = [int]$digits", + "lineNumber": 60 + } + ] + }, + "score": 0.14094239473342896 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 972 + }, + "endPosition": { + "line": 1144 + } + }, + "contents": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:\n\n\n work_items.append((seq_dir, zip_path, state_path, seq_state))\n queued += 1\n if queued <= 5 or queued % 5 == 0:\n total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))\n size_gb = total_bytes / (1024 ** 3)\n log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")\n\n if not work_items:\n if not RENDER_ROOT.exists():\n log(\"zip\", \"Render root 'Renders' not found; nothing to zip.\")\n else:\n log(\"zip\", \"Archives already up to date; no sequences needed zipping.\")\n log(\n \"scan\",\n f\"Summary: scanned {total_scanned}, quick-skipped {quick_skipped}, \"\n f\"state-skipped {state_skipped}, empty {empty_dirs}, queued {queued}\",\n )\n removed = cleanup_orphan_archives(verbose=verbose)\n if removed:\n log(\"zip\", f\"Removed {removed} orphan archive(s).\", verbose=verbose)\n return 0\n\n # Calculate RAM-aware worker count based on work items\n worker_count, ram_limits_dict = max_workers(requested_workers, work_items, verbose=verbose)\n log(\n \"init\",\n f\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\",\n )\n\n updated_paths: list[Path] = []\n\n total = len(work_items)\n completed = 0\n\n with ThreadPoolExecutor(max_workers=worker_count) as executor:\n future_map = {\n executor.submit(process_zip, seq_dir, zip_path, state_path, seq_state, ram_limits_dict.get(seq_dir), worker_count, verbose=verbose): seq_dir\n for seq_dir, zip_path, state_path, seq_state in work_items\n }\n\n for future in as_completed(future_map):\n updated_paths.extend(future.result())\n completed += 1\n seq_dir = future_map[future]\n rel = seq_dir.relative_to(RENDER_ROOT)\n log(\"zip\", f\"{completed}/{total} {rel}\")\n\n updated_count = len(updated_paths) // 2", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 973, + "column": 1 + }, + "endPosition": { + "line": 974, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:", + "lineNumber": 973, + "isSignature": true + }, + { + "lineNumber": 1077 + }, + { + "lineNumber": 1078 + }, + { + "text": " work_items.append((seq_dir, zip_path, state_path, seq_state))", + "lineNumber": 1079 + }, + { + "text": " queued += 1", + "lineNumber": 1080 + }, + { + "text": " if queued <= 5 or queued % 5 == 0:", + "lineNumber": 1081 + }, + { + "text": " total_bytes = sum(entry.get(\"size\", 0) for entry in seq_state.get(\"files\", []))", + "lineNumber": 1082 + }, + { + "text": " size_gb = total_bytes / (1024 ** 3)", + "lineNumber": 1083 + }, + { + "text": " log(\"scan\", f\"Queued {rel} for compression (~{size_gb:.2f}GB) [{queued} total]\")", + "lineNumber": 1084 + }, + { + "lineNumber": 1085 + }, + { + "text": " if not work_items:", + "lineNumber": 1086 + }, + { + "text": " if not RENDER_ROOT.exists():", + "lineNumber": 1087 + }, + { + "text": " log(\"zip\", \"Render root 'Renders' not found; nothing to zip.\")", + "lineNumber": 1088 + }, + { + "text": " else:", + "lineNumber": 1089 + }, + { + "text": " log(\"zip\", \"Archives already up to date; no sequences needed zipping.\")", + "lineNumber": 1090 + }, + { + "text": " log(", + "lineNumber": 1091 + }, + { + "text": " \"scan\",", + "lineNumber": 1092 + }, + { + "text": " f\"Summary: scanned {total_scanned}, quick-skipped {quick_skipped}, \"", + "lineNumber": 1093 + }, + { + "text": " f\"state-skipped {state_skipped}, empty {empty_dirs}, queued {queued}\",", + "lineNumber": 1094 + }, + { + "text": " )", + "lineNumber": 1095 + }, + { + "text": " removed = cleanup_orphan_archives(verbose=verbose)", + "lineNumber": 1096 + }, + { + "text": " if removed:", + "lineNumber": 1097 + }, + { + "text": " log(\"zip\", f\"Removed {removed} orphan archive(s).\", verbose=verbose)", + "lineNumber": 1098 + }, + { + "text": " return 0", + "lineNumber": 1099 + }, + { + "lineNumber": 1100 + }, + { + "text": " # Calculate RAM-aware worker count based on work items", + "lineNumber": 1101 + }, + { + "text": " worker_count, ram_limits_dict = max_workers(requested_workers, work_items, verbose=verbose)", + "lineNumber": 1102 + }, + { + "text": " log(", + "lineNumber": 1103 + }, + { + "text": " \"init\",", + "lineNumber": 1104 + }, + { + "text": " f\"Preparing to compress {len(work_items)} sequence(s) with {worker_count} worker(s)\",", + "lineNumber": 1105 + }, + { + "text": " )", + "lineNumber": 1106 + }, + { + "lineNumber": 1107 + }, + { + "text": " updated_paths: list[Path] = []", + "lineNumber": 1108 + }, + { + "lineNumber": 1109 + }, + { + "text": " total = len(work_items)", + "lineNumber": 1110 + }, + { + "text": " completed = 0", + "lineNumber": 1111 + }, + { + "lineNumber": 1112 + }, + { + "text": " with ThreadPoolExecutor(max_workers=worker_count) as executor:", + "lineNumber": 1113 + }, + { + "text": " future_map = {", + "lineNumber": 1114 + }, + { + "text": " executor.submit(process_zip, seq_dir, zip_path, state_path, seq_state, ram_limits_dict.get(seq_dir), worker_count, verbose=verbose): seq_dir", + "lineNumber": 1115 + }, + { + "text": " for seq_dir, zip_path, state_path, seq_state in work_items", + "lineNumber": 1116 + }, + { + "text": " }", + "lineNumber": 1117 + }, + { + "lineNumber": 1118 + }, + { + "text": " for future in as_completed(future_map):", + "lineNumber": 1119 + }, + { + "text": " updated_paths.extend(future.result())", + "lineNumber": 1120 + }, + { + "text": " completed += 1", + "lineNumber": 1121 + }, + { + "text": " seq_dir = future_map[future]", + "lineNumber": 1122 + }, + { + "text": " rel = seq_dir.relative_to(RENDER_ROOT)", + "lineNumber": 1123 + }, + { + "text": " log(\"zip\", f\"{completed}/{total} {rel}\")", + "lineNumber": 1124 + }, + { + "lineNumber": 1125 + }, + { + "text": " updated_count = len(updated_paths) // 2", + "lineNumber": 1126 + } + ] + }, + "score": 0.13523639738559723 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\NewProject.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 63 + } + }, + "contents": "@echo off\nsetlocal EnableExtensions\n\nset \"SCRIPT_DIR=%~dp0\"\nset \"CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1\"\nset \"CONFIG_PATH=%SCRIPT_DIR%config.json\"\n\nif not exist \"%CONFIG_LOADER%\" (\n echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.\n exit /b 1\n)\n\nif not exist \"%CONFIG_PATH%\" (\n echo [ERROR] config.json not found next to NewProject.bat.\n exit /b 1\n)\n\nfor /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^\n \"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)\"`) do set \"STRUCT_DIR=%%I\"\n\nif not defined STRUCT_DIR (\n echo [ERROR] Unable to resolve ProjectStructure directory from config.\n exit /b 1\n)\n\n:: Get current date in YYYY-MM-DD format\nfor /f \"tokens=2-4 delims=/ \" %%a in ('date /t') do (\n set mm=%%a\n set dd=%%b\n set yy=%%c\n)\nset yyyy=20%yy:~-2%\nset yymmdd=%yy:~-2%%mm%%dd%\n\n:: Ask for project name\nset /p projectName=\"Enter project name (press Enter for default 'NewProject'): \"\nif \"%projectName%\"==\"\" set projectName=NewProject\nset projectRoot=%yymmdd%_%projectName%\n\n:: Create main project directory\nmkdir \"%projectRoot%\"\n\n:: Create Assets structure\nmkdir \"%projectRoot%\\Assets\\ElevenLabs\"\nif exist \"%STRUCT_DIR%\\NewDaily.bat\" copy /Y \"%STRUCT_DIR%\\NewDaily.bat\" \"%projectRoot%\\Assets\\ElevenLabs\\NewDaily.bat\" >nul\nmkdir \"%projectRoot%\\Assets\\Blends\"\nmkdir \"%projectRoot%\\Assets\\Mocap\"\nmkdir \"%projectRoot%\\Assets\\VO\"\nif exist \"%~dp0NewDaily.bat\" copy /Y \"%~dp0NewDaily.bat\" \"%projectRoot%\\Assets\\VO\\NewDaily.bat\" >nul\n\n:: Create Blends structure\nmkdir \"%projectRoot%\\Blends\\animations\\\"\nmkdir \"%projectRoot%\\Blends\\stills\\img-BG\"\n\n:: Create Deliverable structure\nmkdir \"%projectRoot%\\Deliverable\\\"\n\n:: Create Pr structure\nmkdir \"%projectRoot%\\Pr\\RnR\\RIFE\"\n\n:: Add project root additions\nif not exist \"%projectRoot%\\Renders\" mkdir \"%projectRoot%\\Renders\"", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal EnableExtensions", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "set \"SCRIPT_DIR=%~dp0\"", + "lineNumber": 4 + }, + { + "text": "set \"CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1\"", + "lineNumber": 5 + }, + { + "text": "set \"CONFIG_PATH=%SCRIPT_DIR%config.json\"", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": "if not exist \"%CONFIG_LOADER%\" (", + "lineNumber": 8 + }, + { + "text": " echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.", + "lineNumber": 9 + }, + { + "text": " exit /b 1", + "lineNumber": 10 + }, + { + "text": ")", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": "if not exist \"%CONFIG_PATH%\" (", + "lineNumber": 13 + }, + { + "text": " echo [ERROR] config.json not found next to NewProject.bat.", + "lineNumber": 14 + }, + { + "text": " exit /b 1", + "lineNumber": 15 + }, + { + "text": ")", + "lineNumber": 16 + }, + { + "lineNumber": 17 + }, + { + "text": "for /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^", + "lineNumber": 18 + }, + { + "text": " \"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)\"`) do set \"STRUCT_DIR=%%I\"", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": "if not defined STRUCT_DIR (", + "lineNumber": 21 + }, + { + "text": " echo [ERROR] Unable to resolve ProjectStructure directory from config.", + "lineNumber": 22 + }, + { + "text": " exit /b 1", + "lineNumber": 23 + }, + { + "text": ")", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": ":: Get current date in YYYY-MM-DD format", + "lineNumber": 26 + }, + { + "text": "for /f \"tokens=2-4 delims=/ \" %%a in ('date /t') do (", + "lineNumber": 27 + }, + { + "text": " set mm=%%a", + "lineNumber": 28 + }, + { + "text": " set dd=%%b", + "lineNumber": 29 + }, + { + "text": " set yy=%%c", + "lineNumber": 30 + }, + { + "text": ")", + "lineNumber": 31 + }, + { + "text": "set yyyy=20%yy:~-2%", + "lineNumber": 32 + }, + { + "text": "set yymmdd=%yy:~-2%%mm%%dd%", + "lineNumber": 33 + }, + { + "lineNumber": 34 + }, + { + "text": ":: Ask for project name", + "lineNumber": 35 + }, + { + "text": "set /p projectName=\"Enter project name (press Enter for default 'NewProject'): \"", + "lineNumber": 36 + }, + { + "text": "if \"%projectName%\"==\"\" set projectName=NewProject", + "lineNumber": 37 + }, + { + "text": "set projectRoot=%yymmdd%_%projectName%", + "lineNumber": 38 + }, + { + "lineNumber": 39 + }, + { + "text": ":: Create main project directory", + "lineNumber": 40 + }, + { + "text": "mkdir \"%projectRoot%\"", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": ":: Create Assets structure", + "lineNumber": 43 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\ElevenLabs\"", + "lineNumber": 44 + }, + { + "text": "if exist \"%STRUCT_DIR%\\NewDaily.bat\" copy /Y \"%STRUCT_DIR%\\NewDaily.bat\" \"%projectRoot%\\Assets\\ElevenLabs\\NewDaily.bat\" >nul", + "lineNumber": 45 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\Blends\"", + "lineNumber": 46 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\Mocap\"", + "lineNumber": 47 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\VO\"", + "lineNumber": 48 + }, + { + "text": "if exist \"%~dp0NewDaily.bat\" copy /Y \"%~dp0NewDaily.bat\" \"%projectRoot%\\Assets\\VO\\NewDaily.bat\" >nul", + "lineNumber": 49 + }, + { + "lineNumber": 50 + }, + { + "text": ":: Create Blends structure", + "lineNumber": 51 + }, + { + "text": "mkdir \"%projectRoot%\\Blends\\animations\\\"", + "lineNumber": 52 + }, + { + "text": "mkdir \"%projectRoot%\\Blends\\stills\\img-BG\"", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": ":: Create Deliverable structure", + "lineNumber": 55 + }, + { + "text": "mkdir \"%projectRoot%\\Deliverable\\\"", + "lineNumber": 56 + }, + { + "lineNumber": 57 + }, + { + "text": ":: Create Pr structure", + "lineNumber": 58 + }, + { + "text": "mkdir \"%projectRoot%\\Pr\\RnR\\RIFE\"", + "lineNumber": 59 + }, + { + "lineNumber": 60 + }, + { + "text": ":: Add project root additions", + "lineNumber": 61 + }, + { + "text": "if not exist \"%projectRoot%\\Renders\" mkdir \"%projectRoot%\\Renders\"", + "lineNumber": 62 + } + ] + }, + "score": 0.1340077519416809 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 972 + }, + "endPosition": { + "line": 1144 + } + }, + "contents": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:\n\n old_stored_state = None\n if ZIPPER_TYPE == \"7z\":\n old_zip_path = zip_path.with_suffix(\".zip\")\n if old_zip_path.exists():\n old_state_path = state_path_for(old_zip_path)\n old_stored_state = load_state(old_state_path)\n # If old .zip exists and .7z doesn't, use old .zip's state for comparison\n if not zip_path.exists() and old_stored_state is not None:\n stored_state = old_stored_state\n \n # If .7z archive exists and we have stored state, do quick check before computing full state\n if zip_path.exists() and stored_state is not None:\n # Quick check: if directory mtime is older than archive, likely unchanged\n try:\n dir_mtime = seq_dir.stat().st_mtime_ns\n archive_mtime = zip_path.stat().st_mtime_ns\n # If directory wasn't modified since archive was created, skip state computation\n if dir_mtime <= archive_mtime:\n quick_skipped += 1\n if quick_skipped <= 5:\n log(\"scan\", f\"Skipping {rel} (unchanged since archive)\")\n # Still need to check for old .zip cleanup (we have .7z, so .zip is obsolete)\n if old_zip_path and old_zip_path.exists():\n old_zip_path.unlink(missing_ok=True)\n old_state_path = state_path_for(old_zip_path)\n if old_state_path.exists():\n old_state_path.unlink(missing_ok=True)\n continue\n except OSError:\n # If stat fails, fall through to full state computation\n pass\n \n # Compute current state only if we need to\n seq_state = compute_state(seq_dir)\n if not seq_state[\"files\"]:\n empty_dirs += 1", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 973, + "column": 1 + }, + "endPosition": { + "line": 974, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def run_zip(requested_workers: int | None, *, verbose: bool) -> int:", + "lineNumber": 973, + "isSignature": true + }, + { + "lineNumber": 1006 + }, + { + "text": " old_stored_state = None", + "lineNumber": 1007 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 1008 + }, + { + "text": " old_zip_path = zip_path.with_suffix(\".zip\")", + "lineNumber": 1009 + }, + { + "text": " if old_zip_path.exists():", + "lineNumber": 1010 + }, + { + "text": " old_state_path = state_path_for(old_zip_path)", + "lineNumber": 1011 + }, + { + "text": " old_stored_state = load_state(old_state_path)", + "lineNumber": 1012 + }, + { + "text": " # If old .zip exists and .7z doesn't, use old .zip's state for comparison", + "lineNumber": 1013 + }, + { + "text": " if not zip_path.exists() and old_stored_state is not None:", + "lineNumber": 1014 + }, + { + "text": " stored_state = old_stored_state", + "lineNumber": 1015 + }, + { + "text": " ", + "lineNumber": 1016 + }, + { + "text": " # If .7z archive exists and we have stored state, do quick check before computing full state", + "lineNumber": 1017 + }, + { + "text": " if zip_path.exists() and stored_state is not None:", + "lineNumber": 1018 + }, + { + "text": " # Quick check: if directory mtime is older than archive, likely unchanged", + "lineNumber": 1019 + }, + { + "text": " try:", + "lineNumber": 1020 + }, + { + "text": " dir_mtime = seq_dir.stat().st_mtime_ns", + "lineNumber": 1021 + }, + { + "text": " archive_mtime = zip_path.stat().st_mtime_ns", + "lineNumber": 1022 + }, + { + "text": " # If directory wasn't modified since archive was created, skip state computation", + "lineNumber": 1023 + }, + { + "text": " if dir_mtime <= archive_mtime:", + "lineNumber": 1024 + }, + { + "text": " quick_skipped += 1", + "lineNumber": 1025 + }, + { + "text": " if quick_skipped <= 5:", + "lineNumber": 1026 + }, + { + "text": " log(\"scan\", f\"Skipping {rel} (unchanged since archive)\")", + "lineNumber": 1027 + }, + { + "text": " # Still need to check for old .zip cleanup (we have .7z, so .zip is obsolete)", + "lineNumber": 1028 + }, + { + "text": " if old_zip_path and old_zip_path.exists():", + "lineNumber": 1029 + }, + { + "text": " old_zip_path.unlink(missing_ok=True)", + "lineNumber": 1030 + }, + { + "text": " old_state_path = state_path_for(old_zip_path)", + "lineNumber": 1031 + }, + { + "text": " if old_state_path.exists():", + "lineNumber": 1032 + }, + { + "text": " old_state_path.unlink(missing_ok=True)", + "lineNumber": 1033 + }, + { + "text": " continue", + "lineNumber": 1034 + }, + { + "text": " except OSError:", + "lineNumber": 1035 + }, + { + "text": " # If stat fails, fall through to full state computation", + "lineNumber": 1036 + }, + { + "text": " pass", + "lineNumber": 1037 + }, + { + "text": " ", + "lineNumber": 1038 + }, + { + "text": " # Compute current state only if we need to", + "lineNumber": 1039 + }, + { + "text": " seq_state = compute_state(seq_dir)", + "lineNumber": 1040 + }, + { + "text": " if not seq_state[\"files\"]:", + "lineNumber": 1041 + }, + { + "text": " empty_dirs += 1", + "lineNumber": 1042 + } + ] + }, + "score": 0.13383010029792786 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 157, + "column": 30 + }, + "endPosition": { + "line": 236 + } + }, + "contents": "def get_available_ram() -> int | None:\n \"\"\"Get available RAM in bytes, reserving 20% for system.\n \n Returns:\n Available RAM in bytes, or None if detection fails.\n \"\"\"\n try:\n if HAS_PSUTIL:\n # Use psutil for cross-platform RAM detection\n mem = psutil.virtual_memory()\n # Reserve 20% for system, use 80% for compression jobs\n available = int(mem.total * 0.8)\n return available\n elif HAS_CTYPES and platform.system() == \"Windows\":\n # Windows fallback using ctypes\n class MEMORYSTATUSEX(ctypes.Structure):\n _fields_ = [\n (\"dwLength\", ctypes.c_ulong),\n (\"dwMemoryLoad\", ctypes.c_ulong),\n (\"ullTotalPhys\", ctypes.c_ulonglong),\n (\"ullAvailPhys\", ctypes.c_ulonglong),\n (\"ullTotalPageFile\", ctypes.c_ulonglong),\n (\"ullAvailPageFile\", ctypes.c_ulonglong),\n (\"ullTotalVirtual\", ctypes.c_ulonglong),\n (\"ullAvailVirtual\", ctypes.c_ulonglong),\n (\"ullAvailExtendedVirtual\", ctypes.c_ulonglong),\n ]\n \n kernel32 = ctypes.windll.kernel32\n kernel32.GlobalMemoryStatusEx.argtypes = [ctypes.POINTER(MEMORYSTATUSEX)]\n kernel32.GlobalMemoryStatusEx.restype = ctypes.c_bool\n \n mem_status = MEMORYSTATUSEX()\n mem_status.dwLength = ctypes.sizeof(MEMORYSTATUSEX)\n \n if kernel32.GlobalMemoryStatusEx(ctypes.byref(mem_status)):\n # Reserve 20% for system, use 80% for compression jobs\n available = int(mem_status.ullTotalPhys * 0.8)\n return available\n except Exception:\n pass\n \n return None\n\n\ndef estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:\n \"\"\"", + "signatures": {}, + "detailedLines": [ + { + "text": "def get_available_ram() -> int | None:", + "lineNumber": 161, + "isSignature": true + }, + { + "text": " \"\"\"Get available RAM in bytes, reserving 20% for system.", + "lineNumber": 162 + }, + { + "text": " ", + "lineNumber": 163 + }, + { + "text": " Returns:", + "lineNumber": 164 + }, + { + "text": " Available RAM in bytes, or None if detection fails.", + "lineNumber": 165 + }, + { + "text": " \"\"\"", + "lineNumber": 166 + }, + { + "text": " try:", + "lineNumber": 167 + }, + { + "text": " if HAS_PSUTIL:", + "lineNumber": 168 + }, + { + "text": " # Use psutil for cross-platform RAM detection", + "lineNumber": 169 + }, + { + "text": " mem = psutil.virtual_memory()", + "lineNumber": 170 + }, + { + "text": " # Reserve 20% for system, use 80% for compression jobs", + "lineNumber": 171 + }, + { + "text": " available = int(mem.total * 0.8)", + "lineNumber": 172 + }, + { + "text": " return available", + "lineNumber": 173 + }, + { + "text": " elif HAS_CTYPES and platform.system() == \"Windows\":", + "lineNumber": 174 + }, + { + "text": " # Windows fallback using ctypes", + "lineNumber": 175 + }, + { + "text": " class MEMORYSTATUSEX(ctypes.Structure):", + "lineNumber": 176 + }, + { + "text": " _fields_ = [", + "lineNumber": 177 + }, + { + "text": " (\"dwLength\", ctypes.c_ulong),", + "lineNumber": 178 + }, + { + "text": " (\"dwMemoryLoad\", ctypes.c_ulong),", + "lineNumber": 179 + }, + { + "text": " (\"ullTotalPhys\", ctypes.c_ulonglong),", + "lineNumber": 180 + }, + { + "text": " (\"ullAvailPhys\", ctypes.c_ulonglong),", + "lineNumber": 181 + }, + { + "text": " (\"ullTotalPageFile\", ctypes.c_ulonglong),", + "lineNumber": 182 + }, + { + "text": " (\"ullAvailPageFile\", ctypes.c_ulonglong),", + "lineNumber": 183 + }, + { + "text": " (\"ullTotalVirtual\", ctypes.c_ulonglong),", + "lineNumber": 184 + }, + { + "text": " (\"ullAvailVirtual\", ctypes.c_ulonglong),", + "lineNumber": 185 + }, + { + "text": " (\"ullAvailExtendedVirtual\", ctypes.c_ulonglong),", + "lineNumber": 186 + }, + { + "text": " ]", + "lineNumber": 187 + }, + { + "text": " ", + "lineNumber": 188 + }, + { + "text": " kernel32 = ctypes.windll.kernel32", + "lineNumber": 189 + }, + { + "text": " kernel32.GlobalMemoryStatusEx.argtypes = [ctypes.POINTER(MEMORYSTATUSEX)]", + "lineNumber": 190 + }, + { + "text": " kernel32.GlobalMemoryStatusEx.restype = ctypes.c_bool", + "lineNumber": 191 + }, + { + "text": " ", + "lineNumber": 192 + }, + { + "text": " mem_status = MEMORYSTATUSEX()", + "lineNumber": 193 + }, + { + "text": " mem_status.dwLength = ctypes.sizeof(MEMORYSTATUSEX)", + "lineNumber": 194 + }, + { + "text": " ", + "lineNumber": 195 + }, + { + "text": " if kernel32.GlobalMemoryStatusEx(ctypes.byref(mem_status)):", + "lineNumber": 196 + }, + { + "text": " # Reserve 20% for system, use 80% for compression jobs", + "lineNumber": 197 + }, + { + "text": " available = int(mem_status.ullTotalPhys * 0.8)", + "lineNumber": 198 + }, + { + "text": " return available", + "lineNumber": 199 + }, + { + "text": " except Exception:", + "lineNumber": 200 + }, + { + "text": " pass", + "lineNumber": 201 + }, + { + "text": " ", + "lineNumber": 202 + }, + { + "text": " return None", + "lineNumber": 203, + "isSignature": true + }, + { + "lineNumber": 204 + }, + { + "lineNumber": 205 + }, + { + "text": "def estimate_ram_per_job(seq_dir: Path, seq_state: dict) -> int:", + "lineNumber": 206, + "isSignature": true + }, + { + "text": " \"\"\"", + "lineNumber": 207 + } + ] + }, + "score": 0.13349765539169312 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": { + "line": 106, + "column": 26 + }, + "endPosition": { + "line": 186 + } + }, + "contents": "def remap_missing_datablocks():\n \"\"\"Remap materials that have missing/broken library links\"\"\"\n \n print(\"\\nChecking for missing datablocks to remap...\")\n \n materials = bpy.data.materials\n remapping_count = 0\n \n # Find materials with missing library links\n missing_materials = []\n for mat in materials:\n if mat.library is not None and mat.library.filepath and not os.path.exists(bpy.path.abspath(mat.library.filepath)):\n missing_materials.append(mat)\n print(f\"Found missing datablock: {mat.name} (from {mat.library.filepath})\")\n \n if not missing_materials:\n print(\"No missing datablocks found.\")\n return 0\n \n # For each missing material, try to find a replacement\n for missing_mat in missing_materials:\n base_name = missing_mat.name.split(\".blend\")[0] if \".blend\" in missing_mat.name else missing_mat.name\n base_name = base_name.split(\".\")[0] if", + "signatures": {}, + "detailedLines": [ + { + "text": "def remap_missing_datablocks():", + "lineNumber": 109, + "isSignature": true + }, + { + "text": " \"\"\"Remap materials that have missing/broken library links\"\"\"", + "lineNumber": 110 + }, + { + "text": " ", + "lineNumber": 111 + }, + { + "text": " print(\"\\nChecking for missing datablocks to remap...\")", + "lineNumber": 112 + }, + { + "text": " ", + "lineNumber": 113 + }, + { + "text": " materials = bpy.data.materials", + "lineNumber": 114 + }, + { + "text": " remapping_count = 0", + "lineNumber": 115 + }, + { + "text": " ", + "lineNumber": 116 + }, + { + "text": " # Find materials with missing library links", + "lineNumber": 117 + }, + { + "text": " missing_materials = []", + "lineNumber": 118 + }, + { + "text": " for mat in materials:", + "lineNumber": 119 + }, + { + "text": " if mat.library is not None and mat.library.filepath and not os.path.exists(bpy.path.abspath(mat.library.filepath)):", + "lineNumber": 120 + }, + { + "text": " missing_materials.append(mat)", + "lineNumber": 121 + }, + { + "text": " print(f\"Found missing datablock: {mat.name} (from {mat.library.filepath})\")", + "lineNumber": 122 + }, + { + "text": " ", + "lineNumber": 123 + }, + { + "text": " if not missing_materials:", + "lineNumber": 124 + }, + { + "text": " print(\"No missing datablocks found.\")", + "lineNumber": 125 + }, + { + "text": " return 0", + "lineNumber": 126 + }, + { + "text": " ", + "lineNumber": 127 + }, + { + "text": " # For each missing material, try to find a replacement", + "lineNumber": 128 + }, + { + "text": " for missing_mat in missing_materials:", + "lineNumber": 129 + }, + { + "text": " base_name = missing_mat.name.split(\".blend\")[0] if \".blend\" in missing_mat.name else missing_mat.name", + "lineNumber": 130 + }, + { + "text": " base_name = base_name.split(\".\")[0] if", + "lineNumber": 131 + } + ] + }, + "score": 0.133012056350708 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": { + "line": 186, + "column": 26 + }, + "endPosition": { + "line": 355 + } + }, + "contents": "def replace_cel_materials():\n \"\"\"Replace all CEL materials with their BSDF counterparts using Blender's user remapping\"\"\"\n \n # First, link BSDF materials from library\n linked_materials = link_bsdf_materials()\n \n # Then, remap any missing datablocks\n missing_remaps = remap_missing_datablocks()\n \n # Then, remap any appended BSDF materials to linked versions\n appended_remaps = remap_appended_to_linked()\n \n print(f\"\\n=== STARTING MATERIAL REPLACEMENT ===\")\n \n # Custom material mappings (source -> target)\n custom_mappings = {\n \"bag BLACK (squid ink)\": \"BSDF_black_SQUID-INK\",\n \"bag WHITE\": \"BSDF_WHITE\",\n \"Wheel-White\": \"BSDF_WHITE\",\n \"Bag Colors\": \"BSDF_Bag Colors\",\n \"cardboard\": \"Package_Cardboard\",\n \"blue (triton)\": \"BSDF_blue-2_TRITON\",\n \"gray (snow)\": \"BSDF_gray-6_SNOW\",\n \"gray (storm)\": \"BSDF_gray-2_STORM\",\n \"gray (summit)\": \"BSDF_gray-5_SUMMIT\",\n \"light blue (prime)\": \"BSDF_blue-4_PRIME\",\n \"yellow (summer)\": \"BSDF_orange-5_SUMMER\",\n \"Accessory_CEL_gray-6_SNOW\": \"BSDF_gray-6_SNOW\",\n \"Accessory_CEL_SquidInk\": \"BSDF_black_SQUID-INK\",\n \"FingerScanner\": \"BSDF_black_SQUID-INK\",\n \"cel BLACK (squid ink)\": \"BSDF_black_SQUID-INK\",\n \"cel WHITE\": \"BSDF_WHITE\",\n \"gray (stone)\": \"BSDF_gray-3_STONE\",\n \"green (oxygen)\": \"BSDF_green-3_OXYGEN\",\n \"orange (smile)\": \"BSDF_orange-3_SMILE\",\n \"orange (blaze)\": \"BSDF_orange-1_BLAZE\"\n }\n \n # Get all materials in the scene\n materials = bpy.data.materials\n \n # Dictionary to store source -> target material mapping\n material_mapping = {}\n \n # Replace all CEL materials with their BSDF counterparts, ignoring numeric suffixes\n cel_pattern = re.compile(r\"^(CEL_.+?)(\\.\\d{3})?$\")\n bsdf_pattern = re.compile(r\"^(BSDF_.+?)(\\.\\d{3})?$\")\n \n # Build a mapping from base BSDF name to BSDF material (without suffix)", + "signatures": {}, + "detailedLines": [ + { + "text": "def replace_cel_materials():", + "lineNumber": 189, + "isSignature": true + }, + { + "text": " \"\"\"Replace all CEL materials with their BSDF counterparts using Blender's user remapping\"\"\"", + "lineNumber": 190 + }, + { + "text": " ", + "lineNumber": 191 + }, + { + "text": " # First, link BSDF materials from library", + "lineNumber": 192 + }, + { + "text": " linked_materials = link_bsdf_materials()", + "lineNumber": 193 + }, + { + "text": " ", + "lineNumber": 194 + }, + { + "text": " # Then, remap any missing datablocks", + "lineNumber": 195 + }, + { + "text": " missing_remaps = remap_missing_datablocks()", + "lineNumber": 196 + }, + { + "text": " ", + "lineNumber": 197 + }, + { + "text": " # Then, remap any appended BSDF materials to linked versions", + "lineNumber": 198 + }, + { + "text": " appended_remaps = remap_appended_to_linked()", + "lineNumber": 199 + }, + { + "text": " ", + "lineNumber": 200 + }, + { + "text": " print(f\"\\n=== STARTING MATERIAL REPLACEMENT ===\")", + "lineNumber": 201 + }, + { + "text": " ", + "lineNumber": 202 + }, + { + "text": " # Custom material mappings (source -> target)", + "lineNumber": 203 + }, + { + "text": " custom_mappings = {", + "lineNumber": 204 + }, + { + "text": " \"bag BLACK (squid ink)\": \"BSDF_black_SQUID-INK\",", + "lineNumber": 205 + }, + { + "text": " \"bag WHITE\": \"BSDF_WHITE\",", + "lineNumber": 206 + }, + { + "text": " \"Wheel-White\": \"BSDF_WHITE\",", + "lineNumber": 207 + }, + { + "text": " \"Bag Colors\": \"BSDF_Bag Colors\",", + "lineNumber": 208 + }, + { + "text": " \"cardboard\": \"Package_Cardboard\",", + "lineNumber": 209 + }, + { + "text": " \"blue (triton)\": \"BSDF_blue-2_TRITON\",", + "lineNumber": 210 + }, + { + "text": " \"gray (snow)\": \"BSDF_gray-6_SNOW\",", + "lineNumber": 211 + }, + { + "text": " \"gray (storm)\": \"BSDF_gray-2_STORM\",", + "lineNumber": 212 + }, + { + "text": " \"gray (summit)\": \"BSDF_gray-5_SUMMIT\",", + "lineNumber": 213 + }, + { + "text": " \"light blue (prime)\": \"BSDF_blue-4_PRIME\",", + "lineNumber": 214 + }, + { + "text": " \"yellow (summer)\": \"BSDF_orange-5_SUMMER\",", + "lineNumber": 215 + }, + { + "text": " \"Accessory_CEL_gray-6_SNOW\": \"BSDF_gray-6_SNOW\",", + "lineNumber": 216 + }, + { + "text": " \"Accessory_CEL_SquidInk\": \"BSDF_black_SQUID-INK\",", + "lineNumber": 217 + }, + { + "text": " \"FingerScanner\": \"BSDF_black_SQUID-INK\",", + "lineNumber": 218 + }, + { + "text": " \"cel BLACK (squid ink)\": \"BSDF_black_SQUID-INK\",", + "lineNumber": 219 + }, + { + "text": " \"cel WHITE\": \"BSDF_WHITE\",", + "lineNumber": 220 + }, + { + "text": " \"gray (stone)\": \"BSDF_gray-3_STONE\",", + "lineNumber": 221 + }, + { + "text": " \"green (oxygen)\": \"BSDF_green-3_OXYGEN\",", + "lineNumber": 222 + }, + { + "text": " \"orange (smile)\": \"BSDF_orange-3_SMILE\",", + "lineNumber": 223 + }, + { + "text": " \"orange (blaze)\": \"BSDF_orange-1_BLAZE\"", + "lineNumber": 224 + }, + { + "text": " }", + "lineNumber": 225 + }, + { + "text": " ", + "lineNumber": 226 + }, + { + "text": " # Get all materials in the scene", + "lineNumber": 227 + }, + { + "text": " materials = bpy.data.materials", + "lineNumber": 228 + }, + { + "text": " ", + "lineNumber": 229 + }, + { + "text": " # Dictionary to store source -> target material mapping", + "lineNumber": 230 + }, + { + "text": " material_mapping = {}", + "lineNumber": 231 + }, + { + "text": " ", + "lineNumber": 232 + }, + { + "text": " # Replace all CEL materials with their BSDF counterparts, ignoring numeric suffixes", + "lineNumber": 233 + }, + { + "text": " cel_pattern = re.compile(r\"^(CEL_.+?)(\\.\\d{3})?$\")", + "lineNumber": 234 + }, + { + "text": " bsdf_pattern = re.compile(r\"^(BSDF_.+?)(\\.\\d{3})?$\")", + "lineNumber": 235 + }, + { + "text": " ", + "lineNumber": 236 + }, + { + "text": " # Build a mapping from base BSDF name to BSDF material (without suffix)", + "lineNumber": 237 + } + ] + }, + "score": 0.13131725788116455 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Accessing `bpy.context.preferences` in background mode with `--factory-startup` can cause access violations because the context may not be fully initialized. Updating the script to handle this safely: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "2123f0c5-1029-4492-946f-7c70dd0225a4" +} +``` +Status: **completed** + + +
+
+ +--- + + +Fixed the access violation issues in the compression script. The main problems were: + +1. **Accessing `bpy.context.preferences` in background mode**: Lines 36-37 accessed preferences, which can cause access violations in `--background --factory-startup` because the context isn't fully initialized. Removed these lines since `compress=True` in `save_mainfile()` already handles compression. + +2. **Loading UI data unnecessarily**: Added `load_ui=False` to `open_mainfile()` to avoid loading UI-related data that can cause access violations in background mode. + +3. **Better error reporting**: Added traceback printing to capture more detailed error information when crashes occur. + +The script should now run without access violations. The compression setting is handled by the `compress=True` parameter when saving, so the preferences access wasn't needed. + +Try running the compression again; it should work without crashing. + +--- + +_**User**_ + +@c:\Users\Nathan\AppData\Local\Temp\blender.crash.txt:1-620 + +--- + +_**Agent (model default, mode Agent)**_ + + +The crash log shows the access violation occurs during library expansion when reading the blend file. The stack trace shows: + +``` +read_id_in_lib → expand_doit_library → expand_cb → mesh_foreach_id → read_libraries → BLO_read_from_file → wm_open_mainfile_exec +``` + +This indicates the file has linked libraries (external references) that Blender can't resolve, causing a null pointer dereference. Updating the script to handle this more safely: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +The crash occurs when expanding linked libraries during file load. Adding options to handle broken library links safely: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "70c42a46-131c-492b-9809-44a4571ad14c" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding additional safety checks and better error handling: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "bd26e182-377b-43b7-9a27-d826680a9f54" +} +``` +Status: **completed** + + +
+
+ +--- + + +Updated the script to address the access violation. The crash log shows it occurs during library expansion when reading linked libraries. + +**Changes made:** + +1. **Added `use_placeholders=True`** — Prevents Blender from loading missing linked libraries, which caused the crash in `read_id_in_lib` → `expand_doit_library`. + +2. **Added `use_scripts=False`** — Avoids loading scripts that could cause issues in background mode. + +3. **Added file validation** — Checks that the file exists and is readable before attempting to open it. + +The crash occurred because the blend file references external libraries that Blender tried to load, leading to a null pointer dereference. With `use_placeholders=True`, Blender uses placeholders instead of loading missing libraries, which should prevent the crash. + +Try running the compression again. If it still crashes, the file may be corrupted at a lower level and might need recovery or should be skipped. + +--- + +_**User**_ + + +Warning! 'C:\\Program Files\\Blender Foundation\\Blender 5.0\\5.0\\scripts\\startup\\fa_hotkeys.py' has no register function, this is now a requirement for registerable scripts +register_class(...): +Warning: 'Submenu' does not contain '_MT_' with prefix and suffix +register_class(...): +Warning: 'Submenu2' does not contain '_MT_' with prefix and suffix +register_class(...): +Warning: 'Submenu3' does not contain '_MT_' with prefix and suffix +register_class(...): +Warning: 'view3D.Pie_menu' does not contain '_MT_' with prefix and suffix +register_class(...): +Warning: 'CustomPanel' does not contain '_PT_' with prefix and suffix +register_class(...): +Warning: 'view3D.Quad_menu' does not contain '_MT_' with prefix and suffix +register_class(...): +Warning: 'Special_Tools_Menu' does not contain '_MT_' with prefix and suffix +Found 35 .blend files to compress +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_1.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_3.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_4.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_5.blend +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_6.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_6.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_8-1.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_8-1.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_8-2.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_8-2.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_9-1.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_9-1.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_9-2.blend +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-1.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-1.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-2.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-2.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-3.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_10-3.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_11.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_11.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_12.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_12.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_14.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_14.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_14b.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_14b.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_15.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_15.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_16.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_16a.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_16b.blend +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_17.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_17.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_18.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_18.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_19.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_19.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_20-1.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_20-1.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_20-2.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_20-2.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_22.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_22.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-1.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-1.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-2.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-2.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-3.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_23-3.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_24.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_24.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_25.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_25.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +PROCESSING:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_26.blend +CRASH:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_26.blend:Converting py args to operator properties:: keyword "use_placeholders" unrecognized +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_33abc.blend +SKIP_ALREADY_COMPRESSED:A:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\Non-Con_34a_b_c_d.blend +SKIP_NOT_BLEND:2>&1 +COMPRESSION_COMPLETE +Blender 5.0.1 (hash a3db93c5b259 built 2025-12-16 01:32:30) + +Blender quit + + +--- + +_**Agent (model default, mode Agent)**_ + + +The `use_placeholders` parameter isn't recognized in Blender 5.0.1. Checking supported parameters and updating the script: + + +--- + + +
+ Tool use: **web_search** • Searched web `undefined` • **1** results + +| Title | URL | Chunk | +|-------|-----|-------| +| Web Search Results | | As of Blender 5.0, the `bpy.ops.wm.open_mainfile` function does not include a parameter named `use_placeholders`. The available parameters for this function are:

- `filepath` (string, optional): Path to the file.
- `filter_blender` (boolean, optional): Filter .blend files.
- `filter_backup` (boolean, optional): Filter backup files.
- `filter_image` (boolean, optional): Filter image files.
- `filter_movie` (boolean, optional): Filter movie files.
- `filter_python` (boolean, optional): Filter Python files.
- `filter_font` (boolean, optional): Filter font files.
- `filter_sound` (boolean, optional): Filter sound files.
- `filter_text` (boolean, optional): Filter text files.
- `filter_btx` (boolean, optional): Filter BTX files.
- `filter_collada` (boolean, optional): Filter COLLADA files.
- `filter_folder` (boolean, optional): Filter folders.
- `filemode` (int in [1, 9], optional): File browser mode.
- `display_type` (enum, optional): Display type for the file browser.
- `load_ui` (boolean, optional): Load user interface setup from the .blend file.
- `use_scripts` (boolean, optional): Allow the execution of scripts automatically.

For more details, you can refer to the Blender 5.0 API documentation. | + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Removing the invalid `use_placeholders` parameter. Updating the script to use only supported parameters: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "cd7e82db-d352-4cfa-943e-07009771ffa7" +} +``` +Status: **completed** + + +
+
+ +--- + + +Removed the invalid `use_placeholders` parameter. The script now uses only supported Blender 5.0.1 parameters: `load_ui=False` and `use_scripts=False`. + +The access violation may still occur if files have broken library links, since Blender 5.0 doesn't support placeholders. If it persists, options include: + +1. Opening the problematic files in Blender GUI to fix/remove broken library links +2. Using a subprocess wrapper to catch crashes (more complex) +3. Skipping files that crash and reporting them + +Run the script again; it should no longer error on invalid parameters. If access violations persist, those files likely have corrupted or broken library links that need manual fixing. + +--- + +_**User**_ + +let's make sure it's compatible with previous versions of blender as well and it detects the blender version, like we have in E:\atomic_data_manager except in one python file. + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the version detection pattern in `E:\atomic_data_manager`: + + +--- + + +
+Tool use: **list_dir_v2** + + +Parameters: + +```json +{ + "targetDirectory": "E:\\atomic_data_manager" +} +``` +Result: + +```json +{ + "directoryTreeRoot": { + "absPath": "E:\\atomic_data_manager", + "childrenDirs": [ + { + "absPath": "E:\\atomic_data_manager\\.github", + "childrenDirs": [ + { + "absPath": "E:\\atomic_data_manager\\.github\\workflows", + "childrenFiles": [ + { + "name": "release.yml" + } + ], + "childrenWereProcessed": true + } + ], + "childrenWereProcessed": true + }, + { + "absPath": "E:\\atomic_data_manager\\.specstory", + "childrenDirs": [ + { + "absPath": "E:\\atomic_data_manager\\.specstory\\history", + "childrenFiles": [ + { + "name": "2025-10-09_00-08Z-update-blender-addon-for-version-4-5.md" + }, + { + "name": "2025-12-01_19-37Z-integrate-multi-blender-version-support.md" + }, + { + "name": "2025-12-01_20-15Z-blender-integration-error-troubleshooting.md" + }, + { + "name": "2025-12-01_20-46Z-setting-up-a-blender-extension-api.md" + }, + { + "name": "2025-12-16_23-24Z-blender-library-packed-files-error.md" + } + ], + "childrenWereProcessed": true + } + ], + "childrenFiles": [ + { + "name": ".gitignore" + } + ], + "childrenWereProcessed": true + }, + { + "absPath": "E:\\atomic_data_manager\\ops", + "childrenDirs": [ + { + "absPath": "E:\\atomic_data_manager\\ops\\utils", + "childrenFiles": [ + { + "name": "clean.py" + }, + { + "name": "delete.py" + }, + { + "name": "duplicate.py" + }, + { + "name": "nuke.py" + } + ], + "childrenWereProcessed": true + } + ], + "childrenFiles": [ + { + "name": "__init__.py" + }, + { + "name": "direct_use_ops.py" + }, + { + "name": "inspect_ops.py" + }, + { + "name": "main_ops.py" + }, + { + "name": "missing_file_ops.py" + }, + { + "name": "support_me_ops.py" + } + ], + "childrenWereProcessed": true + }, + { + "absPath": "E:\\atomic_data_manager\\stats", + "childrenFiles": [ + { + "name": "count.py" + }, + { + "name": "misc.py" + }, + { + "name": "missing.py" + }, + { + "name": "unnamed.py" + }, + { + "name": "unused.py" + }, + { + "name": "users.py" + } + ], + "childrenWereProcessed": true + }, + { + "absPath": "E:\\atomic_data_manager\\ui", + "childrenDirs": [ + { + "absPath": "E:\\atomic_data_manager\\ui\\utils", + "childrenFiles": [ + { + "name": "ui_layouts.py" + } + ], + "childrenWereProcessed": true + } + ], + "childrenFiles": [ + { + "name": "__init__.py" + }, + { + "name": "inspect_ui.py" + }, + { + "name": "main_panel_ui.py" + }, + { + "name": "missing_file_ui.py" + }, + { + "name": "pie_menu_ui.py" + }, + { + "name": "preferences_ui.py" + }, + { + "name": "stats_panel_ui.py" + }, + { + "name": "support_me_ui.py" + } + ], + "childrenWereProcessed": true + }, + { + "absPath": "E:\\atomic_data_manager\\utils", + "childrenFiles": [ + { + "name": "__init__.py" + }, + { + "name": "compat.py" + }, + { + "name": "version.py" + } + ], + "childrenWereProcessed": true + } + ], + "childrenFiles": [ + { + "name": "__init__.py" + }, + { + "name": ".cursorindexingignore" + }, + { + "name": ".gitignore" + }, + { + "name": "blender_manifest.toml" + }, + { + "name": "CHANGELOG.md" + }, + { + "name": "config.py" + }, + { + "name": "LICENSE.txt" + }, + { + "name": "rainys_repo_bootstrap.py" + }, + { + "name": "README.md" + } + ], + "childrenWereProcessed": true + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **glob_file_search** • Searched codebase "undefined" • **31** results in **1** directory + +Directory: **E:\atomic_data_manager** (31 files) + +| File | +|------| +| `../E:\atomic_data_manager\stats\missing.py` | +| `../E:\atomic_data_manager\stats\count.py` | +| `../E:\atomic_data_manager\ops\inspect_ops.py` | +| `../E:\atomic_data_manager\ops\main_ops.py` | +| `../E:\atomic_data_manager\utils\compat.py` | +| `../E:\atomic_data_manager\stats\unnamed.py` | +| `../E:\atomic_data_manager\stats\unused.py` | +| `../E:\atomic_data_manager\ops\utils\nuke.py` | +| `../E:\atomic_data_manager\__init__.py` | +| `../E:\atomic_data_manager\ops\direct_use_ops.py` | +| `../E:\atomic_data_manager\ui\inspect_ui.py` | +| `../E:\atomic_data_manager\ui\main_panel_ui.py` | +| `../E:\atomic_data_manager\ops\missing_file_ops.py` | +| `../E:\atomic_data_manager\ui\missing_file_ui.py` | +| `../E:\atomic_data_manager\ops\support_me_ops.py` | +| `../E:\atomic_data_manager\ui\pie_menu_ui.py` | +| `../E:\atomic_data_manager\ui\preferences_ui.py` | +| `../E:\atomic_data_manager\ui\stats_panel_ui.py` | +| `../E:\atomic_data_manager\ui\support_me_ui.py` | +| `../E:\atomic_data_manager\stats\users.py` | +| `../E:\atomic_data_manager\rainys_repo_bootstrap.py` | +| `../E:\atomic_data_manager\config.py` | +| `../E:\atomic_data_manager\ops\__init__.py` | +| `../E:\atomic_data_manager\ui\__init__.py` | +| `../E:\atomic_data_manager\utils\version.py` | +| `../E:\atomic_data_manager\utils\__init__.py` | +| `../E:\atomic_data_manager\ops\utils\clean.py` | +| `../E:\atomic_data_manager\stats\misc.py` | +| `../E:\atomic_data_manager\ops\utils\delete.py` | +| `../E:\atomic_data_manager\ops\utils\duplicate.py` | +| `../E:\atomic_data_manager\ui\utils\ui_layouts.py` | + +
+
+ +--- + + + + + +--- + + +
Tool use: **read_file_v2** • Read file: E:\atomic_data_manager\utils\version.py + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: E:\atomic_data_manager\utils\compat.py + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "bpy\\.app\\.version|blender.*version|version.*blender", + "path": "E:\\atomic_data_manager", + "caseInsensitive": true +} +``` +Result: + +```json +{ + "success": { + "pattern": "bpy\\.app\\.version|blender.*version|version.*blender", + "path": "E:\\atomic_data_manager", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": { + "matches": [ + { + "file": "E:\\atomic_data_manager\\CHANGELOG.md", + "matches": [ + { + "lineNumber": 20, + "content": "- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\blender_manifest.toml", + "matches": [ + { + "lineNumber": 9, + "content": "blender_version_min = \"4.2.0\"" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\.specstory\\history\\2025-12-16_23-24Z-blender-library-packed-files-error.md", + "matches": [ + { + "lineNumber": 1361, + "content": "this should be changed for blender 5.0 compat, because I think previous versions may have used packed_file." + }, + { + "lineNumber": 1368, + "content": "Making the code version-aware for Blender 4.2/4.5 vs 5.0. Checking the version utilities and updating the code:" + }, + { + "lineNumber": 1390, + "content": " \"query\": \"How are Blender version differences handled for Image packed_files vs packed_file?\"," + }, + { + "lineNumber": 1409, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains functions that detect missing files in the Blender\\nproject.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nimport os\\n\\n\\ndef get_missing(data):\\n # returns a list of keys of unpacked data-blocks with non-existent\\n # filepaths\\n\\n missing = []\\n\\n # list of keys that should not be flagged\\n do_not_flag = [\\\"Render Result\\\", \\\"Viewer Node\\\", \\\"D-NOISE Export\\\"]\\n\\n for datablock in data:\\n\\n # the absolute path to our data-block\\n abspath = bpy.path.abspath(datablock.filepath)\\n\\n # Check if data-block is packed\\n # Image objects have 'packed_files' (plural), Library objects have 'packed_file' (singular)\\n is_packed = False\\n if hasattr(datablock, 'packed_files'):\\n is_packed = bool(datablock.packed_files)\\n elif hasattr(datablock, 'packed_file'):\\n is_packed = bool(datablock.packed_file)\\n\\n # if data-block is not packed and has an invalid filepath\\n if not is_packed and not os.path.isfile(abspath):\\n\\n # if data-block is not in our do not flag list\\n # append it to the missing data list\\n if datablock.name not in do_not_flag:\\n missing.append(datablock.name)\\n\\n # if data-block is packed but it does not have a filepath\\n elif is_packed and not abspath:\\n\\n # if data-block is not in our do not flag list\"," + }, + { + "lineNumber": 1661, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis module provides version detection and comparison utilities for\\nmulti-version Blender support (4.2 LTS, 4.5 LTS, and 5.0).\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\n\\n# Version constants\\nVERSION_4_2_LTS = (4, 2, 0)\\nVERSION_4_5_LTS = (4, 5, 0)\\nVERSION_5_0 = (5, 0, 0)\\n\\n\\ndef get_blender_version():\\n \\\"\\\"\\\"\\n Returns the current Blender version as a tuple (major, minor, patch).\\n \\n Returns:\\n tuple: (major, minor, patch) version numbers\\n \\\"\\\"\\\"\\n return bpy.app.version\\n\\n\\ndef get_version_string():\\n \\\"\\\"\\\"\\n Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\\n \\n Returns:\\n str: Version string in format \\\"major.minor.patch\\\"\\n \"," + }, + { + "lineNumber": 1743, + "content": " \"text\": \"multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0).\"," + }, + { + "lineNumber": 1786, + "content": " \"text\": \"def get_blender_version():\"," + }, + { + "lineNumber": 1795, + "content": " \"text\": \" Returns the current Blender version as a tuple (major, minor, patch).\"," + }, + { + "lineNumber": 1815, + "content": " \"text\": \" return bpy.app.version\"," + }, + { + "lineNumber": 1835, + "content": " \"text\": \" Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\"," + }, + { + "lineNumber": 1870, + "content": " \"contents\": \"def is_version_at_least(major, minor=0, patch=0):\\n \\\"\\\"\\\"\\n Check if the current Blender version is at least the specified version.\\n \\n Args:\\n major (int): Major version number\\n minor (int): Minor version number (default: 0)\\n patch (int): Patch version number (default: 0)\\n \\n Returns:\\n bool: True if current version &gt;= specified version\\n \\\"\\\"\\\"\\n current = get_blender_version()\\n target = (major, minor, patch)\\n \\n if current[0] != target[0]:\\n return current[0] &gt; target[0]\\n if current[1] != target[1]:\\n return current[1] &gt; target[1]\\n return current[2] &gt;= target[2]\\n\\n\\ndef is_version_less_than(major, minor=0, patch=0):\\n \\\"\\\"\\\"\"," + }, + { + "lineNumber": 1883, + "content": " \"text\": \" Check if the current Blender version is at least the specified version.\"," + }, + { + "lineNumber": 1923, + "content": " \"text\": \" current = get_blender_version()\"," + }, + { + "lineNumber": 1986, + "content": " \"contents\": \"def get_version_category():\\n \\\"\\\"\\\"\\n Returns the version category string for the current Blender version.\\n \\n Returns:\\n str: '4.2', '4.5', or '5.0' based on the current version\\n \\\"\\\"\\\"\\n version = get_blender_version()\\n major, minor = version[0], version[1]\\n \\n if major == 4:\\n if minor &lt; 5:\\n return '4.2'\\n else:\\n return '4.5'\\n elif major &gt;= 5:\\n return '5.0'\\n else:\\n # Fallback for older versions\\n return f\\\"{major}.{minor}\\\"\\n\\n\\ndef is_version_4_2():\\n \\\"\\\"\\\"Check if running Blender 4.2 LTS (4.2.x only, not 4.3 or 4.4).\\\"\\\"\\\"\"," + }, + { + "lineNumber": 1999, + "content": " \"text\": \" Returns the version category string for the current Blender version.\"," + }, + { + "lineNumber": 2019, + "content": " \"text\": \" version = get_blender_version()\"," + }, + { + "lineNumber": 2100, + "content": " \"contents\": \"name: Create Release\\n\\non:\\n workflow_dispatch:\\n inputs:\\n release_tag:\\n description: 'Release tag (e.g., v2.0.0)'\\n required: true\\n type: string\\n\\njobs:\\n build:\\n runs-on: ubuntu-latest\\n permissions:\\n contents: write\\n steps:\\n - uses: actions/checkout@v4\\n\\n - name: Get Version\\n id: get_version\\n run: |\\n python - &lt;&lt;'PY'\\n import os\\n import tomllib\\n from pathlib import Path\\n\\n data = tomllib.loads(Path(\\\"blender_manifest.toml\\\").read_text(encoding=\\\"utf-8\\\"))\\n version = data[\\\"version\\\"]\\n\\n with open(os.environ[\\\"GITHUB_OUTPUT\\\"], \\\"a\\\", encoding=\\\"utf-8\\\") as fh:\\n fh.write(f\\\"version={version}\\\\n\\\")\\n fh.write(f\\\"zip_name=Atomic_Data_Manager.v{version}.zip\\\\n\\\")\\n PY\\n\\n - name: Create ZIP\\n run: |\\n ADDON_DIR=addon_files/atomic_data_manager\\n mkdir -p \\\"$ADDON_DIR\\\"\\n\\n # Copy top-level files if they exist\\n for file in __init__.py rainys_repo_bootstrap.py blender_manifest.toml README.md CHANGELOG.md LICENSE.txt config.py; do\\n if [ -f \\\"$file\\\" ]; then\\n cp \\\"$file\\\" \\\"$ADDON_DIR\\\"/\\n else\\n echo \\\"Skipping missing file: $file\\\"\\n fi\\n done\\n\\n # Copy packages\\n for dir in ops ui stats utils; do\\n if [ -d \\\"$dir\\\" ]; then\\n cp -r \\\"$dir\\\" \\\"$ADDON_DIR\\\"/\\n else\\n echo \\\"Skipping missing directory: $dir\\\"\\n fi\\n done\\n\\n # Create the zip from the temporary directory\\n cd addon_files\\n zip -r \\\"../${{ steps.get_version.outputs.zip_name }}\\\" ./*\\n\\n - name: Upload Release Asset\\n env:\\n \"," + }, + { + "lineNumber": 2362, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains operations for missing file handling. This includes\\nthe option to reload, remove, replace, and search for these missing files.\\n\\nIt also contains the post-reload report dialog that appears after\\nattempting to reload missing project files.\\n\\n# TODO: implement missing file replace and search once Blender fixes the\\n# TODO: bugs with the file chooser not opening from a dialog\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class\\nfrom bpy.utils import unregister_class\\nfrom ..stats import missing\\nfrom ..ui.utils import ui_layouts\\n\\n\\n# Atomic Data Manager Reload Missing Files Operator\\nclass ATOMIC_OT_reload_missing(bpy.types.Operator):\\n \\\"\\\"\\\"Reload missing files\\\"\\\"\\\"\\n bl_idname = \\\"atomic.reload_missing\\\"\\n bl_label = \\\"Reload Missing Files\\\"\\n\\n def execute(self, context):\\n # reload images\\n for image in bpy.data.images:\\n image.reload()\\n\\n # reload libraries\\n for library in bpy.data.libraries:\\n library.reload()\\n\\n # call reload report\\n bpy.ops.atomic.reload_report('INVOKE_DEFAULT')\\n return {'FINISHED'}\\n\\n\\n# Atomic Data Manager Reload Missing Files Report Operator\\nclass ATOMIC_OT_reload_report(bpy.types.Operator):\\n \\\"\\\"\\\"Reload report for missing files\\\"\\\"\\\"\"," + }, + { + "lineNumber": 2611, + "content": " \"contents\": \"def is_version_less_than(major, minor=0, patch=0):\\n \\\"\\\"\\\"\\n Check if the current Blender version is less than the specified version.\\n \\n Args:\\n major (int): Major version number\\n minor (int): Minor version number (default: 0)\\n patch (int): Patch version number (default: 0)\\n \\n Returns:\\n bool: True if current version &lt; specified version\\n \\\"\\\"\\\"\\n return not is_version_at_least(major, minor, patch)\\n\\n\\ndef get_version_category():\\n \\\"\\\"\\\"\\n Returns the version category string for the current Blender version.\\n \\n Returns:\\n str: '4.2', '4.5', or '5.0' based on the current version\\n \"," + }, + { + "lineNumber": 2624, + "content": " \"text\": \" Check if the current Blender version is less than the specified version.\"," + }, + { + "lineNumber": 2684, + "content": " \"text\": \" Returns the version category string for the current Blender version.\"," + }, + { + "lineNumber": 2719, + "content": " \"contents\": \"def is_version_4_2():\\n \\\"\\\"\\\"Check if running Blender 4.2 LTS (4.2.x only, not 4.3 or 4.4).\\\"\\\"\\\"\\n version = get_blender_version()\\n return version[0] == 4 and version[1] == 2\\n\\n\\ndef is_version_4_5():\\n \\\"\\\"\\\"Check if running Blender 4.5 LTS.\\\"\\\"\\\"\"," + }, + { + "lineNumber": 2732, + "content": " \"text\": \" version = get_blender_version()\"," + }, + { + "lineNumber": 2768, + "content": " \"contents\": \"schema_version = \\\"1.0.0\\\"\\n\\nid = \\\"atomic_data_manager\\\"\\nname = \\\"Atomic Data Manager\\\"\\nversion = \\\"2.0.0\\\"\\ntype = \\\"add-on\\\"\\nauthor = \\\"RaincloudTheDragon\\\"\\nmaintainer = \\\"RaincloudTheDragon\\\"\\nblender_version_min = \\\"4.2.0\\\"\\nlicense = [\\\"GPL-3.0-or-later\\\"]\\ndescription = \\\"An Intelligent Data Manager for Blender.\\\"\\nhomepage = \\\"https://github.com/grantwilk/atomic-data-manager\\\"\\ntagline = \\\"Smart cleanup and inspection of Blender data-blocks\\\"\\n\\ntags = [\\\"utility\\\", \\\"management\\\", \\\"cleanup\\\"]\\n\\n# Python modules to load for this add-on\\nmodules = [\\\"atomic_data_manager\\\"]\"," + }, + { + "lineNumber": 2803, + "content": " \"text\": \"blender_version_min = \\\"4.2.0\\\"\"," + }, + { + "lineNumber": 2857, + "content": " \"contents\": \"def is_version_5_0():\\n \\\"\\\"\\\"Check if running Blender 5.0 or later.\\\"\\\"\\\"\\n return is_version_at_least(5, 0, 0)\"," + }, + { + "lineNumber": 2888, + "content": " \"contents\": \"# Changelog\\n\\nAll notable changes to this project will be documented in this file.\\n\\n## [v2.0.0] - Raincloud's first re-release\\n\\n### Feature\\n- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\\n - Version detection utilities in &#96;utils/version.py&#96;\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\n\\n### Fixes\\n- Blender 5.0 compatibility: Fixed &#96;AttributeError&#96; when accessing scene compositor node tree (changed from &#96;scene.node_tree&#96; to &#96;scene.compositing_node_tree&#96;)\\n- Collections assigned to &#96;rigidbody_world.collection&#96; are now correctly detected as used\\n\\n### Internal\\n- GitHub Actions release workflow\\n- Integrated &#96;rainys_repo_bootstrap&#96; into &#96;__init__.py&#96; so the Rainy's Extensions repository is registered on add-on enable and the bootstrap guard resets on disable.\\n- Removed \\\"Support Remington Creative\\\" popup and all related functionality\\n - Removed Support popup preferences\"," + }, + { + "lineNumber": 2917, + "content": " \"text\": \"- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\"," + }, + { + "lineNumber": 2979, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis module provides API compatibility functions for handling differences\\nbetween Blender 4.2 LTS, 4.5 LTS, and 5.0.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class, unregister_class\\nfrom . import version\\n\\n\\ndef safe_register_class(cls):\\n \\\"\\\"\\\"\\n Safely register a class, handling any version-specific registration issues.\\n \\n Args:\\n cls: The class to register\\n \\n Returns:\\n bool: True if registration succeeded, False otherwise\\n \\\"\\\"\\\"\\n try:\\n register_class(cls)\\n return True\\n except Exception as e:\\n print(f\\\"Warning: Failed to register {cls.__name__}: {e}\\\")\\n return False\\n\\n\\ndef safe_unregister_class(cls):\\n \\\"\\\"\\\"\"," + }, + { + "lineNumber": 3189, + "content": " \"contents\": \"def get_version_string():\\n \\\"\\\"\\\"\\n Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\\n \\n Returns:\\n str: Version string in format \\\"major.minor.patch\\\"\\n \\\"\\\"\\\"\\n version = get_blender_version()\\n return f\\\"{version[0]}.{version[1]}.{version[2]}\\\"\\n\\n\\ndef is_version_at_least(major, minor=0, patch=0):\\n \\\"\\\"\\\"\"," + }, + { + "lineNumber": 3202, + "content": " \"text\": \" Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\"," + }, + { + "lineNumber": 3222, + "content": " \"text\": \" version = get_blender_version()\"," + }, + { + "lineNumber": 3262, + "content": " \"contents\": \"def get_scene_compositor_node_tree(scene):\\n \\\"\\\"\\\"\\n Get the compositor node tree from a scene, handling version differences.\\n \\n In Blender 4.2/4.5: scene.node_tree\\n In Blender 5.0+: scene.compositing_node_tree\\n \\n Args:\\n scene: The scene object\\n \\n Returns:\\n NodeTree or None: The compositor node tree if available\\n \\\"\\\"\\\"\\n # Blender 5.0+ uses compositing_node_tree\\n if version.is_version_at_least(5, 0, 0):\\n if hasattr(scene, 'compositing_node_tree') and scene.compositing_node_tree:\\n return scene.compositing_node_tree\\n else:\\n # Blender 4.2/4.5 uses node_tree\\n if hasattr(scene, 'node_tree') and scene.node_tree:\\n return scene.node_tree\\n return None\"," + }, + { + "lineNumber": 3371, + "content": " \"contents\": \"def is_version_4_5():\\n \\\"\\\"\\\"Check if running Blender 4.5 LTS.\\\"\\\"\\\"\\n return is_version_at_least(4, 5, 0) and is_version_less_than(5, 0, 0)\\n\\n\\ndef is_version_5_0():\\n \\\"\\\"\\\"Check if running Blender 5.0 or later.\\\"\\\"\\\"\"," + }, + { + "lineNumber": 3417, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains miscellaneous statistics functions.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nimport os\\n\\n\\ndef blend_size():\\n # returns the size of the current Blender file as a string\\n\\n filepath = bpy.data.filepath\\n size_bytes = os.stat(filepath).st_size if filepath != '' else -1\\n\\n kilobyte = 1024 # bytes\\n megabyte = 1048576 # bytes\\n gigabyte = 1073741824 # bytes\\n\\n if 0 &lt;= size_bytes &lt; kilobyte:\\n size_scaled = \\\"{:.1f} B\\\".format(size_bytes)\\n elif kilobyte &lt;= size_bytes &lt; megabyte:\\n size_scaled = \\\"{:.1f} KB\\\".format(size_bytes / kilobyte)\\n elif megabyte &lt;= size_bytes &lt; gigabyte:\\n size_scaled = \\\"{:.1f} MB\\\".format(size_bytes / megabyte)\\n elif size_bytes &gt;= gigabyte:\\n size_scaled = \\\"{:.1f} GB\\\".format(size_bytes / gigabyte)\\n else:\\n size_scaled = \\\"No Data!\\\"\\n\\n return size_scaled\"," + }, + { + "lineNumber": 5047, + "content": "Updating the code to be version-aware: in Blender 5.0+, Image uses `packed_files` (plural) and Library uses `packed_file` (singular); in 4.2/4.5, both use `packed_file` (singular)." + }, + { + "lineNumber": 5154, + "content": "Updated the code to be version-aware for Blender compatibility:" + }, + { + "lineNumber": 5168, + "content": "1. Checks the Blender version using `version.is_version_at_least(5, 0, 0)`" + }, + { + "lineNumber": 5172, + "content": "This ensures compatibility across supported Blender versions and resolves the error in Blender 5.0." + }, + { + "lineNumber": 5319, + "content": "The version-aware fix is working in Blender 5.0. The addon:" + }, + { + "lineNumber": 5320, + "content": "- Detects the Blender version correctly" + }, + { + "lineNumber": 7354, + "content": "2. **blender_manifest.toml**: Bumped version from `2.0.0` to `2.0.1`" + }, + { + "lineNumber": 7493, + "content": " \"contents\": \"def get_missing(data):\\n # returns a list of keys of unpacked data-blocks with non-existent\\n # filepaths\\n\\n missing = []\\n\\n # list of keys that should not be flagged\\n do_not_flag = [\\\"Render Result\\\", \\\"Viewer Node\\\", \\\"D-NOISE Export\\\"]\\n\\n for datablock in data:\\n\\n # the absolute path to our data-block\\n abspath = bpy.path.abspath(datablock.filepath)\\n\\n # Check if data-block is packed\\n # Blender 5.0+: Image objects use 'packed_files' (plural), Library objects use 'packed_file' (singular)\\n # Blender 4.2/4.5: Both Image and Library objects use 'packed_file' (singular)\\n is_packed = False\\n if version.is_version_at_least(5, 0, 0):\\n # Blender 5.0+: Check type-specific attributes\\n if isinstance(datablock, bpy.types.Image):\\n is_packed = bool(datablock.packed_files) if hasattr(datablock, 'packed_files') else False\\n elif isinstance(datablock, bpy.types.Library):\\n is_packed = bool(datablock.packed_file) if hasattr(datablock, 'packed_file') else False\\n else:\\n # Blender 4.2/4.5: Both use 'packed_file' (singular)\\n is_packed = bool(datablock.packed_file) if hasattr(datablock, 'packed_file') else False\\n\\n # if data-block is not packed and has an invalid filepath\\n if not is_packed and not os.path.isfile(abspath):\\n\\n # if data-block is not in our do not flag list\\n # append it to the missing data list\\n if datablock.name not in do_not_flag:\\n missing.append(datablock.name)\\n\\n # if data-block is packed but it does not have a filepath\\n elif is_packed and not abspath:\\n\\n # if data-block is not in our do not flag list\\n # append it to the missing data list\\n if datablock.name not in do_not_flag:\\n missing.append(datablock.name)\\n\\n return missing\\n\\n\\ndef images():\\n # returns a list of keys of images with a non-existent filepath\\n \"," + }, + { + "lineNumber": 7704, + "content": " \"contents\": \"schema_version = \\\"1.0.0\\\"\\n\\nid = \\\"atomic_data_manager\\\"\\nname = \\\"Atomic Data Manager\\\"\\nversion = \\\"2.0.1\\\"\\ntype = \\\"add-on\\\"\\nauthor = \\\"RaincloudTheDragon\\\"\\nmaintainer = \\\"RaincloudTheDragon\\\"\\nblender_version_min = \\\"4.2.0\\\"\\nlicense = [\\\"GPL-3.0-or-later\\\"]\\ndescription = \\\"An Intelligent Data Manager for Blender.\\\"\\nhomepage = \\\"https://github.com/grantwilk/atomic-data-manager\\\"\\ntagline = \\\"Smart cleanup and inspection of Blender data-blocks\\\"\\n\\ntags = [\\\"utility\\\", \\\"management\\\", \\\"cleanup\\\"]\\n\\n# Python modules to load for this add-on\\nmodules = [\\\"atomic_data_manager\\\"]\"," + }, + { + "lineNumber": 7739, + "content": " \"text\": \"blender_version_min = \\\"4.2.0\\\"\"," + }, + { + "lineNumber": 9142, + "content": " \"contents\": \"## [v2.0.1] - 2025-12-16\\n\\n### Fixes\\n- Blender 5.0 compatibility: Fixed &#96;AttributeError&#96; when detecting missing library files (Library objects use &#96;packed_file&#96; singular, Image objects use &#96;packed_files&#96; plural in 5.0)\\n- Fixed unregistration errors in Blender 4.5 by using safe unregister functions throughout the codebase\\n\\n## [v2.0.0] - Raincloud's first re-release\\n\\n### Feature\\n- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\\n - Version detection utilities in &#96;utils/version.py&#96;\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\n\\n### Fixes\\n- Blender 5.0 compatibility: Fixed &#96;AttributeError&#96; when accessing scene compositor node tree (changed from &#96;scene.node_tree&#96; to &#96;scene.compositing_node_tree&#96;)\\n- Collections assigned to &#96;rigidbody_world.collection&#96; are now correctly detected as used\\n\\n### Internal\\n- GitHub Actions release workflow\\n- Integrated &#96;rainys_repo_bootstrap&#96; into &#96;__init__.py&#96; so the Rainy's Extensions repository is registered on add-on enable and the bootstrap guard resets on disable.\\n- Removed \\\"Support Remington Creative\\\" popup and all related functionality\\n - Removed Support popup preferences\"," + }, + { + "lineNumber": 9179, + "content": " \"text\": \"- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\"," + }, + { + "lineNumber": 10066, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains the Atomic preferences UI, preferences properties, and\\nsome functions for syncing the preference properties with external factors.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class\\nfrom ..utils import compat\\nfrom .. import config\\n# updater removed in Blender 4.5 extension format\\n\\n\\ndef _get_addon_prefs():\\n # robustly find our AddonPreferences instance regardless of module name\\n prefs = bpy.context.preferences\\n for addon in prefs.addons.values():\\n ap = getattr(addon, \\\"preferences\\\", None)\\n if ap and hasattr(ap, \\\"bl_idname\\\") and ap.bl_idname == ATOMIC_PT_preferences_panel.bl_idname:\\n return ap\\n # fallback: match by known property\\n if ap and hasattr(ap, \\\"enable_missing_file_warning\\\"):\\n return ap\\n return None\\n\\n\\ndef _save_after_pref_change():\\n \\\"\\\"\\\"\\n Persist user preferences after programmatic updates.\\n \\\"\\\"\\\"\"," + }, + { + "lineNumber": 10270, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis module provides version detection and comparison utilities for\\nmulti-version Blender support (4.2 LTS, 4.5 LTS, and 5.0).\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\n\\n# Version constants\\nVERSION_4_2_LTS = (4, 2, 0)\\nVERSION_4_5_LTS = (4, 5, 0)\\nVERSION_5_0 = (5, 0, 0)\\n\\n\\ndef get_blender_version():\\n \\\"\\\"\\\"\\n Returns the current Blender version as a tuple (major, minor, patch).\\n \\n Returns:\\n tuple: (major, minor, patch) version numbers\\n \\\"\\\"\\\"\\n return bpy.app.version\\n\\n\\ndef get_version_string():\\n \\\"\\\"\\\"\\n Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\\n \\n Returns:\\n str: Version string in format \\\"major.minor.patch\\\"\\n \"," + }, + { + "lineNumber": 10352, + "content": " \"text\": \"multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0).\"," + }, + { + "lineNumber": 10395, + "content": " \"text\": \"def get_blender_version():\"," + }, + { + "lineNumber": 10404, + "content": " \"text\": \" Returns the current Blender version as a tuple (major, minor, patch).\"," + }, + { + "lineNumber": 10424, + "content": " \"text\": \" return bpy.app.version\"," + }, + { + "lineNumber": 10444, + "content": " \"text\": \" Returns the current Blender version as a string (e.g., \\\"4.2.0\\\").\"," + }, + { + "lineNumber": 10477, + "content": " \"contents\": \"name: Create Release\\n\\non:\\n workflow_dispatch:\\n inputs:\\n release_tag:\\n description: 'Release tag (e.g., v2.0.0)'\\n required: true\\n type: string\\n\\njobs:\\n build:\\n runs-on: ubuntu-latest\\n permissions:\\n contents: write\\n steps:\\n - uses: actions/checkout@v4\\n\\n - name: Get Version\\n id: get_version\\n run: |\\n python - &lt;&lt;'PY'\\n import os\\n import tomllib\\n from pathlib import Path\\n\\n data = tomllib.loads(Path(\\\"blender_manifest.toml\\\").read_text(encoding=\\\"utf-8\\\"))\\n version = data[\\\"version\\\"]\\n\\n with open(os.environ[\\\"GITHUB_OUTPUT\\\"], \\\"a\\\", encoding=\\\"utf-8\\\") as fh:\\n fh.write(f\\\"version={version}\\\\n\\\")\\n fh.write(f\\\"zip_name=Atomic_Data_Manager.v{version}.zip\\\\n\\\")\\n PY\\n\\n - name: Create ZIP\\n run: |\\n ADDON_DIR=addon_files/atomic_data_manager\\n mkdir -p \\\"$ADDON_DIR\\\"\\n\\n # Copy top-level files if they exist\\n for file in __init__.py rainys_repo_bootstrap.py blender_manifest.toml README.md CHANGELOG.md LICENSE.txt config.py; do\\n if [ -f \\\"$file\\\" ]; then\\n cp \\\"$file\\\" \\\"$ADDON_DIR\\\"/\\n else\\n echo \\\"Skipping missing file: $file\\\"\\n fi\\n done\\n\\n # Copy packages\\n for dir in ops ui stats utils; do\\n if [ -d \\\"$dir\\\" ]; then\\n cp -r \\\"$dir\\\" \\\"$ADDON_DIR\\\"/\\n else\\n echo \\\"Skipping missing directory: $dir\\\"\\n fi\\n done\\n\\n # Create the zip from the temporary directory\\n cd addon_files\\n zip -r \\\"../${{ steps.get_version.outputs.zip_name }}\\\" ./*\\n\\n - name: Upload Release Asset\\n env:\\n \"," + }, + { + "lineNumber": 11162, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis module provides API compatibility functions for handling differences\\nbetween Blender 4.2 LTS, 4.5 LTS, and 5.0.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class, unregister_class\\nfrom . import version\\n\\n\\ndef safe_register_class(cls):\\n \\\"\\\"\\\"\\n Safely register a class, handling any version-specific registration issues.\\n \\n Args:\\n cls: The class to register\\n \\n Returns:\\n bool: True if registration succeeded, False otherwise\\n \\\"\\\"\\\"\\n try:\\n register_class(cls)\\n return True\\n except Exception as e:\\n print(f\\\"Warning: Failed to register {cls.__name__}: {e}\\\")\\n return False\\n\\n\\ndef safe_unregister_class(cls):\\n \\\"\\\"\\\"\"," + }, + { + "lineNumber": 11819, + "content": " \"contents\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains operations for missing file handling. This includes\\nthe option to reload, remove, replace, and search for these missing files.\\n\\nIt also contains the post-reload report dialog that appears after\\nattempting to reload missing project files.\\n\\n# TODO: implement missing file replace and search once Blender fixes the\\n# TODO: bugs with the file chooser not opening from a dialog\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class\\nfrom ..utils import compat\\nfrom ..stats import missing\\nfrom ..ui.utils import ui_layouts\\n\\n\\n# Atomic Data Manager Reload Missing Files Operator\\nclass ATOMIC_OT_reload_missing(bpy.types.Operator):\\n \\\"\\\"\\\"Reload missing files\\\"\\\"\\\"\\n bl_idname = \\\"atomic.reload_missing\\\"\\n bl_label = \\\"Reload Missing Files\\\"\\n\\n def execute(self, context):\\n # reload images\\n for image in bpy.data.images:\\n image.reload()\\n\\n # reload libraries\\n for library in bpy.data.libraries:\\n library.reload()\\n\\n # call reload report\\n bpy.ops.atomic.reload_report('INVOKE_DEFAULT')\\n return {'FINISHED'}\\n\\n\\n# Atomic Data Manager Reload Missing Files Report Operator\\nclass ATOMIC_OT_reload_report(bpy.types.Operator):\\n \\\"\\\"\\\"Reload report for missing files\\\"\\\"\\\"\"," + }, + { + "lineNumber": 12182, + "content": " \"content\": \" \\\"contents\\\": \\\"\\\\\\\"\\\\\\\"\\\\\\\"\\\\nCopyright (C) 2019 Remington Creative\\\\n\\\\nThis file is part of Atomic Data Manager.\\\\n\\\\nAtomic Data Manager is free software: you can redistribute\\\\nit and/or modify it under the terms of the GNU General Public License\\\\nas published by the Free Software Foundation, either version 3 of the\\\\nLicense, or (at your option) any later version.\\\\n\\\\nAtomic Data Manager is distributed in the hope that it will\\\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\\\nGeneral Public License for more details.\\\\n\\\\nYou should have received a copy of the GNU General Public License along\\\\nwith Atomic Data Manager. If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.\\\\n\\\\n---\\\\n\\\\nThis file contains functions that detect missing files in the Blender\\\\nproject.\\\\n\\\\n\\\\\\\"\\\\\\\"\\\\\\\"\\\\n\\\\nimport bpy\\\\nimport os\\\\n\\\\n\\\\ndef get_missing(data):\\\\n # returns a list of keys of unpacked data-blocks with non-existent\\\\n # fi\"" + }, + { + "lineNumber": 12202, + "content": " \"content\": \"Updating the code to be version-aware: in Blender 5.0+, Image uses &#96;packed_files&#96; (plural) and Library uses &#96;packed_file&#96; (singular); in 4.2/4.5, both use &#96;packed_file&#96; (singular).\"" + }, + { + "lineNumber": 15038, + "content": " \"contents\": \"def get_missing(data):\\n # returns a list of keys of unpacked data-blocks with non-existent\\n # filepaths\\n\\n missing = []\\n\\n # list of keys that should not be flagged\\n do_not_flag = [\\\"Render Result\\\", \\\"Viewer Node\\\", \\\"D-NOISE Export\\\"]\\n\\n for datablock in data:\\n\\n # the absolute path to our data-block\\n abspath = bpy.path.abspath(datablock.filepath)\\n\\n # Check if data-block is packed\\n # Blender 5.0+: Image objects use 'packed_files' (plural), Library objects use 'packed_file' (singular)\\n # Blender 4.2/4.5: Both Image and Library objects use 'packed_file' (singular)\\n is_packed = False\\n if version.is_version_at_least(5, 0, 0):\\n # Blender 5.0+: Check type-specific attributes\\n if isinstance(datablock, bpy.types.Image):\\n is_packed = bool(datablock.packed_files) if hasattr(datablock, 'packed_files') else False\\n elif isinstance(datablock, bpy.types.Library):\\n is_packed = bool(datablock.packed_file) if hasattr(datablock, 'packed_file') else False\\n else:\\n # Blender 4.2/4.5: Both use 'packed_file' (singular)\\n is_packed = bool(datablock.packed_file) if hasattr(datablock, 'packed_file') else False\\n\\n # if data-block is not packed and has an invalid filepath\\n if not is_packed and not os.path.isfile(abspath):\\n\\n # if data-block is not in our do not flag list\\n # append it to the missing data list\\n if datablock.name not in do_not_flag:\\n missing.append(datablock.name)\\n\\n # if data-block is packed but it does not have a filepath\\n elif is_packed and not abspath:\\n\\n # if data-block is not in our do not flag list\\n # append it to the missing data list\\n if datablock.name not in do_not_flag:\\n missing.append(datablock.name)\\n\\n return missing\\n\\n\\ndef images():\\n # returns a list of keys of images with a non-existent filepath\\n \"," + }, + { + "lineNumber": 20631, + "content": "**Solution:** Disable the installed version in Blender's preferences:" + }, + { + "lineNumber": 20643, + "content": "@blender (229-330) ah, it was because there was another version of the addon in this blender, now it's good." + }, + { + "lineNumber": 21381, + "content": "1. **blender_manifest.toml**: Version bumped from `2.0.1` to `2.0.2`" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\.specstory\\history\\2025-12-01_20-15Z-blender-integration-error-troubleshooting.md", + "matches": [ + { + "lineNumber": 9, + "content": "@.specstory/history/2025-12-01_19-37Z-integrate-multi-blender-version-support.md there, I did it. Now we can actually get it working." + }, + { + "lineNumber": 438, + "content": "All direct uses of `bpy.context.scene.node_tree` have been replaced with the version-safe compatibility function. The addon should now work in Blender 5.0 without the `AttributeError`." + }, + { + "lineNumber": 440, + "content": "The fix automatically detects the Blender version and uses the correct API, so it will work across 4.2 LTS, 4.5 LTS, and 5.0." + }, + { + "lineNumber": 1247, + "content": "Updating the version to 2.0.0 in both `bl_info` (for 4.2 LTS) and `blender_manifest.toml` (for 4.5+):" + }, + { + "lineNumber": 1347, + "content": "1. **`bl_info[\"version\"]`** in `__init__.py` — changed from `(1, 1, 0)` to `(2, 0, 0)` (for Blender 4.2 LTS)" + }, + { + "lineNumber": 1348, + "content": "2. **`version`** in `blender_manifest.toml` — changed from `\"1.1.0\"` to `\"2.0.0\"` (for Blender 4.5+)" + }, + { + "lineNumber": 1350, + "content": "The version is now 2.0.0 across all supported Blender versions." + }, + { + "lineNumber": 2175, + "content": "+ - Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)" + }, + { + "lineNumber": 2186, + "content": "+ - Minimum Blender version requirement updated to 4.2.0 (was 4.5.0)" + }, + { + "lineNumber": 2228, + "content": "- **Changed**: Version bump and minimum Blender version requirement" + }, + { + "lineNumber": 2383, + "content": "I’m sorting through the instructions to remove the `bl_info` block and update metadata to use only `blender_manifest` with a 4.2 minimum version. Also, I’m double-checking configuration options that were removed unrelated to the support_me feature, aiming to restore any general preferences or functions unintentionally deleted earlier, while keeping support_me-specific settings out. Since things are a bit unclear about which config items to bring back, I’m comparing current and prior files carefully to avoid breaking anything or missing user intentions.</details></think>" + }, + { + "lineNumber": 2759, + "content": " \"diffString\": \" from . import ui\\n from .ui import inspect_ui\\n- from .utils import version\\n from . import rainys_repo_bootstrap\\n- # updater removed for Blender 4.5 extension format\\n- \\n- # bl_info is required for Blender 4.2 LTS, replaced by blender_manifest.toml in 4.3+\\n- # Conditionally define bl_info only for 4.2 LTS (4.2.0 to 4.2.x)\\n- if version.is_version_4_2():\\n- bl_info = {\\n- \\\"name\\\": \\\"Atomic Data Manager\\\",\\n- \\\"author\\\": \\\"RaincloudTheDragon\\\",\\n- \\\"version\\\": (2, 0, 0),\\n- \\\"blender\\\": (4, 2, 0),\\n- \\\"location\\\": \\\"Properties &gt; Scene\\\",\\n- \\\"description\\\": \\\"An Intelligent Data Manager for Blender.\\\",\\n- \\\"warning\\\": \\\"\\\",\\n- \\\"doc_url\\\": \\\"https://github.com/grantwilk/atomic-data-manager\\\",\\n- \\\"category\\\": \\\"Utility\\\",\\n- }\\n \\n \"," + }, + { + "lineNumber": 2866, + "content": " \"contentsAfterEdit\": \"\\\"\\\"\\\"\\nCopyright (C) 2019 Remington Creative\\n\\nThis file is part of Atomic Data Manager.\\n\\nAtomic Data Manager is free software: you can redistribute\\nit and/or modify it under the terms of the GNU General Public License\\nas published by the Free Software Foundation, either version 3 of the\\nLicense, or (at your option) any later version.\\n\\nAtomic Data Manager is distributed in the hope that it will\\nbe useful, but WITHOUT ANY WARRANTY; without even the implied warranty\\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\\nGeneral Public License for more details.\\n\\nYou should have received a copy of the GNU General Public License along\\nwith Atomic Data Manager. If not, see &lt;https://www.gnu.org/licenses/&gt;.\\n\\n---\\n\\nThis file contains the Atomic preferences UI, preferences properties, and\\nsome functions for syncing the preference properties with external factors.\\n\\n\\\"\\\"\\\"\\n\\nimport bpy\\nfrom bpy.utils import register_class\\nfrom bpy.utils import unregister_class\\nfrom .. import config\\n# updater removed in Blender 4.5 extension format\\n\\n\\ndef _get_addon_prefs():\\n # robustly find our AddonPreferences instance regardless of module name\\n prefs = bpy.context.preferences\\n for addon in prefs.addons.values():\\n ap = getattr(addon, \\\"preferences\\\", None)\\n if ap and hasattr(ap, \\\"bl_idname\\\") and ap.bl_idname == ATOMIC_PT_preferences_panel.bl_idname:\\n return ap\\n # fallback: match by known property\\n if ap and hasattr(ap, \\\"enable_missing_file_warning\\\"):\\n return ap\\n return None\\n\\n\\ndef _save_after_pref_change():\\n \\\"\\\"\\\"\\n Persist user preferences after programmatic updates.\\n \\\"\\\"\\\"\\n bpy.ops.wm.save_userpref()\\n\\n\\ndef set_enable_missing_file_warning(value):\\n \\\"\\\"\\\"\\n Programmatically toggle the missing file warning preference.\\n \\\"\\\"\\\"\\n ap = _get_addon_prefs()\\n if not ap:\\n return\\n ap.enable_missing_file_warning = value\\n copy_prefs_to_config(None, None)\\n _save_after_pref_change()\\n\\n\\ndef set_include_fake_users(value):\\n \\\"\\\"\\\"\\n Programmatically toggle inclusion of fake users.\\n \\\"\\\"\\\"\\n ap = _get_addon_prefs()\\n if not ap:\\n return\\n ap.include_fake_users = value\\n copy_prefs_to_config(None, None)\\n _save_after_pref_change()\\n\\n\\ndef set_enable_pie_menu_ui(value):\\n \\\"\\\"\\\"\\n Programmatically toggle the pie menu UI preference.\\n \\\"\\\"\\\"\\n ap = _get_addon_prefs()\\n if not ap:\\n return\\n ap.enable_pie_menu_ui = value\\n copy_prefs_to_config(None, None)\\n _save_after_pref_change()\\n\\n\\n\\n\\ndef copy_prefs_to_config(self, context):\\n # copies the values of Atomic's preferences to the variables in\\n # config.py for global use\\n\\n atomic_preferences = _get_addon_prefs()\\n if not atomic_preferences:\\n return\\n\\n # visible atomic preferences\\n config.enable_missing_file_warning = \\\\\\n atomic_preferences.enable_missing_file_warning\\n\\n config.enable_pie_menu_ui = \\\\\\n atomic_preferences.enable_pie_menu_ui\\n\\n config.include_fake_users = \\\\\\n atomic_preferences.include_fake_users\\n\\n # hidden atomic preferences\\n config.pie_menu_type = \\\\\\n atomic_preferences.pie_menu_type\\n\\n config.pie_menu_alt = \\\\\\n atomic_preferences.pie_menu_alt\\n\\n config.pie_menu_any = \\\\\\n atomic_preferences.pie_menu_any\\n\\n config.pie_menu_ctrl = \\\\\\n atomic_preferences.pie_menu_ctrl\\n\\n config.pie_menu_oskey = \\\\\\n atomic_preferences.pie_menu_oskey\\n\\n config.pie_menu_shift = \\\\\\n atomic_preferences.pie_menu_shift\\n\\n\\ndef update_pie_menu_hotkeys(self, context):\\n atomic_preferences = _get_addon_prefs()\\n if not atomic_preferences:\\n return\\n\\n # add the hotkeys if the preference is enabled\\n if atomic_preferences.enable_pie_menu_ui:\\n add_pie_menu_hotkeys()\\n\\n # remove the hotkeys otherwise\\n else:\\n remove_pie_menu_hotkeys()\\n\\n\\ndef add_pie_menu_hotkeys():\\n # adds the pie menu hotkeys to blender's addon keymaps\\n\\n global keymaps\\n keyconfigs = bpy.context.window_manager.keyconfigs.addon\\n\\n # check to see if a window keymap already exists\\n if \\\"Window\\\" in keyconfigs.keymaps.keys():\\n km = keyconfigs.keymaps['Window']\\n\\n # if not, crate a new one\\n else:\\n km = keyconfigs.keymaps.new(\\n name=\\\"Window\\\",\\n space_type='EMPTY',\\n region_type='WINDOW'\\n )\\n\\n # add a new keymap item to that keymap\\n kmi = km.keymap_items.new(\\n idname=\\\"atomic.invoke_pie_menu_ui\\\",\\n type=config.pie_menu_type,\\n value=\\\"PRESS\\\",\\n alt=config.pie_menu_alt,\\n any=config.pie_menu_any,\\n ctrl=config.pie_menu_ctrl,\\n oskey=config.pie_menu_oskey,\\n shift=config.pie_menu_shift,\\n )\\n\\n # # point the keymap item to our pie menu\\n # kmi.properties.name = \\\"ATOMIC_MT_main_pie\\\"\\n keymaps.append((km, kmi))\\n\\n\\ndef remove_pie_menu_hotkeys():\\n # removes the pie menu hotkeys from blender's addon keymaps if they\\n # exist there\\n\\n global keymaps\\n\\n # remove each hotkey in our keymaps list if it exists in blenders\\n # addon keymaps\\n for km, kmi in keymaps:\\n km.keymap_items.remove(kmi)\\n\\n # clear our keymaps list\\n keymaps.clear()\\n\\n\\n# Atomic Data Manager Preference Panel UI\\nclass ATOMIC_PT_preferences_panel(bpy.types.AddonPreferences):\\n bl_idname = \\\"atomic_data_manager\\\"\\n\\n # visible atomic preferences\\n enable_missing_file_warning: bpy.props.BoolProperty(\\n description=\\\"Display a warning on startup if Atomic detects \\\"\\n \\\"missing files in your project\\\",\\n default=True\\n )\\n\\n include_fake_users: bpy.props.BoolProperty(\\n description=\\\"Include data-blocks with only fake users in unused \\\"\\n \\\"data detection\\\",\\n default=False\\n )\\n\\n enable_pie_menu_ui: bpy.props.BoolProperty(\\n description=\\\"Enable the Atomic pie menu UI, so you can clean \\\"\\n \\\"your project from anywhere.\\\",\\n default=True,\\n update=update_pie_menu_hotkeys\\n )\\n\\n # hidden atomic preferences\\n pie_menu_type: bpy.props.StringProperty(\\n default=\\\"D\\\"\\n )\\n\\n pie_menu_alt: bpy.props.BoolProperty(\\n default=False\\n )\\n\\n pie_menu_any: bpy.props.BoolProperty(\\n default=False\\n )\\n\\n pie_menu_ctrl: bpy.props.BoolProperty(\\n default=False\\n )\\n\\n pie_menu_oskey: bpy.props.BoolProperty(\\n default=False\\n )\\n\\n pie_menu_shift: bpy.props.BoolProperty(\\n default=False\\n )\\n\\n # updater properties removed\\n\\n def draw(self, context):\\n layout = self.layout\\n\\n split = layout.split()\\n\\n # left column\\n col = split.column()\\n\\n # enable missing file warning toggle\\n col.prop(\\n self,\\n \\\"enable_missing_file_warning\\\",\\n text=\\\"Show Missing File Warning\\\"\\n )\\n\\n # right column\\n col = split.column()\\n\\n # ignore fake users toggle\\n col.prop(\\n self,\\n \\\"include_fake_users\\\",\\n text=\\\"Include Fake Users\\\"\\n )\\n\\n # pie menu settings\\n pie_split = col.split(factor=0.55) # nice\\n\\n # enable pie menu ui toggle\\n pie_split.prop(\\n self,\\n \\\"enable_pie_menu_ui\\\",\\n text=\\\"Enable Pie Menu\\\"\\n )\\n\\n # put the property in a row so it can be disabled\\n pie_row = pie_split.row()\\n pie_row.enabled = self.enable_pie_menu_ui\\n\\n if pie_row.enabled:\\n # keymap item that contains our pie menu hotkey\\n # note: keymap item index hardcoded with an index -- may be\\n # dangerous if more keymap items are added\\n kmi = bpy.context.window_manager.keyconfigs.addon.keymaps[\\n 'Window'].keymap_items[0]\\n\\n # hotkey property\\n pie_row.prop(\\n kmi,\\n \\\"type\\\",\\n text=\\\"\\\",\\n full_event=True\\n )\\n\\n # update hotkey preferences\\n self.pie_menu_type = kmi.type\\n self.pie_menu_any = kmi.any\\n self.pie_menu_alt = kmi.alt\\n self.pie_menu_ctrl = kmi.ctrl\\n self.pie_menu_oskey = kmi.oskey\\n self.pie_menu_shift = kmi.shift\\n\\n separator = layout.row() # extra space\\n\\n # updater UI removed\\n\\n # update config with any new preferences\\n copy_prefs_to_config(None, None)\\n\\n\\nreg_list = [ATOMIC_PT_preferences_panel]\\nkeymaps = []\\n\\n\\ndef register():\\n for cls in reg_list:\\n register_class(cls)\\n\\n # make sure global preferences are updated on registration\\n copy_prefs_to_config(None, None)\\n\\n # update keymaps\\n add_pie_menu_hotkeys()\\n\\n\\ndef unregister():\\n for cls in reg_list:\\n unregister_class(cls)\\n\\n remove_pie_menu_hotkeys()\\n\"" + }, + { + "lineNumber": 2912, + "content": " \"diffString\": \" - Version detection utilities in &#96;utils/version.py&#96;\\r\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\r\\n+ - Blender metadata now relies solely on &#96;blender_manifest.toml&#96; (legacy &#96;bl_info&#96; removed)\\r\\n \\r\\n ### Fixes\\r\"," + }, + { + "lineNumber": 2933, + "content": " \"contentsAfterEdit\": \"# Changelog\\r\\n\\r\\nAll notable changes to this project will be documented in this file.\\r\\n\\r\\n## [v2.0.0] - Raincloud's first re-release\\r\\n\\r\\n### Feature\\r\\n- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\\r\\n - Version detection utilities in &#96;utils/version.py&#96;\\r\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\r\\n- Blender metadata now relies solely on &#96;blender_manifest.toml&#96; (legacy &#96;bl_info&#96; removed)\\r\\n\\r\\n### Fixes\\r\\n- Blender 5.0 compatibility: Fixed &#96;AttributeError&#96; when accessing scene compositor node tree (changed from &#96;scene.node_tree&#96; to &#96;scene.compositing_node_tree&#96;)\\r\\n- Collections assigned to &#96;rigidbody_world.collection&#96; are now correctly detected as used\\r\\n\\r\\n### Internal\\r\\n- GitHub Actions release workflow\\r\\n- Integrated &#96;rainys_repo_bootstrap&#96; into &#96;__init__.py&#96; so the Rainy's Extensions repository is registered on add-on enable and the bootstrap guard resets on disable.\\r\\n- Removed \\\"Support Remington Creative\\\" popup and all related functionality\\r\\n - Removed Support popup preferences\\r\\n- Restored non-support preference helper setters for scriptable configuration updates\"" + }, + { + "lineNumber": 3054, + "content": " \"diffString\": \" steps:\\r\\n - uses: actions/checkout@v4\\r\\n- \\r\\n- - name: Install dependencies\\r\\n- run: pip install toml\\r\\n- \\r\\n+ \\r\\n - name: Get Version\\r\\n id: get_version\\r\\n run: |\\r\\n- VERSION=$(python -c \\\"import toml; print(toml.load('blender_manifest.toml')['version'])\\\")\\r\\n- echo \\\"version=$VERSION\\\" &gt;&gt; $GITHUB_OUTPUT\\r\\n- echo \\\"zip_name=atomic_data_manager.${{ inputs.release_tag }}.zip\\\" &gt;&gt; $GITHUB_OUTPUT\\r\\n- \\r\\n+ python - &lt;&lt;'PY'\\r\\n+ import os\\r\\n+ import tomllib\\r\\n+ from pathlib import Path\\r\\n+ \\r\\n+ data = tomllib.loads(Path(\\\"blender_manifest.toml\\\").read_text(encoding=\\\"utf-8\\\"))\\r\\n+ version = data[\\\"version\\\"]\\r\\n+ \\r\\n+ with open(os.environ[\\\"GITHUB_OUTPUT\\\"], \\\"a\\\", encoding=\\\"utf-8\\\") as fh:\\r\\n+ fh.write(f\\\"version={version}\\\\n\\\")\\r\\n+ fh.write(f\\\"zip_name=Atomic_Data_Manager.v{version}.zip\\\\n\\\")\\r\\n+ PY\\r\\n+ \\r\\n - name: Create ZIP\\r\\n run: |\\r\\n- # Create a temporary directory structure\\r\\n- mkdir -p addon_files/atomic_data_manager\\r\\n- \\r\\n- # Copy the required files for Blender Extensions\\r\\n- cp __init__.py rainys_repo_bootstrap.py README.md blender_manifest.toml LICENSE.txt config.py addon_files/atomic_data_manager/\\r\\n- \\r\\n- # Copy subdirectories\\r\\n- cp -r ops ui stats utils addon_files/atomic_data_manager/\\r\\n- \\r\\n- # Create the ZIP file\\r\\n+ ADDON_DIR=addon_files/atomic_data_manager\\r\\n+ mkdir -p \\\"$ADDON_DIR\\\"\\r\\n+ \\r\\n+ # Copy top-level files if they exist\\r\\n+ for file in __init__.py rainys_repo_bootstrap.py blender_manifest.toml README.md CHANGELOG.md LICENSE.txt config.py; do\\r\\n+ if [ -f \\\"$file\\\" ]; then\\r\\n+ cp \\\"$file\\\" \\\"$ADDON_DIR\\\"/\\r\\n+ else\\r\\n+ echo \\\"Skipping missing file: $file\\\"\\r\\n+ fi\\r\\n+ done\\r\\n+ \\r\\n+ # Copy packages\\r\\n+ for dir in ops ui stats utils; do\\r\\n+ if [ -d \\\"$dir\\\" ]; then\\r\\n+ cp -r \\\"$dir\\\" \\\"$ADDON_DIR\\\"/\\r\\n+ else\\r\\n+ echo \\\"Skipping missing directory: $dir\\\"\\r\\n+ fi\\r\\n+ done\\r\\n+ \\r\\n+ # Create the zip from the temporary directory\\r\\n cd addon_files\\r\\n- zip -r ../${{ steps.get_version.outputs.zip_name }} atomic_data_manager/\\r\\n- cd ..\\r\\n- \\r\\n- - name: Create Release\\r\\n+ zip -r \\\"../${{ steps.get_version.outputs.zip_name }}\\\" ./*\\r\\n+ \\r\\n+ - name: Upload Release Asset\\r\\n+ env:\\r\\n+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\\r\\n uses: softprops/action-gh-release@v1\\r\\n with:\\r\\n+ files: ${{ steps.get_version.outputs.zip_name }}\\r\\n tag_name: ${{ inputs.release_tag }}\\r\\n name: Atomic Data Manager ${{ steps.get_version.outputs.version }}\\r\\n- files: ${{ steps.get_version.outputs.zip_name }}\\r\\n- draft: false\\r\\n- prerelease: false\\r\\n- env:\\r\\n- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\\r\\n- \\r\\n- \\n+ draft: true\"," + }, + { + "lineNumber": 3067, + "content": " \"contentsAfterEdit\": \"name: Create Release\\r\\n\\r\\non:\\r\\n workflow_dispatch:\\r\\n inputs:\\r\\n release_tag:\\r\\n description: 'Release tag (e.g., v2.0.0)'\\r\\n required: true\\r\\n type: string\\r\\n\\r\\njobs:\\r\\n build:\\r\\n runs-on: ubuntu-latest\\r\\n permissions:\\r\\n contents: write\\r\\n steps:\\r\\n - uses: actions/checkout@v4\\r\\n\\r\\n - name: Get Version\\r\\n id: get_version\\r\\n run: |\\r\\n python - &lt;&lt;'PY'\\r\\n import os\\r\\n import tomllib\\r\\n from pathlib import Path\\r\\n\\r\\n data = tomllib.loads(Path(\\\"blender_manifest.toml\\\").read_text(encoding=\\\"utf-8\\\"))\\r\\n version = data[\\\"version\\\"]\\r\\n\\r\\n with open(os.environ[\\\"GITHUB_OUTPUT\\\"], \\\"a\\\", encoding=\\\"utf-8\\\") as fh:\\r\\n fh.write(f\\\"version={version}\\\\n\\\")\\r\\n fh.write(f\\\"zip_name=Atomic_Data_Manager.v{version}.zip\\\\n\\\")\\r\\n PY\\r\\n\\r\\n - name: Create ZIP\\r\\n run: |\\r\\n ADDON_DIR=addon_files/atomic_data_manager\\r\\n mkdir -p \\\"$ADDON_DIR\\\"\\r\\n\\r\\n # Copy top-level files if they exist\\r\\n for file in __init__.py rainys_repo_bootstrap.py blender_manifest.toml README.md CHANGELOG.md LICENSE.txt config.py; do\\r\\n if [ -f \\\"$file\\\" ]; then\\r\\n cp \\\"$file\\\" \\\"$ADDON_DIR\\\"/\\r\\n else\\r\\n echo \\\"Skipping missing file: $file\\\"\\r\\n fi\\r\\n done\\r\\n\\r\\n # Copy packages\\r\\n for dir in ops ui stats utils; do\\r\\n if [ -d \\\"$dir\\\" ]; then\\r\\n cp -r \\\"$dir\\\" \\\"$ADDON_DIR\\\"/\\r\\n else\\r\\n echo \\\"Skipping missing directory: $dir\\\"\\r\\n fi\\r\\n done\\r\\n\\r\\n # Create the zip from the temporary directory\\r\\n cd addon_files\\r\\n zip -r \\\"../${{ steps.get_version.outputs.zip_name }}\\\" ./*\\r\\n\\r\\n - name: Upload Release Asset\\r\\n env:\\r\\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\\r\\n uses: softprops/action-gh-release@v1\\r\\n with:\\r\\n files: ${{ steps.get_version.outputs.zip_name }}\\r\\n tag_name: ${{ inputs.release_tag }}\\r\\n name: Atomic Data Manager ${{ steps.get_version.outputs.version }}\\r\\n draft: true\"" + }, + { + "lineNumber": 3111, + "content": " \"diffString\": \" - Version detection utilities in &#96;utils/version.py&#96;\\r\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\r\\n+ - Blender metadata now relies solely on &#96;blender_manifest.toml&#96; (legacy &#96;bl_info&#96; removed)\\r\\n \\r\\n ### Fixes\\r\"," + }, + { + "lineNumber": 3132, + "content": " \"contentsAfterEdit\": \"# Changelog\\r\\n\\r\\nAll notable changes to this project will be documented in this file.\\r\\n\\r\\n## [v2.0.0] - Raincloud's first re-release\\r\\n\\r\\n### Feature\\r\\n- Multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)\\r\\n - Version detection utilities in &#96;utils/version.py&#96;\\r\\n - API compatibility layer in &#96;utils/compat.py&#96; for handling version differences\\r\\n- Blender metadata now relies solely on &#96;blender_manifest.toml&#96; (legacy &#96;bl_info&#96; removed)\\r\\n\\r\\n### Fixes\\r\\n- Blender 5.0 compatibility: Fixed &#96;AttributeError&#96; when accessing scene compositor node tree (changed from &#96;scene.node_tree&#96; to &#96;scene.compositing_node_tree&#96;)\\r\\n- Collections assigned to &#96;rigidbody_world.collection&#96; are now correctly detected as used\\r\\n\\r\\n### Internal\\r\\n- GitHub Actions release workflow\\r\\n- Integrated &#96;rainys_repo_bootstrap&#96; into &#96;__init__.py&#96; so the Rainy's Extensions repository is registered on add-on enable and the bootstrap guard resets on disable.\\r\\n- Removed \\\"Support Remington Creative\\\" popup and all related functionality\\r\\n - Removed Support popup preferences\\r\\n- Restored non-support preference helper setters for scriptable configuration updates\"" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\.specstory\\history\\2025-12-01_20-46Z-setting-up-a-blender-extension-api.md", + "matches": [ + { + "lineNumber": 91, + "content": "| Web Search Results | | To enable instant updates for your Blender add-ons directly within Blender, you can integrate an auto-update mechanism into your add-ons. One effective approach is to utilize the [Blender Addon Updater](https://github.com/CGCookie/blender-addon-updater), a Python module designed to facilitate automatic update checks and installations for Blender add-ons. ([github.com](https://github.com/CGCookie/blender-addon-updater?utm_source=openai))<br/><br/>**Key Features of Blender Addon Updater:**<br/><br/>- **Repository Support:** Compatible with GitHub, GitLab, and Bitbucket repositories.<br/>- **One-Click Update Check:** Allows users to easily check for available updates.<br/>- **Auto-Check Capability:** Enables automatic background checks for updates, with customizable intervals.<br/>- **Contextual Update Notifications:** Notifies users when updates are available.<br/>- **Version Installation Flexibility:** Supports installation of different versions, including older or development versions.<br/><br/>**Setting Up Your Own Update Platform:**<br/><br/>You can host your add-ons on platforms like GitHub and configure the Blender Addon Updater to check for updates from your repository. This setup allows users to receive and install updates directly within Blender. While GitHub Pages can serve static content, it may not be suitable for hosting dynamic update files. Instead, hosting your add-ons directly on GitHub and utilizing its API for update checks is a more straightforward approach.<br/><br/>**Implementation Steps:**<br/><br/>1. **Integrate the Updater Module:**<br/> - Add the Blender Addon Updater module to your add-on's codebase.<br/> - Configure it to point to your GitHub repository.<br/><br/>2. **Configure Update Checks:**<br/> - Set up the updater to check for updates at specified intervals.<br/> - Enable notifications for users when updates are available.<br/><br/>3. **Handle Update Installations:**<br/> - Implement functionality to download and install updates directly within Blender.<br/> - Ensure that the add-on reloads or Blender restarts as needed to apply updates.<br/><br/>For detailed instructions and code examples, refer to the [Blender Addon Updater repository](https://github.com/CGCookie/blender-addon-updater). This resource provides comprehensive guidance on integrating the updater into your add-ons.<br/><br/>By implementing this system, you can streamline the update process for your add-ons, providing users with a seamless experience and ensuring they always have access to the latest features and fixes. |" + }, + { + "lineNumber": 113, + "content": "| Web Search Results | | To enable instant updates for your Blender add-ons directly within Blender, you can integrate an update mechanism into your add-ons that fetches the latest versions from a repository. The [Blender Addon Updater](https://github.com/CGCookie/blender-addon-updater) is a Python module designed for this purpose. It allows add-on developers to implement automatic update checks and one-click installations of new versions directly from Blender.<br/><br/>**Key Features of Blender Addon Updater:**<br/><br/>- **Repository Integration:** Supports GitHub, GitLab, or Bitbucket repositories as sources for versions and code.<br/>- **One-Click Update Check:** Users can easily check for updates with a single click.<br/>- **Auto-Check Capability:** Option to automatically check for updates in the background, with customizable intervals.<br/>- **Contextual Update Notifications:** Notifies users with a popup when an update is available.<br/>- **Version Installation Flexibility:** Allows installation of other versions, such as older or development versions.<br/><br/>To set up your own platform for hosting these updates, you can utilize GitHub Pages (`github.io`). By hosting your add-on's code and releases on a GitHub repository, you can configure the Blender Addon Updater to check this repository for updates. This setup enables users to update your add-ons directly within Blender by fetching the latest versions from your GitHub repository.<br/><br/>**Steps to Implement:**<br/><br/>1. **Host Your Add-on on GitHub:**<br/> - Create a GitHub repository for each of your add-ons.<br/> - Use GitHub Releases to manage different versions of your add-on.<br/><br/>2. **Integrate Blender Addon Updater:**<br/> - Add the Blender Addon Updater module to your add-on's codebase.<br/> - Configure it to point to your GitHub repository and releases.<br/><br/>3. **Configure Update Mechanism:**<br/> - Set up the updater to check for updates at desired intervals.<br/> - Ensure that the updater can download and install updates directly from your GitHub repository.<br/><br/>By following these steps, you can establish a seamless update mechanism for your Blender add-ons, allowing users to receive updates instantly via the repository API. |" + }, + { + "lineNumber": 135, + "content": "| Web Search Results | | To enable instant updates for your Blender add-ons directly within Blender, you can implement an auto-update mechanism that checks for new versions and installs them seamlessly. One effective approach is to utilize the [Blender Addon Updater](https://github.com/CGCookie/blender-addon-updater), a Python module designed specifically for this purpose.<br/><br/>**Key Features of Blender Addon Updater:**<br/><br/>- **Repository Integration:** Supports GitHub, GitLab, and Bitbucket repositories as sources for version control and code updates.<br/>- **One-Click Update:** Allows users to check for and install updates with a single click.<br/>- **Auto-Check Capability:** Enables automatic background checks for updates at user-defined intervals.<br/>- **Version Selection:** Provides options to install specific versions, including older or development releases.<br/><br/>**Setting Up Your Own Update Platform:**<br/><br/>To host your add-ons and manage updates, you can use GitHub Pages (`github.io`) as your platform. Here's how you can set it up:<br/><br/>1. **Host Your Add-ons on GitHub:**<br/> - Create a GitHub repository for each add-on.<br/> - Use Git tags or releases to mark different versions of your add-on.<br/><br/>2. **Configure GitHub Pages:**<br/> - Enable GitHub Pages for your repository to serve files directly from GitHub.<br/> - This will provide a URL (e.g., `https://yourusername.github.io/your-addon/`) where your add-on files can be accessed.<br/><br/>3. **Integrate Blender Addon Updater:**<br/> - Incorporate the Blender Addon Updater module into your add-on's codebase.<br/> - Configure it to check for updates by pointing it to your GitHub repository's API or the GitHub Pages URL where your add-on releases are hosted.<br/><br/>By following this setup, Blender will be able to check for updates to your add-ons hosted on GitHub and install them directly, providing a seamless update experience for users.<br/><br/>**Additional Resources:**<br/><br/>- **Blender Addon Updater Documentation:** Provides detailed instructions on integrating the updater into your add-ons.<br/>- **GitHub Pages Documentation:** Guides on setting up and managing GitHub Pages for hosting your add-on files.<br/><br/>Implementing this system will streamline the update process for your add-ons, ensuring users always have access to the latest features and fixes with minimal effort. |" + }, + { + "lineNumber": 312, + "content": "+ - Version numbers must match between catalog.json and blender_manifest.toml" + }, + { + "lineNumber": 740, + "content": "+ 2. **Update your &#96;blender_manifest.toml&#96;** with the new version:" + }, + { + "lineNumber": 1074, + "content": "| `e:/BasedPlayblast/.specstory/history/2025-12-01_21-28Z-verify-and-add-github-repository.md` | `\"contentsAfterEdit\": \"name: Create Release\\r\\n\\r\\non:\\r\\n workflow_dispatch:\\r\\n inputs:\\r\\n release_tag:\\r\\n description: 'Release tag (e.g., v0.2.0)'\\r\\n required: true\\r\\n type: string\\r\\n\\r\\njobs:\\r\\n build:\\r\\n runs-on: ubuntu-latest\\r\\n permissions:\\r\\n contents: write\\r\\n steps:\\r\\n - uses: actions/checkout@v4\\r\\n \\r\\n - name: Install dependencies\\r\\n run: pip install toml\\r\\n \\r\\n - name: Get Version\\r\\n id: get_version\\r\\n run: \\|\\r\\n VERSION=$(python -c \\\"import toml; print(toml.load('blender_manifest.toml')['version'])\\\")\\r\\n echo \\\"version=$VERSION\\\" &gt;&gt; $GITHUB_OUTPUT\\r\\n echo \\\"zip_name=BasedPlayblast.$\\{{ inputs.release_tag \\}}.zip\\\" &gt;&gt; $GITHUB_OUTPUT\\r\\n \\r\\n - name: Create ZIP\\r\\n run: \\|\\r\\n # Create a temporary directory structure\\r\\n mkdir -p addon_files/BasedPlayblast\\r\\n \\r\\n # Copy the [... omitted end of long line]` | L4078 |" + }, + { + "lineNumber": 1202, + "content": " \"plan\": \"# Integrate Bootstrap and Add Release Workflow\\n\\n## Overview\\nIntegrate &#96;rainys_repo_bootstrap.py&#96; into the addon's registration system and create a GitHub Actions release workflow that packages the addon including the bootstrap module.\\n\\n## Changes Required\\n\\n### 1. Update &#96;__init__.py&#96; to integrate bootstrap\\n- Import &#96;rainys_repo_bootstrap&#96; module\\n- Call &#96;rainys_repo_bootstrap.register()&#96; in the main &#96;register()&#96; function\\n- Call &#96;rainys_repo_bootstrap.unregister()&#96; in the main &#96;unregister()&#96; function\\n\\n**File**: [&#96;__init__.py&#96;](__init__.py)\\n- Add import: &#96;from . import rainys_repo_bootstrap&#96;\\n- In &#96;register()&#96;: Add &#96;rainys_repo_bootstrap.register()&#96; call after existing registrations\\n- In &#96;unregister()&#96;: Add &#96;rainys_repo_bootstrap.unregister()&#96; call before existing unregistrations\\n\\n### 2. Create GitHub Actions release workflow\\n- Create &#96;.github/workflows/release.yml&#96;\\n- Workflow should:\\n - Trigger on &#96;workflow_dispatch&#96; with a release tag input\\n - Read version from &#96;blender_manifest.toml&#96;\\n - Create a ZIP file with the addon structure:\\n - Folder: &#96;atomic_data_manager/&#96; (matching the module name from manifest)\\n - Include: &#96;__init__.py&#96;, &#96;rainys_repo_bootstrap.py&#96;, &#96;blender_manifest.toml&#96;, &#96;README.md&#96;, and all subdirectories (&#96;ops/&#96;, &#96;ui/&#96;, &#96;stats/&#96;, &#96;utils/&#96;)\\n - Create a GitHub release with the ZIP as an asset\\n\\n**File**: &#96;.github/workflows/release.yml&#96; (new file)\\n\\n## Implementation Details\\n\\nThe bootstrap module's &#96;register()&#96; function calls &#96;ensure_rainys_extensions_repo()&#96; which automatically adds the Rainy's Extensions repository to Blender's extension preferences. The &#96;unregister()&#96; function resets the bootstrap guard for clean re-registration.\\n\\nThe release workflow will package the entire addon directory structure into a ZIP file that can be installed directly in Blender.\"," + }, + { + "lineNumber": 1457, + "content": "+ VERSION=$(python -c \"import toml; print(toml.load('blender_manifest.toml')['version'])\")" + }, + { + "lineNumber": 1566, + "content": " - Version reading from `blender_manifest.toml`" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\.specstory\\history\\2025-12-01_19-37Z-integrate-multi-blender-version-support.md", + "matches": [ + { + "lineNumber": 5, + "content": "# Integrate multi-blender version support (2025-12-01 19:37Z)" + }, + { + "lineNumber": 9, + "content": "Let's integrate multi-blender version support. Each blender version can be called differently. Let's support 4.5 LTS, 5.0, and 4.2 LTS on the same codebase." + }, + { + "lineNumber": 20, + "content": "Reviewing the codebase to identify Blender API usage and plan multi-version support." + }, + { + "lineNumber": 51, + "content": " <summary>Tool use: **codebase_search** • Searched codebase \"How are Blender API imports and version-specific features used in the codebase?\" • **15** results</summary>" + }, + { + "lineNumber": 213, + "content": " &quot;prompt&quot;: &quot;How should we handle addon metadata? Blender 4.2 LTS uses &#96;bl_info&#96; in &#96;__init__.py&#96;, while 4.5 LTS and 5.0 use &#96;blender_manifest.toml&#96;. Should we support both formats based on version, or maintain separate builds?&quot;," + }, + { + "lineNumber": 231, + "content": " &quot;prompt&quot;: &quot;Are there specific Blender API differences between 4.2 LTS, 4.5 LTS, and 5.0 that you know need version-specific handling in this addon?&quot;," + }, + { + "lineNumber": 387, + "content": " &quot;plan&quot;: &quot;# Multi-Blender Version Support Implementation\\n\\n## Overview\\nImplement multi-version support for Blender 4.2 LTS, 4.5 LTS, and 5.0 by adding version detection infrastructure and supporting both addon metadata formats.\\n\\n## Key Changes\\n\\n### 1. Version Detection Module\\nCreate &#96;utils/version.py&#96; to:\\n- Detect Blender version using &#96;bpy.app.version&#96;\\n- Provide version comparison utilities\\n- Define version constants for 4.2 LTS, 4.5 LTS, and 5.0\\n- Export a &#96;get_blender_version()&#96; function that returns a normalized version string\\n\\n### 2. Dual Metadata Format Support\\nUpdate &#96;__init__.py&#96; to:\\n- Add &#96;bl_info&#96; dictionary for Blender 4.2 LTS compatibility\\n- Keep existing &#96;blender_manifest.toml&#96; for 4.5 LTS and 5.0\\n- Use version detection to determine which format to register\\n- Ensure both formats contain consistent metadata\\n\\n### 3. Version-Specific API Compatibility Layer\\nCreate &#96;utils/compat.py&#96; to:\\n- Provide version-specific API wrappers\\n- Handle API differences between versions\\n- Export compatibility functions that can be used throughout the codebase\\n- Include placeholders for known API differences (to be filled in based on user&#39;s specific requirements)\\n\\n### 4. Update Manifest\\nModify &#96;blender_manifest.toml&#96; to:\\n- Set &#96;blender_version_min = \\&quot;4.2.0\\&quot;&#96; to support all three versions\\n- Keep existing metadata structure\\n\\n### 5. Registration Logic\\nUpdate registration in &#96;__init__.py&#96;:\\n- Ensure registration works across all versions\\n- Add version checks if needed for version-specific registration steps\\n\\n## Implementation Details\\n\\n### Files to Create\\n- &#96;utils/__init__.py&#96; - Initialize utils package\\n- &#96;utils/version.py&#96; - Version detection and comparison utilities\\n- &#96;utils/compat.py&#96; - API compatibility layer for version-specific code\\n\\n### Files to Modify\\n- &#96;__init__.py&#96; - Add bl_info and version-aware registration\\n- &#96;blender_manifest.toml&#96; - Update minimum version requirement\\n\\n## Testing Strategy\\n- Test registration in each Blender version\\n- Verify metadata is correctly recognized\\n- Test core functionality across versions\\n- Add version-specific API tests as differences are identified&quot;," + }, + { + "lineNumber": 388, + "content": " &quot;name&quot;: &quot;Multi-Blender Version Support&quot;," + }, + { + "lineNumber": 421, + "content": " &quot;content&quot;: &quot;Update blender_manifest.toml to set minimum version to 4.2.0&quot;," + }, + { + "lineNumber": 435, + "content": " &quot;overview&quot;: &quot;Add support for Blender 4.2 LTS, 4.5 LTS, and 5.0 in the same codebase by implementing version detection, dual metadata format support (bl_info and blender_manifest.toml), and a framework for version-specific API handling.&quot;" + }, + { + "lineNumber": 477, + "content": " &quot;content&quot;: &quot;Update blender_manifest.toml to set minimum version to 4.2.0&quot;," + }, + { + "lineNumber": 857, + "content": " &quot;plan&quot;: &quot;# Multi-Blender Version Support Implementation\\n\\n## Overview\\nImplement multi-version support for Blender 4.2 LTS, 4.5 LTS, and 5.0. The addon was originally built for 2.83 (2019), updated for 4.5, but not yet for 5.0. Geometry nodes support exists but may need enhancement. API differences between versions need to be discovered and handled.\\n\\n## Key Changes\\n\\n### 1. Version Detection Module\\nCreate &#96;utils/version.py&#96; to:\\n- Detect Blender version using &#96;bpy.app.version&#96; (tuple: major, minor, patch)\\n- Provide version comparison utilities (&#96;is_version_at_least()&#96;, &#96;get_version_string()&#96;)\\n- Define version constants: VERSION_4_2_LTS = (4, 2, 0), VERSION_4_5_LTS = (4, 5, 0), VERSION_5_0 = (5, 0, 0)\\n- Export &#96;get_blender_version()&#96; that returns normalized version tuple\\n- Export &#96;get_version_category()&#96; that returns &#39;4.2&#39;, &#39;4.5&#39;, or &#39;5.0&#39;\\n\\n### 2. Dual Metadata Format Support\\nUpdate &#96;__init__.py&#96; to:\\n- Add &#96;bl_info&#96; dictionary for Blender 4.2 LTS compatibility (required for 4.2)\\n- Keep existing &#96;blender_manifest.toml&#96; for 4.5 LTS and 5.0 (required for 4.5+)\\n- Use version detection to conditionally define bl_info only for 4.2\\n- Ensure both formats contain consistent metadata (name, version, author, etc.)\\n\\n### 3. Version-Specific API Compatibility Layer\\nCreate &#96;utils/compat.py&#96; to:\\n- Provide version-specific API wrappers for discovered differences\\n- Handle API differences between 4.2, 4.5, and 5.0\\n- Export compatibility functions (e.g., &#96;safe_register_class()&#96;, &#96;get_addon_prefs()&#96;)\\n- Include framework for geometry nodes API differences if any\\n- Add placeholders for operator registration differences\\n\\n### 4. Update Manifest\\nModify &#96;blender_manifest.toml&#96; to:\\n- Set &#96;blender_version_min = \\&quot;4.2.0\\&quot;&#96; to support all three versions\\n- Keep existing metadata structure for 4.5/5.0\\n\\n### 5. Operator Compatibility\\nReview and update operators in &#96;ops/&#96;:\\n- Check for version-specific operator registration issues\\n- Ensure operators work across 4.2, 4.5, and 5.0\\n- Add version checks where API differences are discovered\\n\\n### 6. Geometry Nodes Support\\nReview existing geometry nodes code in &#96;stats/users.py&#96;:\\n- Verify geometry nodes detection works across all versions\\n- Check for API changes in modifier.node_group access\\n- Ensure compatibility with 4.2, 4.5, and 5.0 geometry nodes API\\n\\n## Implementation Details\\n\\n### Files to Create\\n- &#96;utils/__init__.py&#96; - Initialize utils package\\n- &#96;utils/version.py&#96; - Version detection and comparison utilities\\n- &#96;utils/compat.py&#96; - API compatibility layer for version-specific code\\n\\n### Files to Modify\\n- &#96;__init__.py&#96; - Add conditional bl_info and version-aware registration\\n- &#96;blender_manifest.toml&#96; - Update minimum version to 4.2.0\\n- &#96;ops/__init__.py&#96; - Review operator registration for version compatibility\\n- &#96;stats/users.py&#96; - Verify geometry nodes code works across versions\\n\\n## Discovery and Testing Strategy\\n- Test registration in each Blender version (4.2 LTS, 4.5 LTS, 5.0)\\n- Verify metadata is correctly recognized in each version\\n- Test core functionality (nuke, clean, inspect) across versions\\n- Discover API differences through testing and add to compat.py\\n- Verify geometry nodes detection works in all versions\\n- Document discovered API differences for future reference&quot;," + }, + { + "lineNumber": 858, + "content": " &quot;name&quot;: &quot;Multi-Blender Version Support&quot;," + }, + { + "lineNumber": 891, + "content": " &quot;content&quot;: &quot;Update blender_manifest.toml to set minimum version to 4.2.0&quot;," + }, + { + "lineNumber": 921, + "content": " &quot;overview&quot;: &quot;Add support for Blender 4.2 LTS, 4.5 LTS, and 5.0 in the same codebase by implementing version detection, dual metadata format support (bl_info and blender_manifest.toml), and a framework for version-specific API handling.&quot;" + }, + { + "lineNumber": 1010, + "content": "- [ ] Update blender_manifest.toml to set minimum version to 4.2.0" + }, + { + "lineNumber": 1106, + "content": "+ multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)." + }, + { + "lineNumber": 1118, + "content": "+ def get_blender_version():" + }, + { + "lineNumber": 1120, + "content": "+ Returns the current Blender version as a tuple (major, minor, patch)." + }, + { + "lineNumber": 1125, + "content": "+ return bpy.app.version" + }, + { + "lineNumber": 1130, + "content": "+ Returns the current Blender version as a string (e.g., &quot;4.2.0&quot;)." + }, + { + "lineNumber": 1135, + "content": "+ version = get_blender_version()" + }, + { + "lineNumber": 1141, + "content": "+ Check if the current Blender version is at least the specified version." + }, + { + "lineNumber": 1151, + "content": "+ current = get_blender_version()" + }, + { + "lineNumber": 1163, + "content": "+ Check if the current Blender version is less than the specified version." + }, + { + "lineNumber": 1178, + "content": "+ Returns the version category string for the current Blender version." + }, + { + "lineNumber": 1183, + "content": "+ version = get_blender_version()" + }, + { + "lineNumber": 1376, + "content": "- [ ] Update blender_manifest.toml to set minimum version to 4.2.0" + }, + { + "lineNumber": 1444, + "content": "- blender_version_min = &quot;4.5.0&quot;" + }, + { + "lineNumber": 1445, + "content": "+ blender_version_min = &quot;4.2.0&quot;" + }, + { + "lineNumber": 1463, + "content": "- [x] Update blender_manifest.toml to set minimum version to 4.2.0" + }, + { + "lineNumber": 1530, + "content": "- [x] Update blender_manifest.toml to set minimum version to 4.2.0" + }, + { + "lineNumber": 1758, + "content": "- [x] Update blender_manifest.toml to set minimum version to 4.2.0" + }, + { + "lineNumber": 1779, + "content": " - `get_blender_version()` — Returns version tuple" + }, + { + "lineNumber": 1796, + "content": "5. **Updated manifest** — Changed `blender_version_min` from \"4.5.0\" to \"4.2.0\" in `blender_manifest.toml`" + }, + { + "lineNumber": 1802, + "content": "7. **Operator review** — Reviewed all operators; they use standard Blender API patterns compatible across versions" + }, + { + "lineNumber": 1804, + "content": "All todos are complete. The addon supports Blender 4.2 LTS, 4.5 LTS, and 5.0 with version detection and compatibility handling. The codebase is ready for testing across these versions." + }, + { + "lineNumber": 1820, + "content": "Checking the version detection logic for Blender 4.3:" + }, + { + "lineNumber": 1845, + "content": "There's an issue: Blender 4.3 would define `bl_info` (since 4.3 < 4.5), but 4.3 likely uses `blender_manifest.toml` like 4.5+, not `bl_info`. Checking when the extension format was introduced and fixing the version detection:" + }, + { + "lineNumber": 1856, + "content": "| Web Search Results | | Your addon, originally developed for Blender 2.83, has been updated to support versions 4.2 LTS, 4.5 LTS, and 5.0. Blender 4.3, released on November 19, 2024, introduced several new features and API changes. ([blender.org](https://www.blender.org/press/blender-4-3-release/?utm_source=openai))<br/><br/>Given that your addon includes version detection and compatibility handling, it should be able to run on Blender 4.3. However, since 4.3 is not explicitly listed among the supported versions, it's advisable to test the addon in this environment to ensure full compatibility.<br/><br/>Key changes in Blender 4.3 include:<br/><br/>- **Grease Pencil Rewrite**: A complete overhaul introducing Layer Groups and Geometry Nodes support. ([code.blender.org](https://code.blender.org/2024/10/blender-4-3-beta-is-here/?utm_source=openai))<br/><br/>- **EEVEE Enhancements**: Introduction of Light Linking and Multi-pass Compositing. ([code.blender.org](https://code.blender.org/2024/10/blender-4-3-beta-is-here/?utm_source=openai))<br/><br/>- **Brush Assets**: Brushes are now treated as assets, allowing reuse across projects. ([code.blender.org](https://code.blender.org/2024/10/blender-4-3-beta-is-here/?utm_source=openai))<br/><br/>Additionally, the Python API saw updates, such as the addition of `bpy.app.python_args` and new handlers like `bpy.app.handlers.blend_import_pre` and `bpy.app.handlers.blend_import_post`. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.3/python_api/?utm_source=openai))<br/><br/>To ensure your addon functions correctly in Blender 4.3, consider the following steps:<br/><br/>1. **Test the Addon**: Run your addon in Blender 4.3 to identify any issues arising from the new features or API changes.<br/><br/>2. **Update Compatibility Functions**: If any incompatibilities are found, update your compatibility functions to handle the specific changes introduced in 4.3.<br/><br/>3. **Review Release Notes**: Consult the Blender 4.3 release notes for detailed information on changes that might affect your addon. ([blender.org](https://www.blender.org/download/releases/4-3/?utm_source=openai))<br/><br/>By proactively testing and updating your addon, you can ensure it remains functional across Blender versions, including 4.3. |" + }, + { + "lineNumber": 1867, + "content": "Fixing the version detection: `bl_info` is defined for any version < 4.5 (including 4.3), but 4.3 likely uses `blender_manifest.toml` like 4.5+. Updating the logic to define `bl_info` only for 4.2 LTS:" + }, + { + "lineNumber": 1922, + "content": "+ version = get_blender_version()" + }, + { + "lineNumber": 1962, + "content": "2. **`blender_manifest.toml` will be used**: The manifest has `blender_version_min = \"4.2.0\"`, so Blender 4.3 qualifies and will use the manifest format." + }, + { + "lineNumber": 2466, + "content": "| Web Search Results | | Blender 4.3 introduced several significant changes that may affect addon compatibility:<br/><br/>1. **Grease Pencil Overhaul**: The Grease Pencil system underwent a complete rewrite, introducing Layer Groups and Geometry Nodes support. This overhaul may require updates to addons that interact with Grease Pencil data. ([code.blender.org](https://code.blender.org/2024/10/blender-4-3-beta-is-here/?utm_source=openai))<br/><br/>2. **Python API Changes**: Blender 4.3 introduced new handlers in the `bpy.app` module, such as `blend_import_pre` and `blend_import_post`, and added a `rename` function to the `ID` class. Additionally, certain properties were removed from the EEVEE module. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.3/python_api/?utm_source=openai))<br/><br/>3. **Attribute Domain Size Function**: A new `domain_size` function was added to all `bpy.types.AttributeGroup*` classes, which may affect addons that manipulate attributes. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.3/python_api/?utm_source=openai))<br/><br/>Given these changes, it's advisable to test your addon in Blender 4.3 to ensure compatibility. If issues arise, you may need to implement version-specific handling to accommodate the new API and system modifications.<br/><br/>For a comprehensive overview of Blender 4.3's new features and changes, you might find this official overview video helpful:<br/><br/>[Blender 4.3 New Features Official Overview](https://www.youtube.com/watch?v=KnpvIIfm7uQ&utm_source=openai)<br/> |" + }, + { + "lineNumber": 2575, + "content": "| Web Search Results | | Blender 4.3 introduced several significant changes that could affect addon compatibility:<br/><br/>- **Grease Pencil Overhaul**: The Grease Pencil system underwent a complete rewrite, introducing new features like Layer Groups and Geometry Nodes support. This overhaul may require updates to addons that interact with Grease Pencil data. ([code.blender.org](https://code.blender.org/2024/10/blender-4-3-beta-is-here/?utm_source=openai))<br/><br/>- **Python API Modifications**: The Python API saw changes, including the addition of new handlers (`blend_import_pre` and `blend_import_post`) and the introduction of a `rename` function to the `ID` class. Additionally, assigning embedded ID pointers to properties is now forbidden, which could impact addons relying on such assignments. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.3/python_api/?utm_source=openai))<br/><br/>- **EEVEE Enhancements**: EEVEE now supports Light and Shadow Linking, allowing lights to affect only specific objects. Addons that manipulate lighting may need adjustments to accommodate these new features. ([blender.org](https://www.blender.org/download/releases/4-3/?utm_source=openai))<br/><br/>Given these updates, it's advisable to test your addon in Blender 4.3 to identify any compatibility issues. If problems arise, you may need to implement version-specific handling to ensure functionality across different Blender versions. |" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\utils\\version.py", + "matches": [ + { + "lineNumber": 22, + "content": "multi-version Blender support (4.2 LTS, 4.5 LTS, and 5.0)." + }, + { + "lineNumber": 34, + "content": "def get_blender_version():" + }, + { + "lineNumber": 36, + "content": " Returns the current Blender version as a tuple (major, minor, patch)." + }, + { + "lineNumber": 41, + "content": " return bpy.app.version" + }, + { + "lineNumber": 46, + "content": " Returns the current Blender version as a string (e.g., \"4.2.0\")." + }, + { + "lineNumber": 51, + "content": " version = get_blender_version()" + }, + { + "lineNumber": 57, + "content": " Check if the current Blender version is at least the specified version." + }, + { + "lineNumber": 67, + "content": " current = get_blender_version()" + }, + { + "lineNumber": 79, + "content": " Check if the current Blender version is less than the specified version." + }, + { + "lineNumber": 94, + "content": " Returns the version category string for the current Blender version." + }, + { + "lineNumber": 99, + "content": " version = get_blender_version()" + }, + { + "lineNumber": 116, + "content": " version = get_blender_version()" + } + ] + }, + { + "file": "E:\\atomic_data_manager\\.specstory\\history\\2025-10-09_00-08Z-update-blender-addon-for-version-4-5.md", + "matches": [ + { + "lineNumber": 5, + "content": "# Update blender addon for version 4.5 (2025-10-09 00:08Z)" + }, + { + "lineNumber": 320, + "content": "I'm updating the addon by removing imports from __init__.py and the register/unregister calls. Next, I need to update the maintainer in bl_info to the new author, RaincloudTheDragon. However, since I'm using the extension manifest approach for Blender 4.5, I’ll create a new blender_manifest.toml file and remove bl_info. This manifest will require details like name, version, type, and maintainer. It looks like I’ll need to confirm the required fields through a web search. I’ll also update the package naming to `atomic_data_manager`." + }, + { + "lineNumber": 323, + "content": "I'm looking at how earlier code uses `addon_updater_ops.register(bl_info)` that relies on the bl_info variable. Since I'm removing the updater, I won’t need it anymore. Interestingly, Blender can still load the addon without it, especially through the manifest for version 4.5, but older versions may require it. So, I’ll remove bl_info to clear up any confusion and update the registration functions accordingly." + }, + { + "lineNumber": 362, + "content": "I need to focus on the maintainer field in the Blender manifest, as it’s important for the new extension format. Starting with Blender 4.2, the 'blender_manifest.toml' will replace bl_info in __init__.py. I'll create this new manifest file with necessary fields like 'id', 'version', 'type', 'name', 'maintainer', 'blender_version_min', 'website', and 'license'. While it helps to maintain backwards compatibility with bl_info, the goal is to shift towards the new format. I also need to remove the 'updater' directory and adjust preferences_ui.py to eliminate updater properties and UI elements." + }, + { + "lineNumber": 428, + "content": "I’m planning to update the Blender version and remove the `bl_info` variable entirely as it’s only used in the updater registration. I’ll clean up the code by removing any imports and references related to the updater operations in files like `preferences_ui.py`. Additionally, I’ll implement functions in `stats/users.py` to detect materials, images, and textures used in geometry nodes. This includes creating checks for legacy datablocks to avoid errors and ensuring proper detection without excessive changes. Once again, I’ll maintain structure while enhancing functionality." + }, + { + "lineNumber": 460, + "content": "+ blender_version_min = \"4.5.0\"" + }, + { + "lineNumber": 1312, + "content": " <summary>Searched web `Blender 4.5 extension blender_manifest.toml required fields tagline modules schema_version 1.0.0` • **1** results</summary>" + }, + { + "lineNumber": 1316, + "content": "| Web Search Results | | The recent updates to your Blender add-on were necessary to ensure compatibility with Blender 4.5 and to enhance its functionality. Here's why each change was important:<br/><br/>1. **Updating the Maintainer Information**: Reflecting the new maintainer, RaincloudTheDragon, in the `__init__.py` file ensures that users and developers know who to contact for support or contributions.<br/><br/>2. **Removing the CG Cookie Updater**: With the introduction of Blender's native extension system in version 4.2, third-party updaters like CG Cookie's are redundant. Integrating with Blender's built-in system simplifies maintenance and provides a more seamless user experience. ([code.blender.org](https://code.blender.org/2024/05/geometry-nodes-workshop-may-2024/?utm_source=openai))<br/><br/>3. **Adding the Blender Extension Manifest**: The `blender_manifest.toml` file is essential for Blender 4.5's extension system. It provides metadata about the add-on, facilitating better integration and management within Blender.<br/><br/>4. **Implementing Geometry Nodes Usage Detection**: Blender 4.5 introduced significant enhancements to Geometry Nodes, including new nodes and improved performance. Updating your add-on to detect and handle Geometry Nodes ensures that materials, images, and other data blocks used within these nodes are correctly identified and preserved during cleaning operations. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.5/geometry_nodes/?utm_source=openai))<br/><br/>5. **Guarding Against Legacy API Usage**: Blender 4.5 has deprecated certain legacy APIs, such as those related to textures and particles. By adding guards in your add-on, you prevent potential errors and ensure compatibility with the latest Blender version. ([developer.blender.org](https://developer.blender.org/docs/release_notes/4.5/bugfixes/?utm_source=openai))<br/><br/>These updates collectively enhance the add-on's stability, compatibility, and functionality within Blender 4.5, providing a better experience for users. |" + }, + { + "lineNumber": 1345, + "content": "I know that the blender_manifest.toml should include fields like schema_version, id, version, name, tagline, and maintainer or authors, along with using an SPDX ID for the license. The error I see indicates that \"vscode_development '...atomic-data-manager' is not a supported module name,” which likely relates to file naming. To fix this, I need to ensure that the package directory name uses underscores to match the Python package name \"atomic_data_manager.\" Plus, the \"modules\" directory is important for proper loading." + }, + { + "lineNumber": 1358, + "content": " <summary>Searched web `blender_manifest.toml example modules tagline Blender Extensions schema_version 1.0.0` • **1** results</summary>" + }, + { + "lineNumber": 1362, + "content": "| Web Search Results | | The recent updates to your Blender add-on were necessary to ensure compatibility with Blender 4.5 and to enhance its functionality. Here's a breakdown of the key changes and their purposes:<br/><br/>1. **Updating the Maintainer Information**: The `bl_info` in `__init__.py` was updated to reflect the new maintainer, RaincloudTheDragon, following the previous maintainer's departure. This ensures that users and developers have accurate contact information for support and collaboration.<br/><br/>2. **Removing the CG Cookie Updater**: The CG Cookie updater system was removed to streamline the add-on and eliminate dependencies on external update mechanisms. This simplifies maintenance and reduces potential points of failure.<br/><br/>3. **Adding the Blender Extension Manifest**: A `blender_manifest.toml` file was added to align with Blender 4.5's new extension system. This manifest provides metadata about the add-on, facilitating better integration and management within Blender.<br/><br/>4. **Enhancing Geometry Nodes Usage Detection**: The add-on's logic was updated to accurately detect and account for the usage of materials, images, and other datablocks within Geometry Nodes. This prevents unintended removal of these elements during cleanup operations, preserving the integrity of projects that utilize Geometry Nodes.<br/><br/>5. **Guarding Legacy APIs**: Safeguards were implemented to handle legacy APIs, such as `textures` and `particles`, ensuring that the add-on remains functional and stable across different Blender versions.<br/><br/>These updates were essential to maintain the add-on's compatibility with Blender 4.5, leverage new features, and provide a more robust and user-friendly experience. |" + }, + { + "lineNumber": 1373, + "content": "For the Blender extension, I need to correctly list the Python module names in the manifest. It looks like the format includes fields like schema_version, id, version, name, tagline, maintainer, and type. The \"modules\" field under the root key or [addon] should register the modules. The error about the missing \"tagline\" indicates we need to add that field. I can refer to examples from Blender's extension repository to ensure I'm following the specifications correctly." + }, + { + "lineNumber": 1404, + "content": " blender_version_min = \"4.5.0\"" + }, + { + "lineNumber": 1466, + "content": " blender_version_min = \"4.5.0\"" + }, + { + "lineNumber": 1494, + "content": "Your `blender_manifest.toml` now includes required fields: schema_version, id, name, version, type, author, maintainer, blender_version_min, license, description, homepage, tagline, tags, modules." + }, + { + "lineNumber": 1532, + "content": "-blender_version_min = \"4.5.0\"" + }, + { + "lineNumber": 1534, + "content": "+blender_version_min = \"4.5.0\"" + } + ] + } + ], + "totalLines": 167, + "totalMatchedLines": 167 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +Updating the script to detect the Blender version and use version-appropriate parameters: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Updating the script to detect the Blender version and use version-appropriate parameters: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "22331c17-24fc-406d-a542-04fd08e720af" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "dd155901-4716-4a1b-8afc-19a5f9818611" +} +``` +Status: **completed** + + +
+
+ +--- + + +Updated the script to detect the Blender version and use version-appropriate parameters. Changes: + +1. **Version detection functions**: Added `get_blender_version()` and `is_version_at_least()` similar to the atomic_data_manager pattern +2. **Version reporting**: Prints the detected Blender version at startup +3. **Version-aware parameters**: Uses `use_scripts=False` only for Blender 2.80+ (when it was introduced), while `load_ui=False` is used for all versions + +The script is now compatible with: +- Blender 2.79 and earlier (uses only `load_ui=False`) +- Blender 2.80–4.x (uses both `load_ui=False` and `use_scripts=False`) +- Blender 5.0+ (uses both parameters) + +This should prevent parameter errors across versions. The access violation issue may still occur if files have broken library links, but the script will handle version differences correctly. + +Try running it again; it should work with different Blender versions. + +--- + +_**User**_ + +@a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +--- + +_**Agent (model default, mode Agent)**_ + + +Reading the output file to check the current status: + + +--- + + +
Tool use: **read_file_v2** • Read file: a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +
+
+ +--- + + +The output file shows Blender quit, but no script output. Checking the script for issues: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +The script may be failing silently. The f-string in the batch echo might be causing issues. Fixing the version string formatting: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "4298439f-8e4f-4584-a2a0-9f87cf8359e8" +} +``` +Status: **completed** + + +
+
+ +--- + + +Fixed the f-string issue that could cause syntax errors. The output file shows Blender started and quit, but no script output, which suggests: + +1. The script may have failed early (syntax error) +2. No files were passed to the script +3. Output was buffered and not captured + +The f-string fix should help. The script should now: +- Detect the Blender version correctly +- Print version information +- Process files with version-appropriate parameters + +Try running the compression again. If you still see no output, check: +- Whether files were dragged onto the batch file +- Whether the temporary Python script is being created correctly +- Whether there are any early errors preventing the script from running + +If issues persist, we can add more debug output to trace where it's failing. + +--- + +_**User**_ + +@a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +--- + +_**Agent (model default, mode Agent)**_ + + +Re-reading the output file to check for updates: + + +--- + + +
Tool use: **read_file_v2** • Read file: a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +
+
+ +--- + + +The output file shows Blender started and quit, but no Python script output. Checking how the batch file runs Blender and captures output: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
+Tool use: **semantic_search_full** + + +Parameters: + +```json +{ + "query": "How does the batch file run Blender with Python script and capture output?", + "topK": 10, + "explanation": "" +} +``` +Result: + +```json +{ + "codeResults": [ + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 104 + }, + "endPosition": { + "line": 148 + } + }, + "contents": "echo # Save with compression to output folder\necho bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho except Exception as e:\necho print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho import traceback\necho traceback.print_exc^(^)\necho else:\necho print^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"COMPRESSION_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n echo Script created successfully: !TEMP_SCRIPT!\n) else (\n echo ERROR: Failed to create script file!\n pause\n exit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho COMPRESSION REPORT\necho ========================================\necho.\n\nREM Count processed files and get file info\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1", + "signatures": {}, + "detailedLines": [ + { + "text": "echo # Save with compression to output folder", + "lineNumber": 105 + }, + { + "text": "echo bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 106 + }, + { + "text": "echo print^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 107 + }, + { + "text": "echo except Exception as e:", + "lineNumber": 108 + }, + { + "text": "echo print^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 109 + }, + { + "text": "echo import traceback", + "lineNumber": 110 + }, + { + "text": "echo traceback.print_exc^(^)", + "lineNumber": 111 + }, + { + "text": "echo else:", + "lineNumber": 112 + }, + { + "text": "echo print^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 113 + }, + { + "text": "echo.", + "lineNumber": 114 + }, + { + "text": "echo print^(\"COMPRESSION_COMPLETE\"^)", + "lineNumber": 115 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 116 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 117 + }, + { + "lineNumber": 118 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 119 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 120 + }, + { + "text": " echo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 121 + }, + { + "text": ") else (", + "lineNumber": 122 + }, + { + "text": " echo ERROR: Failed to create script file!", + "lineNumber": 123 + }, + { + "text": " pause", + "lineNumber": 124 + }, + { + "text": " exit /b 1", + "lineNumber": 125 + }, + { + "text": ")", + "lineNumber": 126 + }, + { + "lineNumber": 127 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 128 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 129 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 130 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 131 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 132 + }, + { + "lineNumber": 133 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 134 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 135 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 136 + }, + { + "lineNumber": 137 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 138 + }, + { + "text": "echo.", + "lineNumber": 139 + }, + { + "text": "echo ========================================", + "lineNumber": 140 + }, + { + "text": "echo COMPRESSION REPORT", + "lineNumber": 141 + }, + { + "text": "echo ========================================", + "lineNumber": 142 + }, + { + "text": "echo.", + "lineNumber": 143 + }, + { + "lineNumber": 144 + }, + { + "text": "REM Count processed files and get file info", + "lineNumber": 145 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 146 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 147 + }, + { + "text": " set /a processed+=1", + "lineNumber": 148 + } + ] + }, + "score": 0.4947406053543091 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": { + "line": 64 + }, + "endPosition": { + "line": 107 + } + }, + "contents": "echo ^\t\t\t# Save to output folder\necho ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)\necho ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)\necho ^\t\texcept Exception as e:\necho ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)\necho ^\telse:\necho ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)\necho.\necho print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)\necho bpy.ops.wm.quit_blender^(^)\n) > \"!TEMP_SCRIPT!\"\n\nREM Check if script was created successfully\nif exist \"!TEMP_SCRIPT!\" (\n\techo Script created successfully: !TEMP_SCRIPT!\n) else (\n\techo ERROR: Failed to create script file!\n\tpause\n\texit /b 1\n)\n\nREM Run Blender to process the dragged files\necho Processing dragged .blend files...\necho Using script: !TEMP_SCRIPT!\ndel /f /q \"blender_output.txt\" 2>nul\nblender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"\n\nREM Clean up temporary script\necho Cleaning up temporary script...\ndel \"!TEMP_SCRIPT!\" 2>nul\n\nREM Parse output and generate report\necho.\necho ========================================\necho ACTION FAKE USER REMOVAL REPORT\necho ========================================\necho.\n\nREM Track updated counts per file during parse\nfor /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n\tif \"%%a\"==\"SUCCESS\" (\n\t\tset /a processed+=1\n\t\techo [!processed!] SUCCESS: %%b", + "signatures": {}, + "detailedLines": [ + { + "text": "echo ^\t\t\t# Save to output folder", + "lineNumber": 65 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 66 + }, + { + "text": "echo ^\t\t\tprint^(\"SUCCESS:\" + blend_file + \":\" + output_file^)", + "lineNumber": 67 + }, + { + "text": "echo ^\t\texcept Exception as e:", + "lineNumber": 68 + }, + { + "text": "echo ^\t\t\tprint^(\"CRASH:\" + blend_file + \":\" + str^(e^)^)", + "lineNumber": 69 + }, + { + "text": "echo ^\telse:", + "lineNumber": 70 + }, + { + "text": "echo ^\t\tprint^(\"SKIP_NOT_BLEND:\" + blend_file^)", + "lineNumber": 71 + }, + { + "text": "echo.", + "lineNumber": 72 + }, + { + "text": "echo print^(\"FAKE_USER_REMOVAL_COMPLETE\"^)", + "lineNumber": 73 + }, + { + "text": "echo bpy.ops.wm.quit_blender^(^)", + "lineNumber": 74 + }, + { + "text": ") > \"!TEMP_SCRIPT!\"", + "lineNumber": 75 + }, + { + "lineNumber": 76 + }, + { + "text": "REM Check if script was created successfully", + "lineNumber": 77 + }, + { + "text": "if exist \"!TEMP_SCRIPT!\" (", + "lineNumber": 78 + }, + { + "text": "\techo Script created successfully: !TEMP_SCRIPT!", + "lineNumber": 79 + }, + { + "text": ") else (", + "lineNumber": 80 + }, + { + "text": "\techo ERROR: Failed to create script file!", + "lineNumber": 81 + }, + { + "text": "\tpause", + "lineNumber": 82 + }, + { + "text": "\texit /b 1", + "lineNumber": 83 + }, + { + "text": ")", + "lineNumber": 84 + }, + { + "lineNumber": 85 + }, + { + "text": "REM Run Blender to process the dragged files", + "lineNumber": 86 + }, + { + "text": "echo Processing dragged .blend files...", + "lineNumber": 87 + }, + { + "text": "echo Using script: !TEMP_SCRIPT!", + "lineNumber": 88 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 89 + }, + { + "text": "blender --background --factory-startup --python \"!TEMP_SCRIPT!\" -- %* 2^>^&1 | powershell -NoProfile -Command \"$input | Tee-Object -FilePath 'blender_output.txt'\"", + "lineNumber": 90 + }, + { + "lineNumber": 91 + }, + { + "text": "REM Clean up temporary script", + "lineNumber": 92 + }, + { + "text": "echo Cleaning up temporary script...", + "lineNumber": 93 + }, + { + "text": "del \"!TEMP_SCRIPT!\" 2>nul", + "lineNumber": 94 + }, + { + "lineNumber": 95 + }, + { + "text": "REM Parse output and generate report", + "lineNumber": 96 + }, + { + "text": "echo.", + "lineNumber": 97 + }, + { + "text": "echo ========================================", + "lineNumber": 98 + }, + { + "text": "echo ACTION FAKE USER REMOVAL REPORT", + "lineNumber": 99 + }, + { + "text": "echo ========================================", + "lineNumber": 100 + }, + { + "text": "echo.", + "lineNumber": 101 + }, + { + "lineNumber": 102 + }, + { + "text": "REM Track updated counts per file during parse", + "lineNumber": 103 + }, + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 104 + }, + { + "text": "\tif \"%%a\"==\"SUCCESS\" (", + "lineNumber": 105 + }, + { + "text": "\t\tset /a processed+=1", + "lineNumber": 106 + }, + { + "text": "\t\techo [!processed!] SUCCESS: %%b", + "lineNumber": 107 + } + ] + }, + "score": 0.4602561593055725 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 68 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\n\necho Starting Blender file compression...\n\nREM Check if files were dragged onto the batch file\nif \"%~1\"==\"\" (\n echo.\n echo Usage: Drag .blend files onto this batch file to compress them.\n echo Output will be saved to the \\output folder.\n echo.\n pause\n exit /b 1\n)\n\nREM Create output directory if it doesn't exist\nif not exist \"output\" mkdir \"output\"\n\nREM Initialize counters and tracking\nset \"processed=0\"\nset \"crashed=0\"\nset \"skipped=0\"\nset \"deleted_similar=0\"\nset \"threshold_percent=10\"\n\nREM Create a temporary Python script for compression\nset \"TEMP_SCRIPT=compress_blend_temp.py\"\necho Creating Python script: !TEMP_SCRIPT!\n\n(\necho import bpy\necho import os\necho import sys\necho.\necho # Blender version detection and compatibility\necho def get_blender_version^(^):\necho \"\"\"Returns the current Blender version as a tuple (major, minor, patch).\"\"\"\necho return bpy.app.version\necho.\necho def is_version_at_least^(major, minor=0, patch=0^):\necho \"\"\"Check if the current Blender version is at least the specified version.\"\"\"\necho current = get_blender_version^(^)\necho target = ^(major, minor, patch^)\necho if current[0] != target[0]:\necho return current[0] ^> target[0]\necho if current[1] != target[1]:\necho return current[1] ^> target[1]\necho return current[2] ^>= target[2]\necho.\necho # Detect Blender version\necho version = get_blender_version^(^)\necho version_str = str^(version[0]^) + \".\" + str^(version[1]^) + \".\" + str^(version[2]^)\necho print^(\"Blender version: \" + version_str^)\necho.\necho # Create output directory if it doesn't exist\necho if not os.path.exists^(\"output\"^):\necho os.makedirs^(\"output\"^)\necho.\necho # Get command line arguments ^(the dragged files^)\necho blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []\necho.\necho print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)\necho.\necho def is_compressed_file^(path^):\necho try:\necho with open^(path, 'rb'^) as f:\necho header = f.read^(4^)\necho if header[:2] == b'\\x1f\\x8b': # gzip", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "echo Starting Blender file compression...", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "REM Check if files were dragged onto the batch file", + "lineNumber": 6 + }, + { + "text": "if \"%~1\"==\"\" (", + "lineNumber": 7 + }, + { + "text": " echo.", + "lineNumber": 8 + }, + { + "text": " echo Usage: Drag .blend files onto this batch file to compress them.", + "lineNumber": 9 + }, + { + "text": " echo Output will be saved to the \\output folder.", + "lineNumber": 10 + }, + { + "text": " echo.", + "lineNumber": 11 + }, + { + "text": " pause", + "lineNumber": 12 + }, + { + "text": " exit /b 1", + "lineNumber": 13 + }, + { + "text": ")", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "REM Create output directory if it doesn't exist", + "lineNumber": 16 + }, + { + "text": "if not exist \"output\" mkdir \"output\"", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "REM Initialize counters and tracking", + "lineNumber": 19 + }, + { + "text": "set \"processed=0\"", + "lineNumber": 20 + }, + { + "text": "set \"crashed=0\"", + "lineNumber": 21 + }, + { + "text": "set \"skipped=0\"", + "lineNumber": 22 + }, + { + "text": "set \"deleted_similar=0\"", + "lineNumber": 23 + }, + { + "text": "set \"threshold_percent=10\"", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "REM Create a temporary Python script for compression", + "lineNumber": 26 + }, + { + "text": "set \"TEMP_SCRIPT=compress_blend_temp.py\"", + "lineNumber": 27 + }, + { + "text": "echo Creating Python script: !TEMP_SCRIPT!", + "lineNumber": 28 + }, + { + "lineNumber": 29 + }, + { + "text": "(", + "lineNumber": 30 + }, + { + "text": "echo import bpy", + "lineNumber": 31 + }, + { + "text": "echo import os", + "lineNumber": 32 + }, + { + "text": "echo import sys", + "lineNumber": 33 + }, + { + "text": "echo.", + "lineNumber": 34 + }, + { + "text": "echo # Blender version detection and compatibility", + "lineNumber": 35 + }, + { + "text": "echo def get_blender_version^(^):", + "lineNumber": 36 + }, + { + "text": "echo \"\"\"Returns the current Blender version as a tuple (major, minor, patch).\"\"\"", + "lineNumber": 37 + }, + { + "text": "echo return bpy.app.version", + "lineNumber": 38 + }, + { + "text": "echo.", + "lineNumber": 39 + }, + { + "text": "echo def is_version_at_least^(major, minor=0, patch=0^):", + "lineNumber": 40 + }, + { + "text": "echo \"\"\"Check if the current Blender version is at least the specified version.\"\"\"", + "lineNumber": 41 + }, + { + "text": "echo current = get_blender_version^(^)", + "lineNumber": 42 + }, + { + "text": "echo target = ^(major, minor, patch^)", + "lineNumber": 43 + }, + { + "text": "echo if current[0] != target[0]:", + "lineNumber": 44 + }, + { + "text": "echo return current[0] ^> target[0]", + "lineNumber": 45 + }, + { + "text": "echo if current[1] != target[1]:", + "lineNumber": 46 + }, + { + "text": "echo return current[1] ^> target[1]", + "lineNumber": 47 + }, + { + "text": "echo return current[2] ^>= target[2]", + "lineNumber": 48 + }, + { + "text": "echo.", + "lineNumber": 49 + }, + { + "text": "echo # Detect Blender version", + "lineNumber": 50 + }, + { + "text": "echo version = get_blender_version^(^)", + "lineNumber": 51 + }, + { + "text": "echo version_str = str^(version[0]^) + \".\" + str^(version[1]^) + \".\" + str^(version[2]^)", + "lineNumber": 52 + }, + { + "text": "echo print^(\"Blender version: \" + version_str^)", + "lineNumber": 53 + }, + { + "text": "echo.", + "lineNumber": 54 + }, + { + "text": "echo # Create output directory if it doesn't exist", + "lineNumber": 55 + }, + { + "text": "echo if not os.path.exists^(\"output\"^):", + "lineNumber": 56 + }, + { + "text": "echo os.makedirs^(\"output\"^)", + "lineNumber": 57 + }, + { + "text": "echo.", + "lineNumber": 58 + }, + { + "text": "echo # Get command line arguments ^(the dragged files^)", + "lineNumber": 59 + }, + { + "text": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []", + "lineNumber": 60 + }, + { + "text": "echo.", + "lineNumber": 61 + }, + { + "text": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to compress\"^)", + "lineNumber": 62 + }, + { + "text": "echo.", + "lineNumber": 63 + }, + { + "text": "echo def is_compressed_file^(path^):", + "lineNumber": 64 + }, + { + "text": "echo try:", + "lineNumber": 65 + }, + { + "text": "echo with open^(path, 'rb'^) as f:", + "lineNumber": 66 + }, + { + "text": "echo header = f.read^(4^)", + "lineNumber": 67 + }, + { + "text": "echo if header[:2] == b'\\x1f\\x8b': # gzip", + "lineNumber": 68 + } + ] + }, + "score": 0.43321263790130615 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 66 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\n\necho Starting fake user removal for actions...\n\nREM Check if files were dragged onto the batch file\nif \"%~1\"==\"\" (\n\techo.\n\techo Usage: Drag .blend files onto this batch file to remove fake users from actions.\n\techo Output will be saved to the \\output folder.\n\techo.\n\tpause\n\texit /b 1\n)\n\nREM Create output directory if it doesn't exist\nif not exist \"output\" mkdir \"output\"\n\nREM Initialize counters and tracking\nset \"processed=0\"\nset \"crashed=0\"\nset \"skipped=0\"\nset \"deleted_similar=0\"\nset \"threshold_percent=10\"\nset \"updated_total=0\"\n\nREM Create a temporary Python script for processing\nset \"TEMP_SCRIPT=remove_action_fake_users_temp.py\"\necho Creating Python script: !TEMP_SCRIPT!\n\n(\necho import bpy\necho import os\necho import sys\necho.\necho # Create output directory if it doesn't exist\necho if not os.path.exists^(\"output\"^):\necho \tos.makedirs^(\"output\"^)\necho.\necho # Get command line arguments ^(the dragged files^)\necho blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []\necho.\necho print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to process\"^)\necho.\necho for blend_file in blend_files:\necho ^\tif blend_file.lower^(^).endswith^(\".blend\"^):\necho ^\t\ttry:\necho ^\t\t\t# Create output path in output folder\necho ^\t\t\tfilename = os.path.basename^(blend_file^)\necho ^\t\t\toutput_file = os.path.join^(\"output\", filename^)\necho ^\t\t\t# Check if output file already exists\necho ^\t\t\tif os.path.exists^(output_file^):\necho ^\t\t\t\tprint^(\"SKIP_EXISTING:\" + blend_file^)\necho ^\t\t\t\tcontinue\necho ^\t\t\tprint^(\"PROCESSING:\" + blend_file^)\necho ^\t\t\t# Load the blend file\necho ^\t\t\tbpy.ops.wm.open_mainfile^(filepath=blend_file^)\necho ^\t\t\t# Remove fake users from all actions\necho ^\t\t\tchanged = 0\necho ^\t\t\tfor action in bpy.data.actions:\necho ^\t\t\t\tif getattr^(action, \"use_fake_user\", False^):\necho ^\t\t\t\t\taction.use_fake_user = False\necho ^\t\t\t\t\tchanged += 1\necho ^\t\t\tprint^(\"UPDATED:\" + blend_file + \":\" + str^(changed^)^)\necho ^\t\t\t# Save to output folder\necho ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "echo Starting fake user removal for actions...", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "REM Check if files were dragged onto the batch file", + "lineNumber": 6 + }, + { + "text": "if \"%~1\"==\"\" (", + "lineNumber": 7 + }, + { + "text": "\techo.", + "lineNumber": 8 + }, + { + "text": "\techo Usage: Drag .blend files onto this batch file to remove fake users from actions.", + "lineNumber": 9 + }, + { + "text": "\techo Output will be saved to the \\output folder.", + "lineNumber": 10 + }, + { + "text": "\techo.", + "lineNumber": 11 + }, + { + "text": "\tpause", + "lineNumber": 12 + }, + { + "text": "\texit /b 1", + "lineNumber": 13 + }, + { + "text": ")", + "lineNumber": 14 + }, + { + "lineNumber": 15 + }, + { + "text": "REM Create output directory if it doesn't exist", + "lineNumber": 16 + }, + { + "text": "if not exist \"output\" mkdir \"output\"", + "lineNumber": 17 + }, + { + "lineNumber": 18 + }, + { + "text": "REM Initialize counters and tracking", + "lineNumber": 19 + }, + { + "text": "set \"processed=0\"", + "lineNumber": 20 + }, + { + "text": "set \"crashed=0\"", + "lineNumber": 21 + }, + { + "text": "set \"skipped=0\"", + "lineNumber": 22 + }, + { + "text": "set \"deleted_similar=0\"", + "lineNumber": 23 + }, + { + "text": "set \"threshold_percent=10\"", + "lineNumber": 24 + }, + { + "text": "set \"updated_total=0\"", + "lineNumber": 25 + }, + { + "lineNumber": 26 + }, + { + "text": "REM Create a temporary Python script for processing", + "lineNumber": 27 + }, + { + "text": "set \"TEMP_SCRIPT=remove_action_fake_users_temp.py\"", + "lineNumber": 28 + }, + { + "text": "echo Creating Python script: !TEMP_SCRIPT!", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": "(", + "lineNumber": 31 + }, + { + "text": "echo import bpy", + "lineNumber": 32 + }, + { + "text": "echo import os", + "lineNumber": 33 + }, + { + "text": "echo import sys", + "lineNumber": 34 + }, + { + "text": "echo.", + "lineNumber": 35 + }, + { + "text": "echo # Create output directory if it doesn't exist", + "lineNumber": 36 + }, + { + "text": "echo if not os.path.exists^(\"output\"^):", + "lineNumber": 37 + }, + { + "text": "echo \tos.makedirs^(\"output\"^)", + "lineNumber": 38 + }, + { + "text": "echo.", + "lineNumber": 39 + }, + { + "text": "echo # Get command line arguments ^(the dragged files^)", + "lineNumber": 40 + }, + { + "text": "echo blend_files = sys.argv[sys.argv.index^(\"--\"^) + 1:] if \"--\" in sys.argv else []", + "lineNumber": 41 + }, + { + "text": "echo.", + "lineNumber": 42 + }, + { + "text": "echo print^(\"Found \" + str^(len^(blend_files^)^) + \" .blend files to process\"^)", + "lineNumber": 43 + }, + { + "text": "echo.", + "lineNumber": 44 + }, + { + "text": "echo for blend_file in blend_files:", + "lineNumber": 45 + }, + { + "text": "echo ^\tif blend_file.lower^(^).endswith^(\".blend\"^):", + "lineNumber": 46 + }, + { + "text": "echo ^\t\ttry:", + "lineNumber": 47 + }, + { + "text": "echo ^\t\t\t# Create output path in output folder", + "lineNumber": 48 + }, + { + "text": "echo ^\t\t\tfilename = os.path.basename^(blend_file^)", + "lineNumber": 49 + }, + { + "text": "echo ^\t\t\toutput_file = os.path.join^(\"output\", filename^)", + "lineNumber": 50 + }, + { + "text": "echo ^\t\t\t# Check if output file already exists", + "lineNumber": 51 + }, + { + "text": "echo ^\t\t\tif os.path.exists^(output_file^):", + "lineNumber": 52 + }, + { + "text": "echo ^\t\t\t\tprint^(\"SKIP_EXISTING:\" + blend_file^)", + "lineNumber": 53 + }, + { + "text": "echo ^\t\t\t\tcontinue", + "lineNumber": 54 + }, + { + "text": "echo ^\t\t\tprint^(\"PROCESSING:\" + blend_file^)", + "lineNumber": 55 + }, + { + "text": "echo ^\t\t\t# Load the blend file", + "lineNumber": 56 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.open_mainfile^(filepath=blend_file^)", + "lineNumber": 57 + }, + { + "text": "echo ^\t\t\t# Remove fake users from all actions", + "lineNumber": 58 + }, + { + "text": "echo ^\t\t\tchanged = 0", + "lineNumber": 59 + }, + { + "text": "echo ^\t\t\tfor action in bpy.data.actions:", + "lineNumber": 60 + }, + { + "text": "echo ^\t\t\t\tif getattr^(action, \"use_fake_user\", False^):", + "lineNumber": 61 + }, + { + "text": "echo ^\t\t\t\t\taction.use_fake_user = False", + "lineNumber": 62 + }, + { + "text": "echo ^\t\t\t\t\tchanged += 1", + "lineNumber": 63 + }, + { + "text": "echo ^\t\t\tprint^(\"UPDATED:\" + blend_file + \":\" + str^(changed^)^)", + "lineNumber": 64 + }, + { + "text": "echo ^\t\t\t# Save to output folder", + "lineNumber": 65 + }, + { + "text": "echo ^\t\t\tbpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 66 + } + ] + }, + "score": 0.4154841899871826 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 65 + }, + "endPosition": { + "line": 106 + } + }, + "contents": "echo with open^(path, 'rb'^) as f:\necho header = f.read^(4^)\necho if header[:2] == b'\\x1f\\x8b': # gzip\necho return True\necho if header == b'\\x28\\xb5\\x2f\\xfd': # zstd\necho return True\necho return False\necho except Exception:\necho return False\necho.\necho for blend_file in blend_files:\necho if blend_file.lower^(^).endswith^(\".blend\"^):\necho try:\necho # Create output path in output folder\necho filename = os.path.basename^(blend_file^)\necho output_file = os.path.join^(\"output\", filename^)\necho # Check if output file already exists\necho if os.path.exists^(output_file^):\necho print^(\"SKIP_EXISTING:\" + blend_file^)\necho continue\necho # Skip if source is already compressed\necho if is_compressed_file^(blend_file^):\necho print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)\necho continue\necho # Check if source file exists and is readable\necho if not os.path.exists^(blend_file^) or not os.access^(blend_file, os.R_OK^):\necho print^(\"SKIP_UNREADABLE:\" + blend_file^)\necho continue\necho print^(\"PROCESSING:\" + blend_file^)\necho # Load the blend file with version-appropriate options\necho # Build parameters based on Blender version\necho open_params = {\"filepath\": blend_file}\necho # load_ui parameter available in all versions\necho open_params[\"load_ui\"] = False\necho # use_scripts parameter available in Blender 2.8+\necho if is_version_at_least^(2, 80^):\necho open_params[\"use_scripts\"] = False\necho # Call with appropriate parameters for the version\necho bpy.ops.wm.open_mainfile^(**open_params^)\necho # Save with compression to output folder\necho bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "signatures": {}, + "detailedLines": [ + { + "text": "echo with open^(path, 'rb'^) as f:", + "lineNumber": 66 + }, + { + "text": "echo header = f.read^(4^)", + "lineNumber": 67 + }, + { + "text": "echo if header[:2] == b'\\x1f\\x8b': # gzip", + "lineNumber": 68 + }, + { + "text": "echo return True", + "lineNumber": 69 + }, + { + "text": "echo if header == b'\\x28\\xb5\\x2f\\xfd': # zstd", + "lineNumber": 70 + }, + { + "text": "echo return True", + "lineNumber": 71 + }, + { + "text": "echo return False", + "lineNumber": 72 + }, + { + "text": "echo except Exception:", + "lineNumber": 73 + }, + { + "text": "echo return False", + "lineNumber": 74 + }, + { + "text": "echo.", + "lineNumber": 75 + }, + { + "text": "echo for blend_file in blend_files:", + "lineNumber": 76 + }, + { + "text": "echo if blend_file.lower^(^).endswith^(\".blend\"^):", + "lineNumber": 77 + }, + { + "text": "echo try:", + "lineNumber": 78 + }, + { + "text": "echo # Create output path in output folder", + "lineNumber": 79 + }, + { + "text": "echo filename = os.path.basename^(blend_file^)", + "lineNumber": 80 + }, + { + "text": "echo output_file = os.path.join^(\"output\", filename^)", + "lineNumber": 81 + }, + { + "text": "echo # Check if output file already exists", + "lineNumber": 82 + }, + { + "text": "echo if os.path.exists^(output_file^):", + "lineNumber": 83 + }, + { + "text": "echo print^(\"SKIP_EXISTING:\" + blend_file^)", + "lineNumber": 84 + }, + { + "text": "echo continue", + "lineNumber": 85 + }, + { + "text": "echo # Skip if source is already compressed", + "lineNumber": 86 + }, + { + "text": "echo if is_compressed_file^(blend_file^):", + "lineNumber": 87 + }, + { + "text": "echo print^(\"SKIP_ALREADY_COMPRESSED:\" + blend_file^)", + "lineNumber": 88 + }, + { + "text": "echo continue", + "lineNumber": 89 + }, + { + "text": "echo # Check if source file exists and is readable", + "lineNumber": 90 + }, + { + "text": "echo if not os.path.exists^(blend_file^) or not os.access^(blend_file, os.R_OK^):", + "lineNumber": 91 + }, + { + "text": "echo print^(\"SKIP_UNREADABLE:\" + blend_file^)", + "lineNumber": 92 + }, + { + "text": "echo continue", + "lineNumber": 93 + }, + { + "text": "echo print^(\"PROCESSING:\" + blend_file^)", + "lineNumber": 94 + }, + { + "text": "echo # Load the blend file with version-appropriate options", + "lineNumber": 95 + }, + { + "text": "echo # Build parameters based on Blender version", + "lineNumber": 96 + }, + { + "text": "echo open_params = {\"filepath\": blend_file}", + "lineNumber": 97 + }, + { + "text": "echo # load_ui parameter available in all versions", + "lineNumber": 98 + }, + { + "text": "echo open_params[\"load_ui\"] = False", + "lineNumber": 99 + }, + { + "text": "echo # use_scripts parameter available in Blender 2.8+", + "lineNumber": 100 + }, + { + "text": "echo if is_version_at_least^(2, 80^):", + "lineNumber": 101 + }, + { + "text": "echo open_params[\"use_scripts\"] = False", + "lineNumber": 102 + }, + { + "text": "echo # Call with appropriate parameters for the version", + "lineNumber": 103 + }, + { + "text": "echo bpy.ops.wm.open_mainfile^(**open_params^)", + "lineNumber": 104 + }, + { + "text": "echo # Save with compression to output folder", + "lineNumber": 105 + }, + { + "text": "echo bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)", + "lineNumber": 106 + } + ] + }, + "score": 0.3888314962387085 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\compress_blend_files.bat", + "range": { + "startPosition": { + "line": 145 + }, + "endPosition": { + "line": 202, + "column": 6 + } + }, + "contents": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n if \"%%a\"==\"SUCCESS\" (\n set /a processed+=1\n set \"original_file=%%b\"\n set \"compressed_file=%%c\"\n \n REM Get file sizes\n for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"\n for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"\n \n echo [!processed!] !original_file!\n echo Original: !original_size! bytes\n echo Compressed: !compressed_size! bytes\n set /a \"savings=!original_size! - !compressed_size!\"\n if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)\n echo Savings: !savings! bytes\n echo Reduction: !percent!%%\n if !percent! lss !threshold_percent! (\n del \"!compressed_file!\" 2>nul\n set /a deleted_similar+=1\n echo Deleted: too similar to original\n )\n echo.\n ) else if \"%%a\"==\"CRASH\" (\n set /a crashed+=1\n echo [!crashed!] CRASHED: %%b\n echo Error: %%c\n echo.\n ) else if \"%%a\"==\"SKIP_EXISTING\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already exists^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(not .blend^): %%b\n echo.\n ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (\n set /a skipped+=1\n echo [!skipped!] SKIPPED ^(already compressed^): %%b\n echo.\n )\n)\n\nREM Clean up output file\ndel /f /q \"blender_output.txt\" 2>nul\n\necho ========================================\necho SUMMARY\necho ========================================\necho Processed: !processed! files\necho Crashed: !crashed! files\necho Skipped: !skipped! files\necho Deleted ^(too similar^): !deleted_similar! files\necho Total: %* files\necho ========================================\necho.\necho Done!\npause ", + "signatures": {}, + "detailedLines": [ + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 146 + }, + { + "text": " if \"%%a\"==\"SUCCESS\" (", + "lineNumber": 147 + }, + { + "text": " set /a processed+=1", + "lineNumber": 148 + }, + { + "text": " set \"original_file=%%b\"", + "lineNumber": 149 + }, + { + "text": " set \"compressed_file=%%c\"", + "lineNumber": 150 + }, + { + "text": " ", + "lineNumber": 151 + }, + { + "text": " REM Get file sizes", + "lineNumber": 152 + }, + { + "text": " for %%f in (\"!original_file!\") do set \"original_size=%%~zf\"", + "lineNumber": 153 + }, + { + "text": " for %%f in (\"!compressed_file!\") do set \"compressed_size=%%~zf\"", + "lineNumber": 154 + }, + { + "text": " ", + "lineNumber": 155 + }, + { + "text": " echo [!processed!] !original_file!", + "lineNumber": 156 + }, + { + "text": " echo Original: !original_size! bytes", + "lineNumber": 157 + }, + { + "text": " echo Compressed: !compressed_size! bytes", + "lineNumber": 158 + }, + { + "text": " set /a \"savings=!original_size! - !compressed_size!\"", + "lineNumber": 159 + }, + { + "text": " if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)", + "lineNumber": 160 + }, + { + "text": " echo Savings: !savings! bytes", + "lineNumber": 161 + }, + { + "text": " echo Reduction: !percent!%%", + "lineNumber": 162 + }, + { + "text": " if !percent! lss !threshold_percent! (", + "lineNumber": 163 + }, + { + "text": " del \"!compressed_file!\" 2>nul", + "lineNumber": 164 + }, + { + "text": " set /a deleted_similar+=1", + "lineNumber": 165 + }, + { + "text": " echo Deleted: too similar to original", + "lineNumber": 166 + }, + { + "text": " )", + "lineNumber": 167 + }, + { + "text": " echo.", + "lineNumber": 168 + }, + { + "text": " ) else if \"%%a\"==\"CRASH\" (", + "lineNumber": 169 + }, + { + "text": " set /a crashed+=1", + "lineNumber": 170 + }, + { + "text": " echo [!crashed!] CRASHED: %%b", + "lineNumber": 171 + }, + { + "text": " echo Error: %%c", + "lineNumber": 172 + }, + { + "text": " echo.", + "lineNumber": 173 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_EXISTING\" (", + "lineNumber": 174 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 175 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already exists^): %%b", + "lineNumber": 176 + }, + { + "text": " echo.", + "lineNumber": 177 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_NOT_BLEND\" (", + "lineNumber": 178 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 179 + }, + { + "text": " echo [!skipped!] SKIPPED ^(not .blend^): %%b", + "lineNumber": 180 + }, + { + "text": " echo.", + "lineNumber": 181 + }, + { + "text": " ) else if \"%%a\"==\"SKIP_ALREADY_COMPRESSED\" (", + "lineNumber": 182 + }, + { + "text": " set /a skipped+=1", + "lineNumber": 183 + }, + { + "text": " echo [!skipped!] SKIPPED ^(already compressed^): %%b", + "lineNumber": 184 + }, + { + "text": " echo.", + "lineNumber": 185 + }, + { + "text": " )", + "lineNumber": 186 + }, + { + "text": ")", + "lineNumber": 187 + }, + { + "lineNumber": 188 + }, + { + "text": "REM Clean up output file", + "lineNumber": 189 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 190 + }, + { + "lineNumber": 191 + }, + { + "text": "echo ========================================", + "lineNumber": 192 + }, + { + "text": "echo SUMMARY", + "lineNumber": 193 + }, + { + "text": "echo ========================================", + "lineNumber": 194 + }, + { + "text": "echo Processed: !processed! files", + "lineNumber": 195 + }, + { + "text": "echo Crashed: !crashed! files", + "lineNumber": 196 + }, + { + "text": "echo Skipped: !skipped! files", + "lineNumber": 197 + }, + { + "text": "echo Deleted ^(too similar^): !deleted_similar! files", + "lineNumber": 198 + }, + { + "text": "echo Total: %* files", + "lineNumber": 199 + }, + { + "text": "echo ========================================", + "lineNumber": 200 + }, + { + "text": "echo.", + "lineNumber": 201 + }, + { + "text": "echo Done!", + "lineNumber": 202 + }, + { + "text": "pause ", + "lineNumber": 203 + } + ] + }, + "score": 0.35409796237945557 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\remove_action_fake_users.bat", + "range": { + "startPosition": { + "line": 103 + }, + "endPosition": { + "line": 161 + } + }, + "contents": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (\n\tif \"%%a\"==\"SUCCESS\" (\n\t\tset /a processed+=1\n\t\techo [!processed!] SUCCESS: %%b\n\t\techo Saved to: %%c\n\t\tREM Compare sizes and delete if reduction < threshold\n\t\tfor %%f in (\"%%b\") do set \"original_size=%%~zf\"\n\t\tfor %%f in (\"%%c\") do set \"compressed_size=%%~zf\"\n\t\tset /a savings=original_size-compressed_size\n\t\tif !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)\n\t\techo Original: !original_size! bytes\n\t\techo Output: !compressed_size! bytes\n\t\techo Reduction: !percent!%%\n\t\tif !percent! lss !threshold_percent! (\n\t\t\tdel \"%%c\" 2>nul\n\t\t\tset /a deleted_similar+=1\n\t\t\techo Deleted: too similar to original\n\t\t)\n\t\techo.\n\t) else if \"%%a\"==\"CRASH\" (\n\t\tset /a crashed+=1\n\t\techo [!crashed!] CRASHED: %%b\n\t\techo Error: %%c\n\t\techo.\n\t) else if \"%%a\"==\"SKIP_EXISTING\" (\n\t\tset /a skipped+=1\n\t\techo [!skipped!] SKIPPED ^(already exists^): %%b\n\t\techo.\n\t) else if \"%%a\"==\"SKIP_NOT_BLEND\" (\n\t\tset /a skipped+=1\n\t\techo [!skipped!] SKIPPED ^(not .blend^): %%b\n\t\techo.\n\t) else if \"%%a\"==\"UPDATED\" (\n\t\tREM Accumulate total updated actions\n\t\tfor /f \"delims= tokens=1\" %%x in (\"%%c\") do set \"updated_in_file=%%x\"\n\t\tset /a updated_total+=updated_in_file\n\t\techo Updated actions: !updated_in_file!\n\t)\n)\n\nREM Clean up output file\ndel /f /q \"blender_output.txt\" 2>nul\n\necho ========================================\necho SUMMARY\necho ========================================\necho Processed: !processed! files\necho Crashed: !crashed! files\necho Skipped: !skipped! files\necho Deleted ^(too similar^): !deleted_similar! files\necho Total actions updated: !updated_total!\necho Total: %* files\necho ========================================\necho.\necho Done!\npause", + "signatures": {}, + "detailedLines": [ + { + "text": "for /f \"tokens=1,2,3 delims=:\" %%a in (blender_output.txt) do (", + "lineNumber": 104 + }, + { + "text": "\tif \"%%a\"==\"SUCCESS\" (", + "lineNumber": 105 + }, + { + "text": "\t\tset /a processed+=1", + "lineNumber": 106 + }, + { + "text": "\t\techo [!processed!] SUCCESS: %%b", + "lineNumber": 107 + }, + { + "text": "\t\techo Saved to: %%c", + "lineNumber": 108 + }, + { + "text": "\t\tREM Compare sizes and delete if reduction < threshold", + "lineNumber": 109 + }, + { + "text": "\t\tfor %%f in (\"%%b\") do set \"original_size=%%~zf\"", + "lineNumber": 110 + }, + { + "text": "\t\tfor %%f in (\"%%c\") do set \"compressed_size=%%~zf\"", + "lineNumber": 111 + }, + { + "text": "\t\tset /a savings=original_size-compressed_size", + "lineNumber": 112 + }, + { + "text": "\t\tif !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)", + "lineNumber": 113 + }, + { + "text": "\t\techo Original: !original_size! bytes", + "lineNumber": 114 + }, + { + "text": "\t\techo Output: !compressed_size! bytes", + "lineNumber": 115 + }, + { + "text": "\t\techo Reduction: !percent!%%", + "lineNumber": 116 + }, + { + "text": "\t\tif !percent! lss !threshold_percent! (", + "lineNumber": 117 + }, + { + "text": "\t\t\tdel \"%%c\" 2>nul", + "lineNumber": 118 + }, + { + "text": "\t\t\tset /a deleted_similar+=1", + "lineNumber": 119 + }, + { + "text": "\t\t\techo Deleted: too similar to original", + "lineNumber": 120 + }, + { + "text": "\t\t)", + "lineNumber": 121 + }, + { + "text": "\t\techo.", + "lineNumber": 122 + }, + { + "text": "\t) else if \"%%a\"==\"CRASH\" (", + "lineNumber": 123 + }, + { + "text": "\t\tset /a crashed+=1", + "lineNumber": 124 + }, + { + "text": "\t\techo [!crashed!] CRASHED: %%b", + "lineNumber": 125 + }, + { + "text": "\t\techo Error: %%c", + "lineNumber": 126 + }, + { + "text": "\t\techo.", + "lineNumber": 127 + }, + { + "text": "\t) else if \"%%a\"==\"SKIP_EXISTING\" (", + "lineNumber": 128 + }, + { + "text": "\t\tset /a skipped+=1", + "lineNumber": 129 + }, + { + "text": "\t\techo [!skipped!] SKIPPED ^(already exists^): %%b", + "lineNumber": 130 + }, + { + "text": "\t\techo.", + "lineNumber": 131 + }, + { + "text": "\t) else if \"%%a\"==\"SKIP_NOT_BLEND\" (", + "lineNumber": 132 + }, + { + "text": "\t\tset /a skipped+=1", + "lineNumber": 133 + }, + { + "text": "\t\techo [!skipped!] SKIPPED ^(not .blend^): %%b", + "lineNumber": 134 + }, + { + "text": "\t\techo.", + "lineNumber": 135 + }, + { + "text": "\t) else if \"%%a\"==\"UPDATED\" (", + "lineNumber": 136 + }, + { + "text": "\t\tREM Accumulate total updated actions", + "lineNumber": 137 + }, + { + "text": "\t\tfor /f \"delims= tokens=1\" %%x in (\"%%c\") do set \"updated_in_file=%%x\"", + "lineNumber": 138 + }, + { + "text": "\t\tset /a updated_total+=updated_in_file", + "lineNumber": 139 + }, + { + "text": "\t\techo Updated actions: !updated_in_file!", + "lineNumber": 140 + }, + { + "text": "\t)", + "lineNumber": 141 + }, + { + "text": ")", + "lineNumber": 142 + }, + { + "lineNumber": 143 + }, + { + "text": "REM Clean up output file", + "lineNumber": 144 + }, + { + "text": "del /f /q \"blender_output.txt\" 2>nul", + "lineNumber": 145 + }, + { + "lineNumber": 146 + }, + { + "text": "echo ========================================", + "lineNumber": 147 + }, + { + "text": "echo SUMMARY", + "lineNumber": 148 + }, + { + "text": "echo ========================================", + "lineNumber": 149 + }, + { + "text": "echo Processed: !processed! files", + "lineNumber": 150 + }, + { + "text": "echo Crashed: !crashed! files", + "lineNumber": 151 + }, + { + "text": "echo Skipped: !skipped! files", + "lineNumber": 152 + }, + { + "text": "echo Deleted ^(too similar^): !deleted_similar! files", + "lineNumber": 153 + }, + { + "text": "echo Total actions updated: !updated_total!", + "lineNumber": 154 + }, + { + "text": "echo Total: %* files", + "lineNumber": 155 + }, + { + "text": "echo ========================================", + "lineNumber": 156 + }, + { + "text": "echo.", + "lineNumber": 157 + }, + { + "text": "echo Done!", + "lineNumber": 158 + }, + { + "text": "pause", + "lineNumber": 159 + } + ] + }, + "score": 0.31772756576538086 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\ZipSeqArchv.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 44 + } + }, + "contents": "@echo off\nsetlocal EnableExtensions EnableDelayedExpansion\n\nset \"REN_DIR=%~dp0\"\nfor %%I in (\"%REN_DIR%..\") do set \"PROJ_ROOT=%%~fI\"\n\nset \"CONFIG_DIR=%PROJ_ROOT%\\.config\"\nset \"CONFIG_PATH=%CONFIG_DIR%\\config.json\"\nset \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"\n\nif not exist \"%CONFIG_PATH%\" (\n echo [ERROR] config.json not found at %CONFIG_PATH%\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nif not exist \"%GET_STRUCT_DIR%\" (\n echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nfor /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^\n \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \\\"zip_sequences.py not found at $pyPath\\\" }; Write-Output $pyPath\"`) do set \"PY_SCRIPT=%%~I\"\n\nif not defined PY_SCRIPT (\n echo [ERROR] Unable to resolve zip_sequences.py path from config.\n exit /b 1\n)\n\npushd \"%PROJ_ROOT%\" >nul 2>&1\n\npython \"%PY_SCRIPT%\" --verbose %*\nset \"ERR=!ERRORLEVEL!\"\n\npopd >nul 2>&1\n\nif !ERR! NEQ 0 (\n echo Failed to update render sequence archives ^(exit code !ERR!^).\n exit /b !ERR!\n)\n\nexit /b 0", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal EnableExtensions EnableDelayedExpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "set \"REN_DIR=%~dp0\"", + "lineNumber": 4 + }, + { + "text": "for %%I in (\"%REN_DIR%..\") do set \"PROJ_ROOT=%%~fI\"", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "set \"CONFIG_DIR=%PROJ_ROOT%\\.config\"", + "lineNumber": 7 + }, + { + "text": "set \"CONFIG_PATH=%CONFIG_DIR%\\config.json\"", + "lineNumber": 8 + }, + { + "text": "set \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "if not exist \"%CONFIG_PATH%\" (", + "lineNumber": 11 + }, + { + "text": " echo [ERROR] config.json not found at %CONFIG_PATH%", + "lineNumber": 12 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 13 + }, + { + "text": " exit /b 1", + "lineNumber": 14 + }, + { + "text": ")", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": "if not exist \"%GET_STRUCT_DIR%\" (", + "lineNumber": 17 + }, + { + "text": " echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%", + "lineNumber": 18 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 19 + }, + { + "text": " exit /b 1", + "lineNumber": 20 + }, + { + "text": ")", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": "for /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^", + "lineNumber": 23 + }, + { + "text": " \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \\\"zip_sequences.py not found at $pyPath\\\" }; Write-Output $pyPath\"`) do set \"PY_SCRIPT=%%~I\"", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "if not defined PY_SCRIPT (", + "lineNumber": 26 + }, + { + "text": " echo [ERROR] Unable to resolve zip_sequences.py path from config.", + "lineNumber": 27 + }, + { + "text": " exit /b 1", + "lineNumber": 28 + }, + { + "text": ")", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": "pushd \"%PROJ_ROOT%\" >nul 2>&1", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": "python \"%PY_SCRIPT%\" --verbose %*", + "lineNumber": 33 + }, + { + "text": "set \"ERR=!ERRORLEVEL!\"", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": "popd >nul 2>&1", + "lineNumber": 36 + }, + { + "lineNumber": 37 + }, + { + "text": "if !ERR! NEQ 0 (", + "lineNumber": 38 + }, + { + "text": " echo Failed to update render sequence archives ^(exit code !ERR!^).", + "lineNumber": 39 + }, + { + "text": " exit /b !ERR!", + "lineNumber": 40 + }, + { + "text": ")", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": "exit /b 0", + "lineNumber": 43 + } + ] + }, + "score": 0.3127357065677643 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UnzipSeqArchv.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 42 + } + }, + "contents": "@echo off\nsetlocal EnableExtensions EnableDelayedExpansion\n\nset \"REN_DIR=%~dp0\"\nfor %%I in (\"%REN_DIR%..\") do set \"PROJ_ROOT=%%~fI\"\n\nset \"CONFIG_DIR=%PROJ_ROOT%\\.config\"\nset \"CONFIG_PATH=%CONFIG_DIR%\\config.json\"\nset \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"\n\nif not exist \"%CONFIG_PATH%\" (\n echo [ERROR] config.json not found at %CONFIG_PATH%\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nif not exist \"%GET_STRUCT_DIR%\" (\n echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nfor /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^\n \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \\\"zip_sequences.py not found at $pyPath\\\" }; Write-Output $pyPath\"`) do set \"PY_SCRIPT=%%I\"\n\nif not defined PY_SCRIPT (\n echo [ERROR] Unable to resolve zip_sequences.py path from config.\n exit /b 1\n)\n\npushd \"%PROJ_ROOT%\" >nul 2>&1\n\npython \"%PY_SCRIPT%\" --mode expand --verbose %*\nset \"ERR=!ERRORLEVEL!\"\n\nif not \"!ERR!\"==\"0\" (\n echo Failed to expand render sequence archives (exit code !ERR!).\n)\n\npopd >nul 2>&1\nexit /b !ERR!", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal EnableExtensions EnableDelayedExpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "set \"REN_DIR=%~dp0\"", + "lineNumber": 4 + }, + { + "text": "for %%I in (\"%REN_DIR%..\") do set \"PROJ_ROOT=%%~fI\"", + "lineNumber": 5 + }, + { + "lineNumber": 6 + }, + { + "text": "set \"CONFIG_DIR=%PROJ_ROOT%\\.config\"", + "lineNumber": 7 + }, + { + "text": "set \"CONFIG_PATH=%CONFIG_DIR%\\config.json\"", + "lineNumber": 8 + }, + { + "text": "set \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": "if not exist \"%CONFIG_PATH%\" (", + "lineNumber": 11 + }, + { + "text": " echo [ERROR] config.json not found at %CONFIG_PATH%", + "lineNumber": 12 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 13 + }, + { + "text": " exit /b 1", + "lineNumber": 14 + }, + { + "text": ")", + "lineNumber": 15 + }, + { + "lineNumber": 16 + }, + { + "text": "if not exist \"%GET_STRUCT_DIR%\" (", + "lineNumber": 17 + }, + { + "text": " echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%", + "lineNumber": 18 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 19 + }, + { + "text": " exit /b 1", + "lineNumber": 20 + }, + { + "text": ")", + "lineNumber": 21 + }, + { + "lineNumber": 22 + }, + { + "text": "for /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^", + "lineNumber": 23 + }, + { + "text": " \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \\\"zip_sequences.py not found at $pyPath\\\" }; Write-Output $pyPath\"`) do set \"PY_SCRIPT=%%I\"", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "if not defined PY_SCRIPT (", + "lineNumber": 26 + }, + { + "text": " echo [ERROR] Unable to resolve zip_sequences.py path from config.", + "lineNumber": 27 + }, + { + "text": " exit /b 1", + "lineNumber": 28 + }, + { + "text": ")", + "lineNumber": 29 + }, + { + "lineNumber": 30 + }, + { + "text": "pushd \"%PROJ_ROOT%\" >nul 2>&1", + "lineNumber": 31 + }, + { + "lineNumber": 32 + }, + { + "text": "python \"%PY_SCRIPT%\" --mode expand --verbose %*", + "lineNumber": 33 + }, + { + "text": "set \"ERR=!ERRORLEVEL!\"", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": "if not \"!ERR!\"==\"0\" (", + "lineNumber": 36 + }, + { + "text": " echo Failed to expand render sequence archives (exit code !ERR!).", + "lineNumber": 37 + }, + { + "text": ")", + "lineNumber": 38 + }, + { + "lineNumber": 39 + }, + { + "text": "popd >nul 2>&1", + "lineNumber": 40 + }, + { + "text": "exit /b !ERR!", + "lineNumber": 41 + } + ] + }, + "score": 0.3116072118282318 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n if COMPRESSION_METHOD == \"PPMd\":\n # PPMd is single-threaded, so -mmt won't help\n # But we can still set it for consistency\n cmd.append(\"-mmt=on\")\n elif worker_count == 1:\n # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)\n cmd.append(f\"-mmt={cpu_cores}\")\n else:\n # Multiple workers: use auto mode (7z will manage threads)\n cmd.append(\"-mmt=on\")\n \n cmd.extend([\n str(temp_zip_abs),\n f\"@{list_file_abs}\",\n ])\n \n # Log the command in verbose mode for debugging\n if verbose:\n cmd_str = \" \".join(cmd)\n log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)\n \n result = subprocess.run(\n cmd,\n cwd=seq_dir,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z compression failed: {error_msg}\")\n \n # Move temp zip to final location, replacing any existing file\n if zip_path.exists():\n zip_path.unlink()\n temp_zip.replace(zip_path)\n temp_zip = None # Mark as moved so we don't delete it\n finally:\n # Clean up temp zip if it wasn't moved", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 760 + }, + { + "text": " if COMPRESSION_METHOD == \"PPMd\":", + "lineNumber": 761 + }, + { + "text": " # PPMd is single-threaded, so -mmt won't help", + "lineNumber": 762 + }, + { + "text": " # But we can still set it for consistency", + "lineNumber": 763 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 764 + }, + { + "text": " elif worker_count == 1:", + "lineNumber": 765 + }, + { + "text": " # Single worker: use all CPU cores for maximum speed (LZMA2, BZip2, Deflate support this)", + "lineNumber": 766 + }, + { + "text": " cmd.append(f\"-mmt={cpu_cores}\")", + "lineNumber": 767 + }, + { + "text": " else:", + "lineNumber": 768 + }, + { + "text": " # Multiple workers: use auto mode (7z will manage threads)", + "lineNumber": 769 + }, + { + "text": " cmd.append(\"-mmt=on\")", + "lineNumber": 770 + }, + { + "text": " ", + "lineNumber": 771 + }, + { + "text": " cmd.extend([", + "lineNumber": 772 + }, + { + "text": " str(temp_zip_abs),", + "lineNumber": 773 + }, + { + "text": " f\"@{list_file_abs}\",", + "lineNumber": 774 + }, + { + "text": " ])", + "lineNumber": 775 + }, + { + "text": " ", + "lineNumber": 776 + }, + { + "text": " # Log the command in verbose mode for debugging", + "lineNumber": 777 + }, + { + "text": " if verbose:", + "lineNumber": 778 + }, + { + "text": " cmd_str = \" \".join(cmd)", + "lineNumber": 779 + }, + { + "text": " log(\"zip\", f\"7z command: {cmd_str}\", verbose_only=True, verbose=verbose)", + "lineNumber": 780 + }, + { + "text": " ", + "lineNumber": 781 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 782 + }, + { + "text": " cmd,", + "lineNumber": 783 + }, + { + "text": " cwd=seq_dir,", + "lineNumber": 784 + }, + { + "text": " check=False,", + "lineNumber": 785 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 786 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 787 + }, + { + "text": " text=True,", + "lineNumber": 788 + }, + { + "text": " )", + "lineNumber": 789 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 790 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 791 + }, + { + "text": " if result.stdout:", + "lineNumber": 792 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 793 + }, + { + "text": " raise RuntimeError(f\"7z compression failed: {error_msg}\")", + "lineNumber": 794 + }, + { + "text": " ", + "lineNumber": 795 + }, + { + "text": " # Move temp zip to final location, replacing any existing file", + "lineNumber": 796 + }, + { + "text": " if zip_path.exists():", + "lineNumber": 797 + }, + { + "text": " zip_path.unlink()", + "lineNumber": 798 + }, + { + "text": " temp_zip.replace(zip_path)", + "lineNumber": 799 + }, + { + "text": " temp_zip = None # Mark as moved so we don't delete it", + "lineNumber": 800 + }, + { + "text": " finally:", + "lineNumber": 801 + }, + { + "text": " # Clean up temp zip if it wasn't moved", + "lineNumber": 802 + } + ] + }, + "score": 0.28124749660491943 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpgradeToGitProj.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 61, + "column": 6 + } + }, + "contents": "@echo off\nsetlocal EnableExtensions EnableDelayedExpansion\n\n:: UpgradeToGitProj.bat\n:: Run this inside an existing (pre-git) project root.\n:: - Merges .gitignore and .gitattributes from components/ templates\n:: - Initializes git and installs Git LFS\n:: - Creates Renders, copies helper scripts\n:: - Collects seq/ outputs from daily_* folders under Blends\\animations (with submodule support)\n\n:: -----------------------------\n:: Arguments\n:: -----------------------------\nset \"DRY=0\"\nif /I \"%~1\"==\"/dry-run\" set \"DRY=1\"\nif /I \"%~1\"==\"--dry-run\" set \"DRY=1\"\nif /I \"%~1\"==\"-n\" set \"DRY=1\"\nif /I \"%~1\"==\"/n\" set \"DRY=1\"\n\nset \"projectDir=%CD%\"\nset \"scriptDir=%~dp0\"\nset \"RUN_ID=%RANDOM%_%RANDOM%\"\nset \"DELETE_LIST=%TEMP%\\upg_delete_%RUN_ID%.lst\"\n\nsetlocal EnableDelayedExpansion\nset /a debugTotal=0\nset /a debugPlanned=0\nset /a debugMissing=0\n\necho ==============================================\necho UpgradeToGitProj in \"%projectDir%\"\nif \"%DRY%\"==\"1\" (echo Mode: DRY-RUN) else (echo Mode: APPLY)\necho ==============================================\n\n:: -----------------------------\n:: Ensure Renders exists and copy helper scripts\n:: -----------------------------\nset \"rendersDir=%projectDir%\\Renders\"\nif not exist \"%rendersDir%\" (\n if \"%DRY%\"==\"1\" (\n echo [DRY] mkdir \"%rendersDir%\"\n ) else (\n mkdir \"%rendersDir%\" >nul 2>&1\n )\n)\n\nfor %%F in (UpdateSequences.bat ZipSeqArchv.bat UnzipSeqArchv.bat) do (\n if exist \"%scriptDir%%%F\" (\n if \"%DRY%\"==\"1\" (\n echo [DRY] copy \"%scriptDir%%%F\" \"%rendersDir%\\%%F\"\n ) else (\n copy /Y \"%scriptDir%%%F\" \"%rendersDir%\\%%F\" >nul\n )\n ) else (\n echo [WARN] Missing template: \"%scriptDir%%%F\"\n )\n)\n\nfor %%F in (ConfigLoader.ps1 config.json) do (\n if exist \"%scriptDir%%%F\" (\n if \"%DRY%\"==\"1\" (\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal EnableExtensions EnableDelayedExpansion", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": ":: UpgradeToGitProj.bat", + "lineNumber": 4 + }, + { + "text": ":: Run this inside an existing (pre-git) project root.", + "lineNumber": 5 + }, + { + "text": ":: - Merges .gitignore and .gitattributes from components/ templates", + "lineNumber": 6 + }, + { + "text": ":: - Initializes git and installs Git LFS", + "lineNumber": 7 + }, + { + "text": ":: - Creates Renders, copies helper scripts", + "lineNumber": 8 + }, + { + "text": ":: - Collects seq/ outputs from daily_* folders under Blends\\animations (with submodule support)", + "lineNumber": 9 + }, + { + "lineNumber": 10 + }, + { + "text": ":: -----------------------------", + "lineNumber": 11 + }, + { + "text": ":: Arguments", + "lineNumber": 12 + }, + { + "text": ":: -----------------------------", + "lineNumber": 13 + }, + { + "text": "set \"DRY=0\"", + "lineNumber": 14 + }, + { + "text": "if /I \"%~1\"==\"/dry-run\" set \"DRY=1\"", + "lineNumber": 15 + }, + { + "text": "if /I \"%~1\"==\"--dry-run\" set \"DRY=1\"", + "lineNumber": 16 + }, + { + "text": "if /I \"%~1\"==\"-n\" set \"DRY=1\"", + "lineNumber": 17 + }, + { + "text": "if /I \"%~1\"==\"/n\" set \"DRY=1\"", + "lineNumber": 18 + }, + { + "lineNumber": 19 + }, + { + "text": "set \"projectDir=%CD%\"", + "lineNumber": 20 + }, + { + "text": "set \"scriptDir=%~dp0\"", + "lineNumber": 21 + }, + { + "text": "set \"RUN_ID=%RANDOM%_%RANDOM%\"", + "lineNumber": 22 + }, + { + "text": "set \"DELETE_LIST=%TEMP%\\upg_delete_%RUN_ID%.lst\"", + "lineNumber": 23 + }, + { + "lineNumber": 24 + }, + { + "text": "setlocal EnableDelayedExpansion", + "lineNumber": 25 + }, + { + "text": "set /a debugTotal=0", + "lineNumber": 26 + }, + { + "text": "set /a debugPlanned=0", + "lineNumber": 27 + }, + { + "text": "set /a debugMissing=0", + "lineNumber": 28 + }, + { + "lineNumber": 29 + }, + { + "text": "echo ==============================================", + "lineNumber": 30 + }, + { + "text": "echo UpgradeToGitProj in \"%projectDir%\"", + "lineNumber": 31 + }, + { + "text": "if \"%DRY%\"==\"1\" (echo Mode: DRY-RUN) else (echo Mode: APPLY)", + "lineNumber": 32 + }, + { + "text": "echo ==============================================", + "lineNumber": 33 + }, + { + "lineNumber": 34 + }, + { + "text": ":: -----------------------------", + "lineNumber": 35 + }, + { + "text": ":: Ensure Renders exists and copy helper scripts", + "lineNumber": 36 + }, + { + "text": ":: -----------------------------", + "lineNumber": 37 + }, + { + "text": "set \"rendersDir=%projectDir%\\Renders\"", + "lineNumber": 38 + }, + { + "text": "if not exist \"%rendersDir%\" (", + "lineNumber": 39 + }, + { + "text": " if \"%DRY%\"==\"1\" (", + "lineNumber": 40 + }, + { + "text": " echo [DRY] mkdir \"%rendersDir%\"", + "lineNumber": 41 + }, + { + "text": " ) else (", + "lineNumber": 42 + }, + { + "text": " mkdir \"%rendersDir%\" >nul 2>&1", + "lineNumber": 43 + }, + { + "text": " )", + "lineNumber": 44 + }, + { + "text": ")", + "lineNumber": 45 + }, + { + "lineNumber": 46 + }, + { + "text": "for %%F in (UpdateSequences.bat ZipSeqArchv.bat UnzipSeqArchv.bat) do (", + "lineNumber": 47 + }, + { + "text": " if exist \"%scriptDir%%%F\" (", + "lineNumber": 48 + }, + { + "text": " if \"%DRY%\"==\"1\" (", + "lineNumber": 49 + }, + { + "text": " echo [DRY] copy \"%scriptDir%%%F\" \"%rendersDir%\\%%F\"", + "lineNumber": 50 + }, + { + "text": " ) else (", + "lineNumber": 51 + }, + { + "text": " copy /Y \"%scriptDir%%%F\" \"%rendersDir%\\%%F\" >nul", + "lineNumber": 52 + }, + { + "text": " )", + "lineNumber": 53 + }, + { + "text": " ) else (", + "lineNumber": 54 + }, + { + "text": " echo [WARN] Missing template: \"%scriptDir%%%F\"", + "lineNumber": 55 + }, + { + "text": " )", + "lineNumber": 56 + }, + { + "text": ")", + "lineNumber": 57 + }, + { + "lineNumber": 58 + }, + { + "text": "for %%F in (ConfigLoader.ps1 config.json) do (", + "lineNumber": 59 + }, + { + "text": " if exist \"%scriptDir%%%F\" (", + "lineNumber": 60 + }, + { + "text": " if \"%DRY%\"==\"1\" (", + "lineNumber": 61 + }, + { + "text": " ", + "lineNumber": 62 + } + ] + }, + "score": 0.26310840249061584 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpdateAllSequences.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 58 + } + }, + "contents": "@echo off\nsetlocal enabledelayedexpansion\necho UpdateAllSequences - Running all UpdateSequences.bat files in submodule folders...\necho.\n\necho Searching for submodule folders and UpdateSequences.bat files...\necho.\n\nREM Find all submodule folders and check for UpdateSequences.bat\nset /a count=0\nset /a found=0\n\necho Found the following submodule folders with UpdateSequences.bat:\nfor /d %%S in (*) do (\n if exist \"%%S\\UpdateSequences.bat\" (\n echo - %%S\n set /a found+=1\n )\n)\n\nif !found!==0 (\n echo No submodule folders with UpdateSequences.bat found!\n echo.\n echo Note: This script looks for UpdateSequences.bat files in immediate subdirectories.\n echo Make sure you're running this from the project root where submodules are located.\n pause\n exit /b 1\n)\n\necho.\necho Found !found! submodule folders with UpdateSequences.bat files.\necho.\necho Starting execution...\necho.\n\nREM Execute each UpdateSequences.bat found in submodule folders\nfor /d %%S in (*) do (\n if exist \"%%S\\UpdateSequences.bat\" (\n set /a count+=1\n echo [!count!/!found!] Running UpdateSequences.bat in %%S...\n pushd \"%%S\"\n call \"UpdateSequences.bat\" < NUL\n set \"rc=!errorlevel!\"\n popd\n if !rc!==0 (\n echo Completed: %%S (SUCCESS)\n ) else (\n echo Completed: %%S (FAILED - RC=!rc!)\n )\n echo.\n )\n)\n\necho.\necho Operation completed. Successfully executed !count! UpdateSequences.bat files.\necho.\necho Note: Check individual _CURRENT\\_UpdateSequences.log files in each submodule for details.\npause", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal enabledelayedexpansion", + "lineNumber": 2 + }, + { + "text": "echo UpdateAllSequences - Running all UpdateSequences.bat files in submodule folders...", + "lineNumber": 3 + }, + { + "text": "echo.", + "lineNumber": 4 + }, + { + "lineNumber": 5 + }, + { + "text": "echo Searching for submodule folders and UpdateSequences.bat files...", + "lineNumber": 6 + }, + { + "text": "echo.", + "lineNumber": 7 + }, + { + "lineNumber": 8 + }, + { + "text": "REM Find all submodule folders and check for UpdateSequences.bat", + "lineNumber": 9 + }, + { + "text": "set /a count=0", + "lineNumber": 10 + }, + { + "text": "set /a found=0", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": "echo Found the following submodule folders with UpdateSequences.bat:", + "lineNumber": 13 + }, + { + "text": "for /d %%S in (*) do (", + "lineNumber": 14 + }, + { + "text": " if exist \"%%S\\UpdateSequences.bat\" (", + "lineNumber": 15 + }, + { + "text": " echo - %%S", + "lineNumber": 16 + }, + { + "text": " set /a found+=1", + "lineNumber": 17 + }, + { + "text": " )", + "lineNumber": 18 + }, + { + "text": ")", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": "if !found!==0 (", + "lineNumber": 21 + }, + { + "text": " echo No submodule folders with UpdateSequences.bat found!", + "lineNumber": 22 + }, + { + "text": " echo.", + "lineNumber": 23 + }, + { + "text": " echo Note: This script looks for UpdateSequences.bat files in immediate subdirectories.", + "lineNumber": 24 + }, + { + "text": " echo Make sure you're running this from the project root where submodules are located.", + "lineNumber": 25 + }, + { + "text": " pause", + "lineNumber": 26 + }, + { + "text": " exit /b 1", + "lineNumber": 27 + }, + { + "text": ")", + "lineNumber": 28 + }, + { + "lineNumber": 29 + }, + { + "text": "echo.", + "lineNumber": 30 + }, + { + "text": "echo Found !found! submodule folders with UpdateSequences.bat files.", + "lineNumber": 31 + }, + { + "text": "echo.", + "lineNumber": 32 + }, + { + "text": "echo Starting execution...", + "lineNumber": 33 + }, + { + "text": "echo.", + "lineNumber": 34 + }, + { + "lineNumber": 35 + }, + { + "text": "REM Execute each UpdateSequences.bat found in submodule folders", + "lineNumber": 36 + }, + { + "text": "for /d %%S in (*) do (", + "lineNumber": 37 + }, + { + "text": " if exist \"%%S\\UpdateSequences.bat\" (", + "lineNumber": 38 + }, + { + "text": " set /a count+=1", + "lineNumber": 39 + }, + { + "text": " echo [!count!/!found!] Running UpdateSequences.bat in %%S...", + "lineNumber": 40 + }, + { + "text": " pushd \"%%S\"", + "lineNumber": 41 + }, + { + "text": " call \"UpdateSequences.bat\" < NUL", + "lineNumber": 42 + }, + { + "text": " set \"rc=!errorlevel!\"", + "lineNumber": 43 + }, + { + "text": " popd", + "lineNumber": 44 + }, + { + "text": " if !rc!==0 (", + "lineNumber": 45 + }, + { + "text": " echo Completed: %%S (SUCCESS)", + "lineNumber": 46 + }, + { + "text": " ) else (", + "lineNumber": 47 + }, + { + "text": " echo Completed: %%S (FAILED - RC=!rc!)", + "lineNumber": 48 + }, + { + "text": " )", + "lineNumber": 49 + }, + { + "text": " echo.", + "lineNumber": 50 + }, + { + "text": " )", + "lineNumber": 51 + }, + { + "text": ")", + "lineNumber": 52 + }, + { + "lineNumber": 53 + }, + { + "text": "echo.", + "lineNumber": 54 + }, + { + "text": "echo Operation completed. Successfully executed !count! UpdateSequences.bat files.", + "lineNumber": 55 + }, + { + "text": "echo.", + "lineNumber": 56 + }, + { + "text": "echo Note: Check individual _CURRENT\\_UpdateSequences.log files in each submodule for details.", + "lineNumber": 57 + }, + { + "text": "pause", + "lineNumber": 58 + } + ] + }, + "score": 0.262160062789917 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\replace_cel_with_bsdf.py", + "range": { + "startPosition": { + "line": 355, + "column": 51 + }, + "endPosition": { + "line": 374, + "column": 51 + } + }, + "contents": "# Run the replacement\nif __name__ == \"__main__\":\n replace_cel_materials()\n\nprint(\"\\nRemaining CEL materials in file:\")\ncel_count = 0\nfor mat in bpy.data.materials:\n if mat.name.startswith(\"CEL_\"):\n print(f\" {mat.name} ({mat.users} users)\")\n cel_count += 1\n\nif cel_count == 0:\n print(\" None - all CEL materials have been replaced!\")\n\nprint(\"\\nBSDF materials in file:\")\nfor mat in bpy.data.materials:\n if mat.name.startswith(\"BSDF_\"):\n print(f\" {mat.name} ({mat.users} users)\") ", + "signatures": {}, + "detailedLines": [ + { + "text": "# Run the replacement", + "lineNumber": 358 + }, + { + "text": "if __name__ == \"__main__\":", + "lineNumber": 359 + }, + { + "text": " replace_cel_materials()", + "lineNumber": 360 + }, + { + "lineNumber": 361 + }, + { + "text": "print(\"\\nRemaining CEL materials in file:\")", + "lineNumber": 362 + }, + { + "text": "cel_count = 0", + "lineNumber": 363 + }, + { + "text": "for mat in bpy.data.materials:", + "lineNumber": 364 + }, + { + "text": " if mat.name.startswith(\"CEL_\"):", + "lineNumber": 365 + }, + { + "text": " print(f\" {mat.name} ({mat.users} users)\")", + "lineNumber": 366 + }, + { + "text": " cel_count += 1", + "lineNumber": 367 + }, + { + "lineNumber": 368 + }, + { + "text": "if cel_count == 0:", + "lineNumber": 369 + }, + { + "text": " print(\" None - all CEL materials have been replaced!\")", + "lineNumber": 370 + }, + { + "lineNumber": 371 + }, + { + "text": "print(\"\\nBSDF materials in file:\")", + "lineNumber": 372 + }, + { + "text": "for mat in bpy.data.materials:", + "lineNumber": 373 + }, + { + "text": " if mat.name.startswith(\"BSDF_\"):", + "lineNumber": 374 + }, + { + "text": " print(f\" {mat.name} ({mat.users} users)\") ", + "lineNumber": 375 + } + ] + }, + "score": 0.25491034984588623 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpdateSequences.ps1", + "range": { + "startPosition": { + "line": 262 + }, + "endPosition": { + "line": 301 + } + }, + "contents": " }\n } | Sort-Object -Property Path\n\n if (-not $sequenceFolders) {\n Write-Host \"No sequence folders found.\" -ForegroundColor Yellow\n if ($logFile) { \"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] No sequence folders found.\" | Add-Content -LiteralPath $logFile }\n exit 0\n }\n\n $totalSequences = 0\n $filesRenamedTotal = 0\n $renameCollisions = 0\n $renameErrors = 0\n $mp4RenamedTotal = 0\n $mp4Collisions = 0\n $mp4Errors = 0\n\n foreach ($seq in $sequenceFolders) {\n $totalSequences++\n $renameResult = Sync-SequenceFilenames -SequenceFolderPath $seq.Path -SequenceName $seq.Name -LogFile $logFile\n if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) {\n Write-Host \"[RENAME]|$($seq.Path)|$($seq.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)\" -ForegroundColor Cyan\n }\n $filesRenamedTotal += $renameResult.Renamed\n $renameCollisions += $renameResult.Collisions\n $renameErrors += $renameResult.Errors\n\n if ($renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) {\n $mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $seq.Path -SequenceName $seq.Name -StartFrame $renameResult.MinFrame -EndFrame $renameResult.MaxFrame -LogFile $logFile\n if ($DebugMode -or $mp4Result.Renamed -gt 0 -or $mp4Result.Collisions -gt 0 -or $mp4Result.Errors -gt 0) {\n Write-Host \"[MP4]|$($seq.Path)|$($seq.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)\" -ForegroundColor Cyan\n }\n $mp4RenamedTotal += $mp4Result.Renamed\n $mp4Collisions += $mp4Result.Collisions\n $mp4Errors += $mp4Result.Errors\n }\n }\n\n Write-Host \"=== SUMMARY REPORT ===\" -ForegroundColor Magenta", + "signatures": {}, + "detailedLines": [ + { + "text": " }", + "lineNumber": 263 + }, + { + "text": " } | Sort-Object -Property Path", + "lineNumber": 264 + }, + { + "lineNumber": 265 + }, + { + "text": " if (-not $sequenceFolders) {", + "lineNumber": 266 + }, + { + "text": " Write-Host \"No sequence folders found.\" -ForegroundColor Yellow", + "lineNumber": 267 + }, + { + "text": " if ($logFile) { \"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] No sequence folders found.\" | Add-Content -LiteralPath $logFile }", + "lineNumber": 268 + }, + { + "text": " exit 0", + "lineNumber": 269 + }, + { + "text": " }", + "lineNumber": 270 + }, + { + "lineNumber": 271 + }, + { + "text": " $totalSequences = 0", + "lineNumber": 272 + }, + { + "text": " $filesRenamedTotal = 0", + "lineNumber": 273 + }, + { + "text": " $renameCollisions = 0", + "lineNumber": 274 + }, + { + "text": " $renameErrors = 0", + "lineNumber": 275 + }, + { + "text": " $mp4RenamedTotal = 0", + "lineNumber": 276 + }, + { + "text": " $mp4Collisions = 0", + "lineNumber": 277 + }, + { + "text": " $mp4Errors = 0", + "lineNumber": 278 + }, + { + "lineNumber": 279 + }, + { + "text": " foreach ($seq in $sequenceFolders) {", + "lineNumber": 280 + }, + { + "text": " $totalSequences++", + "lineNumber": 281 + }, + { + "text": " $renameResult = Sync-SequenceFilenames -SequenceFolderPath $seq.Path -SequenceName $seq.Name -LogFile $logFile", + "lineNumber": 282 + }, + { + "text": " if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) {", + "lineNumber": 283 + }, + { + "text": " Write-Host \"[RENAME]|$($seq.Path)|$($seq.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)\" -ForegroundColor Cyan", + "lineNumber": 284 + }, + { + "text": " }", + "lineNumber": 285 + }, + { + "text": " $filesRenamedTotal += $renameResult.Renamed", + "lineNumber": 286 + }, + { + "text": " $renameCollisions += $renameResult.Collisions", + "lineNumber": 287 + }, + { + "text": " $renameErrors += $renameResult.Errors", + "lineNumber": 288 + }, + { + "lineNumber": 289 + }, + { + "text": " if ($renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) {", + "lineNumber": 290 + }, + { + "text": " $mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $seq.Path -SequenceName $seq.Name -StartFrame $renameResult.MinFrame -EndFrame $renameResult.MaxFrame -LogFile $logFile", + "lineNumber": 291 + }, + { + "text": " if ($DebugMode -or $mp4Result.Renamed -gt 0 -or $mp4Result.Collisions -gt 0 -or $mp4Result.Errors -gt 0) {", + "lineNumber": 292 + }, + { + "text": " Write-Host \"[MP4]|$($seq.Path)|$($seq.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)\" -ForegroundColor Cyan", + "lineNumber": 293 + }, + { + "text": " }", + "lineNumber": 294 + }, + { + "text": " $mp4RenamedTotal += $mp4Result.Renamed", + "lineNumber": 295 + }, + { + "text": " $mp4Collisions += $mp4Result.Collisions", + "lineNumber": 296 + }, + { + "text": " $mp4Errors += $mp4Result.Errors", + "lineNumber": 297 + }, + { + "text": " }", + "lineNumber": 298 + }, + { + "text": " }", + "lineNumber": 299 + }, + { + "lineNumber": 300 + }, + { + "text": " Write-Host \"=== SUMMARY REPORT ===\" -ForegroundColor Magenta", + "lineNumber": 301 + } + ] + }, + "score": 0.2520172595977783 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\NewProject.bat", + "range": { + "startPosition": {}, + "endPosition": { + "line": 63 + } + }, + "contents": "@echo off\nsetlocal EnableExtensions\n\nset \"SCRIPT_DIR=%~dp0\"\nset \"CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1\"\nset \"CONFIG_PATH=%SCRIPT_DIR%config.json\"\n\nif not exist \"%CONFIG_LOADER%\" (\n echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.\n exit /b 1\n)\n\nif not exist \"%CONFIG_PATH%\" (\n echo [ERROR] config.json not found next to NewProject.bat.\n exit /b 1\n)\n\nfor /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^\n \"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)\"`) do set \"STRUCT_DIR=%%I\"\n\nif not defined STRUCT_DIR (\n echo [ERROR] Unable to resolve ProjectStructure directory from config.\n exit /b 1\n)\n\n:: Get current date in YYYY-MM-DD format\nfor /f \"tokens=2-4 delims=/ \" %%a in ('date /t') do (\n set mm=%%a\n set dd=%%b\n set yy=%%c\n)\nset yyyy=20%yy:~-2%\nset yymmdd=%yy:~-2%%mm%%dd%\n\n:: Ask for project name\nset /p projectName=\"Enter project name (press Enter for default 'NewProject'): \"\nif \"%projectName%\"==\"\" set projectName=NewProject\nset projectRoot=%yymmdd%_%projectName%\n\n:: Create main project directory\nmkdir \"%projectRoot%\"\n\n:: Create Assets structure\nmkdir \"%projectRoot%\\Assets\\ElevenLabs\"\nif exist \"%STRUCT_DIR%\\NewDaily.bat\" copy /Y \"%STRUCT_DIR%\\NewDaily.bat\" \"%projectRoot%\\Assets\\ElevenLabs\\NewDaily.bat\" >nul\nmkdir \"%projectRoot%\\Assets\\Blends\"\nmkdir \"%projectRoot%\\Assets\\Mocap\"\nmkdir \"%projectRoot%\\Assets\\VO\"\nif exist \"%~dp0NewDaily.bat\" copy /Y \"%~dp0NewDaily.bat\" \"%projectRoot%\\Assets\\VO\\NewDaily.bat\" >nul\n\n:: Create Blends structure\nmkdir \"%projectRoot%\\Blends\\animations\\\"\nmkdir \"%projectRoot%\\Blends\\stills\\img-BG\"\n\n:: Create Deliverable structure\nmkdir \"%projectRoot%\\Deliverable\\\"\n\n:: Create Pr structure\nmkdir \"%projectRoot%\\Pr\\RnR\\RIFE\"\n\n:: Add project root additions\nif not exist \"%projectRoot%\\Renders\" mkdir \"%projectRoot%\\Renders\"", + "signatures": {}, + "detailedLines": [ + { + "text": "@echo off", + "lineNumber": 1 + }, + { + "text": "setlocal EnableExtensions", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "set \"SCRIPT_DIR=%~dp0\"", + "lineNumber": 4 + }, + { + "text": "set \"CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1\"", + "lineNumber": 5 + }, + { + "text": "set \"CONFIG_PATH=%SCRIPT_DIR%config.json\"", + "lineNumber": 6 + }, + { + "lineNumber": 7 + }, + { + "text": "if not exist \"%CONFIG_LOADER%\" (", + "lineNumber": 8 + }, + { + "text": " echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.", + "lineNumber": 9 + }, + { + "text": " exit /b 1", + "lineNumber": 10 + }, + { + "text": ")", + "lineNumber": 11 + }, + { + "lineNumber": 12 + }, + { + "text": "if not exist \"%CONFIG_PATH%\" (", + "lineNumber": 13 + }, + { + "text": " echo [ERROR] config.json not found next to NewProject.bat.", + "lineNumber": 14 + }, + { + "text": " exit /b 1", + "lineNumber": 15 + }, + { + "text": ")", + "lineNumber": 16 + }, + { + "lineNumber": 17 + }, + { + "text": "for /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^", + "lineNumber": 18 + }, + { + "text": " \"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)\"`) do set \"STRUCT_DIR=%%I\"", + "lineNumber": 19 + }, + { + "lineNumber": 20 + }, + { + "text": "if not defined STRUCT_DIR (", + "lineNumber": 21 + }, + { + "text": " echo [ERROR] Unable to resolve ProjectStructure directory from config.", + "lineNumber": 22 + }, + { + "text": " exit /b 1", + "lineNumber": 23 + }, + { + "text": ")", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": ":: Get current date in YYYY-MM-DD format", + "lineNumber": 26 + }, + { + "text": "for /f \"tokens=2-4 delims=/ \" %%a in ('date /t') do (", + "lineNumber": 27 + }, + { + "text": " set mm=%%a", + "lineNumber": 28 + }, + { + "text": " set dd=%%b", + "lineNumber": 29 + }, + { + "text": " set yy=%%c", + "lineNumber": 30 + }, + { + "text": ")", + "lineNumber": 31 + }, + { + "text": "set yyyy=20%yy:~-2%", + "lineNumber": 32 + }, + { + "text": "set yymmdd=%yy:~-2%%mm%%dd%", + "lineNumber": 33 + }, + { + "lineNumber": 34 + }, + { + "text": ":: Ask for project name", + "lineNumber": 35 + }, + { + "text": "set /p projectName=\"Enter project name (press Enter for default 'NewProject'): \"", + "lineNumber": 36 + }, + { + "text": "if \"%projectName%\"==\"\" set projectName=NewProject", + "lineNumber": 37 + }, + { + "text": "set projectRoot=%yymmdd%_%projectName%", + "lineNumber": 38 + }, + { + "lineNumber": 39 + }, + { + "text": ":: Create main project directory", + "lineNumber": 40 + }, + { + "text": "mkdir \"%projectRoot%\"", + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": ":: Create Assets structure", + "lineNumber": 43 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\ElevenLabs\"", + "lineNumber": 44 + }, + { + "text": "if exist \"%STRUCT_DIR%\\NewDaily.bat\" copy /Y \"%STRUCT_DIR%\\NewDaily.bat\" \"%projectRoot%\\Assets\\ElevenLabs\\NewDaily.bat\" >nul", + "lineNumber": 45 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\Blends\"", + "lineNumber": 46 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\Mocap\"", + "lineNumber": 47 + }, + { + "text": "mkdir \"%projectRoot%\\Assets\\VO\"", + "lineNumber": 48 + }, + { + "text": "if exist \"%~dp0NewDaily.bat\" copy /Y \"%~dp0NewDaily.bat\" \"%projectRoot%\\Assets\\VO\\NewDaily.bat\" >nul", + "lineNumber": 49 + }, + { + "lineNumber": 50 + }, + { + "text": ":: Create Blends structure", + "lineNumber": 51 + }, + { + "text": "mkdir \"%projectRoot%\\Blends\\animations\\\"", + "lineNumber": 52 + }, + { + "text": "mkdir \"%projectRoot%\\Blends\\stills\\img-BG\"", + "lineNumber": 53 + }, + { + "lineNumber": 54 + }, + { + "text": ":: Create Deliverable structure", + "lineNumber": 55 + }, + { + "text": "mkdir \"%projectRoot%\\Deliverable\\\"", + "lineNumber": 56 + }, + { + "lineNumber": 57 + }, + { + "text": ":: Create Pr structure", + "lineNumber": 58 + }, + { + "text": "mkdir \"%projectRoot%\\Pr\\RnR\\RIFE\"", + "lineNumber": 59 + }, + { + "lineNumber": 60 + }, + { + "text": ":: Add project root additions", + "lineNumber": 61 + }, + { + "text": "if not exist \"%projectRoot%\\Renders\" mkdir \"%projectRoot%\\Renders\"", + "lineNumber": 62 + } + ] + }, + "score": 0.24866819381713867 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpdateSequences.bat", + "range": { + "startPosition": { + "line": 31 + }, + "endPosition": { + "line": 64 + } + }, + "contents": "if not exist \"%CONFIG_PATH%\" (\n echo [ERROR] config.json not found at %CONFIG_PATH%\n if defined project_config (\n echo Also checked: %project_config%\n )\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nset \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"\n\nif not exist \"%GET_STRUCT_DIR%\" (\n echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%\n echo Please run ConfigLoader.ps1 to deploy helper files.\n exit /b 1\n)\n\nfor /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^\n \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $ps1Path = Join-Path $structDir 'UpdateSequences.ps1'; if (-not (Test-Path -LiteralPath $ps1Path)) { throw \\\"UpdateSequences.ps1 not found at $ps1Path\\\" }; Write-Output $ps1Path\"`) do set \"ps1=%%I\"\n\nif not defined ps1 (\n echo [ERROR] Unable to resolve UpdateSequences.ps1 path from config.\n exit /b 1\n)\n\necho Running PowerShell update script...\npowershell -NoProfile -ExecutionPolicy Bypass -File \"%ps1%\"\nset \"rc=%errorlevel%\"\necho PowerShell exited with RC=%rc%\necho Done.\npause >nul\nexit /b %rc%", + "signatures": {}, + "detailedLines": [ + { + "text": "if not exist \"%CONFIG_PATH%\" (", + "lineNumber": 33 + }, + { + "text": " echo [ERROR] config.json not found at %CONFIG_PATH%", + "lineNumber": 34 + }, + { + "text": " if defined project_config (", + "lineNumber": 35 + }, + { + "text": " echo Also checked: %project_config%", + "lineNumber": 36 + }, + { + "text": " )", + "lineNumber": 37 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 38 + }, + { + "text": " exit /b 1", + "lineNumber": 39 + }, + { + "text": ")", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "text": "set \"GET_STRUCT_DIR=%CONFIG_DIR%\\GetStructDir.ps1\"", + "lineNumber": 42 + }, + { + "lineNumber": 43 + }, + { + "text": "if not exist \"%GET_STRUCT_DIR%\" (", + "lineNumber": 44 + }, + { + "text": " echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%", + "lineNumber": 45 + }, + { + "text": " echo Please run ConfigLoader.ps1 to deploy helper files.", + "lineNumber": 46 + }, + { + "text": " exit /b 1", + "lineNumber": 47 + }, + { + "text": ")", + "lineNumber": 48 + }, + { + "lineNumber": 49 + }, + { + "text": "for /f \"usebackq delims=\" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^", + "lineNumber": 50 + }, + { + "text": " \"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \\\"Failed to get structDir from GetStructDir.ps1\\\" }; $ps1Path = Join-Path $structDir 'UpdateSequences.ps1'; if (-not (Test-Path -LiteralPath $ps1Path)) { throw \\\"UpdateSequences.ps1 not found at $ps1Path\\\" }; Write-Output $ps1Path\"`) do set \"ps1=%%I\"", + "lineNumber": 51 + }, + { + "lineNumber": 52 + }, + { + "text": "if not defined ps1 (", + "lineNumber": 53 + }, + { + "text": " echo [ERROR] Unable to resolve UpdateSequences.ps1 path from config.", + "lineNumber": 54 + }, + { + "text": " exit /b 1", + "lineNumber": 55 + }, + { + "text": ")", + "lineNumber": 56 + }, + { + "lineNumber": 57 + }, + { + "text": "echo Running PowerShell update script...", + "lineNumber": 58 + }, + { + "text": "powershell -NoProfile -ExecutionPolicy Bypass -File \"%ps1%\"", + "lineNumber": 59 + }, + { + "text": "set \"rc=%errorlevel%\"", + "lineNumber": 60 + }, + { + "text": "echo PowerShell exited with RC=%rc%", + "lineNumber": 61 + }, + { + "text": "echo Done.", + "lineNumber": 62 + }, + { + "text": "pause >nul", + "lineNumber": 63 + }, + { + "text": "exit /b %rc%", + "lineNumber": 64 + } + ] + }, + "score": 0.24233824014663696 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 849, + "column": 5 + }, + "endPosition": { + "line": 957 + } + }, + "contents": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:\n target_dir = sequence_dir_for(zip_path)\n if target_dir.exists():\n shutil.rmtree(target_dir)\n target_dir.mkdir(parents=True, exist_ok=True)\n\n if ZIPPER_TYPE == \"7z\":\n if SEVEN_Z_EXE is None:\n raise RuntimeError(\n \"7z extraction requested but 7z executable not found in PATH. \"\n \"Please install 7z or set zipper to 'zip' in config.json\"\n )\n cmd = [\n SEVEN_Z_EXE,\n \"x\",\n \"-y\",\n \"-mtc=on\", # Preserve timestamps during extraction\n str(zip_path),\n f\"-o{target_dir}\",\n ]\n result = subprocess.run(\n cmd,\n check=False,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n text=True,\n )\n if result.returncode != 0:\n error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"\n if result.stdout:\n error_msg += f\"\\nstdout: {result.stdout.strip()}\"\n raise RuntimeError(f\"7z extraction failed: {error_msg}\")\n elif ZIPPER_TYPE == \"zip\":\n from zipfile import ZipFile\n\n with ZipFile(zip_path, \"r\") as archive:\n archive.extractall(target_dir)\n else:\n raise RuntimeError(\n f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"\n f\"Expected '7z' or 'zip'. \"\n f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"\n )\n\n # Restore timestamps from stored state\n # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding\n is_windows = platform.system() == \"Windows\"\n restored_count = 0\n failed_count = 0\n mismatch_count = 0\n \n # Process files in sorted order to match state ordering\n files_to_restore = sorted(\n seq_state.get(\"files\", []),\n key=lambda e: e[\"path\"]\n )\n \n for entry in files_to_restore:", + "signatures": {}, + "detailedLines": [ + { + "text": "def expand_sequence(zip_path: Path, seq_state: dict, *, verbose: bool = False) -> None:", + "lineNumber": 853, + "isSignature": true + }, + { + "text": " target_dir = sequence_dir_for(zip_path)", + "lineNumber": 854 + }, + { + "text": " if target_dir.exists():", + "lineNumber": 855 + }, + { + "text": " shutil.rmtree(target_dir)", + "lineNumber": 856 + }, + { + "text": " target_dir.mkdir(parents=True, exist_ok=True)", + "lineNumber": 857 + }, + { + "lineNumber": 858 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 859 + }, + { + "text": " if SEVEN_Z_EXE is None:", + "lineNumber": 860 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 861 + }, + { + "text": " \"7z extraction requested but 7z executable not found in PATH. \"", + "lineNumber": 862 + }, + { + "text": " \"Please install 7z or set zipper to 'zip' in config.json\"", + "lineNumber": 863 + }, + { + "text": " )", + "lineNumber": 864 + }, + { + "text": " cmd = [", + "lineNumber": 865 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 866 + }, + { + "text": " \"x\",", + "lineNumber": 867 + }, + { + "text": " \"-y\",", + "lineNumber": 868 + }, + { + "text": " \"-mtc=on\", # Preserve timestamps during extraction", + "lineNumber": 869 + }, + { + "text": " str(zip_path),", + "lineNumber": 870 + }, + { + "text": " f\"-o{target_dir}\",", + "lineNumber": 871 + }, + { + "text": " ]", + "lineNumber": 872 + }, + { + "text": " result = subprocess.run(", + "lineNumber": 873 + }, + { + "text": " cmd,", + "lineNumber": 874 + }, + { + "text": " check=False,", + "lineNumber": 875 + }, + { + "text": " stdout=subprocess.PIPE,", + "lineNumber": 876 + }, + { + "text": " stderr=subprocess.PIPE,", + "lineNumber": 877 + }, + { + "text": " text=True,", + "lineNumber": 878 + }, + { + "text": " )", + "lineNumber": 879 + }, + { + "text": " if result.returncode != 0:", + "lineNumber": 880 + }, + { + "text": " error_msg = result.stderr.strip() if result.stderr else \"Unknown error\"", + "lineNumber": 881 + }, + { + "text": " if result.stdout:", + "lineNumber": 882 + }, + { + "text": " error_msg += f\"\\nstdout: {result.stdout.strip()}\"", + "lineNumber": 883 + }, + { + "text": " raise RuntimeError(f\"7z extraction failed: {error_msg}\")", + "lineNumber": 884 + }, + { + "text": " elif ZIPPER_TYPE == \"zip\":", + "lineNumber": 885 + }, + { + "text": " from zipfile import ZipFile", + "lineNumber": 886 + }, + { + "lineNumber": 887 + }, + { + "text": " with ZipFile(zip_path, \"r\") as archive:", + "lineNumber": 888 + }, + { + "text": " archive.extractall(target_dir)", + "lineNumber": 889 + }, + { + "text": " else:", + "lineNumber": 890 + }, + { + "text": " raise RuntimeError(", + "lineNumber": 891 + }, + { + "text": " f\"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. \"", + "lineNumber": 892 + }, + { + "text": " f\"Expected '7z' or 'zip'. \"", + "lineNumber": 893 + }, + { + "text": " f\"Config zipper value: {CONFIG.get('zipper', 'not set')!r}\"", + "lineNumber": 894 + }, + { + "text": " )", + "lineNumber": 895 + }, + { + "lineNumber": 896 + }, + { + "text": " # Restore timestamps from stored state", + "lineNumber": 897 + }, + { + "text": " # Windows NTFS supports 100-nanosecond precision, so we need to handle rounding", + "lineNumber": 898 + }, + { + "text": " is_windows = platform.system() == \"Windows\"", + "lineNumber": 899 + }, + { + "text": " restored_count = 0", + "lineNumber": 900 + }, + { + "text": " failed_count = 0", + "lineNumber": 901 + }, + { + "text": " mismatch_count = 0", + "lineNumber": 902 + }, + { + "text": " ", + "lineNumber": 903 + }, + { + "text": " # Process files in sorted order to match state ordering", + "lineNumber": 904 + }, + { + "text": " files_to_restore = sorted(", + "lineNumber": 905 + }, + { + "text": " seq_state.get(\"files\", []),", + "lineNumber": 906 + }, + { + "text": " key=lambda e: e[\"path\"]", + "lineNumber": 907 + }, + { + "text": " )", + "lineNumber": 908 + }, + { + "text": " ", + "lineNumber": 909 + }, + { + "text": " for entry in files_to_restore:", + "lineNumber": 910 + } + ] + }, + "score": 0.23871874809265137 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\ConfigLoader.ps1", + "range": { + "startPosition": { + "line": 296 + }, + "endPosition": { + "line": 346 + } + }, + "contents": " }\n\n $updated = 0\n $failed = 0\n\n foreach ($target in $targets) {\n try {\n Copy-Item -Path $spec.Source -Destination $target.FullName -Force\n # Set hidden attribute for .gitattributes and .gitignore files\n if ($spec.Name -eq '.gitattributes' -or $spec.Name -eq '.gitignore') {\n $file = Get-Item -LiteralPath $target.FullName -Force\n $file.Attributes = $file.Attributes -bor [System.IO.FileAttributes]::Hidden\n }\n Write-Host \"[OK] $($target.FullName)\" -ForegroundColor Green\n $updated++\n\n $targetDir = $target.Directory.FullName\n if (-not $touchedDirs.ContainsKey($targetDir)) {\n $touchedDirs[$targetDir] = $true\n }\n }\n catch {\n Write-Host \"[FAIL] $($target.FullName)\" -ForegroundColor Red\n Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed\n $failed++\n }\n }\n\n $summary += [pscustomobject]@{\n Name = $spec.Name\n Updated = $updated\n Failed = $failed\n Skipped = 0\n Total = @($targets).Count\n }\n }\n\n Write-Host \"`n=== Summary ===\" -ForegroundColor Cyan\n foreach ($item in $summary) {\n Write-Host (\"{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}\" -f $item.Name, $item.Updated, $item.Failed, $item.Total)\n }\n\n if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {\n Write-Host \"Completed with errors.\" -ForegroundColor Yellow\n exit 1\n }\n\n Write-Host \"All batch files refreshed successfully.\" -ForegroundColor Green\n\n # Deploy ZipSeqArchv.bat and UnzipSeqArchv.bat to \\Renders", + "signatures": {}, + "detailedLines": [ + { + "text": " }", + "lineNumber": 297 + }, + { + "lineNumber": 298 + }, + { + "text": " $updated = 0", + "lineNumber": 299 + }, + { + "text": " $failed = 0", + "lineNumber": 300 + }, + { + "lineNumber": 301 + }, + { + "text": " foreach ($target in $targets) {", + "lineNumber": 302 + }, + { + "text": " try {", + "lineNumber": 303 + }, + { + "text": " Copy-Item -Path $spec.Source -Destination $target.FullName -Force", + "lineNumber": 304 + }, + { + "text": " # Set hidden attribute for .gitattributes and .gitignore files", + "lineNumber": 305 + }, + { + "text": " if ($spec.Name -eq '.gitattributes' -or $spec.Name -eq '.gitignore') {", + "lineNumber": 306 + }, + { + "text": " $file = Get-Item -LiteralPath $target.FullName -Force", + "lineNumber": 307 + }, + { + "text": " $file.Attributes = $file.Attributes -bor [System.IO.FileAttributes]::Hidden", + "lineNumber": 308 + }, + { + "text": " }", + "lineNumber": 309 + }, + { + "text": " Write-Host \"[OK] $($target.FullName)\" -ForegroundColor Green", + "lineNumber": 310 + }, + { + "text": " $updated++", + "lineNumber": 311 + }, + { + "lineNumber": 312 + }, + { + "text": " $targetDir = $target.Directory.FullName", + "lineNumber": 313 + }, + { + "text": " if (-not $touchedDirs.ContainsKey($targetDir)) {", + "lineNumber": 314 + }, + { + "text": " $touchedDirs[$targetDir] = $true", + "lineNumber": 315 + }, + { + "text": " }", + "lineNumber": 316 + }, + { + "text": " }", + "lineNumber": 317 + }, + { + "text": " catch {", + "lineNumber": 318 + }, + { + "text": " Write-Host \"[FAIL] $($target.FullName)\" -ForegroundColor Red", + "lineNumber": 319 + }, + { + "text": " Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed", + "lineNumber": 320 + }, + { + "text": " $failed++", + "lineNumber": 321 + }, + { + "text": " }", + "lineNumber": 322 + }, + { + "text": " }", + "lineNumber": 323 + }, + { + "lineNumber": 324 + }, + { + "text": " $summary += [pscustomobject]@{", + "lineNumber": 325 + }, + { + "text": " Name = $spec.Name", + "lineNumber": 326 + }, + { + "text": " Updated = $updated", + "lineNumber": 327 + }, + { + "text": " Failed = $failed", + "lineNumber": 328 + }, + { + "text": " Skipped = 0", + "lineNumber": 329 + }, + { + "text": " Total = @($targets).Count", + "lineNumber": 330 + }, + { + "text": " }", + "lineNumber": 331 + }, + { + "text": " }", + "lineNumber": 332 + }, + { + "lineNumber": 333 + }, + { + "text": " Write-Host \"`n=== Summary ===\" -ForegroundColor Cyan", + "lineNumber": 334 + }, + { + "text": " foreach ($item in $summary) {", + "lineNumber": 335 + }, + { + "text": " Write-Host (\"{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}\" -f $item.Name, $item.Updated, $item.Failed, $item.Total)", + "lineNumber": 336 + }, + { + "text": " }", + "lineNumber": 337 + }, + { + "lineNumber": 338 + }, + { + "text": " if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {", + "lineNumber": 339 + }, + { + "text": " Write-Host \"Completed with errors.\" -ForegroundColor Yellow", + "lineNumber": 340 + }, + { + "text": " exit 1", + "lineNumber": 341 + }, + { + "text": " }", + "lineNumber": 342 + }, + { + "lineNumber": 343 + }, + { + "text": " Write-Host \"All batch files refreshed successfully.\" -ForegroundColor Green", + "lineNumber": 344 + }, + { + "lineNumber": 345 + }, + { + "text": " # Deploy ZipSeqArchv.bat and UnzipSeqArchv.bat to \\Renders", + "lineNumber": 346 + } + ] + }, + "score": 0.23682093620300293 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 664 + }, + "endPosition": { + "line": 849 + } + }, + "contents": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:\n\n try:\n # Create temporary archive file path (but don't create the file - let 7z create it)\n temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)\n temp_zip = Path(temp_zip_path)\n \n # Create list file with absolute path\n fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)\n list_file_path = Path(temp_path)\n with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:\n for rel_path in file_list:\n list_file.write(rel_path + \"\\n\")\n list_file.flush()\n os.fsync(list_file.fileno()) # Ensure data is written to disk\n # File is closed here by context manager, small delay to ensure OS releases handle\n time.sleep(0.1)\n \n # Use absolute paths for both list file and temp zip\n list_file_abs = list_file_path.resolve()\n temp_zip_abs = temp_zip.resolve()\n # Create archive in temp location first (7z will create it fresh)\n cmd = [\n SEVEN_Z_EXE,\n \"a\",\n \"-y\",\n \"-bb0\", # Suppress progress output\n f\"-mx={COMPRESSION_LEVEL}\",\n \"-t7z\", # Use 7z format, not zip\n ]\n \n # Set compression method and memory/dictionary size based on method\n # At compression level 0, use Copy (store) method for maximum speed\n FIXED_DICT_SIZE_MB = 1024\n if COMPRESSION_LEVEL == 0:\n # Level 0 = no compression, just store files (fastest)\n cmd.append(\"-m0=Copy\")", + "signatures": { + "ranges": [ + { + "startPosition": { + "line": 665, + "column": 1 + }, + "endPosition": { + "line": 666, + "column": 5 + } + } + ] + }, + "detailedLines": [ + { + "text": "def zip_sequence(seq_dir: Path, zip_path: Path, per_job_memory_limit: int | None = None, worker_count: int = 1, *, verbose: bool = False) -> None:", + "lineNumber": 665, + "isSignature": true + }, + { + "lineNumber": 697 + }, + { + "text": " try:", + "lineNumber": 698 + }, + { + "text": " # Create temporary archive file path (but don't create the file - let 7z create it)", + "lineNumber": 699 + }, + { + "text": " temp_zip_path = tempfile.mktemp(suffix=\".7z\", dir=zip_path.parent)", + "lineNumber": 700 + }, + { + "text": " temp_zip = Path(temp_zip_path)", + "lineNumber": 701 + }, + { + "text": " ", + "lineNumber": 702 + }, + { + "text": " # Create list file with absolute path", + "lineNumber": 703 + }, + { + "text": " fd, temp_path = tempfile.mkstemp(suffix=\".lst\", text=True)", + "lineNumber": 704 + }, + { + "text": " list_file_path = Path(temp_path)", + "lineNumber": 705 + }, + { + "text": " with os.fdopen(fd, \"w\", encoding=\"utf-8\") as list_file:", + "lineNumber": 706 + }, + { + "text": " for rel_path in file_list:", + "lineNumber": 707 + }, + { + "text": " list_file.write(rel_path + \"\\n\")", + "lineNumber": 708 + }, + { + "text": " list_file.flush()", + "lineNumber": 709 + }, + { + "text": " os.fsync(list_file.fileno()) # Ensure data is written to disk", + "lineNumber": 710 + }, + { + "text": " # File is closed here by context manager, small delay to ensure OS releases handle", + "lineNumber": 711 + }, + { + "text": " time.sleep(0.1)", + "lineNumber": 712 + }, + { + "text": " ", + "lineNumber": 713 + }, + { + "text": " # Use absolute paths for both list file and temp zip", + "lineNumber": 714 + }, + { + "text": " list_file_abs = list_file_path.resolve()", + "lineNumber": 715 + }, + { + "text": " temp_zip_abs = temp_zip.resolve()", + "lineNumber": 716 + }, + { + "text": " # Create archive in temp location first (7z will create it fresh)", + "lineNumber": 717 + }, + { + "text": " cmd = [", + "lineNumber": 718 + }, + { + "text": " SEVEN_Z_EXE,", + "lineNumber": 719 + }, + { + "text": " \"a\",", + "lineNumber": 720 + }, + { + "text": " \"-y\",", + "lineNumber": 721 + }, + { + "text": " \"-bb0\", # Suppress progress output", + "lineNumber": 722 + }, + { + "text": " f\"-mx={COMPRESSION_LEVEL}\",", + "lineNumber": 723 + }, + { + "text": " \"-t7z\", # Use 7z format, not zip", + "lineNumber": 724 + }, + { + "text": " ]", + "lineNumber": 725 + }, + { + "text": " ", + "lineNumber": 726 + }, + { + "text": " # Set compression method and memory/dictionary size based on method", + "lineNumber": 727 + }, + { + "text": " # At compression level 0, use Copy (store) method for maximum speed", + "lineNumber": 728 + }, + { + "text": " FIXED_DICT_SIZE_MB = 1024", + "lineNumber": 729 + }, + { + "text": " if COMPRESSION_LEVEL == 0:", + "lineNumber": 730 + }, + { + "text": " # Level 0 = no compression, just store files (fastest)", + "lineNumber": 731 + }, + { + "text": " cmd.append(\"-m0=Copy\")", + "lineNumber": 732 + } + ] + }, + "score": 0.2298084944486618 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpgradeToGitProj.bat", + "range": { + "startPosition": { + "line": 215 + }, + "endPosition": { + "line": 297, + "column": 2 + } + }, + "contents": " del \"%DELETE_LIST%\" >nul 2>&1\n )\n) else (\n if exist \"%DELETE_LIST%\" (\n echo.\n echo Cleanup option: Remove original seq folders that were copied?\n choice /C YN /N /M \"Delete original seq folders now? (Y/N): \"\n if errorlevel 2 (\n echo Skipping deletion of original seq folders.\n ) else (\n for /f \"usebackq delims=\" %%P in (\"%DELETE_LIST%\") do (\n echo Deleting seq folder: \"%%P\"\n rd /S /Q \"%%P\" 2>nul\n )\n echo Cleanup complete.\n )\n del \"%DELETE_LIST%\" >nul 2>&1\n )\n)\necho Done.\nexit /b 0\n\n:: ---------------------------------\n:: MergeTemplate: copy if missing; else append only missing lines\n:: %1 = templatePath, %2 = destinationPath\n:: ---------------------------------\n:MergeTemplate\nsetlocal\nset \"tpl=%~1\"\nset \"dst=%~2\"\n\nif not exist \"%tpl%\" (\n echo [WARN] Template missing: \"%tpl%\"\n endlocal & exit /b 0\n)\n\nif not exist \"%dst%\" (\n if \"%DRY%\"==\"1\" (\n echo [DRY] copy \"%tpl%\" \"%dst%\"\n ) else (\n copy /Y \"%tpl%\" \"%dst%\" >nul\n )\n) else (\n if \"%DRY%\"==\"1\" (\n echo [DRY] merge missing lines from \"%tpl%\" into \"%dst%\"\n ) else (\n for /f \"usebackq delims=\" %%L in (\"%tpl%\") do (\n >nul 2>&1 findstr /x /c:\"%%L\" \"%dst%\" || (>>\"%dst%\" echo %%L)\n )\n )\n)\n\nendlocal & exit /b 0\n\n:: ---------------------------------\n:: CopySeqToRenders: copies contents of src seq dir into dest\n:: %1 = srcSeqDir, %2 = destDir\n:: ---------------------------------\n:CopySeqToRenders\nsetlocal EnableExtensions EnableDelayedExpansion\nset \"src=%~1\"\nset \"dst=%~2\"\n\nif \"%DRY%\"==\"1\" (\n if not exist \"%src%\" endlocal & exit /b 0\n if not exist \"%dst%\" echo [DRY] mkdir \"%dst%\"\n >> \"%DELETE_LIST%\" echo %src%\n endlocal & exit /b 0\n)\n\nif not exist \"%src%\" (\n echo [INFO] Skip: missing seq folder \"%src%\"\n endlocal & exit /b 0\n)\n\nif not exist \"%dst%\" mkdir \"%dst%\" >nul 2>&1\n\nset \"ROBO_OPTS=/E /XO /XN /XC /R:1 /W:1 /NFL /NDL /NP /NJH /NJS\"\necho Copy seq: \"%src%\" -> \"%dst%\"\nrobocopy \"%src%\" \"%dst%\" * %ROBO_OPTS% >nul\nset \"__rc=%ERRORLEVEL%\"\nif not \"%__rc%\"==\"\" (\n ", + "signatures": {}, + "detailedLines": [ + { + "text": " del \"%DELETE_LIST%\" >nul 2>&1", + "lineNumber": 216 + }, + { + "text": " )", + "lineNumber": 217 + }, + { + "text": ") else (", + "lineNumber": 218 + }, + { + "text": " if exist \"%DELETE_LIST%\" (", + "lineNumber": 219 + }, + { + "text": " echo.", + "lineNumber": 220 + }, + { + "text": " echo Cleanup option: Remove original seq folders that were copied?", + "lineNumber": 221 + }, + { + "text": " choice /C YN /N /M \"Delete original seq folders now? (Y/N): \"", + "lineNumber": 222 + }, + { + "text": " if errorlevel 2 (", + "lineNumber": 223 + }, + { + "text": " echo Skipping deletion of original seq folders.", + "lineNumber": 224 + }, + { + "text": " ) else (", + "lineNumber": 225 + }, + { + "text": " for /f \"usebackq delims=\" %%P in (\"%DELETE_LIST%\") do (", + "lineNumber": 226 + }, + { + "text": " echo Deleting seq folder: \"%%P\"", + "lineNumber": 227 + }, + { + "text": " rd /S /Q \"%%P\" 2>nul", + "lineNumber": 228 + }, + { + "text": " )", + "lineNumber": 229 + }, + { + "text": " echo Cleanup complete.", + "lineNumber": 230 + }, + { + "text": " )", + "lineNumber": 231 + }, + { + "text": " del \"%DELETE_LIST%\" >nul 2>&1", + "lineNumber": 232 + }, + { + "text": " )", + "lineNumber": 233 + }, + { + "text": ")", + "lineNumber": 234 + }, + { + "text": "echo Done.", + "lineNumber": 235 + }, + { + "text": "exit /b 0", + "lineNumber": 236 + }, + { + "lineNumber": 237 + }, + { + "text": ":: ---------------------------------", + "lineNumber": 238 + }, + { + "text": ":: MergeTemplate: copy if missing; else append only missing lines", + "lineNumber": 239 + }, + { + "text": ":: %1 = templatePath, %2 = destinationPath", + "lineNumber": 240 + }, + { + "text": ":: ---------------------------------", + "lineNumber": 241 + }, + { + "text": ":MergeTemplate", + "lineNumber": 242 + }, + { + "text": "setlocal", + "lineNumber": 243 + }, + { + "text": "set \"tpl=%~1\"", + "lineNumber": 244 + }, + { + "text": "set \"dst=%~2\"", + "lineNumber": 245 + }, + { + "lineNumber": 246 + }, + { + "text": "if not exist \"%tpl%\" (", + "lineNumber": 247 + }, + { + "text": " echo [WARN] Template missing: \"%tpl%\"", + "lineNumber": 248 + }, + { + "text": " endlocal & exit /b 0", + "lineNumber": 249 + }, + { + "text": ")", + "lineNumber": 250 + }, + { + "lineNumber": 251 + }, + { + "text": "if not exist \"%dst%\" (", + "lineNumber": 252 + }, + { + "text": " if \"%DRY%\"==\"1\" (", + "lineNumber": 253 + }, + { + "text": " echo [DRY] copy \"%tpl%\" \"%dst%\"", + "lineNumber": 254 + }, + { + "text": " ) else (", + "lineNumber": 255 + }, + { + "text": " copy /Y \"%tpl%\" \"%dst%\" >nul", + "lineNumber": 256 + }, + { + "text": " )", + "lineNumber": 257 + }, + { + "text": ") else (", + "lineNumber": 258 + }, + { + "text": " if \"%DRY%\"==\"1\" (", + "lineNumber": 259 + }, + { + "text": " echo [DRY] merge missing lines from \"%tpl%\" into \"%dst%\"", + "lineNumber": 260 + }, + { + "text": " ) else (", + "lineNumber": 261 + }, + { + "text": " for /f \"usebackq delims=\" %%L in (\"%tpl%\") do (", + "lineNumber": 262 + }, + { + "text": " >nul 2>&1 findstr /x /c:\"%%L\" \"%dst%\" || (>>\"%dst%\" echo %%L)", + "lineNumber": 263 + }, + { + "text": " )", + "lineNumber": 264 + }, + { + "text": " )", + "lineNumber": 265 + }, + { + "text": ")", + "lineNumber": 266 + }, + { + "lineNumber": 267 + }, + { + "text": "endlocal & exit /b 0", + "lineNumber": 268 + }, + { + "lineNumber": 269 + }, + { + "text": ":: ---------------------------------", + "lineNumber": 270 + }, + { + "text": ":: CopySeqToRenders: copies contents of src seq dir into dest", + "lineNumber": 271 + }, + { + "text": ":: %1 = srcSeqDir, %2 = destDir", + "lineNumber": 272 + }, + { + "text": ":: ---------------------------------", + "lineNumber": 273 + }, + { + "text": ":CopySeqToRenders", + "lineNumber": 274 + }, + { + "text": "setlocal EnableExtensions EnableDelayedExpansion", + "lineNumber": 275 + }, + { + "text": "set \"src=%~1\"", + "lineNumber": 276 + }, + { + "text": "set \"dst=%~2\"", + "lineNumber": 277 + }, + { + "lineNumber": 278 + }, + { + "text": "if \"%DRY%\"==\"1\" (", + "lineNumber": 279 + }, + { + "text": " if not exist \"%src%\" endlocal & exit /b 0", + "lineNumber": 280 + }, + { + "text": " if not exist \"%dst%\" echo [DRY] mkdir \"%dst%\"", + "lineNumber": 281 + }, + { + "text": " >> \"%DELETE_LIST%\" echo %src%", + "lineNumber": 282 + }, + { + "text": " endlocal & exit /b 0", + "lineNumber": 283 + }, + { + "text": ")", + "lineNumber": 284 + }, + { + "lineNumber": 285 + }, + { + "text": "if not exist \"%src%\" (", + "lineNumber": 286 + }, + { + "text": " echo [INFO] Skip: missing seq folder \"%src%\"", + "lineNumber": 287 + }, + { + "text": " endlocal & exit /b 0", + "lineNumber": 288 + }, + { + "text": ")", + "lineNumber": 289 + }, + { + "lineNumber": 290 + }, + { + "text": "if not exist \"%dst%\" mkdir \"%dst%\" >nul 2>&1", + "lineNumber": 291 + }, + { + "lineNumber": 292 + }, + { + "text": "set \"ROBO_OPTS=/E /XO /XN /XC /R:1 /W:1 /NFL /NDL /NP /NJH /NJS\"", + "lineNumber": 293 + }, + { + "text": "echo Copy seq: \"%src%\" -> \"%dst%\"", + "lineNumber": 294 + }, + { + "text": "robocopy \"%src%\" \"%dst%\" * %ROBO_OPTS% >nul", + "lineNumber": 295 + }, + { + "text": "set \"__rc=%ERRORLEVEL%\"", + "lineNumber": 296 + }, + { + "text": "if not \"%__rc%\"==\"\" (", + "lineNumber": 297 + }, + { + "text": " ", + "lineNumber": 298 + } + ] + }, + "score": 0.22935299575328827 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": {}, + "endPosition": { + "line": 102 + } + }, + "contents": "#!/usr/bin/env python3\n\"\"\"Maintain zipped render sequences for Git hooks.\n\nDefault mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,\nand stages any updated archives so commits only track compact files. Switch to\n`--mode expand` to inflate the tracked archives back into the ignored working\ndirectories after checkouts or pulls.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport platform\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport traceback\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom pathlib import Path\nfrom typing import Iterator, Sequence\n\n# Try to import psutil for cross-platform RAM detection\ntry:\n import psutil\n HAS_PSUTIL = True\nexcept ImportError:\n HAS_PSUTIL = False\n # For Windows fallback\n if platform.system() == \"Windows\":\n try:\n import ctypes\n HAS_CTYPES = True\n except ImportError:\n HAS_CTYPES = False\n else:\n HAS_CTYPES = False\n\n\nRENDER_ROOT = Path(\"Renders\")\nARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"\nSEQUENCE_EXTENSIONS = {\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".tif\",\n \".tiff\",\n \".exr\",\n}\nSTATE_SUFFIX = \".meta.json\"\nDEFAULT_CONFIG = {\n \"zipper\": \"7z\",\n \"compression\": 9,\n \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate\n \"dailyFormat\": \"daily_YYMMDD\",\n \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)\n}\n\n\ndef log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:\n if verbose_only and not verbose:\n return\n print(f\"[{mode}] {message}\", flush=True)\n\n\ndef load_config() -> dict:\n # First try to load from project's .config folder (current working directory)\n # Then fall back to ProjectStructure repo config (next to zip_sequences.py)\n ", + "signatures": {}, + "detailedLines": [ + { + "text": "#!/usr/bin/env python3", + "lineNumber": 1 + }, + { + "text": "\"\"\"Maintain zipped render sequences for Git hooks.", + "lineNumber": 2 + }, + { + "lineNumber": 3 + }, + { + "text": "Default mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,", + "lineNumber": 4 + }, + { + "text": "and stages any updated archives so commits only track compact files. Switch to", + "lineNumber": 5 + }, + { + "text": "`--mode expand` to inflate the tracked archives back into the ignored working", + "lineNumber": 6 + }, + { + "text": "directories after checkouts or pulls.", + "lineNumber": 7 + }, + { + "text": "\"\"\"", + "lineNumber": 8 + }, + { + "lineNumber": 9 + }, + { + "text": "from __future__ import annotations", + "lineNumber": 10 + }, + { + "lineNumber": 11 + }, + { + "text": "import argparse", + "lineNumber": 12 + }, + { + "text": "import json", + "lineNumber": 13 + }, + { + "text": "import os", + "lineNumber": 14 + }, + { + "text": "import platform", + "lineNumber": 15 + }, + { + "text": "import shutil", + "lineNumber": 16 + }, + { + "text": "import subprocess", + "lineNumber": 17 + }, + { + "text": "import sys", + "lineNumber": 18 + }, + { + "text": "import tempfile", + "lineNumber": 19 + }, + { + "text": "import time", + "lineNumber": 20 + }, + { + "text": "import traceback", + "lineNumber": 21 + }, + { + "text": "from concurrent.futures import ThreadPoolExecutor, as_completed", + "lineNumber": 22 + }, + { + "text": "from pathlib import Path", + "lineNumber": 23 + }, + { + "text": "from typing import Iterator, Sequence", + "lineNumber": 24 + }, + { + "lineNumber": 25 + }, + { + "text": "# Try to import psutil for cross-platform RAM detection", + "lineNumber": 26 + }, + { + "text": "try:", + "lineNumber": 27 + }, + { + "text": " import psutil", + "lineNumber": 28 + }, + { + "text": " HAS_PSUTIL = True", + "lineNumber": 29 + }, + { + "text": "except ImportError:", + "lineNumber": 30 + }, + { + "text": " HAS_PSUTIL = False", + "lineNumber": 31 + }, + { + "text": " # For Windows fallback", + "lineNumber": 32 + }, + { + "text": " if platform.system() == \"Windows\":", + "lineNumber": 33 + }, + { + "text": " try:", + "lineNumber": 34 + }, + { + "text": " import ctypes", + "lineNumber": 35 + }, + { + "text": " HAS_CTYPES = True", + "lineNumber": 36 + }, + { + "text": " except ImportError:", + "lineNumber": 37 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 38 + }, + { + "text": " else:", + "lineNumber": 39 + }, + { + "text": " HAS_CTYPES = False", + "lineNumber": 40 + }, + { + "lineNumber": 41 + }, + { + "lineNumber": 42 + }, + { + "text": "RENDER_ROOT = Path(\"Renders\")", + "lineNumber": 43 + }, + { + "text": "ARCHIVE_ROOT = RENDER_ROOT / \"_zipped\"", + "lineNumber": 44 + }, + { + "text": "SEQUENCE_EXTENSIONS = {", + "lineNumber": 45 + }, + { + "text": " \".png\",", + "lineNumber": 46 + }, + { + "text": " \".jpg\",", + "lineNumber": 47 + }, + { + "text": " \".jpeg\",", + "lineNumber": 48 + }, + { + "text": " \".tif\",", + "lineNumber": 49 + }, + { + "text": " \".tiff\",", + "lineNumber": 50 + }, + { + "text": " \".exr\",", + "lineNumber": 51 + }, + { + "text": "}", + "lineNumber": 52 + }, + { + "text": "STATE_SUFFIX = \".meta.json\"", + "lineNumber": 53 + }, + { + "text": "DEFAULT_CONFIG = {", + "lineNumber": 54 + }, + { + "text": " \"zipper\": \"7z\",", + "lineNumber": 55 + }, + { + "text": " \"compression\": 9,", + "lineNumber": 56 + }, + { + "text": " \"compressionMethod\": \"LZMA2\", # Compression method: LZMA2 (multi-threaded), PPMd (single-threaded), BZip2, Deflate", + "lineNumber": 57 + }, + { + "text": " \"dailyFormat\": \"daily_YYMMDD\",", + "lineNumber": 58 + }, + { + "text": " \"Max7zInst\": 0, # Maximum concurrent 7z instances (0 = auto-calculate)", + "lineNumber": 59 + }, + { + "text": "}", + "lineNumber": 60 + }, + { + "lineNumber": 61 + }, + { + "lineNumber": 62 + }, + { + "text": "def log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:", + "lineNumber": 63, + "isSignature": true + }, + { + "text": " if verbose_only and not verbose:", + "lineNumber": 64 + }, + { + "text": " return", + "lineNumber": 65 + }, + { + "text": " print(f\"[{mode}] {message}\", flush=True)", + "lineNumber": 66, + "isSignature": true + }, + { + "lineNumber": 67 + }, + { + "lineNumber": 68 + }, + { + "text": "def load_config() -> dict:", + "lineNumber": 69, + "isSignature": true + }, + { + "text": " # First try to load from project's .config folder (current working directory)", + "lineNumber": 70, + "isSignature": true + }, + { + "text": " # Then fall back to ProjectStructure repo config (next to zip_sequences.py)", + "lineNumber": 71, + "isSignature": true + }, + { + "text": " ", + "lineNumber": 72 + } + ] + }, + "score": 0.2289503961801529 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\zip_sequences.py", + "range": { + "startPosition": { + "line": 1274, + "column": 23 + }, + "endPosition": { + "line": 1300, + "column": 32 + } + }, + "contents": "def main() -> int:\n args = parse_args()\n log(\"init\", \"zip_sequences starting up...\")\n log(\"init\", f\"Working directory: {Path.cwd()}\")\n log(\"init\", f\"Mode: {args.mode}, zipper: {ZIPPER_TYPE}, jobs arg: {args.jobs or 'auto'}\")\n if ZIPPER_TYPE == \"7z\":\n exe = SEVEN_Z_EXE or \"not found\"\n max_inst = MAX_7Z_INSTANCES if MAX_7Z_INSTANCES is not None else \"auto\"\n log(\"init\", f\"7z executable: {exe}, Max7zInst: {max_inst}, method: {COMPRESSION_METHOD}, level: {COMPRESSION_LEVEL}\")\n\n if args.mode == \"expand\":\n # For expand mode, use simple CPU-based worker calculation\n workers, _ = max_workers(args.jobs, work_items=None, verbose=args.verbose)\n run_expand(workers, verbose=args.verbose)\n return 0\n\n # For zip mode, work items will be calculated in run_zip\n updated = run_zip(args.jobs, verbose=args.verbose)\n return 0 if updated >= 0 else 1\n\n\nif __name__ == \"__main__\":\n try:\n raise SystemExit(main())", + "signatures": {}, + "detailedLines": [ + { + "text": "def main() -> int:", + "lineNumber": 1278, + "isSignature": true + }, + { + "text": " args = parse_args()", + "lineNumber": 1279 + }, + { + "text": " log(\"init\", \"zip_sequences starting up...\")", + "lineNumber": 1280 + }, + { + "text": " log(\"init\", f\"Working directory: {Path.cwd()}\")", + "lineNumber": 1281 + }, + { + "text": " log(\"init\", f\"Mode: {args.mode}, zipper: {ZIPPER_TYPE}, jobs arg: {args.jobs or 'auto'}\")", + "lineNumber": 1282 + }, + { + "text": " if ZIPPER_TYPE == \"7z\":", + "lineNumber": 1283 + }, + { + "text": " exe = SEVEN_Z_EXE or \"not found\"", + "lineNumber": 1284 + }, + { + "text": " max_inst = MAX_7Z_INSTANCES if MAX_7Z_INSTANCES is not None else \"auto\"", + "lineNumber": 1285 + }, + { + "text": " log(\"init\", f\"7z executable: {exe}, Max7zInst: {max_inst}, method: {COMPRESSION_METHOD}, level: {COMPRESSION_LEVEL}\")", + "lineNumber": 1286 + }, + { + "lineNumber": 1287 + }, + { + "text": " if args.mode == \"expand\":", + "lineNumber": 1288 + }, + { + "text": " # For expand mode, use simple CPU-based worker calculation", + "lineNumber": 1289 + }, + { + "text": " workers, _ = max_workers(args.jobs, work_items=None, verbose=args.verbose)", + "lineNumber": 1290 + }, + { + "text": " run_expand(workers, verbose=args.verbose)", + "lineNumber": 1291 + }, + { + "text": " return 0", + "lineNumber": 1292 + }, + { + "lineNumber": 1293 + }, + { + "text": " # For zip mode, work items will be calculated in run_zip", + "lineNumber": 1294 + }, + { + "text": " updated = run_zip(args.jobs, verbose=args.verbose)", + "lineNumber": 1295 + }, + { + "text": " return 0 if updated >= 0 else 1", + "lineNumber": 1296, + "isSignature": true + }, + { + "lineNumber": 1297 + }, + { + "lineNumber": 1298 + }, + { + "text": "if __name__ == \"__main__\":", + "lineNumber": 1299 + }, + { + "text": " try:", + "lineNumber": 1300 + }, + { + "text": " raise SystemExit(main())", + "lineNumber": 1301 + } + ] + }, + "score": 0.22807790338993073 + }, + { + "codeBlock": { + "relativeWorkspacePath": ".\\UpdateProjectBatches.ps1", + "range": { + "startPosition": { + "line": 112 + }, + "endPosition": { + "line": 164 + } + }, + "contents": " $updated++\n\n $targetDir = $target.Directory.FullName\n if (-not $touchedDirs.ContainsKey($targetDir)) {\n $touchedDirs[$targetDir] = $true\n }\n }\n catch {\n Write-Host \"[FAIL] $($target.FullName)\" -ForegroundColor Red\n Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed\n $failed++\n }\n }\n\n $summary += [pscustomobject]@{\n Name = $spec.Name\n Updated = $updated\n Failed = $failed\n Skipped = 0\n Total = @($targets).Count\n }\n}\n\nWrite-Host \"`n=== Summary ===\" -ForegroundColor Cyan\nforeach ($item in $summary) {\n Write-Host (\"{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}\" -f $item.Name, $item.Updated, $item.Failed, $item.Total)\n}\n\nif (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {\n Write-Host \"Completed with errors.\" -ForegroundColor Yellow\n exit 1\n}\n\nWrite-Host \"All batch files refreshed successfully.\" -ForegroundColor Green\n\n# Deploy config files to projectroot\\.config\\\nWrite-Host \"`n=== Deploying config files to .config\\ ===\" -ForegroundColor Magenta\nforeach ($asset in $configAssets) {\n $targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name\n try {\n Copy-Item -Path $asset.Source -Destination $targetPath -Force\n Write-Host \"[OK] $targetPath\" -ForegroundColor Green\n }\n catch {\n Write-Host \"[FAIL] $targetPath\" -ForegroundColor Red\n Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed\n exit 1\n }\n}\n\nWrite-Host \"Config files deployed successfully.\" -ForegroundColor Green", + "signatures": {}, + "detailedLines": [ + { + "text": " $updated++", + "lineNumber": 113 + }, + { + "lineNumber": 114 + }, + { + "text": " $targetDir = $target.Directory.FullName", + "lineNumber": 115 + }, + { + "text": " if (-not $touchedDirs.ContainsKey($targetDir)) {", + "lineNumber": 116 + }, + { + "text": " $touchedDirs[$targetDir] = $true", + "lineNumber": 117 + }, + { + "text": " }", + "lineNumber": 118 + }, + { + "text": " }", + "lineNumber": 119 + }, + { + "text": " catch {", + "lineNumber": 120 + }, + { + "text": " Write-Host \"[FAIL] $($target.FullName)\" -ForegroundColor Red", + "lineNumber": 121 + }, + { + "text": " Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed", + "lineNumber": 122 + }, + { + "text": " $failed++", + "lineNumber": 123 + }, + { + "text": " }", + "lineNumber": 124 + }, + { + "text": " }", + "lineNumber": 125 + }, + { + "lineNumber": 126 + }, + { + "text": " $summary += [pscustomobject]@{", + "lineNumber": 127 + }, + { + "text": " Name = $spec.Name", + "lineNumber": 128 + }, + { + "text": " Updated = $updated", + "lineNumber": 129 + }, + { + "text": " Failed = $failed", + "lineNumber": 130 + }, + { + "text": " Skipped = 0", + "lineNumber": 131 + }, + { + "text": " Total = @($targets).Count", + "lineNumber": 132 + }, + { + "text": " }", + "lineNumber": 133 + }, + { + "text": "}", + "lineNumber": 134 + }, + { + "lineNumber": 135 + }, + { + "text": "Write-Host \"`n=== Summary ===\" -ForegroundColor Cyan", + "lineNumber": 136 + }, + { + "text": "foreach ($item in $summary) {", + "lineNumber": 137 + }, + { + "text": " Write-Host (\"{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}\" -f $item.Name, $item.Updated, $item.Failed, $item.Total)", + "lineNumber": 138 + }, + { + "text": "}", + "lineNumber": 139 + }, + { + "lineNumber": 140 + }, + { + "text": "if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {", + "lineNumber": 141 + }, + { + "text": " Write-Host \"Completed with errors.\" -ForegroundColor Yellow", + "lineNumber": 142 + }, + { + "text": " exit 1", + "lineNumber": 143 + }, + { + "text": "}", + "lineNumber": 144 + }, + { + "lineNumber": 145 + }, + { + "text": "Write-Host \"All batch files refreshed successfully.\" -ForegroundColor Green", + "lineNumber": 146 + }, + { + "lineNumber": 147 + }, + { + "text": "# Deploy config files to projectroot\\.config\\", + "lineNumber": 148 + }, + { + "text": "Write-Host \"`n=== Deploying config files to .config\\ ===\" -ForegroundColor Magenta", + "lineNumber": 149 + }, + { + "text": "foreach ($asset in $configAssets) {", + "lineNumber": 150 + }, + { + "text": " $targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name", + "lineNumber": 151 + }, + { + "text": " try {", + "lineNumber": 152 + }, + { + "text": " Copy-Item -Path $asset.Source -Destination $targetPath -Force", + "lineNumber": 153 + }, + { + "text": " Write-Host \"[OK] $targetPath\" -ForegroundColor Green", + "lineNumber": 154 + }, + { + "text": " }", + "lineNumber": 155 + }, + { + "text": " catch {", + "lineNumber": 156 + }, + { + "text": " Write-Host \"[FAIL] $targetPath\" -ForegroundColor Red", + "lineNumber": 157 + }, + { + "text": " Write-Host \" $($_.Exception.Message)\" -ForegroundColor DarkRed", + "lineNumber": 158 + }, + { + "text": " exit 1", + "lineNumber": 159 + }, + { + "text": " }", + "lineNumber": 160 + }, + { + "text": "}", + "lineNumber": 161 + }, + { + "lineNumber": 162 + }, + { + "text": "Write-Host \"Config files deployed successfully.\" -ForegroundColor Green", + "lineNumber": 163 + } + ] + }, + "score": 0.2242240011692047 + } + ] +} +``` +Status: **completed** + + +
+
+ +--- + + +The script may be failing silently or output isn't being flushed. Adding error handling and ensuring output is flushed: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "2207d097-27e8-4bcb-ae5b-367079579f76" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "731511b7-b3f7-469d-b16f-b7d6d47c1af8" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "215bab71-adfc-4c8b-8d1f-daba4d180adf" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "e1203d4b-ad57-42da-b705-324cf7ed2fc0" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding a flush after each print to ensure output is captured, and handling the case where `reconfigure` might not be available: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "84eb59c3-02ce-4bf4-a1ec-05dc80c94f7c" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "47f9922f-0ec8-435a-a2ae-1d24a16bde70" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "8345a360-abeb-4058-ba0d-2906b7d85a52" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "b3259b29-313b-4297-b245-9a1c4fdad16a" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding a top-level try-except to catch early failures and ensure we see errors: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "2814e5f0-5638-4046-9fe2-9df6caa45fe6" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "19d45f1e-6e55-440a-b3ad-4218487ac73d" +} +``` +Status: **completed** + + +
+
+ +--- + + +Fixing indentation so all code is inside the try block: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Restructuring so functions are defined at module level and only the execution code is in the try block: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "a943cc8f-d6c2-49a0-b5e5-c84febac4dc5" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "f092f9de-2fc8-468b-89d8-db27a19596b3" +} +``` +Status: **completed** + + +
+
+ +--- + + +Updated the script with: + +1. **Top-level error handling** - Wraps the entire script in a try-except to catch fatal errors +2. **Output flushing** - Calls `sys.stdout.flush()` after each print to ensure output is captured +3. **Early diagnostic output** - Prints "Script started successfully" immediately to confirm the script is running +4. **Proper indentation** - All code is inside the try block for proper error handling +5. **Version compatibility** - Handles cases where `reconfigure()` might not be available + +The script should now: +- Show output immediately when it starts +- Display the Blender version +- Show how many files were found +- Process files with proper error reporting +- Capture all output even if there are errors + +Run the compression again. You should see output in `blender_output.txt`, including any errors that were previously hidden. If you still see no output, the issue may be with how Blender is being invoked or how output is being redirected. + +--- + +_**User**_ + +it's instantly closing, I can't read it. + +--- + +_**Agent (model default, mode Agent)**_ + + +Adding a pause at the end so the window stays open, and ensuring output is saved to a file: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +There's a pause at the end, but if the script fails early, it may exit before the pause. Also, the output file is deleted. Fixing this: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "7223371d-407d-4526-a614-4f9b81dd8ab2" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "ab24e4b7-7463-4fcf-814d-ff3c42e4e59c" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "88b98ba3-889d-4bcc-8fe9-d2a6fa2c3308" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "496b2c40-a780-4d83-8c59-895e1e6b017d" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding error handling for when Blender isn't found: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "e56281e0-7da5-4059-b925-45cc732c2378" +} +``` +Status: **completed** + + +
+
+ +--- + + +Made these changes: + +1. **Changed all `pause` to `pause >nul`** — waits without showing "Press any key to continue..." +2. **Added Blender PATH check** — verifies Blender is available before running +3. **Preserved output file** — no longer deletes `blender_output.txt` so you can review it +4. **Shows output preview** — displays the first 50 lines of the output file in the console +5. **Better error messages** — clearer messages if something fails +6. **Exit code tracking** — shows Blender's exit code for debugging + +The window should stay open and show: +- Progress messages +- A preview of the Blender output +- A summary of processed files +- The location of the full output file + +Run it again; the window should stay open so you can see what's happening. The full output is saved in `blender_output.txt` in the same directory as the batch file. + +--- + +_**User**_ + +it's still instantly closing and the logs are useless @a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the output file and adding better error handling: + + +--- + + +
Tool use: **read_file_v2** • Read file: a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +The Python script isn't producing output, suggesting it's failing silently. Adding error handling and keeping the window open. Also checking the generated script: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "5fd2c27b-8419-4a80-8bc3-1a6a4ebf8a49" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "3b2f9a83-14b7-41f7-ba5c-07294808d784" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "9ceba06b-772e-42d5-9fa2-c403b43a29b4" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "57c15e7e-8660-4603-a92b-f0594d14302f" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "f032df71-ebee-48d6-9126-7b2f52949f9d" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding a Python syntax check and using absolute paths to ensure the script is found: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "c806c3b9-c1ee-44fe-93af-e0de1009417a" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "d32a06dd-4cc3-457f-a797-19cac98a9d0a" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding an immediate test print at the start of the Python script to verify it's running: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "a2d853d0-f43b-4c80-923a-d36617b26b50" +} +``` +Status: **completed** + + +
+
+ +--- + + +Made these changes: + +1. **Immediate test output** - Added a print at the very start of the Python script (before the try block) so we can see if Blender is executing it +2. **Absolute paths** - Using full paths for the script file so Blender can find it +3. **Python syntax check** - Validates the generated script before running +4. **Better debugging** - Shows script location, size, and first 15 lines +5. **Window stays open** - All error paths now pause before exiting + +The script should now: +- Show the working directory +- Display the generated Python script content +- Check Python syntax +- Show detailed Blender output +- Keep the window open so you can read everything + +Run it again. The window should stay open and show detailed debugging information. If the Python script still doesn't produce output, the debugging info will help identify why. + +--- + +_**User**_ + +now it closes even faster, doesn't even seem to run anything anymore. + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the batch file for syntax errors that might cause an immediate exit: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Fixing path handling and simplifying the batch file: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "89d7bd93-082c-421f-8d48-6108ed09581c" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "a513a637-bb75-4838-845a-313dee7818d6" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "437c1c4f-2793-4173-9cd8-12697c11646f" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "7a91d6a0-5d28-4deb-89f2-7f94fc07c2fd" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding a simple test at the start to verify the batch file runs: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "39d6ad25-5ca4-47b0-bef4-0527935f62f4" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "2a848892-012b-41b1-8361-af417187d5aa" +} +``` +Status: **completed** + + +
+
+ +--- + + +Creating a minimal test to verify the batch file runs. Adding a simple pause at the very start: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: cd "A:\1 Amazon_Active_Projects\3 ProjectStructure" && echo Test && pause + +```bash +cd "A:\1 Amazon_Active_Projects\3 ProjectStructure" && echo Test && pause +``` + +``` +Test +Read-Host: PowerShell is in NonInteractive mode. Read and Prompt functionality is not available. + +``` +
+
+ +--- + + +Simplifying the output display and adding better error handling: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "0687edb4-0828-4a38-bbe8-3979a6a6fa82" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "0c192452-cf8a-4fc4-a6e0-608f8e374353" +} +``` +Status: **completed** + + +
+
+ +--- + + +Adding a test at the start to verify the batch file runs: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "eed4b25b-d270-49f7-b673-532224dbff0b" +} +``` +Status: **completed** + + +
+
+ +--- + + +The batch file should now: +1. Show "[BATCH FILE STARTED]" immediately when it runs +2. Display all debugging information +3. Keep the window open at the end + +If it still closes instantly, try: +1. Right-click the batch file → "Run as administrator" +2. Run it from a command prompt to see error messages +3. Check if Windows is blocking the script execution + +The script now includes: +- Immediate output to confirm it's running +- Better error messages +- Full output display +- A pause at the end to keep the window open + +Run it again. If it still closes instantly, the "[BATCH FILE STARTED]" message should appear briefly, confirming the batch file is executing but exiting early. + +--- + +_**User**_ + +now it's outputting to the batch's repo, not the directory from which the blends were opened @blender_output.txt + +--- + +_**Agent (model default, mode Agent)**_ + + +Fixing path handling so the output is saved in the blend files' directory, not the batch file's directory: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Updating the script to detect the blend files' directory and use it for output: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "97807f75-6d7b-4998-b8a9-10a226033a54" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "686f3b31-a231-4c2f-9e76-63f93611131c" +} +``` +Status: **completed** + + +
+
+ +--- + +File not found: + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "2e48117f-b832-4978-9f11-f55839403cbe" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "fbb6d5c7-a3ca-4a01-9062-fc1d2afc876f" +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Updating the output parsing to use the correct file path: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + + + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "4a0fad8c-ed0a-4e35-8fff-6ce6482f1982" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "0425ce9f-de48-401c-90fb-bd6adcd5dd2a" +} +``` +Status: **completed** + + +
+
+ +--- + + +Fixing the for loop syntax: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "582ed715-7ebd-454b-ae32-0cd6cc926c66" +} +``` +Status: **completed** + + +
+
+ +--- + + +The script now: + +1. **Detects the blend files' directory** - Uses `%~dp1` to get the directory of the first dragged file +2. **Changes to that directory** - Sets the working directory to where the blend files are located +3. **Creates output there** - Creates the `output` folder and `blender_output.txt` in the blend files' directory +4. **Uses absolute paths** - The Python script and output file use full paths + +When you drag blend files onto the batch file: +- The script runs from the blend files' directory +- The `output` folder is created there +- `blender_output.txt` is saved there +- All paths are relative to the blend files' location + +Try running it again; the output should appear in the same directory as your blend files, not in the batch file's repository. + +--- + +_**User**_ + +@a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt why are the logs still useless? + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the output file to see what's happening: + + +--- + + +
Tool use: **read_file_v2** • Read file: a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +The Python script isn't producing output; Blender starts and quits without script output. Adding error handling and ensuring output is captured: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "788c20e1-ad91-42d1-b543-0fb6dfa93000" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "17dd3695-d870-4a40-b479-3c14c5559ee6" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "543318d5-29df-4304-958d-1bf15f1a657f" +} +``` +Status: **completed** + + +
+
+ +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "6842a6f1-5255-4226-bba2-4393e257fff6" +} +``` +Status: **completed** + + +
+
+ +--- + +The string to replace was not found in the file. + +--- + + +Finding the correct location for the Blender command: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + + + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Fixing the Blender command to use the correct output file: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "14e67548-ae0e-446c-b21e-242e7ce5a650" +} +``` +Status: **completed** + + +
+
+ +--- + + +Made these changes: + +1. **Added stderr output** - Writes to both stdout and stderr so errors are visible even if stdout is suppressed +2. **Simplified output redirection** - Replaced the PowerShell pipe with direct file redirection (`>"!OUTPUT_LOG!" 2>&1`) to capture all output +3. **Added script verification** - Verifies the script exists before running Blender +4. **Fixed OUTPUT_LOG variable** - Sets it before the Blender command + +The main issue was the PowerShell pipe (`| powershell -NoProfile -Command...`) which may have been dropping output. Using direct file redirection should capture all output, including Python script output. + +Run it again. The output file should include: +- The "BLENDER COMPRESSION SCRIPT STARTED" message +- All Python print statements +- Any errors or exceptions +- Full traceback if something fails + +The logs should now be useful for debugging. + +--- + +_**User**_ + +@a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt still useless. + +--- + +_**Agent (model default, mode Agent)**_ + + +Checking the output file and inspecting the generated Python script: + + +--- + + +
Tool use: **read_file_v2** • Read file: a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +
+
+ +--- + + +
+Tool use: **glob_file_search** • Searched codebase "undefined" • **0** result in **1** directory + +Directory: **a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills** (0 file) + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Syntax error in the generated Python script: the `>` operator is being lost. Fixing the batch file escaping: + + +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "current\\[0\\] \\^&gt;", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "current\\[0\\] \\^&gt;", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": {} + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +Fixing the `>` escaping in the batch file. The `^>` isn't working correctly: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "a9d424d9-bb6f-40f9-9ebc-a10ee5bf00f3" +} +``` +Status: **completed** + + +
+
+ +--- + + +Checking for other comparison operators that need fixing: + + +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "\\^&gt;=|&lt;|&gt;", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "\\^&gt;=|&lt;|&gt;", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": {} + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Rewriting the version comparison to use Python tuple comparison, avoiding the `>` operator: + + +--- + + +
+Tool use: **edit_file_v2** + + +Parameters: + +```json +{ + "relativeWorkspacePath": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat" +} +``` +Additional data: + +```json +{ + "codeblockId": "aa2cef8b-9d5c-41a2-a297-b61f1ef65db9" +} +``` +Status: **completed** + + +
+
+ +--- + + +Checking for other comparison operators that might cause issues: + + +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "print.*file=sys", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "print.*file=sys", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": { + "matches": [ + { + "file": "compress_blend_files.bat", + "matches": [ + { + "lineNumber": 63, + "content": "echo print^(\"=\" * 60, file=sys.stderr^)" + }, + { + "lineNumber": 64, + "content": "echo print^(\"BLENDER COMPRESSION SCRIPT STARTED\", file=sys.stderr^)" + }, + { + "lineNumber": 65, + "content": "echo print^(\"=\" * 60, file=sys.stderr^)" + }, + { + "lineNumber": 191, + "content": "echo traceback.print_exc^(file=sys.stderr^)" + } + ] + } + ], + "totalLines": 4, + "totalMatchedLines": 4 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +Checking the f-string usage: + + +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "f\\{", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "f\\{", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": {} + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + + + + +--- + + +
+Tool use: **ripgrep_raw_search** + + +Parameters: + +```json +{ + "pattern": "f\\\".*file_count", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "caseInsensitive": false +} +``` +Result: + +```json +{ + "success": { + "pattern": "f\\\".*file_count", + "path": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure\\compress_blend_files.bat", + "outputMode": "content", + "workspaceResults": { + "A:\\1 Amazon_Active_Projects\\3 ProjectStructure": { + "content": { + "matches": [ + { + "file": "compress_blend_files.bat", + "matches": [ + { + "lineNumber": 129, + "content": "echo sys.stderr.write^(f\"Found {file_count} .blend files to compress\\n\"^)" + } + ] + } + ], + "totalLines": 1, + "totalMatchedLines": 1 + } + } + } + } +} +``` +Status: **completed** + + +
+
+ +--- + + +
Tool use: **read_file_v2** • Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\compress_blend_files.bat + +
+
+ +--- + + +The main issue was the `>` comparison operator. Fixed by using Python's tuple comparison, which is cleaner and avoids batch file escaping issues. + +The script should now: +1. Generate valid Python syntax (no more syntax errors) +2. Properly compare Blender versions using tuple comparison +3. Produce useful output in the log file + +Run it again. The output file should show: +- "BLENDER COMPRESSION SCRIPT STARTED" message +- Blender version detection +- File processing information +- Any errors with full tracebacks + +The logs should now be useful for debugging. + +--- + +_**User**_ + +@a:\1 Amazon_Active_Projects\251216_NonCon_Media\Blends\stills\blender_output.txt + +--- + diff --git a/components/gitignore b/components/gitignore index d0c5b87..242ab6a 100644 --- a/components/gitignore +++ b/components/gitignore @@ -120,5 +120,7 @@ Adobe Premiere Pro Video Previews/ Adobe Premiere Pro Audio Previews/ Adobe Premiere Pro (Beta) Video Previews/ Adobe Premiere Pro (Beta) Audio Previews/ +footage/ +plates/ Pr/RnR/ \ No newline at end of file diff --git a/config.json b/config.json index 71c04d8..d6572d9 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { - "dailyFormat": "YYYY-MM-DD", - "structDir": "R:\\Creative\\artsy\\maya\\0 ProjectStructure", - "zipper": "7z", - "compression": 0, - "Max7zInst": 0 + "dailyFormat": "YYYY-MM-DD", + "structDir": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure", + "zipper": "7z", + "compression": 0, + "Max7zInst": 0 } \ No newline at end of file