From 712c1215ed309ca11ccc1be39fb5460ff6b38ac8 Mon Sep 17 00:00:00 2001 From: Nathan Date: Thu, 6 Nov 2025 11:02:01 -0700 Subject: [PATCH] update flamscripts with new render seqzip workflow --- ...7-28Z-update-path-structure-for-renders.md | 1030 +++++++++++++++++ scripts/TalkingHeads Custom Render.js | 57 +- scripts/TalkingHeads cycles_optix_gpu.js | 39 +- 3 files changed, 1047 insertions(+), 79 deletions(-) create mode 100644 .specstory/history/2025-11-06_17-28Z-update-path-structure-for-renders.md diff --git a/.specstory/history/2025-11-06_17-28Z-update-path-structure-for-renders.md b/.specstory/history/2025-11-06_17-28Z-update-path-structure-for-renders.md new file mode 100644 index 0000000..f6c71cd --- /dev/null +++ b/.specstory/history/2025-11-06_17-28Z-update-path-structure-for-renders.md @@ -0,0 +1,1030 @@ + + + + +# Update path structure for renders (2025-11-06 17:28Z) + +_**User (2025-11-06 17:28Z)**_ + +@TalkingHeads Custom Render.js @TalkingHeads cycles_optix_gpu.js we're updating the path structure: no more dailies; sequences should just go straight to their Renders\blendname folder, or their submodule if checked (Renders\submodule\blendname) + +--- + +_**Agent (model gpt-5-codex, mode Agent)**_ + +
Thought Process +**Redesigning render output path logic** + +I’m working through updating the render output paths to remove the daily folder concept entirely, restructuring sequences to go directly under `Renders\` or `Renders\\`. This means removing all daily path references and adjusting settings, eval expressions, and functions accordingly, while ensuring the blend name drives folder naming instead of job name or daily paths. There's complexity balancing backward compatibility and clarity, but I’m focusing on simplifying by basing paths on the blend file stem and cleaning out obsolete daily-related code. +**Detailing patch preparation for daily removal** + +I’m refining the plan to remove all daily path handling, carefully adjusting path parts buildup to exclude daily while allowing empty strings in path joins without breaking structure. I’m systematically identifying code blocks to remove or update, including settings entries, print statements, and obsolete functions like `formatDailyYYMMDD`. Next, I’ll create a precise patch with multiple changes to ensure all references to daily paths get cleared consistently across relevant scripts.
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js +@@ + { + key: "render_output_root", + type: "string", + subtype: "dir_path", + required: false, + visible: "submission", + eval: "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))", + evalInfo: { + showLinkButton: true, + description: "Auto-detect the project's Renders folder" + }, + description: "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically." + }, +- { +- key: "daily_path", +- type: "string", +- required: false, +- visible: "submission", +- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')", +- evalInfo: { +- showLinkButton: true, +- description: "Auto-fill with today's daily folder name" +- }, +- description: "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date." +- }, + { + key: "use_submodule", + label: "Use Submodule", + type: "bool", + required: false, +@@ + { + key: "render_output_path", + type: "string", + subtype: "file_path", + editable: false, +- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", ++ eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", + description: "Final file path of where render output will be saved" + }, +@@ +- if (!settings.daily_path || String(settings.daily_path).trim() === '') { +- const createdDate = job && job.created ? new Date(job.created) : new Date(); +- settings.daily_path = formatDailyYYMMDD(createdDate); +- } + const recomposed = computeAutoRenderOutputPath(job); + if (recomposed) settings.render_output_path = recomposed; +@@ +- const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings); +- +- // Resolve render root +- let renderRoot = null; +- if (settings.render_output_root && ("" + settings.render_output_root).trim()) { +- renderRoot = ("" + settings.render_output_root).trim(); +- } else if (projectRoot) { +- renderRoot = path.join(projectRoot, 'Renders'); +- } else { +- // Fallback to the blendfile's directory Renders sibling +- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); +- } +- +- // Resolve daily path +- let daily = null; +- if (settings.daily_path && ("" + settings.daily_path).trim()) { +- daily = ("" + settings.daily_path).trim(); +- } else { +- const createdDate = job && job.created ? new Date(job.created) : new Date(); +- daily = formatDailyYYMMDD(createdDate); +- } +- const jobname = (job && job.name) ? job.name : path.stem(settings.blendfile).replace('.flamenco', ''); ++ const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings); ++ ++ // Resolve render root ++ let renderRoot = null; ++ if (settings.render_output_root && ("" + settings.render_output_root).trim()) { ++ renderRoot = ("" + settings.render_output_root).trim(); ++ } else if (projectRoot) { ++ renderRoot = path.join(projectRoot, 'Renders'); ++ } else { ++ // Fallback to the blendfile's directory Renders sibling ++ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); ++ } ++ ++ const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); + + print('AutoPath: blendfile=', settings.blendfile); + print('AutoPath: projectRoot=', projectRoot); + print('AutoPath: renderRoot=', renderRoot); + print('AutoPath: submodule=', submodule); +- print('AutoPath: daily=', daily); +- print('AutoPath: jobname=', jobname); ++ print('AutoPath: blendname=', blendname); + + const parts = [renderRoot]; + if (submodule) parts.push(submodule); +- parts.push(daily, jobname, `${jobname}_######`); ++ parts.push(blendname, `${blendname}_######`); + const finalPath = path.join.apply(path, parts); + print('AutoPath: finalPath=', finalPath); + return finalPath; + } +@@ +-function formatDailyYYMMDD(dateObj) { +- const pad2 = (n) => (n < 10 ? '0' + n : '' + n); +- const yy = (dateObj.getFullYear() % 100); +- const mm = dateObj.getMonth() + 1; +- const dd = dateObj.getDate(); +- return `daily_${pad2(yy)}${pad2(mm)}${pad2(dd)}`; +-} +- +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Preparing patch for script adjustments**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js +@@ + { + key: 'render_output_root', + type: 'string', + subtype: 'dir_path', + required: false, + visible: 'submission', + eval: + "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))", + evalInfo: { showLinkButton: true, description: "Auto-detect the project's Renders folder" }, + description: + "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.", + }, +- { +- key: 'daily_path', +- type: 'string', +- required: false, +- visible: 'submission', +- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')", +- evalInfo: { showLinkButton: true, description: "Auto-fill with today's daily folder name" }, +- description: +- "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.", +- }, + { + key: 'use_submodule', + label: 'Use Submodule', + type: 'bool', + required: false, +@@ + { + key: 'render_output_path', + type: 'string', + subtype: 'file_path', + editable: false, +- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", ++ eval: ++ "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", + description: 'Final file path of where render output will be saved', + }, +@@ +- if (!settings.daily_path || String(settings.daily_path).trim() === '') { +- const createdDate = job && job.created ? new Date(job.created) : new Date(); +- settings.daily_path = formatDailyYYMMDD(createdDate); +- } + const recomposed = computeAutoRenderOutputPath(job); + if (recomposed) settings.render_output_path = recomposed; +@@ +- const submodule = detectSubmodule(settings); +- // Resolve render root +- let renderRoot = null; +- if (settings.render_output_root && ("" + settings.render_output_root).trim()) { +- renderRoot = ("" + settings.render_output_root).trim(); +- } else if (projectRoot) { +- renderRoot = path.join(projectRoot, 'Renders'); +- } else { +- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); +- } +- // Resolve daily path +- let daily = null; +- if (settings.daily_path && ("" + settings.daily_path).trim()) { +- daily = ("" + settings.daily_path).trim(); +- } else { +- const createdDate = job && job.created ? new Date(job.created) : new Date(); +- daily = formatDailyYYMMDD(createdDate); +- } +- const jobname = job && job.name ? job.name : path.stem(settings.blendfile).replace('.flamenco', ''); +- +- const parts = [renderRoot]; +- if (submodule) parts.push(submodule); +- parts.push(daily, jobname, `${jobname}_######`); +- return path.join.apply(path, parts); ++ const submodule = detectSubmodule(settings); ++ // Resolve render root ++ let renderRoot = null; ++ if (settings.render_output_root && ("" + settings.render_output_root).trim()) { ++ renderRoot = ("" + settings.render_output_root).trim(); ++ } else if (projectRoot) { ++ renderRoot = path.join(projectRoot, 'Renders'); ++ } else { ++ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); ++ } ++ const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); ++ ++ const parts = [renderRoot]; ++ if (submodule) parts.push(submodule); ++ parts.push(blendname, `${blendname}_######`); ++ return path.join.apply(path, parts); + } +@@ +-function formatDailyYYMMDD(dateObj) { +- const pad2 = (n) => (n < 10 ? '0' + n : '' + n); +- const yy = dateObj.getFullYear() % 100; +- const mm = dateObj.getMonth() + 1; +- const dd = dateObj.getDate(); +- return `daily_${pad2(yy)}${pad2(mm)}${pad2(dd)}`; +-} +- +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Verifying removal of formatDaily references**
+ +--- + + +
+Tool use: **grep** • Grep for "daily_path" in "scripts" • 0 matches + +Output mode: content + + +_No matches found_ + +
+
+ +--- + +
Thought Process +**Checking formatDaily function**
+ +--- + + +
+Tool use: **grep** • Grep for "formatDaily" in "scripts" • 0 matches + +Output mode: content + + +_No matches found_ + +
+
+ +--- + +
Thought Process +**Verifying removal of jobname references**
+ +--- + + +
+ Tool use: **read_lints** • Read lints for 2 files + +Lint paths: + +- `scripts/TalkingHeads Custom Render.js` +- `scripts/TalkingHeads cycles_optix_gpu.js` + +Lint results + + +**No lint errors found** +
+
+ +--- + +
Thought Process +**Verifying parentheses and formatting consistency**
+ +--- + + +
Tool use: **read_file** • Read file: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js + +
+
+ +--- + +
Thought Process +**Updating comments to remove outdated references**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js +@@ +-// Derive project root, submodule, and daily path from the blendfile path. ++// Derive project root and submodule from the blendfile path. +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Updating code comment**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js +@@ +-// Derive project root, submodule, and daily path from the blendfile path. ++// Derive project root and submodule from the blendfile path. +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Deciding whether to rerun read_lints**
+ +--- + + +
+ Tool use: **read_lints** • Read lints for 2 files + +Lint paths: + +- `scripts/TalkingHeads Custom Render.js` +- `scripts/TalkingHeads cycles_optix_gpu.js` + +Lint results + + +**No lint errors found** +
+
+ +--- + +
Thought Process +**Preparing final response and code citations** + +I’m gathering precise code snippets with line numbers to clearly show modifications, confirming no leftover 'daily' references, and planning to explain changes, paths, and verification steps in the final summary.
+ +--- + + +
Tool use: **read_file** • Read file: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js + +
+
+ +--- + +
Thought Process +**Preparing code snippet for cycles file**
+ +--- + + +
Tool use: **read_file** • Read file: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js + +
+
+ +--- + +
Thought Process +**Preparing code snippet for computeAuto**
+ +--- + + +
Tool use: **read_file** • Read file: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js + +
+
+ +--- + +
Thought Process +**Summarizing configuration changes and message structure** + +I'll clearly explain the removal of `daily_path` and the new default for `render_output_path`, then prepare a concise final message with sections for summary, code references—showing precise code snippets with line numbers—and next steps like triggering a test render, ensuring clarity and completeness.
+ +--- + +## Summary +- Updated both TalkingHeads job types to drop `daily_path` usage; auto paths now go straight to `Renders/` or `Renders//`. +- Simplified compile-time autofill logic to rely on the blend name, keeping optional submodule support untouched. + +## Code References +- Render output defaults now skip the daily folder and target the blend-name directory. +```71:76:scripts/TalkingHeads Custom Render.js + { + key: "render_output_path", + type: "string", + subtype: "file_path", + editable: false, + eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", + description: "Final file path of where render output will be saved" +``` +```60:65:scripts/TalkingHeads cycles_optix_gpu.js + { + key: 'render_output_path', + type: 'string', + subtype: 'file_path', + editable: false, + eval: + "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +``` +- Auto-path assembly now appends only the blend name (plus frame pattern), preserving optional submodule handling. +```187:218:scripts/TalkingHeads Custom Render.js +// Derive project root and submodule from the blendfile path. +function computeAutoRenderOutputPath(job) { + const settings = job.settings || {}; + if (!settings.blendfile) return null; + + const projectRoot = findProjectRootFromBlendfile(settings.blendfile); + + const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings); + + // Resolve render root + let renderRoot = null; + if (settings.render_output_root && ("" + settings.render_output_root).trim()) { + renderRoot = ("" + settings.render_output_root).trim(); + } else if (projectRoot) { + renderRoot = path.join(projectRoot, 'Renders'); + } else { + // Fallback to the blendfile's directory Renders sibling + renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); + } + + const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); + + const parts = [renderRoot]; + if (submodule) parts.push(submodule); + parts.push(blendname, `${blendname}_######`); + const finalPath = path.join.apply(path, parts); + return finalPath; +``` +```364:386:scripts/TalkingHeads cycles_optix_gpu.js +// Derive project root and submodule from the blendfile path. +function computeAutoRenderOutputPath(job) { + const settings = job.settings || {}; + if (!settings.blendfile) return null; + + const projectRoot = findProjectRootFromBlendfile(settings.blendfile); + + const submodule = detectSubmodule(settings); + // Resolve render root + let renderRoot = null; + if (settings.render_output_root && ("" + settings.render_output_root).trim()) { + renderRoot = ("" + settings.render_output_root).trim(); + } else if (projectRoot) { + renderRoot = path.join(projectRoot, 'Renders'); + } else { + renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); + } + const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); + + const parts = [renderRoot]; + if (submodule) parts.push(submodule); + parts.push(blendname, `${blendname}_######`); + return path.join.apply(path, parts); +} +``` + +## Next Steps +- Kick off a test render for each job type to confirm outputs land in the expected `Renders/` (and optional submodule) directories. + +--- + +_**User (2025-11-06 17:28Z)**_ + +Python: Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 120, in eval_setting + + value = eval(setting_eval, {}, eval_locals) + + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + File "", line 1, in + + File "", line 1, in + +NameError: name 'Path' is not defined + + + +The above exception was the direct cause of the following exception: + + + +Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 122, in eval_setting + + raise SettingEvalError(setting_key, setting_eval, eval_locals, ex) from ex + +flamenco.job_types_propgroup.SettingEvalError: Evaluation error of setting 'render_output_path': name 'Path' is not defined + + + +--- + +_**Agent (model gpt-5-codex, mode Agent)**_ + +
Thought Process +**Adjusting eval string to define Path**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js +@@ +- { +- key: "render_output_path", +- type: "string", +- subtype: "file_path", +- editable: false, +- eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: "Final file path of where render output will be saved" +- }, ++ { ++ key: "render_output_path", ++ type: "string", ++ subtype: "file_path", ++ editable: false, ++ eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: "Final file path of where render output will be saved" ++ }, +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Applying change to cycles script**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js +@@ +- { +- key: 'render_output_path', +- type: 'string', +- subtype: 'file_path', +- editable: false, +- eval: +- "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: 'Final file path of where render output will be saved', +- }, ++ { ++ key: 'render_output_path', ++ type: 'string', ++ subtype: 'file_path', ++ editable: false, ++ eval: ++ "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: 'Final file path of where render output will be saved', ++ }, +*** End Patch +``` + +
+
+ +--- + +
Thought Process +**Running read_lints command**
+ +--- + + +
+ Tool use: **read_lints** • Read lints for 2 files + +Lint paths: + +- `scripts/TalkingHeads Custom Render.js` +- `scripts/TalkingHeads cycles_optix_gpu.js` + +Lint results + + +**No lint errors found** +
+
+ +--- + +
Thought Process +**Preparing response with fix explanation**
+ +--- + +Updated both TalkingHeads job types so the `render_output_path` eval now inlines `Path` via `__import__('pathlib').Path`, eliminating the NameError while keeping the new render-folder layout. Let me know if Blender still complains or if we should adjust the join logic further. + +--- + +_**User (2025-11-06 17:28Z)**_ + +Python: Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 120, in eval_setting + + value = eval(setting_eval, {}, eval_locals) + + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + File "", line 1, in + + File "", line 1, in + +NameError: name 'abspath' is not defined + + + +The above exception was the direct cause of the following exception: + + + +Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 122, in eval_setting + + raise SettingEvalError(setting_key, setting_eval, eval_locals, ex) from ex + +flamenco.job_types_propgroup.SettingEvalError: Evaluation error of setting 'render_output_path': name 'abspath' is not defined + + + +--- + +_**Agent (model gpt-5-codex, mode Agent)**_ + +
Thought Process +**Reworking path construction in eval**
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js +@@ +- { +- key: "render_output_path", +- type: "string", +- subtype: "file_path", +- editable: false, +- eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: "Final file path of where render output will be saved" +- }, ++ { ++ key: "render_output_path", ++ type: "string", ++ subtype: "file_path", ++ editable: false, ++ eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: "Final file path of where render output will be saved" ++ }, +*** End Patch +``` + +
+
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js +@@ +- { +- key: 'render_output_path', +- type: 'string', +- subtype: 'file_path', +- editable: false, +- eval: +- "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: 'Final file path of where render output will be saved', +- }, ++ { ++ key: 'render_output_path', ++ type: 'string', ++ subtype: 'file_path', ++ editable: false, ++ eval: ++ "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: 'Final file path of where render output will be saved', ++ }, +*** End Patch +``` + +
+
+ +--- + + +
+ Tool use: **read_lints** • Read lints for 2 files + +Lint paths: + +- `scripts/TalkingHeads Custom Render.js` +- `scripts/TalkingHeads cycles_optix_gpu.js` + +Lint results + + +**No lint errors found** +
+
+ +--- + +
Thought Process +**Referencing updated evaluation**
+ +--- + +The eval now passes both `Path` and `abspath` explicitly, so Blender can resolve them without assuming any globals. Each TalkingHeads job type uses: + +``` +((lambda Path, abspath, os_path, blend: + str( + Path(abspath(settings.render_output_root or '//')) + / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) + else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) + / blend + / (blend + '_######') + ) +)( + __import__('pathlib').Path, + __import__('os').path.abspath, + __import__('os').path, + __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0] +)) +``` + +Give Blender another shot and it should evaluate `render_output_path` without the NameError. Let me know if it still throws anything. + +--- + +_**User (2025-11-06 17:28Z)**_ + +Python: Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 120, in eval_setting + + value = eval(setting_eval, {}, eval_locals) + + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + File "", line 1, in + + File "", line 1, in + +NameError: name 'settings' is not defined + + + +The above exception was the direct cause of the following exception: + + + +Traceback (most recent call last): + + File "C:\Program Files (x86)\Steam\steamapps\common\Blender\portable\scripts\addons\flamenco\job_types_propgroup.py", line 122, in eval_setting + + raise SettingEvalError(setting_key, setting_eval, eval_locals, ex) from ex + +flamenco.job_types_propgroup.SettingEvalError: Evaluation error of setting 'render_output_path': name 'settings' is not defined + + + +--- + +_**Agent (model gpt-5-codex, mode Agent)**_ + +
Thought Process +**Investigating eval context for settings**
+ +--- + + +
+Tool use: **grep** • Grep for "settings.render_output_path" • 8 matches + +Output mode: content + + +| File | Content | Line | +|------|------|------| +| `.\scripts\TalkingHeads cycles_optix_gpu.js` | `if (recomposed) settings.render_output_path = recomposed;` | L175 | +| `.\scripts\TalkingHeads cycles_optix_gpu.js` | `const renderOutput = normalizePathSeparators(settings.render_output_path \|\| renderOutputPath(job));` | L183 | +| `.\scripts\TalkingHeads cycles_optix_gpu.js` | `settings.render_output_path = renderOutput;` | L187 | +| `.\scripts\TalkingHeads cycles_optix_gpu.js` | `let path = job.settings.render_output_path;` | L209 | +| `.\scripts\TalkingHeads Custom Render.js` | `if (recomposed) settings.render_output_path = recomposed;` | L157 | +| `.\scripts\TalkingHeads Custom Render.js` | `const renderOutput = normalizePathSeparators(settings.render_output_path \|\| renderOutputPath(job));` | L165 | +| `.\scripts\TalkingHeads Custom Render.js` | `settings.render_output_path = renderOutput;` | L169 | +| `.\scripts\TalkingHeads Custom Render.js` | `let path = job.settings.render_output_path;` | L276 | + + +
+
+ +--- + +
Thought Process +**Searching for eval definitions**
+ +--- + + +
+Tool use: **grep** • Grep for "render_output_path','\s*type" in "scripts" • 0 matches + +Output mode: content + + +_No matches found_ + +
+
+ +--- + +
Thought Process +**Analyzing lambda evaluation scope issues** + +I'm figuring out why the new lambda-based eval code can't see the `settings` variable, while the old string eval could. It looks like Python lambdas capture free variables differently, needing `settings` passed explicitly as a parameter, so I'll modify the lambda to accept `settings` and update the calls accordingly to fix the NameError problem.
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js +@@ +- { +- key: "render_output_path", +- type: "string", +- subtype: "file_path", +- editable: false, +- eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: "Final file path of where render output will be saved" +- }, ++ { ++ key: "render_output_path", ++ type: "string", ++ subtype: "file_path", ++ editable: false, ++ eval: "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: "Final file path of where render output will be saved" ++ }, +*** End Patch +``` + +
+
+ +--- + + +
+ Tool use: **apply_patch** • Apply patch for undefined + + +```diff +*** Begin Patch +*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js +@@ +- { +- key: 'render_output_path', +- type: 'string', +- subtype: 'file_path', +- editable: false, +- eval: +- "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", +- description: 'Final file path of where render output will be saved', +- }, ++ { ++ key: 'render_output_path', ++ type: 'string', ++ subtype: 'file_path', ++ editable: false, ++ eval: ++ "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", ++ description: 'Final file path of where render output will be saved', ++ }, +*** End Patch +``` + +
+
+ +--- + + +
+ Tool use: **read_lints** • Read lints for 2 files + +Lint paths: + +- `scripts/TalkingHeads Custom Render.js` +- `scripts/TalkingHeads cycles_optix_gpu.js` + +Lint results + + +**No lint errors found** +
+
+ +--- + +Wrapped the eval in both job types so the lambda now receives `settings` explicitly. Blender should no longer raise “name 'settings' is not defined” when resolving `render_output_path`. Let me know if any other eval errors pop up. + +--- + diff --git a/scripts/TalkingHeads Custom Render.js b/scripts/TalkingHeads Custom Render.js index 36a1193..8716eca 100644 --- a/scripts/TalkingHeads Custom Render.js +++ b/scripts/TalkingHeads Custom Render.js @@ -46,18 +46,6 @@ const JOB_TYPE = { }, description: "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically." }, - { - key: "daily_path", - type: "string", - required: false, - visible: "submission", - eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')", - evalInfo: { - showLinkButton: true, - description: "Auto-fill with today's daily folder name" - }, - description: "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date." - }, { key: "use_submodule", label: "Use Submodule", @@ -79,14 +67,14 @@ const JOB_TYPE = { }, description: "Optional submodule under Renders (e.g. 'Waterspider B'). If empty, omitted." }, - { - key: "render_output_path", - type: "string", - subtype: "file_path", - editable: false, - eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", - description: "Final file path of where render output will be saved" - }, + { + key: "render_output_path", + type: "string", + subtype: "file_path", + editable: false, + eval: "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", + description: "Final file path of where render output will be saved" + }, // Automatically evaluated settings: { @@ -165,10 +153,6 @@ function compileJob(job) { if (projectRoot) settings.render_output_root = path.join(projectRoot, 'Renders'); else settings.render_output_root = path.join(path.dirname(settings.blendfile), 'Renders'); } - if (!settings.daily_path || String(settings.daily_path).trim() === '') { - const createdDate = job && job.created ? new Date(job.created) : new Date(); - settings.daily_path = formatDailyYYMMDD(createdDate); - } const recomposed = computeAutoRenderOutputPath(job); if (recomposed) settings.render_output_path = recomposed; } catch (e) { @@ -200,7 +184,7 @@ function compileJob(job) { } } -// Derive project root, submodule, and daily path from the blendfile path. +// Derive project root and submodule from the blendfile path. function computeAutoRenderOutputPath(job) { const settings = job.settings || {}; if (!settings.blendfile) return null; @@ -220,26 +204,17 @@ function computeAutoRenderOutputPath(job) { renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); } - // Resolve daily path - let daily = null; - if (settings.daily_path && ("" + settings.daily_path).trim()) { - daily = ("" + settings.daily_path).trim(); - } else { - const createdDate = job && job.created ? new Date(job.created) : new Date(); - daily = formatDailyYYMMDD(createdDate); - } - const jobname = (job && job.name) ? job.name : path.stem(settings.blendfile).replace('.flamenco', ''); + const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); print('AutoPath: blendfile=', settings.blendfile); print('AutoPath: projectRoot=', projectRoot); print('AutoPath: renderRoot=', renderRoot); print('AutoPath: submodule=', submodule); - print('AutoPath: daily=', daily); - print('AutoPath: jobname=', jobname); + print('AutoPath: blendname=', blendname); const parts = [renderRoot]; if (submodule) parts.push(submodule); - parts.push(daily, jobname, `${jobname}_######`); + parts.push(blendname, `${blendname}_######`); const finalPath = path.join.apply(path, parts); print('AutoPath: finalPath=', finalPath); return finalPath; @@ -296,14 +271,6 @@ function detectSubmodule(settings) { return detectSubmoduleFromBlendfile(bf); } -function formatDailyYYMMDD(dateObj) { - const pad2 = (n) => (n < 10 ? '0' + n : '' + n); - const yy = (dateObj.getFullYear() % 100); - const mm = dateObj.getMonth() + 1; - const dd = dateObj.getDate(); - return `daily_${pad2(yy)}${pad2(mm)}${pad2(dd)}`; -} - // Do field replacement on the render output path. function renderOutputPath(job) { let path = job.settings.render_output_path; diff --git a/scripts/TalkingHeads cycles_optix_gpu.js b/scripts/TalkingHeads cycles_optix_gpu.js index 9c2c87f..c28f386 100644 --- a/scripts/TalkingHeads cycles_optix_gpu.js +++ b/scripts/TalkingHeads cycles_optix_gpu.js @@ -37,16 +37,6 @@ const JOB_TYPE = { description: "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.", }, - { - key: 'daily_path', - type: 'string', - required: false, - visible: 'submission', - eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')", - evalInfo: { showLinkButton: true, description: "Auto-fill with today's daily folder name" }, - description: - "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.", - }, { key: 'use_submodule', label: 'Use Submodule', @@ -71,7 +61,8 @@ const JOB_TYPE = { type: 'string', subtype: 'file_path', editable: false, - eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))", + eval: + "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))", description: 'Final file path of where render output will be saved', }, @@ -180,10 +171,6 @@ function compileJob(job) { if (projectRoot) settings.render_output_root = path.join(projectRoot, 'Renders'); else settings.render_output_root = path.join(path.dirname(settings.blendfile), 'Renders'); } - if (!settings.daily_path || String(settings.daily_path).trim() === '') { - const createdDate = job && job.created ? new Date(job.created) : new Date(); - settings.daily_path = formatDailyYYMMDD(createdDate); - } const recomposed = computeAutoRenderOutputPath(job); if (recomposed) settings.render_output_path = recomposed; } catch (e) { @@ -374,7 +361,7 @@ function cleanupJobSettings(settings) { } } -// Derive project root, submodule, and daily path from the blendfile path. +// Derive project root and submodule from the blendfile path. function computeAutoRenderOutputPath(job) { const settings = job.settings || {}; if (!settings.blendfile) return null; @@ -391,19 +378,11 @@ function computeAutoRenderOutputPath(job) { } else { renderRoot = path.join(path.dirname(settings.blendfile), 'Renders'); } - // Resolve daily path - let daily = null; - if (settings.daily_path && ("" + settings.daily_path).trim()) { - daily = ("" + settings.daily_path).trim(); - } else { - const createdDate = job && job.created ? new Date(job.created) : new Date(); - daily = formatDailyYYMMDD(createdDate); - } - const jobname = job && job.name ? job.name : path.stem(settings.blendfile).replace('.flamenco', ''); + const blendname = path.stem(settings.blendfile).replace('.flamenco', ''); const parts = [renderRoot]; if (submodule) parts.push(submodule); - parts.push(daily, jobname, `${jobname}_######`); + parts.push(blendname, `${blendname}_######`); return path.join.apply(path, parts); } @@ -454,11 +433,3 @@ function detectSubmodule(settings) { } catch (_) {} return detectSubmoduleFromBlendfile(bf); } - -function formatDailyYYMMDD(dateObj) { - const pad2 = (n) => (n < 10 ? '0' + n : '' + n); - const yy = dateObj.getFullYear() % 100; - const mm = dateObj.getMonth() + 1; - const dd = dateObj.getDate(); - return `daily_${pad2(yy)}${pad2(mm)}${pad2(dd)}`; -}