954 lines
26 KiB
Python
954 lines
26 KiB
Python
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import re
|
|
import os
|
|
from pathlib import Path
|
|
from typing import List, Tuple, Generator, Dict, Union, Any, Optional
|
|
|
|
import bpy
|
|
|
|
from . import cmglobals
|
|
from .logger import LoggerFactory, log_new_lines
|
|
from .models import FolderListModel
|
|
|
|
logger = LoggerFactory.getLogger(__name__)
|
|
|
|
VERSION_DIR_MODEL = FolderListModel()
|
|
|
|
_cachefiles_enum_list: List[Tuple[str, str, str]] = []
|
|
_versions_enum_list: List[Tuple[str, str, str]] = []
|
|
_version_dir_model_init: bool = False
|
|
|
|
|
|
def init_version_dir_model(
|
|
context: bpy.types.Context,
|
|
) -> None:
|
|
|
|
global VERSION_DIR_MODEL
|
|
global _version_dir_model_init
|
|
|
|
# Is None if invalid.
|
|
if not context.scene.cm.cache_version_dir_path:
|
|
logger.error(
|
|
"Failed to initialize version directory model. Invalid path. Check addon preferences."
|
|
)
|
|
return
|
|
|
|
cache_version_dir = Path(context.scene.cm.cache_version_dir_path)
|
|
|
|
VERSION_DIR_MODEL.reset()
|
|
VERSION_DIR_MODEL.root_path = cache_version_dir
|
|
|
|
if context.scene.cm.category == "EXPORT":
|
|
if not VERSION_DIR_MODEL.items:
|
|
VERSION_DIR_MODEL.append_item("v001")
|
|
|
|
_version_dir_model_init = True
|
|
|
|
|
|
def get_version(str_value: str, format: type = str) -> Union[str, int, None]:
|
|
match = re.search(cmglobals._VERSION_PATTERN, str_value)
|
|
if match:
|
|
version = match.group()
|
|
if format == str:
|
|
return version
|
|
if format == int:
|
|
return int(version.replace("v", ""))
|
|
return None
|
|
|
|
|
|
def add_version_increment() -> str:
|
|
items = VERSION_DIR_MODEL.items # should be already sorted
|
|
|
|
versions = [get_version(item) for item in items if get_version(item)]
|
|
|
|
if len(versions) > 0:
|
|
latest_version = items[0]
|
|
increment = "v{:03}".format(int(latest_version.replace("v", "")) + 1)
|
|
else:
|
|
increment = "v001"
|
|
|
|
VERSION_DIR_MODEL.append_item(increment)
|
|
return increment
|
|
|
|
|
|
def get_versions_enum_list(
|
|
self: Any,
|
|
context: bpy.types.Context,
|
|
) -> List[Tuple[str, str, str]]:
|
|
|
|
global _versions_enum_list
|
|
global VERSION_DIR_MODEL
|
|
global init_version_dir_model
|
|
|
|
# Init model if it did not happen.
|
|
if not _version_dir_model_init:
|
|
init_version_dir_model(context)
|
|
|
|
# Clear all versions in enum list.
|
|
_versions_enum_list.clear()
|
|
_versions_enum_list.extend(VERSION_DIR_MODEL.items_as_enum_list)
|
|
|
|
return _versions_enum_list
|
|
|
|
|
|
def add_version_custom(custom_version: str) -> None:
|
|
global _versions_enum_list
|
|
global VERSION_DIR_MODEL
|
|
|
|
VERSION_DIR_MODEL.append_item(custom_version)
|
|
|
|
|
|
def _get_cachefiles(cachedir_path: Path, file_ext: str = ".abc") -> List[Path]:
|
|
if file_ext == ".*":
|
|
return [Path(f) for f in cachedir_path.iterdir() if f.is_file()]
|
|
else:
|
|
return [
|
|
Path(f)
|
|
for f in cachedir_path.iterdir()
|
|
if f.is_file() and f.suffix == file_ext
|
|
]
|
|
|
|
|
|
def get_cachefiles_enum(
|
|
self: bpy.types.Operator, context: bpy.types.Context
|
|
) -> List[Tuple[str, str, str]]:
|
|
|
|
_cachefiles_enum_list.clear()
|
|
|
|
if not context.scene.cm.is_cachedir_valid:
|
|
return _cachefiles_enum_list
|
|
|
|
_cachefiles_enum_list.extend(
|
|
[
|
|
(path.as_posix(), path.name, "")
|
|
for path in _get_cachefiles(context.scene.cm.cachedir_path)
|
|
]
|
|
)
|
|
|
|
return _cachefiles_enum_list
|
|
|
|
|
|
def traverse_collection_tree(
|
|
collection: bpy.types.Collection,
|
|
) -> Generator[bpy.types.Collection, None, None]:
|
|
yield collection
|
|
for child in collection.children:
|
|
yield from traverse_collection_tree(child)
|
|
|
|
|
|
def _print_log_list(log_list: Dict[str, List[str]], header_str: str) -> None:
|
|
if log_list:
|
|
log_new_lines(1)
|
|
text = [f"{obj_name}:\n{''.join(log_list[obj_name])}" for obj_name in log_list]
|
|
logger.info("%s\n%s", header_str, "".join(text))
|
|
|
|
|
|
def _append_str_to_log_list(
|
|
log_list: Dict[str, List[str]], obj_name: str, str_value: str
|
|
) -> Dict[str, List[str]]:
|
|
log_list.setdefault(obj_name, [])
|
|
log_list[obj_name].append(f"{str_value},\n")
|
|
return log_list
|
|
|
|
|
|
def disable_vis_drivers(
|
|
objects: List[bpy.types.Object], modifiers: bool = True
|
|
) -> List[bpy.types.Driver]:
|
|
|
|
# Store driver that were muted to entmute them after.
|
|
muted_drivers: List[bpy.types.Driver] = []
|
|
|
|
# Log list.
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj in objects:
|
|
if obj.animation_data:
|
|
for driver in obj.animation_data.drivers:
|
|
|
|
# Get suffix of data path, if modifiers modifier name is at the beginning.
|
|
data_path_split = driver.data_path.split(".")
|
|
data_path_suffix = data_path_split[-1]
|
|
|
|
# If modifiers == False do not adjust drivers of which the data
|
|
# paths are starting with modifiers.
|
|
if not modifiers:
|
|
if len(data_path_split) > 1:
|
|
if data_path_split[0].startswith("modifiers"):
|
|
continue
|
|
|
|
# Only disable drivers that drive visibility data paths.
|
|
if data_path_suffix not in cmglobals.DRIVER_VIS_DATA_PATHS:
|
|
continue
|
|
|
|
# If muted already continue.
|
|
if driver.mute == True:
|
|
continue
|
|
|
|
# Mute.
|
|
driver.mute = True
|
|
muted_drivers.append(driver)
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(log_list, obj.name, driver.data_path)
|
|
# Log.
|
|
_print_log_list(log_list, "Disable visibility drivers:")
|
|
return muted_drivers
|
|
|
|
|
|
def disable_drivers_by_data_path(
|
|
objects: List[bpy.types.Object], data_path: str
|
|
) -> List[bpy.types.Driver]:
|
|
|
|
# Store driver that were muted to entmute them after.
|
|
muted_drivers: List[bpy.types.Driver] = []
|
|
|
|
# Log list.
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj in objects:
|
|
if obj.animation_data:
|
|
for driver in obj.animation_data.drivers:
|
|
|
|
if driver.data_path != data_path:
|
|
continue
|
|
|
|
# Skip if driver already muted.
|
|
if driver.mute == True:
|
|
continue
|
|
|
|
# Mute.
|
|
driver.mute = True
|
|
muted_drivers.append(driver)
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(log_list, obj.name, driver.data_path)
|
|
|
|
return muted_drivers
|
|
|
|
|
|
def sync_modifier_vis_with_render_setting(
|
|
objs: List[bpy.types.Object],
|
|
) -> List[Tuple[bpy.types.Modifier, bool, bool]]:
|
|
|
|
mods_vis_override: List[Tuple[bpy.types.Modifier, bool, bool]] = []
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj in objs:
|
|
|
|
for mod in obj.modifiers:
|
|
|
|
# Do not affect those for export.
|
|
if mod.type in cmglobals.MODIFIERS_KEEP:
|
|
continue
|
|
|
|
# If already synced continue.
|
|
if mod.show_viewport == mod.show_render:
|
|
continue
|
|
|
|
# Save cache for reconstruction later.
|
|
show_viewport_cache = mod.show_viewport
|
|
show_render_cache = mod.show_render
|
|
|
|
# Sync show_viewport with show_render setting.
|
|
mod.show_viewport = mod.show_render
|
|
mods_vis_override.append((mod, show_viewport_cache, show_render_cache))
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
obj.name,
|
|
f"{mod.name}: V: {show_viewport_cache} -> {mod.show_viewport}",
|
|
)
|
|
# Log.
|
|
_print_log_list(log_list, "Sync modifier viewport vis with render vis:")
|
|
|
|
return mods_vis_override
|
|
|
|
|
|
def apply_modifier_suffix_vis_override(
|
|
objs: List[bpy.types.Object], category: str
|
|
) -> List[Tuple[bpy.types.Modifier, bool, bool]]:
|
|
|
|
mods_vis_override: List[Tuple[bpy.types.Modifier, bool, bool]] = []
|
|
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj in objs:
|
|
|
|
for mod in list(obj.modifiers):
|
|
|
|
show_viewport_cache = mod.show_viewport
|
|
show_render_cache = mod.show_render
|
|
|
|
if category == "EXPORT":
|
|
|
|
if mod.name.endswith(cmglobals.CACHE_OFF_SUFFIX):
|
|
if mod.show_viewport == False and mod.show_render == False:
|
|
continue
|
|
mod.show_viewport = False
|
|
mod.show_render = False
|
|
|
|
elif mod.name.endswith(cmglobals.CACHE_ON_SUFFIX):
|
|
if mod.show_viewport == True and mod.show_render == True:
|
|
continue
|
|
mod.show_viewport = True
|
|
mod.show_render = True
|
|
|
|
else:
|
|
continue
|
|
|
|
if category == "IMPORT":
|
|
|
|
if mod.name.endswith(cmglobals.CACHE_OFF_SUFFIX):
|
|
if mod.show_viewport == True and mod.show_render == True:
|
|
continue
|
|
mod.show_viewport = True
|
|
mod.show_render = True
|
|
|
|
elif mod.name.endswith(cmglobals.CACHE_ON_SUFFIX):
|
|
if mod.show_viewport == False and mod.show_render == False:
|
|
continue
|
|
mod.show_viewport = False
|
|
mod.show_render = False
|
|
|
|
else:
|
|
continue
|
|
|
|
mods_vis_override.append((mod, show_viewport_cache, show_render_cache))
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
obj.name,
|
|
f"{mod.name}: V: {show_viewport_cache} -> {mod.show_viewport} R: {show_render_cache} -> {mod.show_render}",
|
|
)
|
|
# Log.
|
|
_print_log_list(log_list, "Apply modifier suffix vis override:")
|
|
|
|
return mods_vis_override
|
|
|
|
|
|
def restore_modifier_vis(
|
|
modifiers: List[Tuple[bpy.types.Modifier, bool, bool]]
|
|
) -> None:
|
|
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for mod, show_viewport, show_render in modifiers:
|
|
|
|
if mod.show_viewport == show_viewport and mod.show_render == show_render:
|
|
continue
|
|
|
|
show_viewport_cache = mod.show_viewport
|
|
show_render_cache = mod.show_render
|
|
|
|
mod.show_viewport = show_viewport
|
|
mod.show_render = show_render
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
mod.id_data.name,
|
|
f"{mod.name}: V: {show_viewport_cache} -> {mod.show_viewport} R: {show_render_cache} -> {mod.show_render}",
|
|
)
|
|
|
|
# Log.
|
|
_print_log_list(log_list, "Restore modifier visiblity:")
|
|
|
|
|
|
def config_modifiers_keep_state(
|
|
objs: List[bpy.types.Object],
|
|
enable: bool = True,
|
|
) -> List[Tuple[bpy.types.Modifier, bool, bool]]:
|
|
|
|
mods_vis_override: List[Tuple[bpy.types.Modifier, bool, bool]] = []
|
|
|
|
noun = "Enabled" if enable else "Disabled"
|
|
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj in objs:
|
|
|
|
for mod in list(obj.modifiers):
|
|
|
|
if mod.type not in cmglobals.MODIFIERS_KEEP:
|
|
continue
|
|
|
|
show_viewport_cache = mod.show_viewport
|
|
show_render_cache = mod.show_render
|
|
|
|
if enable:
|
|
if mod.show_viewport == True and mod.show_render == True:
|
|
continue
|
|
# Do not change viewport setting on enable, might create overhead
|
|
# for mods that are only needed for render.
|
|
mod.show_render = True
|
|
|
|
else:
|
|
if mod.show_viewport == False and mod.show_render == False:
|
|
continue
|
|
mod.show_viewport = False
|
|
mod.show_render = False
|
|
|
|
mods_vis_override.append((mod, show_viewport_cache, show_render_cache))
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
obj.name,
|
|
mod.name,
|
|
)
|
|
# Log.
|
|
_print_log_list(log_list, f"{noun} modifiers:")
|
|
|
|
return mods_vis_override
|
|
|
|
|
|
def set_item_vis(
|
|
items: List[Union[bpy.types.Object, bpy.types.Collection]],
|
|
show: bool,
|
|
) -> List[Tuple[Union[bpy.types.Object, bpy.types.Collection], bool, bool]]:
|
|
|
|
items_vis: List[
|
|
Tuple[Union[bpy.types.Object, bpy.types.Collection], bool, bool]
|
|
] = []
|
|
hide = not show
|
|
noun = "Hide" if hide else "Show"
|
|
|
|
for item in items:
|
|
|
|
hide_viewport_cache = item.hide_viewport
|
|
hide_render_cache = item.hide_render
|
|
|
|
if hide:
|
|
if item.hide_viewport and item.hide_render:
|
|
continue
|
|
|
|
item.hide_render = True
|
|
item.hide_viewport = True
|
|
|
|
else:
|
|
if not item.hide_viewport and not item.hide_render:
|
|
continue
|
|
|
|
item.hide_render = False
|
|
item.hide_viewport = False
|
|
|
|
items_vis.append((item, hide_viewport_cache, hide_render_cache))
|
|
|
|
if items_vis:
|
|
# Log.
|
|
logger.info(
|
|
"%s:\n%s",
|
|
noun,
|
|
",\n".join([item.name for item, hv, hr in items_vis]),
|
|
)
|
|
|
|
return items_vis
|
|
|
|
|
|
def restore_item_vis(
|
|
item_vis_list: List[
|
|
Tuple[Union[bpy.types.Object, bpy.types.Collection], bool, bool]
|
|
]
|
|
) -> None:
|
|
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for item, hide_viewport, hide_render in item_vis_list:
|
|
|
|
if item.hide_viewport == hide_viewport and item.hide_render == hide_render:
|
|
continue
|
|
|
|
hide_viewport_cache = item.hide_viewport
|
|
hide_render_cache = item.hide_render
|
|
|
|
item.hide_viewport = hide_viewport
|
|
item.hide_render = hide_render
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
item.name,
|
|
f"V: {not hide_viewport_cache} -> {not item.hide_viewport} R: {not hide_render_cache} -> {not item.hide_render}",
|
|
)
|
|
|
|
# Log.
|
|
_print_log_list(log_list, "Restore visibility:")
|
|
|
|
|
|
def get_layer_colls_from_colls(
|
|
context: bpy.types.Context, collections: List[bpy.types.Collection]
|
|
) -> List[bpy.types.LayerCollection]:
|
|
|
|
layer_colls: List[bpy.types.LayerCollection] = []
|
|
coll_name: List[str] = [coll.name for coll in collections]
|
|
|
|
for lcoll in list(traverse_collection_tree(context.view_layer.layer_collection)):
|
|
if lcoll.name in coll_name:
|
|
layer_colls.append(lcoll)
|
|
|
|
return layer_colls
|
|
|
|
|
|
def set_layer_coll_exlcude(
|
|
layer_collections: List[bpy.types.LayerCollection], exclude: bool
|
|
) -> List[Tuple[bpy.types.LayerCollection, bool]]:
|
|
|
|
layer_colls_vis: List[Tuple[bpy.types.LayerCollection, bool]] = []
|
|
|
|
noun = "Exclude" if exclude else "Include"
|
|
|
|
for lcoll in layer_collections:
|
|
|
|
exclude_cache = lcoll.exclude
|
|
|
|
if exclude:
|
|
if lcoll.exclude and lcoll.hide_render:
|
|
continue
|
|
|
|
lcoll.exclude = True
|
|
|
|
else:
|
|
if not lcoll.exclude:
|
|
continue
|
|
|
|
lcoll.exclude = False
|
|
|
|
layer_colls_vis.append((lcoll, exclude_cache))
|
|
|
|
if layer_colls_vis:
|
|
# Log.
|
|
logger.info(
|
|
"%s layer collections in current view layer:\n%s",
|
|
noun,
|
|
",\n".join([lcoll.name for lcoll, ex in layer_colls_vis]),
|
|
)
|
|
|
|
return layer_colls_vis
|
|
|
|
|
|
def restore_layer_coll_exlude(
|
|
lcoll_vis_list: List[Tuple[bpy.types.LayerCollection, bool]]
|
|
) -> None:
|
|
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for lcoll, exclude in lcoll_vis_list:
|
|
|
|
if lcoll.exclude == exclude:
|
|
continue
|
|
|
|
exclude_cache = lcoll.exclude
|
|
|
|
lcoll.exclude = exclude
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
lcoll.name,
|
|
f"exclude: {exclude_cache} -> {lcoll.exclude}",
|
|
)
|
|
|
|
# Log.
|
|
_print_log_list(log_list, "Restore layer collection visibility:")
|
|
|
|
|
|
def enable_muted_drivers(
|
|
muted_drivers: List[bpy.types.Driver],
|
|
) -> List[bpy.types.Driver]:
|
|
|
|
# Log list.
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for driver in muted_drivers:
|
|
|
|
if driver.mute == False:
|
|
continue
|
|
|
|
driver.mute = False
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(log_list, driver.id_data.name, driver.data_path)
|
|
|
|
# Log.
|
|
_print_log_list(log_list, "Enable drivers:")
|
|
|
|
return muted_drivers
|
|
|
|
|
|
def gen_abc_object_path(obj: bpy.types.Object) -> str:
|
|
# If object is duplicated (multiple copies of the same object that get different cachses)
|
|
# we have to kill the .001 postfix that gets created auto on duplication
|
|
# otherwise object path is not valid.
|
|
|
|
object_name = obj.name
|
|
object_path = "/" + object_name
|
|
|
|
if obj.data and obj.type != "LATTICE":
|
|
object_data_name = obj.data.name
|
|
object_path = "/" + object_name + "/" + object_data_name
|
|
|
|
# Dot and whitespace not valid in abc tree will be replaced with underscore.
|
|
replace = [" ", "."]
|
|
for char in replace:
|
|
object_path = object_path.replace(char, "_")
|
|
|
|
return str(object_path)
|
|
|
|
|
|
def disable_non_keep_modifiers(obj: bpy.types.Object) -> int:
|
|
modifiers = list(obj.modifiers)
|
|
a_index: int = -1
|
|
disabled_mods = []
|
|
for idx, mod in enumerate(modifiers):
|
|
if mod.type not in cmglobals.MODIFIERS_KEEP:
|
|
# Save index of first armature modifier to.
|
|
if a_index == -1 and mod.type == "ARMATURE":
|
|
a_index = idx
|
|
|
|
if not mod.show_viewport and not mod.show_render:
|
|
continue
|
|
|
|
mod.show_viewport = False
|
|
mod.show_render = False
|
|
mod.show_in_editmode = False
|
|
disabled_mods.append(mod.name)
|
|
|
|
if disabled_mods:
|
|
logger.info("%s Disabled modifiers: %s", obj.name, ", ".join(disabled_mods))
|
|
|
|
return a_index
|
|
|
|
|
|
def disable_non_keep_constraints(obj: bpy.types.Object) -> List[bpy.types.Constraint]:
|
|
constraints = list(obj.constraints)
|
|
disabled_const: List[bpy.types.Constraint] = []
|
|
|
|
for c in constraints:
|
|
if c.type in cmglobals.CONSTRAINTS_KEEP:
|
|
continue
|
|
|
|
if c.mute:
|
|
continue
|
|
|
|
c.mute = True
|
|
disabled_const.append(c)
|
|
|
|
if disabled_const:
|
|
logger.info(
|
|
"%s Disabled constaints: %s",
|
|
obj.name,
|
|
", ".join([c.name for c in disabled_const]),
|
|
)
|
|
return disabled_const
|
|
|
|
|
|
def ensure_cachefile(cachefile_path: str) -> bpy.types.CacheFile:
|
|
# Get cachefile path for this collection.
|
|
cachefile_name = Path(cachefile_path).name
|
|
|
|
# Import Alembic Cache. if its already imported reload it.
|
|
try:
|
|
bpy.data.cache_files[cachefile_name]
|
|
except KeyError:
|
|
bpy.ops.cachefile.open(filepath=cachefile_path)
|
|
logger.info("Imported cachefile: %s", cachefile_path)
|
|
else:
|
|
bpy.ops.cachefile.reload()
|
|
|
|
cachefile = bpy.data.cache_files[cachefile_name]
|
|
cachefile.scale = 1
|
|
return cachefile
|
|
|
|
|
|
def ensure_cache_modifier(obj: bpy.types.Object) -> bpy.types.MeshSequenceCacheModifier:
|
|
modifier_name = cmglobals.MODIFIER_NAME
|
|
|
|
# If modifier does not exist yet create it.
|
|
if obj.modifiers.find(modifier_name) == -1: # not found
|
|
mod = obj.modifiers.new(modifier_name, "MESH_SEQUENCE_CACHE")
|
|
logger.info(
|
|
"%s added %s modifier.",
|
|
obj.name,
|
|
modifier_name,
|
|
)
|
|
mod = obj.modifiers.get(modifier_name)
|
|
return mod
|
|
|
|
|
|
def ensure_cache_constraint(
|
|
obj: bpy.types.Object,
|
|
) -> bpy.types.TransformCacheConstraint:
|
|
constraint_name = cmglobals.CONSTRAINT_NAME
|
|
# If constraint does not exist yet create it.
|
|
if obj.constraints.find(constraint_name) == -1: # not found
|
|
con = obj.constraints.new("TRANSFORM_CACHE")
|
|
con.name = constraint_name
|
|
logger.info(
|
|
"%s added %s constraint.",
|
|
obj.name,
|
|
constraint_name,
|
|
)
|
|
con = obj.constraints.get(constraint_name)
|
|
return con
|
|
|
|
|
|
def kill_increment(str_value: str) -> str:
|
|
match = re.search(r"\.\d\d\d", str_value)
|
|
if match:
|
|
return str_value.replace(match.group(0), "")
|
|
return str_value
|
|
|
|
|
|
def config_cache_modifier(
|
|
context: bpy.types.Context,
|
|
mod: bpy.types.MeshSequenceCacheModifier,
|
|
modifier_index: int,
|
|
cachefile: bpy.types.CacheFile,
|
|
abc_obj_path: str,
|
|
) -> bpy.types.MeshSequenceCacheModifier:
|
|
obj = mod.id_data
|
|
# Move to index
|
|
# as we need to use bpy.ops for that object needs to be active.
|
|
bpy.context.view_layer.objects.active = obj
|
|
override = context.copy()
|
|
override["modifier"] = mod
|
|
bpy.ops.object.modifier_move_to_index(
|
|
override, modifier=mod.name, index=modifier_index
|
|
)
|
|
# Adjust settings.
|
|
mod.cache_file = cachefile
|
|
mod.object_path = abc_obj_path
|
|
|
|
return mod
|
|
|
|
|
|
def config_cache_constraint(
|
|
context: bpy.types.Context,
|
|
con: bpy.types.TransformCacheConstraint,
|
|
cachefile: bpy.types.CacheFile,
|
|
abc_obj_path: str,
|
|
) -> bpy.types.TransformCacheConstraint:
|
|
obj = con.id_data
|
|
|
|
# Move to index.
|
|
current_index = obj.constraints.find(con.name)
|
|
obj.constraints.move(current_index, 0)
|
|
|
|
# Adjust settings.
|
|
con.cache_file = cachefile
|
|
con.object_path = abc_obj_path
|
|
|
|
return con
|
|
|
|
|
|
def add_coll_to_cache_collections(
|
|
context: bpy.types.Context, coll: bpy.types.Collection, category: str
|
|
) -> Optional[bpy.types.Collection]:
|
|
|
|
scn = context.scene
|
|
|
|
scn_category = scn.cm.colls_export
|
|
idx = scn.cm.colls_export_index
|
|
|
|
if category == "IMPORT":
|
|
scn_category = scn.cm.colls_import
|
|
idx = scn.cm.colls_import_index
|
|
|
|
if coll in [c[1].coll_ptr for c in scn_category.items()]:
|
|
logger.info(
|
|
"%s already in the %s cache collections list", coll.name, category.lower()
|
|
)
|
|
# Set is_cache_coll.
|
|
coll.cm.is_cache_coll = True
|
|
|
|
return None
|
|
else:
|
|
if category == "EXPORT" and not coll.override_library and not coll.library:
|
|
# Local collection
|
|
# blend file needs to be saved for that.
|
|
if not bpy.data.filepath:
|
|
logger.error(
|
|
"Failed to add local collection %s to export list. Blend files needs to be saved.",
|
|
coll.name,
|
|
)
|
|
return None
|
|
|
|
item = scn_category.add()
|
|
item.coll_ptr = coll
|
|
item.name = item.coll_ptr.name
|
|
idx = len(scn_category) - 1
|
|
|
|
# Set is_cache_coll.
|
|
coll.cm.is_cache_coll = True
|
|
|
|
logger.info(
|
|
"%s added to %s cache collections list", item.name, category.lower()
|
|
)
|
|
|
|
return coll
|
|
|
|
|
|
def rm_coll_from_cache_collections(
|
|
context: bpy.types.Context, category: str
|
|
) -> Optional[bpy.types.Collection]:
|
|
|
|
scn = context.scene
|
|
|
|
scn_category = scn.cm.colls_export
|
|
idx = scn.cm.colls_export_index
|
|
|
|
if category == "IMPORT":
|
|
scn_category = scn.cm.colls_import
|
|
idx = scn.cm.colls_import_index
|
|
|
|
try:
|
|
item = scn_category[idx]
|
|
except IndexError:
|
|
return None
|
|
else:
|
|
|
|
item = scn_category[idx]
|
|
item_name = item.name
|
|
scn_category.remove(idx)
|
|
idx -= 1
|
|
|
|
# Reset coll.cm properties.
|
|
coll = item.coll_ptr
|
|
if coll: # check if not None (coll might be deleted)
|
|
coll.cm.reset_properties()
|
|
|
|
logger.info(
|
|
"Removed %s from %s cache collections list", item_name, category.lower()
|
|
)
|
|
return coll
|
|
|
|
|
|
def get_cache_frame_range(context: bpy.types.Context) -> Tuple[int, int]:
|
|
frame_in = context.scene.frame_start - context.scene.cm.frame_handles_left
|
|
if frame_in < 0:
|
|
frame_in = 0
|
|
frame_end = context.scene.frame_end + context.scene.cm.frame_handles_right
|
|
|
|
return (frame_in, frame_end)
|
|
|
|
|
|
def set_instancing_type_of_empties(
|
|
object_list: List[bpy.types.Object], instance_type: str
|
|
) -> List[Tuple[bpy.types.Object, str]]:
|
|
if instance_type not in cmglobals.INSTANCE_TYPES:
|
|
raise ValueError(f"Invalid instance type: {instance_type}")
|
|
|
|
empties_to_restore: List[Tuple[bpy.types.Object, str]] = []
|
|
|
|
for obj in object_list:
|
|
if not obj.type == "EMPTY":
|
|
continue
|
|
|
|
if obj.instance_type == instance_type:
|
|
continue
|
|
|
|
instance_type_cache = obj.instance_type
|
|
|
|
obj.instance_type = instance_type
|
|
|
|
empties_to_restore.append((obj, instance_type_cache))
|
|
|
|
if empties_to_restore:
|
|
logger.info(
|
|
"Set instance type to %s for empties: %s\n",
|
|
instance_type,
|
|
" ,".join([obj.name for obj, it in empties_to_restore]),
|
|
)
|
|
|
|
return empties_to_restore
|
|
|
|
|
|
def restore_instancing_type(restore_list: List[Tuple[bpy.types.Object, str]]) -> None:
|
|
|
|
# Log list.
|
|
log_list: Dict[str, List[str]] = {}
|
|
|
|
for obj, instance_type in restore_list:
|
|
|
|
if obj.instance_type == instance_type:
|
|
continue
|
|
|
|
instance_type_cache = obj.instance_type
|
|
obj.instance_type = instance_type
|
|
|
|
# Populate log list.
|
|
_append_str_to_log_list(
|
|
log_list,
|
|
obj.name,
|
|
f"{instance_type_cache}: -> {obj.instance_type}",
|
|
)
|
|
|
|
# Log.
|
|
_print_log_list(log_list, "Restore instance types:")
|
|
|
|
|
|
def is_item_local(
|
|
item: Union[bpy.types.Collection, bpy.types.Object, bpy.types.Camera]
|
|
) -> bool:
|
|
# Local collection of blend file.
|
|
if not item.override_library and not item.library:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_item_lib_override(
|
|
item: Union[bpy.types.Collection, bpy.types.Object, bpy.types.Camera]
|
|
) -> bool:
|
|
# Collection from libfile and overwritten.
|
|
if item.override_library and not item.library:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_item_lib_source(
|
|
item: Union[bpy.types.Collection, bpy.types.Object, bpy.types.Camera]
|
|
) -> bool:
|
|
# Source collection from libfile not overwritten.
|
|
if not item.override_library and item.library:
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_item_libfile(
|
|
item: Union[bpy.types.Collection, bpy.types.Object, bpy.types.Camera]
|
|
) -> str:
|
|
if is_item_lib_source(item):
|
|
# Source collection not overwritten.
|
|
lib = item.library
|
|
return Path(os.path.abspath(bpy.path.abspath(lib.filepath))).as_posix()
|
|
|
|
if is_item_local(item):
|
|
# Local collection
|
|
# blend file needs to be saved for that.
|
|
if not bpy.data.filepath:
|
|
return ""
|
|
return Path(os.path.abspath(bpy.path.abspath(bpy.data.filepath))).as_posix()
|
|
|
|
if is_item_lib_override(item):
|
|
# Overwritten collection.
|
|
lib = item.override_library.reference.library
|
|
return Path(os.path.abspath(bpy.path.abspath(lib.filepath))).as_posix()
|
|
|
|
return ""
|
|
|
|
|
|
def set_simplify(use_simplify: bool) -> None:
|
|
if bpy.context.scene.render.use_simplify == use_simplify:
|
|
return
|
|
|
|
noun = "Enabled"
|
|
if not use_simplify:
|
|
noun = "Disabled"
|
|
bpy.context.scene.render.use_simplify = use_simplify
|
|
logger.info("%s simplify", noun)
|