def _create_transforms_from_islands(self): # Преобразует острава в боксы в формате mathutils.geometry.box_pack_2d for mat, builder in self._islands.items(): for bbox in builder.bboxes: if not bbox.is_valid(): raise ValueError("box is invalid: ", bbox, mat, builder, builder.bboxes) origin_w, origin_h = self._matsizes[mat] t = UVTransform() t.material = mat # две точки -> одна точка + размер x, w = bbox.mn.x, (bbox.mx.x - bbox.mn.x) y, h = bbox.mn.y, (bbox.mx.y - bbox.mn.y) # t.origin_tex = (x, y, w, h) t.origin_norm = _Vector((x / origin_w, y / origin_h, w / origin_w, h / origin_h)) # добавляем отступы xp, yp = x - self.padding, y - self.padding, wp, hp = w + 2 * self.padding, h + 2 * self.padding # meta.padded_tex = (xp, yp, wp, hp) t.padded_norm = _Vector((xp / origin_w, yp / origin_h, wp / origin_w, hp / origin_h)) # Координаты для упаковки # Т.к. box_pack_2d пытается запаковать в квадрат, а у нас может быть текстура любой формы, # то необходимо скорректировать пропорции xb, yb = xp / self.target_size[0], yp / self.target_size[1] wb, hb = wp / self.target_size[0], hp / self.target_size[1] t.packed_norm = _Vector((xb, yb, wb, hb)) metas = _commons.dict_get_or_add(self._transforms, mat, list) metas.append(t)
def prepare_and_get_alpha_shader_node(mat: 'Material'): # Находет ShaderNode с именем KAWA_BAKE_ALPHA # Если такого нет, то ищет с меткой KAWA_BAKE_ALPHA try: nodes = mat.node_tree.nodes sh_default = prepare_and_get_default_shader_node(mat) sh_alpha = get_shader_node_by_label(mat, KAWA_BAKE_ALPHA, _bpy.types.ShaderNodeEmission) if sh_alpha is None: sh_alpha = nodes.new('ShaderNodeEmission') sh_alpha.name = KAWA_BAKE_ALPHA sh_alpha.label = KAWA_BAKE_ALPHA sh_alpha_in = sh_alpha.inputs['Color'] if sh_default is not None: # Если есть default шейдер, то размещаем новый над ним и пытаемся своровать 'Alpha' sh_alpha.location = sh_default.location + _Vector((0, 200)) n_alpha = get_node_input_safe(sh_default, 'Alpha') if n_alpha is not None and get_socket_input_safe(sh_alpha_in) is None: # Если ничего не забинджено в ALPHA шедер, то подрубаем из DEFAULT mat.node_tree.links.new(n_alpha, sh_alpha.inputs['Color']) return sh_alpha except Exception as exc: raise _commons.MaterialConfigurationError(mat, "Can not prepare ALPHA shader node") from exc sh_default = prepare_and_get_default_shader_node(mat) if sh_default is None: raise RuntimeError(mat, "There is no DEFAULT shader node, can not switch to ALPHA.") sh_alpha = prepare_and_get_alpha_shader_node(mat) if sh_alpha is None: raise RuntimeError(mat, "There is no ALPHA shader node, can not switch to ALPHA.") while len(output_s.links) > 0: mat.node_tree.links.remove(output_s.links[0]) mat.node_tree.links.new(sh_alpha.outputs['Emission'], output_s)
def _pack_islands(self): # Несколько итераций перепаковки # TODO вернуть систему с раундами boxes = list() # type: List[List[Union[float, UVTransform]]] for metas in self._transforms.values(): for meta in metas: boxes.append([*meta.packed_norm, meta]) _log.info("Packing {0} islands...".format(len(boxes))) best = _int_maxsize rounds = 15 # TODO while rounds > 0: rounds -= 1 # Т.к. box_pack_2d псевдослучайный и может давать несколько результатов, # то итеративно отбираем лучшие _shuffle(boxes) pack_x, pack_y = _box_pack_2d(boxes) score = max(pack_x, pack_y) _log.info("Packing round: {0}, score: {1}...".format(rounds, score)) if score >= best: continue for box in boxes: box[4].packed_norm = _Vector(tuple(box[i] / score for i in range(4))) best = score if best == _int_maxsize: raise AssertionError()
def _apply_baked_materials_bmesh(self, obj: 'Object', mesh: 'Mesh', bm: 'BMesh'): # Через BMesh редактировать UV намного быстрее. # Прямой доступ к UV слоям через bpy.types.Mesh раза в 4 медленее. self._bmesh_loops_mem.clear() self._bmesh_loops_mem_hits = 0 for material_index in range(len(mesh.materials)): source_mat = mesh.materials[material_index] transforms = self._transforms.get(source_mat) if transforms is None: continue # Нет преобразований для данного материала # Дегенеративная геометрия вызывает проблемы, по этому нужен epsilon. # Зазор между боксами не менее epsilon материала, по этому возьмём половину. epsilon = self._get_epsilon_safe(obj, source_mat) src_size_x, src_size_y = self._matsizes[source_mat] epsilon_x, epsilon_y = epsilon / src_size_x / 2, epsilon / src_size_y / 2 target_mat = self._materials.get((obj, source_mat)) uv_name = self.get_uv_name(obj, source_mat) or 0 bm_uv_layer = bm.loops.layers.uv[uv_name] # type: BMLayerItem # Здесь мы, получается, обходм все фейсы по несколько раз (на каждый материал) # Но это лучше, чем проходить один раз, но каждый раз дёргать # dict.get(bm_face.material_index) и распаковывать метаданные _t3 = _perf_counter() bm.faces.ensure_lookup_table() for bm_face in bm.faces: if bm_face.material_index != material_index: continue # Среднее UV фейса. По идее можно брать любую точку для теста принадлежности, # но я не хочу проблем с пограничными случаями. # Нужно тестировать, скорее всего тут можно ускорить. mean_uv = _Vector((0, 0)) for bm_loop in bm_face.loops: mean_uv += bm_loop[bm_uv_layer].uv mean_uv /= len(bm_face.loops) transform = None # Поиск трансформа для данного полигона _t1 = _perf_counter() for t in transforms: # Должно работать без эпсилонов if t.is_match(mean_uv, epsilon_x=epsilon_x, epsilon_y=epsilon_y): transform = t break self._perf_find_transform += _perf_counter() - _t1 if transform is None: # Такая ситуация не должна случаться: # Если материал подлежал запеканию, то все участки должны были ранее покрыты трансформами. msg = 'No UV transform for Obj={0}, Mesh={1}, SMat={2}, Poly={3}, UV={4}, Transforms:' \ .format(repr(obj.name), repr(mesh.name), repr(source_mat.name), repr(bm_face), repr(mean_uv)) _log.error(msg) for transform in transforms: _log.error('\t- {}'.format(repr(transform))) raise AssertionError(msg, obj, source_mat, repr(bm_face), mean_uv, transforms) _t2 = _perf_counter() for bm_loop in bm_face.loops: if bm_loop.index in self._bmesh_loops_mem: # Что бы не применить трансформ дважды к одному loop # Хотя как я понял в bmesh они не переиспользуются # Нужно изучать, возможно можно убрать self._already_processed_loops вовсе self._bmesh_loops_mem_hits += 1 continue self._bmesh_loops_mem.add(bm_loop.index) vec2 = bm_loop[bm_uv_layer].uv vec2 = transform.apply(vec2) bm_loop[bm_uv_layer].uv = vec2 self._perf_apply_transform += _perf_counter() - _t2 self._perf_iter_polys += _perf_counter() - _t3 # Внимание! Меняем меш, хотя потом она будет перезаписана из bmesh. # Но это ОК, т.к. bmesh похуй на материалы, там хранятся только индексы. mesh.materials[material_index] = target_mat obj.material_slots[material_index].material = target_mat if _log.is_debug(): _log.info("BMesh loops hits for {} = {}".format(repr(obj.name), repr(self._bmesh_loops_mem_hits))) self._bmesh_loops_mem.clear()
def get_points(self) -> 'Sequence[Vector]': return self.mn, self.mx, _Vector((self.mn.x, self.mx.y)), _Vector( (self.mx.x, self.mn.y))