"""Automated static mesh imports using the engine's enabled import pipelines.""" from pathlib import Path import unreal from .paths import GENERATED, PROJECT, slug def _existing_model(name): destination = f"{GENERATED}/Imports/{slug(name)}" if not unreal.EditorAssetLibrary.does_directory_exist(destination): return None meshes = [unreal.load_asset(path) for path in unreal.EditorAssetLibrary.list_assets(destination, recursive=True)] meshes = [obj for obj in meshes if isinstance(obj, unreal.StaticMesh)] return meshes[0] if len(meshes) == 1 else None def import_model(filename, name="Character", reimport=True): if not reimport: mesh = _existing_model(name) if mesh is not None: return mesh source = Path(filename).expanduser() source = (source if source.is_absolute() else PROJECT / source).resolve() if not source.is_file(): raise FileNotFoundError(f"Brak modelu: {source}") if source.suffix.lower() not in {".glb", ".gltf", ".fbx", ".obj"}: raise ValueError("Model musi byc plikiem GLB, glTF, FBX lub OBJ.") destination = f"{GENERATED}/Imports/{slug(name)}" task = unreal.AssetImportTask() task.set_editor_property("filename", str(source)) task.set_editor_property("destination_path", destination) task.set_editor_property("destination_name", name) task.set_editor_property("automated", True) task.set_editor_property("replace_existing", True) task.set_editor_property("replace_existing_settings", True) task.set_editor_property("save", True) task.set_editor_property("async_", False) if source.suffix.lower() in {".glb", ".gltf"}: # Defaults reuse existing materials without updating them. Our generated # import folder belongs to the source file, so apply source properties. template = unreal.load_asset("/Interchange/Pipelines/DefaultGLTFAssetsPipeline") pipeline = unreal.load_asset(GENERATED + "/Pipelines/GLTFWorkshop") if pipeline is None: pipeline = unreal.AssetToolsHelpers.get_asset_tools().duplicate_asset( "GLTFWorkshop", GENERATED + "/Pipelines", template) pipeline.set_editor_property("reimport_strategy", unreal.ReimportStrategyFlags.APPLY_PIPELINE_PROPERTIES) pipeline.common_meshes_properties.set_editor_property( "force_all_mesh_as_type", unreal.InterchangeForceMeshType.IFMT_STATIC_MESH) pipeline.mesh_pipeline.set_editor_property("combine_static_meshes", True) pipeline.mesh_pipeline.set_editor_property("import_skeletal_meshes", False) pipeline.material_pipeline.set_editor_property("import_materials", True) # PBR images are imported explicitly into WorkshopTextures below. This # avoids Interchange's dependency-preserving material reimport behavior. pipeline.material_pipeline.texture_pipeline.set_editor_property("import_textures", False) pipeline.set_editor_property("scene_name_sub_folder", True) pipeline.set_editor_property("asset_type_sub_folders", True) pipeline.material_pipeline.set_editor_property( "search_location", unreal.InterchangeMaterialSearchLocation.DO_NOT_SEARCH) pipeline.material_pipeline.set_editor_property( "material_import", unreal.InterchangeMaterialImportOption.IMPORT_AS_MATERIAL_INSTANCES) unreal.EditorAssetLibrary.save_loaded_asset(pipeline) options = unreal.InterchangePipelineStackOverride() options.set_editor_property("override_pipelines", [ unreal.SoftObjectPath(pipeline.get_path_name()), unreal.SoftObjectPath("/Interchange/Pipelines/DefaultGLTFPipeline.DefaultGLTFPipeline")]) task.set_editor_property("options", options) if source.suffix.lower() in {".fbx", ".obj"}: options = unreal.FbxImportUI() options.set_editor_property("import_mesh", True) options.set_editor_property("import_as_skeletal", False) options.set_editor_property("import_animations", False) options.set_editor_property("mesh_type_to_import", unreal.FBXImportType.FBXIT_STATIC_MESH) options.set_editor_property("automated_import_should_detect_type", False) options.static_mesh_import_data.set_editor_property("combine_meshes", True) task.set_editor_property("options", options) unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) meshes = [obj for obj in task.get_objects() if isinstance(obj, unreal.StaticMesh)] if len(meshes) != 1: raise RuntimeError( f"Import {source.name}: oczekiwano jednego StaticMesh, otrzymano {len(meshes)}. " "Plik musi zawierac statyczna geometrie modelu.") if source.suffix.lower() in {".glb", ".gltf"}: # UE preserves material edits on mesh reimport. In this workflow Blender # owns the look; explicitly synchronize the supported PBR parameters. from ._gltf import link_textures link_textures(source, meshes[0], destination) else: # Optional single-atlas convention, also used by the bundled FBX demos. files = {file.stem.lower(): file for file in source.parent.iterdir() if file.suffix.lower() in {".png", ".jpg", ".jpeg"}} maps = {parameter: files.get(source.stem.lower() + suffix) for suffix, parameter in (("_bc", "base_color"), ("_n", "normal"), ("_r", "roughness"), ("_m", "metallic"), ("_e", "emissive"))} if maps["base_color"] is not None: from .materials import texture_set material = texture_set(name + "Imported", **maps) for index in range(len(meshes[0].static_materials)): meshes[0].set_material(index, material) unreal.EditorAssetLibrary.save_loaded_asset(meshes[0]) return meshes[0]