From deadd2c88cdf3b210f37a59326dc689ced94432c Mon Sep 17 00:00:00 2001 From: march3 Date: Tue, 4 Jul 2023 10:02:55 +0800 Subject: [PATCH] =?UTF-8?q?Python=E8=B6=85=E4=BA=BA-=E5=AE=87=E5=AE=99?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/func.py | 10 ++ sim_scenes/func.py | 25 ++++ sim_scenes/science/speed_of_light_3d.py | 31 ++++- tools/sim_video_3d_cap.py | 150 ++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 tools/sim_video_3d_cap.py diff --git a/common/func.py b/common/func.py index 18d6525..e79feee 100644 --- a/common/func.py +++ b/common/func.py @@ -12,6 +12,7 @@ import numpy as np import random import os import math +import time def get_dominant_colors(infile, resize=(20, 20)): @@ -96,6 +97,15 @@ def find_file(file_path, default_val=None, find_deep=5): return default_val +def wait_for(secs): + wait = secs * 800 + while True: + if wait <= 0: + return + time.sleep(0.001) + wait -= 1 + + def calculate_distance(pos1, pos2=[0, 0, 0]): """ 计算两点间的距离 diff --git a/sim_scenes/func.py b/sim_scenes/func.py index 01c98f9..ff644e3 100644 --- a/sim_scenes/func.py +++ b/sim_scenes/func.py @@ -285,6 +285,31 @@ def create_light_ship(size_scale, init_position, speed=LIGHT_SPEED): init_velocity=[0, 0, speed]).set_light_disable(True) +def create_3d_card(left=-.885, top=0.495, width=0.02, height=0.02): + # 创建一个 Panel 组件 + from ursina import Text, Panel, color, camera, Vec3 + from simulators.ursina.ursina_config import UrsinaConfig + panel = Panel( + parent=None, + model='quad', + # texture='white_cube', + color=color.black, + origin=(-.48, .48, -.48), + scale=(width, height), + position=(left, top, 0) + ) + + def switch_color(): + if panel.color == color.black: + panel.color = color.white + else: + panel.color = color.black + + panel.switch_color = switch_color + + return panel + + def create_text_panel(width=0.35, height=.5): # 创建一个 Panel 组件 from ursina import Text, Panel, color, camera, Vec3 diff --git a/sim_scenes/science/speed_of_light_3d.py b/sim_scenes/science/speed_of_light_3d.py index a8bfa6d..86e7e70 100644 --- a/sim_scenes/science/speed_of_light_3d.py +++ b/sim_scenes/science/speed_of_light_3d.py @@ -7,8 +7,10 @@ # python_version :3.8 # ============================================================================== import sys +import time +from common.func import wait_for from common.consts import AU -from sim_scenes.func import ursina_run, create_solar_system_bodies, create_light_ship +from sim_scenes.func import ursina_run, create_solar_system_bodies, create_light_ship, create_3d_card from common.consts import LIGHT_SPEED from sim_scenes.science.speed_of_light_init import SpeedOfLightInit @@ -42,7 +44,7 @@ else: camera_pos = "right" print("camera_pos:", camera_pos) -camera_l2r = 0.1 * AU +camera_l2r = 0.01 * AU if camera_pos == "right": # 摄像机右眼 init.light_init_position[0] += camera_l2r @@ -56,9 +58,22 @@ init.light_init_position[1] = 1000000 # 从 init 对象中获取 光体的大小(light_size_scale),光体的位置(light_init_position) # 创建一个以光速前进的天体(模拟一个光子) speed=1光速=299792.458千米/秒,注意:质量为0才能达到光速,虽然如此,但也可以试试超光速 light_ship = create_light_ship(init.light_size_scale, init.light_init_position, speed=LIGHT_SPEED * 1) +light_ship.camera_pos = camera_pos # 增加光速天体到天体集合 bodies.append(light_ship) + +def switch_position(): + if light_ship.camera_pos == "right": # 摄像机右眼 + light_ship.position[0] -= 2 * camera_l2r + light_ship.camera_pos = "left" + elif light_ship.camera_pos == "left": # 摄像机左眼 + light_ship.position[0] += 2 * camera_l2r + light_ship.camera_pos = "right" + + +light_ship.switch_position = switch_position + # 运行前指定bodies、light_body并订阅事件 init.light_ship = light_ship init.bodies = bodies @@ -68,22 +83,32 @@ UrsinaEvent.on_reset_unsubscription(init.on_reset) def on_reset(): - init.on_reset + init.on_reset() init.arrived_info = "距离[太阳中心]:${distance}\n\n" init.arrived_info = "距离[太阳中心]:${distance}\n\n光速飞船速度:${speed}\n\n" +def on_ready(): + init._3d_card = create_3d_card() + + def on_timer_changed(time_data: TimeData): init.text_panel.parent.enabled = False velocity, _ = get_value_direction_vectors(light_ship.velocity) distance = round(init.light_ship.position[2] / AU, 4) text = init.arrived_info.replace("${distance}", "%.4f AU" % distance) init.text_panel.text = text.replace("${speed}", str(round(velocity / LIGHT_SPEED, 1)) + "倍光速") + init._3d_card.switch_color() + light_ship.switch_position() + if time_data.total_seconds > 20: + wait_for(0.3) # 订阅重新开始事件 # 按键盘的 “O” 重置键会触发 on_reset UrsinaEvent.on_reset_subscription(on_reset) + +UrsinaEvent.on_ready_subscription(on_ready) # 订阅计时器事件(记录已到达天体列表) # 运行中,每时每刻都会触发 on_timer_changed UrsinaEvent.on_timer_changed_subscription(on_timer_changed) diff --git a/tools/sim_video_3d_cap.py b/tools/sim_video_3d_cap.py new file mode 100644 index 0000000..4750e5d --- /dev/null +++ b/tools/sim_video_3d_cap.py @@ -0,0 +1,150 @@ +import cv2 +from PIL import ImageGrab, Image +import numpy as np +import argparse +import time +import os +import win32gui +import win32ui +import win32con +import win32api +import traceback + + +def get_window_handle(window_name="universe_sim"): + """ + 获取模拟器窗口句柄 + @param window_name: + @return: + """ + handle = win32gui.FindWindow(None, window_name) + return handle + + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--fps', type=int, default=30, help='frame per second') + parser.add_argument('--total_time', type=int, default=10000000, help='video total time') + parser.add_argument('--save_name', type=str, default='video.mp4', help='save file name') + # parser.add_argument('--screen_type', default=0, type=int, choices=[0, 1], help='1: full screen, 0: region screen') + args = parser.parse_args() + print("total_time:", args.total_time) + print("fps:", args.fps) + print("save_name:", args.save_name) + return args + + +def screen_shot(window_img_dc): + width, height = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN), \ + win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN) + # 创建一个内存设备描述表 + mem_dc = window_img_dc.CreateCompatibleDC() + # 创建位图对象 + screenshot = win32ui.CreateBitmap() + screenshot.CreateCompatibleBitmap(window_img_dc, width, height) + mem_dc.SelectObject(screenshot) + # 截图至内存设备描述表 + mem_dc.BitBlt((0, 0), (width, height), window_img_dc, (0, 0), win32con.SRCCOPY) + # 将截图保存到文件中 + # screenshot.SaveBitmapFile(mem_dc, 'screenshot.bmp') + signedIntsArray = screenshot.GetBitmapBits(True) + # 下面3个语句都能实现转换,推荐第1个 + img = np.fromstring(signedIntsArray, dtype='uint8') + img.shape = (height, width, 4) + # 内存释放 + mem_dc.DeleteDC() + win32gui.DeleteObject(screenshot.GetHandle()) + img = img[:, :, 0:3] # 去掉透明数据 + return img + + +# def is_blank_screen(img_arr): +# for x in range(500, 600): +# for y in range(10, 20): +# pix = img_arr[x, y, ] +# # 检查标题栏,此时标题栏的颜色为白色 +# if pix.sum() > 600: +# return True +# return False + + +def sim_window_screen_shot(wait_ses=-1): + times = wait_ses * 100 + while True: + handle = get_window_handle() + if handle > 0: + desktop_dc = win32gui.GetWindowDC(handle) + img_dc = win32ui.CreateDCFromHandle(desktop_dc) + try: + img = screen_shot(img_dc) + except Exception as e: + print("ERROR:", str(e)) + traceback.print_exc() + return None + return img + if wait_ses < 0: + return None + time.sleep(0.01) + times -= 1 + if times <= 0: + return None + + +def create_video(args, height, width): + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video = cv2.VideoWriter(args.save_name, fourcc, args.fps, (width, height)) + return video + + +# def show_image(img): +# from PIL import Image +# image = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) +# image = Image.fromarray(image) +# print(type(image)) # 结果为 +# print(image.size) # 结果为(822,694),这里注意Image输出的结果先显示列数,后显示行数 +# image.show() + + +if __name__ == '__main__': + args = get_args() + handle = get_window_handle() + # print(get_args()) + print("请在10秒内打开模拟器") + img = sim_window_screen_shot(10) + if img is None: + print("没有找到模拟器窗口,录屏失败!") + exit(1) + + # show_image(img) + video = create_video(args, img.shape[0], img.shape[1]) + imageNum = 0 + print("开始录屏") + while True: + img = sim_window_screen_shot() + if img is None: + print("\n模拟器窗口已关闭,退出录屏") + break + + # if is_blank_screen(img): + # if imageNum % args.fps == 0: + # print('x', end='') + # + # continue + + if imageNum % args.fps == 0: + print('.', end='') + # else: + # print(imageNum, end='') + + imageNum += 1 + + # frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) + if imageNum < args.fps * args.total_time: + # show_image(frame) + video.write(img) + + print("视频保存中") + video.release() + cv2.destroyAllWindows() + # crop('video.mp4') + print("视频保存完成") -- GitLab