# ***** BEGIN GPL LICENSE BLOCK ***** # # Copyright (C) 2021-2024 Robossembler LLC # # Created by Ilia Kurochkin (brothermechanic) # contact: brothermechanic@yandex.com # # This file is part of Robossembler Framework # project repo: https://gitlab.com/robossembler/framework # # Robossembler Framework is free software; # you can redistribute it and/or modify # it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 3 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # ***** END GPL LICENSE BLOCK ***** # # coding: utf-8 ''' DESCRIPTION. Generate render asset from assembly tree and CG libs database. ''' __version__ = '1.0' import logging import json import os import random import shutil import bpy from mathutils import Vector, Matrix from .utils.collection_tools import remove_collections_with_objects from .utils.cleanup_orphan_data import cleanup_orphan_data from .utils.object_relations import unparenting, parenting from .utils.object_transforms import round_transforms logger = logging.getLogger(__name__) def recursive_layer_collection(layer_coll, coll_name): found = None if layer_coll.name == coll_name: return layer_coll for layer in layer_coll.children: found = recursive_layer_collection(layer, coll_name) if found: return found return False def assembly_builder(item, libs_data, libs_data_dir, collection=None, parent=None): ''' ''' if not collection: collection = bpy.context.scene.collection item_obj = None if item['type'] == 'LOCATOR': item_obj = bpy.data.objects.new(item['name'], None) item_obj.empty_display_type = 'CUBE' item_obj.empty_display_size = 0.0005 collection.objects.link(item_obj) elif item['type'] == 'LCS': item_obj = bpy.data.objects.new(item['name'], None) item_obj.empty_display_type = 'ARROWS' item_obj.empty_display_size = round(random.uniform(0.05, 0.15), 3) item_obj.show_in_front = True collection.objects.link(item_obj) elif item['type'] == 'PART': item_data = [ data for data in libs_data if data['type'] == 'OBJECT' if data['name'] == item['base_name']] if not item_data: logger.error('No %s in database!', item['base_name']) return False # if there is local base_named object in scene -> rename it local_obj = None # for clones if item['base_name'] != item['name']: local_obj = [ loc for loc in bpy.data.objects if loc.name == item['base_name'] if not loc.library] if local_obj: local_obj[0].name += '_loc' item_file_path = os.path.join(libs_data_dir, item_data[0]['path']) # TODO already linked bpy.ops.wm.link( filepath=item_file_path, directory=os.path.join(item_file_path, 'Collection'), filename=item['base_name'], relative_path=True, do_reuse_local_id=True, active_collection=True, ) item_obj = bpy.data.objects[item['base_name']] # for clones if item['base_name'] != item['name']: item_obj.name = item['name'] # rename local back if local_obj: local_obj[0].name = item['base_name'] item_obj.empty_display_type = 'PLAIN_AXES' item_obj.empty_display_size = 0.0005 else: logger.error('Unknown object type %s of %s', item['type'], item['name']) return False item_obj.location = Vector(item['pose'][0]['loc_xyz']) * 0.001 item_obj.rotation_mode = 'QUATERNION' item_obj.rotation_quaternion = [ item['pose'][1]['rot_xyzw'][3]] + item['pose'][1]['rot_xyzw'][:3] if item.get('attributes'): for attr in item['attributes']: item_obj[list(attr)[0]] = attr[list(attr)[0]] item_obj.parent = parent if item.get('children'): for child_item in item.get('children'): assembly_builder( child_item, libs_data, libs_data_dir, collection, parent=item_obj) return True def assembly_rebuilder(): ''' Restructure assembling hierarchy. ''' default_origin = [ lcs for lcs in bpy.data.objects if lcs.type == 'EMPTY' if lcs.get('Robossembler_SocketFlow') == 'inlet' if lcs.get('Robossembler_DefaultOrigin')] if not default_origin: raise Exception('Default Origin not found!') if len(default_origin) > 1: raise Exception('Several Default Origins do not supported!') default_origin = default_origin[0] root_locator = [ obj for obj in bpy.data.objects if not obj.parent if not obj.library] for i in root_locator: print(i.name) if len(root_locator) > 1: raise Exception('Render asset should consist of only one hierarchy!') root_locator = root_locator[0] # retree_by lcs for lcs in bpy.data.objects: if lcs.type == 'EMPTY': if lcs.get('Robossembler_SocketFlow'): unparenting(lcs) round_transforms(lcs) parenting(default_origin, root_locator) for lcs in bpy.data.objects: if lcs.type == 'EMPTY': if lcs.get('Robossembler_SocketFlow'): if lcs != default_origin: parenting(default_origin, lcs) default_origin.matrix_world = Matrix() logger.info('Restructuring assembling hierarchy finished!') return default_origin def build_render_assets(project_dir): ''' ''' assets_data = [] libs_data_path = os.path.join(project_dir, 'libs.json') if not os.path.exists(libs_data_path): raise Exception('No libs database found! Check %s directory' % project_dir) with open(libs_data_path, encoding='utf-8') as data: libs_data = json.load(data) trees_path = os.path.join(project_dir, 'trees.json') if not os.path.exists(trees_path): raise Exception('No trees database found! Check %s directory' % project_dir) with open(trees_path, encoding='utf-8') as data: tree_item_list = json.load(data) # render assets dir render_assets_dir = os.path.join(project_dir, 'assets', 'render') if os.path.exists(render_assets_dir): shutil.rmtree(render_assets_dir) os.makedirs(render_assets_dir) else: os.makedirs(render_assets_dir) for tree_item in tree_item_list: # start from stratch bpy.ops.wm.read_homefile() remove_collections_with_objects() cleanup_orphan_data() # create redner collection render_collection = bpy.data.collections.new(tree_item['name']) bpy.context.scene.collection.children.link(render_collection) active_collection = recursive_layer_collection( bpy.context.view_layer.layer_collection, render_collection.name) bpy.context.view_layer.active_layer_collection = active_collection # build original hierarchy assembly_builder(tree_item, libs_data, project_dir, render_collection) # rebuild to LCS hierarchy assembly_rebuilder() # mark as asset render_collection.asset_mark() # TODO collection thumbnail blend_path = os.path.join(render_assets_dir, tree_item['name'] + '.blend') bpy.ops.wm.save_as_mainfile(filepath=blend_path) logger.info('Render asset %s generated!', tree_item['name']) assets_data.append( { 'type': 'RENDER', 'name': tree_item['name'], 'path': os.path.relpath(blend_path, project_dir), 'thumbnail': '' } ) logger.info('%s Render Assets was generated!', len(tree_item_list)) # write db file assets_data_path = os.path.join(project_dir, 'assets.json') with open(assets_data_path, 'w', encoding='utf-8') as assets_data_file: json.dump(assets_data, assets_data_file, ensure_ascii=False, indent=4) logger.info('Database saved successfully to %s!', assets_data_path) return render_assets_dir