feat: 运动相机支持 + move_camera.txt 关键帧驱动
input.txt 新增: move_camera: 0 # 0=固定视角, 1=按 move_camera.txt 运动 move_camera.txt 格式(4列:帧号 距离 俯仰角 方位角): 0 40.0 0 0 100 80.0 -30 180 200 40.0 0 360 display.txt header 传递 camera_keyframes JSON 数组, draw.py 按帧时间线性插值驱动相机运动(循环播放)。
This commit is contained in:
+36
-2
@@ -65,6 +65,7 @@ box_color_r = None
|
||||
box_color_g = None
|
||||
box_color_b = None
|
||||
use_marker = 0
|
||||
camera_keyframes_raw = ""
|
||||
|
||||
# 力开关
|
||||
GRAVITY_FIELD = 1 # 均匀重力场
|
||||
@@ -86,6 +87,27 @@ Z_MIN = None
|
||||
Z_MAX = None
|
||||
|
||||
|
||||
def _load_camera_keyframes(path):
|
||||
"""读取 move_camera.txt,返回 JSON 数组字符串 [[frame,dist,el,az],...] 或空串。"""
|
||||
if not os.path.exists(path):
|
||||
print(f"[compute] 警告: 未找到 {path},跳过运动相机")
|
||||
return ""
|
||||
keyframes = []
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) >= 4:
|
||||
keyframes.append([int(parts[0]), float(parts[1]),
|
||||
float(parts[2]), float(parts[3])])
|
||||
if not keyframes:
|
||||
return ""
|
||||
import json
|
||||
return json.dumps(keyframes)
|
||||
|
||||
|
||||
def _to_text_value(value):
|
||||
"""Convert numpy-heavy objects into JSON-friendly plain Python values."""
|
||||
if isinstance(value, np.ndarray):
|
||||
@@ -642,7 +664,7 @@ def run_from_config(config, out_dir=None):
|
||||
global X_MIN, X_MAX, Y_MIN, Y_MAX, Z_MIN, Z_MAX
|
||||
global ball_radius, ball_color_r, ball_color_g, ball_color_b
|
||||
global box_color_r, box_color_g, box_color_b
|
||||
global use_marker
|
||||
global use_marker, camera_keyframes_raw
|
||||
global warmup_steps, sample_start, sample_end
|
||||
global GRAVITY_FIELD, GRAVITY_INTERACTION, ELASTIC_FORCE, DAMPING_FORCE, GRAVITY_STRENGTH
|
||||
global DRIVING_FORCE, DRIVER_DATA
|
||||
@@ -726,6 +748,17 @@ def run_from_config(config, out_dir=None):
|
||||
driver_path = os.path.join(out_dir, driver_rel)
|
||||
DRIVER_DATA = load_driver_file(driver_path, ATOM_IDS)
|
||||
|
||||
# 加载运动相机关键帧
|
||||
camera_keyframes_raw = ""
|
||||
move_camera = int(config.get("move_camera", 0))
|
||||
camera_keyframes_raw = ""
|
||||
if move_camera:
|
||||
cam_rel = str(config.get("move_camera_file", os.path.join("input", "move_camera.txt")))
|
||||
cam_path = cam_rel
|
||||
if out_dir is not None and not os.path.isabs(cam_rel):
|
||||
cam_path = os.path.join(out_dir, cam_rel)
|
||||
camera_keyframes_raw = _load_camera_keyframes(cam_path)
|
||||
|
||||
print(f"[compute] 使用算法: {METHOD}")
|
||||
print(f"[compute] 已加载成键信息: {len(BOND_PAIRS)} 条键")
|
||||
if config.get("_skip_run", False):
|
||||
@@ -1464,7 +1497,8 @@ def run_simulation(save_trajectory=0):
|
||||
"atom_radii": ",".join(str(r) for r in ATOM_RADII),
|
||||
"camera_distance": str(config.get("camera_distance", 40.0)),
|
||||
"camera_elevation": str(config.get("camera_elevation", 0)),
|
||||
"camera_azimuth": str(config.get("camera_azimuth", 0))}
|
||||
"camera_azimuth": str(config.get("camera_azimuth", 0)),
|
||||
"camera_keyframes": str(camera_keyframes_raw)}
|
||||
)
|
||||
print(f"[compute] display.txt 已保存至: {disp_path} ({n_frames_actual} 帧)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user