"""An isolated generated map, simple meshes and a ready-to-use character.""" import unreal from .paths import GENERATED, slug class Scene: """Owns one map. Building again replaces only that generated map's actors.""" def __init__(self, name="MyShot"): from . import render if render.is_rendering() or unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem).is_rendering(): raise RuntimeError("Poczekaj na zakonczenie renderu przed przebudowa sceny.") # Discard Sequencer's live bindings before replacing their actors. current_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence() if current_sequence and current_sequence.get_path_name().startswith(GENERATED + "/"): unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence() self.name = slug(name) self.map_path = f"{GENERATED}/{name}/Map" self.subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) current = editor.get_current_level() current_path = current.get_outer().get_path_name().split(".")[0] if current else "" if current_path == self.map_path: # This map is wholly generated by this exercise, never a user's other map. for actor in self.subsystem.get_all_level_actors(): if "Workshop" in [str(tag) for tag in actor.tags]: self.subsystem.destroy_actor(actor) else: if unreal.EditorLoadingAndSavingUtils.get_dirty_map_packages(): raise RuntimeError("Zapisz zmiany aktualnej mapy przed utworzeniem sceny warsztatowej.") if unreal.EditorAssetLibrary.does_asset_exist(self.map_path): if not editor.load_level(self.map_path): raise RuntimeError("Nie mozna otworzyc " + self.map_path) for actor in self.subsystem.get_all_level_actors(): if "Workshop" in [str(tag) for tag in actor.tags]: self.subsystem.destroy_actor(actor) elif not editor.new_level(self.map_path): raise RuntimeError("Nie mozna utworzyc " + self.map_path) def spawn(self, actor_class, name, location=(0, 0, 0), rotation=(0, 0, 0)): actor = self.subsystem.spawn_actor_from_class(actor_class, unreal.Vector(*location), unreal.Rotator(*rotation)) if actor is None: raise RuntimeError("Nie mozna utworzyc aktora " + name) actor.set_actor_label(name) actor.tags = ["Workshop"] actor.set_folder_path("Workshop") return actor def mesh(self, asset, name="Model", location=(0, 0, 0), scale=(1, 1, 1), material=None): mesh = unreal.load_asset(asset) if isinstance(asset, str) else asset if not isinstance(mesh, unreal.StaticMesh): raise TypeError(f"Oczekiwano StaticMesh: {asset}") actor = self.spawn(unreal.StaticMeshActor, name, location) actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) actor.static_mesh_component.set_static_mesh(mesh) actor.set_actor_scale3d(unreal.Vector(*scale)) if material is not None: actor.static_mesh_component.set_material(0, material) return actor def primitive(self, shape="Cube", **kwargs): if shape not in {"Cube", "Sphere", "Cylinder", "Cone", "Plane"}: raise ValueError("Ksztalt: Cube, Sphere, Cylinder, Cone lub Plane.") return self.mesh(f"/Engine/BasicShapes/{shape}.{shape}", **kwargs) def character(self, material=None, head_scale=1.0): root = self.spawn(unreal.StaticMeshActor, "Character") root.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) for shape, name, position, scale in [ ("Cube", "Body", (0, 0, 90), (0.55, 0.8, 0.9)), ("Sphere", "Head", (0, 0, 175), (0.7 * head_scale,) * 3), ("Cube", "LeftLeg", (0, -23, 25), (0.35, 0.3, 0.5)), ("Cube", "RightLeg", (0, 23, 25), (0.35, 0.3, 0.5)), ("Sphere", "Nose", (34, 0, 177), (0.24, 0.2, 0.2)), ]: actor = self.primitive(shape, name=name, location=position, scale=scale, material=material) actor.attach_to_actor(root, "", unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False) return root def save(self): editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) if not editor.save_current_level(): raise RuntimeError("Nie mozna zapisac mapy " + self.map_path)