Last bit of merge fixes hopefully

This commit is contained in:
2025-09-29 10:40:45 -05:00
parent b7a2ca25b0
commit e4e6fd9ebf
17 changed files with 145 additions and 301 deletions
+1
View File
@@ -6,6 +6,7 @@
#include "util/algorithm.h"
#include "util/foreach.h"
#include "util/set.h"
#include <chrono>
CCL_NAMESPACE_BEGIN
+18 -44
View File
@@ -1116,12 +1116,7 @@ def _extensions_enabled_from_repo_directory_and_pkg_id_sequence(repo_directory_a
return extensions_enabled_pending
def _extensions_repo_sync_wheels(
repo_cache_store, # `bl_extension_utils.RepoCacheStore`
extensions_enabled, # `set[tuple[str, str]]`
*,
error_fn, # `Callable[[Exception], None]`
): # `-> None`
def _extensions_repo_sync_wheels(repo_cache_store, extensions_enabled):
"""
This function collects all wheels from all packages and ensures the packages are either extracted or removed
when they are no longer used.
@@ -1133,7 +1128,7 @@ def _extensions_repo_sync_wheels(
wheel_list = []
for repo_index, pkg_manifest_local in enumerate(repo_cache_store.pkg_manifest_from_local_ensure(
error_fn=error_fn,
error_fn=print,
ignore_missing=True,
)):
repo = repos_all[repo_index]
@@ -1165,21 +1160,10 @@ def _extensions_repo_sync_wheels(
)
def _extensions_repo_refresh_on_change(
repo_cache_store, # `bl_extension_utils.RepoCacheStore`
*,
extensions_enabled, # `set[tuple[str, str]] | None`
compat_calc, # `bool`
stats_calc, # `bool`
error_fn, # `Callable[[Exception], None]`
): # `-> None`
def _extensions_repo_refresh_on_change(repo_cache_store, *, extensions_enabled, compat_calc, stats_calc):
import addon_utils
if extensions_enabled is not None:
_extensions_repo_sync_wheels(
repo_cache_store,
extensions_enabled,
error_fn=error_fn,
)
_extensions_repo_sync_wheels(repo_cache_store, extensions_enabled)
# Wheel sync handled above.
if compat_calc:
@@ -1789,10 +1773,7 @@ class EXTENSIONS_OT_repo_refresh_all(Operator):
# In-line `bpy.ops.preferences.addon_refresh`.
addon_utils.modules_refresh()
# Ensure compatibility info and wheels is up to date.
addon_utils.extensions_refresh(
ensure_wheels=True,
handle_error=lambda ex: self.report({'ERROR'}, str(ex)),
)
addon_utils.extensions_refresh(ensure_wheels=True)
_preferences_ui_redraw()
_preferences_ui_refresh_addons()
@@ -2259,10 +2240,6 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
del self.repo_lock
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
# Refresh installed packages for repositories that were operated on.
repo_cache_store = repo_cache_store_ensure()
for directory in self._repo_directories:
@@ -2286,9 +2263,12 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled,
compat_calc=True,
stats_calc=True,
error_fn=handle_error,
)
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
for directory, pkg_id_sequence in self._repo_map_packages_addon_only:
pkg_manifest_local = repo_cache_store.refresh_local_from_directory(
@@ -2317,7 +2297,6 @@ class EXTENSIONS_OT_package_install_marked(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled_test,
compat_calc=False,
stats_calc=False,
error_fn=handle_error,
)
_preferences_ui_redraw()
@@ -2441,7 +2420,6 @@ class EXTENSIONS_OT_package_uninstall_marked(Operator, _ExtCmdMixIn):
extensions_enabled=_extensions_enabled(),
compat_calc=True,
stats_calc=True,
error_fn=handle_error,
)
_preferences_theme_state_restore(self._theme_restore)
@@ -2644,10 +2622,6 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
del self.repo_lock
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
pkg_manifest_local = repo_cache_store.refresh_local_from_directory(
directory=self.repo_directory,
error_fn=self.error_fn_from_exception,
@@ -2668,9 +2642,13 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled,
compat_calc=True,
stats_calc=True,
error_fn=handle_error,
)
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
_preferences_ensure_enabled_all(
addon_restore=self._addon_restore,
handle_error=handle_error,
@@ -2700,7 +2678,6 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled_test,
compat_calc=False,
stats_calc=False,
error_fn=handle_error,
)
_extensions_repo_temp_files_make_stale(self.repo_directory)
@@ -3017,10 +2994,6 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
def exec_command_finish(self, canceled):
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
# Unlock repositories.
lock_result_any_failed_with_report(self, self.repo_lock.release(), report_type='WARNING')
del self.repo_lock
@@ -3048,9 +3021,12 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled,
compat_calc=True,
stats_calc=True,
error_fn=handle_error,
)
# TODO: it would be nice to include this message in the banner.
def handle_error(ex):
self.report({'ERROR'}, str(ex))
_preferences_ensure_enabled_all(
addon_restore=self._addon_restore,
handle_error=handle_error,
@@ -3080,7 +3056,6 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
extensions_enabled=extensions_enabled_test,
compat_calc=False,
stats_calc=False,
error_fn=handle_error,
)
_extensions_repo_temp_files_make_stale(self.repo_directory)
@@ -3519,7 +3494,6 @@ class EXTENSIONS_OT_package_uninstall(Operator, _ExtCmdMixIn):
extensions_enabled=_extensions_enabled(),
compat_calc=True,
stats_calc=True,
error_fn=handle_error,
)
_preferences_theme_state_restore(self._theme_restore)
@@ -222,6 +222,16 @@ def blender_ext_cmd(python_args: Sequence[str]) -> Sequence[str]:
# Call JSON.
#
def non_blocking_call(cmd: Sequence[str]) -> subprocess.Popen[bytes]:
# pylint: disable-next=consider-using-with
ps = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout = ps.stdout
assert stdout is not None
# Needed so whatever is available can be read (without waiting).
file_handle_make_non_blocking(stdout)
return ps
def command_output_from_json_0(
args: Sequence[str],
use_idle: bool,
@@ -235,76 +245,57 @@ def command_output_from_json_0(
chunk_list = []
request_exit_signal_sent = False
# WIN32 needs to use a separate process-group else Blender will recieve the "break", see #131947.
creationflags = 0
if sys.platform == "win32":
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
while True:
# It's possible this is multiple chunks.
try:
chunk = stdout.read()
except Exception as ex:
if not file_handle_non_blocking_is_error_blocking(ex):
raise ex
chunk = b''
with subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=creationflags) as ps:
stdout = ps.stdout
assert stdout is not None
json_messages = []
# Needed so whatever is available can be read (without waiting).
file_handle_make_non_blocking(stdout)
if not chunk:
if ps.poll() is not None:
break
if use_idle:
time.sleep(IDLE_WAIT_ON_READ)
elif (chunk_zero_index := chunk.find(b'\0')) == -1:
chunk_list.append(chunk)
else:
# Extract contiguous data from `chunk_list`.
chunk_list.append(chunk[:chunk_zero_index])
chunk_list = []
request_exit_signal_sent = False
json_bytes_list = [b''.join(chunk_list)]
chunk_list.clear()
while True:
# It's possible this is multiple chunks.
try:
chunk = stdout.read()
except Exception as ex:
if not file_handle_non_blocking_is_error_blocking(ex):
raise ex
chunk = b''
json_messages = []
if not chunk:
if ps.poll() is not None:
break
if use_idle:
time.sleep(IDLE_WAIT_ON_READ)
elif (chunk_zero_index := chunk.find(b'\0')) == -1:
chunk_list.append(chunk)
else:
# Extract contiguous data from `chunk_list`.
chunk_list.append(chunk[:chunk_zero_index])
json_bytes_list = [b''.join(chunk_list)]
chunk_list.clear()
# There may be data afterwards, even whole chunks.
if chunk_zero_index + 1 != len(chunk):
# There may be data afterwards, even whole chunks.
if chunk_zero_index + 1 != len(chunk):
chunk = chunk[chunk_zero_index + 1:]
# Add whole chunks.
while (chunk_zero_index := chunk.find(b'\0')) != -1:
json_bytes_list.append(chunk[:chunk_zero_index])
chunk = chunk[chunk_zero_index + 1:]
# Add whole chunks.
while (chunk_zero_index := chunk.find(b'\0')) != -1:
json_bytes_list.append(chunk[:chunk_zero_index])
chunk = chunk[chunk_zero_index + 1:]
if chunk:
chunk_list.append(chunk)
if chunk:
chunk_list.append(chunk)
request_exit = False
request_exit = False
for json_bytes in json_bytes_list:
json_data = json.loads(json_bytes.decode("utf-8"))
for json_bytes in json_bytes_list:
json_data = json.loads(json_bytes.decode("utf-8"))
assert len(json_data) == 2
assert isinstance(json_data[0], str)
assert len(json_data) == 2
assert isinstance(json_data[0], str)
json_messages.append((json_data[0], json_data[1]))
json_messages.append((json_data[0], json_data[1]))
# Yield even when `json_messages`, otherwise this generator can block.
# It also means a request to exit might not be responded to soon enough.
request_exit = yield json_messages
if request_exit and not request_exit_signal_sent:
if sys.platform == "win32":
# Caught by the `signal.SIGBREAK` signal handler.
ps.send_signal(signal.CTRL_BREAK_EVENT)
else:
ps.send_signal(signal.SIGINT)
request_exit_signal_sent = True
# Yield even when `json_messages`, otherwise this generator can block.
# It also means a request to exit might not be responded to soon enough.
request_exit = yield json_messages
if request_exit and not request_exit_signal_sent:
ps.send_signal(signal.SIGINT)
request_exit_signal_sent = True
# -----------------------------------------------------------------------------
+32 -59
View File
@@ -1290,26 +1290,6 @@ class PathPatternMatch:
# -----------------------------------------------------------------------------
# URL Downloading
# NOTE:
# - Using return arguments isn't ideal but is better than including
# a static value in the iterator.
# - Other data could be added here as needed (response headers if the caller needs them).
class DataRetrieveInfo:
"""
When accessing a file from a URL or from the file-system,
this is a "return" argument so the caller can know the size of the chunks it's iterating over,
or -1 when the size is not known.
"""
__slots__ = (
"size_hint",
)
size_hint: int
def __init__(self) -> None:
self.size_hint = -1
# Originally based on `urllib.request.urlretrieve`.
def url_retrieve_to_data_iter(
url: str,
@@ -1320,13 +1300,19 @@ def url_retrieve_to_data_iter(
timeout_in_seconds: float,
) -> Generator[tuple[bytes, int, Any], None, None]:
"""
Iterate over byte data downloaded from a from a URL
limited to ``chunk_size``.
Retrieve a URL into a temporary location on disk.
- The ``retrieve_info.size_hint``
will be set once the iterator starts and can be used for progress display.
- The iterator will start with an empty block, so the size can be known
before time is spent downloading data.
Requires a URL argument. If a filename is passed, it is used as
the temporary file location. The reporthook argument should be
a callable that accepts a block number, a read size, and the
total file size of the URL target. The data argument should be
valid URL encoded data.
If a filename is passed and the URL points to a local resource,
the result is a copy from local file to new file.
Returns a tuple containing the path to the newly created
data file as well as the resulting HTTPMessage object.
"""
from urllib.error import ContentTooShortError
from urllib.request import urlopen
@@ -1348,10 +1334,7 @@ def url_retrieve_to_data_iter(
if "content-length" in response_headers:
size = int(response_headers["Content-Length"])
retrieve_info.size_hint = size
# Yield an empty block so progress display may start.
yield b""
yield (b'', size, response_headers)
if timeout_in_seconds <= 0.0:
while True:
@@ -1359,14 +1342,14 @@ def url_retrieve_to_data_iter(
if not block:
break
read += len(block)
yield block
yield (block, size, response_headers)
else:
while True:
block = read_with_timeout(fp, chunk_size, timeout_in_seconds=timeout_in_seconds)
if not block:
break
read += len(block)
yield block
yield (block, size, response_headers)
if size >= 0 and read < size:
raise ContentTooShortError(
@@ -1375,7 +1358,6 @@ def url_retrieve_to_data_iter(
)
# See `url_retrieve_to_data_iter` doc-string.
def url_retrieve_to_filepath_iter(
url: str,
filepath: str,
@@ -1387,19 +1369,17 @@ def url_retrieve_to_filepath_iter(
) -> Generator[tuple[int, int, Any], None, None]:
# Handle temporary file setup.
with open(filepath, 'wb') as fh_output:
for block in url_retrieve_to_data_iter(
for block, size, response_headers in url_retrieve_to_data_iter(
url,
headers=headers,
data=data,
chunk_size=chunk_size,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=retrieve_info,
):
fh_output.write(block)
yield len(block)
yield (len(block), size, response_headers)
# See `url_retrieve_to_data_iter` doc-string.
def filepath_retrieve_to_filepath_iter(
filepath_src: str,
filepath: str,
@@ -1411,12 +1391,11 @@ def filepath_retrieve_to_filepath_iter(
# Handle temporary file setup.
_ = timeout_in_seconds
with open(filepath_src, 'rb') as fh_input:
retrieve_info.size_hint = os.fstat(fh_input.fileno()).st_size
yield 0
size = os.fstat(fh_input.fileno()).st_size
with open(filepath, 'wb') as fh_output:
while (block := fh_input.read(chunk_size)):
fh_output.write(block)
yield len(block)
yield (len(block), size)
def url_retrieve_to_data_iter_or_filesystem(
@@ -1424,25 +1403,25 @@ def url_retrieve_to_data_iter_or_filesystem(
headers: dict[str, str],
chunk_size: int,
timeout_in_seconds: float,
retrieve_info: DataRetrieveInfo,
) -> Iterator[bytes]:
) -> Generator[bytes, None, None]:
if url_is_filesystem(url):
with open(path_from_url(url), "rb") as fh_source:
retrieve_info.size_hint = os.fstat(fh_source.fileno()).st_size
yield b""
while (block := fh_source.read(chunk_size)):
yield block
else:
yield from url_retrieve_to_data_iter(
for (
block,
_size,
_response_headers,
) in url_retrieve_to_data_iter(
url,
headers=headers,
chunk_size=chunk_size,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=retrieve_info,
)
):
yield block
# See `url_retrieve_to_data_iter` doc-string.
def url_retrieve_to_filepath_iter_or_filesystem(
url: str,
filepath: str,
@@ -1460,17 +1439,16 @@ def url_retrieve_to_filepath_iter_or_filesystem(
filepath,
chunk_size=chunk_size,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=retrieve_info,
)
else:
yield from url_retrieve_to_filepath_iter(
for (read, size, _response_headers) in url_retrieve_to_filepath_iter(
url,
filepath,
headers=headers,
chunk_size=chunk_size,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=retrieve_info,
)
):
yield (read, size)
def url_retrieve_exception_is_connectivity(
@@ -2987,9 +2965,8 @@ def repo_sync_from_remote(
return False
try:
retrieve_info = DataRetrieveInfo()
read_total = 0
for read in url_retrieve_to_filepath_iter_or_filesystem(
for (read, size) in url_retrieve_to_filepath_iter_or_filesystem(
remote_json_url,
local_json_path_temp,
headers=url_request_headers_create(
@@ -2999,14 +2976,12 @@ def repo_sync_from_remote(
),
chunk_size=CHUNK_SIZE_DEFAULT,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=retrieve_info,
):
request_exit |= msglog.progress("Downloading...", read_total, retrieve_info.size_hint, 'BYTE')
request_exit |= msglog.progress("Downloading...", read_total, size, 'BYTE')
if request_exit:
break
read_total += read
del read_total
del retrieve_info
except (Exception, KeyboardInterrupt) as ex:
msg = url_retrieve_exception_as_message(ex, prefix="sync", url=remote_url)
if demote_connection_errors_to_status and url_retrieve_exception_is_connectivity(ex):
@@ -3913,7 +3888,6 @@ class subcmd_client:
),
chunk_size=CHUNK_SIZE_DEFAULT,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=DataRetrieveInfo(), # Unused.
):
result.write(block)
@@ -4365,7 +4339,6 @@ class subcmd_client:
),
chunk_size=CHUNK_SIZE_DEFAULT,
timeout_in_seconds=timeout_in_seconds,
retrieve_info=DataRetrieveInfo(), # Unused.
):
request_exit |= msglog.progress(
"Downloading \"{:s}\"".format(pkg_idname),
@@ -1233,15 +1233,13 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
num_loops = len(me.loops)
t_ln = np.empty(num_loops * 3, dtype=normal_bl_dtype)
# t_lnw = np.zeros(len(me.loops), dtype=np.float64)
# WARNING: Since tangent layers are recomputed inside the loop, do not directly iterate over the
# uvlayers. Instead, cache their keys (names), and use this cached data inside the loop to compute
# the tangent layers.
uvlayer_names = [uvl.name for uvl in me.uv_layers]
for idx, name in enumerate(uvlayer_names):
# Annoying, `me.calc_tangent` errors in case there is no geometry...
if num_loops > 0:
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
# Annoying, `me.calc_tangent` errors in case there is no geometry...
if num_loops > 0:
for name in uv_names:
me.calc_tangents(uvmap=name)
for idx, uvlayer in enumerate(me.uv_layers):
name = uvlayer.name
# Loop bitangents (aka binormals).
# NOTE: this is not supported by importer currently.
me.loops.foreach_get("bitangent", t_ln)
@@ -144,7 +144,7 @@ def get_channel_groups(obj_uuid: str, blender_action: bpy.types.Action, export_s
target_property = fcurve.data_path
target_data = targets_extra.get(target, {})
target_data['type'] = type_
target_data['bone'] = target.name if type_ == "BONE" else None
target_data['bone'] = target.name
target_data['obj_uuid'] = obj_uuid
target_properties = target_data.get('properties', {})
channels = target_properties.get(target_property, [])
@@ -44,9 +44,6 @@ class BlenderAnimation():
BlenderPointerAnim.anim(gltf, anim_idx, light, light_idx, 'LIGHT')
for mat_idx, mat in enumerate(gltf.data.materials if gltf.data.materials else []):
if len(mat.blender_material) == 0:
# The animated material is not used in Blender, so do not animate it
continue
if len(mat.animations) != 0:
BlenderPointerAnim.anim(gltf, anim_idx, mat, mat_idx, 'MATERIAL')
if mat.normal_texture is not None and len(mat.normal_texture.animations) != 0:
@@ -160,8 +157,5 @@ class BlenderAnimation():
restore_animation_on_object(light['blender_object_data'], animation_name)
for mat in gltf.data.materials if gltf.data.materials else []:
if len(mat.blender_material) == 0:
# The animated material is not used in Blender, so do not animate it
continue
restore_animation_on_object(mat.blender_nodetree, animation_name)
restore_animation_on_object(mat.blender_mat, animation_name)
@@ -203,15 +203,6 @@ class PoseActionCreator:
"""Resolve an RNA path + array index to an actual value."""
value_or_array = cls._path_resolve(datablock, data_path)
if isinstance(value_or_array, str):
# Enums resolve to a string.
bone_path, enum_property_name = data_path.rsplit("[", 1)
unescaped_property_name = bpy.utils.unescape_identifier(enum_property_name)
# unescaped_property_name still has the quotes and a bracket at the end hence the [1:-2].
value = cls._path_resolve(datablock, bone_path).get(unescaped_property_name[1:-2])
assert isinstance(value, (int, float))
return cast(FCurveValue, value)
# Both indices -1 and 0 are used for non-array properties.
# -1 cannot be used in arrays, whereas 0 can be used in both arrays and non-arrays.
@@ -241,12 +241,8 @@ class VIEW3D_PT_vr_info(bpy.types.Panel):
return not bpy.app.build_options.xr_openxr
def draw(self, context):
import platform
layout = self.layout
missing_support_string = "Built without VR/OpenXR features"
if platform.system() == "Darwin":
missing_support_string = "VR is not supported on macOS at the moment"
layout.label(icon='ERROR', text=missing_support_string)
layout.label(icon='ERROR', text="Built without VR/OpenXR features")
classes = (
@@ -13,7 +13,6 @@ __all__ = (
"apply_action",
)
import contextlib
import os
import re
import shutil
@@ -180,9 +179,6 @@ def _zipfile_extractall_safe(
zip_fh: zipfile.ZipFile,
path: str,
path_restrict: str,
*,
error_fn: Callable[[Exception], None],
remove_error_fn: Callable[[str, Exception], None],
) -> None:
"""
A version of ``ZipFile.extractall`` that wont write to paths outside ``path_restrict``.
@@ -207,69 +203,20 @@ def _zipfile_extractall_safe(
path_restrict_with_slash = path_restrict + sep
assert len(path) >= len(path_restrict_with_slash)
if not path.startswith(path_restrict_with_slash):
# This is an internal error if it ever happens.
raise Exception("Expected the restricted directory to start with \"{:s}\"".format(path_restrict_with_slash))
has_error = False
member_index = 0
# Use an iterator to avoid duplicating the checks (for the cleanup pass).
def zip_iter_filtered(*, verbose: bool) -> Iterator[tuple[zipfile.ZipInfo, str, str]]:
for member in zip_fh.infolist():
filename_orig = member.filename
filename_next = path_prefix + filename_orig
# This isn't likely to happen so accept a noisy print here.
# If this ends up happening more often, it could be suppressed.
# (although this hints at bigger problems because we might be excluding necessary files).
if os.path.normpath(filename_next).startswith(".." + sep):
if verbose:
print("Skipping path:", filename_next, "that escapes:", path_restrict)
continue
yield member, filename_orig, filename_next
for member, filename_orig, filename_next in zip_iter_filtered(verbose=True):
# Increment before extracting, so a potential cleanup will a file that failed to extract.
member_index += 1
member.filename = filename_next
# Extraction can fail for many reasons, see: #132924.
try:
zip_fh.extract(member, path_restrict)
except Exception as ex:
error_fn(ex)
filepath_native = path_restrict + sep + filename_next.replace("/", sep)
print("Failed to extract path:", filepath_native, "error", str(ex))
remove_error_fn(filepath_native, ex)
has_error = True
raise Exception("Expected the restricted directory to start with ")
for member in zip_fh.infolist():
filename_orig = member.filename
member.filename = path_prefix + filename_orig
# This isn't likely to happen so accept a noisy print here.
# If this ends up happening more often, it could be suppressed.
# (although this hints at bigger problems because we might be excluding necessary files).
if os.path.normpath(member.filename).startswith(".." + sep):
print("Skipping path:", member.filename, "that escapes:", path_restrict)
continue
zip_fh.extract(member, path_restrict)
member.filename = filename_orig
if has_error:
break
# If the zip-file failed to extract, remove all files that were extracted.
# This is done so failure to extract a file never results in a partially-working
# state which can cause confusing situations for users.
if has_error:
# NOTE: this currently leaves empty directories which is not ideal.
# It's possible to calculate directories created by this extraction but more involved.
member_cleanup_len = member_index + 1
member_index = 0
for member, filename_orig, filename_next in zip_iter_filtered(verbose=False):
member_index += 1
if member_index >= member_cleanup_len:
break
filepath_native = path_restrict + sep + filename_next.replace("/", sep)
try:
os.unlink(filepath_native)
except Exception as ex:
remove_error_fn(filepath_native, ex)
WHEEL_VERSION_RE = re.compile(r"(\d+)?(?:\.(\d+))?(?:\.(\d+))")
@@ -472,20 +419,5 @@ def apply_action(
filepath = wheels_dir_info_to_filepath_map[dir_info]
# `ZipFile.extractall` is needed because some wheels contain paths that point to parent directories.
# Handle this *safely* by allowing extracting to parent directories but limit this to the `local_dir`.
try:
# pylint: disable-next=consider-using-with
zip_fh_context = zipfile.ZipFile(filepath, mode="r")
except Exception as ex:
print("Error ({:s}) opening zip-file: {:s}".format(str(ex), filepath))
error_fn(ex)
continue
with contextlib.closing(zip_fh_context) as zip_fh:
_zipfile_extractall_safe(
zip_fh,
local_dir_site_packages,
local_dir,
error_fn=error_fn,
remove_error_fn=remove_error_fn,
)
with zipfile.ZipFile(filepath, mode="r") as zip_fh:
_zipfile_extractall_safe(zip_fh, local_dir_site_packages, local_dir)
-4
View File
@@ -1212,10 +1212,6 @@ def _initialize_extensions_compat_data(
if ensure_wheels:
if updated:
if error_fn is None:
def error_fn(ex):
print("Error:", str(ex))
try:
_initialize_extensions_compat_ensure_up_to_date_wheels(extensions_directory, wheel_list, debug)
except Exception:
@@ -4848,6 +4848,7 @@ def km_weight_paint(params):
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
{"properties": [("data_path", "tool_settings.weight_paint.brush.use_smooth_stroke")]}),
op_menu_pie("VIEW3D_MT_wpaint_vgroup_lock_pie", {"type": 'K', "value": 'PRESS'}),
("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None),
*_template_items_context_panel("VIEW3D_PT_paint_weight_context_menu", params.context_menu_event),
op_asset_shelf_popup(
"VIEW3D_AST_brush_weight_paint",
+6 -16
View File
@@ -7,25 +7,15 @@
import sys
import os
exe_dir, exe_file = os.path.split(sys.executable)
is_python = exe_file.startswith("python")
# Path to Blender shared libraries.
shared_lib_dirname = "blender.shared" if sys.platform == "win32" else "lib"
if os.path.basename(__file__) == "bpy_site_customize.py":
# Blender as Python Module.
is_python = True
shared_lib_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
# On Windows no subdirectory is used.
if sys.platform != "win32":
shared_lib_dir = os.path.join(shared_lib_dir, shared_lib_dirname)
if is_python:
shared_lib_dir = os.path.abspath(os.path.join(exe_dir, "..", "..", "..", shared_lib_dirname))
else:
exe_dir, exe_file = os.path.split(sys.executable)
is_python = exe_file.startswith("python")
if is_python:
# Python executable bundled with Blender.
shared_lib_dir = os.path.abspath(os.path.join(exe_dir, "..", "..", "..", shared_lib_dirname))
else:
# Blender executable.
shared_lib_dir = os.path.abspath(os.path.join(exe_dir, shared_lib_dirname))
shared_lib_dir = os.path.abspath(os.path.join(exe_dir, shared_lib_dirname))
if sys.platform == "win32":
# Directory for extensions to find DLLs.
+1
View File
@@ -6900,6 +6900,7 @@ class VIEW3D_PT_overlay_object(Panel):
sub = split.column(align=True)
sub.prop(overlay, "show_bones", text="Bones")
sub.prop(overlay, "isolate_bones", text="Isolate Bones")
sub.prop(overlay, "show_motion_paths")
sub.prop(overlay, "show_object_origins", text="Origins")
subsub = sub.column()
+1 -3
View File
@@ -1265,7 +1265,6 @@ elseif(WIN32)
install(
FILES
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python${_PYTHON_VERSION_NO_DOTS}.dll
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python3.dll
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python.exe
DESTINATION ${BLENDER_VERSION}/python/bin
CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel
@@ -1273,7 +1272,6 @@ elseif(WIN32)
install(
FILES
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python${_PYTHON_VERSION_NO_DOTS}_d.dll
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python3_d.dll
${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS}/bin/python_d.exe
DESTINATION ${BLENDER_VERSION}/python/bin
CONFIGURATIONS Debug
@@ -1770,7 +1768,7 @@ install(
# -----------------------------------------------------------------------------
# Bundle assets
set(ASSET_BUNDLE_DIR ${CMAKE_SOURCE_DIR}/assets/)
set(ASSET_BUNDLE_DIR ${CMAKE_SOURCE_DIR}/release/datafiles/assets/publish/)
if(EXISTS "${ASSET_BUNDLE_DIR}")
install(
+2 -5
View File
@@ -2484,11 +2484,8 @@ static bool handle_load_file(bContext *C, const char *filepath_arg, const bool l
/* Load the file. */
ReportList reports;
BKE_reports_init(&reports, RPT_PRINT);
/* When activating from the command line there isn't an exact equivalent to operator properties.
* Instead, enabling auto-execution via `--enable-autoexec` causes the auto-execution
* check to be skipped (if it's set), so it's fine to always enable the check here. */
const bool use_scripts_autoexec_check = true;
const bool success = WM_file_read(C, filepath, use_scripts_autoexec_check, &reports);
WM_file_autoexec_init(filepath);
const bool success = WM_file_read(C, filepath, &reports);
BKE_reports_free(&reports);
if (success) {
+11
View File
@@ -753,6 +753,17 @@ if(WITH_CYCLES OR WITH_GPU_RENDER_TESTS)
list(APPEND _gpu_render_tests_arguments --fail-silently)
endif()
# Eevee
foreach(render_test ${gpu_render_tests})
add_render_test(
eevee_${render_test}
${CMAKE_CURRENT_LIST_DIR}/eevee_render_tests.py
-testdir "${TEST_SRC_DIR}/render/${render_test}"
-outdir "${TEST_OUT_DIR}/eevee"
${_gpu_render_tests_arguments}
)
endforeach()
# Eevee Next
if(WITH_OPENGL_BACKEND)
foreach(render_test ${gpu_render_tests})