Follow-ups to the batch: name numbering keeps no double space (#2908), Material Options error names the parameters (#2924), corrupt file timecodes stay on timecode (#2835), restore #2934's scroll-gutter asserts, #2825's Motion Tile test compiles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-11-03 09:30:22 -05:00
1 parent e96f47f3c2
commit 26228247e2
5 files changed
+12 -7

No files matched your search

+2 -2
View File
@@ -827,8 +827,8 @@ mod tests {
0.0,
);
assert_eq!((out.img.width, out.img.height), (10, 8));
for (x, mirrored) in [(2u32, 0u32), (7, 9)] {
for y in 0..out.img.height {
for (x, mirrored) in [(2i64, 0i64), (7, 9)] {
for y in 0..i64::from(out.img.height) {
let a = out.img.get(x, y);
assert!((a[3] - 1.0).abs() < 1e-5, "fold x={x},y={y} faded to alpha {}", a[3]);
let b = img.get(mirrored, y);
+1 -1
View File
@@ -245,7 +245,7 @@ fn apply_material(l: &mut Layer, p: &Value, t: Tick, cmd: &str) -> Result<Vec<&'
fn material_mismatch(l: Option<&Layer>, missing: &[&str]) -> EngineError {
let names = missing.iter().map(|k| format!("`{k}`")).collect::<Vec<_>>().join(", ");
let Some(l) = l else { return bad("material.set", format!("no layer has {names}")) };
let Some(g) = l.props.sub("materialOptions") else { return bad("material.set", format!("`{}` has no Material Options", l.name)) };
let Some(g) = l.props.sub("materialOptions") else { return bad("material.set", format!("`{}` has no Material Options, so it has no {names}", l.name)) };
let has: Vec<&str> = g.children.iter().map(|c| c.match_id()).collect();
let why = if matches!(l.source, LayerSource::Model { .. }) { ": a 3D model layer keeps its model file's materials" } else { "" };
bad(
+6 -1
View File
@@ -185,6 +185,9 @@ pub fn import(
const EDL_MEDIA_TIME_NOTE: &str = "EDL source timecodes were imported as media time";
/// An EDL's source times are its source timecodes, which FilmCraft's reader keeps as media time.
/// The largest start timecode, in frames, a file may have: a day at 1000 fps.
const MAX_FILE_TIMECODE: i64 = 24 * 3600 * 1000;
/// Put the clips of each file that has a start timecode on the file's own time, as FilmCraft does
/// when it links the media of an imported EDL. Returns the names of the media that stay on
/// timecode though their clips use an hour or more of it (the file is missing or has none).
@@ -212,7 +215,9 @@ fn rebase_to_file_timecode(project: &mut fp::Project, probe: &mut dyn FnMut(&str
let mut left = Vec::new();
for (id, name, path, offline) in files {
match if offline { None } else { probe(&path) } {
Some(Footage { file_timecode: Some(tc), native_rate, frame_rate, .. }) => {
// A corrupt timecode past a day of frames (at up to 1000 fps) would overflow the tick
// arithmetic; such a file stays on timecode.
Some(Footage { file_timecode: Some(tc), native_rate, frame_rate, .. }) if (0..=MAX_FILE_TIMECODE).contains(&tc) => {
filmcraft_interchange::rebase_source_timecode(project, id, tc, crate::rate_out(native_rate.unwrap_or(frame_rate)));
}
_ if on_timecode.contains(&id) => left.push(name),
+1 -1
View File
@@ -12,7 +12,7 @@ pub(crate) fn numbered_name(base: &str, taken: impl Fn(&str) -> bool) -> String
let head = base.trim_end_matches(|c: char| c.is_ascii_digit());
let stem = match head.strip_suffix(' ') {
Some(stem) if head.len() < base.len() && !stem.trim_end().is_empty() => stem.trim_end(),
_ => base,
_ => base.trim_end(),
};
(2u64..).map(|k| format!("{stem} {k}")).find(|n| !taken(n)).unwrap_or_default()
}
+2 -2
View File
@@ -409,13 +409,13 @@ fn panel_rows_stop_before_their_scroll_bar() {
assert!(scrolled(&h, bar).1 > 0.0, "{bar}: the rows overflow");
let track = rect(&h, bar);
if let Some(right) = rightmost(&h, prefix, bar, Some(track)) {
if right > track.min.x { eprintln!("MUT {}", format!("{bar}: the rows end at {right}, on the track {track:?}")); }
assert!(right <= track.min.x, "{bar}: the rows end at {right}, on the track {track:?}");
} else {
assert!(matches!(bar, "info.scroll" | "markers.scroll"), "{bar}: no rows beside the track");
}
let lane = Rect::from_x_y_ranges(track.min.x..=track.max.x, track.y_range());
for (text, at) in painted_text(&h) {
if at.intersects(lane) { eprintln!("MUT {}", format!("{bar}: {text:?} shows at {at:?}, on the track {track:?}")); }
assert!(!at.intersects(lane), "{bar}: {text:?} shows at {at:?}, on the track {track:?}");
}
}
let _ = std::fs::remove_dir_all(&folder);