From e4e6fd9ebf15086df166e3abb38face2039852dc Mon Sep 17 00:00:00 2001 From: Cody Winchester Date: Mon, 29 Sep 2025 10:40:45 -0500 Subject: [PATCH] Last bit of merge fixes hopefully --- intern/cycles/util/profiling.cpp | 1 + .../addons_core/bl_pkg/bl_extension_ops.py | 62 +++------- .../addons_core/bl_pkg/bl_extension_utils.py | 111 ++++++++---------- scripts/addons_core/bl_pkg/cli/blender_ext.py | 91 +++++--------- .../io_scene_fbx/export_fbx_bin.py | 14 +-- .../blender/exp/animation/fcurves/channels.py | 2 +- .../io_scene_gltf2/blender/imp/animation.py | 6 - .../addons_core/pose_library/pose_creation.py | 9 -- .../addons_core/viewport_vr_preview/gui.py | 6 +- .../_bpy_internal/extensions/wheel_manager.py | 94 ++------------- scripts/modules/addon_utils.py | 4 - .../keyconfig/keymap_data/blender_default.py | 1 + scripts/site/sitecustomize.py | 22 +--- scripts/startup/bl_ui/space_view3d.py | 1 + source/creator/CMakeLists.txt | 4 +- source/creator/creator_args.cc | 7 +- tests/python/CMakeLists.txt | 11 ++ 17 files changed, 145 insertions(+), 301 deletions(-) diff --git a/intern/cycles/util/profiling.cpp b/intern/cycles/util/profiling.cpp index 54be0fcd1d1..f6b48e12cbc 100644 --- a/intern/cycles/util/profiling.cpp +++ b/intern/cycles/util/profiling.cpp @@ -6,6 +6,7 @@ #include "util/algorithm.h" #include "util/foreach.h" #include "util/set.h" +#include CCL_NAMESPACE_BEGIN diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index e40d0d7c603..89df64b907e 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -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) diff --git a/scripts/addons_core/bl_pkg/bl_extension_utils.py b/scripts/addons_core/bl_pkg/bl_extension_utils.py index 6055136d680..2696a2d5713 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_utils.py +++ b/scripts/addons_core/bl_pkg/bl_extension_utils.py @@ -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 # ----------------------------------------------------------------------------- diff --git a/scripts/addons_core/bl_pkg/cli/blender_ext.py b/scripts/addons_core/bl_pkg/cli/blender_ext.py index 8759c36fd26..c6f53e66b0c 100644 --- a/scripts/addons_core/bl_pkg/cli/blender_ext.py +++ b/scripts/addons_core/bl_pkg/cli/blender_ext.py @@ -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), diff --git a/scripts/addons_core/io_scene_fbx/export_fbx_bin.py b/scripts/addons_core/io_scene_fbx/export_fbx_bin.py index 361047ed0cd..04749b199f3 100644 --- a/scripts/addons_core/io_scene_fbx/export_fbx_bin.py +++ b/scripts/addons_core/io_scene_fbx/export_fbx_bin.py @@ -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) diff --git a/scripts/addons_core/io_scene_gltf2/blender/exp/animation/fcurves/channels.py b/scripts/addons_core/io_scene_gltf2/blender/exp/animation/fcurves/channels.py index 9a12e55edcb..1fa789efa1a 100644 --- a/scripts/addons_core/io_scene_gltf2/blender/exp/animation/fcurves/channels.py +++ b/scripts/addons_core/io_scene_gltf2/blender/exp/animation/fcurves/channels.py @@ -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, []) diff --git a/scripts/addons_core/io_scene_gltf2/blender/imp/animation.py b/scripts/addons_core/io_scene_gltf2/blender/imp/animation.py index 07681a87199..290b77b707b 100644 --- a/scripts/addons_core/io_scene_gltf2/blender/imp/animation.py +++ b/scripts/addons_core/io_scene_gltf2/blender/imp/animation.py @@ -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) diff --git a/scripts/addons_core/pose_library/pose_creation.py b/scripts/addons_core/pose_library/pose_creation.py index 2b9389a80db..c7384041bc0 100644 --- a/scripts/addons_core/pose_library/pose_creation.py +++ b/scripts/addons_core/pose_library/pose_creation.py @@ -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. diff --git a/scripts/addons_core/viewport_vr_preview/gui.py b/scripts/addons_core/viewport_vr_preview/gui.py index 0c06a41c14c..b4e35cbc62e 100644 --- a/scripts/addons_core/viewport_vr_preview/gui.py +++ b/scripts/addons_core/viewport_vr_preview/gui.py @@ -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 = ( diff --git a/scripts/modules/_bpy_internal/extensions/wheel_manager.py b/scripts/modules/_bpy_internal/extensions/wheel_manager.py index 5c0b0853c16..2115ab2fc6f 100644 --- a/scripts/modules/_bpy_internal/extensions/wheel_manager.py +++ b/scripts/modules/_bpy_internal/extensions/wheel_manager.py @@ -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) diff --git a/scripts/modules/addon_utils.py b/scripts/modules/addon_utils.py index 2e7cd7759bc..937eb4db5dc 100644 --- a/scripts/modules/addon_utils.py +++ b/scripts/modules/addon_utils.py @@ -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: diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 83ebeada103..d0bd3a682c2 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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", diff --git a/scripts/site/sitecustomize.py b/scripts/site/sitecustomize.py index 30aa6cd0ce8..e199df65133 100644 --- a/scripts/site/sitecustomize.py +++ b/scripts/site/sitecustomize.py @@ -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. diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index cde193e172e..1c6517f73d9 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -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() diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 6679d263542..355344b8587 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -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( diff --git a/source/creator/creator_args.cc b/source/creator/creator_args.cc index 89121e75aa0..48d4038e51b 100644 --- a/source/creator/creator_args.cc +++ b/source/creator/creator_args.cc @@ -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) { diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 90c315b0b9b..42fd1d9ee1e 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -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})