From 10de948a7923cd294cff816c0c2362150724d3e2 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Thu, 24 Jul 2025 10:16:58 +0800 Subject: [PATCH] feat: update scripts - add meshes builder (copyer). - fix json compressor. --- scripts/README.md | 8 ++- scripts/build_icons.py | 2 +- scripts/build_jsons.py | 102 +++++++++------------------- scripts/build_meshes.py | 27 ++++++++ scripts/common.py | 140 +++++++++++++++++++++++---------------- scripts/uv.lock | 2 +- scripts/validate_json.py | 2 +- 7 files changed, 151 insertions(+), 132 deletions(-) create mode 100644 scripts/build_meshes.py diff --git a/scripts/README.md b/scripts/README.md index 868a865..0a9c94e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,4 +1,4 @@ -# Tools +# Scripts These tool scripts is served for BBP_NG specifically. We use Astral UV with a single Python project file to manage these tools. You can browse their usage in this file. @@ -14,6 +14,12 @@ Compress BME prototype JSON files into smaller size. Execute `uv run build_json.py` +## Build Meshes + +Copy Ballance element placeholder into Blender plugin. + +Execute `uv run build_meshes.py` + ## Validate BME Prototype Validate the correction of BME prorotype JSON files. diff --git a/scripts/build_icons.py b/scripts/build_icons.py index 2c00a27..7d8726e 100644 --- a/scripts/build_icons.py +++ b/scripts/build_icons.py @@ -13,7 +13,7 @@ class ThumbnailBuilder(): def build_thumbnails(self) -> None: # get folder path - root_folder = common.get_plugin_folder() + root_folder = common.get_root_folder() # prepare handler def folder_handler(rel_name: str, src_folder: Path, dst_folder: Path) -> None: diff --git a/scripts/build_jsons.py b/scripts/build_jsons.py index 60d3947..3e2e83f 100644 --- a/scripts/build_jsons.py +++ b/scripts/build_jsons.py @@ -1,84 +1,44 @@ -import os, json, typing -import bme_utils, bme_relatives, simple_po +import json, logging +from pathlib import Path import common +from common import AssetKind -class JsonCompressor(): - __mReporter: bme_utils.Reporter - __mPoWriter: simple_po.PoWriter - # __mValidator: bme_relatives.BMEValidator - __mExtractor: bme_relatives.BMEExtractor +def _compress_json(src_file: Path, dst_file: Path) -> None: + # load data first + with open(src_file, 'r', encoding='utf-8') as f: + loaded_prototypes = json.load(f) - def __init__(self): - self.__mReporter = bme_utils.Reporter() - self.__mPoWriter = simple_po.PoWriter( - os.path.join(common.get_plugin_folder(), 'i18n', 'bme.pot'), - 'BME Prototypes' - ) - # self.__mValidator = bme_relatives.BMEValidator(self.__mReporter) - self.__mExtractor = bme_relatives.BMEExtractor(self.__mReporter, self.__mPoWriter) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - def close(self) -> None: - self.__mPoWriter.close() - - def run(self) -> None: - self.__compress_jsons() - - def __compress_jsons(self) -> None: - # get folder path - root_folder: str = common.get_plugin_folder() - - # prepare handler - def folder_handler(rel_name: str, src_folder: str, dst_folder: str) -> None: - # just create folder - self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}') - os.makedirs(dst_folder, exist_ok = True) - def file_handler(rel_name: str, src_file: str, dst_file: str) -> None: - # skip non-json - if not src_file.endswith('.json'): return - # call compress func - self.__mReporter.info(f'Processing JSON: {src_file} -> {dst_file}') - self.__compress_json(rel_name, src_file, dst_file) - - # call common processor - common.common_file_migrator( - os.path.join(root_folder, 'raw_jsons'), - os.path.join(root_folder, 'jsons'), - folder_handler, - file_handler + # save result with compress config + with open(dst_file, 'w', encoding='utf-8') as f: + json.dump( + loaded_prototypes, # loaded data + f, + indent=None, # no indent. the most narrow style. + separators=(',', ':'), # also for narrow style. + sort_keys=False, # do not sort key ) - self.__mReporter.info('Building JSON done.') - def __compress_json(self, rel_name: str, src_file: str, dst_file: str) -> None: - # load data first - loaded_prototypes: typing.Any - with open(src_file, 'r', encoding = 'utf-8') as fr: - loaded_prototypes = json.load(fr) +def build_jsons() -> None: + raw_jsons_dir = common.get_raw_assets_folder(AssetKind.Jsons) + plg_jsons_dir = common.get_plugin_assets_folder(AssetKind.Jsons) - # validate loaded data - # self.__mValidator.validate(rel_name, loaded_prototypes) + for raw_json_file in raw_jsons_dir.glob('*.json'): + # Skip non-file. + if not raw_json_file.is_file(): + continue - # extract translation - self.__mExtractor.extract(rel_name, loaded_prototypes) + # Build final path + plg_json_file = plg_jsons_dir / raw_json_file.relative_to(raw_jsons_dir) - # save result - with open(dst_file, 'w', encoding = 'utf-8') as fw: - json.dump( - loaded_prototypes, # loaded data - fw, - indent = None, # no indent. the most narrow style. - separators = (',', ':'), # also for narrow style. - sort_keys = False, # do not sort key - ) + # Show message + logging.info(f'Compressing {raw_json_file} -> {plg_json_file}') + + # Compress json + _compress_json(raw_json_file, plg_json_file) if __name__ == '__main__': - with JsonCompressor() as json_compressor: - json_compressor.run() + common.setup_logging() + build_jsons() diff --git a/scripts/build_meshes.py b/scripts/build_meshes.py new file mode 100644 index 0000000..1ab445e --- /dev/null +++ b/scripts/build_meshes.py @@ -0,0 +1,27 @@ +import shutil, logging +import common +from common import AssetKind + + +def build_meshes() -> None: + raw_meshes_dir = common.get_raw_assets_folder(AssetKind.Meshes) + plg_meshes_dir = common.get_plugin_assets_folder(AssetKind.Meshes) + + for raw_ph_file in raw_meshes_dir.glob('*.ph'): + # Skip non-file. + if not raw_ph_file.is_file(): + continue + + # Build final path + plg_ph_file = plg_meshes_dir / raw_ph_file.relative_to(raw_meshes_dir) + + # Show message + logging.info(f'Copying {raw_ph_file} -> {plg_ph_file}') + + # Copy placeholder + shutil.copyfile(raw_ph_file, plg_ph_file) + + +if __name__ == '__main__': + common.setup_logging() + build_meshes() diff --git a/scripts/common.py b/scripts/common.py index 44570ec..8402abd 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -1,76 +1,102 @@ -import os, typing, logging +import os, typing, logging, enum from pathlib import Path -def get_plugin_folder() -> Path: - """ - Get the absolute path to plugin root folder. - :return: The absolute path to plugin root folder. +def get_root_folder() -> Path: + """ + Get the path to the root folder of this repository. + + :return: The absolute path to the root folder of this repository. """ return Path(__file__).resolve().parent.parent -def relative_to_folder(abs_path: Path, src_parent: Path, dst_parent: Path) -> Path: + +class AssetKind(enum.StrEnum): + Icons = 'icons' + Jsons = 'jsons' + Meshes = 'meshes' + + +def get_raw_assets_folder(kind: AssetKind) -> Path: """ - Rebase one path to another path. + Get the path to the raw assets folder of given kind. - Give a absolute file path and folder path, and compute the relative path of given file to given folder. - Then applied the computed relative path to another given folder path. - Thus it seems like the file was rebased to from a folder to another folder with keeping the folder hierarchy. - - For example, given `/path/to/file` and `/path`, it will compute relative path `to/file`. - Then it was applied to another folder path `/new` and got `/new/to/file`. - - :param abs_path: The absolute path to a folder or file. - :param src_parent: The absolute path to folder which the `abs_path` will have relative path to. - :param dst_parent: The absolute path to folder which the relative path will be applied to. + :return: The absolute path to the raw assets folder of given kind. """ - return dst_parent / (abs_path.relative_to(src_parent)) + return get_root_folder() / 'assets' / str(kind) -def common_file_migrator( - from_folder: Path, to_folder: Path, - fct_proc_folder: typing.Callable[[str, Path, Path], None], - fct_proc_file: typing.Callable[[str, Path, Path], None]) -> None: + +def get_plugin_assets_folder(kind: AssetKind) -> Path: """ - Common file migrator used by some build script. + Get the path to the plugin assets folder of given kind. - This function receive 2 absolute folder path. `from_folder` indicate the file migrated out, - and `to_folder` indicate the file migrated in. - `fct_proc_folder` is a function pointer from caller which handle folder migration in detail. - `fct_proc_file` is same but handle file migration. - - `fct_proc_folder` will receive 3 args. - First is the name of this folder which can be shown for end user. - Second is the source folder and third is expected dest folder. - `fct_proc_file` is same, but receive the file path instead. - Both of these function pointer should do the migration in detail. This function will only just iterate - folder and give essential args and will not do any migration operations such as copying or moving. - - :param from_folder: The folder need to be migrated. - :param to_folder: The folder will be migrated to. - :param fct_proc_folder: Folder migration detail handler. - :param fct_proc_file: File migration detail handler. + :return: The absolute path to the plugin assets folder of given kind. """ - # TODO: If we have Python 3.12, use Path.walk instead of current polyfill. + return get_root_folder() / 'bbp_ng' / str(kind) - # iterate from_folder folder - for root, dirs, files in os.walk(from_folder, topdown=True): - root = Path(root) - # iterate folders - for name in dirs: - # prepare handler args - src_folder = root / name - dst_folder = relative_to_folder(src_folder, from_folder, to_folder) - # call handler - fct_proc_folder(name, src_folder, dst_folder) +# def relative_to_folder(abs_path: Path, src_parent: Path, dst_parent: Path) -> Path: +# """ +# Rebase one path to another path. + +# Give a absolute file path and folder path, and compute the relative path of given file to given folder. +# Then applied the computed relative path to another given folder path. +# Thus it seems like the file was rebased to from a folder to another folder with keeping the folder hierarchy. + +# For example, given `/path/to/file` and `/path`, it will compute relative path `to/file`. +# Then it was applied to another folder path `/new` and got `/new/to/file`. + +# :param abs_path: The absolute path to a folder or file. +# :param src_parent: The absolute path to folder which the `abs_path` will have relative path to. +# :param dst_parent: The absolute path to folder which the relative path will be applied to. +# """ +# return dst_parent / (abs_path.relative_to(src_parent)) + + +# def common_file_migrator(from_folder: Path, to_folder: Path, fct_proc_folder: typing.Callable[[str, Path, Path], None], +# fct_proc_file: typing.Callable[[str, Path, Path], None]) -> None: +# """ +# Common file migrator used by some build script. + +# This function receive 2 absolute folder path. `from_folder` indicate the file migrated out, +# and `to_folder` indicate the file migrated in. +# `fct_proc_folder` is a function pointer from caller which handle folder migration in detail. +# `fct_proc_file` is same but handle file migration. + +# `fct_proc_folder` will receive 3 args. +# First is the name of this folder which can be shown for end user. +# Second is the source folder and third is expected dest folder. +# `fct_proc_file` is same, but receive the file path instead. +# Both of these function pointer should do the migration in detail. This function will only just iterate +# folder and give essential args and will not do any migration operations such as copying or moving. + +# :param from_folder: The folder need to be migrated. +# :param to_folder: The folder will be migrated to. +# :param fct_proc_folder: Folder migration detail handler. +# :param fct_proc_file: File migration detail handler. +# """ +# # TODO: If we have Python 3.12, use Path.walk instead of current polyfill. + +# # iterate from_folder folder +# for root, dirs, files in os.walk(from_folder, topdown=True): +# root = Path(root) + +# # iterate folders +# for name in dirs: +# # prepare handler args +# src_folder = root / name +# dst_folder = relative_to_folder(src_folder, from_folder, to_folder) +# # call handler +# fct_proc_folder(name, src_folder, dst_folder) + +# # iterate files +# for name in files: +# # prepare handler args +# src_file = root / name +# dst_file = relative_to_folder(src_file, from_folder, to_folder) +# # call handler +# fct_proc_file(name, src_file, dst_file) - # iterate files - for name in files: - # prepare handler args - src_file = root / name - dst_file = relative_to_folder(src_file, from_folder, to_folder) - # call handler - fct_proc_file(name, src_file, dst_file) def setup_logging() -> None: """ diff --git a/scripts/uv.lock b/scripts/uv.lock index 689444e..5115a27 100644 --- a/scripts/uv.lock +++ b/scripts/uv.lock @@ -131,7 +131,7 @@ wheels = [ ] [[package]] -name = "tools" +name = "scripts" version = "1.0.0" source = { virtual = "." } dependencies = [ diff --git a/scripts/validate_json.py b/scripts/validate_json.py index 4d217cb..4c24d49 100644 --- a/scripts/validate_json.py +++ b/scripts/validate_json.py @@ -156,7 +156,7 @@ class Prototypes(RootModel): def validate_json() -> None: - raw_json_folder = common.get_plugin_folder() / 'raw_jsons' + raw_json_folder = common.get_root_folder() / 'raw_jsons' for json_file in raw_json_folder.rglob('*.json'): logging.info(f'Validating {json_file} ...')