import json import pathlib import sys import bpy NIL_UUID = "00000000-0000-0000-0000-000000000000" def catalog_records(path): version = None records = [] for line_number, raw_line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): line = raw_line.strip() if not line or line.startswith("#"): continue if version is None: if not line.startswith("VERSION "): raise RuntimeError(f"catalog definition line {line_number} has no version marker") version = int(line.removeprefix("VERSION ")) continue catalog_id, catalog_path, simple_name = line.split(":", 2) records.append({ "catalogId": catalog_id, "path": catalog_path, "simpleName": simple_name.strip(), "parentPath": catalog_path.rpartition("/")[0] or None, }) return version, records def custom_property_value(value): if isinstance(value, (bool, int, float, str)): return value if hasattr(value, "to_list"): return value.to_list() if isinstance(value, (list, tuple)): return list(value) raise RuntimeError(f"unsupported asset custom property type {type(value).__name__}") def asset_record(id_type, asset): metadata = asset.asset_data custom_properties = [ { "name": name, "type": type(metadata[name]).__name__.upper(), "value": custom_property_value(metadata[name]), } for name in sorted(metadata.keys()) ] return { "idType": id_type, "name": asset.name, "catalogId": metadata.catalog_id or NIL_UUID, "catalogSimpleName": metadata.catalog_simple_name, "author": metadata.author, "description": metadata.description, "copyright": metadata.copyright, "license": metadata.license, "tags": [tag.name for tag in metadata.tags], "activeTag": metadata.active_tag, "usePreferredImportMethod": metadata.use_preferred_import_method, "preferredImportMethod": metadata.preferred_import_method, "customProperties": custom_properties, } def main(catalog_file, output_file): catalog_path = pathlib.Path(catalog_file).resolve() output_path = pathlib.Path(output_file).resolve() version, catalogs = catalog_records(catalog_path) assets = [] for id_type, collection in ( ("MATERIAL", bpy.data.materials), ("OBJECT", bpy.data.objects), ("WORLD", bpy.data.worlds), ): assets.extend(asset_record(id_type, asset) for asset in collection if asset.asset_data is not None) assets.sort(key=lambda item: (item["idType"], item["name"])) report = { "schemaVersion": 1, "task": "M12-01B", "enablingTask": True, "parityStateChange": False, "blenderVersion": ".".join(str(value) for value in bpy.app.version), "catalogDefinition": { "fileName": catalog_path.name, "version": version, "records": catalogs, }, "assets": assets, "nextTask": "M12-01C", } output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(json.dumps(report, indent=2, ensure_ascii=True) + "\n", encoding="utf-8", newline="\n") print(f"m12-asset-catalog-v1-canonical catalogs={len(catalogs)} assets={len(assets)} output={output_path}") if __name__ == "__main__": arguments = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] if len(arguments) != 2: raise SystemExit("usage: blender --background FIXTURE --python export-asset-catalog-v1.py -- CATALOG_FILE OUTPUT_JSON") main(*arguments)