""" This module allows to convert and optimize 3D models. Usage ----- converting and optimizing a model:: import yoga.model yoga.model.optimize("./input.fbx", "./output.glb") You can also tune the output by passing options:: yoga.model.optimize("./input.fbx", "./output.glb", options={ # Model options "output_format": "glb", # "glb"|"gltf" "fallback_texture": None, # None||"./image.png" "no_graph_optimization": False, # True|False "no_meshes_optimization": False, # True|False "no_textures_optimization": False, # True|False "no_fix_infacing_normals": False, # True|False # Images (textures) options "image_output_format": "orig", # "orig"|"auto"|"jpeg"|"png"|"webp"|"webpl" "image_resize": "orig", # "orig"|[width,height] "image_jpeg_quality": 0.84, # 0.00-1.0 "image_webp_quality": 0.90, # 0.00-1.0 "image_opacity_threshold": 254, # 0-255 "image_png_slow_optimization": False, # True|False }) Available Model Options ----------------------- output_format ~~~~~~~~~~~~~ The format of the output model. :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "output_format": "glb", }) The following formats are supported: * ``glb`` (default) * ``gltf`` fallback_texture ~~~~~~~~~~~~~~~~ This option allows you to provide a fallback texture that will be used when YOGA is unable to find one of the model textures. The following values are allowed: * ``None``: no fallback texture, * a Python ``str`` (and ``unicode`` for Python 2): path of a fallback image file (e.g. ``"./fallback.png"``), * a "File-like" object (file, ``BytesIO``, etc.). :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "fallback_texture": None, }) :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "fallback_texture": "./fallback.png", }) :: image_file = open("./fallback.png", "rb") yoga.model.optimize("./input.fbx", "./output.glb", options={ "fallback_texture": image_file, }) no_graph_optimization ~~~~~~~~~~~~~~~~~~~~~ Disables empty graph nodes merging. :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "no_graph_optimization": False, }) no_meshes_optimization ~~~~~~~~~~~~~~~~~~~~~~ Disable mesh optimization. :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "no_meshes_optimization": False, }) no_textures_optimization ~~~~~~~~~~~~~~~~~~~~~~~~ Disable texture optimizations (textures will not be optimized using YOGA Image). :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "no_textures_optimization": False, }) no_fix_infacing_normals ~~~~~~~~~~~~~~~~~~~~~~~~ Disables the assimp's infacing normals fix. This postprocess tries to determine which meshes have normal vectors that are facing inwards and inverts them. See the `assimp documentation `_ for more details. :: yoga.model.optimize("./input.fbx", "./output.glb", options={ "no_fix_infacing_normals": False, }) Available Image Options ----------------------- YOGA Model optimizes textures using YOGA Images, so there are options equivalent to the YOGA Image ones available: * The YOGA Model ``image_output_format`` option is equivalent to the ``output_format`` of YOGA Image, * The YOGA Model ``image_resize`` option is equivalent to the ``resize`` of YOGA Image, * The YOGA Model ``image_jpeg_quality`` option is equivalent to the ``jpeg_quality`` of YOGA Image, * The YOGA Model ``image_webp_quality`` option is equivalent to the ``webp_quality`` of YOGA Image, * The YOGA Model ``image_opacity_threshold`` option is equivalent to the ``opacity_threshold`` of YOGA Image, * The YOGA Model ``image_png_slow_optimization`` option is equivalent to the ``png_slow_optimization`` of YOGA Image, * The YOGA Model ``image_enable_quantization`` option is equivalent to the ``enable_quantization`` of YOGA Image, * The YOGA Model ``image_quantization_dithering_level`` option is equivalent to the ``quantization_dithering_level`` of YOGA Image, * The YOGA Model ``image_quantization_max_colors`` option is equivalent to the ``quantization_max_colors`` of YOGA Image, See :ref:`YOGA Image options ` for more information. API --- """ import sys import os.path from .assimp import assimp_import_from_bytes, assimp_export_to_bytes from .options import normalize_options, extract_image_options from .helpers import model_embed_images def optimize( input_file, output_file, options={}, textures=None, verbose=False, quiet=False, ): """Optimize given model. :param str,file-like input_file: The input model file. :param str,file-like output_file: The output model file. :param dict options: Optimization options (see above). :param dict textures: A dictionary that maps textures path to bytes. When not ``None``, there will be no file system reads in order to find referenced textures. YOGA will look into that dictionary instead. :param bool verbose: If ``True``, Assmimp debug message will be print to stdout. :param bool quiet: If ``True``, YOGA will not write any warning on stdout or stderr. Example ``textures`` dictionary:: textures = { "images/texture1.png": open("./images/texture1.png", "rb").read(), "texture2.png": open("./texture1.png", "rb").read(), } """ model_options = normalize_options(options) image_options = extract_image_options(options) if quiet: verbose = False # Open file if possible if not hasattr(input_file, "read"): input_file = open(input_file, "rb") root_path = None if hasattr(input_file, "name"): root_path = os.path.dirname(os.path.abspath(input_file.name)) if sys.version_info.major == 2 and type(root_path) is str: root_path = root_path.decode("utf-8") # input_file -> string (path), bytes, file-like if hasattr(input_file, "read"): input_file = input_file.read() # Import the scene scene = assimp_import_from_bytes( input_file, not model_options["no_graph_optimization"], not model_options["no_meshes_optimization"], not model_options["no_fix_infacing_normals"], verbose, ) # Embed images # @note We save the bytes to a dictionary so that the garbage collector # does not occur before exporting the scene a bit later images_bytes = {} model_embed_images( scene["cffi_pointer"].images, images_bytes, not model_options["no_textures_optimization"], model_options["fallback_texture"], root_path, image_options, textures, quiet, ) # Export the scene bytes_out = assimp_export_to_bytes( scene["cffi_pointer"], model_options["output_format"], ) # Write to output if not hasattr(output_file, "write"): output_file = open(output_file, "wb") output_file.write(bytes_out)