mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-13 15:09:03 +00:00
Merge pull request #1175 from storybored379/fix/roto-trim-span
# Conflicts: # ROADMAP.md
This commit is contained in:
commit
3aac824976
4 files changed
+45
-4
No files matched your search
@@ -117,6 +117,8 @@ Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-10-12: Roto Brush span edits report strokes outside a trimmed layer without panicking;
|
||||
strokes and undo history are preserved so restoring the timing allows the span edit again.
|
||||
- 2026-10-11: Ukrainian panels caught up with the Chinese catalogs. The 95 rows the panel
|
||||
catalog gained since 9 October are translated: the Home screen (recent files, their Kind and
|
||||
Opened columns, the empty and no-match states), the viewer's toolbar tooltips, magnification,
|
||||
@@ -149,6 +151,7 @@ Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | Open-issue count (134 to 216), crash list and localization share (12% to 18%) updated to match docs/gaps.md |
|
||||
| 2026-10-12 | patch | Roto Brush span validation after trimming; command regressions for both layer edges |
|
||||
| 2026-10-10 | minor | Hours to ≈ 95% for each audience |
|
||||
| 2026-10-10 | minor | Headline adds mainstream practitioner (≈ 47%) and essentials user (≈ 52%) |
|
||||
| 2026-10-10 | minor | Stage checked against the new core-workflow gate (all six pass; still alpha) |
|
||||
|
||||
@@ -268,16 +268,22 @@ fn span(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Err(bad(cmd, "the Roto Brush is frozen: Unfreeze it to change the span"));
|
||||
}
|
||||
let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?;
|
||||
let lim = rt::frame_limits(comp, l);
|
||||
let [lo, hi] = rt::frame_limits(comp, l);
|
||||
let mut d = data_of(g);
|
||||
let base = d.base.ok_or_else(|| bad(cmd, "no strokes yet"))?;
|
||||
let first = d.strokes.iter().map(|s| s.frame).min().unwrap_or(base);
|
||||
let last = d.strokes.iter().map(|s| s.frame).max().unwrap_or(base);
|
||||
// Trimming or shortening the comp can leave saved strokes outside the layer. No span
|
||||
// inside its new bounds can contain those strokes; keep them for undo or a later untrim.
|
||||
if first < lo || last > hi {
|
||||
return Err(bad(cmd, "strokes outside the layer: restore the layer/comp timing or clear those strokes before changing the span"));
|
||||
}
|
||||
let [start, end] = &mut d.span;
|
||||
if let Some(a) = f_p(p, "start") {
|
||||
d.span[0] = (a.round() as i64).clamp(lim[0], first);
|
||||
*start = (a.round() as i64).clamp(lo, first);
|
||||
}
|
||||
if let Some(b) = f_p(p, "end") {
|
||||
d.span[1] = (b.round() as i64).clamp(last, lim[1]);
|
||||
*end = (b.round() as i64).clamp(last, hi);
|
||||
}
|
||||
write_data(s, "Segmentation Span", cid, lid, uid, &d)?;
|
||||
Ok(json!({"layer": lid.0, "effect": uid, "span": d.span, "base": base}))
|
||||
|
||||
@@ -27,6 +27,35 @@ pub(crate) fn hold_roto_cache_test_lock() {
|
||||
}
|
||||
const R: f64 = 40.0;
|
||||
|
||||
fn span_after_trimming_strokes(trim: Value, span: Value, expected: [i64; 2]) {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"width": 64, "height": 64, "frameRate": 25, "duration": 1})).unwrap();
|
||||
let layer = s.execute("layer.newSolid", json!({"width": 64, "height": 64})).unwrap()["layer"].as_u64().unwrap();
|
||||
for frame in [0, 20] {
|
||||
s.execute("roto.stroke", json!({"layer": layer, "frame": frame, "points": [[32, 32]], "radius": 4})).unwrap();
|
||||
}
|
||||
s.execute("layer.timing", trim).unwrap();
|
||||
let before = s.project.to_json();
|
||||
let error = s.execute("roto.span", span.clone()).unwrap_err().to_string();
|
||||
assert!(error.contains("strokes outside the layer"), "{error}");
|
||||
assert_eq!(s.project.to_json(), before, "refusing the span must preserve strokes and project state");
|
||||
assert!(s.undo(), "the failed command must not add an undo step");
|
||||
let result = s.execute("roto.span", span).unwrap();
|
||||
assert_eq!(result["span"], json!(expected));
|
||||
let status = s.execute("roto.status", json!({"layer": layer})).unwrap();
|
||||
assert_eq!(status["strokeFrames"], json!([0, 20]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roto_span_rejects_strokes_before_trimmed_in_point() {
|
||||
span_after_trimming_strokes(json!({"in": 0.2}), json!({"start": 0}), [0, 20]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roto_span_rejects_strokes_after_trimmed_out_point() {
|
||||
span_after_trimming_strokes(json!({"out": 0.5}), json!({"end": 24}), [0, 24]);
|
||||
}
|
||||
|
||||
fn noise(x: f64, y: f64, seed: u32) -> f64 {
|
||||
fn h(i: i64, j: i64, s: u32) -> f64 {
|
||||
let mut v = (i.wrapping_mul(73856093) ^ j.wrapping_mul(19349663) ^ (s as i64).wrapping_mul(83492791)) as u64;
|
||||
|
||||
+4
-1
@@ -452,7 +452,10 @@ direction?: forward|backward|both, to?, wait?}` segments the span (in the backgr
|
||||
`wait`); strokes on any other frame correct it and propagation restarts from there.
|
||||
`roto.span {start?, end?}`, `roto.freeze {wait?}` / `roto.unfreeze`, `roto.clearStrokes {frame?,
|
||||
kind?}`, `roto.cancel` and `roto.options {diameter?, refineDiameter?, view?: alphaBoundary|alpha|
|
||||
alphaOverlay|none}` complete the set; every edit is one undo step. `roto.status {layer, frame?,
|
||||
alphaOverlay|none}` complete the set; every edit is one undo step. If trimming the layer or
|
||||
shortening the comp leaves strokes outside the layer, `roto.span` returns an error without
|
||||
changing the strokes: restore the timing or remove those strokes with `roto.clearStrokes` first.
|
||||
`roto.status {layer, frame?,
|
||||
matte?, compute?, compareTo?}` reports the base frame, span, computed and stroked frames, frozen
|
||||
state, job progress and, for a frame, the matte's area, centroid, RLE matte and IoU against a
|
||||
reference. Matte settings are ordinary properties (`effects/#1/rotoBrushMatte/searchRadius`,
|
||||
|
||||
Reference in new issue
Block a user