708e8a0d8f
Deploy / deploy (push) Successful in 3m28s
Blender 5 logs through CLOG, prefixing every line with elapsed time and a
category ("00:02.954 render | Saved: '...'"), and reports errors as
"ERROR ..." instead of "Error: ...". The parser anchored on "Saved:" at the
start of the line, so it counted no frames from a render that wrote all of
them and the job failed as "saved no frames". Progress (Fra:) and error
detection were dead on 5.x for the same reason.
The prefix is now stripped before matching; 4.x output parses as before.
Warnings no longer count as errors: add-ons print them on every launch and
one would otherwise be reported as the reason a render failed. A run that
saves nothing now reports Blender's own error when there is one.
Verified on the real DH2 test.blend under Blender 5.2: the 1.6.2 parser
counts 0 of 3 saved frames, this one counts 3.
Bump worker to 1.6.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.3 KiB
C#
89 lines
3.3 KiB
C#
using System.Text.RegularExpressions;
|
|
|
|
namespace VFXReviewWorker;
|
|
|
|
/// <summary>
|
|
/// Parses `blender -b` stdout. Blender reports the frame it is working on in
|
|
/// its status lines ("Fra:1042 Mem:512.00M ...") and confirms each written file
|
|
/// with "Saved: '...'". The saved lines are the reliable signal — a frame can
|
|
/// be reported for a long time while it renders — so progress counts those and
|
|
/// falls back to Fra: for a first sign of life.
|
|
///
|
|
/// Blender 5.x logs through CLOG, which prefixes every line with an elapsed
|
|
/// time and a category ("00:02.954 render | Saved: '...'") and
|
|
/// reports errors as "ERROR ..." rather than "Error: ...". The prefix is
|
|
/// stripped before matching, so both formats parse the same way.
|
|
/// </summary>
|
|
public static partial class BlenderProgressParser
|
|
{
|
|
[GeneratedRegex(@"^(?:\d+:)?\d{2}:\d{2}\.\d{3}\s+\S+\s*\| ?", RegexOptions.Compiled)]
|
|
private static partial Regex LogPrefix();
|
|
|
|
[GeneratedRegex(@"^Fra:\s*(\d+)", RegexOptions.Compiled)]
|
|
private static partial Regex FrameLine();
|
|
|
|
[GeneratedRegex(@"^Saved:\s*'(.+)'", RegexOptions.Compiled)]
|
|
private static partial Regex SavedLine();
|
|
|
|
// Errors only. Warnings are not failures, and add-ons print them on every
|
|
// launch ("Warning: add-on missing 'bl_info'"), so counting them made an
|
|
// add-on's complaint the reported reason for a failed render.
|
|
[GeneratedRegex(@"(?i:^Error: )|^(ERROR|FATAL) ", RegexOptions.Compiled)]
|
|
private static partial Regex ErrorLine();
|
|
|
|
/// <summary>The line without Blender 5.x's "elapsed category | " prefix.</summary>
|
|
private static string Message(string line)
|
|
{
|
|
var m = LogPrefix().Match(line);
|
|
return m.Success ? line[m.Length..] : line;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Failures that will repeat identically on another machine — a missing
|
|
/// file, an unknown scene, an unwritable output. Retrying these burns the
|
|
/// queue for nothing; a human has to fix the .blend or the path.
|
|
/// </summary>
|
|
private static readonly string[] NonRetryablePatterns =
|
|
{
|
|
"cannot read file",
|
|
"file not found",
|
|
"unable to open",
|
|
"is not a blend file",
|
|
"failed to read blend file",
|
|
"unknown scene",
|
|
"scene not found",
|
|
"unable to create directory",
|
|
"couldn't create directory",
|
|
"cannot write file",
|
|
"cannot save:",
|
|
"no camera",
|
|
"python script fail",
|
|
"unknown argument",
|
|
};
|
|
|
|
/// <summary>The frame Blender is currently working on, or null.</summary>
|
|
public static int? ParseFrame(string line)
|
|
{
|
|
var m = FrameLine().Match(Message(line));
|
|
return m.Success && int.TryParse(m.Groups[1].Value, out var f) ? f : null;
|
|
}
|
|
|
|
/// <summary>The path of a frame Blender has just written, or null.</summary>
|
|
public static string? ParseSaved(string line)
|
|
{
|
|
var m = SavedLine().Match(Message(line));
|
|
return m.Success ? m.Groups[1].Value : null;
|
|
}
|
|
|
|
public static bool IsErrorLine(string line) => ErrorLine().IsMatch(Message(line));
|
|
|
|
public static bool IsNonRetryableError(string line)
|
|
{
|
|
foreach (var p in NonRetryablePatterns)
|
|
{
|
|
if (line.Contains(p, StringComparison.OrdinalIgnoreCase)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|