M4.9: Preserve frame rate when nested or in render queue, Preserve resolution when nested
Composition Settings ▸ Advanced has both options (they were missing from the model, the
renderer and the dialog):
- Preserve frame rate: nested in another comp, the comp shows only its own frames
(`EvalCtx::nested_time` floors the precomp layer's source time to the nested comp's frame
rate; used for the nested frame, its proxy, aux channels, collapsed transformations and the
viewer's internal wireframes), and the Render Queue renders it at its own rate whatever the
Frame Rate override (`RenderSettings::rate`).
- Preserve resolution: nested in a comp rendered at Half / Third / Quarter, it renders at full
size and the buffer's scale places it.
- `comp.settings {preserveFrameRate, preserveResolution}`, `comp.info` reports them, scripts
read and set `CompItem.preserveNestedFrameRate` / `preserveNestedResolution`, Pre-compose
copies them, and the dialog registers `dialog.comp.preserveFrameRate` /
`dialog.comp.preserveResolution`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4e98b8c4bd
commit
0fe6db00e8
|
|
@ -90,6 +90,12 @@ fn apply_settings(c: &mut Comp, p: &Value) {
|
|||
if let Some(n) = p.get("adaptiveSampleLimit").and_then(Value::as_u64) {
|
||||
c.motion_blur_adaptive_limit = (n as u32).clamp(16, 256);
|
||||
}
|
||||
if let Some(b) = p.get("preserveFrameRate").and_then(Value::as_bool) {
|
||||
c.preserve_frame_rate = b;
|
||||
}
|
||||
if let Some(b) = p.get("preserveResolution").and_then(Value::as_bool) {
|
||||
c.preserve_resolution = b;
|
||||
}
|
||||
}
|
||||
|
||||
fn new_comp(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
|
@ -252,7 +258,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
"New Composition...",
|
||||
["Composition"],
|
||||
Some("Cmd+N"),
|
||||
"{name?, width?, height?, frameRate?, duration? (s), startTime? (s) | startTimecode?, background? [r,g,b]|#hex, pixelAspect?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), renderer? classic3D|advanced3D, anchor?, open?}",
|
||||
"{name?, width?, height?, frameRate?, duration? (s), startTime? (s) | startTimecode?, background? [r,g,b]|#hex, pixelAspect?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), preserveFrameRate?: bool, preserveResolution?: bool, renderer? classic3D|advanced3D, anchor?, open?}",
|
||||
always,
|
||||
new_comp
|
||||
),
|
||||
|
|
@ -261,7 +267,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
"Composition Settings...",
|
||||
["Composition"],
|
||||
Some("Cmd+K"),
|
||||
"{comp?, name?, width?, height?, anchor? 0-8 (resize anchor, 4 = center), frameRate?, duration?, startTime? (s) | startTimecode?, background?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), pixelAspect?, renderer? classic3D|advanced3D}",
|
||||
"{comp?, name?, width?, height?, anchor? 0-8 (resize anchor, 4 = center), frameRate?, duration?, startTime? (s) | startTimecode?, background?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), preserveFrameRate?: bool (nested or in the render queue it shows only its own frames), preserveResolution?: bool (nested, it renders at full size), pixelAspect?, renderer? classic3D|advanced3D}",
|
||||
has_comp,
|
||||
settings
|
||||
),
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ fn comp_info(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
Ok(json!({
|
||||
"id": cid.0, "name": name, "width": c.width, "height": c.height, "frameRate": c.frame_rate.as_f64(), "duration": c.duration.seconds(),
|
||||
"workArea": [c.work_area.0.seconds(), c.work_area.1.seconds()], "background": c.background,
|
||||
"preserveFrameRate": c.preserve_frame_rate, "preserveResolution": c.preserve_resolution,
|
||||
"layers": c.layers.iter().enumerate().map(|(i, l)| layer_json(l, i + 1)).collect::<Vec<_>>(),
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -811,3 +811,24 @@ fn project_files_keep_their_format_warn_about_newer_versions_and_never_save_unre
|
|||
let e = s.execute("file.open", json!({"path": junk})).unwrap_err().to_string();
|
||||
assert!(e.contains("is not a project EffectCraft can open"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comp_settings_preserve_frame_rate_and_resolution() {
|
||||
let mut s = comp();
|
||||
let info = s.execute("comp.info", json!({})).unwrap();
|
||||
assert_eq!((info["preserveFrameRate"].clone(), info["preserveResolution"].clone()), (json!(false), json!(false)));
|
||||
s.execute_checked("comp.settings", json!({"preserveFrameRate": true, "preserveResolution": true})).unwrap();
|
||||
let info = s.execute("comp.info", json!({})).unwrap();
|
||||
assert_eq!((info["preserveFrameRate"].clone(), info["preserveResolution"].clone()), (json!(true), json!(true)));
|
||||
// Pre-compose keeps them; one undo step clears them.
|
||||
let a = solid(&mut s, "#ff0000");
|
||||
let r = s.execute("layer.precompose", json!({"layers": [a], "name": "Inner"})).unwrap();
|
||||
let inner = s.project.comp(effectcraft_project::ItemId(r["comp"].as_u64().unwrap())).unwrap();
|
||||
assert!(inner.preserve_frame_rate && inner.preserve_resolution);
|
||||
// Undo Pre-compose and the solid; then one more step undoes the settings.
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert!(s.active_comp().unwrap().preserve_frame_rate);
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert!(!s.active_comp().unwrap().preserve_frame_rate && !s.active_comp().unwrap().preserve_resolution);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,6 +308,14 @@ pub struct Comp {
|
|||
pub enable_frame_blending: bool,
|
||||
#[serde(default)]
|
||||
pub draft_3d: bool,
|
||||
/// Composition Settings ▸ Advanced ▸ Preserve frame rate when nested or in render queue:
|
||||
/// nested in another comp, or rendered at another rate, it shows only its own frames.
|
||||
#[serde(default)]
|
||||
pub preserve_frame_rate: bool,
|
||||
/// Composition Settings ▸ Advanced ▸ Preserve resolution when nested: nested in a comp
|
||||
/// previewed at a lower resolution, it still renders at full size.
|
||||
#[serde(default)]
|
||||
pub preserve_resolution: bool,
|
||||
#[serde(default)]
|
||||
pub poster_time: Tick,
|
||||
/// Global Light for layer styles (Layer ▸ Layer Styles ▸ Blending Options).
|
||||
|
|
@ -358,6 +366,8 @@ impl Comp {
|
|||
enable_motion_blur: true,
|
||||
enable_frame_blending: true,
|
||||
draft_3d: false,
|
||||
preserve_frame_rate: false,
|
||||
preserve_resolution: false,
|
||||
poster_time: Tick::ZERO,
|
||||
guides: vec![],
|
||||
global_light: styles::GlobalLight::default(),
|
||||
|
|
@ -377,6 +387,8 @@ impl Comp {
|
|||
renderer: self.renderer,
|
||||
enable_motion_blur: self.enable_motion_blur,
|
||||
enable_frame_blending: self.enable_frame_blending,
|
||||
preserve_frame_rate: self.preserve_frame_rate,
|
||||
preserve_resolution: self.preserve_resolution,
|
||||
..Comp::new(width, height, self.frame_rate, duration)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -418,8 +418,10 @@ impl RenderSettings {
|
|||
let b = b.clamp(a, comp.duration);
|
||||
(a, b)
|
||||
}
|
||||
/// The output frame rate: the Frame Rate override, unless the comp preserves its frame rate
|
||||
/// (Composition Settings ▸ Preserve frame rate when nested or in render queue).
|
||||
pub fn rate(&self, comp: &Comp) -> FrameRate {
|
||||
self.frame_rate.unwrap_or(comp.frame_rate)
|
||||
if comp.preserve_frame_rate { comp.frame_rate } else { self.frame_rate.unwrap_or(comp.frame_rate) }
|
||||
}
|
||||
/// First output-rate frame starting at or after `t`.
|
||||
fn ceil_frame(r: FrameRate, t: Tick) -> i64 {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,19 @@ impl<'a> EvalCtx<'a> {
|
|||
}
|
||||
layer.layer_time(self.time)
|
||||
}
|
||||
/// The time a precomp layer's nested comp is shown at: [`Self::source_time`], floored to
|
||||
/// the nested comp's own frames when it preserves its frame rate (Composition Settings ▸
|
||||
/// Preserve frame rate when nested or in render queue).
|
||||
pub fn nested_time(&self, layer: &Layer) -> Tick {
|
||||
let t = self.source_time(layer);
|
||||
match layer.source {
|
||||
LayerSource::Comp { item } => match self.project.comp(item) {
|
||||
Some(nc) if nc.preserve_frame_rate => nc.frame_rate.tick_of(crate::frame_position(t, nc.frame_rate).0),
|
||||
_ => t,
|
||||
},
|
||||
_ => t,
|
||||
}
|
||||
}
|
||||
pub fn layer(&self, id: LayerId) -> Option<&'a Layer> {
|
||||
self.comp.layer(id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ impl EffectHost for FxHost<'_, '_, '_> {
|
|||
if self.r.depth > MAX_FX_DEPTH || self.r.project.comp_contains(*item, self.ctx.comp_id) {
|
||||
return None;
|
||||
}
|
||||
self.r.nested().comp_aux(*item, self.ctx.source_time(self.layer)).map(Arc::new)
|
||||
self.r.nested().comp_aux(*item, self.ctx.nested_time(self.layer)).map(Arc::new)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -558,7 +558,7 @@ impl<'a> Renderer<'a> {
|
|||
/// 3D layers use this comp's camera and lights.
|
||||
pub fn collapse_into(&self, ctx: &EvalCtx<'a>, layer: &Layer, item: ItemId, opacity: f32) -> Option<(Renderer<'a>, EvalCtx<'a>)> {
|
||||
let nc = self.project.comp(item)?;
|
||||
let nctx = EvalCtx { comp_id: item, comp: nc, time: ctx.source_time(layer), ..*ctx };
|
||||
let nctx = EvalCtx { comp_id: item, comp: nc, time: ctx.nested_time(layer), ..*ctx };
|
||||
let (l2c, _) = ctx.layer_to_comp(layer);
|
||||
let outer = Some(self.outer.map_or(l2c, |o| o * l2c));
|
||||
let world = ctx.world_matrix(layer);
|
||||
|
|
@ -928,19 +928,24 @@ impl<'a> Renderer<'a> {
|
|||
if let Some(pf) = self.proxy_for(*item)
|
||||
&& let Some(nc) = self.project.comp(*item)
|
||||
{
|
||||
let img = self.footage.frame(*item, pf, ctx.source_time(layer))?;
|
||||
let img = self.footage.frame(*item, pf, ctx.nested_time(layer))?;
|
||||
return Some(self.footage_buf(&img, pf, nc.width, nc.height));
|
||||
}
|
||||
// Essential Properties overrides render the nested comp with this instance's
|
||||
// values.
|
||||
let ov = essential_overrides(ctx, layer);
|
||||
let tmp = if ov.is_empty() { None } else { effectcraft_project::essential::with_overrides(self.project, *item, &ov) };
|
||||
// Preserve resolution when nested: drawn at full size even when this comp renders
|
||||
// smaller (the buffer's scale places it).
|
||||
let full = s < 1.0 && self.project.comp(*item).is_some_and(|nc| nc.preserve_resolution);
|
||||
let bs = if full { 1.0 } else { s };
|
||||
let base = self.nested_through(layer);
|
||||
let base = Renderer { opts: RenderOpts { scale: bs, ..base.opts }, ..base };
|
||||
let sub = match &tmp {
|
||||
Some(p) => Renderer { project: p, ..base },
|
||||
None => base,
|
||||
};
|
||||
let lt = ctx.source_time(layer);
|
||||
let lt = ctx.nested_time(layer);
|
||||
// Frame blending between the nested comp's frames (time-stretched/remapped).
|
||||
let mode = frame_blend_mode(ctx, layer);
|
||||
if mode != FrameBlend::Off
|
||||
|
|
@ -950,10 +955,10 @@ impl<'a> Renderer<'a> {
|
|||
if w > 1e-6 {
|
||||
let a = sub.comp_frame(*item, nc.frame_rate.tick_of(i));
|
||||
let b = sub.comp_frame(*item, nc.frame_rate.tick_of(i + 1));
|
||||
return Some(Buf { img: blend_frames(mode, &a, &b, w as f32), offset: [0.0; 2], scale: s });
|
||||
return Some(Buf { img: blend_frames(mode, &a, &b, w as f32), offset: [0.0; 2], scale: bs });
|
||||
}
|
||||
}
|
||||
Some(Buf { img: sub.comp_frame(*item, lt), offset: [0.0; 2], scale: s })
|
||||
Some(Buf { img: sub.comp_frame(*item, lt), offset: [0.0; 2], scale: bs })
|
||||
}
|
||||
LayerSource::Footage { item } => {
|
||||
let it = self.project.item(*item)?;
|
||||
|
|
@ -1901,6 +1906,8 @@ mod tests_color;
|
|||
#[cfg(test)]
|
||||
mod tests_frame_blend;
|
||||
#[cfg(test)]
|
||||
mod tests_nested;
|
||||
#[cfg(test)]
|
||||
mod tests_paint;
|
||||
#[cfg(test)]
|
||||
mod tests_time;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
//! Nested compositions: Preserve frame rate when nested or in render queue, Preserve resolution
|
||||
//! when nested.
|
||||
|
||||
use effectcraft_color::Label;
|
||||
use effectcraft_keyframe::{Keyframe, Value};
|
||||
use effectcraft_project::build;
|
||||
use effectcraft_project::render_queue::RenderSettings;
|
||||
use effectcraft_project::{Comp, ItemId, ItemKind, LayerSource, Project, Solid};
|
||||
use effectcraft_time::{FrameRate, Tick};
|
||||
|
||||
use crate::render_frame;
|
||||
|
||||
fn s(x: f64) -> Tick {
|
||||
Tick::from_seconds_f64(x)
|
||||
}
|
||||
|
||||
/// A 30 fps outer comp nesting a 10 fps 100×100 comp whose 10×10 solid moves from x=10 (0 s)
|
||||
/// to x=90 (2 s). Returns (project, outer, inner).
|
||||
fn nested(preserve_rate: bool, preserve_res: bool) -> (Project, ItemId, ItemId) {
|
||||
let mut p = Project::default();
|
||||
let outer = Comp::new(100, 100, FrameRate::FPS_30, s(4.0));
|
||||
let mut inner = Comp::new(100, 100, FrameRate::from_f64(10.0), s(4.0));
|
||||
inner.preserve_frame_rate = preserve_rate;
|
||||
inner.preserve_resolution = preserve_res;
|
||||
let sid = p.add_item("S", Label::Red, None, ItemKind::Solid(Solid { color: [1.0, 1.0, 1.0], width: 10, height: 10, pixel_aspect: 1.0 }));
|
||||
let mut l = build::layer(&mut p, &inner, "S", LayerSource::Solid { item: sid }, (10, 10), None);
|
||||
l.props.prop_mut("transform/position").unwrap().keys =
|
||||
vec![Keyframe::new(Tick::ZERO, Value::Vec3([10.0, 50.0, 0.0])), Keyframe::new(s(2.0), Value::Vec3([90.0, 50.0, 0.0]))];
|
||||
inner.layers.push(l);
|
||||
let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let cid = p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(outer.clone().into()));
|
||||
let pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (100, 100), None);
|
||||
p.comp_mut(cid).unwrap().layers.push(pre);
|
||||
(p, cid, iid)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_comp_preserving_its_frame_rate_shows_only_its_own_frames_when_nested() {
|
||||
// 2/30 s falls between the nested comp's frames 0 (0 s) and 1 (0.1 s).
|
||||
let between = s(2.0 / 30.0);
|
||||
let (p, cid, _) = nested(false, false);
|
||||
assert_ne!(render_frame(&p, cid, between, 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "follows the outer comp's frames");
|
||||
let (p, cid, _) = nested(true, false);
|
||||
assert_eq!(render_frame(&p, cid, between, 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "holds its own frame 0");
|
||||
assert_ne!(render_frame(&p, cid, s(0.1), 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "then its frame 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_render_queue_keeps_a_preserved_frame_rate() {
|
||||
let (p, _, iid) = nested(true, false);
|
||||
let settings = RenderSettings { frame_rate: Some(FrameRate::FPS_30), ..Default::default() };
|
||||
assert_eq!(settings.rate(p.comp(iid).unwrap()), FrameRate::from_f64(10.0));
|
||||
let (p, _, iid) = nested(false, false);
|
||||
assert_eq!(settings.rate(p.comp(iid).unwrap()), FrameRate::FPS_30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_comp_preserving_its_resolution_renders_full_size_and_lands_in_place() {
|
||||
for preserve in [false, true] {
|
||||
let (p, cid, _) = nested(false, preserve);
|
||||
let img = render_frame(&p, cid, Tick::ZERO, 0.5);
|
||||
assert_eq!((img.width, img.height), (50, 50));
|
||||
// The solid covers x 5..15, y 45..55 in the 100×100 comp: 2.5..7.5, 22.5..27.5 at half size.
|
||||
assert!(img.get(5, 25)[3] > 0.99, "preserve {preserve}: inside");
|
||||
assert!(img.get(20, 25)[3] < 0.01 && img.get(5, 40)[3] < 0.01, "preserve {preserve}: outside");
|
||||
}
|
||||
}
|
||||
|
|
@ -131,6 +131,7 @@ fn item(s: &Session, a: &J) -> R {
|
|||
"draft3d": c.draft_3d, "shutterAngle": c.shutter_angle, "shutterPhase": c.shutter_phase,
|
||||
"motionBlurSamplesPerFrame": c.motion_blur_samples,
|
||||
"motionBlurAdaptiveSampleLimit": c.motion_blur_adaptive_limit,
|
||||
"preserveNestedFrameRate": c.preserve_frame_rate, "preserveNestedResolution": c.preserve_resolution,
|
||||
"renderer": match c.renderer { effectcraft_project::Renderer::Classic3D => "ADBE Classic 3D", effectcraft_project::Renderer::Advanced3D => "ADBE Advanced 3d" },
|
||||
"open": s.state.open_comps.contains(&cid), "active": s.active_comp_id() == Some(cid),
|
||||
"hasVideo": true, "hasAudio": c.layers.iter().any(|l| l.switches.audio && matches!(l.source, LayerSource::Footage { .. } | LayerSource::Comp { .. })),
|
||||
|
|
|
|||
|
|
@ -1409,6 +1409,8 @@ CompItem.prototype.constructor = CompItem;
|
|||
def("shutterAngle", function () { return this.__info().shutterAngle; }, function (v) { this.__settings({ shutterAngle: __num(v) }); });
|
||||
def("shutterPhase", function () { return this.__info().shutterPhase; }, function (v) { this.__settings({ shutterPhase: __num(v) }); });
|
||||
def("motionBlurSamplesPerFrame", function () { return this.__info().motionBlurSamplesPerFrame; }, function (v) { this.__settings({ motionBlurSamples: __num(v) }); });
|
||||
def("preserveNestedFrameRate", function () { return this.__info().preserveNestedFrameRate; }, function (v) { this.__settings({ preserveFrameRate: !!v }); });
|
||||
def("preserveNestedResolution", function () { return this.__info().preserveNestedResolution; }, function (v) { this.__settings({ preserveResolution: !!v }); });
|
||||
def("renderer", function () { return this.__info().renderer; }, function (v) {
|
||||
this.__settings({ renderer: String(v).toLowerCase().indexOf("advanced") >= 0 ? "advanced3D" : "classic3D" });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -484,3 +484,21 @@ fn motion_graphics_template_hooks_add_mirrors_and_controllers() {
|
|||
let eg = c.essential.as_ref().unwrap();
|
||||
assert!(matches!(eg.controls[1].kind, effectcraft_engine::project::essential::EgKind::Mirror { of } if of == eg.controls[0].id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comp_preserves_nested_frame_rate_and_resolution() {
|
||||
let mut s = session();
|
||||
let o = ok(
|
||||
&mut s,
|
||||
r#"
|
||||
var comp = app.project.items.addComp("Nested", 320, 180, 1, 2, 12);
|
||||
var before = [comp.preserveNestedFrameRate, comp.preserveNestedResolution];
|
||||
comp.preserveNestedFrameRate = true;
|
||||
comp.preserveNestedResolution = true;
|
||||
before.concat([comp.preserveNestedFrameRate, comp.preserveNestedResolution]).join(",");
|
||||
"#,
|
||||
);
|
||||
assert_eq!(o.result, json!("false,false,true,true"));
|
||||
let (_, c) = comp_named(&s, "Nested");
|
||||
assert!(c.preserve_frame_rate && c.preserve_resolution);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ pub struct CompDraft {
|
|||
pub shutter_phase: f64,
|
||||
pub samples: u32,
|
||||
pub adaptive_limit: u32,
|
||||
pub preserve_frame_rate: bool,
|
||||
pub preserve_resolution: bool,
|
||||
pub advanced_3d: bool,
|
||||
pub tab: Tab,
|
||||
/// Timecode text being edited (start, duration).
|
||||
|
|
@ -112,6 +114,8 @@ impl Default for CompDraft {
|
|||
shutter_phase: -90.0,
|
||||
samples: 16,
|
||||
adaptive_limit: 128,
|
||||
preserve_frame_rate: false,
|
||||
preserve_resolution: false,
|
||||
advanced_3d: false,
|
||||
tab: Tab::Basic,
|
||||
start_tc: None,
|
||||
|
|
@ -304,6 +308,14 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
|
|||
}
|
||||
});
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
let r = ui.checkbox(&mut d.preserve_frame_rate, "Preserve frame rate when nested or in render queue");
|
||||
auto.add("dialog.comp.preserveFrameRate", r.rect, "Preserve frame rate when nested or in render queue");
|
||||
ui.end_row();
|
||||
ui.label("");
|
||||
let r = ui.checkbox(&mut d.preserve_resolution, "Preserve resolution when nested");
|
||||
auto.add("dialog.comp.preserveResolution", r.rect, "Preserve resolution when nested");
|
||||
ui.end_row();
|
||||
ui.label(egui::RichText::new("Motion Blur").strong());
|
||||
ui.end_row();
|
||||
ui.label("Shutter Angle:");
|
||||
|
|
@ -360,6 +372,7 @@ pub fn params(d: &CompDraft) -> serde_json::Value {
|
|||
"name": d.name, "width": d.width, "height": d.height, "pixelAspect": d.pixel_aspect, "frameRate": d.fps,
|
||||
"duration": d.duration, "startTime": d.start, "background": [d.bg[0], d.bg[1], d.bg[2]], "anchor": d.anchor,
|
||||
"shutterAngle": d.shutter_angle, "shutterPhase": d.shutter_phase, "motionBlurSamples": d.samples, "adaptiveSampleLimit": d.adaptive_limit,
|
||||
"preserveFrameRate": d.preserve_frame_rate, "preserveResolution": d.preserve_resolution,
|
||||
"renderer": if d.advanced_3d { "advanced3D" } else { "classic3D" },
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ pub fn open_comp_settings(app: &mut EffectcraftApp) -> Result<(), String> {
|
|||
shutter_phase: c.shutter_phase,
|
||||
samples: c.motion_blur_samples,
|
||||
adaptive_limit: c.motion_blur_adaptive_limit,
|
||||
preserve_frame_rate: c.preserve_frame_rate,
|
||||
preserve_resolution: c.preserve_resolution,
|
||||
advanced_3d: c.renderer == effectcraft_engine::project::Renderer::Advanced3D,
|
||||
..Default::default()
|
||||
};
|
||||
|
|
@ -604,7 +606,7 @@ mod tests {
|
|||
}
|
||||
app.dialog_state.comp.tab = super::super::comp_settings::Tab::Advanced;
|
||||
frame(&mut app, &ctx);
|
||||
for id in ["anchor.4", "shutterAngle", "shutterPhase", "samples", "adaptiveLimit"] {
|
||||
for id in ["anchor.4", "preserveFrameRate", "preserveResolution", "shutterAngle", "shutterPhase", "samples", "adaptiveLimit"] {
|
||||
assert!(app.auto.find(&format!("dialog.comp.{id}")).is_some(), "dialog.comp.{id}");
|
||||
}
|
||||
app.dialog_state.comp.tab = super::super::comp_settings::Tab::Basic;
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
&& let Some(nc) = app.session.project.comp(*item)
|
||||
{
|
||||
let (outer, _) = l2c(&ectx, l);
|
||||
let nctx = EvalCtx::new(&app.session.project, *item, nc, ectx.source_time(l));
|
||||
let nctx = EvalCtx::new(&app.session.project, *item, nc, ectx.nested_time(l));
|
||||
for nl in nc.layers.iter().filter(|nl| nl.is_active_at(nctx.time) && !nl.is_3d()) {
|
||||
if let Some((_, q, _)) = layer_quad(&nctx, nl) {
|
||||
let sq: Vec<Pos2> = q
|
||||
|
|
|
|||
Loading…
Reference in New Issue