Skip to content

Instantly share code, notes, and snippets.

@kawashirov
Last active August 13, 2021 22:44
Show Gist options
  • Save kawashirov/76041f63f2be37c398df9c89fec30e5c to your computer and use it in GitHub Desktop.
Save kawashirov/76041f63f2be37c398df9c89fec30e5c to your computer and use it in GitHub Desktop.
Khrushchyovka Bake Script
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