mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-05 18:18:49 +00:00
3D Camera Tracker: Effect Controls say how many frames a partial solve solved (#2959)
A solve with unsolved frames drew the same block as a full one, so it looked complete. It now gets one more line in the warning colour, "Partial solve: N of M frames solved", under the status line, and the editor grows by that line only then. The line is an automation element, so agents can read it too.
This commit is contained in:
1 parent
7870b5916a
commit
940a57e865
3 files changed
+58
-6
No files matched your search
@@ -9,7 +9,7 @@
|
||||
//! and Camera, Create Multiple …, Delete Selected Points; Delete removes the selection;
|
||||
//! - the "Analyzing in background (step 1 of 2)" / "Solving camera" banner;
|
||||
//! - the Effect Controls block with Analyze / Cancel / Create Camera, Method Used and Average
|
||||
//! Error.
|
||||
//! Error, and how many frames a partial solve solved.
|
||||
//!
|
||||
//! Everything is dispatched to the `camera.*` commands.
|
||||
|
||||
@@ -425,12 +425,14 @@ pub fn editor(
|
||||
}
|
||||
let params = effectcraft_engine::camera_track::static_params(g);
|
||||
let solve = ct::solve(¶ms);
|
||||
let partial = solve.as_deref().and_then(partial_frames);
|
||||
let dy = if partial.is_some() { PARTIAL_LINE } else { 0.0 };
|
||||
if solve.is_some() {
|
||||
if widgets::text_button(ui, b3, crate::i18n::tr("Create Camera"), false, &t, egui::Id::unique(("ct-camera", g.uid))).clicked() {
|
||||
actions.push(("camera.create".into(), me.clone()));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.cameraTracker.{}.createCamera", g.uid), b3, "Create Camera");
|
||||
let export = Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 70.0), vec2(218.0, 22.0));
|
||||
let export = Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 70.0 + dy), vec2(218.0, 22.0));
|
||||
if widgets::text_button(ui, export, tr("Export Scene to Blender..."), false, &t, egui::Id::unique(("ct-blender", g.uid))).clicked() && !busy {
|
||||
actions.push(("camera.exportBlender".into(), me.clone()));
|
||||
}
|
||||
@@ -449,10 +451,19 @@ pub fn editor(
|
||||
},
|
||||
};
|
||||
p.text(pos2(r.min.x + 24.0, r.min.y + 38.0), Align2::LEFT_CENTER, status, Tokens::ui(11.5), t.text_dim);
|
||||
if let Some((solved, frames)) = partial {
|
||||
let text = tr_args("Partial solve: {} of {} frames solved", &[&solved, &frames]);
|
||||
p.text(pos2(r.min.x + 24.0, r.min.y + 56.0), Align2::LEFT_CENTER, &text, Tokens::ui(11.5), t.warning);
|
||||
app.auto.add(
|
||||
&format!("effectControls.cameraTracker.{}.partialSolve", g.uid),
|
||||
Rect::from_min_size(pos2(r.min.x + 24.0, r.min.y + 48.0), vec2(r.width() - 24.0, 16.0)),
|
||||
&text,
|
||||
);
|
||||
}
|
||||
if let Some(sv) = &solve {
|
||||
let f = sv.focal();
|
||||
p.text(
|
||||
pos2(r.min.x + 24.0, r.min.y + 56.0),
|
||||
pos2(r.min.x + 24.0, r.min.y + 56.0 + dy),
|
||||
Align2::LEFT_CENTER,
|
||||
format!("{} solved points · Horizontal Angle of View {:.1}°", sv.points.len(), sv.hfov(f)),
|
||||
Tokens::ui(11.5),
|
||||
@@ -461,5 +472,19 @@ pub fn editor(
|
||||
}
|
||||
}
|
||||
|
||||
/// Height of [`editor`].
|
||||
pub const EDITOR_HEIGHT: f32 = 100.0;
|
||||
/// Height of [`editor`] without the partial-solve line.
|
||||
const EDITOR_HEIGHT: f32 = 100.0;
|
||||
const PARTIAL_LINE: f32 = 18.0;
|
||||
|
||||
/// Height of [`editor`] for tracker `g`.
|
||||
pub fn editor_height(g: &PropGroup) -> f32 {
|
||||
let partial = ct::solve(&effectcraft_engine::camera_track::static_params(g)).is_some_and(|sv| partial_frames(&sv).is_some());
|
||||
if partial { EDITOR_HEIGHT + PARTIAL_LINE } else { EDITOR_HEIGHT }
|
||||
}
|
||||
|
||||
/// `(solved, all)` frames of a solve with unsolved frames, where the solver interpolates the
|
||||
/// camera; `None` when every frame is solved.
|
||||
fn partial_frames(sv: &CameraSolve) -> Option<(usize, usize)> {
|
||||
let solved = sv.frames.iter().filter(|c| c.solved).count();
|
||||
(solved < sv.frames.len()).then_some((solved, sv.frames.len()))
|
||||
}
|
||||
@@ -114,7 +114,7 @@ pub(super) fn editor_height(effect: &str, g: &PropGroup, width: f32) -> f32 {
|
||||
}
|
||||
"ec.color.curves" => 34.0 + curves_size(width) + 26.0,
|
||||
effectcraft_engine::effects::warp_stab::ID => 50.0,
|
||||
effectcraft_engine::effects::camera_tracker::ID => crate::panels::camera_tracker_ui::EDITOR_HEIGHT,
|
||||
effectcraft_engine::effects::camera_tracker::ID => crate::panels::camera_tracker_ui::editor_height(g),
|
||||
effectcraft_engine::effects::roto::ID | effectcraft_engine::effects::object_matte::ID => 50.0,
|
||||
"ec.color.levels" | "ec.color.levelsic" => 34.0 + 80.0 + 58.0,
|
||||
"ec.color.autolevels" | "ec.color.autocontrast" | "ec.color.autocolor" => 24.0 + 80.0 + 12.0,
|
||||
|
||||
@@ -202,6 +202,33 @@ fn blender_export_button_opens_python_dialog_and_writes_scene() {
|
||||
assert_eq!(h.state().session.active_comp().unwrap().layers.len(), 1, "export does not create a camera in the source project");
|
||||
}
|
||||
|
||||
/// A solve with unsolved (interpolated) frames says so in Effect Controls, with the count, on a
|
||||
/// line of its own that pushes the Export button down.
|
||||
#[test]
|
||||
fn effect_controls_show_how_many_frames_a_partial_solve_solved() {
|
||||
let (a, plate, uid) = app();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| a);
|
||||
h.state_mut().show_panel(PanelKind::EffectControls);
|
||||
h.run_steps(4);
|
||||
let line = format!("effectControls.cameraTracker.{uid}.partialSolve");
|
||||
let export = format!("effectControls.cameraTracker.{uid}.exportBlender");
|
||||
assert!(!ids(&h).contains(&line), "a full solve has no partial-solve line");
|
||||
let before = rect(&h, &export);
|
||||
|
||||
let g = h.state().session.active_comp().unwrap().layer(effectcraft_engine::project::LayerId(plate)).unwrap().props.find_group(uid).unwrap();
|
||||
let mut solve = ct::solve(&effectcraft_engine::camera_track::static_params(g)).unwrap().as_ref().clone();
|
||||
for f in solve.frames.iter_mut().skip(24) {
|
||||
f.solved = false;
|
||||
}
|
||||
let s = &mut h.state_mut().session;
|
||||
s.execute("prop.set", json!({"layer": plate, "path": "effects/#1/solve", "value": solve.to_json()})).unwrap();
|
||||
s.camera_pending.clear();
|
||||
h.run_steps(4);
|
||||
let e = h.state().auto.find(&line).expect("the partial-solve line is drawn");
|
||||
assert_eq!(e.label, "Partial solve: 24 of 60 frames solved");
|
||||
assert!((rect(&h, &export).min.y - before.min.y - 18.0).abs() < 0.5, "the Export button moves down one line");
|
||||
}
|
||||
|
||||
/// Drag with `button` from `from` to `to` in steps, then release.
|
||||
fn drag_with(h: &mut Harness<'_, EffectcraftApp>, button: egui::PointerButton, from: egui::Pos2, to: egui::Pos2) {
|
||||
h.input_mut().events.push(egui::Event::PointerMoved(from));
|
||||
|
||||
Reference in new issue
Block a user