2026-03-17

This commit is contained in:
2026-03-17 15:41:02 -06:00
parent 330fed4231
commit 5a151a5ba4
14 changed files with 1393 additions and 73 deletions
+1 -1
View File
@@ -10,6 +10,7 @@ D:\Work\9 iClone\Amazon\
D:\Amazon\00_external-files\
N:\1. CHARACTERS\remapping\
[Recent]
E:\SteamLibrary\steamapps\common\Blender\5.0\scripts\startup\
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Deliverable\
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\stills\
A:\1 Amazon_Active_Projects\260127_Pick-and-Stage_2026_edits\Blends\stills\
@@ -19,4 +20,3 @@ P:\260217_Jarvis-Defense\Blends\animations\
T:\1 BlenderAssets\
A:\1 Amazon_Active_Projects\1 BlenderAssets\
F:\renders\
P:\260217_Jarvis-Defense\Assets\Blends\Props\
+1
View File
@@ -14,3 +14,4 @@
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 591.74}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/NVIDIA 595.79}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 4080 SUPER/PCIe/SSE2/4.6.0 NVIDIA 595.79}=SUPPORTED
{NVIDIA Corporation/NVIDIA GeForce RTX 3090/NVIDIA 595.79}=SUPPORTED
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
schema_version = "1.0.0"
id = "Dark_Purple_Green"
version = "1.0.1"
name = "DarkPurpleGreen"
tagline = "Dark minimal neon theme"
maintainer = "MSBH <msbh.contact@gmail.com>"
type = "theme"
blender_version_min = "5.0.0"
tags = ["Dark"]
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/extensions/licenses.html
license = [
"SPDX:GPL-2.0-or-later",
]
@@ -79,7 +79,7 @@
"id": "sheepit_project_submitter",
"name": "SheepIt Project Submitter",
"tagline": "Submit projects to SheepIt render farm",
"version": "0.0.7",
"version": "0.0.8",
"type": "add-on",
"maintainer": "RaincloudTheDragon",
"license": [
@@ -92,9 +92,9 @@
"submission",
"utility"
],
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.7/SheepIt_Project_Submitter.v0.0.7.zip",
"archive_size": 47250,
"archive_hash": "sha256:cb8dee48c45cc51dd8237981f4ab96d97d476b547c8c640606e9bbfd0390a055"
"archive_url": "https://github.com/RaincloudTheDragon/sheepit_project_submitter/releases/download/v0.0.8/SheepIt_Project_Submitter.v0.0.8.zip",
"archive_size": 47667,
"archive_hash": "sha256:93cd8f18456079130c48c66cfd40235f7fe6414f929f59f90670e7a864821110"
}
]
}
@@ -1,9 +1,18 @@
## [v0.0.8] - 2026-03-16
### Fixed
- Texture copy: dedupe assets by resolved path and use normalized copy_map keys to prevent mixed UNC/drive paths causing missing textures.
- Pack as Blend: report "Blend file saved" instead of "ZIP file saved".
- Pack linked: remove missing libraries before pack_libraries() to prevent failures when external blends are unavailable.
---
## [v0.0.7] - 2026-02-12
### Fixed
- Pack: enable autopack before pack_all; force-load images and run pack_all twice; pack remaining images so textures are embedded (fixes "Failed to create GPU texture from Blender image" when rendering headless).
- Remap: print actual paths in warnings (not placeholders); normalized path lookup and reverse copy_map so library blend image paths resolve.
- pack_linked: catch PermissionError on library path checks so inaccessible (e.g. NAS) libs dont abort; remove missing/inaccessible library refs from blend before save.
- pack_linked: catch PermissionError on library path checks so inaccessible (e.g. NAS) libs don't abort; remove missing/inaccessible library refs from blend before save.
---
@@ -93,4 +102,4 @@
- Modal operator architecture for responsive UI
- Incremental packing system for large projects
- Subprocess-based asset processing for stability
- Comprehensive error handling and user feedback
- Comprehensive error handling and user feedback
@@ -2,7 +2,7 @@ schema_version = "1.0.0"
id = "sheepit_project_submitter"
name = "SheepIt Project Submitter"
version = "0.0.7"
version = "0.0.8"
type = "add-on"
author = "RaincloudTheDragon"
maintainer = "RaincloudTheDragon"
@@ -115,6 +115,14 @@ def compute_target_relpath(abs_path: Path, base_root: Path) -> Path:
return Path(label) / Path(rel_after_anchor)
def _norm_copy_map_key(path) -> str:
"""Normalize path for copy_map key; must match remap script norm_key."""
s = str(Path(path).resolve())
if os.name == "nt":
return s.lower().replace("\\", "/")
return s
def copy_blend_caches(src_blend: Path, dst_blend: Path, missing_on_copy: list,
frame_start: Optional[int] = None, frame_end: Optional[int] = None,
frame_step: Optional[int] = None,
@@ -205,9 +213,7 @@ def copy_blend_caches(src_blend: Path, dst_blend: Path, missing_on_copy: list,
def _add_cache_dir_to_map(sdir: Path, ddir: Path):
if copy_map_out is not None:
copy_map_out[str(Path(sdir).resolve())] = str(ddir.resolve())
if os.name == "nt" and str(sdir) != str(Path(sdir).resolve()):
copy_map_out[str(sdir)] = str(ddir.resolve())
copy_map_out[_norm_copy_map_key(sdir)] = str(ddir.resolve())
for src_dir, dst_dir in candidates:
if os.name == "nt":
@@ -827,11 +833,11 @@ def pack_linked_in_blend(blend_path: Path, max_size_bytes: Optional[int] = None)
"import bpy\n"
"from pathlib import Path\n"
"print('=== Pack Linked Operation ===')\n"
"print(f'Processing: {bpy.path.basename(bpy.data.filepath)}')\n"
"print('Processing: ' + bpy.path.basename(bpy.data.filepath))\n"
"print('Libraries found:', len(bpy.data.libraries))\n"
"missing_files = []\n"
"oversized_files = []\n"
"libs_to_remove = []\n"
"lib_info = []\n"
"for lib in list(bpy.data.libraries):\n"
" lib_path = Path(lib.filepath)\n"
" if lib.filepath.startswith('//'):\n"
@@ -842,51 +848,67 @@ def pack_linked_in_blend(blend_path: Path, max_size_bytes: Optional[int] = None)
" exists = False\n"
" if not exists:\n"
" missing_files.append(str(lib_path))\n"
" libs_to_remove.append(lib)\n"
" lib_info.append((lib.name, str(lib_path), 'missing'))\n"
" print(' Library (MISSING or inaccessible):', lib.name, ', path:', lib.filepath)\n"
" else:\n"
" try:\n"
" file_size = lib_path.stat().st_size\n"
" except (OSError, PermissionError):\n"
" missing_files.append(str(lib_path))\n"
" libs_to_remove.append(lib)\n"
" lib_info.append((lib.name, str(lib_path), 'missing'))\n"
" print(' Library (inaccessible):', lib.name, ', path:', lib.filepath)\n"
" continue\n"
" file_size_gb = file_size / (1024 * 1024 * 1024)\n"
" if file_size > " + str(max_size_bytes) + ":\n"
" oversized_files.append(str(lib_path))\n"
" lib_info.append((lib.name, str(lib_path), 'oversized'))\n"
" print(' Library (OVER limit, cannot pack):', lib.name, ', path:', lib.filepath, ', size:', round(file_size_gb, 2), 'GB')\n"
" else:\n"
" lib_info.append((lib.name, str(lib_path), 'ok'))\n"
" print(' Library (found,', round(file_size_gb, 2), 'GB):', lib.name, ', path:', lib.filepath)\n"
"for lib in libs_to_remove:\n"
" try:\n"
"missing_lib_names = [name for name, path, status in lib_info if status == 'missing']\n"
"if missing_lib_names:\n"
" print('WARNING:', len(missing_lib_names), 'linked libraries not found, removing from blend before pack')\n"
" for lib_name in missing_lib_names:\n"
" try:\n"
" bpy.data.libraries.remove(lib, do_unlink=True, do_id_user=True, do_ui_user=True)\n"
" except TypeError:\n"
" bpy.data.libraries.remove(lib)\n"
" print(' Removed missing library from blend:', lib.name)\n"
" except Exception as e:\n"
" print(' Could not remove library', lib.name, ':', e)\n"
"if missing_files:\n"
" print('WARNING:', len(missing_files), 'linked libraries not found and were removed from the blend')\n"
" lib = bpy.data.libraries.get(lib_name)\n"
" if lib:\n"
" bpy.data.libraries.remove(lib)\n"
" print(' Removed missing library:', lib_name)\n"
" except Exception as e:\n"
" print(' Could not remove library', lib_name, ':', e)\n"
"if oversized_files:\n"
" print('WARNING:', len(oversized_files), 'linked libraries are over size limit and cannot be packed by Blender')\n"
"try:\n"
" bpy.ops.file.make_paths_relative()\n"
" print('Made paths relative')\n"
"except Exception as e:\n"
" print(f'Warning: make_paths_relative failed: {e}')\n"
" print('Warning: make_paths_relative failed: ' + str(e))\n"
"packed_count = 0\n"
"pack_errors = []\n"
"print('Libraries before pack: ' + str(len(bpy.data.libraries)))\n"
"for lib in bpy.data.libraries:\n"
" print(' - ' + lib.name + ': ' + lib.filepath)\n"
"try:\n"
" print('Starting pack_libraries()...')\n"
" bpy.ops.file.pack_libraries()\n"
" packed_count = 1\n"
" print('pack_libraries() completed successfully')\n"
" result = bpy.ops.file.pack_libraries()\n"
" print('pack_libraries() returned: ' + str(result))\n"
" packed_count = len(bpy.data.libraries)\n"
" print('pack_libraries() completed successfully, ' + str(packed_count) + ' libraries processed')\n"
"except Exception as e:\n"
" error_msg = f'{type(e).__name__}: {str(e)}'\n"
" error_msg = type(e).__name__ + ': ' + str(e)\n"
" pack_errors.append(error_msg)\n"
" print(f'Warning: pack_libraries() failed: {error_msg}')\n"
" print('Warning: pack_libraries() failed: ' + error_msg)\n"
"print('Libraries after pack: ' + str(len(bpy.data.libraries)))\n"
"for lib in bpy.data.libraries:\n"
" is_packed = hasattr(lib, 'packed_file') and lib.packed_file is not None\n"
" print(' - ' + lib.name + ': packed=' + str(is_packed))\n"
"try:\n"
" print('Running pack_all() to ensure all data is packed...')\n"
" bpy.ops.file.pack_all()\n"
" print('pack_all() completed')\n"
"except Exception as e:\n"
" print('pack_all() warning: ' + str(e))\n"
"try:\n"
" fp = bpy.context.preferences.filepaths\n"
" for k in ('use_autopack', 'use_autopack_files', 'use_auto_pack'):\n"
@@ -899,13 +921,13 @@ def pack_linked_in_blend(blend_path: Path, max_size_bytes: Optional[int] = None)
" pass\n"
"print('Saving file...')\n"
"bpy.ops.wm.save_mainfile(compress=True)\n"
"print(f'=== Pack Linked Complete (packed: {packed_count}, missing: {len(missing_files)}, oversized: {len(oversized_files)}) ===')\n"
"print('=== Pack Linked Complete (packed: ' + str(packed_count) + ', missing: ' + str(len(missing_files)) + ', oversized: ' + str(len(oversized_files)) + ') ===')\n"
"for mf in missing_files:\n"
" print(f'MISSING_FILE: {mf}')\n"
" print('MISSING_FILE: ' + str(mf))\n"
"for of in oversized_files:\n"
" print(f'OVERSIZED_FILE: {of}')\n"
" print('OVERSIZED_FILE: ' + str(of))\n"
"for err in pack_errors:\n"
" print(f'PACK_ERROR: {err}')\n"
" print('PACK_ERROR: ' + str(err))\n"
)
stdout, stderr, returncode = _run_blender_script(script, blend_path, timeout=600) # 10 minute timeout for pack_linked
@@ -1159,17 +1181,19 @@ class IncrementalPacker:
if self.progress_callback:
self.progress_callback(10.0, "Collecting file paths...")
self.all_filepaths = []
self.all_filepaths.extend(au.library_abspath(lib) for lib in self.asset_usages.keys())
self.all_filepaths.extend(au.library_abspath(lib).resolve() for lib in self.asset_usages.keys())
self.all_filepaths.extend(
asset_usage.abspath
asset_usage.abspath.resolve()
for asset_usages in self.asset_usages.values()
for asset_usage in asset_usages
)
# Exclude temp file from common root calculation (it's just a source, not part of the project)
if self.temp_blend_path:
temp_path_resolved = self.temp_blend_path.resolve()
self.all_filepaths = [p for p in self.all_filepaths if p.resolve() != temp_path_resolved]
self.all_filepaths = [p for p in self.all_filepaths if p != temp_path_resolved]
print(f"[SheepIt Pack] Excluded temp file from common root calculation")
# Use resolved paths only so common_root is deterministic (no A:\ vs UNC mix)
self.all_filepaths = [Path(p).resolve() for p in self.all_filepaths]
print(f"[SheepIt Pack] Collected {len(self.all_filepaths)} total file paths")
self.phase = 'FIND_COMMON_ROOT'
return ('FIND_COMMON_ROOT', False)
@@ -1223,14 +1247,15 @@ class IncrementalPacker:
print(f"[SheepIt Pack] Computed relative path: {current_relpath}")
target_path_file = self.target_path / current_relpath
if current_blend_abspath not in self.copied_paths:
current_blend_resolved = current_blend_abspath.resolve()
if current_blend_resolved not in self.copied_paths:
print(f"[SheepIt Pack] Copying: {current_blend_abspath} -> {target_path_file}")
try:
target_path_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(current_blend_abspath, target_path_file)
self.copied_paths.add(current_blend_abspath)
self.copied_paths.add(current_blend_resolved)
if current_blend_abspath.suffix.lower() == ".blend":
self.copy_map[str(current_blend_abspath.resolve())] = str(target_path_file.resolve())
self.copy_map[_norm_copy_map_key(current_blend_resolved)] = str(target_path_file.resolve())
self.top_level_target_blend = target_path_file.resolve()
print(f"[SheepIt Pack] Copied successfully, size: {target_path_file.stat().st_size} bytes")
# Copy caches - use original blend path for cache lookup if temp file
@@ -1257,13 +1282,14 @@ class IncrementalPacker:
print(f"[SheepIt Pack] ERROR copying top-level blend: {type(e).__name__}: {str(e)}")
self.missing_on_copy.append(current_blend_abspath)
# Prepare asset copy list
# Prepare asset copy list (dedupe by resolved path so each file copied once)
total_assets = sum(len(links) for links in self.asset_usages.values())
print(f"[SheepIt Pack] Preparing to copy {total_assets} asset files...")
seen_resolved = set()
for lib, links_to in self.asset_usages.items():
for asset_usage in links_to:
if asset_usage.abspath in self.copied_paths:
resolved = asset_usage.abspath.resolve()
if resolved in self.copied_paths or resolved in seen_resolved:
continue
# Skip cache directories: already copied in copy_blend_caches from blend dir;
# including them here would try UNC path and fail with PermissionError.
@@ -1272,15 +1298,11 @@ class IncrementalPacker:
len(asset_usage.abspath.parts) >= 2 and asset_usage.abspath.parts[-2] == "bakes"
):
continue
seen_resolved.add(resolved)
try:
# Try to get relative path to common root
asset_relpath = asset_usage.abspath.relative_to(self.common_root)
# Use relative path as-is (even if it's just a filename)
# This preserves the original relative structure
asset_relpath = resolved.relative_to(self.common_root)
except ValueError:
# Paths are not relative to common root (different drive/UNC),
# use compute_target_relpath to create DRIVE_C/UNC structure
asset_relpath = compute_target_relpath(asset_usage.abspath, self.common_root)
asset_relpath = compute_target_relpath(resolved, self.common_root)
self.assets_to_copy.append((asset_usage, asset_relpath))
self.assets_copied = 0
@@ -1294,7 +1316,9 @@ class IncrementalPacker:
for i in range(self.assets_copied, batch_end):
asset_usage, asset_relpath = self.assets_to_copy[i]
resolved = asset_usage.abspath.resolve()
if resolved in self.copied_paths:
continue
if not asset_usage.abspath.exists():
print(f"[SheepIt Pack] WARNING: Asset does not exist: {asset_usage.abspath}")
self.missing_on_copy.append(asset_usage.abspath)
@@ -1305,10 +1329,10 @@ class IncrementalPacker:
target_asset_path.parent.mkdir(parents=True, exist_ok=True)
file_size = asset_usage.abspath.stat().st_size
shutil.copy2(asset_usage.abspath, target_asset_path)
self.copied_paths.add(asset_usage.abspath)
self.copied_paths.add(resolved)
# Add to copy_map for remapping (blend files and image/texture files)
if asset_usage.abspath.suffix.lower() in (".blend", ".png", ".jpg", ".jpeg", ".tga", ".tiff", ".exr", ".hdr", ".bmp", ".dds", ".mp4", ".avi", ".mov", ".usd", ".usdc", ".usda"):
self.copy_map[str(asset_usage.abspath.resolve())] = str(target_asset_path.resolve())
self.copy_map[_norm_copy_map_key(resolved)] = str(target_asset_path.resolve())
if (i < 5) or (i % 50 == 0):
print(f"[SheepIt Pack] Copied: {asset_usage.abspath.name} ({file_size} bytes)")
except Exception as e:
@@ -1660,15 +1684,16 @@ def pack_project(workflow: str, target_path: Optional[Path] = None, enable_nla:
print(f"[SheepIt Pack] Computed relative path: {current_relpath}")
top_level_target_blend = None
if current_blend_abspath not in copied_paths:
current_blend_resolved = current_blend_abspath.resolve()
if current_blend_resolved not in copied_paths:
target_path_file = target_path / current_relpath
print(f"[SheepIt Pack] Copying: {current_blend_abspath} -> {target_path_file}")
try:
target_path_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(current_blend_abspath, target_path_file)
copied_paths.add(current_blend_abspath)
copied_paths.add(current_blend_resolved)
if current_blend_abspath.suffix.lower() == ".blend":
copy_map[str(current_blend_abspath.resolve())] = str(target_path_file.resolve())
copy_map[_norm_copy_map_key(current_blend_resolved)] = str(target_path_file.resolve())
top_level_target_blend = target_path_file.resolve()
print(f"[SheepIt Pack] Copied successfully, size: {target_path_file.stat().st_size} bytes")
# Copy caches
@@ -1686,11 +1711,13 @@ def pack_project(workflow: str, target_path: Optional[Path] = None, enable_nla:
total_assets = sum(len(links) for links in asset_usages.values())
print(f"[SheepIt Pack] Copying {total_assets} asset files...")
asset_count = 0
seen_resolved = set()
for lib, links_to in asset_usages.items():
for asset_usage in links_to:
if asset_usage.abspath in copied_paths:
resolved = asset_usage.abspath.resolve()
if resolved in copied_paths or resolved in seen_resolved:
continue
seen_resolved.add(resolved)
asset_count += 1
# Update progress every 10 files or every 1% of total
if asset_count % 10 == 0 or (total_assets > 0 and asset_count % max(1, total_assets // 100) == 0):
@@ -1702,9 +1729,9 @@ def pack_project(workflow: str, target_path: Optional[Path] = None, enable_nla:
raise InterruptedError("Packing cancelled by user")
try:
asset_relpath = asset_usage.abspath.relative_to(common_root)
asset_relpath = resolved.relative_to(common_root)
except ValueError:
asset_relpath = compute_target_relpath(asset_usage.abspath, common_root)
asset_relpath = compute_target_relpath(resolved, common_root)
if not asset_usage.abspath.exists():
print(f"[SheepIt Pack] WARNING: Asset does not exist: {asset_usage.abspath}")
@@ -1716,10 +1743,10 @@ def pack_project(workflow: str, target_path: Optional[Path] = None, enable_nla:
target_asset_path.parent.mkdir(parents=True, exist_ok=True)
file_size = asset_usage.abspath.stat().st_size
shutil.copy2(asset_usage.abspath, target_asset_path)
copied_paths.add(asset_usage.abspath)
copied_paths.add(resolved)
# Add to copy_map for remapping (blend files and image/texture files)
if asset_usage.abspath.suffix.lower() in (".blend", ".png", ".jpg", ".jpeg", ".tga", ".tiff", ".exr", ".hdr", ".bmp", ".dds", ".mp4", ".avi", ".mov", ".usd", ".usdc", ".usda"):
copy_map[str(asset_usage.abspath.resolve())] = str(target_asset_path.resolve())
copy_map[_norm_copy_map_key(resolved)] = str(target_asset_path.resolve())
if asset_count <= 5 or asset_count % 50 == 0: # Log first 5 and every 50th
print(f"[SheepIt Pack] Copied: {asset_usage.abspath.name} ({file_size} bytes)")
except Exception as e:
@@ -2367,7 +2394,7 @@ class SHEEPIT_OT_pack_zip(Operator):
time.sleep(0.2)
self._cleanup(context, cancelled=False)
self.report({'INFO'}, f"ZIP file saved to: {self._output_path}")
self.report({'INFO'}, self._message if self._message else f"File saved to: {self._output_path}")
return {'FINISHED'}
except Exception as e:
@@ -2766,7 +2793,7 @@ class SHEEPIT_OT_pack_blend(Operator):
time.sleep(0.2)
self._cleanup(context, cancelled=False)
self.report({'INFO'}, f"ZIP file saved to: {self._output_path}")
self.report({'INFO'}, self._message if self._message else f"File saved to: {self._output_path}")
return {'FINISHED'}
except Exception as e:
File diff suppressed because one or more lines are too long