Files
open-wc/reference/blender-wow-studio-3.4-1.1.0_Experimental/io_scene_wmo/utils/misc.py
T

318 lines
8.5 KiB
Python

import bpy
import os
import sys
from mathutils import Vector
from collections import namedtuple
from ..pywowlib import WoWVersionManager
from ..pywowlib.archives.wow_filesystem import WoWFileData
from .. import PACKAGE_NAME
from ..ui.preferences import get_project_preferences
SequenceRecord = namedtuple('SequenceRecord', ['name', 'value', 'index'])
class Sequence(type):
def __new__(mcs, name, bases, dct):
dct['__fields__'] = list(dct.keys())[2:]
dct['_iter'] = 0
return super().__new__(mcs, name, bases, dct)
def __getitem__(self, item):
return getattr(self, self.__fields__[item])
def __iter__(self):
return self
def __next__(self):
if self._iter == len(self.__fields__):
self._iter = 0
raise StopIteration
item = SequenceRecord(self.__fields__[self._iter], getattr(self, self.__fields__[self._iter]), self._iter)
self._iter += 1
return item
def index(self, item):
return self.__fields__.index(item)
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance
def find_nearest_object(obj_, objects):
"""Get closest object to another object"""
dist = sys.float_info.max
result = None
for obj in objects:
obj_location_relative = obj.matrix_world.inverted() @ obj_.location
hit = obj.closest_point_on_mesh(obj_location_relative)
hit_dist = (obj_location_relative - hit[1]).length
if hit_dist < dist:
dist = hit_dist
result = obj
return result
def parse_bitfield(bitfield, last_flag=0x1000):
flags = set()
bit = 1
while bit <= last_flag:
if bitfield & bit:
flags.add(str(bit))
bit <<= 1
return flags
def construct_bitfield(flag_set):
bitfield = 0
for flag in flag_set:
bitfield |= int(flag)
return bitfield
def get_material_viewport_image(material):
""" Get viewport image assigned to a material """
for i in range(3):
try:
img = material.texture_slots[3 - i].texture.image
return img
except:
pass
return None
def load_game_data() -> WoWFileData:
WoWVersionManager().set_client_version(int(bpy.context.scene.wow_scene.version))
if not hasattr(bpy, 'wow_game_data'):
project_preferences = get_project_preferences()
bpy.wow_game_data = WoWFileData(project_preferences.wow_path, project_preferences.project_dir_path)
if not bpy.wow_game_data.files:
raise UserWarning("WoW game data is not loaded. Check settings.")
return bpy.wow_game_data
def custom_relpath(path, start):
if path.lower().startswith(start.lower()):
return path[len(start):].lstrip('\\')
else:
return resolve_outside_texture_path(path)
def resolve_texture_path(filepath: str) -> str:
filepath = os.path.splitext(bpy.path.abspath(filepath))[0] + ".blp"
prefs = get_project_preferences()
# TODO: project folder
rel_path = custom_relpath(filepath, prefs.cache_dir_path)
test_path = os.path.join(prefs.cache_dir_path, rel_path)
if os.path.exists(test_path) and os.path.isfile(test_path):
return rel_path.replace('/', '\\')
game_data = load_game_data()
path = (filepath, "")
rest_path = ""
while True:
path = os.path.split(path[0])
if not path[1]:
print("\nTexture \"{}\" not found.".format(path))
break
rest_path = os.path.join(path[1], rest_path)
rest_path = rest_path[:-1] if rest_path.endswith('\\') else rest_path
if os.name != 'nt':
rest_path_n = rest_path.replace('/', '\\')
else:
rest_path_n = rest_path
rest_path_n = rest_path_n[:-1] if rest_path_n.endswith('\\') else rest_path_n
if game_data.has_file(rest_path_n)[0]:
return rest_path_n
def resolve_outside_texture_path(filepath: str) -> str:
keywords = ["world\\", "dungeon\\", "creature\\", "interface\\", "item\\", "models\\", "spells\\", "textures\\", "tileset\\", "xtextures\\"]
lowercase_filepath = filepath.lower()
for keyword in keywords:
if keyword in lowercase_filepath:
index = lowercase_filepath.rfind(keyword)
extracted_path = lowercase_filepath[index:]
extracted_path = os.path.splitext(extracted_path)[0] + ".blp"
normalized_path = os.path.normpath(extracted_path)
return normalized_path
lowercase_filepath = os.path.splitext(bpy.path.abspath(lowercase_filepath))[0] + ".blp"
return lowercase_filepath
def resolve_outside_model_path(filepath: str) -> str:
keywords = ["world\\", "dungeon\\", "creature\\", "interface\\", "item\\", "models\\", "spells\\", "textures\\", "tileset\\", "xtextures\\"]
lowercase_filepath = filepath.lower()
for keyword in keywords:
if keyword in lowercase_filepath:
index = lowercase_filepath.rfind(keyword)
extracted_path = lowercase_filepath[index:]
extracted_path = os.path.splitext(extracted_path)[0] + ".m2"
normalized_path = os.path.normpath(extracted_path)
return normalized_path
else:
return None
def get_origin_position():
loc = bpy.context.scene.cursor.location
origin_loc = None
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
ctx = bpy.context.copy()
ctx['area'] = area
ctx['region'] = area.regions[-1]
bpy.ops.view3d.snap_cursor_to_selected(ctx)
origin_loc = bpy.context.scene.cursor.location
bpy.context.scene.cursor.location = loc
return origin_loc
def get_obj_boundbox_center(obj):
return obj.matrix_world @ (0.125 * sum((Vector(b) for b in obj.bound_box), Vector()))
def get_obj_radius(obj, bb_center):
mesh = obj.data
radius = 0.0
for vertex in mesh.vertices:
dist = (vertex.co - bb_center).length
if dist > radius:
radius = dist
return radius
def get_obj_boundbox_world(obj):
return tuple(obj.matrix_world @ Vector(obj.bound_box[0])), tuple(obj.matrix_world @ Vector(obj.bound_box[6]))
def get_objs_boundbox_world(objects):
corner1 = [32768, 32768, 32768]
corner2 = [-32768, -32768, -32768]
for obj in objects:
obj_bb_corner1, obj_bb_corner2 = get_obj_boundbox_world(obj)
for i, value in enumerate(obj_bb_corner1):
if value < corner1[i]:
corner1[i] = value
for i, value in enumerate(obj_bb_corner2):
if value > corner2[i]:
corner2[i] = value
return tuple(corner1), tuple(corner2)
def simplify_numbers(num):
num = float('{:.3g}'.format(num))
magnitude = 0
while abs(num) >= 1000:
magnitude += 1
num /= 1000.0
return '{}{}'.format('{:f}'.format(num).rstrip('0').rstrip('.'), ['', 'K', 'M', 'B', 'T'][magnitude])
def wrap_text(width, text):
lines = []
arr = text.split()
length_sum = 0
str_sum = ""
for var in arr:
length_sum += len(var) + 1
if length_sum <= width:
str_sum += " " + var
else:
lines.append(str_sum)
length_sum = 0
str_sum = var
if length_sum != 0:
lines.append(str_sum)
# lines.append(" " + arr[len(arr) - 1])
return lines
def draw_spoiler(layout, data, toggle_prop_name, name="", data1=None, layout_enabled=None, icon=None, align=True):
""" Draw a spoiler-like layout in Blender UI """
is_expanded = getattr(data, toggle_prop_name)
body = layout.box()
header = body.box()
header_row = header.row(align=True)
header_row.prop(data, toggle_prop_name, emboss=False, text='', icon='TRIA_DOWN' if is_expanded else 'TRIA_RIGHT')
if data1 and layout_enabled:
if icon:
header_row.label(text='', icon=icon)
header_row.prop(data1, layout_enabled, text=name)
else:
header_row.label(text=' ' + name if align else name, icon=icon)
if is_expanded:
content = body.column()
if data1 and layout_enabled:
content.enabled = getattr(data1, layout_enabled)
return content
def show_message_box(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)