"""Frame-based Sequencer helpers. Ranges are [start, end), rotations roll/pitch/yaw.""" import unreal from .paths import GENERATED, slug class Sequence: def __init__(self, scene, name="Shot", fps=24, frames=48): if not isinstance(fps, int) or fps < 1 or not isinstance(frames, int) or frames < 1: raise ValueError("FPS i liczba klatek musza byc dodatnimi liczbami calkowitymi.") self.scene, self.fps, self.frames = scene, fps, frames path = f"{GENERATED}/{scene.name}/LS_{slug(name)}" self.asset = unreal.load_asset(path) if self.asset and unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence() == self.asset: unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence() if self.asset is None: self.asset = unreal.AssetToolsHelpers.get_asset_tools().create_asset( "LS_" + name, f"{GENERATED}/{scene.name}", unreal.LevelSequence, unreal.LevelSequenceFactoryNew()) for binding in self.asset.get_bindings(): binding.remove() for track in self.asset.get_tracks(): self.asset.remove_track(track) self.asset.set_display_rate(unreal.FrameRate(fps, 1)) self.asset.set_playback_start(0) self.asset.set_playback_end(frames) self._bindings = {} self._transforms = {} def bind(self, actor): key = actor.get_path_name() if key not in self._bindings: self._bindings[key] = self.asset.add_possessable(actor) return self._bindings[key] def camera(self, actor): binding = self.bind(actor) tracks = self.asset.find_tracks_by_type(unreal.MovieSceneCameraCutTrack) track = tracks[0] if tracks else self.asset.add_track(unreal.MovieSceneCameraCutTrack) for old in track.get_sections(): track.remove_section(old) section = track.add_section() section.set_range(0, self.frames) binding_id = unreal.MovieSceneObjectBindingID() binding_id.set_editor_property("guid", binding.get_id()) section.set_camera_binding_id(binding_id) def transform_key(self, actor, frame, location=None, rotation=None, scale=None, interpolation="linear"): if not isinstance(frame, int) or not 0 <= frame < self.frames: raise ValueError(f"Klatka musi nalezec do 0..{self.frames - 1}.") modes = {"linear": unreal.MovieSceneKeyInterpolation.LINEAR, "auto": unreal.MovieSceneKeyInterpolation.AUTO, "constant": unreal.MovieSceneKeyInterpolation.CONSTANT} if interpolation not in modes: raise ValueError("Interpolacja: linear, auto lub constant.") key = actor.get_path_name() if key not in self._transforms: track = self.bind(actor).add_track(unreal.MovieScene3DTransformTrack) section = track.add_section() section.set_range(0, self.frames) channels = section.get_all_channels() initial = actor.get_actor_transform() r = initial.rotation.rotator() defaults = [*initial.translation.to_tuple(), r.roll, r.pitch, r.yaw, *initial.scale3d.to_tuple()] for channel, value in zip(channels, defaults): channel.set_default(float(value)) self._transforms[key] = channels channels = self._transforms[key] for offset, values in [(0, location), (3, rotation), (6, scale)]: if values is not None: if len(values) != 3: raise ValueError("Transformacja wymaga trzech wartosci.") for channel, value in zip(channels[offset:offset + 3], values): channel.add_key(unreal.FrameNumber(frame), float(value), interpolation=modes[interpolation]) def save(self, open_editor=True): self.scene.save() unreal.EditorAssetLibrary.save_loaded_asset(self.asset) if open_editor: unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(self.asset)