/
Manifest
/
Real_Texture
Обзор
Документация
Войти
/
Manifest
/
Real_Texture
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ToolsPythonForSequenceInUE/init_unreal_backup_#1.py
1 389 строк
70 KB
m.samodurov
init commit for The Invaders
17 фев 2026, 04:57
17 фев 2026, 04:57
4a668d4
Код
Авторство
О чём код?
import unreal class Cinecamera(object): def __init__(self, TransformStruct, Transform, CurrentFocalLength, ManualFocusDistance, CurrentAperture): self.TransformStruct = TransformStruct self.Transform = Transform self.CurrentFocalLength = CurrentFocalLength self.ManualFocusDistance = ManualFocusDistance self.CurrentAperture = CurrentAperture def get_camera(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() camArr = unreal.GameplayStatics().get_all_actors_of_class(episodeLVL, actor_class=unreal.CineCameraActor) if camArr: shotCam = camArr[0] shotCamTfm = shotCam.get_actor_transform() shotCamRot = shotCam.get_actor_rotation() self.TransformStruct = shotCamTfm tx = shotCamTfm.get_editor_property('translation').get_editor_property('x') ty = shotCamTfm.get_editor_property('translation').get_editor_property('y') tz = shotCamTfm.get_editor_property('translation').get_editor_property('z') rx = shotCamRot.get_editor_property('roll') ry = shotCamRot.get_editor_property('pitch') rz = shotCamRot.get_editor_property('yaw') sx = shotCamTfm.get_editor_property('scale3d').get_editor_property('x') sy = shotCamTfm.get_editor_property('scale3d').get_editor_property('y') sz = shotCamTfm.get_editor_property('scale3d').get_editor_property('z') self.Transform = [tx, ty, tz, rx, ry, rz, sx, sy, sz] cine_cam_component = shotCam.get_cine_camera_component() self.CurrentFocalLength = cine_cam_component.current_focal_length self.CurrentAperture = cine_cam_component.current_aperture focusSetings = cine_cam_component.focus_settings self.ManualFocusDistance = focusSetings.manual_focus_distance print('camera Transform, CurrentFocalLength, CurrentAperture, ManualFocusDistance successfully copied') else: print('no CineCameraActor found') def set_camera(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() levelSequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence() frame = unreal.LevelSequenceEditorBlueprintLibrary.get_current_time() camArr = unreal.GameplayStatics().get_all_actors_of_class(episodeLVL, actor_class = unreal.CineCameraActor) if camArr: shotCam = camArr[0] else: print('no camera found') return if levelSequence: tracks = levelSequence.get_tracks() if tracks: for track in tracks: classtarget = str(track.get_class()) if 'CinematicShotTrack' in classtarget: sections = track.get_sections() for section in sections: startFrame = section.get_start_frame() endFrame = section.get_end_frame() if startFrame <= frame < endFrame: camsequence = section.get_sequence() shotSeqPlaybackEnd = camsequence.get_playback_end() shot_tracks = camsequence.get_tracks() for shot_track in shot_tracks: if 'MovieSceneCameraCutTrack' in str(shot_track): cameraTrack = shot_track cameraSections = cameraTrack.get_sections() if cameraSections: for section in cameraSections: cameraTrack.remove_section(section) cambindproxies = camsequence.get_possessables() if cambindproxies: for proxy in cambindproxies: proxy.remove() shotCam.set_actor_transform(self.TransformStruct, False, False) cineCamComponent = shotCam.get_cine_camera_component() cineCamComponent.set_editor_property('current_focal_length', self.CurrentFocalLength) cineCamComponent.set_editor_property('current_aperture', self.CurrentAperture) focusSetings = cineCamComponent.focus_settings focusSetings.manual_focus_distance = self.ManualFocusDistance camBinding = camsequence.add_possessable(shotCam) camComponentBinding = camsequence.add_possessable(cineCamComponent) camTransformTrack = camBinding.add_track(unreal.MovieScene3DTransformTrack) camTransformTrack.set_property_name_and_path('Transform', 'Transform') transformSection = camTransformTrack.add_section() transformSection.set_end_frame(shotSeqPlaybackEnd) transformSection.set_start_frame(1) channels = transformSection.get_all_channels() for i, channel in enumerate(channels): channel.set_default(self.Transform[i]) focalLenghtTrack = camComponentBinding.add_track(unreal.MovieSceneFloatTrack) focalLenghtTrack.set_property_name_and_path('CurrentFocalLength', 'CurrentFocalLength') focalLenghtSection = focalLenghtTrack.add_section() focalLenghtSection.set_end_frame(shotSeqPlaybackEnd) focalLenghtSection.set_start_frame(1) focalChannel = focalLenghtSection.get_all_channels()[0] focalChannel.set_default(self.CurrentFocalLength) apertureTrack = camComponentBinding.add_track(unreal.MovieSceneFloatTrack) apertureTrack.set_property_name_and_path('CurrentAperture', 'CurrentAperture') apertureSection = apertureTrack.add_section() apertureSection.set_end_frame(shotSeqPlaybackEnd) apertureSection.set_start_frame(1) apertureChannel = apertureSection.get_all_channels()[0] apertureChannel.set_default(self.CurrentAperture) focusTrack = camComponentBinding.add_track(unreal.MovieSceneFloatTrack) focusTrack.set_property_name_and_path('ManualFocusDistance(FocusSettings)', 'FocusSettings.ManualFocusDistance') focusSection = focusTrack.add_section() focusSection.set_end_frame(shotSeqPlaybackEnd) focusSection.set_start_frame(1) focusChannel = focusSection.get_all_channels()[0] focusChannel.set_default(self.ManualFocusDistance) unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence() cameraSection = cameraTrack.add_section() cameraSection.set_end_frame(shotSeqPlaybackEnd) cameraSection.set_start_frame(1) cameraBindingID = camsequence.get_binding_id(camBinding) cameraSection.set_camera_binding_id(cameraBindingID) class SequenceCopier: def __init__(self): self.copied = None # Мэппинг трек-классов (расширяется по необходимости) self.TRACK_CLASS_MAP = { "MovieScene3DTransformTrack": unreal.MovieScene3DTransformTrack, "MovieSceneSkeletalAnimationTrack": unreal.MovieSceneSkeletalAnimationTrack, "MovieSceneControlRigParameterTrack": unreal.MovieSceneControlRigParameterTrack, "MovieSceneVisibilityTrack": unreal.MovieSceneVisibilityTrack, } # ----------------------- # ЛОГ # ----------------------- def log(self, msg): unreal.log(msg) def warn(self, msg): unreal.log_warning(msg) def err(self, msg): unreal.log_error(msg) # ----------------------- # ХЕЛПЕРЫ: frame/time # ----------------------- def _frame_time_to_framenumber(self, ft): """Пытаемся получить номер кадра из FrameTime/FrameNumber""" if ft is None: return 0 try: # если ft — MovieSceneFrameTime-like fn = ft.frame_number if hasattr(fn, "value"): return int(fn.value) return int(fn) except Exception: try: return int(ft) except Exception: return 0 # ----------------------- # ХЕЛПЕРЫ: actor info # ----------------------- def _get_actor_asset_info(self, actor): """ Возвращает словарь с info об actor: class_path, label, static_mesh_path, skeletal_mesh_path. Работает для StaticMeshActor и SkeletalMeshActor. Можно расширить """ try: data = { "class_path": actor.get_class().get_path_name(), "label": actor.get_actor_label(), "world_path": None, # оставляем, если нужно искать по path "static_mesh": None, "skeletal_mesh": None } # Пытаемся получить статический меш try: if isinstance(actor, unreal.StaticMeshActor): smc = actor.static_mesh_component if smc and smc.get_static_mesh(): data["static_mesh"] = smc.get_static_mesh().get_path_name() except Exception: pass # Пытаемся получить skeletal mesh try: if isinstance(actor, unreal.SkeletalMeshActor): skc = actor.skeletal_mesh_component # для разных версий UE - имеют разные accessors if hasattr(skc, "get_skeletal_mesh_asset"): skm = skc.get_skeletal_mesh_asset() else: skm = getattr(skc, "skeletal_mesh", None) if skm: data["skeletal_mesh"] = skm.get_path_name() except Exception: pass return data except Exception as e: self.warn(f"[_get_actor_asset_info] error: {e}") return None def _spawn_actor_from_info(self, world, info): """ Создаёт/заспавнит актёра в текущем активном уровне (LevelEditorSubsystem.set_current_level_by_name должен быть вызван заранее), выставляет label и присваивает меши если нужно. """ try: if not info: return None cls_path = info.get("class_path") if not cls_path: self.warn("[_spawn_actor_from_info] class_path missing in info") return None cls = unreal.load_object(None, cls_path) if not cls: self.warn(f"[PASTE] Не удалось загрузить класс: {cls_path}") return None # Spawn в текущем уровне (EditorLevelLibrary.spawn_actor_from_class использует active level) actor = unreal.EditorLevelLibrary.spawn_actor_from_class(cls, unreal.Vector(0,0,0)) if not actor: self.warn(f"[PASTE] Не удалось заспавнить актёра класса: {cls_path}") return None # выставляем label, если есть if info.get("label"): try: actor.set_actor_label(info.get("label"), mark_dirty=True) except Exception: pass # назначаем меши если есть try: if info.get("static_mesh") and isinstance(actor, unreal.StaticMeshActor): sm = unreal.load_object(None, info["static_mesh"]) if sm: actor.static_mesh_component.set_static_mesh(sm) if info.get("skeletal_mesh") and isinstance(actor, unreal.SkeletalMeshActor): sk = unreal.load_object(None, info["skeletal_mesh"]) if sk: # современный метод если есть, иначе деприкейтный try: actor.skeletal_mesh_component.set_skinned_asset_and_update(sk) except Exception: try: actor.skeletal_mesh_component.set_skeletal_mesh(sk) except Exception: pass except Exception: pass return actor except Exception as e: self.err(f"[PASTE] Ошибка спавна: {e}") return None # ----------------------- # ХЕЛПЕРЫ: безопасное имя канала # ----------------------- def _safe_channel_name(self, ch): # пытаемся взять display_name, иначе get_name, иначе класс try: if hasattr(ch, "get_display_name"): dn = ch.get_display_name() if dn: return str(dn) except Exception: pass try: if hasattr(ch, "get_name"): return str(ch.get_name()) except Exception: pass try: return ch.get_class().get_name() except Exception: return "Channel" # ================================================== # COPY # ================================================== def copyProps(self): """ Копирует active LevelSequence: bindings -> tracks -> sections -> каналы -> ключи. Сохраняет информацию об объекте (если есть привязанный actor). Сохраняет также interpolation/tangent modes ключей. """ self.copied = {"fps": None, "display_rate": None, "bindings": []} source_seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence() if not source_seq: self.err("Нет открытого Level Sequence") return self.log(f"Copying from Sequence: {source_seq.get_name()}") # display rate try: dr = source_seq.get_display_rate() self.copied["display_rate"] = dr self.copied["fps"] = float(dr.numerator) / float(dr.denominator if dr.denominator else 1) self.log(f"DisplayRate: {dr.numerator}/{dr.denominator}") except Exception: self.copied["display_rate"] = unreal.FrameRate(25,1) self.copied["fps"] = 25.0 bindings = source_seq.get_bindings() self.log(f"Found {len(bindings)} bindings") for binding in bindings: b_name = binding.get_name() binding_id = source_seq.get_binding_id(binding) if hasattr(source_seq, "get_binding_id") else None # Получить объекты, привязанные к binding (possessable) actor_info = None binding_type = "unknown" try: if binding_id is not None: objs = unreal.LevelSequenceEditorBlueprintLibrary.get_bound_objects(binding_id) else: # fallback: try binding.get_bound_objects? ебучая API objs = binding.get_bound_objects() if hasattr(binding, "get_bound_objects") else [] if objs and len(objs) > 0: # берем первый объект actor_info = self._get_actor_asset_info(objs[0]) binding_type = "possessable" except Exception as e: self.warn(f"[COPY] Не удалось получить объект биндинга '{b_name}': {e}") actor_info = None # Получить spawnable template (если bind был spawnable) spawnable_class_path = None try: # binding может иметь object template (spawnable) if hasattr(binding, "get_object_template"): templ = binding.get_object_template() if templ: try: spawnable_class_path = templ.get_class().get_path_name() if binding_type == "unknown": binding_type = "spawnable" except Exception: spawnable_class_path = None except Exception: pass binding_data = { "name": b_name, "type": binding_type, "actor_info": actor_info, "spawnable_class_path": spawnable_class_path, "tracks": [] } for track in binding.get_tracks(): track_data = { "class_name": track.get_class().get_name(), "name": track.get_name(), "sections": [] } for section in track.get_sections(): sec_channels = [] # сохранение каналов в порядке (index) с безопасным именем и ключами for idx, ch in enumerate(section.get_all_channels()): ch_name = self._safe_channel_name(ch) keys_dump = [] # Сохраняем для каждого ключа: time, value, interpolation, tangent try: ch_keys = ch.get_keys() except Exception: ch_keys = [] for k in ch_keys: try: # безопасно получить время/значение key_time = None key_value = None try: key_time = k.get_time() except Exception: pass try: key_value = k.get_value() except Exception: pass # попытка получить interpolation/tangent (если доступны) interp = None tang = None try: interp = getattr(k, "get_interpolation_mode", lambda: None)() except Exception: interp = None try: tang = getattr(k, "get_tangent_mode", lambda: None)() except Exception: tang = None keys_dump.append({ "time": key_time, "value": key_value, "interp": str(interp) if interp is not None else None, "tangent": str(tang) if tang is not None else None }) except Exception: # fallback minimal try: keys_dump.append({"time": k.get_time(), "value": k.get_value()}) except Exception: pass sec_channels.append({ "index": idx, "class": ch.get_class().get_name(), "name": ch_name, "keys": keys_dump }) track_data["sections"].append({"channels": sec_channels}) binding_data["tracks"].append(track_data) self.copied["bindings"].append(binding_data) self.log("Copy finished successfully.") # ================================================== # ХЕЛПЕР: найти актера по label, но ограничить активным подуровнем # ================================================== def find_actor_by_label_in_active_level(self, label, all_actors, active_level_name): if not label: return None for a in all_actors: try: if a.get_actor_label() != label: continue # попытка определить level/streaming level name актера try: actor_level = a.get_level() # ULevel if actor_level: outer = actor_level.get_outer() # может быть как ULevelStreaming, так UWorld # несколько вариантов, чтобы получить string name/path lvl_name = None try: lvl_name = outer.get_name() except Exception: try: lvl_name = outer.get_path_name() except Exception: lvl_name = None if lvl_name and active_level_name and active_level_name in lvl_name: return a # если мы не можем достоверно определить level name, пропускаем этого actor и продолжаем поиск continue except Exception: # невозможно определить actor level — пропускаем (предпочтительно спавн) continue except Exception: continue return None # =========================== # PASTE. Работает только при одном открытом Viewport'е. НЕ ОТКРЫВАТЬ БОЛЬШЕ ИЛИ МЕНЬШЕ ОДНОГО # =========================== def pasteAddProps(self): if not self.copied: self.err("Нечего вставлять. Сначала сделайте copyProps()") return target_seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence() if not target_seq: self.err("Нет открытого Level Sequence для вставки") return self.log(f"Pasting into Sequence: {target_seq.get_name()}") # activate sublevel seq_name = target_seq.get_name() base_name = seq_name[:-6] if seq_name.endswith("_LSQ_1") else seq_name sublevel_name = base_name + "_LVL_1" level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) success = level_editor_subsystem.set_current_level_by_name(sublevel_name) if success: self.log(f"Activated Sub-level: {sublevel_name}") else: self.warn(f"Sub-level {sublevel_name} не найден — спавн будет в текущем уровне") # editor_world (нужен для поиска/спавна) editor_world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() # применяем display rate, если есть dr = self.copied.get("display_rate") if dr: try: target_seq.set_display_rate(dr) self.log(f"DisplayRate applied: {dr.numerator}/{dr.denominator}") except Exception as e: self.warn(f"[PASTE] Не удалось применить display_rate: {e}") # подготовим список всех текущих актёров в мире для поиска по label (ускорит поиск) try: all_actors = unreal.EditorLevelLibrary.get_all_level_actors() except Exception: all_actors = [] # --- используем новую функцию поиска (ищем ТОЛЬКО в активном sublevel) --- def find_actor_in_active(label): try: # Попытаемся получить имя активного уровня/стриминга (в разных UE API это может быть иначе) active_level_obj = None try: # LevelEditorSubsystem может иметь get_current_level; иначе используем sublevel_name active_level_obj = level_editor_subsystem.get_current_level() if hasattr(level_editor_subsystem, "get_current_level") else None except Exception: active_level_obj = None active_level_name_str = None if active_level_obj: try: active_level_name_str = active_level_obj.get_name() except Exception: try: active_level_name_str = active_level_obj.get_path_name() except Exception: active_level_name_str = sublevel_name else: active_level_name_str = sublevel_name return self.find_actor_by_label_in_active_level(label, all_actors, active_level_name_str) except Exception: return None # Интерация по сохранённым биндингам for i, b in enumerate(self.copied["bindings"], 1): b_name = b.get("name") b_type = b.get("type", "unknown") actor_info = b.get("actor_info") spawnable_class_path = b.get("spawnable_class_path") self.log(f" [{i}/{len(self.copied['bindings'])}] Binding: {b_name} (type={b_type})") # создаём binding proxy по типу binding_proxy = None try: if b_type == "possessable" and actor_info: # сначала пробуем найти актёра именно в активном sublevel по label found_actor = find_actor_in_active(actor_info.get("label")) if found_actor: self.log(f" Found existing actor in active sublevel by label: {actor_info.get('label')}") binding_proxy = target_seq.add_possessable(found_actor) else: # не нашли — спавним прямо в активном уровне spawned = self._spawn_actor_from_info(editor_world, actor_info) if spawned: # выставляем label точно как в инфо (чтобы совпала нэйминг) try: spawned.set_actor_label(actor_info.get("label"), mark_dirty=True) except Exception: pass self.log(f" Spawned actor for possessable in active sublevel: {actor_info.get('label')}") binding_proxy = target_seq.add_possessable(spawned) else: self.warn(f" Не удалось создать actor for possessable '{actor_info.get('label')}', fallback to spawnable") # fallback: если не получилось — попробуем spawnable из saved class if spawnable_class_path: cls = unreal.load_object(None, spawnable_class_path) if cls: binding_proxy = target_seq.add_spawnable_from_class(cls) else: binding_proxy = target_seq.add_spawnable_from_class(unreal.Actor) else: binding_proxy = target_seq.add_spawnable_from_class(unreal.Actor) elif b_type == "spawnable" and spawnable_class_path: cls = unreal.load_object(None, spawnable_class_path) if cls: binding_proxy = target_seq.add_spawnable_from_class(cls) self.log(f" Created spawnable from class: {spawnable_class_path}") else: binding_proxy = target_seq.add_spawnable_from_class(unreal.Actor) self.warn(f" spawnable class not found: {spawnable_class_path}, used Actor") else: # fallback: если нет инфы, просто создаём spawnable Actor binding_proxy = target_seq.add_spawnable_from_class(unreal.Actor) self.warn(f" Unknown binding type or missing info — created generic spawnable Actor") except Exception as e: self.warn(f"[PASTE] Не удалось создать binding proxy: {e}") continue # --- далее идёт вставка треков/секций/ключей --- for track_idx, track_data in enumerate(b.get("tracks", [])): track_class_name = track_data.get("class_name") track_name = track_data.get("name") track_class = self.TRACK_CLASS_MAP.get(track_class_name) if not track_class: self.warn(f" Track class not found in TRACK_CLASS_MAP: {track_class_name} — пропускаем") continue try: new_track = binding_proxy.add_track(track_class) self.log(f" Track: {track_class_name} ({track_name})") except Exception as e: self.warn(f" Не удалось добавить трек {track_name}: {e}") continue # логика создания секций и вставки ключей for section_idx, section_data in enumerate(track_data.get("sections", [])): # вычисляем min/max кадры по всем каналам секции min_frame = None max_frame = None for ch in section_data.get("channels", []): for k in ch.get("keys", []): # теперь ключи — dict с полями time/value key_time = k.get("time") if isinstance(k, dict) else None fn = self._frame_time_to_framenumber(key_time) if min_frame is None or fn < min_frame: min_frame = fn if max_frame is None or fn > max_frame: max_frame = fn if min_frame is None: min_frame = 0 if max_frame is None: max_frame = min_frame + 1 try: new_section = new_track.add_section() except Exception as e: self.warn(f" Не удалось создать секцию: {e}") continue try: new_section.set_range(-10000, 10000) self.log(" Section range set to infinite (-10000 → 10000)") except Exception as e: self.warn(f" Не удалось установить бесконечный диапазон секции: {e}") for tchan in new_section.get_all_channels(): try: existing_keys = tchan.get_keys() if existing_keys: for kk in existing_keys: try: tchan.remove_key(kk) except Exception: pass self.log(f" Removed default keys: {tchan.get_class().get_name()}") except Exception: pass target_channels = new_section.get_all_channels() used_target_idx = set() for ch_dict in section_data.get("channels", []): src_idx = ch_dict.get("index", None) src_class = ch_dict.get("class") src_name = ch_dict.get("name") matched_target = None if src_idx is not None and src_idx < len(target_channels) and src_idx not in used_target_idx: matched_target = target_channels[src_idx] used_target_idx.add(src_idx) self.log(f" Channel match by index: src {src_idx} -> target {src_idx} ({src_name})") else: for ti, tc in enumerate(target_channels): if ti in used_target_idx: continue try: tname = self._safe_channel_name(tc) except Exception: tname = tc.get_class().get_name() if (src_class == tc.get_class().get_name()) and (src_name == tname): matched_target = tc used_target_idx.add(ti) self.log(f" Channel match by name: src '{src_name}' -> target idx {ti}") break if matched_target is None: for ti, tc in enumerate(target_channels): if ti in used_target_idx: continue if src_class == tc.get_class().get_name(): matched_target = tc used_target_idx.add(ti) self.log(f" Channel fallback by class: src '{src_name}' -> target idx {ti}") break if matched_target is None: self.warn(f" Не удалось сопоставить канал '{src_name}' (class {src_class}) — пропускаем") continue inserted = 0 # Вставляем ключи из словарей с сохранёнными свойствами for key_entry in ch_dict.get("keys", []): try: if isinstance(key_entry, dict): key_time = key_entry.get("time") key_value = key_entry.get("value") interp_str = key_entry.get("interp") tangent_str = key_entry.get("tangent") else: # fallback: старый формат (time, value) key_time, key_value = key_entry interp_str = None tangent_str = None frame_number = self._frame_time_to_framenumber(key_time) frame_num_obj = unreal.FrameNumber(frame_number) # add_key может вернуть объект ключа (proxy) либо None depending on API try: new_key = matched_target.add_key(frame_num_obj, key_value) except TypeError: # некоторые реализации expect raw int/framenum - попробуем передать int new_key = matched_target.add_key(frame_number, key_value) except Exception as e: # если не получилось добавить key с frame obj, пробуем упрощённо try: new_key = matched_target.add_key(frame_number, key_value) except Exception as e2: self.warn(f" Не удалось добавить ключ frame={frame_number} value={key_value}: {e2}") new_key = None # Восстанавливаем interpolation/tangent, если возможно if new_key is not None: # Иногда new_key — список/структура/объект — безопасно пробуем методы if interp_str: try: if hasattr(unreal, "RichCurveInterpMode"): for enum_value in unreal.RichCurveInterpMode: # сравниваем по имени enum (строка содержит имя) if enum_value.name in interp_str or enum_value.name == interp_str: try: new_key.set_interpolation_mode(enum_value) except Exception: # Если объект ключа не предоставляет set_interpolation_mode, игнорируем pass break except Exception: pass if tangent_str: try: if hasattr(unreal, "RichCurveTangentMode"): for enum_value in unreal.RichCurveTangentMode: if enum_value.name in tangent_str or enum_value.name == tangent_str: try: new_key.set_tangent_mode(enum_value) except Exception: pass break except Exception: pass inserted += 1 except Exception as e: self.warn(f" Не удалось добавить ключ: {e}") self.log(f" Inserted {inserted} keys into channel '{src_name}'") self.log("Paste finished successfully.") class shotLHandler(object): def __init__(self, Lights, actorTransforms): self.Lights = Lights self.actorTransforms = actorTransforms def getLightSeq(self): levelSequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence() if levelSequence: frame = unreal.LevelSequenceEditorBlueprintLibrary.get_current_time() tracks = levelSequence.get_tracks() if tracks: for track in tracks: trackName = track.get_display_name() if 'lighting_track' in str(trackName): sections = track.get_sections() if sections: for section in sections: startFrame = section.get_start_frame() endFrame = section.get_end_frame() if startFrame <= frame < endFrame: lightSequence = section.get_sequence() return lightSequence def getLightLVL(self): lightSequence = self.getLightSeq() lightTracks = lightSequence.get_tracks() for lightTrack in lightTracks: if 'MovieSceneLevelVisibilityTrack' in str(lightTrack): section = lightTrack.get_sections()[0] lightLVL = section.get_level_names()[0] return lightLVL def copyLights(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() actors = unreal.GameplayStatics().get_all_actors_of_class(episodeLVL, unreal.Actor) self.Lights.clear() self.actorTransforms.clear() lightSequence = self.getLightSeq() lightBindings = lightSequence.get_bindings() if lightBindings: for lightBinding in lightBindings: bindingTracks = lightBinding.get_tracks() for bindingTrack in bindingTracks: bindingSections = bindingTrack.get_sections() for bindingSection in bindingSections: bindingSection.set_completion_mode(unreal.MovieSceneCompletionMode.KEEP_STATE) lightLVL = self.getLightLVL() selectedActors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_selected_level_actors() if not selectedActors: for actor in actors: level = actor.get_level() levelPath = level.get_path_name() levelName = levelPath.split('.')[0] isLevelActor = actor.get_editor_property('can_be_damaged') defaultLabel = actor.get_default_actor_label() baselabels = ('SkyLight', 'PostProcessVolume', 'ExponentialHeightFog', 'SkyAtmosphere') if defaultLabel in baselabels : isLevelActor = True if levelName == lightLVL and isLevelActor: transform = actor.get_actor_transform() strActor = str(actor) self.Lights.append(actor) self.actorTransforms[strActor] = transform else: for actor in actors: level = actor.get_level() levelPath = level.get_path_name() levelName = levelPath.split('.')[0] isLevelActor = actor.get_editor_property('can_be_damaged') defaultLabel = actor.get_default_actor_label() baselabels = ('SkyLight', 'PostProcessVolume', 'ExponentialHeightFog', 'SkyAtmosphere') if defaultLabel in baselabels: isLevelActor = True if actor in selectedActors: isSelected = True else: isSelected = False if levelName == lightLVL and isLevelActor and isSelected: transform = actor.get_actor_transform() strActor = str(actor) self.Lights.append(actor) self.actorTransforms[strActor] = transform def addLights(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() lightLVL = self.getLightLVL() streamingLVL = unreal.GameplayStatics().get_streaming_level(episodeLVL, lightLVL) unreal.EditorLevelUtils().make_level_current(streamingLVL) for light in self.Lights: new_light = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).duplicate_actor(light) strActor = str(light) transform = self.actorTransforms.get(strActor) new_light.set_actor_transform(transform, False, True) def replaceLights(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() actors = unreal.GameplayStatics().get_all_actors_of_class(episodeLVL, unreal.Actor) actorsToDelete = [] lightLVL = self.getLightLVL() streamingLVL = unreal.GameplayStatics().get_streaming_level(episodeLVL, lightLVL) unreal.EditorLevelUtils().make_level_current(streamingLVL) lightSequence = self.getLightSeq() lightBindings = lightSequence.get_bindings() if lightBindings: for lightBinding in lightBindings: lightBinding.remove() for actor in actors: level = actor.get_level() levelPath = level.get_path_name() levelName = levelPath.split('.')[0] isLevelActor = actor.get_editor_property('can_be_damaged') defaultLabel = actor.get_default_actor_label() baselabels = ('SkyLight', 'PostProcessVolume', 'ExponentialHeightFog', 'SkyAtmosphere') if defaultLabel in baselabels: isLevelActor = True if levelName == lightLVL and isLevelActor: actorsToDelete.append(actor) unreal.get_editor_subsystem(unreal.EditorActorSubsystem).set_selected_level_actors(actorsToDelete) unreal.get_editor_subsystem(unreal.EditorActorSubsystem).delete_selected_actors(episodeLVL) # unreal.get_editor_subsystem(unreal.EditorActorSubsystem).destroy_actors(actorsToDelete) for light in self.Lights: new_light = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).duplicate_actor(light) strActor = str(light) transform = self.actorTransforms.get(strActor) new_light.set_actor_transform(transform, False, True) def updateLights(self): episodeLVL = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() actors = unreal.GameplayStatics().get_all_actors_of_class(episodeLVL, unreal.Actor) actorsToDelete = [] lightLVL = self.getLightLVL() streamingLVL = unreal.GameplayStatics().get_streaming_level(episodeLVL, lightLVL) unreal.EditorLevelUtils().make_level_current(streamingLVL) lightSequence = self.getLightSeq() lightBindings = lightSequence.get_bindings() lightLabels = [] newlightLabels = [] for light in self.Lights: lightLabel = light.get_actor_label(create_if_none=False) lightLabels.append(lightLabel) print('copied light labels', lightLabels) for actor in actors: level = actor.get_level() levelPath = level.get_path_name() levelName = levelPath.split('.')[0] isLevelActor = actor.get_editor_property('can_be_damaged') defaultLabel = actor.get_default_actor_label() baselabels = ('SkyLight', 'PostProcessVolume', 'ExponentialHeightFog', 'SkyAtmosphere') if defaultLabel in baselabels: isLevelActor = True if levelName == lightLVL and isLevelActor: label = actor.get_actor_label(create_if_none=False) if label in lightLabels: if lightBindings: for lightBinding in lightBindings: binding_name = lightBinding.get_name() if binding_name == label: lightBinding.remove() newlightLabels.append(label) actorsToDelete.append(actor) print('labels of actors to replace', newlightLabels) unreal.get_editor_subsystem(unreal.EditorActorSubsystem).set_selected_level_actors(actorsToDelete) unreal.get_editor_subsystem(unreal.EditorActorSubsystem).delete_selected_actors(episodeLVL) # unreal.get_editor_subsystem(unreal.EditorActorSubsystem).destroy_actors(actorsToDelete) for light in self.Lights: lightLabel = light.get_actor_label(create_if_none=False) if lightLabel in newlightLabels: new_light = unreal.get_editor_subsystem(unreal.EditorActorSubsystem).duplicate_actor(light) strActor = str(light) transform = self.actorTransforms.get(strActor) new_light.set_actor_transform(transform, False, True) def initialize_sequencer(): sequence = unreal.EditorAssetLibrary().load_asset('/Game/_library/tools/data/empty') unreal.get_editor_subsystem(unreal.AssetEditorSubsystem).open_editor_for_assets([sequence]) unreal.get_editor_subsystem(unreal.AssetEditorSubsystem).close_all_editors_for_asset(sequence) def add_py_button(menu_name = '', label = '', tool_tip = '', icon_style = '', icon_name = '', pycommand = ''): menus = unreal.ToolMenus.get() menu = menus.find_menu(menu_name) entry = unreal.ToolMenuEntry(type=unreal.MultiBlockType.TOOL_BAR_BUTTON) entry.set_label(label) entry.set_tool_tip(tool_tip) entry.set_icon(icon_style, icon_name) entry_type = unreal.ToolMenuStringCommandType.PYTHON command = pycommand value = entry.set_string_command(entry_type, "", command) menu.add_menu_entry('Content', entry) menus.refresh_all_widgets() # пока только для add_auto_animations() def add_py_button_section(menu_name='', label='', tool_tip='', icon_style='', icon_name='', pycommand=''): """ Добавляет комбокнопку в тулбар Unreal с выпадающим меню для выбора типа анимации. При выборе пункта задаёт unreal.SELECTED_ANIM_TYPE и вызывает указанный pycommand. """ menus = unreal.ToolMenus.get() menu = menus.find_menu(menu_name) if not menu: unreal.log_error(f"[AutoAnim] Меню '{menu_name}' не найдено!") return # Добавляем секцию в меню #menu.add_section("ManifestSection", "AutoAnim Tools") # Создаём кнопку (combo button) entry = unreal.ToolMenuEntryExtensions.init_menu_entry( menu.menu_name, "AutoAnimToolbar", label, tool_tip, unreal.ToolMenuStringCommandType.PYTHON, "", "", # Команда не нужна, будет подменю ) entry.set_editor_property("type", unreal.MultiBlockType.TOOL_BAR_COMBO_BUTTON) entry.set_icon(icon_style, icon_name) menu.add_menu_entry("Content", entry) # Регистрируем подменю (одно меню, не два) submenu = menus.register_menu( f"{menu.menu_name}.{entry.get_editor_property('name')}", "", unreal.MultiBoxType.MENU, False, ) submenu.add_section("submenu_section", "Select Animation Type") # Добавляем два пункта: asmbl / anim3D for anim_type in ["asmbl", "anim3D"]: command_string = ( f"import unreal; " f"unreal.SELECTED_ANIM_TYPE = '{anim_type}'; " f"unreal.log('[AutoAnim] Выбран тип: {anim_type}'); " f"{pycommand}" ) submenu_entry = unreal.ToolMenuEntryExtensions.init_menu_entry( submenu.menu_name, f"submenu_entry_{anim_type}", anim_type, f"Применить тип: {anim_type}", unreal.ToolMenuStringCommandType.PYTHON, "", command_string, ) submenu_entry.set_editor_property("type", unreal.MultiBlockType.MENU_ENTRY) submenu.add_menu_entry("submenu_section", submenu_entry) menus.refresh_all_widgets() unreal.log(f"[AutoAnim] Кнопка '{label}' с меню добавлена в {menu_name}.") def add_separator(menu_name = ''): menus = unreal.ToolMenus.get() menu = menus.find_menu(menu_name) entry = unreal.ToolMenuEntry(type=unreal.MultiBlockType.SEPARATOR) menu.add_menu_entry('Content', entry) def toggle_hair(): hair_value = unreal.SystemLibrary.get_console_variable_int_value('r.HairStrands.Enable') world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() print('HairStrands = ', hair_value) if hair_value: unreal.SystemLibrary.execute_console_command(world, "r.HairStrands.Enable 0") else: unreal.SystemLibrary.execute_console_command(world, "r.HairStrands.Enable 1") # =============================== # Очистка треков и обнуление Location/Rotation # =============================== def clear_tracks_in_level_sequence(): unreal.log_warning("[ClearTracks] Начало скрипта очистки треков") # Треки, которые нужно игнорировать при очистки. Пока только Visibility IGNORE_TRACKS = \ [ "MovieSceneVisibilityTrack", # "MovieSceneControlRigParameterTrack", # "MovieSceneSkeletalAnimationTrack", # "MovieScene3DAttachTrack", # "MovieScene3DTransformTrack", ] # =============================== # Получаем текущий открытый Level Sequence # =============================== current_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence() if not current_sequence: unreal.log_error("[ClearTracks] Нет открытого Level Sequence") quit() else: unreal.log(f"[ClearTracks] Открытый Level Sequence: {current_sequence.get_name()}") # =============================== # Активируем SubLevel по текущему Sequence # =============================== seq_name = current_sequence.get_name() base_name = seq_name[:-6] if seq_name.endswith("_LSQ_1") else seq_name sublevel_name = base_name + "_LVL_1" level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) success = level_editor_subsystem.set_current_level_by_name(sublevel_name) if success: unreal.log(f"[ClearTracks] Активирован уровень: {sublevel_name}") else: unreal.log_error(f"[ClearTracks] SubLevel {sublevel_name} не найден") quit() # =============================== # Получаем всех актёров активного уровня # =============================== editor_world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() sub_levels = unreal.EditorLevelUtils.get_levels(editor_world) visible_level_paths = set() for level in sub_levels: level_package_name = level.get_package().get_name() streaming_level = unreal.GameplayStatics.get_streaming_level(editor_world, level_package_name) if streaming_level and streaming_level.is_level_visible(): visible_level_paths.add(level_package_name) actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) all_level_actors = \ [ actor for actor in actor_subsystem.get_all_level_actors() if actor.get_level().get_package().get_name() in visible_level_paths ] unreal.log(f"[ClearTracks] В активном уровне найдено {len(all_level_actors)} актёров") # =============================== # Ищем actor в активном уровне # =============================== for binding in current_sequence.get_bindings(): binding_name = binding.get_name() unreal.log(f"[ClearTracks] Очистка актора: {binding_name}") target_comp = None bp_name_base = binding_name[:-3] if binding_name.endswith("_BP") else binding_name for actor in all_level_actors: if actor.get_actor_label() == binding_name: comps = actor.get_components_by_class(unreal.SkeletalMeshComponent) if not comps: break for comp in comps: if comp.get_name() == bp_name_base: target_comp = comp unreal.log( f"[ClearTracks] -> SkeletalMesh {comp.get_name()} используется для Blueprint'а {binding_name}") break if not target_comp: target_comp = comps[0] unreal.log( f"[ClearTracks] -> SkeletalMesh {target_comp.get_name()} используется для SkeletalMeshActor'а {binding_name}") break if not target_comp: unreal.log_warning(f"[ClearTracks] Не найден SkeletalMeshComponent для {binding_name}") continue # =============================== # Удаляем все треки кроме IGNORE_TRACKS # =============================== for track in list(binding.get_tracks()): if track.get_class().get_name() not in IGNORE_TRACKS: binding.remove_track(track) unreal.log(f"[ClearTracks] -> Очистка треков выполнена для {binding_name}") # =============================== # Обнуляем Location и Rotation актёра # =============================== actor = target_comp.get_owner() actor.set_actor_location_and_rotation(unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0), False, False) unreal.log(f"[ClearTracks] -> Location и Rotation обнулены для {binding_name}") level_editor_subsystem.get_current_level().modify(True) unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence() unreal.log_warning("[ClearTracks] Очистка треков завершена.") # =============================== # Установка анимаций без очистки # =============================== def add_auto_animations(): unreal.log_warning("[AutoSetAnims] Начало скрипта автоматической установки анимаций") # =============================== # Получаем текущий открытый Level Sequence # =============================== current_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence() if not current_sequence: unreal.log_error("[AutoSetAnims] Нет открытого Level Sequence") return else: unreal.log(f"[AutoSetAnims] Открытый Level Sequence: {current_sequence.get_name()}") # =============================== # Определяем папку с анимациями по имени Sequence # =============================== sequence_path = current_sequence.get_path_name() parts = sequence_path.split('/') ep = parts[3] sq = parts[4] sh = parts[5].split('.')[0] anim_folder = f"/Game/3_anim/{ep}/{sq}/{sh}/{unreal.SELECTED_ANIM_TYPE}" unreal.log(f"[AutoSetAnims] unreal.SELECTED_ANIM_TYPE: {unreal.SELECTED_ANIM_TYPE}") unreal.log(f"[AutoSetAnims] Папка с анимациями: {anim_folder}") # =============================== # Сбор анимаций по Skeleton # =============================== anim_assets = {} if unreal.EditorAssetLibrary.does_directory_exist(anim_folder): assets = unreal.EditorAssetLibrary.list_assets(anim_folder, recursive=True, include_folder=False) for asset_path in assets: asset = unreal.EditorAssetLibrary.load_asset(asset_path) if isinstance(asset, unreal.AnimSequence): skel = asset.get_editor_property("skeleton") if skel: anim_assets.setdefault(skel, []).append(asset) unreal.log(f"[AutoSetAnims] Найдена анимация: {asset.get_name()} | Skeleton: {skel.get_name()}") else: unreal.log_warning(f"[AutoSetAnims] Папка с анимациями не найдена: {anim_folder}") # =============================== # Активируем SubLevel # =============================== seq_name = current_sequence.get_name() base_name = seq_name[:-6] if seq_name.endswith("_LSQ_1") else seq_name sublevel_name = base_name + "_LVL_1" level_editor_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) success = level_editor_subsystem.set_current_level_by_name(sublevel_name) if success: unreal.log(f"[AutoSetAnims] Активирован уровень: {sublevel_name}") else: unreal.log_error(f"[AutoSetAnims] SubLevel {sublevel_name} не найден") return # =============================== # Получаем всех актеров активного уровня # =============================== editor_world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() sub_levels = unreal.EditorLevelUtils.get_levels(editor_world) visible_level_paths = set() for level in sub_levels: level_package_name = level.get_package().get_name() streaming_level = unreal.GameplayStatics.get_streaming_level(editor_world, level_package_name) if streaming_level and streaming_level.is_level_visible(): visible_level_paths.add(level_package_name) actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) all_level_actors = [ actor for actor in actor_subsystem.get_all_level_actors() if actor.get_level().get_package().get_name() in visible_level_paths ] unreal.log(f"[AutoSetAnims] В активном уровне найдено {len(all_level_actors)} актёров") # =============================== # Словарь для порядкового распределения анимаций по Skeleton # =============================== skeleton_counters = {} frame_rate = current_sequence.get_display_rate() # =============================== # Применяем анимации # =============================== for binding in current_sequence.get_bindings(): binding_name = binding.get_name() unreal.log(f"[AutoSetAnims] Применение анимации для: {binding_name}") target_comp = None bp_name_base = binding_name[:-3] if binding_name.endswith("_BP") else binding_name # Поиск SkeletalMeshComponent for actor in all_level_actors: if actor.get_actor_label() == binding_name: comps = actor.get_components_by_class(unreal.SkeletalMeshComponent) if comps: for comp in comps: if comp.get_name() == bp_name_base: target_comp = comp break if not target_comp: target_comp = comps[0] break if not target_comp: unreal.log_warning(f"[AutoSetAnims] Не найден SkeletalMeshComponent для {binding_name}") continue skel_asset = target_comp.get_editor_property("skeletal_mesh_asset") if not skel_asset: unreal.log_warning(f"[AutoSetAnims] {binding_name} не имеет SkeletalMesh") continue skel = skel_asset.skeleton if skel not in anim_assets or not anim_assets[skel]: unreal.log_warning(f"[AutoSetAnims] Для Skeleton {skel.get_name()} не найдена анимация") continue # Выбор анимации по порядку для данного Skeleton anim_list = sorted(anim_assets[skel], key=lambda x: x.get_name()) idx = skeleton_counters.get(skel, 0) % len(anim_list) anim = anim_list[idx] skeleton_counters[skel] = idx + 1 # =============================== # Очистка прошлого трека анимации # =============================== for track in list(binding.get_tracks()): if track.get_class().get_name() in "MovieSceneSkeletalAnimationTrack": binding.remove_track(track) # Создание нового трека анимации anim_track = binding.add_track(unreal.MovieSceneSkeletalAnimationTrack) anim_section = anim_track.add_section() params = unreal.MovieSceneSkeletalAnimationParams() params.set_editor_property("Animation", anim) anim_section.set_editor_property("Params", params) # Установка длины анимации + KEEP_STATE у анимации length = anim.get_editor_property("sequence_length") frame_end = round((length * frame_rate.numerator / frame_rate.denominator)) anim_section.set_range(1, frame_end + 1) anim_section.set_completion_mode(unreal.MovieSceneCompletionMode.KEEP_STATE) unreal.log(f"[AutoSetAnims] -> Skeleton'у {skel.get_name()} присвоена анимация {anim.get_name()} для {binding_name} " f"({length:.2f}s / {frame_end} кадров)") unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence() unreal.log_warning("[AutoSetAnims] Установка анимаций завершена.") initialize_sequencer() cam_a = Cinecamera(TransformStruct = {}, Transform = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0], CurrentFocalLength = 0.0, ManualFocusDistance = 0.0, CurrentAperture = 0.0) props_a = SequenceCopier() lights_a = shotLHandler(Lights = [], actorTransforms = {}) add_py_button(menu_name = "LevelEditor.LevelEditorToolbar.User", label = 'hair', tool_tip = 'toggle groom visibility', icon_style = 'Groom', icon_name = 'ClassThumbnail.GroomAsset', pycommand = 'toggle_hair()') add_py_button(menu_name = "LevelEditor.LevelEditorToolbar.User", label = 'render', tool_tip = 'render manager for prodtrack', icon_style = 'MovieRenderPipelineStyle', icon_name = 'MovieRenderPipeline.TabIcon', pycommand = 'unreal.EditorUtilitySubsystem().spawn_and_register_tab(unreal.EditorAssetLibrary.load_asset("/Game/_library/tools/EUW/render_EUW"))') add_py_button(menu_name = 'LevelEditor.LevelEditorToolbar.User', label = 'shot', tool_tip = 'shot takes and locations manager for prodtrack', icon_style = 'EditorStyle', icon_name = 'Icons.Level', pycommand = 'unreal.EditorUtilitySubsystem().spawn_and_register_tab(unreal.EditorAssetLibrary.load_asset("/Game/_library/tools/EUW/shot_EUW"))') add_py_button(menu_name = 'LevelEditor.LevelEditorToolbar.User', label = 'lights', tool_tip = 'lights manager for prodtrack', icon_style = 'EditorStyle', icon_name = 'PlacementBrowser.Icons.Lights', pycommand = 'unreal.EditorUtilitySubsystem().spawn_and_register_tab(unreal.EditorAssetLibrary.load_asset("/Game/_library/tools/EUW/light_EUW"))') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'ClearTracks', tool_tip = 'clear all tracks in sequence', icon_style = 'EditorStyle', icon_name = 'Icons.Delete', pycommand = 'clear_tracks_in_level_sequence()') add_py_button_section( menu_name='Sequencer.MainToolBar', label='AutoAnim', tool_tip='set all anim in actors', icon_style='EditorStyle', icon_name='ClassIcon.SkeletalMesh', pycommand='add_auto_animations()') add_separator(menu_name = 'Sequencer.MainToolBar') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'getCam', tool_tip = 'copy current camera values', icon_style = 'EditorStyle', icon_name = 'Sequencer.Tracks.CameraCut', pycommand = 'cam_a.get_camera()') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'setCam', tool_tip = 'paste copied camera values', icon_style = 'EditorStyle', icon_name = 'BlueprintEditor.ResetCamera', pycommand = 'cam_a.set_camera()') add_separator(menu_name = 'Sequencer.MainToolBar') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'copyPrp', tool_tip = 'copy props from current anim level, if props selected, copy selected, if not copy all', icon_style = 'BspModeStyle', icon_name = 'BspMode.CSGDeintersect.Small', pycommand = 'props_a.copyProps()') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'addPrp', tool_tip = 'paste copied props in current animLVL', icon_style = 'EditorStyle', icon_name = 'Icons.PlaceActors', pycommand = 'props_a.pasteAddProps()') add_py_button(menu_name = 'Sequencer.MainToolBar', label = 'replPrp', tool_tip = 'replace props in current animLVL with copied props', icon_style = 'EditorStyle', icon_name = 'Icons.ReplaceActor', pycommand = 'props_a.pasteReplaceProps()')