Last active
August 13, 2021 22:44
-
-
Save kawashirov/76041f63f2be37c398df9c89fec30e5c to your computer and use it in GitHub Desktop.
Khrushchyovka Bake Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import collections | |
import datetime | |
import os | |
import random | |
import re | |
import bpy | |
import mathutils | |
import kawa_scripts | |
from kawa_scripts.commons import ensure_op_finished, ensure_deselect_all_objects, get_mesh_safe, \ | |
find_all_child_objects, activate_object, dict_get_or_add, move_children_to_grandparent, apply_parent_inverse_matrix | |
from kawa_scripts.uv import repack_active_uv | |
from kawa_scripts.atlas_baker import BaseAtlasBaker | |
from kawa_scripts.instantiator import BaseInstantiator | |
from kawa_scripts.combiner import BaseMeshCombiner | |
from kawa_scripts.tex_size_finder import TexSizeFinder | |
import typing | |
if typing.TYPE_CHECKING: | |
from typing import * | |
from bpy.types import * | |
# | |
# bake2 = bpy.data.texts["bake2.py"].as_module() | |
# | |
log = kawa_scripts.log | |
log.info("bake2.py is loading...") | |
import subprocess | |
# Хрщёвка нормлаьно печётся только в блендере 2.91+ | |
# В версиях ниже почему-то крашится cycles. | |
blender_is_valid = bpy.app.version[1] >= 91 | |
fast_mode = False | |
no_atlas_words = ('NoAtlas', 'NoUV', 'Universal') | |
class KhrushchyovkaTexSizeFinder(TexSizeFinder): | |
def should_count_node(self, node: 'ShaderNodeTexImage') -> bool: | |
return 'IgnoreSize' not in node.label | |
def should_count_image(self, image: 'Image') -> bool: | |
return image.colorspace_settings.name != 'Non-Color' | |
texsizefinder = KhrushchyovkaTexSizeFinder() | |
def find_originals() -> 'Set[Object]': | |
scene = bpy.data.scenes['Raw'] | |
raw_root = bpy.data.objects['Raw'] # Raw | |
originals = find_all_child_objects(raw_root, set(scene.objects.values())) | |
# originals.add(raw_root) | |
return originals | |
def find_collider_materials() -> 'Set[Material]': | |
collider_materials = set(m for m in bpy.data.materials if is_collider_material(m)) | |
log.info('Found {} collider materials...'.format(len(collider_materials))) | |
return collider_materials | |
def is_collider_material(mat: 'Material') -> 'bool': | |
return mat is not None and mat.name.startswith('Clldr') | |
def get_collider_info(obj: 'bpy.types.Object'): | |
groups = dict() # type: Dict[Material, Set[int]] | |
if isinstance(obj.data, bpy.types.Mesh): | |
for idx, slot in enumerate(obj.material_slots): | |
dict_get_or_add(groups, slot.material if is_collider_material(slot.material) else ..., set).add(idx) | |
return groups | |
def is_collider_mesh(obj: 'bpy.types.Object'): | |
if not isinstance(obj.data, bpy.types.Mesh): | |
return False | |
for slot in obj.material_slots: | |
if not is_collider_material(slot.material): | |
return False | |
return True | |
def is_renderer_mesh(obj: 'bpy.types.Object'): | |
# Объект - меш, которая не содержит коллайдер-материалов | |
if not isinstance(obj.data, bpy.types.Mesh): | |
return False | |
for slot in obj.material_slots: | |
if is_collider_material(slot.material): | |
return False | |
return True | |
def remove_collider_uvs_on_working(): | |
working_scene = get_working_scene() | |
for obj in working_scene.objects: | |
if not is_collider_mesh(obj): | |
continue | |
mesh = obj.data # type: Mesh | |
while len(mesh.uv_layers) > 0: | |
mesh.uv_layers.remove(mesh.uv_layers[0]) | |
def get_working_scene(): | |
return bpy.data.scenes['Working'] | |
def _clean_working(): | |
objs = list(get_working_scene().objects) | |
log.info('Removing everything ({}) from working scene...'.format(len(objs))) | |
for obj in objs: | |
bpy.data.objects.remove(obj) | |
log.info('Removed everything ({}) from working scene.'.format(len(objs))) | |
def _remove_editor_only_from_working(): | |
log.info('Removing "editor-only" objects...') | |
working_scene = get_working_scene() | |
objects = list(working_scene.objects) # type: List[Object] | |
to_remove = set() | |
for obj in objects: | |
if not obj.name.startswith('_'): | |
continue | |
to_remove.update(find_all_child_objects(obj, where=objects)) | |
to_remove.add(obj) | |
for obj in to_remove: | |
working_scene.collection.objects.unlink(obj) | |
if len(obj.users_scene) == 0: | |
bpy.data.objects.remove(obj) | |
log.info('Removed {} "editor-only" objects.'.format(len(to_remove))) | |
def _fix_matrices_in_working(): | |
log.info('Fixing transform matrices...') | |
count = 0 | |
for obj in get_working_scene().objects: | |
if apply_parent_inverse_matrix(obj): | |
count += 1 | |
log.info('Fixed {} transform matrices.'.format(count)) | |
def _fix_collider_objects_in_working(): | |
log.info('Fixing collider objects...') | |
count = 0 | |
for obj in get_working_scene().objects: | |
if not is_collider_mesh(obj): | |
continue | |
ensure_deselect_all_objects() | |
activate_object(obj) | |
mesh = obj.data # type: Mesh | |
while len(mesh.uv_layers) > 0: | |
mesh.uv_layers.remove(mesh.uv_layers[0]) | |
mesh.use_auto_smooth = False | |
bpy.ops.object.shade_flat() | |
bpy.ops.mesh.customdata_custom_splitnormals_clear() | |
count += 1 | |
log.info('Fixed {} collider objects.'.format(count)) | |
def _remove_unused_materials_slots_in_working(): | |
ensure_deselect_all_objects() | |
for obj in get_working_scene().objects: | |
if not isinstance(obj.data, bpy.types.Mesh): | |
continue | |
activate_object(obj) | |
bpy.ops.object.material_slot_remove_unused() # Может быть не FINISHED | |
ensure_deselect_all_objects() | |
def _split_colliders_by_materials_in_working(): | |
# Все коллайдеры, на которых более 1 материала разбиваются по материалам | |
working_scene = get_working_scene() | |
ensure_deselect_all_objects() | |
for cobj in working_scene.objects: | |
if is_collider_mesh(cobj): | |
activate_object(cobj) | |
to_separate_mats = len(bpy.context.selected_objects) | |
log.info('Separating {} collider objects by materials...'.format(to_separate_mats)) | |
ensure_op_finished(bpy.ops.mesh.separate(type='MATERIAL'), name='bpy.ops.mesh.separate') | |
log.info('Separated {} -> {} collider objects by materials.'.format(to_separate_mats, len(bpy.context.selected_objects))) | |
def _get_material_lightmap_scale(mat: 'Material'): | |
lightmap_scale = mat.get('lightmap_scale', 1.0) | |
if mat.blend_method == 'CLIP': | |
lightmap_scale *= 0.5 | |
if mat.blend_method == 'BLEND': | |
lightmap_scale *= 0.2 | |
return lightmap_scale | |
def _repack_lightmap_uv(obj): | |
mesh = obj.data # type: Mesh | |
if not isinstance(mesh, bpy.types.Mesh): | |
return | |
has_uv1 = False | |
for idx in range(len(mesh.uv_layers)): | |
if mesh.uv_layers[idx].name == 'UV1': | |
mesh.uv_layers.active_index = idx | |
has_uv1 = True | |
if not has_uv1: | |
return | |
repack_active_uv(obj, get_scale=_get_material_lightmap_scale, margin=0.1, rotate=True) | |
def _combine_meshes_in_working(): | |
working_scene = get_working_scene() | |
ensure_deselect_all_objects() | |
no_cast_words = ('NoCast', 'Graffiti', 'LowFade') | |
no_group_words = ('ZeroLM', 'NoCmb', 'LowFade') | |
group_pattern = re.compile(r'Group:(\w+)', re.IGNORECASE) | |
def explicit_group(name: 'str'): | |
# Явное указание а группу в имени объекта | |
groups = group_pattern.findall(name) | |
if len(groups) > 1: | |
log.warning('{} {}'.format(child_name, groups)) | |
if len(groups) > 0: | |
return groups[0] if groups[0] != 'False' else False | |
class KhrushchyovkaCombiner(BaseMeshCombiner): | |
def group_child(self, root_name: 'str', child_name: 'str') -> 'Union[None, str, bool]': | |
group = explicit_group(child_name) | |
if group is not None: | |
return group | |
# Автовыбор группы по признакам | |
if any(w in child_name for w in no_group_words): | |
return False | |
child = bpy.data.objects[child_name] | |
for slot in child.material_slots: | |
if any(w in slot.material.name for w in no_group_words): | |
return False | |
for slot in child.material_slots: | |
if is_collider_material(slot.material): | |
return slot.material.name | |
# Рендерные объекты | |
if any(w in child_name for w in no_cast_words): | |
return 'NoCast' | |
# См. материалы | |
for slot in child.material_slots: | |
# if 'Emit' in slot.material.name: | |
# return 'Emit' # материалы, с повышенной светимостью. | |
# Больше не используем Emit т.к. Bakery пашол нахуй. | |
if slot.material.blend_method == 'BLEND': | |
# Альфа-бленд объекты не объединяются, т.к. иначе будет обсёр с Z сортиовкой | |
return False | |
# Прочие типы объектов не кастуют тени | |
if slot.material.blend_method != 'OPAQUE': | |
return 'NoCast' | |
if any(w in slot.material.name for w in no_cast_words): | |
return 'NoCast' | |
return None # default | |
def before_join(self, root_name: 'str', join_to_name: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'): | |
join_to = bpy.data.objects[join_to_name] | |
mesh = join_to.data # type: Mesh | |
mesh.use_auto_smooth = True | |
mesh.create_normals_split() | |
if not mesh.has_custom_normals: | |
ensure_deselect_all_objects() | |
activate_object(join_to) | |
bpy.ops.mesh.customdata_custom_splitnormals_add() | |
def after_join(self, root_name: 'str', join_to_name: 'str', group_name: 'Optional[str]'): | |
join_to = bpy.data.objects[join_to_name] | |
# _repack_lightmap_uv(join_to) | |
pass | |
combiner = KhrushchyovkaCombiner() | |
combiner.force_mesh_root = True | |
for obj in working_scene.objects: | |
if 'Cmb' in obj.name and not any(w in obj.name for w in no_group_words): | |
combiner.roots_names.add(obj.name) | |
combiner.combine_meshes() | |
for obj_name in combiner.replaced_objects: | |
obj = bpy.data.objects.get(obj_name) | |
if obj is None: | |
continue | |
working_scene.collection.objects.unlink(obj) | |
if len(obj.users_scene) == 0: | |
bpy.data.objects.remove(obj) | |
def _repack_lightmaps_uvs(): | |
for obj in get_working_scene().objects: | |
_repack_lightmap_uv(obj) | |
def _cleanup_empties(): | |
log.info('Removing unnecessary empty objects...') | |
working_scene = get_working_scene() | |
counter = 0 | |
objects = set(working_scene.objects) # type: Set[Object] | |
while len(objects) > 0: | |
obj = objects.pop() | |
if obj.parent is None or obj.type != 'EMPTY': | |
continue | |
if 'Keep' in obj.name or 'Cmb' in obj.name: | |
continue | |
# К удалению | |
# log.info('Removing empty object: {}'.format(repr(obj.name))) | |
move_children_to_grandparent(obj) | |
working_scene.collection.objects.unlink(obj) | |
if len(obj.users_scene) == 0: | |
bpy.data.objects.remove(obj) | |
counter += 1 | |
log.info('Removed {} empty objects.'.format(counter)) | |
def _unify_uv_names_working(): | |
for obj in get_working_scene().objects: | |
if is_renderer_mesh(obj): | |
_unify_uv_names_object(obj) | |
def _unify_uv_names_object(obj: 'Object'): | |
# TODO | |
mesh = obj.data # type: Mesh | |
len_uv = len(mesh.uv_layers) | |
if len_uv == 0: | |
# Слоёв нет, создаем два пустых | |
log.warning('There is no UV layers in {}, {}.', repr(obj), repr(mesh)) | |
mesh.uv_layers.new(name='UV0') | |
mesh.uv_layers.new(name='UV1') | |
return | |
if len_uv == 1: | |
mesh.uv_layers[0].name = 'UV0' | |
mesh.uv_layers[0].active = True | |
mesh.uv_layers.new(name='UV1') | |
return | |
has_uv0 = 'UV0' in mesh.uv_layers | |
has_uv1 = 'UV1' in mesh.uv_layers | |
if has_uv0 and has_uv1 and len_uv == 2: | |
return | |
log.warning('Bad naming of {} UV layers in {}, {}, can not choose UV0 and UV1 for sure:'.format( | |
len(mesh.uv_layers), repr(obj), repr(mesh))) | |
for idx in range(len(mesh.uv_layers)): | |
log.warning('\t- {}: {}'.format(idx, repr(mesh.uv_layers[idx]))) | |
if not has_uv0 and not has_uv1: | |
# Существует 2 или юольше слоя с нестандартным именем, переименовываем первые | |
mesh.uv_layers[0].name = 'UV0' | |
mesh.uv_layers[1].name = 'UV1' | |
return | |
if has_uv0 and not has_uv1: | |
for idx in range(len(mesh.uv_layers)): | |
if mesh.uv_layers[idx].name == 'UV0': | |
continue | |
# Первый слой, имеющий имя не UV0 получит имя UV1 | |
mesh.uv_layers[idx].name = 'UV1' | |
return | |
if not has_uv0 and has_uv1: | |
for idx in range(len(mesh.uv_layers)): | |
if mesh.uv_layers[idx].name == 'UV1': | |
continue | |
# Первый слой, имеющий имя не UV1 получит имя UV0 | |
mesh.uv_layers[idx].name = 'UV0' | |
return | |
pass | |
def randomize_materials(): | |
for mat in bpy.data.materials: | |
if mat.node_tree is None or mat.node_tree.nodes is None: | |
continue | |
for node in mat.node_tree.nodes: | |
for input in node.inputs: | |
if input.name == 'RandomHue': | |
random.seed(mat.name + node.name) | |
input.default_value = random.random() | |
def mark_family_photos(objs: 'Iterable[Object]'): | |
reference_sizes = (15, 20, 30, 40) | |
mats = dict() | |
for obj in objs: | |
if not isinstance(obj.data, bpy.types.Mesh): | |
continue | |
for slot in obj.material_slots: | |
if slot is None or slot.material is None: | |
continue | |
if 'FamilyPhoto' not in slot.material.name: | |
continue | |
l = mats.get(slot.material) | |
if l is None: | |
l = set() | |
mats[slot.material] = l | |
l.add(obj.data) | |
for mat, meshes in mats.items(): | |
size = (-1, -1) | |
for mesh in meshes: | |
for w_size in reference_sizes: | |
for h_size in reference_sizes: | |
s = str(w_size) + 'x' + str(h_size) | |
if s in mesh.name and size[0] <= w_size and size[1] <= h_size: | |
size = (w_size, h_size) | |
if size[0] > 0 and size[1] > 0: | |
# log.info('FamilyPhotoSize: %s - %s', mat.name, repr(size)) | |
mat['FamilyPhotoSize'] = size | |
def set_cubic_interpolation(): | |
for mat in bpy.data.materials: | |
if mat.node_tree is None or mat.node_tree.nodes is None: | |
continue | |
for node in mat.node_tree.nodes: | |
if not isinstance(node, bpy.types.ShaderNodeTexImage): | |
continue | |
if node.interpolation == 'Cubic': | |
continue | |
log.info("{}.interpolation: {} -> 'Cubic'", repr(node), repr(node.interpolation)) | |
node.interpolation = 'Cubic' | |
def fix_broken_transforms(): | |
ensure_deselect_all_objects() | |
count = 0 | |
for obj in bpy.data.scenes['Raw'].objects: | |
if not apply_parent_inverse_matrix(obj): | |
continue | |
msg = 'Object {0} had broken transform, fixed!'.format(repr(obj.name)) | |
log.warning(msg) | |
print(msg) | |
activate_object(obj) | |
count += 1 | |
msg = '{0} broken transforms detected and fixed.'.format(count) | |
log.info(msg) | |
print(msg) | |
def find_wrong_uvs(): | |
originals = find_originals() | |
ensure_deselect_all_objects() | |
for obj in originals: | |
mesh = obj.data | |
if not is_renderer_mesh(obj): | |
continue | |
uvs = mesh.uv_layers.keys() | |
if len(uvs) == 0 or len(uvs) == 1: | |
continue # Норм ситуация | |
if len(uvs) != 2 or uvs[0] != 'UV0' or uvs[1] != 'UV1': | |
obj.hide_set(False) | |
obj.select_set(True) | |
log.warning('{} uvs: {}'.format(repr(obj), repr(uvs))) | |
def hide_colliders(): | |
for obj in bpy.context.scene.objects: | |
if is_collider_mesh(obj): | |
obj.hide_set(True) | |
def report_largest_images(): | |
raw = bpy.data.objects['Raw'] | |
materials = set() | |
for obj in find_all_child_objects(raw): | |
if not isinstance(obj.data, bpy.types.Mesh): | |
continue | |
for slot in obj.material_slots: | |
if slot is not None and slot.material is not None: | |
materials.add(slot.material) | |
log.info('materials {}'.format(len(materials))) | |
images_d = dict() # type: Dict[bpy.types.Image, List[bpy.types.Material]] | |
for mat in materials: | |
if any(w in mat.name for w in no_atlas_words): | |
continue | |
for node in mat.node_tree.nodes: | |
if not isinstance(node, bpy.types.ShaderNodeTexImage): | |
continue | |
if not isinstance(node.image, bpy.types.Image): | |
continue | |
l = images_d.get(node.image) | |
if l is None: | |
l = list() | |
images_d[node.image] = l | |
l.append(mat) | |
log.info('images {}'.format(len(images_d))) | |
images_l = list(images_d.keys()) | |
images_l.sort(key=lambda x: x.size[0] * x.size[1], reverse=True) | |
log.info('Top 20 largest images:') | |
for im in images_l[:20]: | |
log.info('\t{} x {} - {} - {}'.format(im.size[0], im.size[1], repr(im.name), repr(im.filepath))) | |
for mat in images_d[im]: | |
log.info('\t\t{}'.format(repr(mat.name))) | |
def test_tex_size(mat: 'Material'): | |
return texsizefinder.mat_size(mat) | |
def make_working_hierarchy(): | |
original_scene = bpy.data.scenes['Raw'] | |
working_scene = get_working_scene() | |
_clean_working() | |
originals = find_originals() | |
mark_family_photos(originals) | |
class KInstantiator(BaseInstantiator): | |
def rename_copy(self, obj: 'Object', original_name: 'str') -> 'str': | |
n = original_name if original_name is not None else obj | |
nn = n | |
if n.startswith('_'): | |
nn = nn[1:] | |
nn = 'Bkd-' + nn | |
if n.startswith('_'): | |
nn = '_' + nn | |
return nn | |
def rename_object_from_collection(self, | |
parent_obj: 'Object', _1: 'str', inst_obj: 'Object', inst_obj_orig_name: 'str', collection: 'Collection' | |
) -> 'str': | |
inst_name = inst_obj_orig_name if inst_obj_orig_name is not None else inst_obj | |
if inst_name.startswith(collection.name): | |
inst_name = inst_name[len(collection.name):] | |
n = parent_obj.name + '-' + inst_name | |
return n | |
i = KInstantiator() | |
i.original_scene = original_scene | |
i.working_scene = working_scene | |
i.originals = originals | |
i.run() | |
_remove_editor_only_from_working() | |
_fix_matrices_in_working() | |
_fix_collider_objects_in_working() | |
_remove_unused_materials_slots_in_working() | |
_split_colliders_by_materials_in_working() | |
_unify_uv_names_working() | |
_combine_meshes_in_working() | |
_repack_lightmaps_uvs() | |
_cleanup_empties() | |
log.info('Done! {}'.format(len(working_scene.objects))) | |
def get_target_material(obj: 'bpy.types.Object', mat: 'bpy.types.Material'): | |
bk_solid = bpy.data.materials['Baked-Khrushchyovka-Soild'] | |
bk_lowfade = bpy.data.materials['Baked-Khrushchyovka-LowFade'] | |
bk_cutout = bpy.data.materials['Baked-Khrushchyovka-Cutout'] | |
if mat is None or mat.node_tree is None or mat.node_tree.nodes is None: | |
return False | |
if mat.name.startswith('_'): | |
return False | |
if is_collider_material(mat): | |
return False | |
if any(w in mat.name for w in no_atlas_words): | |
return False | |
if 'LowFade' in mat.name or 'Graffiti' in mat.name: | |
return bk_lowfade | |
if mat.blend_method == 'BLEND': | |
return bk_lowfade | |
if mat.blend_method == 'CLIP': | |
return bk_cutout | |
return bk_solid | |
def make_atlas(): | |
stamp = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') | |
save_dir = bpy.path.abspath('//bake_' + stamp) | |
os.mkdir(save_dir) | |
log.info("Saving results to {}...".format(repr(save_dir))) | |
working_scene = get_working_scene() | |
objects = set() | |
for obj in working_scene.objects: | |
if not isinstance(obj.data, bpy.types.Mesh): | |
continue | |
for slot in obj.material_slots: | |
if slot is None or slot.material is None: | |
continue | |
if get_target_material(obj, slot.material) is not None: | |
objects.add(obj) | |
custom_scales = dict() # type: Dict[bpy.types.Material, float] | |
def rescale_mat(name: 'str', value: 'float'): | |
mat = bpy.data.materials.get(name) | |
if mat is None: | |
log.warning("There is no material {}".format(repr(name))) | |
scale = custom_scales.get(mat, 1.0) | |
custom_scales[mat] = value * scale | |
for mat in bpy.data.materials: | |
if 'carpet' in mat.name.lower(): | |
rescale_mat(mat.name, 1.2) | |
if 'graffiti' in mat.name.lower(): | |
rescale_mat(mat.name, 1.5) | |
class KhrushchyovkaAtlasBaker(BaseAtlasBaker): | |
def get_material_size(self, src_mat: 'Material') -> 'Optional[Tuple[float, float]]': | |
size = None | |
photo_size = src_mat.get('FamilyPhotoSize') | |
if photo_size is not None and len(photo_size) == 2 and photo_size[0] > 0 and photo_size[1] > 0: | |
# log.info('FamilyPhotoSize: %s - %s', src_mat, photo_size) | |
photo_scale = 20 | |
size = photo_size[0] * photo_scale, photo_size[1] * photo_scale | |
if size is None: | |
size = texsizefinder.mat_size(src_mat) | |
if size is None: | |
size = (64, 64) | |
scale = custom_scales.get(src_mat, 1.0) * src_mat.get('atlas_scale', 1.0) | |
if scale <= 0: | |
log.warning("Material {} have invalid custom scale: {}".format(src_mat.name, scale)) | |
scale = 1.0 | |
size = (size[0] * scale, size[1] * scale) | |
return size | |
def get_target_material(self, origin: 'Object', src_mat: 'Material') -> 'Material': | |
return get_target_material(origin, src_mat) | |
def get_target_image(self, bake_type: str) -> 'Optional[Image]': | |
# return None | |
# if fast_mode and bake_type != 'DIFFUSE': | |
# return None | |
raw_type = bake_type in ('METALLIC', 'ROUGHNESS', 'NORMAL', 'ALPHA') | |
image = bpy.data.images['KhrushchyovkaAtlas-' + bake_type] | |
if image is not None and blender_is_valid: | |
size = 2048 | |
if bake_type in ('DIFFUSE', 'ALPHA'): | |
size *= 2 | |
if fast_mode: | |
size /= 4 | |
# image.scale(size, size) | |
image.generated_width = size | |
image.generated_height = size | |
image.generated_color = (0.5, 0.5, 1, 1) if bake_type == 'NORMAL' else (0, 0, 0, 1) | |
image.colorspace_settings.name = 'Non-Color' if raw_type else 'sRGB' | |
image.use_view_as_render = True | |
image.use_generated_float = True | |
image.generated_type = 'COLOR_GRID' if bake_type == 'DIFFUSE' else 'BLANK' | |
image.source = 'GENERATED' | |
return image | |
def get_uv_name(self, _obj: 'Object', _mat: 'Material') -> 'str': | |
return 'UV0' | |
def get_island_mode(self, _origin: 'Object', _mat: 'Material') -> 'str': | |
return 'POLYGON' # if not fast_mode else 'OBJECT' | |
def get_epsilon(self, _obj: 'Object', _mat: 'Material') -> 'Optional[float]': | |
return 16 if fast_mode else 1 | |
def before_bake(self, bake_type: str, target_image: 'Image'): | |
if not blender_is_valid: | |
raise RuntimeError(bpy.app.version_string) | |
# raise RuntimeError("SRAKA!") | |
# if bake_type == 'DIFFUSE': | |
# raise RuntimeError("SRAKA!") | |
pass | |
def after_bake(self, bake_type: str, target_image: 'Image'): | |
save_ext = '.png' if bake_type != 'EMIT' else '.exr' | |
file_format = 'PNG' if bake_type != 'EMIT' else 'OPEN_EXR' | |
save_path = os.path.join(save_dir, target_image.name + save_ext) | |
log.info("Saving Texture={} type={} as {}...".format(repr(target_image.name), repr(bake_type), repr(save_path))) | |
vec3_type = bake_type in ('DIFFUSE', 'EMIT', 'NORMAL') | |
raw_type = bake_type in ('METALLIC', 'ROUGHNESS', 'NORMAL', 'ALPHA') | |
working_scene.render.image_settings.quality = 100 | |
working_scene.render.image_settings.compression = 100 | |
working_scene.render.image_settings.color_mode = 'RGB' if vec3_type else 'BW' | |
working_scene.render.image_settings.file_format = file_format | |
working_scene.render.image_settings.color_depth = '16' | |
working_scene.view_settings.look = 'None' | |
working_scene.view_settings.exposure = 0 | |
working_scene.view_settings.gamma = 1 | |
working_scene.view_settings.view_transform = 'Standard' # 'Raw' if raw_type else | |
target_image.file_format = file_format | |
target_image.save_render(save_path) | |
log.info("Saved Texture={} type={} as {}...".format(repr(target_image.name), repr(bake_type), repr(save_path))) | |
target_image.filepath_raw = save_path | |
target_image.filepath = save_path # trigger reloading | |
target_image.source = 'FILE' | |
# target_image.save() | |
log.info("Reloaded saved Texture={} type={} from {}...".format(target_image.name, bake_type, save_path)) | |
ab = KhrushchyovkaAtlasBaker() | |
ab.objects = objects | |
ab.padding = 16.0 | |
ab.bake_atlas() | |
working_scene.render.image_settings.color_mode = 'RGB' | |
working_scene.view_settings.look = 'None' | |
working_scene.view_settings.exposure = 0 | |
working_scene.view_settings.gamma = 1 | |
working_scene.view_settings.view_transform = 'Standard' | |
working_scene.sequencer_colorspace_settings.name = 'sRGB' | |
working_scene.display_settings.display_device = 'sRGB' | |
save_blend = os.path.join(save_dir, "baked.blend") | |
bpy.ops.wm.save_as_mainfile(filepath=save_blend, check_existing=False, copy=True, compress=True) | |
log.info("Saved to {}".format(save_blend)) | |
path_magic = bpy.path.abspath(r'//ImageMagick\magick.exe') | |
path_d = bpy.data.images['KhrushchyovkaAtlas-DIFFUSE'].filepath | |
path_a = bpy.data.images['KhrushchyovkaAtlas-ALPHA'].filepath | |
path_m = bpy.data.images['KhrushchyovkaAtlas-METALLIC'].filepath | |
path_r = bpy.data.images['KhrushchyovkaAtlas-ROUGHNESS'].filepath | |
path_dpa = os.path.join(os.path.dirname(path_d), 'KhrushchyovkaAtlas-D+A.png') | |
path_mps = os.path.join(os.path.dirname(path_m), 'KhrushchyovkaAtlas-M+S.png') | |
args_dpa = [path_magic, 'convert', path_d, path_a, '-alpha', 'Off', | |
'-compose', 'CopyOpacity', '-composite', path_dpa] | |
args_mps = [path_magic, 'convert', path_m, path_r, '-alpha', 'Off', | |
'-compose', 'CopyOpacity', '-composite', '-channel', 'a', '-negate', path_mps] | |
def log_process(process, prefix): | |
while True: | |
line = process.stdout.readline() | |
if len(line) == 0 and process.poll() is not None: | |
break | |
log.warning('{}: {}'.format(prefix, line)) | |
log.info('Running ImageMagick...') | |
log.info('D+A: {}'.format(repr(args_dpa))) | |
log.info('M+S: {}'.format(repr(args_mps))) | |
with subprocess.Popen(args_dpa, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process_dpa, \ | |
subprocess.Popen(args_mps, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process_mps: | |
log_process(process_dpa, 'Blending D+A') | |
log_process(process_mps, 'Blending M+S') | |
log.info('ImageMagick done: D+A={}, M+S={}'.format(process_dpa.returncode, process_mps.returncode)) | |
log.info("Done.") | |
def make_all(): | |
make_working_hierarchy() | |
make_atlas() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment