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()
Beispiel #5
0
 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))