Files
Bioxel/bioxelnodes/save.py
Ma Nan b2791e3aae fix: eevee specular too high
feat: add pie cutter
feat: support 4d time data
chora: change operators name
docs: update README.md
2024-07-15 14:25:51 +08:00

213 lines
6.9 KiB
Python

import re
import bpy
from pathlib import Path
import shutil
from .utils import copy_to_dir, get_all_layers, get_container, get_container_from_selection, get_container_layers
from .nodes import custom_nodes
CLASS_PREFIX = "BIOXELNODES_MT_NODES"
class ReLinkNodes(bpy.types.Operator):
bl_idname = "bioxelnodes.relink_nodes"
bl_label = "Relink Nodes to Addon"
bl_description = "Relink all nodes to addon source"
def execute(self, context):
file_name = Path(custom_nodes.nodes_file).name
for lib in bpy.data.libraries:
lib_path = Path(bpy.path.abspath(lib.filepath)).resolve()
lib_name = lib_path.name
if lib_name == file_name:
if str(lib_path) != custom_nodes.nodes_file:
lib.filepath = custom_nodes.nodes_file
self.report({"INFO"}, f"Successfully relinked.")
return {'FINISHED'}
class SaveStagedData(bpy.types.Operator):
bl_idname = "bioxelnodes.save_staged_data"
bl_label = "Save Staged Data"
bl_description = "Save all staged data in this file for sharing"
save_layer: bpy.props.BoolProperty(
name="Save Layer VDB Cache",
default=True,
) # type: ignore
cache_dir: bpy.props.StringProperty(
name="Cache Directory",
subtype='DIR_PATH',
default="//"
) # type: ignore
save_lib: bpy.props.BoolProperty(
name="Save Node Library File",
default=True,
) # type: ignore
lib_dir: bpy.props.StringProperty(
name="Library Directory",
subtype='DIR_PATH',
default="//"
) # type: ignore
def execute(self, context):
if self.save_lib:
files = []
for classname in dir(bpy.types):
if CLASS_PREFIX in classname:
cls = getattr(bpy.types, classname)
files.append(cls.nodes_file)
files = list(set(files))
for file in files:
file_name = Path(file).name
# "//"
lib_dir = bpy.path.abspath(self.lib_dir)
output_path: Path = Path(lib_dir, file_name).resolve()
source_path: Path = Path(file).resolve()
if output_path != source_path:
shutil.copy(source_path, output_path)
for lib in bpy.data.libraries:
lib_path = Path(bpy.path.abspath(lib.filepath)).resolve()
if lib_path == source_path:
blend_path = Path(bpy.path.abspath("//")).resolve()
lib.filepath = bpy.path.relpath(
str(output_path), start=str(blend_path))
self.report({"INFO"}, f"Successfully saved to {output_path}")
if self.save_layer:
fails = []
for layer in get_all_layers():
try:
save_layer(layer, self.cache_dir)
except:
fails.append(layer)
if len(fails) == 0:
self.report({"INFO"}, f"Successfully saved bioxel layers.")
else:
self.report(
{"WARNING"}, f"{','.join([layer.name for layer in fails])} fail to save.")
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.invoke_props_dialog(self,
width=500)
return {'RUNNING_MODAL'}
@classmethod
def poll(cls, context):
return bpy.data.is_saved
def draw(self, context):
layout = self.layout
panel = layout.box()
panel.prop(self, "save_layer")
panel.prop(self, "cache_dir")
panel = layout.box()
panel.prop(self, "save_lib")
panel.prop(self, "lib_dir")
def save_layer(layer, output_dir):
pattern = r'\.\d{4}\.'
# "//"
output_dir = bpy.path.abspath(output_dir)
source_dir = bpy.path.abspath(layer.data.filepath)
source_path: Path = Path(source_dir).resolve()
is_sequence = re.search(pattern, source_path.name) is not None
name = layer.name if is_sequence else f"{layer.name}.vdb"
output_path: Path = Path(output_dir, name, source_path.name).resolve() \
if is_sequence else Path(output_dir, name).resolve()
if output_path != source_path:
copy_to_dir(source_path.parent if is_sequence else source_path,
output_path.parent.parent if is_sequence else output_path.parent,
new_name=name)
blend_path = Path(bpy.path.abspath("//")).resolve()
layer.data.filepath = bpy.path.relpath(
str(output_path), start=str(blend_path))
class SaveLayers(bpy.types.Operator):
bl_idname = "bioxelnodes.save_layers"
bl_label = "Save Layers"
bl_description = "Save Container Layers to Directory."
cache_dir: bpy.props.StringProperty(
name="Layer Directory",
subtype='DIR_PATH',
default="//"
) # type: ignore
@classmethod
def poll(cls, context):
containers = get_container_from_selection()
return len(containers) > 0
def execute(self, context):
containers = get_container_from_selection()
if len(containers) == 0:
self.report({"WARNING"}, "Cannot find any bioxel container.")
return {'FINISHED'}
fails = []
for container in containers:
for layer in get_container_layers(container):
try:
save_layer(layer, self.cache_dir)
except:
fails.append(layer)
if len(fails) == 0:
self.report({"INFO"}, f"Successfully saved bioxel layers.")
else:
self.report(
{"WARNING"}, f"{','.join([layer.name for layer in fails])} fail to save.")
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.invoke_props_dialog(self,
width=500)
return {'RUNNING_MODAL'}
class CleanAllCaches(bpy.types.Operator):
bl_idname = "bioxelnodes.clear_all_caches"
bl_label = "Clean All Caches in Temp"
bl_description = "Clean all caches saved in temp"
def execute(self, context):
preferences = context.preferences.addons[__package__].preferences
cache_dir = Path(preferences.cache_dir, 'VDBs')
try:
shutil.rmtree(cache_dir)
self.report({"INFO"}, f"Successfully cleaned caches.")
return {'FINISHED'}
except:
self.report({"WARNING"},
"Fail to clean caches, you may do it manually.")
return {'CANCELLED'}
def invoke(self, context, event):
context.window_manager.invoke_confirm(self,
event,
message="All caches will be cleaned, include other project files, do you still want to clean?")
return {'RUNNING_MODAL'}