import glob
import logging
import os
import re
import stringcase
from cgl.core.path.object import PathObject
from cgl.core.utils import read_write
from cgl.core.utils.read_write import load_json, load_yaml
try:
import bpy
except ModuleNotFoundError:
pass
logger = logging.getLogger("qtutils")
[docs]
def get_namespace(filepath):
"""
gets the namespace when creating a namespace
Args:
filepath:
current:
Returns:
namespace
"""
namespace = ""
path_object = PathObject().from_path_string(filepath)
if path_object.task == "cam":
namespace = "cam"
elif path_object.entity_type == "assets":
namespace = path_object.shot
elif path_object.entity_type == "shots":
namespace = "tempNS"
return get_next_namespace(namespace)
[docs]
def get_top_nodes():
"""
Get the top nodes in blender that are not defaults
"""
ignore = ["Camera", "Cube", "Light"]
top_nodes = []
for obj in bpy.context.scene.objects:
if obj.name() not in ignore:
top_nodes.append(obj)
return top_nodes
[docs]
def get_mesh_nodes_recursive(root_node):
mesh_nodes = [obj for obj in bpy.data.objects if obj.type == "MESH"]
return mesh_nodes
[docs]
def get_selected_namespace():
"""
Returns:
namespace of selected object.
"""
logging.info("Getting Selected Namespace not implemented yet")
return None
def create_shot_mask_info(
shot_number=1,
scene_name="default",
frame_start=0,
frame_end=240,
notes="These are the default settings",
):
# Ensure the Video Sequence Editor is available
bpy.context.window.workspace = bpy.data.workspaces["Video Editing"]
seq = bpy.context.scene.sequence_editor_create()
# Create the shot info text
shot_info = f"Shot: {shot_number}, Scene: {scene_name}, Frames: {frame_start}-{frame_end}, Notes: {notes}"
# Add a text strip with the shot info
text_strip = seq.sequences.new_effect(
name="ShotInfo",
type="TEXT",
channel=2,
frame_start=frame_start,
frame_end=frame_end,
)
text_strip.text = shot_info
text_strip.location = 0.5, 0.9 # Adjust as needed (x, y) in normalized coordinates
[docs]
def select_reference(collection_name):
"""
Selects all objects in the specified collection.
Parameters:
collection_name (str): The name of the collection to select objects from.
"""
# Deselect all objects first
bpy.ops.object.select_all(action="DESELECT")
# Check if the collection exists in the current scene
if collection_name in bpy.data.collections:
collection = bpy.data.collections[collection_name]
for obj in collection.objects:
# Make the object selectable and select
obj.select_set(True)
return [obj.name for obj in collection.objects]
else:
print(f"Collection '{collection_name}' not found.")
return None
[docs]
def get_ref_node(object_name):
obj = bpy.data.objects.get(object_name)
if obj is None:
print(f"Object '{object_name}' not found in the current scene.")
return None
if obj.library:
print(f"Object '{object_name}' is linked from: {obj.library.filepath}")
return obj.library
else:
print(f"Object '{object_name}' is not linked from an external file.")
return None
def get_next_namespace(base_name):
pattern = r"\d+$" # Regex to find a sequence of digits at the end of a string
highest_num = 0
exists = False
# Iterate over all collections in the Blender file
for coll in bpy.data.collections:
if coll.name.startswith(base_name):
exists = True
# Try to find a numeric suffix in the collection name
match = re.search(pattern, coll.name)
if match:
num = int(match.group())
highest_num = max(highest_num, num)
if exists:
# If the base name exists, append the next highest number
return f"{base_name}{highest_num + 1}"
else:
# If the base name does not exist, return the base name
return base_name
[docs]
def get_shape_name(geo):
logging.info("Getting Shape Name not implemented yet")
return None
[docs]
def create_tt(length, tt_object):
"""
Creates a turntable with frame range of 0-length, around the selected object.
Args:
length:
tt_object:
Returns:
None
"""
logging.info("Creating Turntable not implemented yet")
return None
[docs]
def clean_tt(task=None):
"""
Cleans up the turntable by deleting the camera and group node.
Args:
task:
Returns:
None
"""
logging.info("Cleaning Turntable not implemented yet")
return None
[docs]
def get_current_camera():
"""
Returns the name of the current camera in the scene.
Returns:
str: The name of the current camera.
"""
return bpy.context.scene.camera.name
[docs]
def confirm_prompt(title="title", message="message", button="Ok"):
"""
standard confirm prompt, this is an easy wrapper that allows us to do
confirm prompts in the native language of the application while keeping conventions
Args:
title:
message:
button: single button is created with a string, multiple buttons created with array
"""
logging.info("Confirm Prompt not implemented yet")
return None
[docs]
def load_plugin(plugin_name):
"""
Loads a plugin by name.
Args:
plugin_name: The name of the plugin to load.
Returns:
None
"""
logging.info("Load Plugin not implemented yet")
return None
[docs]
def get_playblast_path(path_object):
playblast_file = path_object.copy(
tree="render", filename="playblast", ext=""
).get_path()
return playblast_file
[docs]
def playblast_exists(path_object):
playblast_file = get_playblast_path(path_object)
playblast_files = glob.glob("{}*".format(playblast_file))
if playblast_files:
return playblast_files
else:
return False
[docs]
def review_exists(path_object):
path_, ext = os.path.splitext(path_object.preview_path)
logging.info(path_)
files = glob.glob("{}*".format(path_))
if files:
return files
else:
return False
[docs]
def basic_playblast(path_object=None, appearance="smoothShaded", cam=None, audio=False):
"""
Creates a playblast of the scene.
Args:
path_object:
appearance:
cam:
audio:
Returns:
None
"""
logging.info("Basic Playblast not implemented yet")
return None
[docs]
def create_thumb(path_object, camera=None):
"""
Creates a thumbnail of the scene.
Args:
path_object:
camera:
Returns:
None
"""
logging.info("Create Thumb not implemented yet")
return None
#
# def create_still_preview(path_object, task='mdl'):
# """
# creates a still preview, as is this is built for assets that have turntables.
# :param task:
# :return:
# """
# import cgl.plugins.maya.tasks.lite as lite
# import cgl.core.convert as convert
# from mtoa.cmds.arnoldRender import arnoldRender
#
# tt_tasks = ['mdl', 'mld', 'rig']
# pm.select(task)
# so = path_object
#
# lite.set_renderer()
# lite.turn_off_env_bg()
# lite.set_render_globals(gui=False)
# lite.set_file_type('jpeg')
# pm.setAttr("defaultArnoldDriver.pre", so.preview_path, type="string")
#
# if task in tt_tasks:
# Scene().create_turntable()
# create_env_light('Soft1Front2Backs')
# # time.sleep(1)
# # camera = pm.ls('turntable_cam*', type='transform')[0]
# arnoldRender(1920, 1080, True, True, 'turntable_camera1', ' -layer defaultRenderLayer')
# preview = glob.glob('{}*'.format(so.preview_path))[0]
# os.rename(preview, so.preview_path)
# convert.create_image_thumb(so.preview_path, so.thumb_path)
# # so.update_test_project_msd(attr='preview_file')
# # so.update_test_project_msd(attr='thumb_file')
# if task in tt_tasks:
# clean_tt()
# pm.delete('env_light')
[docs]
def get_hdri_yaml_path():
return f"{CFG.cookbook_dir}/hdri/hdri.yaml"
[docs]
def get_hdri_config():
return load_yaml(get_hdri_yaml_path())
[docs]
def get_hdri_root():
return get_hdri_yaml_path().split("\\CGL-HDRI")[0].replace("\\", "/")
[docs]
def add_hdri_root(filepath):
print(get_hdri_root(), filepath, 333)
return "{}{}".format(get_hdri_root(), filepath)
[docs]
def create_env_light(tex_name):
"""
Creates an environment light with the specified texture.
Args:
tex_name:
Returns:
None
"""
logging.info("Create Env Light not implemented yet")
return None
[docs]
def get_selected_reference() -> list:
"""
This function returns a list of reference nodes based on the currently selected objects in the
scene.
Returns:
list: a list of reference nodes that correspond to the selected objects in the scene. If no
objects are selected, it returns None. If the selected objects do not belong to any references, it
logs an error message and returns an empty list.
"""
selected_objects = bpy.context.selected_objects
reference_nodes = []
if not selected_objects:
logging.error("No objects selected.")
return None
for obj in selected_objects:
if obj.library:
reference_nodes.append(obj.library)
else:
logging.error(f"Object '{obj.name}' is not linked from an external file.")
return reference_nodes
[docs]
def default_matrix():
dm = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
return dm
[docs]
def get_matrix(obj, query=False):
"""
Returns a matrix of values relating to translate, scale, rotate.
Args:
obj:
query:
"""
logging.info("Get Matrix not implemented yet")
return None
[docs]
def get_frame_start():
"""
gets starting frame from render frame range
Returns:
int: start frame
"""
return int(bpy.context.scene.frame_start)
[docs]
def get_frame_end():
"""
gets end frame from render frame range
Returns:
int: end frame
"""
return int(bpy.context.scene.frame_end)
[docs]
def get_handle_start():
"""
gets the handle before the animation starts
Returns:
int: handle start
"""
return int(bpy.context.scene.frame_start)
[docs]
def get_handle_end():
"""
gets the handle after the animation ends.
Returns:
int: handle end
"""
return int(bpy.context.scene.frame_end)
[docs]
def set_subdivs(iterations=1, type_=1):
"""
Args:
iterations: default is 1
type_: 1: catclark, 0: None
Returns:
"""
logging.info("Set Subdivs not implemented yet")
return None
[docs]
def get_frame_range():
"""
gets the frame range of the scene
Returns:
dict: dictionary of frame range values
"""
return {
"start": get_frame_start(),
"end": get_frame_end(),
"handle_start": get_handle_start(),
"handle_end": get_handle_end(),
}
[docs]
def get_reference_meshes():
"""
Returns:
a list of all top nodes of references in the scene
"""
logging.info("Get Reference Meshes not implemented yet")
return None
[docs]
def valid_task_exists(task):
"""
Looks in a scene to see if a group node with name task exists and has children.
Args:
task: name of the group node to look for in the scene
"""
logging.info("Valid Task Exists not implemented yet")
return None
[docs]
def check_child_naming(selected=None, children=None):
"""
checks the naming of children of a selected node
Args:
selected: the selected node
children: the children of the selected node
Returns:
bool: True if the naming is correct, False otherwise
"""
logging.info("Check Child Naming not implemented yet")
return None
[docs]
def set_default_scene_settings():
from cgl.plugins.blender.alchemy import Scene
scene_object = Scene().path_object
scene = bpy.context.scene
scene.eevee.taa_render_samples = 1
scene.eevee.taa_samples = 1
scene.eevee.shadow_cube_size = "2048"
scene.render.resolution_x = int(
scene_object.project_settings["high_resolution"].split("x")[0]
)
scene.render.resolution_y = int(
scene_object.project_settings["high_resolution"].split("x")[1]
)
# Add a row to the Menu File
[docs]
def write_layout(outFile=None):
"""Writes layout to json file
Args:
outFile (str): path to json file
"""
from cgl.plugins.blender.alchemy import Scene
from cgl.core.path.object import PathObject
from cgl.core.utils.read_write import save_json
from pathlib import Path
scene_object = Scene().path_object
if outFile is None:
outFile = scene_object.copy(ext="json", task="lay", user="publish").get_path()
data = {}
for obj in bpy.context.view_layer.objects:
if obj.is_instancer:
logging.info(5 * "_" + obj.name + 5 * "_")
name = obj.name
# blender_transform = np.array(obj.matrix_world).tolist()
blender_transform = [
obj.matrix_world.to_translation().x,
obj.matrix_world.to_translation().y,
obj.matrix_world.to_translation().z,
obj.matrix_world.to_euler().x,
obj.matrix_world.to_euler().y,
obj.matrix_world.to_euler().z,
obj.matrix_world.to_scale().x,
obj.matrix_world.to_scale().y,
obj.matrix_world.to_scale().z,
]
instanced_collection = obj.instance_collection
if instanced_collection:
collection_library = return_linked_library(instanced_collection.name)
if collection_library:
libraryPath = bpy.path.abspath(collection_library.filepath)
filename = Path(bpy.path.abspath(libraryPath)).__str__()
libObject = PathObject(filename)
data[name] = {
"name": libObject.asset,
"source_path": libObject.path,
"blender_transform": blender_transform,
}
else:
logging.info("{} has no instanced collection".format(obj.name))
else:
logging.info("{} has no instanced collection".format(obj.name))
save_json(outFile, data)
return outFile
[docs]
def return_linked_library(collection):
"""
retrieves the linked libraries manually
"""
libraries = bpy.data.libraries
collection_name = collection.split(".")[0]
for i in libraries:
if collection in i.name:
return i
[docs]
def read_layout(outFile=None, linked=False, append=False):
"""
Reads layout from json file
Args:
outFile (str): path to json file
linked (bool): if True, links the assets
append (bool): if True, appends the assets
"""
from cgl.plugins.blender.alchemy import Scene, import_file_old
from cgl.core.path.object import PathObject
from cgl.core.utils.read_write import load_json
bpy.ops.file.make_paths_absolute()
if outFile is None:
outFileObject = Scene.path_object.copy(
ext="json", task="lay", set_filename=True
).latest_version()
outFile = outFileObject.get_path()
data = load_json(outFile)
for p in sorted(data):
logging.info(p)
data_path = data[p]["source_path"]
blender_transform = data[p]["blender_transform"]
transform_data = []
for value in blender_transform:
transform_data.append(float(value))
pathToFile = os.path.join(Scene().path_object.root, data_path)
pathObject = PathObject(pathToFile)
if pathObject.filebase in bpy.data.libraries:
lib = bpy.data.libraries[pathObject.filename]
bpy.data.batch_remove(ids=([lib]))
import_file_old(pathObject.get_path(), linked=linked, append=append)
else:
import_file_old(pathObject.get_path(), linked=linked, append=append)
if p not in bpy.data.objects:
obj = bpy.data.objects.new(p, None)
bpy.context.collection.objects.link(obj)
obj.instance_type = "COLLECTION"
obj.instance_collection = bpy.data.collections[pathObject.asset]
location = (transform_data[0], transform_data[1], transform_data[2])
obj.location = location
rotation = (transform_data[3], transform_data[4], transform_data[5])
obj.rotation_euler = rotation
scale = (transform_data[6], transform_data[7], transform_data[8])
obj.scale = scale
else:
obj = bpy.data.objects[p]
logging.info("updating position")
logging.info(obj.name)
location = (transform_data[0], transform_data[1], transform_data[2])
obj.location = location
rotation = (transform_data[3], transform_data[4], transform_data[5])
obj.rotation_euler = rotation
scale = (transform_data[6], transform_data[7], transform_data[8])
obj.scale = scale
[docs]
def rename_materials(selection=None, material_name=None):
"""
Sequentially renames materials from given object name if empty , renamed from selected object
Args:
selection (list): list of objects to rename materials from
material_name (str): name of the material to rename to
"""
if selection == None:
selection = bpy.context.selected_objects
if material_name == None:
if selection.parent:
material_name = selection.parent.name
else:
material_name = selection.name
for object in selection:
for material_slot in object.material_slots:
material_slot.material.name = object.name
logging.info(object.name, material_slot.name)
if selection:
selection = [bpy.data.objects[selection]]
for object in selection:
for material_slot in object.material_slots:
material_slot.material.name = material_name
logging.info(object.name, material_slot.name)
cleanup_scene_data(bpy.data.materials)
[docs]
def get_valid_meshes_list(objects):
valid_objects = []
for object in objects:
if object and object.type == "MESH":
if object.is_instancer == False:
valid_objects.append(object)
return valid_objects
[docs]
def get_materials_from_object(object):
valid_materials = []
for material_slot in object.material_slots:
material = material_slot.material
valid_materials.append(material_slot.material)
return valid_materials
[docs]
def get_selection(selection=None):
from .alchemy import Scene
scene_object = Scene().path_object
if selection is None:
try:
selection = bpy.context.selected_objects
except:
currentScene = scene_object
assetName = scene_object.shot
obj_in_collection = bpy.data.collections[assetName].all_objects
if not selection:
selection = bpy.data.objects
return selection
[docs]
def get_preview_from_texture(inputs, node_tree):
texture = None
if inputs:
color_input = inputs[1]
transparent = inputs[2]
try:
texture = color_input.links[0].from_node
except IndexError:
logging.info("no texture connected")
pass
if texture:
if texture.type == "TEX_IMAGE":
preview_color = texture.image.pixels
diffuse_color = [
preview_color[7004],
preview_color[7005],
preview_color[7006],
1 - transparent.default_value,
]
if texture.type == "RGB":
simple_color = texture.outputs["Color"].default_value
preview_color = [
simple_color[1],
simple_color[2],
simple_color[3],
1 - transparent.default_value,
]
else:
preview_color = [1, 1, 1, 1]
logging.info("_______COMPLEX NODES PLEASE SET MANUALLY_______")
else:
inputs = preview_inputs_from_node_tree(node_tree)
if inputs:
preview_color = [
color_input.default_value[0],
color_input.default_value[1],
color_input.default_value[2],
1,
]
else:
preview_color = [1, 1, 1, 1]
return preview_color
[docs]
def setup_preview_viewport_display(object):
"""
set up the default viewport display color diffuse_color on materials
Args:
color: Value of the color of the parent menu FloatProperty 4
selection:
"""
materials = get_materials_from_object(object)
for material in materials:
node_tree = material.node_tree.nodes
inputs = preview_inputs_from_node_tree(node_tree)
preview_colors = get_preview_from_texture(inputs, node_tree)
for i in range(0, 3):
material.diffuse_color[i] = preview_colors[i]
[docs]
def get_materials_dictionary():
"""
creates a dictionary of the objects and the faces associated with that object
Returns:
materials (dict): dictionary of the objects and the faces associated with that object
"""
materials = {}
for o in bpy.context.selected_objects:
bpy.ops.object.material_slot_remove_unused()
# Initialize dictionary of all materials applied to object with empty lists
# which will contain indices of faces on which these materials are applied
materialPolys = {ms.material.name: [] for ms in o.material_slots}
for i, p in enumerate(o.data.polygons):
materialPolys[o.material_slots[p.material_index].name].append(i)
materials.update({o.name: materialPolys})
return materials
[docs]
def read_materials(path_object=None):
"""
Reads the materials on the shdr task from defined from a json file
"""
from cgl.plugins.blender import alchemy as alc
from cgl.core.utils.read_write import load_json
from cgl.plugins.blender.alchemy import Scene
if path_object is None:
path_object = Scene().path
shaders = path_object.copy(
task="mld", user="publish", set_filename=True
).latest_version()
outFile = shaders.copy(ext="json").get_path()
data = load_json(outFile)
for obj in data.keys():
object = bpy.data.objects[obj]
# data = object.data
index = 0
for material in data[obj].keys():
if material not in bpy.data.materials:
alc.import_file_old(
shaders.get_path(),
collection_name=material,
type="MATERIAL",
linked=False,
)
if material not in object.data.materials:
object.data.materials.append(bpy.data.materials[material])
face_list = data[obj][material]
for face in face_list:
object.data.polygons[face].select = True
bpy.ops.object.mode_set(mode="EDIT")
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
object.active_material_index = index
bpy.ops.object.material_slot_assign()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode="OBJECT")
index += 1
[docs]
def add_modifier(obj, mod_name, name):
modifier = obj.modifiers.new(name, mod_name)
return modifier
[docs]
def create_task_on_asset(task, path_object=None, version_up=False):
"""
Creates a task on disk based on path object
Args:
task:
path_object:
type: type of task, mdl, shdr, rig, etc
"""
from cgl.plugins.blender.alchemy import Scene
if path_object is None:
path_object = Scene().path
newTask = path_object.copy(task=task)
if version_up:
newTask = newTask.next_minor_version()
taskFolder = newTask.copy(filename="").get_path()
logging.info("{} creating version".format(taskFolder))
logging.info(newTask.get_path())
sourceFolder = newTask.copy(filename="", tree="source").get_path()
renderFolder = newTask.copy(filename="", tree="render").get_path()
folders = [sourceFolder, renderFolder]
for f in folders:
if not os.path.isdir(f):
os.makedirs(f)
return newTask
[docs]
def reorder_list(items, arg=""):
"""
Reorders list in order of importance, putting rig
Args:
items: list of items to reorder
arg: item to put at the top of the list
"""
if arg:
for i in items:
if i == arg:
items.remove(i)
items.insert(0, arg)
return items
[docs]
def unlink_asset(object):
filepath = None
try:
libname = object.data.library
except AttributeError:
for lib in bpy.data.libraries:
libname = object.instance_collection
logging.info("_________unlinking__________")
logging.info(object)
if "proxy" in object.name:
name = object.name.split("_")[0]
else:
name = object.name
obj = bpy.data.objects[name]
if not libname:
bpy.data.batch_remove(ids=([obj]))
else:
try:
filepath = libname.library.filepath
except AttributeError:
pass
if filepath and PathObject(filepath).type == "env":
remove_linked_environment_dependencies(libname.library)
bpy.data.batch_remove(ids=(libname, obj))
remove_unused_libraries()
[docs]
def remove_linked_environment_dependencies(library):
env = library
bpy.ops.file.make_paths_absolute()
env_path = PathObject(env.filepath)
env_layout = env_path.copy(ext="json").get_path()
env_asset_collection = bpy.data.collections["{}_assets".format(env_path.asset)]
data = load_json(env_layout)
for i in data:
logging.info(i)
name = data[i]["name"]
if i in bpy.data.objects:
obj = bpy.data.objects[i]
unlink_asset(obj)
try:
bpy.data.collections.remove(env_asset_collection)
except KeyError:
pass
[docs]
def remove_unused_libraries():
libraries = bpy.data.libraries
objects = bpy.data.objects
instancers = []
libraries_in_scene = []
for obj in objects:
if obj.is_instancer:
instancers.append(obj)
try:
for i in instancers:
lib = i.instance_collection.library
if lib not in libraries_in_scene:
libraries_in_scene.append(lib)
for lib in libraries:
if lib not in libraries_in_scene:
logging.info(lib)
# bpy.data.libraries.remove(lib)
bpy.data.batch_remove(ids=(lib,))
except AttributeError:
pass
[docs]
def remove_instancers():
for object in bpy.data.objects:
filepath = None
try:
libname = object.data.library
except AttributeError:
for lib in bpy.data.libraries:
libname = object.instance_collection
logging.info("_________unlinking__________")
logging.info(object)
if "proxy" in object.name:
name = object.name.split("_")[0]
else:
name = object.name
obj = bpy.data.objects[name]
if not libname:
bpy.data.batch_remove(ids=([obj]))
else:
try:
filepath = libname.library.filepath
except AttributeError:
pass
remove_unused_libraries()
[docs]
def reparent_linked_environemnt_assets(library):
env = library
bpy.ops.file.make_paths_absolute()
env_path = PathObject(env.filepath)
env_layout = env_path.copy(ext="json").get_path()
data = load_json(env_layout)
assets_collection_name = "{}_assets".format(env_path.asset)
if assets_collection_name not in bpy.data.collections["env"].children:
assets_collection = bpy.data.collections.new(assets_collection_name)
bpy.data.collections["env"].children.link(assets_collection)
else:
assets_collection = bpy.data.collections[assets_collection_name]
for i in data:
logging.info(i)
name = data[i]["name"]
if i in bpy.data.objects:
obj = bpy.data.objects[i]
if assets_collection not in obj.users_collection:
assets_collection.objects.link(obj)
keep_single_user_collection(obj, assetName=assets_collection_name)
[docs]
def keep_single_user_collection(obj, assetName=None):
from cgl.plugins.blender.alchemy import Scene
if not assetName:
scene_object = Scene().path_object
assetName = scene_object.shot
try:
bpy.data.collections[assetName].objects.link(obj)
except RuntimeError:
pass
for collection in obj.users_collection:
if collection.name != assetName:
collection.objects.unlink(obj)
[docs]
def reparent_collections(view_layer):
for obj in view_layer:
if obj.instance_type == "COLLECTION":
if obj.instance_collection:
collection = obj.instance_collection
logging.info(collection)
logging.info(collection.library)
if collection.library:
path_object = PathObject(collection.library.filepath)
create_collection(path_object.type)
for collection in bpy.data.collections:
if collection.name == path_object.type:
# logging.info(collection.name )
if collection not in obj.users_collection:
collection.objects.link(obj)
keep_single_collections(obj, path_object.type)
for collection in bpy.context.scene.collection.children:
if len(collection.objects) < 1:
logging.info(collection.name)
bpy.context.scene.collection.children.unlink(collection)
[docs]
def keep_single_collections(obj, collection_name):
"""
Unlink object from all collections except the one specified
Args:
obj: object to adjust
collection_name: name of the collection to keep
"""
user_collections = obj.users_collection
if len(obj.users_collection) > 1:
for collection in user_collections:
if collection.name != collection_name:
try:
logging.info("unlinking {} {}".format(obj.name, collection_name))
collection.objects.unlink(obj)
except:
pass
[docs]
def create_collection(collection_name, parent=None):
"""
Creates a collection in current scene
Args:
collection_name (str): name of the collection to create
parent (str): name of the parent collection
"""
if collection_name not in bpy.data.collections:
bpy.data.collections.new(collection_name)
collection = bpy.data.collections[collection_name]
if parent is None:
parent = bpy.context.scene.collection
else:
parent = bpy.data.collections[parent]
try:
parent.children.link(collection)
except RuntimeError:
logging.info("{} collection already in scene".format(collection_name))
pass
return collection
[docs]
def parent_to_collection(obj, collection_name, replace=False):
"""Parents object to collection
Args:
obj: blender object
collection_name: name of the collection to parent to
replace: if True, replaces the object in the collection
"""
collection = get_collection(collection_name)
if not collection:
create_collection(collection_name)
try:
collection.objects.link(obj)
except RuntimeError:
logging.info("{} already in {} collection".format(obj.name, collection_name))
except AttributeError:
raise
logging.info("I wasnt able to find the collection {} ".format(collection_name))
return
if replace:
for col in obj.users_collection:
col.objects.unlink(obj)
collection.objects.link(obj)
[docs]
def return_asset_name(obj):
if "proxy" in obj.name:
name = obj.name.split("_")[0]
return name
else:
if "." in obj.name:
name = obj.name.split(".")[0]
else:
name = obj.name
return name
[docs]
def get_lib_from_object(object):
instancer = object.is_instancer
if not instancer:
try:
object = bpy.data.object[return_asset_name(object)]
except:
pass
try:
library = object.instance_collection.library
except AttributeError:
return None
pass
return library
[docs]
def return_lib_path(library):
from pathlib import Path
logging.info(library)
library_path = bpy.path.abspath(library.filepath)
filename = Path(bpy.path.abspath(library_path)).__str__()
return filename
[docs]
def create_shot_mask_info():
from cgl.plugins.blender.alchemy import Scene
current = bpy.context.scene
mSettings = current.render
sceneObject = Scene().path_object
current.name = sceneObject.filebase
scene_info = bpy.context.scene.statistics(bpy.context.view_layer)
try:
mSettings.metadata_input = "SCENE"
except AttributeError:
mSettings.use_stamp_strip_meta = 0
mSettings.stamp_font_size = 26
mSettings.use_stamp = 1
mSettings.use_stamp_camera = 1
mSettings.use_stamp_date = 0
mSettings.use_stamp_frame = True
mSettings.use_stamp_frame_range = 0
mSettings.use_stamp_hostname = 0
mSettings.use_stamp_labels = 0
mSettings.use_stamp_lens = 1
mSettings.use_stamp_marker = 0
mSettings.use_stamp_memory = 0
mSettings.use_stamp_note = 0
mSettings.use_stamp_render_time = 0
mSettings.use_stamp_scene = 1
mSettings.use_stamp_sequencer_strip = 0
mSettings.use_stamp_time = 1
mSettings.use_stamp_note = True
mSettings.stamp_note_text = scene_info
logging.info("shot_mask_created")
[docs]
def create_object(name, type=None, parent=None, collection=None, namespace=None):
if namespace:
name = "{}:{}".format(namespace, name)
if collection is None:
collection = bpy.context.collection.name
if name in bpy.data.objects:
object = bpy.data.objects[name]
logging.info("{} object already exists".format(name))
else:
object = bpy.data.objects.new(name, object_data=type)
if parent:
object.parent = parent
parent_to_collection(collection_name=collection, obj=object)
return object
[docs]
def parent_object(child, parent, keep_transform=True):
child.parent = parent
if keep_transform:
child.matrix_parent_inverse = parent.matrix_world.inverted()
[docs]
def clear_parent(objects=None):
if objects is None:
objects = bpy.context.selected_objects
for obj in objects:
parent = obj.parent
children = obj.children
if children:
for child in children:
parent_object(child, parent)
[docs]
def get_objects_in_hirarchy(obj, levels=10):
hirarchy = []
def recurse(obj, parent, depth):
if depth > levels:
return
hirarchy.append(obj.name)
# logging.info(" " * depth, obj.name)
for child in obj.children:
recurse(child, obj, depth + 1)
recurse(obj, obj.parent, 0)
return hirarchy
[docs]
def cleanup_file(task="mdl"):
"""
finds the given task group and deletes everythin ecept for that locator
Args:
task: task to keep in scene
keep_task:
asset:
"""
task_object = get_scene_object(task)
tasks_objects = get_objects_in_hirarchy(task_object)
all_objects = objects_in_scene()
for obj in all_objects:
if obj.name not in tasks_objects:
logging.info(obj)
bpy.data.objects.remove(obj)
for collection in collections_in_scene():
if collection.name not in ["Scene Collection", get_scene_collection().name]:
logging.info(collection)
bpy.data.collections.remove(collection)
purge_unused_data()
[docs]
def build_default_structure(group, default_list=["FG", "CAM", "RIG"], namespace=None):
for layer in default_list:
if namespace:
layer = "{}:{}".format(namespace, layer)
create_object(layer, parent=group)
create_object("BG", parent=group, namespace=namespace)
create_object("MAIN", parent=group, namespace=namespace)
[docs]
def create_folders(path_object):
"""
Creates the folders for the given path object
Args:
path_object: path object to create folders for
"""
import os
path_folder = path_object.copy(filename="").get_path()
if not os.path.isdir(path_folder):
os.makedirs(path_folder)
[docs]
def save_to_task(task):
"""
Saves a copy of the current file into a new version of the given task
Args:
task:
"""
from cgl.plugins.blender.alchemy import Scene
scene = Scene().path_object
new_file = scene.copy(task=task, tree="source", latest=True, set_filename=True)
directory = new_file.copy(filename="")
next_version = new_file.next_minor_version()
create_folders(next_version)
Scene().save_file_as(next_version.get_path())
return next_version
[docs]
def purge_unused_data():
"""
Deletes all the uneused data, ie data that's not currently linked to any scene
"""
remove_unused_libraries()
cleanup_scene_data(bpy.data.collections)
cleanup_scene_data(bpy.data.meshes)
cleanup_scene_data(bpy.data.objects)
cleanup_scene_data(bpy.data.materials)
cleanup_scene_data(bpy.data.images)
cleanup_scene_data(bpy.data.grease_pencils)
cleanup_scene_data(bpy.data.node_groups)
cleanup_scene_data(bpy.data.armatures)
[docs]
def cleanup_scene_data(data_type):
"""
Deletes data that's not currently linked to any object in scene , takes in bpy.data.type ie
bpy.data.materials
Args:
data_type: bpy.data.type ie bpy.data.materials
"""
for child in data_type:
if child.users == 0:
logging.info(child.name)
data_type.remove(child)
[docs]
def return_object_list(task):
object_list = []
for res in bpy.data.objects[task].children:
for materials in res.children:
for obj in materials.children:
object_list.append(obj)
return object_list
[docs]
def objects_in_scene(string=False):
list_of_objects = []
if string is True:
for object in bpy.data.objects:
list_of_objects.append(object.name)
return list_of_objects
return bpy.data.objects
[docs]
def collections_in_scene(string=False):
list_of_objects = []
if string is True:
for collection in bpy.data.collections:
list_of_objects.append(collection.name)
return list_of_objects
else:
return bpy.data.collections
[docs]
def load_library(path_object, collection_name=None, replace=True):
if os.path.exists(path_object.get_path()):
if collection_name is None:
name = "{}_{}_{}:{}".format(
path_object.sequence,
path_object.shot,
path_object.variant,
path_object.task,
)
collection_name = name
if replace:
for lib in bpy.data.libraries:
if lib.name == name:
bpy.data.libraries.remove(lib)
with bpy.data.libraries.load(path_object.get_path(), link=True) as (
data_from,
data_to,
):
for c in data_from.collections:
if c == collection_name:
logging.info(c)
data_to.collections = [c]
return c
[docs]
def get_next_namespace(ns, sel=None, type="lay", levels=3):
import re
from cgl.plugins.blender.alchemy import Scene
pattern = "[0-9]+"
next = False
latest = 0
if sel is None:
scene = Scene().path_object
layer = get_scene_object()
sel = _hirarchy(layer, levels=levels)
for name in sel:
i = get_object(name)
if ns in i.name:
name = i.name.split(ns)
if name[1].startswith("_"):
# logging.info(i.name)
split_namespace = i.name.split(":")
num = re.findall(pattern, i.name)
logging.info(num)
if num:
latest = int(num[-1]) + 1
next = True
name = "{}_{:03d}".format(ns, latest)
logging.info(name)
return name
def set_framerange(start=1, end=1, current=False):
bpy.context.scene.frame_start = start
bpy.context.scene.frame_end = end
current = bpy.context.scene.frame_current
if current:
bpy.context.scene.frame_start = current
bpy.context.scene.frame_end = current
[docs]
def render(preview=False, audio=False):
"""Renders the current scene.
Based on the task we can derive what kind of render and specific render settings.
Args:
preview: determines if exr is used or not
audio: if True renders an mov and setups the audio settings
"""
from cgl.plugins.blender.alchemy import Scene
previewRenderTypes = ["anim", "rig", "mdl", "lay"]
file_out = Scene().path_object.render_path.split("#")[0]
if preview:
bpy.context.scene.render.image_settings.file_format = "JPEG"
bpy.context.scene.render.filepath = file_out
if audio:
bpy.context.scene.render.image_settings.file_format = "FFMPEG"
bpy.context.scene.render.ffmpeg.format = "QUICKTIME"
bpy.context.scene.render.ffmpeg.audio_codec = "MP3"
bpy.ops.render.opengl("INVOKE_DEFAULT", animation=True, view_entity_type=True)
else:
bpy.context.scene.render.image_settings.file_format = "OPEN_EXR_MULTILAYER"
bpy.context.scene.render.filepath = file_out
bpy.ops.render.render(animation=True, use_viewport=True)
[docs]
def get_framerange():
start = bpy.context.scene.frame_start
end = bpy.context.scene.frame_end
return start, end
[docs]
def scene_elem(elem):
return eval("bpy.data.{}".format(elem))
[docs]
def get_scene_collection(task=None, path_object=None):
from cgl.plugins.blender.alchemy import Scene
if not path_object:
path_object = Scene().path_object
if not task:
task = path_object.task
name = "{}_{}_{}:{}".format(
path_object.seq, path_object.shot, path_object.variant, task
)
return get_collection(name)
[docs]
def get_object(name, namespace=None):
if isinstance(name, str):
if namespace:
name = "{}:{}".format(namespace, name)
if name in bpy.data.objects:
return bpy.data.objects[name]
else:
return None
else:
return name
[docs]
def get_layer(name, namespace=None, set_default_namespace=True):
from cgl.plugins.blender.alchemy import Scene
scene = Scene().path_object
if set_default_namespace is True:
if namespace is None:
namespace = "{}_{}_{}".format(scene.sequence, scene.shot, scene.variant)
layer_name = "{}:{}".format(namespace, name)
else:
layer_name = name
return get_object(layer_name)
[docs]
def get_collection_from_path_object(path_object):
from cgl.core.path.object import PathObject
set_all_paths_relative(False)
for i in bpy.data.collections:
if i.library:
lib_path_object = PathObject(i.library.filepath)
if lib_path_object.root == path_object.get_path():
return i
set_all_paths_relative(True)
return "couldnt find collection for {}".format(path_object.get_path())
[docs]
def get_collection(name, namespace=None):
if namespace:
name = "{}:{}".format(namespace, name)
if name in bpy.data.collections:
return bpy.data.collections[name]
else:
return None
[docs]
def set_framerange(start, end):
bpy.context.scene.frame_start = start
bpy.context.scene.frame_end = end
bpy.context.scene.frame_current = start
[docs]
def selection(object=None, clear=False):
if clear:
for ob in bpy.data.objects:
ob.select_set(False)
if object:
object.select_set(True)
bpy.context.view_layer.objects.active = object
[docs]
def select_objects(list):
for i in list:
selection(i)
[docs]
def current_selection(single=False):
if single:
return bpy.context.object
return bpy.context.selected_objects
[docs]
def switch_overlays(visible=False):
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == "VIEW_3D":
for space in area.spaces:
if space.type == "VIEW_3D":
space.overlay.show_overlays = visible
[docs]
def read_matrix(obj, transform_data):
location = (transform_data[0], transform_data[1], transform_data[2])
obj.location = location
rotation = (transform_data[3], transform_data[4], transform_data[5])
obj.rotation_euler = rotation
scale = (transform_data[6], transform_data[7], transform_data[8])
obj.scale = scale
[docs]
def set_collection_name(obj=None):
from cgl.plugins.blender.alchemy import Scene
scene = Scene().path_object
if scene.entity_type == "assets":
name = "{}_{}_{}:{}".format(
scene.sequence, scene.shot, scene.variant, scene.task
)
elif scene.entity_type == "shots":
name = "{}_{}_{}:{}".format(
scene.sequence, scene.shot, scene.variant, scene.task
)
if obj is None:
if name in bpy.data.collections:
logging.info("collection exist")
else:
if "Collection" in bpy.data.collections:
bpy.data.collections["Collection"].name = name
else:
logging.info("default Collection not found")
bpy.context.collection.name = name
else:
if scene.asset in bpy.data.collections:
logging.info("collection exist ")
object = bpy.context.object
object.users_collection[0].name = name
[docs]
def set_context_view_3d():
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == "VIEW_3D":
override = {"window": window, "screen": screen, "area": area}
bpy.ops.screen.screen_full_area(override)
def _hirarchy(obj, levels=10):
hirarchy = []
def recurse(obj, parent, depth):
if depth > levels:
return
hirarchy.append(obj.name)
# logging.info(" " * depth, obj.name)
for child in obj.children:
recurse(child, obj, depth + 1)
recurse(obj, obj.parent, 0)
return hirarchy
[docs]
def rename_collection(current_scene=None):
from cgl.plugins.blender.alchemy import Scene
if current_scene is None:
current_scene = Scene().path_object
name = "{}_{}_{}:{}".format(
current_scene.seq, current_scene.shot, current_scene.variant, current_scene.task
)
collection = get_scene_collection()
if not collection:
if len(bpy.data.collections) == 1:
bpy.data.collections[0].name = name
else:
try:
bpy.data.collections["Collection"].name = name
except KeyError:
asset_collection = get_collection(current_scene.asset)
if asset_collection:
asset_collection.name = name
logging.info("collection in scene")
else:
logging.info("failed to rename ")
pass
[docs]
def delete_object(object_to_delete):
bpy.data.objects.remove(object_to_delete, do_unlink=True)
[docs]
def get_scene_name():
return bpy.context.scene.name
[docs]
def get_scene_object(task=None):
from cgl.plugins.blender.alchemy import Scene
if not task:
task = Scene().path_object.task
name = "{}_{}_{}:{}".format(
Scene().path_object.sequence,
Scene().path_object.asset,
Scene().path_object.variant,
task,
)
object = get_object(name)
if not object:
try:
object = get_object(task)
except:
logging.info("I wasnt able to find any scene object, please click build")
pass
return object
def get_items(type):
command = "bpy.data.{}".format(type)
return eval(command)
[docs]
def move_to_project(project, path_object=None):
from cgl.core.utils.general import cgl_copy
from cgl.plugins.blender.alchemy import Scene
context = ["source", "render"]
if path_object is None:
path_object = Scene().path_object
for item in context:
fromDir = path_object.copy(entity_type=item, filename=None).get_path()
toDir = path_object.copy(
entity_type=item, filename=None, project=project
).get_path()
cgl_copy(fromDir, toDir)
[docs]
def move_linked_libraries_to_project(project=None):
from cgl.core.path.object import PathObject
bpy.ops.file.make_paths_absolute()
for lib in bpy.data.libraries:
path_object = PathObject(lib.filepath)
logging.info(lib.filepath)
move_to_project(project, path_object)
[docs]
def update_libraries_project(project=None):
from cgl.core.path.object import PathObject
from cgl.plugins.blender.alchemy import Scene
if project is None:
project = Scene().path_object.project
bpy.ops.file.make_paths_absolute()
for lib in bpy.data.libraries:
path_object = PathObject(lib.filepath)
new_path = path_object.copy(project=project)
lib.filepath = new_path.get_path()
logging.info(new_path.get_path())
lib.reload()
[docs]
def get_valid_objects_in_scene_hirarchy():
sceneObject = get_scene_object()
children = sceneObject.children
objects = []
if children:
for child in children:
for obj in child.children:
objects.append(obj)
return objects
[docs]
def set_object_names_from_library(objects=None):
from .msd import path_object_from_source_path
bpy.ops.file.make_paths_absolute()
if not objects:
objects = get_valid_objects_in_scene_hirarchy()
for obj in objects:
logging.info(222222222222)
logging.info(obj)
# logging.info(obj[0].name, obj[1].file_path)
path_object = path_object_from_source_path(obj["source_path"])
next_version_number = get_next_namespace(path_object.asset)
logging.info(path_object.get_path())
logging.info(next_version_number)
obj.name = "{}:{}".format(path_object.asset, next_version_number)
[docs]
def set_all_paths_relative(default=True):
if default:
bpy.ops.file.make_paths_relative()
else:
bpy.ops.file.make_paths_absolute()
[docs]
def clean_name(grp):
for i in get_objects_in_hirarchy(grp):
logging.info(i)
object = get_object(i)
object.name = i.split("-")[0]
[docs]
def get_items(type="assets"):
from cgl.plugins.blender.alchemy import Scene
from cgl.core.utils.general import load_json
scene = Scene().path_object
project_msd = load_json(scene.project_msd_path)
shots = project_msd[type]
shot_list = []
for seq in shots:
for shot in shots[seq]:
name = "{} {}".format(seq, shot)
shot_list.append(name)
return shot_list
[docs]
def search_dialog(list, command, flags=None):
try:
bpy.utils.unregister_class(SearchItem)
except:
pass
bpy.utils.register_class(SearchItem)
bpy.context.scene["alchemy_search"] = str(list)
bpy.context.scene["alchemy_command"] = command
if flags:
bpy.context.scene["alchemy_flags"] = flags
# bpy.ops.magic_browser.search_item('INVOKE_DEFAULT')
[docs]
def get_search_selection_task():
from cgl.plugins.blender.alchemy import Scene
from cgl.core.utils.general import load_json
scene = Scene().path_object
project_msd = load_json(scene.project_msd_path)
type, sel = bpy.context.scene["alchemy_selection"].split(" ")
bpy.context.scene["alchemy_temp"] = bpy.context.scene["alchemy_selection"]
context = bpy.context.scene["alchemy_flags"]
logging.info(project_msd[context][type][sel])
object = project_msd[context][type][sel]
task = []
for i in object:
logging.info(i)
task.append(i)
return task
[docs]
def tag_scene(tag, value=None):
if not value:
value = "{} {}".format(
bpy.context.scene["alchemy_temp"], bpy.context.scene["alchemy_selection"]
)
bpy.context.scene[tag] = value
# class SearchItem(bpy.types.Operator):
#
#
# """
# imports latest version of selectd task
# """
# bl_idname = 'magic_browser.search_item'
# bl_label = 'Open Shot'
#
# bl_property = "alchemy_selection"
#
#
# search = format_list(eval(bpy.context.scene['alchemy_search']))
# logging.info(search)
# alchemy_selection = bpy.props.EnumProperty(items=search)
#
# def execute(self, context):
# self.report({'INFO'}, "Selected: %s" % self.alchemy_selection)
# bpy.types.Scene.scene_enum = bpy.props.StringProperty(name=self.alchemy_selection)
# bpy.types.Scene.scene_enum = self.alchemy_selection + ' publish'
# bpy.context.scene['alchemy_selection'] = self.alchemy_selection
# eval(bpy.context.scene['alchemy_command'])
#
# return {'FINISHED'}
#
# def invoke(self, context, event):
# wm = context.window_manager
# wm.invoke_search_popup(self)
# return {'FINISHED'}
#
[docs]
def selected_library(selected=None):
from pathlib import Path
from cgl.core.path.object import PathObject
if not selected:
selected = bpy.context.selected_objects[0]
if "proxy" in selected.name:
name = selected.name.split("_")[0]
else:
if "." in selected.name:
name = selected.name.split(".")[0]
else:
name = selected.name
library = selected.instance_collection.library
library_path = bpy.path.abspath(library.filepath)
filename = Path(bpy.path.abspath(library_path)).__str__()
lumber_object = PathObject(filename)
lumber_object = lumber_object.copy(tree="source")
return lumber_object
[docs]
def switch_library(library, path_object):
library.filepath = path_object.get_path()
library.reload()
# logging.info(library.file_path + ' CHANGED')
[docs]
def switch_item_on_library(object, item, value):
import os
set_all_paths_relative(False)
lumber_object = selected_library(object)
library = object.instance_collection.library
if os.path.isdir(lumber_object.copy(tree="render", filename="").get_path()):
latest_resolution = lumber_object.copy(resolution=value)
switch_library(library, latest_resolution)
if __name__ == "__main__":
# create_menu_file('TomTest', 'Tom Test',
# r'F:\FSU-CMPA\COMPANIES\_config\cgl_tools\blender\menus\TomTest\TomTest.py')
# create_button_file('ButtonAaa', 'Button Aaa', 'TomTest')
add_buttons_to_menu("TomTest")