Adds black; reformat the yoga module

This commit is contained in:
Fabien LOISON 2021-03-29 13:46:56 +02:00
parent 1e3fd63c57
commit 4b6b84b8a3
No known key found for this signature in database
GPG Key ID: FF90CA148348048E
13 changed files with 281 additions and 195 deletions

View File

@ -1,10 +1,19 @@
import nox
PYTHON_FILES = [
"yoga",
"test",
"setup.py",
"noxfile.py",
]
@nox.session
def lint(session):
session.install("flake8")
session.run("flake8", "yoga", "test", "noxfile.py")
session.install("flake8", "black")
session.run("flake8", *PYTHON_FILES)
session.run("black", "--line-length=79", "--check", *PYTHON_FILES)
@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8", "3.9"])

View File

@ -12,14 +12,16 @@ from setuptools.command.build_py import build_py
def _find_msbuild(plat_spec="x64"):
# https://github.com/python/cpython/blob/master/Lib/distutils/_msvccompiler.py
import distutils._msvccompiler as msvc
vc_env = msvc._get_vc_env(plat_spec)
if "vsinstalldir" not in vc_env:
raise Exception("Unable to find any Visual Studio installation")
return os.path.join(vc_env["vsinstalldir"], "MSBuild", "Current", "Bin", "MSBuild.exe") # noqa
return os.path.join(
vc_env["vsinstalldir"], "MSBuild", "Current", "Bin", "MSBuild.exe"
)
class CustomBuildPy(build_py):
def run(self):
if not os.path.isdir("./assimp/build"):
os.mkdir("./assimp/build")
@ -28,29 +30,33 @@ class CustomBuildPy(build_py):
if ccompiler.get_default_compiler() == "unix":
os.environ["CPPFLAGS"] = "--std=c++11"
subprocess.call([
"cmake", "..",
"-DBUILD_SHARED_LIBS=OFF",
"-DASSIMP_BUILD_ASSIMP_TOOLS=OFF",
"-DASSIMP_BUILD_TESTS=OFF",
"-DASSIMP_BUILD_ZLIB=ON",
])
subprocess.call(
[
"cmake",
"..",
"-DBUILD_SHARED_LIBS=OFF",
"-DASSIMP_BUILD_ASSIMP_TOOLS=OFF",
"-DASSIMP_BUILD_TESTS=OFF",
"-DASSIMP_BUILD_ZLIB=ON",
]
)
subprocess.call(["make"])
elif ccompiler.get_default_compiler() == "msvc":
msbuild = _find_msbuild()
subprocess.call([
"cmake", "..",
"-DBUILD_SHARED_LIBS=OFF",
"-DASSIMP_BUILD_ASSIMP_TOOLS=OFF",
"-DASSIMP_BUILD_TESTS=OFF",
"-DASSIMP_BUILD_ZLIB=ON",
"-DLIBRARY_SUFFIX=",
])
subprocess.call([
msbuild,
"-p:Configuration=Release",
"Assimp.sln"
])
subprocess.call(
[
"cmake",
"..",
"-DBUILD_SHARED_LIBS=OFF",
"-DASSIMP_BUILD_ASSIMP_TOOLS=OFF",
"-DASSIMP_BUILD_TESTS=OFF",
"-DASSIMP_BUILD_ZLIB=ON",
"-DLIBRARY_SUFFIX=",
]
)
subprocess.call(
[msbuild, "-p:Configuration=Release", "Assimp.sln"]
)
else:
raise Exception("Unhandled platform")
@ -70,24 +76,20 @@ setup(
description="Yummy Optimizer for Gorgeous Assets",
url="https://github.com/wanadev/yoga",
license="BSD-3-Clause",
long_description=long_description,
keywords="image jpeg png optimizer guetzli zopfli 3d model mesh assimp gltf glb converter", # noqa
author="Wanadev",
author_email="contact@wanadev.fr",
maintainer="Fabien LOISON, Alexis BREUST",
packages=find_packages(),
setup_requires=["cffi>=1.0.0"],
install_requires=[
"cffi>=1.0.0",
"pillow>=6.2.2",
"pyguetzli>=1.0.0",
"unidecode>=1.0.0",
"zopflipy>=1.0"
],
"zopflipy>=1.0",
],
extras_require={
"dev": [
"nox",
@ -95,16 +97,15 @@ setup(
"pytest",
"Sphinx",
"sphinx-rtd-theme",
]},
]
},
entry_points={
"console_scripts": [
"yoga = yoga.__main__:main"
]},
"yoga = yoga.__main__:main",
]
},
cffi_modules=["yoga/model/assimp_build.py:ffibuilder"],
cmdclass={
"build_py": CustomBuildPy,
},
)
},
)

View File

@ -14,8 +14,8 @@ def main():
args.output,
options=vars(args),
verbose=args.verbose,
quiet=args.quiet
)
quiet=args.quiet,
)
if __name__ == "__main__":

View File

@ -15,32 +15,36 @@ def _type_path(mode, string):
path = os.path.dirname(os.path.abspath(string))
if os.access(path, mode):
return string
raise argparse.ArgumentTypeError("the '%s' folder does not exist" % path) # noqa
raise argparse.ArgumentTypeError(
"the '%s' folder does not exist" % path
)
def add_main_cli_arguments(parser):
parser.add_argument(
"input",
help="Input file path",
type=partial(_type_path, os.R_OK)
)
"input",
help="Input file path",
type=partial(_type_path, os.R_OK),
)
parser.add_argument(
"output",
help="Output file path",
type=partial(_type_path, os.W_OK)
)
"output",
help="Output file path",
type=partial(_type_path, os.W_OK),
)
parser.add_argument(
"-v", "--verbose",
help="enable verbose mode",
default=False,
action="store_true"
)
"-v",
"--verbose",
help="enable verbose mode",
default=False,
action="store_true",
)
parser.add_argument(
"-q", "--quiet",
help="enable quiet mode (takes precedence over verbose)",
default=False,
action="store_true"
)
"-q",
"--quiet",
help="enable quiet mode (takes precedence over verbose)",
default=False,
action="store_true",
)
def generate_image_cli(parser=None):
@ -67,15 +71,15 @@ def generate_main_cli():
subparsers = parser.add_subparsers(dest="subcommand")
image_parser = subparsers.add_parser(
"image",
help="Converts and optimizes images"
)
"image",
help="Converts and optimizes images",
)
generate_image_cli(image_parser)
model_parser = subparsers.add_parser(
"model",
help="Converts and optimizes 3D models"
)
"model",
help="Converts and optimizes 3D models",
)
generate_model_cli(model_parser)
return parser

View File

@ -146,8 +146,13 @@ def optimize(input_file, output_file, options={}, verbose=False, quiet=False):
image = Image.open(input_file)
if options["output_format"] == "orig" and image.format not in ("JPEG", "PNG"): # noqa
raise ValueError("The input image must be a JPEG or a PNG when setting 'output_format' to 'orig'") # noqa
if options["output_format"] == "orig" and image.format not in (
"JPEG",
"PNG",
):
raise ValueError(
"The input image must be a JPEG or a PNG when setting 'output_format' to 'orig'" # noqa: E501
)
# resize
if options["resize"] != "orig":
@ -172,7 +177,8 @@ def optimize(input_file, output_file, options={}, verbose=False, quiet=False):
output_image_bytes = None
if output_format == "jpeg":
output_image_bytes = pyguetzli.process_pil_image(
image, int(options["jpeg_quality"] * 100))
image, int(options["jpeg_quality"] * 100)
)
else:
image_io = io.BytesIO()
image.save(image_io, format="PNG", optimize=False)

View File

@ -12,8 +12,10 @@ def _type_resize(string):
return string
if _RESIZE_OPTION_REGEXP.match(string):
return string
message = ("invalid format: '%s' (valid formats are "
"'orig', <SIZE>, <WIDTH>x<HEIGHT>)") % string
message = (
"invalid format: '%s' (valid formats are "
"'orig', <SIZE>, <WIDTH>x<HEIGHT>)"
) % string
raise argparse.ArgumentTypeError(message)
@ -27,36 +29,39 @@ def _type_range(min_, max_, string):
if min_ <= value <= max_:
return value
message = "number not in range: '%s' (range is [%i-%i])" % (
string, min_, max_)
string,
min_,
max_,
)
raise argparse.ArgumentTypeError(message)
def add_image_cli_options(parser, prefix=""):
parser.add_argument(
"--%soutput-format" % prefix,
help="format of the output image",
metavar="{orig,auto,jpeg,png}",
choices=["orig", "auto", "jpeg", "jpg", "png"],
default=DEFAULT_OPTIONS["output_format"]
)
"--%soutput-format" % prefix,
help="format of the output image",
metavar="{orig,auto,jpeg,png}",
choices=["orig", "auto", "jpeg", "jpg", "png"],
default=DEFAULT_OPTIONS["output_format"],
)
parser.add_argument(
"--%sresize" % prefix,
help="resize the image",
metavar="{orig,<SIZE>,<WIDTH>x<HEIGHT>}",
type=_type_resize,
default=DEFAULT_OPTIONS["resize"]
)
"--%sresize" % prefix,
help="resize the image",
metavar="{orig,<SIZE>,<WIDTH>x<HEIGHT>}",
type=_type_resize,
default=DEFAULT_OPTIONS["resize"],
)
parser.add_argument(
"--%sjpeg-quality" % prefix,
help="JPEG quality if the output format is set to 'jpeg'",
metavar="0-100",
type=partial(_type_range, 0, 100),
default=DEFAULT_OPTIONS["jpeg_quality"]
)
"--%sjpeg-quality" % prefix,
help="JPEG quality if the output format is set to 'jpeg'",
metavar="0-100",
type=partial(_type_range, 0, 100),
default=DEFAULT_OPTIONS["jpeg_quality"],
)
parser.add_argument(
"--%sopacity-threshold" % prefix,
help="threshold below which a pixel is considered transparent",
metavar="0-255",
type=partial(_type_range, 0, 255),
default=DEFAULT_OPTIONS["opacity_threshold"]
)
"--%sopacity-threshold" % prefix,
help="threshold below which a pixel is considered transparent",
metavar="0-255",
type=partial(_type_range, 0, 255),
default=DEFAULT_OPTIONS["opacity_threshold"],
)

View File

@ -1,16 +1,19 @@
import re
# fmt: off
DEFAULT_OPTIONS = {
"output_format": "orig", # orig|auto|jpeg|png
"resize": "orig", # orig|[w,h]
"jpeg_quality": 0.84, # 0.00-1.00
"opacity_threshold": 254 # 0-255
}
"output_format": "orig", # orig|auto|jpeg|png
"resize": "orig", # orig|[w,h]
"jpeg_quality": 0.84, # 0.00-1.00
"opacity_threshold": 254 # 0-255
}
# fmt: on
_RESIZE_OPTION_REGEXP = re.compile(
r"^([0-9]+)[x\s:,;]([0-9]+)$", flags=re.IGNORECASE)
r"^([0-9]+)[x\s:,;]([0-9]+)$", flags=re.IGNORECASE
)
def normalize_options(options=None):
@ -28,7 +31,7 @@ def normalize_options(options=None):
value = "jpeg"
if value not in ("orig", "auto", "jpeg", "png"):
raise ValueError("Invalid value for 'output_format': '%s'" % value) # noqa
raise ValueError("Invalid value for 'output_format': '%s'" % value)
result["output_format"] = value
@ -55,9 +58,9 @@ def normalize_options(options=None):
elif _RESIZE_OPTION_REGEXP.match(value):
match = _RESIZE_OPTION_REGEXP.match(value)
value = [
int(match.group(1)),
int(match.group(2)),
]
int(match.group(1)),
int(match.group(2)),
]
elif value != "orig":
raise ValueError("Invalid value for 'resize': %s" % value)

View File

@ -146,13 +146,19 @@ 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 .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):
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.
@ -201,8 +207,8 @@ def optimize(input_file, output_file, options={}, textures=None,
input_file,
not model_options["no_graph_optimization"],
not model_options["no_meshes_optimization"],
verbose
)
verbose,
)
# Embed images
# @note We save the bytes to a dictionnary so that the garbage collector
@ -216,14 +222,14 @@ def optimize(input_file, output_file, options={}, textures=None,
root_path,
image_options,
textures,
quiet
)
quiet,
)
# Export the scene
bytes_out = assimp_export_to_bytes(
scene["cffi_pointer"],
model_options["output_format"]
)
model_options["output_format"],
)
# Write to output
if not hasattr(output_file, "write"):

View File

@ -7,11 +7,8 @@ from ._assimp import lib, ffi
def assimp_import_from_bytes(
bytes_in,
optimize_graph,
optimize_meshes,
verbose
):
bytes_in, optimize_graph, optimize_meshes, verbose
):
"""Generates an abstract 3D scene from a model file's bytes.
:param bytes_in: the input model's bytes
:param optimize_graph: whether the graph scene should be optimized
@ -29,7 +26,7 @@ def assimp_import_from_bytes(
scene = {
"cffi_pointer": None,
"cffi_gc": None
"cffi_gc": None,
}
scene["cffi_pointer"] = ffi.new("Scene*")
scene["cffi_gc"] = ffi.gc(scene["cffi_pointer"], lib.assimp_free_scene)
@ -39,11 +36,13 @@ def assimp_import_from_bytes(
len(bytes_in),
optimization_flags,
scene["cffi_pointer"],
verbose
)
verbose,
)
if scene["cffi_pointer"].assimp_scene == ffi.NULL:
raise ValueError("Invalid model: Assimp was not able to import the model") # noqa
raise ValueError(
"Invalid model: Assimp was not able to import the model"
)
return scene
@ -58,21 +57,24 @@ def assimp_export_to_bytes(scene_p, output_format):
"""
if output_format not in ("glb", "gltf"):
raise ValueError("Invalid output format: should be glb or gltf but is %s" % output_format) # noqa
raise ValueError(
"Invalid output format: should be glb or gltf but is %s"
% output_format
)
output_format_dict = dict({
output_format_dict = dict(
{
"glb": lib.OUTPUT_FORMAT_GLB,
"gltf": lib.OUTPUT_FORMAT_GLTF
})
"gltf": lib.OUTPUT_FORMAT_GLTF,
}
)
bytes_out_p = ffi.new("char**")
bytes_out_p_gc = ffi.gc(bytes_out_p, lib.assimp_free_bytes)
length = lib.assimp_export_to_bytes(
scene_p,
output_format_dict[output_format],
bytes_out_p
)
scene_p, output_format_dict[output_format], bytes_out_p
)
if length == 0:
raise ValueError("Invalid model: Assimp was not able to export")

View File

@ -15,14 +15,33 @@ _LIB_ZLIB = None
if ccompiler.get_default_compiler() == "unix":
# Default libs path for Unix systems
_LIB_ASSIMP = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "libassimp.a") # noqa
_LIB_IRRXML = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "libIrrXML.a") # noqa
_LIB_ZLIB = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "libzlibstatic.a") # noqa
_LIB_ASSIMP = os.path.join(
_ROOT, "..", "..", "assimp", "build", "lib", "libassimp.a"
)
_LIB_IRRXML = os.path.join(
_ROOT, "..", "..", "assimp", "build", "lib", "libIrrXML.a"
)
_LIB_ZLIB = os.path.join(
_ROOT, "..", "..", "assimp", "build", "lib", "libzlibstatic.a"
)
elif ccompiler.get_default_compiler() == "msvc":
# Default libs path for Windows
_LIB_ASSIMP = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "Release", "assimp.lib") # noqa
_LIB_IRRXML = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "Release", "IrrXML.lib") # noqa
_LIB_ZLIB = os.path.join(_ROOT, "..", "..", "assimp", "build", "lib", "Release", "zlibstatic.lib") # noqa
_LIB_ASSIMP = os.path.join(
_ROOT, "..", "..", "assimp", "build", "lib", "Release", "assimp.lib"
)
_LIB_IRRXML = os.path.join(
_ROOT, "..", "..", "assimp", "build", "lib", "Release", "IrrXML.lib"
)
_LIB_ZLIB = os.path.join(
_ROOT,
"..",
"..",
"assimp",
"build",
"lib",
"Release",
"zlibstatic.lib",
)
# Allow to override path through env vars
if "YOGA_BUILD_LIB_ASSIMP" in os.environ:
@ -33,25 +52,31 @@ if "YOGA_BUILD_LIB_ZLIB" in os.environ:
_LIB_ZLIB = os.environ["YOGA_BUILD_LIB_ZLIB"]
if not _LIB_ASSIMP:
raise Exception("Please provide the path to the assimp library using the YOGA_BUILD_LIB_ASSIMP environment variable") # noqa
raise Exception(
"Please provide the path to the assimp library using the YOGA_BUILD_LIB_ASSIMP environment variable" # noqa: E501
)
if not _LIB_IRRXML:
raise Exception("Please provide the path to the IrrXML library using the YOGA_BUILD_LIB_IRRXML environment variable") # noqa
raise Exception(
"Please provide the path to the IrrXML library using the YOGA_BUILD_LIB_IRRXML environment variable" # noqa: E501
)
if not _LIB_ZLIB:
raise Exception("Please provide the path to the zlib library using the YOGA_BUILD_LIB_ZLIB environment variable") # noqa
raise Exception(
"Please provide the path to the zlib library using the YOGA_BUILD_LIB_ZLIB environment variable" # noqa: E501
)
ffibuilder = FFI()
ffibuilder.set_source(
"yoga.model._assimp",
open(_ASSIMP_CPP, "r").read(),
extra_objects=[_LIB_ASSIMP, _LIB_IRRXML, _LIB_ZLIB],
include_dirs=[
_ROOT,
os.path.join(_ROOT, "..", "..", "assimp", "include"),
os.path.join(_ROOT, "..", "..", "assimp", "build", "include")
],
source_extension=".cpp"
)
"yoga.model._assimp",
open(_ASSIMP_CPP, "r").read(),
extra_objects=[_LIB_ASSIMP, _LIB_IRRXML, _LIB_ZLIB],
include_dirs=[
_ROOT,
os.path.join(_ROOT, "..", "..", "assimp", "include"),
os.path.join(_ROOT, "..", "..", "assimp", "build", "include"),
],
source_extension=".cpp",
)
ffibuilder.cdef(open(_ASSIMP_H, "r").read())

View File

@ -5,34 +5,35 @@ from .options import DEFAULT_OPTIONS
def add_model_cli_options(parser):
parser.add_argument(
"--output-format",
help="format of the output model (default: %s)" % DEFAULT_OPTIONS["output_format"], # noqa
metavar="{glb,gltf}",
choices=["glb", "gltf"],
default=DEFAULT_OPTIONS["output_format"]
)
"--output-format",
help="format of the output model (default: %s)"
% DEFAULT_OPTIONS["output_format"],
metavar="{glb,gltf}",
choices=["glb", "gltf"],
default=DEFAULT_OPTIONS["output_format"],
)
parser.add_argument(
"--fallback-texture",
help="fallback image used when unable to find a texture",
metavar="<PATH>",
default=DEFAULT_OPTIONS["fallback_texture"],
type=argparse.FileType("rb")
)
"--fallback-texture",
help="fallback image used when unable to find a texture",
metavar="<PATH>",
default=DEFAULT_OPTIONS["fallback_texture"],
type=argparse.FileType("rb"),
)
parser.add_argument(
"--no-graph-optimization",
help="disable empty graph nodes merging",
default=DEFAULT_OPTIONS["no_graph_optimization"],
action="store_true"
)
"--no-graph-optimization",
help="disable empty graph nodes merging",
default=DEFAULT_OPTIONS["no_graph_optimization"],
action="store_true",
)
parser.add_argument(
"--no-meshes-optimization",
help="disable meshes optimization",
default=DEFAULT_OPTIONS["no_meshes_optimization"],
action="store_true"
)
"--no-meshes-optimization",
help="disable meshes optimization",
default=DEFAULT_OPTIONS["no_meshes_optimization"],
action="store_true",
)
parser.add_argument(
"--no-textures-optimization",
help="disable textures optimization using yoga image",
default=DEFAULT_OPTIONS["no_textures_optimization"],
action="store_true"
)
"--no-textures-optimization",
help="disable textures optimization using yoga image",
default=DEFAULT_OPTIONS["no_textures_optimization"],
action="store_true",
)

View File

@ -45,7 +45,10 @@ def normalize_paths(paths):
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) # noqa
raise ValueError(
"Multiple paths are resolved to the same path %s."
% normalized_path
)
normalized_paths[normalized_path] = paths[path]
return normalized_paths
@ -62,7 +65,9 @@ def find_valid_path(path, paths):
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)) # noqa
split_paths = list(
filter(lambda sp: len(sp) > i and sp[i] == name, split_paths)
)
if len(split_paths) == 0:
break
@ -80,13 +85,24 @@ def extract_files_dictionary(root_path):
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] # noqa
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):
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)
@ -101,7 +117,9 @@ def model_embed_images(images, images_bytes,
# 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) # noqa
valid_image_path = find_valid_path(
normalized_image_path, normalized_textures
)
else:
valid_image_path = find_valid_path(normalized_image_path, files)
@ -110,7 +128,10 @@ def model_embed_images(images, images_bytes,
if fallback_texture is not None:
valid_image_path = None
if not quiet:
print("Warning: Cannot resolve %s, using the fallback texture instead." % normalized_image_path) # noqa
print(
"Warning: Cannot resolve %s, using the fallback texture instead." # noqa: E501
% normalized_image_path
)
else:
raise ValueError("Cannot resolve %s" % normalized_image_path)

View File

@ -1,10 +1,10 @@
DEFAULT_OPTIONS = {
"output_format": "glb", # glb|gltf
"fallback_texture": None,
"no_graph_optimization": False,
"no_meshes_optimization": False,
"no_textures_optimization": False,
}
"output_format": "glb", # glb|gltf
"fallback_texture": None,
"no_graph_optimization": False,
"no_meshes_optimization": False,
"no_textures_optimization": False,
}
def normalize_options(options=None):
@ -18,7 +18,7 @@ def normalize_options(options=None):
value = options["output_format"].lower()
if value not in ("gltf", "glb"):
raise ValueError("Invalid value for 'output_format': '%s'" % value) # noqa
raise ValueError("Invalid value for 'output_format': '%s'" % value)
result["output_format"] = value
@ -33,8 +33,11 @@ def normalize_options(options=None):
result["fallback_texture"] = fallback_texture_file
# Optimization flags
for key in ("no_graph_optimization", "no_meshes_optimization",
"no_textures_optimization"):
for key in (
"no_graph_optimization",
"no_meshes_optimization",
"no_textures_optimization",
):
if key in options:
result[key] = bool(options[key])