Files
2026-10-31 20:16:32 +01:00

102 lines
5.8 KiB
Python

"""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]