yoga/yoga/model/helpers.py

182 lines
5.4 KiB
Python

from ._assimp import ffi
import io
import sys
import os.path
import unidecode
import yoga.image
def normalize_path(path):
# Expects a unicode path, returns a ascii one.
# Paths are normalized to a standard linux relative path,
# without a point, and lowercase.
# That is to say /images\subfolder/..\texture.png -> images/texture.png
# It does not correspond to an effective path,
# as the backslashes on linux are wrongly seen as separators.
# This function is meant to give a standard output.
path = unidecode.unidecode(path)
split_path = path.replace("\\", "/").split("/")[::-1]
normalized_path = []
ignored_folders = 0
for name in split_path:
if name == "" or name == "." or name[-1:] == ":":
continue
elif name == "..":
ignored_folders += 1
elif ignored_folders > 0:
ignored_folders -= 1
else:
normalized_path.append(name)
return "/".join(normalized_path[::-1]).lower()
def normalize_paths(paths):
if paths is None:
return None
# Normalizes all the paths in the texture dict.
normalized_paths = dict()
for path in paths:
normalized_path = normalize_path(path)
if normalized_path in normalized_paths:
raise ValueError(
"Multiple paths are resolved to the same path %s."
% normalized_path
)
normalized_paths[normalized_path] = paths[path]
return normalized_paths
def find_valid_path(path, paths):
if paths is None:
return None
# The path and the paths are supposed to have
# already been normalized.
split_path = path.split("/")[::-1]
split_paths = map(lambda p: p.split("/")[::-1], paths.keys())
for i, name in enumerate(split_path):
split_paths = list(
filter(lambda sp: len(sp) > i and sp[i] == name, split_paths)
)
if len(split_paths) == 0:
break
elif len(split_paths) == 1:
return "/".join(split_paths[0][::-1])
return None
def extract_files_dictionary(root_path):
if root_path is None:
return None
if sys.version_info.major == 2 and type(root_path) is str:
root_path = root_path.decode("utf-8")
# Recursive walk of root_path files
files = [
os.path.join(dp, f)
for dp, dn, filenames in os.walk(root_path)
for f in filenames
]
return normalize_paths(dict(zip(files, files)))
def model_embed_images(
images,
images_bytes,
optimize_textures,
fallback_texture,
root_path,
image_options,
textures,
quiet,
):
optimized_textures = {}
normalized_textures = normalize_paths(textures)
files = extract_files_dictionary(root_path)
image = images
while image:
if image.bytes_length > 0:
continue
image_path = ffi.string(image.path).decode("utf-8")
# If textures exists, we don't look for files on the file system
normalized_image_path = normalize_path(image_path)
if normalized_textures is not None:
valid_image_path = find_valid_path(
normalized_image_path, normalized_textures
)
else:
valid_image_path = find_valid_path(normalized_image_path, files)
# Unable to find a valid image path
if valid_image_path is None:
if fallback_texture is not None:
valid_image_path = None
if not quiet:
print(
"Warning: Cannot resolve %s, using the fallback texture instead." # noqa: E501
% normalized_image_path
)
else:
raise ValueError("Cannot resolve %s" % normalized_image_path)
# If valid_image_path have already been seen, do not reoptimize...
if valid_image_path in optimized_textures:
optimized_texture = optimized_textures[valid_image_path]
image.bytes_length = optimized_texture.bytes_length
image.bytes = optimized_texture.bytes
image.id = optimized_texture.id
image = image.next
continue
# Get the bytes indeed
image_io = None
if valid_image_path is None:
image_io = fallback_texture
elif textures is not None:
image_io = textures[valid_image_path]
else:
image_io = io.BytesIO(open(files[valid_image_path], "rb").read())
# Optimizing the texture if requested
if optimize_textures:
if not quiet:
if valid_image_path is not None:
print("Optimizing texture %s..." % normalized_image_path)
else:
print("Optimizing fallback texture...")
output_io = io.BytesIO()
yoga.image.optimize(image_io, output_io, image_options)
image_io = output_io
image_io.seek(0)
image_bytes = image_io.read()
# Convert to cffi
image_bytes_c = ffi.new("char[%d]" % len(image_bytes), image_bytes)
image.bytes_length = len(image_bytes)
image.bytes = image_bytes_c
image.id = len(optimized_textures)
optimized_textures[valid_image_path] = image
image = image.next
# @note Save the bytes to a dictionary so that the garbage collector
# does not occur before exporting the scene a bit later
images_bytes[valid_image_path] = image_bytes_c