@@ -935,17 +935,17 @@ def compute_and_update_preview_camera_state() -> (
935935 return
936936 time = None
937937 if len (maybe_pose_and_fov_rad ) == 3 : # Time is enabled.
938- pose , fov_rad , time = maybe_pose_and_fov_rad
938+ pose , fov , time = maybe_pose_and_fov_rad
939939 render_tab_state .preview_time = time
940940 else :
941- pose , fov_rad = maybe_pose_and_fov_rad
942- render_tab_state .preview_fov = fov_rad
941+ pose , fov = maybe_pose_and_fov_rad
942+ render_tab_state .preview_fov = fov
943943 render_tab_state .preview_aspect = camera_path .get_aspect ()
944944
945945 if time is not None :
946- return pose , fov_rad , time
946+ return pose , fov , time
947947 else :
948- return pose , fov_rad
948+ return pose , fov
949949
950950 def add_preview_frame_slider () -> Optional [viser .GuiInputHandle [int ]]:
951951 """Helper for creating the current frame # slider. This is removed and
@@ -974,13 +974,13 @@ def _(_) -> None:
974974 if maybe_pose_and_fov_rad is None :
975975 return
976976 if len (maybe_pose_and_fov_rad ) == 3 : # Time is enabled.
977- pose , fov_rad , time = maybe_pose_and_fov_rad
977+ pose , fov , time = maybe_pose_and_fov_rad
978978 else :
979- pose , fov_rad = maybe_pose_and_fov_rad
979+ pose , fov = maybe_pose_and_fov_rad
980980
981981 preview_camera_handle = server .scene .add_camera_frustum (
982982 "/preview_camera" ,
983- fov = fov_rad ,
983+ fov = fov ,
984984 aspect = render_res_vec2 .value [0 ] / render_res_vec2 .value [1 ],
985985 scale = 0.35 ,
986986 wxyz = pose .rotation ().wxyz ,
@@ -993,7 +993,7 @@ def _(_) -> None:
993993 # aspect ratio is not assignable, pass args in get_render instead
994994 client .camera .wxyz = pose .rotation ().wxyz
995995 client .camera .position = pose .translation ()
996- client .camera .fov = fov_rad
996+ client .camera .fov = fov
997997
998998 return preview_frame_slider
999999
@@ -1216,108 +1216,110 @@ def _(_) -> None:
12161216 @save_camera_path_button .on_click
12171217 def _ (event : viser .GuiEvent ) -> None :
12181218 assert event .client is not None
1219- num_frames = int (framerate_number .value * duration_number .value )
1220- json_data = {}
1221- # json data has the properties:
1222- # keyframes: list of keyframes with
1223- # matrix : flattened 4x4 matrix
1224- # fov: float in degrees
1225- # aspect: float
1226- # render_height: int
1227- # render_width: int
1228- # fps: int
1229- # seconds: float
1230- # is_cycle: bool
1231- # smoothness_value: float
1232- # camera_path: list of frames with properties
1233- # camera_to_world: flattened 4x4 matrix
1234- # fov: float in degrees
1235- # aspect: float
1236- # first populate the keyframes:
1237- keyframes = []
1238- for keyframe , dummy in camera_path ._keyframes .values ():
1239- pose = tf .SE3 .from_rotation_and_translation (
1240- tf .SO3 (keyframe .wxyz ) @ tf .SO3 .from_x_radians (np .pi ),
1241- keyframe .position / scale_ratio ,
1242- )
1243- keyframe_dict = {
1244- "matrix" : pose .as_matrix ().flatten ().tolist (),
1245- "fov" : (
1246- np .rad2deg (keyframe .override_fov_rad )
1247- if keyframe .override_fov_enabled
1248- else fov_degrees_slider .value
1249- ),
1250- "aspect" : keyframe .aspect ,
1251- "override_transition_enabled" : keyframe .override_transition_enabled ,
1252- "override_transition_sec" : keyframe .override_transition_sec ,
1253- }
1254- keyframes .append (keyframe_dict )
1255- json_data ["default_fov" ] = fov_degrees_slider .value
1256- json_data ["default_transition_sec" ] = transition_sec_number .value
1257- json_data ["keyframes" ] = keyframes
1258- json_data ["render_height" ] = render_res_vec2 .value [1 ]
1259- json_data ["render_width" ] = render_res_vec2 .value [0 ]
1260- json_data ["fps" ] = framerate_number .value
1261- json_data ["seconds" ] = duration_number .value
1262- json_data ["is_cycle" ] = loop_checkbox .value
1263- json_data ["smoothness_value" ] = tension_slider .value
1264- # now populate the camera path:
1265- camera_path_list = []
1266- for i in range (num_frames ):
1267- maybe_pose_and_fov = camera_path .interpolate_pose_and_fov_rad (
1268- i / num_frames
1269- )
1270- if maybe_pose_and_fov is None :
1271- return
1272- time = None
1273- if len (maybe_pose_and_fov ) == 3 : # Time is enabled.
1274- pose , fov , time = maybe_pose_and_fov
1275- else :
1276- pose , fov = maybe_pose_and_fov
1277- # rotate the axis of the camera 180 about x axis
1278- pose = tf .SE3 .from_rotation_and_translation (
1279- pose .rotation () @ tf .SO3 .from_x_radians (np .pi ),
1280- pose .translation () / scale_ratio ,
1281- )
1282- camera_path_list_dict = {
1283- "camera_to_world" : pose .as_matrix ().flatten ().tolist (),
1284- "fov" : np .rad2deg (fov ),
1285- "aspect" : render_res_vec2 .value [0 ] / render_res_vec2 .value [1 ],
1286- }
1287- if time is not None :
1288- camera_path_list_dict ["render_time" ] = time
1289- camera_path_list .append (camera_path_list_dict )
1290- json_data ["camera_path" ] = camera_path_list
1291- # finally add crop data if crop is enabled
1292- # if control_panel is not None:
1293- # if control_panel.crop_viewport:
1294- # obb = control_panel.crop_obb
1295- # rpy = tf.SO3.from_matrix(obb.R.numpy()).as_rpy_radians()
1296- # color = control_panel.background_color
1297- # json_data["crop"] = {
1298- # "crop_center": obb.T.tolist(),
1299- # "crop_scale": obb.S.tolist(),
1300- # "crop_rot": [rpy.roll, rpy.pitch, rpy.yaw],
1301- # "crop_bg_color": {"r": color[0], "g": color[1], "b": color[2]},
1302- # }
1303-
1304- # now write the json file
1305- try :
1306- json_outfile = (
1307- output_dir / "camera_paths" / f"{ trajectory_name_text .value } .json"
1308- )
1309- json_outfile .parent .mkdir (parents = True , exist_ok = True )
1310- except Exception :
1311- Console (width = 120 ).print (
1312- "[bold yellow]Warning: Failed to write the camera path to the data directory. Saving to the output directory instead."
1313- )
1314- json_outfile = (
1315- output_dir / "camera_paths" / f"{ trajectory_name_text .value } .json"
1316- )
1219+
1220+ json_outfile = (
1221+ output_dir / "camera_paths" / f"{ trajectory_name_text .value } .json"
1222+ )
1223+
1224+ def save_camera_path () -> None :
13171225 json_outfile .parent .mkdir (parents = True , exist_ok = True )
1318- with open (json_outfile .absolute (), "w" ) as outfile :
1319- json .dump (json_data , outfile )
1320- print (f"Camera path saved to { json_outfile .absolute ()} " )
1226+
1227+ num_frames = int (framerate_number .value * duration_number .value )
1228+ json_data = {}
1229+ # json data has the properties:
1230+ # keyframes: list of keyframes with
1231+ # matrix : flattened 4x4 matrix
1232+ # fov: float in degrees
1233+ # aspect: float
1234+ # render_height: int
1235+ # render_width: int
1236+ # fps: int
1237+ # seconds: float
1238+ # is_cycle: bool
1239+ # smoothness_value: float
1240+ # camera_path: list of frames with properties
1241+ # camera_to_world: flattened 4x4 matrix
1242+ # fov: float in degrees
1243+ # aspect: float
1244+ # first populate the keyframes:
1245+ keyframes = []
1246+ for keyframe , dummy in camera_path ._keyframes .values ():
1247+ pose = tf .SE3 .from_rotation_and_translation (
1248+ tf .SO3 (keyframe .wxyz ) @ tf .SO3 .from_x_radians (np .pi ),
1249+ keyframe .position / scale_ratio ,
1250+ )
1251+ keyframe_dict = {
1252+ "matrix" : pose .as_matrix ().flatten ().tolist (),
1253+ "fov" : (
1254+ np .rad2deg (keyframe .override_fov_rad )
1255+ if keyframe .override_fov_enabled
1256+ else fov_degrees_slider .value
1257+ ),
1258+ "aspect" : keyframe .aspect ,
1259+ "override_transition_enabled" : keyframe .override_transition_enabled ,
1260+ "override_transition_sec" : keyframe .override_transition_sec ,
1261+ }
1262+ keyframes .append (keyframe_dict )
1263+ json_data ["default_fov" ] = fov_degrees_slider .value
1264+ json_data ["default_transition_sec" ] = transition_sec_number .value
1265+ json_data ["keyframes" ] = keyframes
1266+ json_data ["render_height" ] = render_res_vec2 .value [1 ]
1267+ json_data ["render_width" ] = render_res_vec2 .value [0 ]
1268+ json_data ["fps" ] = framerate_number .value
1269+ json_data ["seconds" ] = duration_number .value
1270+ json_data ["is_cycle" ] = loop_checkbox .value
1271+ json_data ["smoothness_value" ] = tension_slider .value
1272+ # now populate the camera path:
1273+ camera_path_list = []
1274+ for i in range (num_frames ):
1275+ maybe_pose_and_fov = camera_path .interpolate_pose_and_fov_rad (
1276+ i / num_frames
1277+ )
1278+ if maybe_pose_and_fov is None :
1279+ return
1280+ time = None
1281+ if len (maybe_pose_and_fov ) == 3 : # Time is enabled.
1282+ pose , fov , time = maybe_pose_and_fov
1283+ else :
1284+ pose , fov = maybe_pose_and_fov
1285+ # rotate the axis of the camera 180 about x axis
1286+ pose = tf .SE3 .from_rotation_and_translation (
1287+ pose .rotation () @ tf .SO3 .from_x_radians (np .pi ),
1288+ pose .translation () / scale_ratio ,
1289+ )
1290+ camera_path_list_dict = {
1291+ "camera_to_world" : pose .as_matrix ().flatten ().tolist (),
1292+ "fov" : np .rad2deg (fov ),
1293+ "aspect" : render_res_vec2 .value [0 ] / render_res_vec2 .value [1 ],
1294+ }
1295+ if time is not None :
1296+ camera_path_list_dict ["render_time" ] = time
1297+ camera_path_list .append (camera_path_list_dict )
1298+ json_data ["camera_path" ] = camera_path_list
1299+
1300+ with open (json_outfile .absolute (), "w" ) as outfile :
1301+ json .dump (json_data , outfile )
1302+ print (f"Camera path saved to { json_outfile .absolute ()} " )
1303+
1304+ if json_outfile .exists ():
1305+ with event .client .gui .add_modal ("Save Path" ) as modal :
1306+ event .client .gui .add_markdown (
1307+ "Path already exists. Do you want to overwrite?"
1308+ )
1309+ overwrite_button = event .client .gui .add_button ("Overwrite" )
1310+ cancel_button = event .client .gui .add_button ("Cancel" )
1311+
1312+ @overwrite_button .on_click
1313+ def _ (_ ) -> None :
1314+ modal .close ()
1315+ save_camera_path ()
1316+
1317+ @cancel_button .on_click
1318+ def _ (_ ) -> None :
1319+ modal .close ()
1320+
1321+ else :
1322+ save_camera_path ()
13211323
13221324 @dump_video_button .on_click
13231325 def _ (event : viser .GuiEvent ) -> None :
0 commit comments