<!-- Generated by SpecStory, Markdown v2.1.0 -->

# 2026-01-28 20:03:07Z

<!-- cursoride Session a5234fcd-b9e2-4b62-9bdc-75ade561ac50 (2026-01-28 20:03:07Z) -->

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt It still sent all the files. context:@e:\flamenco\.specstory\history\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md

---

_**Agent (default)**_

Reviewing the terminal output and specstory history to understand what's happening.


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\.specstory\history\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Reading the specstory history to understand the context:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\.specstory\history\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How are files sent when submitting a job? What determines which files get included?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _save_blendfile(self, context):\n\n\n        return filepath\n\n    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n        \"\"\"Ensure that the files are somewhere in the shared storage.\n\n        Returns True if a packing thread has been started, and False otherwise.\n        \"\"\"\n\n        from .bat import interface as bat_interface\n\n        if bat_interface.is_packing():\n            self.report({\"ERROR\"}, \"Another packing operation is running\")\n            self._quit(context)\n            return False\n\n        manager = self._manager_info(context)\n        if not manager:\n            return False\n\n        if manager.shared_storage.shaman_enabled:\n            # self.blendfile_on_farm will be set when BAT created the checkout,\n            # see _on_bat_pack_msg() below.\n            self.blendfile_on_farm = None\n            self._bat_pack_shaman(context, blendfile)\n        elif job_submission.is_file_inside_job_storage(context, blendfile):\n            self.log.info(\n                \"File is already in job storage location, submitting it as-is\"\n            )\n            self._use_blendfile_directly(context, blendfile)\n        else:\n            self.log.info(\n                \"File is not already in job storage location, copying it there\"\n            )\n            try:\n                self.blendfile_on_farm = self._bat_pack_filesystem(context, blendfile)\n            except FileNotFoundError:\n                self._quit(context)\n                return False\n\n        wm = context.window_manager\n        self.timer = wm.event_timer_add(self.TIMER_PERIOD, window=context.window)\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 300,
            "text": "def _save_blendfile(self, context):"
          },
          {
            "lineNumber": 362
          },
          {
            "lineNumber": 363
          },
          {
            "lineNumber": 364,
            "text": "        return filepath"
          },
          {
            "lineNumber": 365
          },
          {
            "lineNumber": 366,
            "text": "    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 367,
            "text": "        \"\"\"Ensure that the files are somewhere in the shared storage."
          },
          {
            "lineNumber": 368
          },
          {
            "lineNumber": 369,
            "text": "        Returns True if a packing thread has been started, and False otherwise."
          },
          {
            "lineNumber": 370,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 371
          },
          {
            "lineNumber": 372,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 373
          },
          {
            "lineNumber": 374,
            "text": "        if bat_interface.is_packing():"
          },
          {
            "lineNumber": 375,
            "text": "            self.report({\"ERROR\"}, \"Another packing operation is running\")"
          },
          {
            "lineNumber": 376,
            "text": "            self._quit(context)"
          },
          {
            "lineNumber": 377,
            "text": "            return False"
          },
          {
            "lineNumber": 378
          },
          {
            "lineNumber": 379,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 380,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 381,
            "text": "            return False"
          },
          {
            "lineNumber": 382
          },
          {
            "lineNumber": 383,
            "text": "        if manager.shared_storage.shaman_enabled:"
          },
          {
            "lineNumber": 384,
            "text": "            # self.blendfile_on_farm will be set when BAT created the checkout,"
          },
          {
            "lineNumber": 385,
            "text": "            # see _on_bat_pack_msg() below."
          },
          {
            "lineNumber": 386,
            "text": "            self.blendfile_on_farm = None"
          },
          {
            "lineNumber": 387,
            "text": "            self._bat_pack_shaman(context, blendfile)"
          },
          {
            "lineNumber": 388,
            "text": "        elif job_submission.is_file_inside_job_storage(context, blendfile):"
          },
          {
            "lineNumber": 389,
            "text": "            self.log.info("
          },
          {
            "lineNumber": 390,
            "text": "                \"File is already in job storage location, submitting it as-is\""
          },
          {
            "lineNumber": 391,
            "text": "            )"
          },
          {
            "lineNumber": 392,
            "text": "            self._use_blendfile_directly(context, blendfile)"
          },
          {
            "lineNumber": 393,
            "text": "        else:"
          },
          {
            "lineNumber": 394,
            "text": "            self.log.info("
          },
          {
            "lineNumber": 395,
            "text": "                \"File is not already in job storage location, copying it there\""
          },
          {
            "lineNumber": 396,
            "text": "            )"
          },
          {
            "lineNumber": 397,
            "text": "            try:"
          },
          {
            "lineNumber": 398,
            "text": "                self.blendfile_on_farm = self._bat_pack_filesystem(context, blendfile)"
          },
          {
            "lineNumber": 399,
            "text": "            except FileNotFoundError:"
          },
          {
            "lineNumber": 400,
            "text": "                self._quit(context)"
          },
          {
            "lineNumber": 401,
            "text": "                return False"
          },
          {
            "lineNumber": 402
          },
          {
            "lineNumber": 403,
            "text": "        wm = context.window_manager"
          },
          {
            "lineNumber": 404,
            "text": "        self.timer = wm.event_timer_add(self.TIMER_PERIOD, window=context.window)"
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.3952980935573578
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=False,  # Always use absolute paths for consistent file sending.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 404
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 414,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 415,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 416
          },
          {
            "lineNumber": 417,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 418,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 419,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 420,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 421
          },
          {
            "lineNumber": 422,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 423,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 424,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 427,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 428,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 429,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 430,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 431,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 432,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 433,
            "text": "        )"
          },
          {
            "lineNumber": 434,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 435
          },
          {
            "lineNumber": 436,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 437,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 441,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 442,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 443,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 444,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 445,
            "text": "            relative_only=False,  # Always use absolute paths for consistent file sending."
          },
          {
            "lineNumber": 446,
            "text": "        )"
          },
          {
            "lineNumber": 447
          },
          {
            "lineNumber": 448,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.3914426863193512
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID.\n\n        Note that this may not be the actually used checkout ID, as that will be\n        made unique to this job by Flamenco Manager. That will be stored in\n        self.actual_shaman_checkout_path after the Shaman checkout is actually\n        done.\n        \"\"\"\n        assert self.job is not None\n\n        # TODO: get project name from preferences/GUI and insert that here too.\n        return PurePosixPath(f\"{self.job.name}\")\n\n    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:\n        \"\"\"Use the Manager's Shaman API to submit the BAT pack.\n\n        :return: the filesystem path of the blend file, for in the render job definition.\n        \"\"\"\n        from .bat import (\n            interface as bat_interface,\n            shaman as bat_shaman,\n        )\n\n        assert self.job is not None\n        self.log.info(\"Sending BAT pack to Shaman\")\n\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=\"/\",  # Target directory irrelevant for Shaman transfers.\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=False,  # Always use absolute paths for consistent file sending.\n            packer_class=bat_shaman.Packer,\n            packer_kwargs=dict(\n                api_client=self.get_api_client(context),\n                checkout_path=self._shaman_checkout_path(),\n            ),\n        )\n\n        # We cannot assume the blendfile location is known until the Shaman\n        # checkout has actually been created.\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 448
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID."
          },
          {
            "lineNumber": 452
          },
          {
            "lineNumber": 453,
            "text": "        Note that this may not be the actually used checkout ID, as that will be"
          },
          {
            "lineNumber": 454,
            "text": "        made unique to this job by Flamenco Manager. That will be stored in"
          },
          {
            "lineNumber": 455,
            "text": "        self.actual_shaman_checkout_path after the Shaman checkout is actually"
          },
          {
            "lineNumber": 456,
            "text": "        done."
          },
          {
            "lineNumber": 457,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 458,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 459
          },
          {
            "lineNumber": 460,
            "text": "        # TODO: get project name from preferences/GUI and insert that here too."
          },
          {
            "lineNumber": 461,
            "text": "        return PurePosixPath(f\"{self.job.name}\")"
          },
          {
            "lineNumber": 462
          },
          {
            "lineNumber": 463,
            "text": "    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:"
          },
          {
            "lineNumber": 464,
            "text": "        \"\"\"Use the Manager's Shaman API to submit the BAT pack."
          },
          {
            "lineNumber": 465
          },
          {
            "lineNumber": 466,
            "text": "        :return: the filesystem path of the blend file, for in the render job definition."
          },
          {
            "lineNumber": 467,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 468,
            "text": "        from .bat import ("
          },
          {
            "lineNumber": 469,
            "text": "            interface as bat_interface,"
          },
          {
            "lineNumber": 470,
            "text": "            shaman as bat_shaman,"
          },
          {
            "lineNumber": 471,
            "text": "        )"
          },
          {
            "lineNumber": 472
          },
          {
            "lineNumber": 473,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 474,
            "text": "        self.log.info(\"Sending BAT pack to Shaman\")"
          },
          {
            "lineNumber": 475
          },
          {
            "lineNumber": 476,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 477,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 478
          },
          {
            "lineNumber": 479,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 480,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 481,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 482,
            "text": "            target=\"/\",  # Target directory irrelevant for Shaman transfers."
          },
          {
            "lineNumber": 483,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 484,
            "text": "            relative_only=False,  # Always use absolute paths for consistent file sending."
          },
          {
            "lineNumber": 485,
            "text": "            packer_class=bat_shaman.Packer,"
          },
          {
            "lineNumber": 486,
            "text": "            packer_kwargs=dict("
          },
          {
            "lineNumber": 487,
            "text": "                api_client=self.get_api_client(context),"
          },
          {
            "lineNumber": 488,
            "text": "                checkout_path=self._shaman_checkout_path(),"
          },
          {
            "lineNumber": 489,
            "text": "            ),"
          },
          {
            "lineNumber": 490,
            "text": "        )"
          },
          {
            "lineNumber": 491
          },
          {
            "lineNumber": 492,
            "text": "        # We cannot assume the blendfile location is known until the Shaman"
          },
          {
            "lineNumber": 493,
            "text": "        # checkout has actually been created."
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.3734486997127533
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        \"\"\"Prepare self.job for sending to Flamenco.\"\"\"\n\n        self.job = job_submission.job_for_scene(context.scene)\n        if self.job is None:\n            self.report({\"ERROR\"}, \"Unable to create job\")\n            return False\n\n        propgroup = getattr(context.scene, \"flamenco_job_settings\", None)\n        assert isinstance(propgroup, JobTypePropertyGroup), \"did not expect %s\" % (\n            type(propgroup)\n        )\n        propgroup.eval_hidden_settings_of_job(context, self.job)\n\n        job_submission.set_blend_file(\n            propgroup.job_type,\n            self.job,\n            # self.blendfile_on_farm is None when we're just checking the job.\n            self.blendfile_on_farm or \"dummy-for-job-check.blend\",\n        )\n\n        if self.actual_shaman_checkout_path:\n            job_submission.set_shaman_checkout_id(\n                self.job, self.actual_shaman_checkout_path\n            )\n\n        return True\n\n    def _submit_job(self, context: bpy.types.Context) -\u003e None:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 526,
            "text": "def _use_blendfile_directly("
          },
          {
            "lineNumber": 527,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 528,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 529,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 530,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 538
          },
          {
            "lineNumber": 539
          },
          {
            "lineNumber": 540,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 541
          },
          {
            "lineNumber": 542,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 543,
            "text": "        \"\"\"Prepare self.job for sending to Flamenco.\"\"\""
          },
          {
            "lineNumber": 544
          },
          {
            "lineNumber": 545,
            "text": "        self.job = job_submission.job_for_scene(context.scene)"
          },
          {
            "lineNumber": 546,
            "text": "        if self.job is None:"
          },
          {
            "lineNumber": 547,
            "text": "            self.report({\"ERROR\"}, \"Unable to create job\")"
          },
          {
            "lineNumber": 548,
            "text": "            return False"
          },
          {
            "lineNumber": 549
          },
          {
            "lineNumber": 550,
            "text": "        propgroup = getattr(context.scene, \"flamenco_job_settings\", None)"
          },
          {
            "lineNumber": 551,
            "text": "        assert isinstance(propgroup, JobTypePropertyGroup), \"did not expect %s\" % ("
          },
          {
            "lineNumber": 552,
            "text": "            type(propgroup)"
          },
          {
            "lineNumber": 553,
            "text": "        )"
          },
          {
            "lineNumber": 554,
            "text": "        propgroup.eval_hidden_settings_of_job(context, self.job)"
          },
          {
            "lineNumber": 555
          },
          {
            "lineNumber": 556,
            "text": "        job_submission.set_blend_file("
          },
          {
            "lineNumber": 557,
            "text": "            propgroup.job_type,"
          },
          {
            "lineNumber": 558,
            "text": "            self.job,"
          },
          {
            "lineNumber": 559,
            "text": "            # self.blendfile_on_farm is None when we're just checking the job."
          },
          {
            "lineNumber": 560,
            "text": "            self.blendfile_on_farm or \"dummy-for-job-check.blend\","
          },
          {
            "lineNumber": 561,
            "text": "        )"
          },
          {
            "lineNumber": 562
          },
          {
            "lineNumber": 563,
            "text": "        if self.actual_shaman_checkout_path:"
          },
          {
            "lineNumber": 564,
            "text": "            job_submission.set_shaman_checkout_id("
          },
          {
            "lineNumber": 565,
            "text": "                self.job, self.actual_shaman_checkout_path"
          },
          {
            "lineNumber": 566,
            "text": "            )"
          },
          {
            "lineNumber": 567
          },
          {
            "lineNumber": 568,
            "text": "        return True"
          },
          {
            "lineNumber": 569
          },
          {
            "lineNumber": 570,
            "text": "    def _submit_job(self, context: bpy.types.Context) -\u003e None:"
          },
          {
            "lineNumber": 571,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.3146071434020996
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n    bl_idname = \"flamenco.submit_job\"\n    bl_label = \"Flamenco: Submit Job\"\n    bl_description = \"Pack the current blend file and send it to Flamenco\"\n    bl_options = {\"REGISTER\"}  # No UNDO.\n\n    blendfile_on_farm: Optional[PurePosixPath] = None\n    actual_shaman_checkout_path: Optional[PurePosixPath] = None\n\n    job_name: bpy.props.StringProperty(name=\"Job Name\")  # type: ignore\n    job: Optional[_SubmittedJob] = None\n    temp_blendfile: Optional[Path] = None\n    ignore_version_mismatch: bpy.props.BoolProperty(  # type: ignore\n        name=\"Ignore Version Mismatch\",\n        default=False,\n    )\n\n    TIMER_PERIOD = 0.25  # seconds\n    timer: Optional[bpy.types.Timer] = None\n    packthread: Optional[_PackThread] = None\n\n    log = _log.getChild(bl_idname)\n\n    @classmethod\n    def poll(cls, context: bpy.types.Context) -\u003e bool:\n        # Only allow submission when there is a job type selected.\n        job_type = job_types.active_job_type(context.scene)\n        cls.poll_message_set(\"No job type selected\")\n        return job_type is not None\n\n    def execute(self, context: bpy.types.Context) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 103,
            "text": "    bl_idname = \"flamenco.submit_job\""
          },
          {
            "lineNumber": 104,
            "text": "    bl_label = \"Flamenco: Submit Job\""
          },
          {
            "lineNumber": 105,
            "text": "    bl_description = \"Pack the current blend file and send it to Flamenco\""
          },
          {
            "lineNumber": 106,
            "text": "    bl_options = {\"REGISTER\"}  # No UNDO."
          },
          {
            "lineNumber": 107
          },
          {
            "lineNumber": 108,
            "text": "    blendfile_on_farm: Optional[PurePosixPath] = None"
          },
          {
            "lineNumber": 109,
            "text": "    actual_shaman_checkout_path: Optional[PurePosixPath] = None"
          },
          {
            "lineNumber": 110
          },
          {
            "lineNumber": 111,
            "text": "    job_name: bpy.props.StringProperty(name=\"Job Name\")  # type: ignore"
          },
          {
            "lineNumber": 112,
            "text": "    job: Optional[_SubmittedJob] = None"
          },
          {
            "lineNumber": 113,
            "text": "    temp_blendfile: Optional[Path] = None"
          },
          {
            "lineNumber": 114,
            "text": "    ignore_version_mismatch: bpy.props.BoolProperty(  # type: ignore"
          },
          {
            "lineNumber": 115,
            "text": "        name=\"Ignore Version Mismatch\","
          },
          {
            "lineNumber": 116,
            "text": "        default=False,"
          },
          {
            "lineNumber": 117,
            "text": "    )"
          },
          {
            "lineNumber": 118
          },
          {
            "lineNumber": 119,
            "text": "    TIMER_PERIOD = 0.25  # seconds"
          },
          {
            "lineNumber": 120,
            "text": "    timer: Optional[bpy.types.Timer] = None"
          },
          {
            "lineNumber": 121,
            "text": "    packthread: Optional[_PackThread] = None"
          },
          {
            "lineNumber": 122
          },
          {
            "lineNumber": 123,
            "text": "    log = _log.getChild(bl_idname)"
          },
          {
            "lineNumber": 124
          },
          {
            "lineNumber": 125,
            "text": "    @classmethod"
          },
          {
            "lineNumber": 126,
            "text": "    def poll(cls, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 127,
            "text": "        # Only allow submission when there is a job type selected."
          },
          {
            "lineNumber": 128,
            "text": "        job_type = job_types.active_job_type(context.scene)"
          },
          {
            "lineNumber": 129,
            "text": "        cls.poll_message_set(\"No job type selected\")"
          },
          {
            "lineNumber": 130,
            "text": "        return job_type is not None"
          },
          {
            "lineNumber": 131
          },
          {
            "lineNumber": 132,
            "text": "    def execute(self, context: bpy.types.Context) -\u003e set[str]:"
          },
          {
            "lineNumber": 133,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "column": 27,
            "line": 98
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {}
      },
      "score": 0.30238020420074463
    },
    {
      "codeBlock": {
        "contents": "_running_packthread: typing.Optional[PackThread] = None\n_packer_lock = threading.RLock()\n\n\ndef copy(  # type: ignore\n    base_blendfile: Path,\n    project: Path,\n    target: str,\n    exclusion_filter: str,\n    *,\n    relative_only: bool,\n    packer_class=submodules.pack.Packer,\n    packer_kwargs: Optional[dict[Any, Any]] = None,\n) -\u003e PackThread:\n    \"\"\"Use BAT to copy the given file and dependencies to the target location.\n\n    Runs BAT in a separate thread, and returns early. Use poll() to get updates\n    \u0026 the final result.\n    \"\"\"\n    global _running_packthread\n\n    with _packer_lock:\n        if _running_packthread is not None:\n            raise RuntimeError(\"other packing operation already in progress\")\n\n    # Due to issues with library overrides and unsynced pointers, it's quite\n    # common for the Blender Animation Studio to get crashes of BAT. To avoid\n    # these, Strict Pointer Mode is disabled.\n    submodules.blendfile.set_strict_pointer_mode(False)\n\n    log.info(\"BAT pack parameters:\")\n    log.info(\"base_blendfile = %r\", base_blendfile)\n    log.info(\"project        = %r\", project)\n    log.info(\"target         = %r\", target)\n\n    if packer_kwargs is None:\n        packer_kwargs = {}\n    packer = packer_class(\n        base_blendfile,\n        project,\n        target,\n        compress=True,\n        relative_only=relative_only,\n        **packer_kwargs,\n    )\n    if exclusion_filter:\n        filter_parts = exclusion_filter.strip().split(\" \")\n        packer.exclude(*filter_parts)\n\n    packthread = PackThread(packer=packer)\n    with _packer_lock:\n        _running_packthread = packthread\n\n    packthread.start()\n    return packthread\n\n\ndef abort() -\u003e None:\n    \"\"\"Abort a running copy() call.\n\n    No-op when there is no running copy(). Can be called from any thread.\n    ",
        "detailedLines": [
          {
            "lineNumber": 197,
            "text": "_running_packthread: typing.Optional[PackThread] = None"
          },
          {
            "lineNumber": 198,
            "text": "_packer_lock = threading.RLock()"
          },
          {
            "lineNumber": 199
          },
          {
            "lineNumber": 200
          },
          {
            "isSignature": true,
            "lineNumber": 201,
            "text": "def copy(  # type: ignore"
          },
          {
            "isSignature": true,
            "lineNumber": 202,
            "text": "    base_blendfile: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 203,
            "text": "    project: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 204,
            "text": "    target: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 205,
            "text": "    exclusion_filter: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 206,
            "text": "    *,"
          },
          {
            "isSignature": true,
            "lineNumber": 207,
            "text": "    relative_only: bool,"
          },
          {
            "isSignature": true,
            "lineNumber": 208,
            "text": "    packer_class=submodules.pack.Packer,"
          },
          {
            "isSignature": true,
            "lineNumber": 209,
            "text": "    packer_kwargs: Optional[dict[Any, Any]] = None,"
          },
          {
            "isSignature": true,
            "lineNumber": 210,
            "text": ") -\u003e PackThread:"
          },
          {
            "lineNumber": 211,
            "text": "    \"\"\"Use BAT to copy the given file and dependencies to the target location."
          },
          {
            "lineNumber": 212
          },
          {
            "lineNumber": 213,
            "text": "    Runs BAT in a separate thread, and returns early. Use poll() to get updates"
          },
          {
            "lineNumber": 214,
            "text": "    \u0026 the final result."
          },
          {
            "lineNumber": 215,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 216,
            "text": "    global _running_packthread"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 219,
            "text": "        if _running_packthread is not None:"
          },
          {
            "lineNumber": 220,
            "text": "            raise RuntimeError(\"other packing operation already in progress\")"
          },
          {
            "lineNumber": 221
          },
          {
            "lineNumber": 222,
            "text": "    # Due to issues with library overrides and unsynced pointers, it's quite"
          },
          {
            "lineNumber": 223,
            "text": "    # common for the Blender Animation Studio to get crashes of BAT. To avoid"
          },
          {
            "lineNumber": 224,
            "text": "    # these, Strict Pointer Mode is disabled."
          },
          {
            "lineNumber": 225,
            "text": "    submodules.blendfile.set_strict_pointer_mode(False)"
          },
          {
            "lineNumber": 226
          },
          {
            "lineNumber": 227,
            "text": "    log.info(\"BAT pack parameters:\")"
          },
          {
            "lineNumber": 228,
            "text": "    log.info(\"base_blendfile = %r\", base_blendfile)"
          },
          {
            "lineNumber": 229,
            "text": "    log.info(\"project        = %r\", project)"
          },
          {
            "lineNumber": 230,
            "text": "    log.info(\"target         = %r\", target)"
          },
          {
            "lineNumber": 231
          },
          {
            "lineNumber": 232,
            "text": "    if packer_kwargs is None:"
          },
          {
            "lineNumber": 233,
            "text": "        packer_kwargs = {}"
          },
          {
            "lineNumber": 234,
            "text": "    packer = packer_class("
          },
          {
            "lineNumber": 235,
            "text": "        base_blendfile,"
          },
          {
            "lineNumber": 236,
            "text": "        project,"
          },
          {
            "lineNumber": 237,
            "text": "        target,"
          },
          {
            "lineNumber": 238,
            "text": "        compress=True,"
          },
          {
            "lineNumber": 239,
            "text": "        relative_only=relative_only,"
          },
          {
            "lineNumber": 240,
            "text": "        **packer_kwargs,"
          },
          {
            "lineNumber": 241,
            "text": "    )"
          },
          {
            "lineNumber": 242,
            "text": "    if exclusion_filter:"
          },
          {
            "lineNumber": 243,
            "text": "        filter_parts = exclusion_filter.strip().split(\" \")"
          },
          {
            "lineNumber": 244,
            "text": "        packer.exclude(*filter_parts)"
          },
          {
            "lineNumber": 245
          },
          {
            "lineNumber": 246,
            "text": "    packthread = PackThread(packer=packer)"
          },
          {
            "lineNumber": 247,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 248,
            "text": "        _running_packthread = packthread"
          },
          {
            "lineNumber": 249
          },
          {
            "lineNumber": 250,
            "text": "    packthread.start()"
          },
          {
            "isSignature": true,
            "lineNumber": 251,
            "text": "    return packthread"
          },
          {
            "lineNumber": 252
          },
          {
            "lineNumber": 253
          },
          {
            "isSignature": true,
            "lineNumber": 254,
            "text": "def abort() -\u003e None:"
          },
          {
            "lineNumber": 255,
            "text": "    \"\"\"Abort a running copy() call."
          },
          {
            "lineNumber": 256
          },
          {
            "lineNumber": 257,
            "text": "    No-op when there is no running copy(). Can be called from any thread."
          },
          {
            "lineNumber": 258,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 264
          },
          "startPosition": {
            "column": 27,
            "line": 193
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.2906742990016937
    },
    {
      "codeBlock": {
        "contents": "class ApiClient(object):\n\n\n    def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None):\n        \"\"\"Builds form parameters.\n\n        :param files: None or a dict with key=param_name and\n            value is a list of open file objects\n        :return: List of tuples of form parameters with file data\n        \"\"\"\n        if files is None:\n            return []\n\n        params = []\n        for param_name, file_instances in files.items():\n            if file_instances is None:\n                # if the file field is nullable, skip None values\n                continue\n            for file_instance in file_instances:\n                if file_instance is None:\n                    # if the file field is nullable, skip None values\n                    continue\n                if file_instance.closed is True:\n                    raise ApiValueError(\n                        \"Cannot read a closed file. The passed in file_type \"\n                        \"for %s must be open.\" % param_name\n                    )\n                filename = os.path.basename(file_instance.name)\n                filedata = self.get_file_data_and_close_file(file_instance)\n                mimetype = (mimetypes.guess_type(filename)[0] or\n                            'application/octet-stream')\n                params.append(\n                    tuple([param_name, tuple([filename, filedata, mimetype])]))\n\n        return params\n\n    def select_header_accept(self, accepts):\n        \"\"\"Returns &#96;Accept&#96; based on an array of accepts provided.\n\n        :param accepts: List of headers.\n        :return: Accept (e.g. application/json).\n        \"\"\"\n        if not accepts:\n            return\n\n        accepts = [x.lower() for x in accepts]\n\n        if 'application/json' in accepts:\n            return 'application/json'\n        else:\n            return ', '.join(accepts)\n\n    def select_header_content_type(self, content_types, method=None, body=None):\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 42,
            "text": "class ApiClient(object):"
          },
          {
            "lineNumber": 525
          },
          {
            "lineNumber": 526
          },
          {
            "lineNumber": 527,
            "text": "    def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None):"
          },
          {
            "lineNumber": 528,
            "text": "        \"\"\"Builds form parameters."
          },
          {
            "lineNumber": 529
          },
          {
            "lineNumber": 530,
            "text": "        :param files: None or a dict with key=param_name and"
          },
          {
            "lineNumber": 531,
            "text": "            value is a list of open file objects"
          },
          {
            "lineNumber": 532,
            "text": "        :return: List of tuples of form parameters with file data"
          },
          {
            "lineNumber": 533,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 534,
            "text": "        if files is None:"
          },
          {
            "lineNumber": 535,
            "text": "            return []"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        params = []"
          },
          {
            "lineNumber": 538,
            "text": "        for param_name, file_instances in files.items():"
          },
          {
            "lineNumber": 539,
            "text": "            if file_instances is None:"
          },
          {
            "lineNumber": 540,
            "text": "                # if the file field is nullable, skip None values"
          },
          {
            "lineNumber": 541,
            "text": "                continue"
          },
          {
            "lineNumber": 542,
            "text": "            for file_instance in file_instances:"
          },
          {
            "lineNumber": 543,
            "text": "                if file_instance is None:"
          },
          {
            "lineNumber": 544,
            "text": "                    # if the file field is nullable, skip None values"
          },
          {
            "lineNumber": 545,
            "text": "                    continue"
          },
          {
            "lineNumber": 546,
            "text": "                if file_instance.closed is True:"
          },
          {
            "lineNumber": 547,
            "text": "                    raise ApiValueError("
          },
          {
            "lineNumber": 548,
            "text": "                        \"Cannot read a closed file. The passed in file_type \""
          },
          {
            "lineNumber": 549,
            "text": "                        \"for %s must be open.\" % param_name"
          },
          {
            "lineNumber": 550,
            "text": "                    )"
          },
          {
            "lineNumber": 551,
            "text": "                filename = os.path.basename(file_instance.name)"
          },
          {
            "lineNumber": 552,
            "text": "                filedata = self.get_file_data_and_close_file(file_instance)"
          },
          {
            "lineNumber": 553,
            "text": "                mimetype = (mimetypes.guess_type(filename)[0] or"
          },
          {
            "lineNumber": 554,
            "text": "                            'application/octet-stream')"
          },
          {
            "lineNumber": 555,
            "text": "                params.append("
          },
          {
            "lineNumber": 556,
            "text": "                    tuple([param_name, tuple([filename, filedata, mimetype])]))"
          },
          {
            "lineNumber": 557
          },
          {
            "lineNumber": 558,
            "text": "        return params"
          },
          {
            "lineNumber": 559
          },
          {
            "lineNumber": 560,
            "text": "    def select_header_accept(self, accepts):"
          },
          {
            "lineNumber": 561,
            "text": "        \"\"\"Returns &#96;Accept&#96; based on an array of accepts provided."
          },
          {
            "lineNumber": 562
          },
          {
            "lineNumber": 563,
            "text": "        :param accepts: List of headers."
          },
          {
            "lineNumber": 564,
            "text": "        :return: Accept (e.g. application/json)."
          },
          {
            "lineNumber": 565,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 566,
            "text": "        if not accepts:"
          },
          {
            "lineNumber": 567,
            "text": "            return"
          },
          {
            "lineNumber": 568
          },
          {
            "lineNumber": 569,
            "text": "        accepts = [x.lower() for x in accepts]"
          },
          {
            "lineNumber": 570
          },
          {
            "lineNumber": 571,
            "text": "        if 'application/json' in accepts:"
          },
          {
            "lineNumber": 572,
            "text": "            return 'application/json'"
          },
          {
            "lineNumber": 573,
            "text": "        else:"
          },
          {
            "lineNumber": 574,
            "text": "            return ', '.join(accepts)"
          },
          {
            "lineNumber": 575
          },
          {
            "lineNumber": 576,
            "text": "    def select_header_content_type(self, content_types, method=None, body=None):"
          },
          {
            "lineNumber": 577,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 626
          },
          "startPosition": {
            "line": 41
          }
        },
        "relativeWorkspacePath": ".\\manager\\api_client.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 43
              },
              "startPosition": {
                "column": 1,
                "line": 42
              }
            }
          ]
        }
      },
      "score": 0.2900973856449127
    },
    {
      "codeBlock": {
        "contents": "def set_blend_file(\n    job_type: _AvailableJobType,\n    job: _SubmittedJob,\n    blendfile: Union[str, Path, PurePosixPath],\n) -\u003e None:\n    \"\"\"Update the job's 'blendfile' setting, if available.\n\n    If a job has a 'blendfile' setting, type 'str', it'll be set to the path/URL\n    of the BAT-packed blend file.\n    \"\"\"\n    from .manager.models import AvailableJobSetting, AvailableJobSettingType\n\n    expected_type = AvailableJobSettingType(\"string\")\n    for setting in job_type.settings:\n        if setting.key == BLENDFILE_SETTING_KEY and setting.type == expected_type:\n            break\n    else:\n        # Not having this setting is fine.\n        return\n\n    assert isinstance(setting, AvailableJobSetting)\n    job.settings[BLENDFILE_SETTING_KEY] = str(blendfile)\n\n\ndef set_shaman_checkout_id(job: _SubmittedJob, checkout_id: PurePosixPath) -\u003e None:\n    ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 72,
            "text": "def set_blend_file("
          },
          {
            "isSignature": true,
            "lineNumber": 73,
            "text": "    job_type: _AvailableJobType,"
          },
          {
            "isSignature": true,
            "lineNumber": 74,
            "text": "    job: _SubmittedJob,"
          },
          {
            "isSignature": true,
            "lineNumber": 75,
            "text": "    blendfile: Union[str, Path, PurePosixPath],"
          },
          {
            "isSignature": true,
            "lineNumber": 76,
            "text": ") -\u003e None:"
          },
          {
            "lineNumber": 77,
            "text": "    \"\"\"Update the job's 'blendfile' setting, if available."
          },
          {
            "lineNumber": 78
          },
          {
            "lineNumber": 79,
            "text": "    If a job has a 'blendfile' setting, type 'str', it'll be set to the path/URL"
          },
          {
            "lineNumber": 80,
            "text": "    of the BAT-packed blend file."
          },
          {
            "lineNumber": 81,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 82,
            "text": "    from .manager.models import AvailableJobSetting, AvailableJobSettingType"
          },
          {
            "lineNumber": 83
          },
          {
            "lineNumber": 84,
            "text": "    expected_type = AvailableJobSettingType(\"string\")"
          },
          {
            "lineNumber": 85,
            "text": "    for setting in job_type.settings:"
          },
          {
            "lineNumber": 86,
            "text": "        if setting.key == BLENDFILE_SETTING_KEY and setting.type == expected_type:"
          },
          {
            "lineNumber": 87,
            "text": "            break"
          },
          {
            "lineNumber": 88,
            "text": "    else:"
          },
          {
            "lineNumber": 89,
            "text": "        # Not having this setting is fine."
          },
          {
            "lineNumber": 90,
            "text": "        return"
          },
          {
            "lineNumber": 91
          },
          {
            "lineNumber": 92,
            "text": "    assert isinstance(setting, AvailableJobSetting)"
          },
          {
            "isSignature": true,
            "lineNumber": 93,
            "text": "    job.settings[BLENDFILE_SETTING_KEY] = str(blendfile)"
          },
          {
            "lineNumber": 94
          },
          {
            "lineNumber": 95
          },
          {
            "isSignature": true,
            "lineNumber": 96,
            "text": "def set_shaman_checkout_id(job: _SubmittedJob, checkout_id: PurePosixPath) -\u003e None:"
          },
          {
            "lineNumber": 97,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 101
          },
          "startPosition": {
            "column": 14,
            "line": 68
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.28946229815483093
    },
    {
      "codeBlock": {
        "contents": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore\n\n\n    def _upload_missing_files(\n        self, shaman_file_specs: _ShamanRequirementsRequest\n    ) -\u003e list[_ShamanFileSpec]:\n        self.log.info(\"Feeding %d files to the Shaman\", len(shaman_file_specs.files))\n        if self.log.isEnabledFor(logging.INFO):\n            for spec in shaman_file_specs.files:\n                self.log.info(\"   - %s\", spec.path)\n\n        # Try to upload all the files.\n        failed_files: set[HashableShamanFileSpec] = set()\n        max_tries = 50\n        for try_index in range(max_tries):\n            # Send the file to the Shaman and see what we still need to send there.\n            to_upload = self._send_checkout_def_to_shaman(shaman_file_specs)\n            if to_upload is None:\n                # An error has already been logged.\n                return make_file_specs_regular_list(failed_files)\n\n            if not to_upload:\n                break\n\n            # Send the files that still need to be sent.\n            self.log.info(\"Upload attempt %d\", try_index + 1)\n            failed_files = self._upload_files(to_upload)\n            if not failed_files:\n                break\n\n            # Having failed paths at this point is expected when multiple\n            # clients are sending the same files. Instead of retrying on a\n            # file-by-file basis, we just re-send the checkout definition\n            # file to the Shaman and obtain a new list of files to upload.\n        return make_file_specs_regular_list(failed_files)\n\n    def _create_checkout_definition(self) -\u003e Optional[_ShamanRequirementsRequest]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 99,
            "text": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore"
          },
          {
            "lineNumber": 167
          },
          {
            "lineNumber": 168
          },
          {
            "lineNumber": 169,
            "text": "    def _upload_missing_files("
          },
          {
            "lineNumber": 170,
            "text": "        self, shaman_file_specs: _ShamanRequirementsRequest"
          },
          {
            "lineNumber": 171,
            "text": "    ) -\u003e list[_ShamanFileSpec]:"
          },
          {
            "lineNumber": 172,
            "text": "        self.log.info(\"Feeding %d files to the Shaman\", len(shaman_file_specs.files))"
          },
          {
            "lineNumber": 173,
            "text": "        if self.log.isEnabledFor(logging.INFO):"
          },
          {
            "lineNumber": 174,
            "text": "            for spec in shaman_file_specs.files:"
          },
          {
            "lineNumber": 175,
            "text": "                self.log.info(\"   - %s\", spec.path)"
          },
          {
            "lineNumber": 176
          },
          {
            "lineNumber": 177,
            "text": "        # Try to upload all the files."
          },
          {
            "lineNumber": 178,
            "text": "        failed_files: set[HashableShamanFileSpec] = set()"
          },
          {
            "lineNumber": 179,
            "text": "        max_tries = 50"
          },
          {
            "lineNumber": 180,
            "text": "        for try_index in range(max_tries):"
          },
          {
            "lineNumber": 181,
            "text": "            # Send the file to the Shaman and see what we still need to send there."
          },
          {
            "lineNumber": 182,
            "text": "            to_upload = self._send_checkout_def_to_shaman(shaman_file_specs)"
          },
          {
            "lineNumber": 183,
            "text": "            if to_upload is None:"
          },
          {
            "lineNumber": 184,
            "text": "                # An error has already been logged."
          },
          {
            "lineNumber": 185,
            "text": "                return make_file_specs_regular_list(failed_files)"
          },
          {
            "lineNumber": 186
          },
          {
            "lineNumber": 187,
            "text": "            if not to_upload:"
          },
          {
            "lineNumber": 188,
            "text": "                break"
          },
          {
            "lineNumber": 189
          },
          {
            "lineNumber": 190,
            "text": "            # Send the files that still need to be sent."
          },
          {
            "lineNumber": 191,
            "text": "            self.log.info(\"Upload attempt %d\", try_index + 1)"
          },
          {
            "lineNumber": 192,
            "text": "            failed_files = self._upload_files(to_upload)"
          },
          {
            "lineNumber": 193,
            "text": "            if not failed_files:"
          },
          {
            "lineNumber": 194,
            "text": "                break"
          },
          {
            "lineNumber": 195
          },
          {
            "lineNumber": 196,
            "text": "            # Having failed paths at this point is expected when multiple"
          },
          {
            "lineNumber": 197,
            "text": "            # clients are sending the same files. Instead of retrying on a"
          },
          {
            "lineNumber": 198,
            "text": "            # file-by-file basis, we just re-send the checkout definition"
          },
          {
            "lineNumber": 199,
            "text": "            # file to the Shaman and obtain a new list of files to upload."
          },
          {
            "lineNumber": 200,
            "text": "        return make_file_specs_regular_list(failed_files)"
          },
          {
            "lineNumber": 201
          },
          {
            "lineNumber": 202,
            "text": "    def _create_checkout_definition(self) -\u003e Optional[_ShamanRequirementsRequest]:"
          },
          {
            "lineNumber": 203,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 473
          },
          "startPosition": {
            "line": 98
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 100
              },
              "startPosition": {
                "column": 1,
                "line": 99
              }
            }
          ]
        }
      },
      "score": 0.28850218653678894
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        from .bat import interface as bat_interface\n\n        if isinstance(msg, bat_interface.MsgDone):\n            if self.blendfile_on_farm is None:\n                # Adjust the blendfile to match the Shaman checkout path. Shaman\n                # may have checked out at a different location than we\n                # requested.\n                #\n                # Manager automatically creates a variable \"jobs\" that will\n                # resolve to the job storage directory.\n                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path\n\n            self.actual_shaman_checkout_path = msg.actual_checkout_path\n            self._submit_job(context)\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgException):\n            self.log.error(\"Error performing BAT pack: %s\", msg.ex)\n            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)\n\n            # This was an exception caught at the top level of the thread, so\n            # the packing thread itself has stopped.\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgSetWMAttribute):\n            wm = context.window_manager\n            setattr(wm, msg.attribute_name, msg.value)\n\n        return {\"RUNNING_MODAL\"}\n\n    def _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n        self.temp_blendfile = None\n\n        # The blend file is contained in the job storage path, no need to\n        # copy anything.\n        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)\n\n        # No Shaman is involved when using the file directly.\n        self.actual_shaman_checkout_path = None\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 493
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 497
          },
          {
            "lineNumber": 498,
            "text": "        if isinstance(msg, bat_interface.MsgDone):"
          },
          {
            "lineNumber": 499,
            "text": "            if self.blendfile_on_farm is None:"
          },
          {
            "lineNumber": 500,
            "text": "                # Adjust the blendfile to match the Shaman checkout path. Shaman"
          },
          {
            "lineNumber": 501,
            "text": "                # may have checked out at a different location than we"
          },
          {
            "lineNumber": 502,
            "text": "                # requested."
          },
          {
            "lineNumber": 503,
            "text": "                #"
          },
          {
            "lineNumber": 504,
            "text": "                # Manager automatically creates a variable \"jobs\" that will"
          },
          {
            "lineNumber": 505,
            "text": "                # resolve to the job storage directory."
          },
          {
            "lineNumber": 506,
            "text": "                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path"
          },
          {
            "lineNumber": 507
          },
          {
            "lineNumber": 508,
            "text": "            self.actual_shaman_checkout_path = msg.actual_checkout_path"
          },
          {
            "lineNumber": 509,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 510,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 511
          },
          {
            "lineNumber": 512,
            "text": "        if isinstance(msg, bat_interface.MsgException):"
          },
          {
            "lineNumber": 513,
            "text": "            self.log.error(\"Error performing BAT pack: %s\", msg.ex)"
          },
          {
            "lineNumber": 514,
            "text": "            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)"
          },
          {
            "lineNumber": 515
          },
          {
            "lineNumber": 516,
            "text": "            # This was an exception caught at the top level of the thread, so"
          },
          {
            "lineNumber": 517,
            "text": "            # the packing thread itself has stopped."
          },
          {
            "lineNumber": 518,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 519
          },
          {
            "lineNumber": 520,
            "text": "        if isinstance(msg, bat_interface.MsgSetWMAttribute):"
          },
          {
            "lineNumber": 521,
            "text": "            wm = context.window_manager"
          },
          {
            "lineNumber": 522,
            "text": "            setattr(wm, msg.attribute_name, msg.value)"
          },
          {
            "lineNumber": 523
          },
          {
            "lineNumber": 524,
            "text": "        return {\"RUNNING_MODAL\"}"
          },
          {
            "lineNumber": 525
          },
          {
            "lineNumber": 526,
            "text": "    def _use_blendfile_directly("
          },
          {
            "lineNumber": 527,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 528,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 529,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 530,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 531,
            "text": "        self.temp_blendfile = None"
          },
          {
            "lineNumber": 532
          },
          {
            "lineNumber": 533,
            "text": "        # The blend file is contained in the job storage path, no need to"
          },
          {
            "lineNumber": 534,
            "text": "        # copy anything."
          },
          {
            "lineNumber": 535,
            "text": "        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        # No Shaman is involved when using the file directly."
          },
          {
            "lineNumber": 538,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 539
          },
          {
            "lineNumber": 540,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 541
          },
          {
            "lineNumber": 542,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 543,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.28053605556488037
    },
    {
      "codeBlock": {
        "contents": "log = logging.getLogger(__name__)\n\n\ndef job_for_scene(scene: bpy.types.Scene) -\u003e Optional[_SubmittedJob]:\n    from flamenco.manager.models import SubmittedJob, JobMetadata\n    from flamenco.manager.model.job_status import JobStatus\n\n    propgroup = getattr(scene, \"flamenco_job_settings\", None)\n    assert isinstance(propgroup, JobTypePropertyGroup), \"did not expect %s\" % (\n        type(propgroup)\n    )\n\n    settings = propgroup.as_jobsettings()\n    metadata = JobMetadata()\n\n    priority = getattr(scene, \"flamenco_job_priority\", 50)\n\n    submit_as_paused = getattr(scene, \"flamenco_job_submit_as_paused\", False)\n    if submit_as_paused:\n        initial_status = JobStatus(\"paused\")\n    else:\n        initial_status = JobStatus(\"queued\")\n\n    job: SubmittedJob = SubmittedJob(\n        name=scene.flamenco_job_name,\n        type=propgroup.job_type.name,\n        priority=priority,\n        settings=settings,\n        metadata=metadata,\n        submitter_platform=platform.system().lower(),\n        type_etag=propgroup.job_type.etag,\n        initial_status=initial_status,\n    )\n\n    worker_tag: str = getattr(scene, \"flamenco_worker_tag\", \"\")\n    if worker_tag and worker_tag != \"-\":\n        job.worker_tag = worker_tag\n\n    return job\n\n\ndef set_blend_file(\n    job_type: _AvailableJobType,\n    job: _SubmittedJob,\n    blendfile: Union[str, Path, PurePosixPath],\n) -\u003e None:\n    ",
        "detailedLines": [
          {
            "lineNumber": 31,
            "text": "log = logging.getLogger(__name__)"
          },
          {
            "lineNumber": 32
          },
          {
            "lineNumber": 33
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "def job_for_scene(scene: bpy.types.Scene) -\u003e Optional[_SubmittedJob]:"
          },
          {
            "lineNumber": 35,
            "text": "    from flamenco.manager.models import SubmittedJob, JobMetadata"
          },
          {
            "lineNumber": 36,
            "text": "    from flamenco.manager.model.job_status import JobStatus"
          },
          {
            "lineNumber": 37
          },
          {
            "lineNumber": 38,
            "text": "    propgroup = getattr(scene, \"flamenco_job_settings\", None)"
          },
          {
            "lineNumber": 39,
            "text": "    assert isinstance(propgroup, JobTypePropertyGroup), \"did not expect %s\" % ("
          },
          {
            "lineNumber": 40,
            "text": "        type(propgroup)"
          },
          {
            "lineNumber": 41,
            "text": "    )"
          },
          {
            "lineNumber": 42
          },
          {
            "lineNumber": 43,
            "text": "    settings = propgroup.as_jobsettings()"
          },
          {
            "lineNumber": 44,
            "text": "    metadata = JobMetadata()"
          },
          {
            "lineNumber": 45
          },
          {
            "lineNumber": 46,
            "text": "    priority = getattr(scene, \"flamenco_job_priority\", 50)"
          },
          {
            "lineNumber": 47
          },
          {
            "lineNumber": 48,
            "text": "    submit_as_paused = getattr(scene, \"flamenco_job_submit_as_paused\", False)"
          },
          {
            "lineNumber": 49,
            "text": "    if submit_as_paused:"
          },
          {
            "lineNumber": 50,
            "text": "        initial_status = JobStatus(\"paused\")"
          },
          {
            "lineNumber": 51,
            "text": "    else:"
          },
          {
            "lineNumber": 52,
            "text": "        initial_status = JobStatus(\"queued\")"
          },
          {
            "lineNumber": 53
          },
          {
            "lineNumber": 54,
            "text": "    job: SubmittedJob = SubmittedJob("
          },
          {
            "lineNumber": 55,
            "text": "        name=scene.flamenco_job_name,"
          },
          {
            "lineNumber": 56,
            "text": "        type=propgroup.job_type.name,"
          },
          {
            "lineNumber": 57,
            "text": "        priority=priority,"
          },
          {
            "lineNumber": 58,
            "text": "        settings=settings,"
          },
          {
            "lineNumber": 59,
            "text": "        metadata=metadata,"
          },
          {
            "lineNumber": 60,
            "text": "        submitter_platform=platform.system().lower(),"
          },
          {
            "lineNumber": 61,
            "text": "        type_etag=propgroup.job_type.etag,"
          },
          {
            "lineNumber": 62,
            "text": "        initial_status=initial_status,"
          },
          {
            "lineNumber": 63,
            "text": "    )"
          },
          {
            "lineNumber": 64
          },
          {
            "lineNumber": 65,
            "text": "    worker_tag: str = getattr(scene, \"flamenco_worker_tag\", \"\")"
          },
          {
            "lineNumber": 66,
            "text": "    if worker_tag and worker_tag != \"-\":"
          },
          {
            "lineNumber": 67,
            "text": "        job.worker_tag = worker_tag"
          },
          {
            "lineNumber": 68
          },
          {
            "isSignature": true,
            "lineNumber": 69,
            "text": "    return job"
          },
          {
            "lineNumber": 70
          },
          {
            "lineNumber": 71
          },
          {
            "isSignature": true,
            "lineNumber": 72,
            "text": "def set_blend_file("
          },
          {
            "isSignature": true,
            "lineNumber": 73,
            "text": "    job_type: _AvailableJobType,"
          },
          {
            "isSignature": true,
            "lineNumber": 74,
            "text": "    job: _SubmittedJob,"
          },
          {
            "isSignature": true,
            "lineNumber": 75,
            "text": "    blendfile: Union[str, Path, PurePosixPath],"
          },
          {
            "isSignature": true,
            "lineNumber": 76,
            "text": ") -\u003e None:"
          },
          {
            "lineNumber": 77,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 92
          },
          "startPosition": {
            "column": 35,
            "line": 28
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.27670809626579285
    },
    {
      "codeBlock": {
        "contents": "# SPDX-License-Identifier: GPL-3.0-or-later\nfrom pathlib import Path, PurePosixPath\nfrom typing import TYPE_CHECKING, Optional, Union\nimport platform\nimport logging\n\nimport bpy\n\nfrom .job_types_propgroup import JobTypePropertyGroup\nfrom .bat.submodules import bpathlib\nfrom . import manager_info\n\nif TYPE_CHECKING:\n    from .manager import ApiClient as _ApiClient\n    from .manager.models import (\n        AvailableJobType as _AvailableJobType,\n        Job as _Job,\n        SubmittedJob as _SubmittedJob,\n    )\nelse:\n    _AvailableJobType = object\n    _ApiClient = object\n    _Job = object\n    _SubmittedJob = object\n\n\n# If a job has a setting with this key, type 'str', and subtype 'file_path',\n# it'll be set to the path of the BAT-packed blend file.\nBLENDFILE_SETTING_KEY = \"blendfile\"\n\nlog = logging.getLogger(__name__)\n\n\ndef job_for_scene(scene: bpy.types.Scene) -\u003e Optional[_SubmittedJob]:\n    ",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# SPDX-License-Identifier: GPL-3.0-or-later"
          },
          {
            "lineNumber": 2,
            "text": "from pathlib import Path, PurePosixPath"
          },
          {
            "lineNumber": 3,
            "text": "from typing import TYPE_CHECKING, Optional, Union"
          },
          {
            "lineNumber": 4,
            "text": "import platform"
          },
          {
            "lineNumber": 5,
            "text": "import logging"
          },
          {
            "lineNumber": 6
          },
          {
            "lineNumber": 7,
            "text": "import bpy"
          },
          {
            "lineNumber": 8
          },
          {
            "lineNumber": 9,
            "text": "from .job_types_propgroup import JobTypePropertyGroup"
          },
          {
            "lineNumber": 10,
            "text": "from .bat.submodules import bpathlib"
          },
          {
            "lineNumber": 11,
            "text": "from . import manager_info"
          },
          {
            "lineNumber": 12
          },
          {
            "lineNumber": 13,
            "text": "if TYPE_CHECKING:"
          },
          {
            "lineNumber": 14,
            "text": "    from .manager import ApiClient as _ApiClient"
          },
          {
            "lineNumber": 15,
            "text": "    from .manager.models import ("
          },
          {
            "lineNumber": 16,
            "text": "        AvailableJobType as _AvailableJobType,"
          },
          {
            "lineNumber": 17,
            "text": "        Job as _Job,"
          },
          {
            "lineNumber": 18,
            "text": "        SubmittedJob as _SubmittedJob,"
          },
          {
            "lineNumber": 19,
            "text": "    )"
          },
          {
            "lineNumber": 20,
            "text": "else:"
          },
          {
            "lineNumber": 21,
            "text": "    _AvailableJobType = object"
          },
          {
            "lineNumber": 22,
            "text": "    _ApiClient = object"
          },
          {
            "lineNumber": 23,
            "text": "    _Job = object"
          },
          {
            "lineNumber": 24,
            "text": "    _SubmittedJob = object"
          },
          {
            "lineNumber": 25
          },
          {
            "lineNumber": 26
          },
          {
            "lineNumber": 27,
            "text": "# If a job has a setting with this key, type 'str', and subtype 'file_path',"
          },
          {
            "lineNumber": 28,
            "text": "# it'll be set to the path of the BAT-packed blend file."
          },
          {
            "lineNumber": 29,
            "text": "BLENDFILE_SETTING_KEY = \"blendfile\""
          },
          {
            "lineNumber": 30
          },
          {
            "lineNumber": 31,
            "text": "log = logging.getLogger(__name__)"
          },
          {
            "lineNumber": 32
          },
          {
            "lineNumber": 33
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "def job_for_scene(scene: bpy.types.Scene) -\u003e Optional[_SubmittedJob]:"
          },
          {
            "lineNumber": 35,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 68
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.2761038541793823
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

Searching for recent changes related to file sending and exclusion filters:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "exclusion_filter|exclude|relative_only"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "exclusion_filter|exclude|relative_only",
  "topFiles": [
    {
      "matchCount": 4,
      "uri": ".\\operators.py"
    },
    {
      "matchCount": 6,
      "uri": ".\\bat\\interface.py"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\docs\\EventJobUpdate.md"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\docs\\EventTaskUpdate.md"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\docs\\ShamanRequirementsResponse.md"
    }
  ],
  "totalFiles": 5,
  "totalMatches": 13
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\bat\interface.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking the shaman packing method:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "contextAfter": 3,
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "relative_only|exclusion_filter"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "relative_only|exclusion_filter",
  "topFiles": [
    {
      "matchCount": 10,
      "uri": "operators.py"
    }
  ],
  "totalFiles": 1,
  "totalMatches": 4
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

Reading the specstory file to understand the intended fix:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": true,
  "path": "e:\\flamenco\\.specstory\\history\\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md",
  "pattern": "relative_only|relative|absolute|project"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\.specstory\\history\\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md",
  "pattern": "relative_only|relative|absolute|project",
  "topFiles": [
    {
      "matchCount": 50,
      "uri": "..\\..\\.specstory\\history\\2026-01-28_19-46Z-flamenco-project-path-consistency-addon.md"
    }
  ],
  "totalFiles": 1,
  "totalMatches": 50
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "What does relative_only parameter do in BAT packer? How does it affect which files are included?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "_running_packthread: typing.Optional[PackThread] = None\n_packer_lock = threading.RLock()\n\n\ndef copy(  # type: ignore\n    base_blendfile: Path,\n    project: Path,\n    target: str,\n    exclusion_filter: str,\n    *,\n    relative_only: bool,\n    packer_class=submodules.pack.Packer,\n    packer_kwargs: Optional[dict[Any, Any]] = None,\n) -\u003e PackThread:\n    \"\"\"Use BAT to copy the given file and dependencies to the target location.\n\n    Runs BAT in a separate thread, and returns early. Use poll() to get updates\n    \u0026 the final result.\n    \"\"\"\n    global _running_packthread\n\n    with _packer_lock:\n        if _running_packthread is not None:\n            raise RuntimeError(\"other packing operation already in progress\")\n\n    # Due to issues with library overrides and unsynced pointers, it's quite\n    # common for the Blender Animation Studio to get crashes of BAT. To avoid\n    # these, Strict Pointer Mode is disabled.\n    submodules.blendfile.set_strict_pointer_mode(False)\n\n    log.info(\"BAT pack parameters:\")\n    log.info(\"base_blendfile = %r\", base_blendfile)\n    log.info(\"project        = %r\", project)\n    log.info(\"target         = %r\", target)\n\n    if packer_kwargs is None:\n        packer_kwargs = {}\n    packer = packer_class(\n        base_blendfile,\n        project,\n        target,\n        compress=True,\n        relative_only=relative_only,\n        **packer_kwargs,\n    )\n    if exclusion_filter:\n        filter_parts = exclusion_filter.strip().split(\" \")\n        packer.exclude(*filter_parts)\n\n    packthread = PackThread(packer=packer)\n    with _packer_lock:\n        _running_packthread = packthread\n\n    packthread.start()\n    return packthread\n\n\ndef abort() -\u003e None:\n    \"\"\"Abort a running copy() call.\n\n    No-op when there is no running copy(). Can be called from any thread.\n    ",
        "detailedLines": [
          {
            "lineNumber": 197,
            "text": "_running_packthread: typing.Optional[PackThread] = None"
          },
          {
            "lineNumber": 198,
            "text": "_packer_lock = threading.RLock()"
          },
          {
            "lineNumber": 199
          },
          {
            "lineNumber": 200
          },
          {
            "isSignature": true,
            "lineNumber": 201,
            "text": "def copy(  # type: ignore"
          },
          {
            "isSignature": true,
            "lineNumber": 202,
            "text": "    base_blendfile: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 203,
            "text": "    project: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 204,
            "text": "    target: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 205,
            "text": "    exclusion_filter: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 206,
            "text": "    *,"
          },
          {
            "isSignature": true,
            "lineNumber": 207,
            "text": "    relative_only: bool,"
          },
          {
            "isSignature": true,
            "lineNumber": 208,
            "text": "    packer_class=submodules.pack.Packer,"
          },
          {
            "isSignature": true,
            "lineNumber": 209,
            "text": "    packer_kwargs: Optional[dict[Any, Any]] = None,"
          },
          {
            "isSignature": true,
            "lineNumber": 210,
            "text": ") -\u003e PackThread:"
          },
          {
            "lineNumber": 211,
            "text": "    \"\"\"Use BAT to copy the given file and dependencies to the target location."
          },
          {
            "lineNumber": 212
          },
          {
            "lineNumber": 213,
            "text": "    Runs BAT in a separate thread, and returns early. Use poll() to get updates"
          },
          {
            "lineNumber": 214,
            "text": "    \u0026 the final result."
          },
          {
            "lineNumber": 215,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 216,
            "text": "    global _running_packthread"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 219,
            "text": "        if _running_packthread is not None:"
          },
          {
            "lineNumber": 220,
            "text": "            raise RuntimeError(\"other packing operation already in progress\")"
          },
          {
            "lineNumber": 221
          },
          {
            "lineNumber": 222,
            "text": "    # Due to issues with library overrides and unsynced pointers, it's quite"
          },
          {
            "lineNumber": 223,
            "text": "    # common for the Blender Animation Studio to get crashes of BAT. To avoid"
          },
          {
            "lineNumber": 224,
            "text": "    # these, Strict Pointer Mode is disabled."
          },
          {
            "lineNumber": 225,
            "text": "    submodules.blendfile.set_strict_pointer_mode(False)"
          },
          {
            "lineNumber": 226
          },
          {
            "lineNumber": 227,
            "text": "    log.info(\"BAT pack parameters:\")"
          },
          {
            "lineNumber": 228,
            "text": "    log.info(\"base_blendfile = %r\", base_blendfile)"
          },
          {
            "lineNumber": 229,
            "text": "    log.info(\"project        = %r\", project)"
          },
          {
            "lineNumber": 230,
            "text": "    log.info(\"target         = %r\", target)"
          },
          {
            "lineNumber": 231
          },
          {
            "lineNumber": 232,
            "text": "    if packer_kwargs is None:"
          },
          {
            "lineNumber": 233,
            "text": "        packer_kwargs = {}"
          },
          {
            "lineNumber": 234,
            "text": "    packer = packer_class("
          },
          {
            "lineNumber": 235,
            "text": "        base_blendfile,"
          },
          {
            "lineNumber": 236,
            "text": "        project,"
          },
          {
            "lineNumber": 237,
            "text": "        target,"
          },
          {
            "lineNumber": 238,
            "text": "        compress=True,"
          },
          {
            "lineNumber": 239,
            "text": "        relative_only=relative_only,"
          },
          {
            "lineNumber": 240,
            "text": "        **packer_kwargs,"
          },
          {
            "lineNumber": 241,
            "text": "    )"
          },
          {
            "lineNumber": 242,
            "text": "    if exclusion_filter:"
          },
          {
            "lineNumber": 243,
            "text": "        filter_parts = exclusion_filter.strip().split(\" \")"
          },
          {
            "lineNumber": 244,
            "text": "        packer.exclude(*filter_parts)"
          },
          {
            "lineNumber": 245
          },
          {
            "lineNumber": 246,
            "text": "    packthread = PackThread(packer=packer)"
          },
          {
            "lineNumber": 247,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 248,
            "text": "        _running_packthread = packthread"
          },
          {
            "lineNumber": 249
          },
          {
            "lineNumber": 250,
            "text": "    packthread.start()"
          },
          {
            "isSignature": true,
            "lineNumber": 251,
            "text": "    return packthread"
          },
          {
            "lineNumber": 252
          },
          {
            "lineNumber": 253
          },
          {
            "isSignature": true,
            "lineNumber": 254,
            "text": "def abort() -\u003e None:"
          },
          {
            "lineNumber": 255,
            "text": "    \"\"\"Abort a running copy() call."
          },
          {
            "lineNumber": 256
          },
          {
            "lineNumber": 257,
            "text": "    No-op when there is no running copy(). Can be called from any thread."
          },
          {
            "lineNumber": 258,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 264
          },
          "startPosition": {
            "column": 27,
            "line": 193
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.3934226632118225
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=False,  # Always use absolute paths for consistent file sending.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 404
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 414,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 415,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 416
          },
          {
            "lineNumber": 417,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 418,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 419,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 420,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 421
          },
          {
            "lineNumber": 422,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 423,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 424,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 427,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 428,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 429,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 430,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 431,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 432,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 433,
            "text": "        )"
          },
          {
            "lineNumber": 434,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 435
          },
          {
            "lineNumber": 436,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 437,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 441,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 442,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 443,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 444,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 445,
            "text": "            relative_only=False,  # Always use absolute paths for consistent file sending."
          },
          {
            "lineNumber": 446,
            "text": "        )"
          },
          {
            "lineNumber": 447
          },
          {
            "lineNumber": 448,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.39199894666671753
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID.\n\n        Note that this may not be the actually used checkout ID, as that will be\n        made unique to this job by Flamenco Manager. That will be stored in\n        self.actual_shaman_checkout_path after the Shaman checkout is actually\n        done.\n        \"\"\"\n        assert self.job is not None\n\n        # TODO: get project name from preferences/GUI and insert that here too.\n        return PurePosixPath(f\"{self.job.name}\")\n\n    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:\n        \"\"\"Use the Manager's Shaman API to submit the BAT pack.\n\n        :return: the filesystem path of the blend file, for in the render job definition.\n        \"\"\"\n        from .bat import (\n            interface as bat_interface,\n            shaman as bat_shaman,\n        )\n\n        assert self.job is not None\n        self.log.info(\"Sending BAT pack to Shaman\")\n\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=\"/\",  # Target directory irrelevant for Shaman transfers.\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=False,  # Always use absolute paths for consistent file sending.\n            packer_class=bat_shaman.Packer,\n            packer_kwargs=dict(\n                api_client=self.get_api_client(context),\n                checkout_path=self._shaman_checkout_path(),\n            ),\n        )\n\n        # We cannot assume the blendfile location is known until the Shaman\n        # checkout has actually been created.\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 448
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID."
          },
          {
            "lineNumber": 452
          },
          {
            "lineNumber": 453,
            "text": "        Note that this may not be the actually used checkout ID, as that will be"
          },
          {
            "lineNumber": 454,
            "text": "        made unique to this job by Flamenco Manager. That will be stored in"
          },
          {
            "lineNumber": 455,
            "text": "        self.actual_shaman_checkout_path after the Shaman checkout is actually"
          },
          {
            "lineNumber": 456,
            "text": "        done."
          },
          {
            "lineNumber": 457,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 458,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 459
          },
          {
            "lineNumber": 460,
            "text": "        # TODO: get project name from preferences/GUI and insert that here too."
          },
          {
            "lineNumber": 461,
            "text": "        return PurePosixPath(f\"{self.job.name}\")"
          },
          {
            "lineNumber": 462
          },
          {
            "lineNumber": 463,
            "text": "    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:"
          },
          {
            "lineNumber": 464,
            "text": "        \"\"\"Use the Manager's Shaman API to submit the BAT pack."
          },
          {
            "lineNumber": 465
          },
          {
            "lineNumber": 466,
            "text": "        :return: the filesystem path of the blend file, for in the render job definition."
          },
          {
            "lineNumber": 467,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 468,
            "text": "        from .bat import ("
          },
          {
            "lineNumber": 469,
            "text": "            interface as bat_interface,"
          },
          {
            "lineNumber": 470,
            "text": "            shaman as bat_shaman,"
          },
          {
            "lineNumber": 471,
            "text": "        )"
          },
          {
            "lineNumber": 472
          },
          {
            "lineNumber": 473,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 474,
            "text": "        self.log.info(\"Sending BAT pack to Shaman\")"
          },
          {
            "lineNumber": 475
          },
          {
            "lineNumber": 476,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 477,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 478
          },
          {
            "lineNumber": 479,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 480,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 481,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 482,
            "text": "            target=\"/\",  # Target directory irrelevant for Shaman transfers."
          },
          {
            "lineNumber": 483,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 484,
            "text": "            relative_only=False,  # Always use absolute paths for consistent file sending."
          },
          {
            "lineNumber": 485,
            "text": "            packer_class=bat_shaman.Packer,"
          },
          {
            "lineNumber": 486,
            "text": "            packer_kwargs=dict("
          },
          {
            "lineNumber": 487,
            "text": "                api_client=self.get_api_client(context),"
          },
          {
            "lineNumber": 488,
            "text": "                checkout_path=self._shaman_checkout_path(),"
          },
          {
            "lineNumber": 489,
            "text": "            ),"
          },
          {
            "lineNumber": 490,
            "text": "        )"
          },
          {
            "lineNumber": 491
          },
          {
            "lineNumber": 492,
            "text": "        # We cannot assume the blendfile location is known until the Shaman"
          },
          {
            "lineNumber": 493,
            "text": "        # checkout has actually been created."
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.33290135860443115
    },
    {
      "codeBlock": {
        "contents": "# Mypy doesn't understand that submodules.pack.Packer exists.\nclass Packer(submodules.pack.Packer):  # type: ignore\n    \"\"\"Creates BAT Packs on a Shaman server.\"\"\"\n\n    def __init__(\n        self,\n        blendfile: Path,\n        project_root: Path,\n        target: str,\n        *,\n        api_client: _ApiClient,\n        checkout_path: str,\n        **kwargs: dict[Any, Any],\n    ) -\u003e None:\n        \"\"\"Constructor\n\n        :param target: mock target root directory to construct project-relative paths.\n        \"\"\"\n        super().__init__(blendfile, project_root, target, **kwargs)\n        self.checkout_path = checkout_path\n        self.api_client = api_client\n        self.shaman_transferrer: Optional[Transferrer] = None\n\n    # Mypy doesn't understand that submodules.transfer.FileTransferer exists.\n    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore\n        self.shaman_transferrer = Transferrer(\n            self.api_client, self.project, self.checkout_path\n        )\n        return self.shaman_transferrer\n\n    def _make_target_path(self, target: str) -\u003e PurePath:\n        return _root_path()\n\n    @property\n    def output_path(self) -\u003e PurePath:\n        \"\"\"The path of the packed blend file in the target directory.\"\"\"\n        assert self._output_path is not None\n\n        rel_output = self._output_path.relative_to(self._target_path)\n        out_path: PurePath = self.actual_checkout_path / rel_output\n        return out_path\n\n    @property\n    def actual_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"The actual Shaman checkout path.\n\n        Only valid after packing is complete. Shaman ensures that the checkout\n        is unique, and thus the actual path can be different than the requested\n        one.\n        \"\"\"\n        assert self.shaman_transferrer is not None\n        return PurePosixPath(self.shaman_transferrer.checkout_path)\n\n    def execute(self):\n        try:\n            super().execute()",
        "detailedLines": [
          {
            "lineNumber": 37,
            "text": "# Mypy doesn't understand that submodules.pack.Packer exists."
          },
          {
            "isSignature": true,
            "lineNumber": 38,
            "text": "class Packer(submodules.pack.Packer):  # type: ignore"
          },
          {
            "lineNumber": 39,
            "text": "    \"\"\"Creates BAT Packs on a Shaman server.\"\"\""
          },
          {
            "lineNumber": 40
          },
          {
            "lineNumber": 41,
            "text": "    def __init__("
          },
          {
            "lineNumber": 42,
            "text": "        self,"
          },
          {
            "lineNumber": 43,
            "text": "        blendfile: Path,"
          },
          {
            "lineNumber": 44,
            "text": "        project_root: Path,"
          },
          {
            "lineNumber": 45,
            "text": "        target: str,"
          },
          {
            "lineNumber": 46,
            "text": "        *,"
          },
          {
            "lineNumber": 47,
            "text": "        api_client: _ApiClient,"
          },
          {
            "lineNumber": 48,
            "text": "        checkout_path: str,"
          },
          {
            "lineNumber": 49,
            "text": "        **kwargs: dict[Any, Any],"
          },
          {
            "lineNumber": 50,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 51,
            "text": "        \"\"\"Constructor"
          },
          {
            "lineNumber": 52
          },
          {
            "lineNumber": 53,
            "text": "        :param target: mock target root directory to construct project-relative paths."
          },
          {
            "lineNumber": 54,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 55,
            "text": "        super().__init__(blendfile, project_root, target, **kwargs)"
          },
          {
            "lineNumber": 56,
            "text": "        self.checkout_path = checkout_path"
          },
          {
            "lineNumber": 57,
            "text": "        self.api_client = api_client"
          },
          {
            "lineNumber": 58,
            "text": "        self.shaman_transferrer: Optional[Transferrer] = None"
          },
          {
            "lineNumber": 59
          },
          {
            "lineNumber": 60,
            "text": "    # Mypy doesn't understand that submodules.transfer.FileTransferer exists."
          },
          {
            "lineNumber": 61,
            "text": "    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore"
          },
          {
            "lineNumber": 62,
            "text": "        self.shaman_transferrer = Transferrer("
          },
          {
            "lineNumber": 63,
            "text": "            self.api_client, self.project, self.checkout_path"
          },
          {
            "lineNumber": 64,
            "text": "        )"
          },
          {
            "lineNumber": 65,
            "text": "        return self.shaman_transferrer"
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "    def _make_target_path(self, target: str) -\u003e PurePath:"
          },
          {
            "lineNumber": 68,
            "text": "        return _root_path()"
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "    @property"
          },
          {
            "lineNumber": 71,
            "text": "    def output_path(self) -\u003e PurePath:"
          },
          {
            "lineNumber": 72,
            "text": "        \"\"\"The path of the packed blend file in the target directory.\"\"\""
          },
          {
            "lineNumber": 73,
            "text": "        assert self._output_path is not None"
          },
          {
            "lineNumber": 74
          },
          {
            "lineNumber": 75,
            "text": "        rel_output = self._output_path.relative_to(self._target_path)"
          },
          {
            "lineNumber": 76,
            "text": "        out_path: PurePath = self.actual_checkout_path / rel_output"
          },
          {
            "lineNumber": 77,
            "text": "        return out_path"
          },
          {
            "lineNumber": 78
          },
          {
            "lineNumber": 79,
            "text": "    @property"
          },
          {
            "lineNumber": 80,
            "text": "    def actual_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 81,
            "text": "        \"\"\"The actual Shaman checkout path."
          },
          {
            "lineNumber": 82
          },
          {
            "lineNumber": 83,
            "text": "        Only valid after packing is complete. Shaman ensures that the checkout"
          },
          {
            "lineNumber": 84,
            "text": "        is unique, and thus the actual path can be different than the requested"
          },
          {
            "lineNumber": 85,
            "text": "        one."
          },
          {
            "lineNumber": 86,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 87,
            "text": "        assert self.shaman_transferrer is not None"
          },
          {
            "lineNumber": 88,
            "text": "        return PurePosixPath(self.shaman_transferrer.checkout_path)"
          },
          {
            "lineNumber": 89
          },
          {
            "lineNumber": 90,
            "text": "    def execute(self):"
          },
          {
            "lineNumber": 91,
            "text": "        try:"
          },
          {
            "lineNumber": 92,
            "text": "            super().execute()"
          }
        ],
        "range": {
          "endPosition": {
            "line": 95
          },
          "startPosition": {
            "column": 72,
            "line": 33
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {}
      },
      "score": 0.26465868949890137
    },
    {
      "codeBlock": {
        "contents": "class PackThread(threading.Thread):\n    queue: queue.SimpleQueue[Message]\n\n    # MyPy doesn't understand the way BAT subpackages are imported.\n    def __init__(self, packer: submodules.pack.Packer) -\u003e None:  # type: ignore\n        # Quitting Blender should abort the transfer (instead of hanging until\n        # the transfer is done), hence daemon=True.\n        super().__init__(daemon=True, name=\"PackThread\")\n\n        self.queue = queue.SimpleQueue()\n\n        self.packer = packer\n        self.packer.progress_cb = BatProgress(queue=self.queue)\n\n    def run(self) -\u003e None:\n        global _running_packthread\n\n        try:\n            self._run()\n        except BaseException as ex:\n            self._set_bat_status(\"ABORTED\")\n            log.exception(\"Error packing with BAT: %s\", ex)\n            self.queue.put(MsgException(ex=ex))\n        finally:\n            with _packer_lock:\n                _running_packthread = None\n\n    def _run(self) -\u003e None:\n        with self.packer:\n            log.debug(\"awaiting strategise\")\n            self._set_bat_status(\"INVESTIGATING\")\n            self.packer.strategise()\n\n            log.debug(\"awaiting execute\")\n            self._set_bat_status(\"TRANSFERRING\")\n            self.packer.execute()\n\n            log.debug(\"done\")\n            self._set_bat_status(\"DONE\")\n\n            msg = MsgDone(\n                self.packer.output_path,\n                self.packer.missing_files,\n                getattr(self.packer, \"actual_checkout_path\", None),\n            )\n            self.queue.put(msg)\n\n    def _set_bat_status(self, status: str) -\u003e None:\n        self.queue.put(MsgSetWMAttribute(\"flamenco_bat_status\", status))\n\n    def poll(self, timeout: Optional[int] = None) -\u003e Optional[Message]:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 130,
            "text": "class PackThread(threading.Thread):"
          },
          {
            "lineNumber": 131,
            "text": "    queue: queue.SimpleQueue[Message]"
          },
          {
            "lineNumber": 132
          },
          {
            "lineNumber": 133,
            "text": "    # MyPy doesn't understand the way BAT subpackages are imported."
          },
          {
            "lineNumber": 134,
            "text": "    def __init__(self, packer: submodules.pack.Packer) -\u003e None:  # type: ignore"
          },
          {
            "lineNumber": 135,
            "text": "        # Quitting Blender should abort the transfer (instead of hanging until"
          },
          {
            "lineNumber": 136,
            "text": "        # the transfer is done), hence daemon=True."
          },
          {
            "lineNumber": 137,
            "text": "        super().__init__(daemon=True, name=\"PackThread\")"
          },
          {
            "lineNumber": 138
          },
          {
            "lineNumber": 139,
            "text": "        self.queue = queue.SimpleQueue()"
          },
          {
            "lineNumber": 140
          },
          {
            "lineNumber": 141,
            "text": "        self.packer = packer"
          },
          {
            "lineNumber": 142,
            "text": "        self.packer.progress_cb = BatProgress(queue=self.queue)"
          },
          {
            "lineNumber": 143
          },
          {
            "lineNumber": 144,
            "text": "    def run(self) -\u003e None:"
          },
          {
            "lineNumber": 145,
            "text": "        global _running_packthread"
          },
          {
            "lineNumber": 146
          },
          {
            "lineNumber": 147,
            "text": "        try:"
          },
          {
            "lineNumber": 148,
            "text": "            self._run()"
          },
          {
            "lineNumber": 149,
            "text": "        except BaseException as ex:"
          },
          {
            "lineNumber": 150,
            "text": "            self._set_bat_status(\"ABORTED\")"
          },
          {
            "lineNumber": 151,
            "text": "            log.exception(\"Error packing with BAT: %s\", ex)"
          },
          {
            "lineNumber": 152,
            "text": "            self.queue.put(MsgException(ex=ex))"
          },
          {
            "lineNumber": 153,
            "text": "        finally:"
          },
          {
            "lineNumber": 154,
            "text": "            with _packer_lock:"
          },
          {
            "lineNumber": 155,
            "text": "                _running_packthread = None"
          },
          {
            "lineNumber": 156
          },
          {
            "lineNumber": 157,
            "text": "    def _run(self) -\u003e None:"
          },
          {
            "lineNumber": 158,
            "text": "        with self.packer:"
          },
          {
            "lineNumber": 159,
            "text": "            log.debug(\"awaiting strategise\")"
          },
          {
            "lineNumber": 160,
            "text": "            self._set_bat_status(\"INVESTIGATING\")"
          },
          {
            "lineNumber": 161,
            "text": "            self.packer.strategise()"
          },
          {
            "lineNumber": 162
          },
          {
            "lineNumber": 163,
            "text": "            log.debug(\"awaiting execute\")"
          },
          {
            "lineNumber": 164,
            "text": "            self._set_bat_status(\"TRANSFERRING\")"
          },
          {
            "lineNumber": 165,
            "text": "            self.packer.execute()"
          },
          {
            "lineNumber": 166
          },
          {
            "lineNumber": 167,
            "text": "            log.debug(\"done\")"
          },
          {
            "lineNumber": 168,
            "text": "            self._set_bat_status(\"DONE\")"
          },
          {
            "lineNumber": 169
          },
          {
            "lineNumber": 170,
            "text": "            msg = MsgDone("
          },
          {
            "lineNumber": 171,
            "text": "                self.packer.output_path,"
          },
          {
            "lineNumber": 172,
            "text": "                self.packer.missing_files,"
          },
          {
            "lineNumber": 173,
            "text": "                getattr(self.packer, \"actual_checkout_path\", None),"
          },
          {
            "lineNumber": 174,
            "text": "            )"
          },
          {
            "lineNumber": 175,
            "text": "            self.queue.put(msg)"
          },
          {
            "lineNumber": 176
          },
          {
            "lineNumber": 177,
            "text": "    def _set_bat_status(self, status: str) -\u003e None:"
          },
          {
            "lineNumber": 178,
            "text": "        self.queue.put(MsgSetWMAttribute(\"flamenco_bat_status\", status))"
          },
          {
            "lineNumber": 179
          },
          {
            "lineNumber": 180,
            "text": "    def poll(self, timeout: Optional[int] = None) -\u003e Optional[Message]:"
          },
          {
            "lineNumber": 181,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 193
          },
          "startPosition": {
            "column": 32,
            "line": 126
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.2607738971710205
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        from .bat import interface as bat_interface\n\n        if isinstance(msg, bat_interface.MsgDone):\n            if self.blendfile_on_farm is None:\n                # Adjust the blendfile to match the Shaman checkout path. Shaman\n                # may have checked out at a different location than we\n                # requested.\n                #\n                # Manager automatically creates a variable \"jobs\" that will\n                # resolve to the job storage directory.\n                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path\n\n            self.actual_shaman_checkout_path = msg.actual_checkout_path\n            self._submit_job(context)\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgException):\n            self.log.error(\"Error performing BAT pack: %s\", msg.ex)\n            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)\n\n            # This was an exception caught at the top level of the thread, so\n            # the packing thread itself has stopped.\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgSetWMAttribute):\n            wm = context.window_manager\n            setattr(wm, msg.attribute_name, msg.value)\n\n        return {\"RUNNING_MODAL\"}\n\n    def _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n        self.temp_blendfile = None\n\n        # The blend file is contained in the job storage path, no need to\n        # copy anything.\n        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)\n\n        # No Shaman is involved when using the file directly.\n        self.actual_shaman_checkout_path = None\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 493
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 497
          },
          {
            "lineNumber": 498,
            "text": "        if isinstance(msg, bat_interface.MsgDone):"
          },
          {
            "lineNumber": 499,
            "text": "            if self.blendfile_on_farm is None:"
          },
          {
            "lineNumber": 500,
            "text": "                # Adjust the blendfile to match the Shaman checkout path. Shaman"
          },
          {
            "lineNumber": 501,
            "text": "                # may have checked out at a different location than we"
          },
          {
            "lineNumber": 502,
            "text": "                # requested."
          },
          {
            "lineNumber": 503,
            "text": "                #"
          },
          {
            "lineNumber": 504,
            "text": "                # Manager automatically creates a variable \"jobs\" that will"
          },
          {
            "lineNumber": 505,
            "text": "                # resolve to the job storage directory."
          },
          {
            "lineNumber": 506,
            "text": "                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path"
          },
          {
            "lineNumber": 507
          },
          {
            "lineNumber": 508,
            "text": "            self.actual_shaman_checkout_path = msg.actual_checkout_path"
          },
          {
            "lineNumber": 509,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 510,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 511
          },
          {
            "lineNumber": 512,
            "text": "        if isinstance(msg, bat_interface.MsgException):"
          },
          {
            "lineNumber": 513,
            "text": "            self.log.error(\"Error performing BAT pack: %s\", msg.ex)"
          },
          {
            "lineNumber": 514,
            "text": "            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)"
          },
          {
            "lineNumber": 515
          },
          {
            "lineNumber": 516,
            "text": "            # This was an exception caught at the top level of the thread, so"
          },
          {
            "lineNumber": 517,
            "text": "            # the packing thread itself has stopped."
          },
          {
            "lineNumber": 518,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 519
          },
          {
            "lineNumber": 520,
            "text": "        if isinstance(msg, bat_interface.MsgSetWMAttribute):"
          },
          {
            "lineNumber": 521,
            "text": "            wm = context.window_manager"
          },
          {
            "lineNumber": 522,
            "text": "            setattr(wm, msg.attribute_name, msg.value)"
          },
          {
            "lineNumber": 523
          },
          {
            "lineNumber": 524,
            "text": "        return {\"RUNNING_MODAL\"}"
          },
          {
            "lineNumber": 525
          },
          {
            "lineNumber": 526,
            "text": "    def _use_blendfile_directly("
          },
          {
            "lineNumber": 527,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 528,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 529,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 530,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 531,
            "text": "        self.temp_blendfile = None"
          },
          {
            "lineNumber": 532
          },
          {
            "lineNumber": 533,
            "text": "        # The blend file is contained in the job storage path, no need to"
          },
          {
            "lineNumber": 534,
            "text": "        # copy anything."
          },
          {
            "lineNumber": 535,
            "text": "        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        # No Shaman is involved when using the file directly."
          },
          {
            "lineNumber": 538,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 539
          },
          {
            "lineNumber": 540,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 541
          },
          {
            "lineNumber": 542,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 543,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.25651633739471436
    },
    {
      "codeBlock": {
        "contents": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore\n\n\n    def _create_checkout_definition(self) -\u003e Optional[_ShamanRequirementsRequest]:\n        \"\"\"Create the checkout definition file for this BAT pack.\n\n        :returns: the checkout definition.\n\n        If there was an error and file transfer was aborted, the checkout\n        definition will be empty.\n        \"\"\"\n\n        from ..manager.models import (\n            ShamanRequirementsRequest,\n            ShamanFileSpec,\n        )\n\n        filespecs: list[ShamanFileSpec] = []\n\n        for src, dst, act in self.iter_queue():\n            try:\n                checksum = cache.compute_cached_checksum(src)\n                filesize = src.stat().st_size\n                relpath = str(_root_path_strip(dst))\n\n                filespec = ShamanFileSpec(\n                    sha=checksum,\n                    size=filesize,\n                    path=relpath,\n                )\n                if filespec in filespecs:\n                    # FIXME: there is an issue in BAT that some UDIM files are\n                    # reported twice. There is no use asking Shaman to check\n                    # them out twice, so avoid duplicates here for now.\n                    # ShamanFileSpec is not a hashable type, so unfortunately we\n                    # can't use a set() here.\n                    continue\n                filespecs.append(filespec)\n                self._rel_to_local_path[relpath] = src\n\n                if act == submodules.transfer.Action.MOVE:\n                    self._delete_when_done.append(src)\n            except Exception:\n                # We have to catch exceptions in a broad way, as this is running in",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 99,
            "text": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore"
          },
          {
            "lineNumber": 200
          },
          {
            "lineNumber": 201
          },
          {
            "lineNumber": 202,
            "text": "    def _create_checkout_definition(self) -\u003e Optional[_ShamanRequirementsRequest]:"
          },
          {
            "lineNumber": 203,
            "text": "        \"\"\"Create the checkout definition file for this BAT pack."
          },
          {
            "lineNumber": 204
          },
          {
            "lineNumber": 205,
            "text": "        :returns: the checkout definition."
          },
          {
            "lineNumber": 206
          },
          {
            "lineNumber": 207,
            "text": "        If there was an error and file transfer was aborted, the checkout"
          },
          {
            "lineNumber": 208,
            "text": "        definition will be empty."
          },
          {
            "lineNumber": 209,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 210
          },
          {
            "lineNumber": 211,
            "text": "        from ..manager.models import ("
          },
          {
            "lineNumber": 212,
            "text": "            ShamanRequirementsRequest,"
          },
          {
            "lineNumber": 213,
            "text": "            ShamanFileSpec,"
          },
          {
            "lineNumber": 214,
            "text": "        )"
          },
          {
            "lineNumber": 215
          },
          {
            "lineNumber": 216,
            "text": "        filespecs: list[ShamanFileSpec] = []"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "        for src, dst, act in self.iter_queue():"
          },
          {
            "lineNumber": 219,
            "text": "            try:"
          },
          {
            "lineNumber": 220,
            "text": "                checksum = cache.compute_cached_checksum(src)"
          },
          {
            "lineNumber": 221,
            "text": "                filesize = src.stat().st_size"
          },
          {
            "lineNumber": 222,
            "text": "                relpath = str(_root_path_strip(dst))"
          },
          {
            "lineNumber": 223
          },
          {
            "lineNumber": 224,
            "text": "                filespec = ShamanFileSpec("
          },
          {
            "lineNumber": 225,
            "text": "                    sha=checksum,"
          },
          {
            "lineNumber": 226,
            "text": "                    size=filesize,"
          },
          {
            "lineNumber": 227,
            "text": "                    path=relpath,"
          },
          {
            "lineNumber": 228,
            "text": "                )"
          },
          {
            "lineNumber": 229,
            "text": "                if filespec in filespecs:"
          },
          {
            "lineNumber": 230,
            "text": "                    # FIXME: there is an issue in BAT that some UDIM files are"
          },
          {
            "lineNumber": 231,
            "text": "                    # reported twice. There is no use asking Shaman to check"
          },
          {
            "lineNumber": 232,
            "text": "                    # them out twice, so avoid duplicates here for now."
          },
          {
            "lineNumber": 233,
            "text": "                    # ShamanFileSpec is not a hashable type, so unfortunately we"
          },
          {
            "lineNumber": 234,
            "text": "                    # can't use a set() here."
          },
          {
            "lineNumber": 235,
            "text": "                    continue"
          },
          {
            "lineNumber": 236,
            "text": "                filespecs.append(filespec)"
          },
          {
            "lineNumber": 237,
            "text": "                self._rel_to_local_path[relpath] = src"
          },
          {
            "lineNumber": 238
          },
          {
            "lineNumber": 239,
            "text": "                if act == submodules.transfer.Action.MOVE:"
          },
          {
            "lineNumber": 240,
            "text": "                    self._delete_when_done.append(src)"
          },
          {
            "lineNumber": 241,
            "text": "            except Exception:"
          },
          {
            "lineNumber": 242,
            "text": "                # We have to catch exceptions in a broad way, as this is running in"
          }
        ],
        "range": {
          "endPosition": {
            "line": 473
          },
          "startPosition": {
            "line": 98
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 100
              },
              "startPosition": {
                "column": 1,
                "line": 99
              }
            }
          ]
        }
      },
      "score": 0.2555246353149414
    },
    {
      "codeBlock": {
        "contents": "# MyPy doesn't understand the way BAT subpackages are imported.\nclass BatProgress(submodules.progress.Callback):  # type: ignore\n    \"\"\"Report progress of BAT Packing to the given queue.\"\"\"\n\n    def __init__(self, queue: queue.SimpleQueue[Message]) -\u003e None:\n        super().__init__()\n        self.queue = queue\n\n    def _set_attr(self, attr: str, value: Any) -\u003e None:\n        msg = MsgSetWMAttribute(attr, value)\n        self.queue.put(msg)\n\n    def _txt(self, msg: str) -\u003e None:\n        \"\"\"Set a text in a thread-safe way.\"\"\"\n        self._set_attr(\"flamenco_bat_status_txt\", msg)\n\n    def _status(self, status: str) -\u003e None:\n        \"\"\"Set the flamenco_bat_status property in a thread-safe way.\"\"\"\n        self._set_attr(\"flamenco_bat_status\", status)\n\n    def _progress(self, progress: int) -\u003e None:\n        \"\"\"Set the flamenco_bat_progress property in a thread-safe way.\"\"\"\n        self._set_attr(\"flamenco_bat_progress\", progress)\n        msg = MsgProgress(percentage=progress)\n        self.queue.put(msg)\n\n    def pack_start(self) -\u003e None:\n        self._txt(\"Starting BAT Pack operation\")\n\n    def pack_done(\n        self, output_blendfile: Path, missing_files: typing.Set[Path]\n    ) -\u003e None:\n        if missing_files:\n            self._txt(\"There were %d missing files\" % len(missing_files))\n            self._log_missing_files(missing_files)\n        else:\n            self._txt(\"Pack of %s done\" % output_blendfile.name)\n\n    def pack_aborted(self, reason: str) -\u003e None:\n        self._txt(\"Aborted: %s\" % reason)\n        self._status(\"ABORTED\")\n\n    def trace_blendfile(self, filename: Path) -\u003e None:\n        \"\"\"Called for every blendfile opened when tracing dependencies.\"\"\"\n        self._txt(\"Inspecting %s\" % filename.name)\n\n    def trace_asset(self, filename: Path) -\u003e None:\n        if filename.stem == \".blend\":\n            return\n        self._txt(\"Found asset %s\" % filename.name)\n\n    def rewrite_blendfile(self, orig_filename: Path) -\u003e None:\n        self._txt(\"Rewriting %s\" % orig_filename.name)",
        "detailedLines": [
          {
            "lineNumber": 57,
            "text": "# MyPy doesn't understand the way BAT subpackages are imported."
          },
          {
            "isSignature": true,
            "lineNumber": 58,
            "text": "class BatProgress(submodules.progress.Callback):  # type: ignore"
          },
          {
            "lineNumber": 59,
            "text": "    \"\"\"Report progress of BAT Packing to the given queue.\"\"\""
          },
          {
            "lineNumber": 60
          },
          {
            "lineNumber": 61,
            "text": "    def __init__(self, queue: queue.SimpleQueue[Message]) -\u003e None:"
          },
          {
            "lineNumber": 62,
            "text": "        super().__init__()"
          },
          {
            "lineNumber": 63,
            "text": "        self.queue = queue"
          },
          {
            "lineNumber": 64
          },
          {
            "lineNumber": 65,
            "text": "    def _set_attr(self, attr: str, value: Any) -\u003e None:"
          },
          {
            "lineNumber": 66,
            "text": "        msg = MsgSetWMAttribute(attr, value)"
          },
          {
            "lineNumber": 67,
            "text": "        self.queue.put(msg)"
          },
          {
            "lineNumber": 68
          },
          {
            "lineNumber": 69,
            "text": "    def _txt(self, msg: str) -\u003e None:"
          },
          {
            "lineNumber": 70,
            "text": "        \"\"\"Set a text in a thread-safe way.\"\"\""
          },
          {
            "lineNumber": 71,
            "text": "        self._set_attr(\"flamenco_bat_status_txt\", msg)"
          },
          {
            "lineNumber": 72
          },
          {
            "lineNumber": 73,
            "text": "    def _status(self, status: str) -\u003e None:"
          },
          {
            "lineNumber": 74,
            "text": "        \"\"\"Set the flamenco_bat_status property in a thread-safe way.\"\"\""
          },
          {
            "lineNumber": 75,
            "text": "        self._set_attr(\"flamenco_bat_status\", status)"
          },
          {
            "lineNumber": 76
          },
          {
            "lineNumber": 77,
            "text": "    def _progress(self, progress: int) -\u003e None:"
          },
          {
            "lineNumber": 78,
            "text": "        \"\"\"Set the flamenco_bat_progress property in a thread-safe way.\"\"\""
          },
          {
            "lineNumber": 79,
            "text": "        self._set_attr(\"flamenco_bat_progress\", progress)"
          },
          {
            "lineNumber": 80,
            "text": "        msg = MsgProgress(percentage=progress)"
          },
          {
            "lineNumber": 81,
            "text": "        self.queue.put(msg)"
          },
          {
            "lineNumber": 82
          },
          {
            "lineNumber": 83,
            "text": "    def pack_start(self) -\u003e None:"
          },
          {
            "lineNumber": 84,
            "text": "        self._txt(\"Starting BAT Pack operation\")"
          },
          {
            "lineNumber": 85
          },
          {
            "lineNumber": 86,
            "text": "    def pack_done("
          },
          {
            "lineNumber": 87,
            "text": "        self, output_blendfile: Path, missing_files: typing.Set[Path]"
          },
          {
            "lineNumber": 88,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 89,
            "text": "        if missing_files:"
          },
          {
            "lineNumber": 90,
            "text": "            self._txt(\"There were %d missing files\" % len(missing_files))"
          },
          {
            "lineNumber": 91,
            "text": "            self._log_missing_files(missing_files)"
          },
          {
            "lineNumber": 92,
            "text": "        else:"
          },
          {
            "lineNumber": 93,
            "text": "            self._txt(\"Pack of %s done\" % output_blendfile.name)"
          },
          {
            "lineNumber": 94
          },
          {
            "lineNumber": 95,
            "text": "    def pack_aborted(self, reason: str) -\u003e None:"
          },
          {
            "lineNumber": 96,
            "text": "        self._txt(\"Aborted: %s\" % reason)"
          },
          {
            "lineNumber": 97,
            "text": "        self._status(\"ABORTED\")"
          },
          {
            "lineNumber": 98
          },
          {
            "lineNumber": 99,
            "text": "    def trace_blendfile(self, filename: Path) -\u003e None:"
          },
          {
            "lineNumber": 100,
            "text": "        \"\"\"Called for every blendfile opened when tracing dependencies.\"\"\""
          },
          {
            "lineNumber": 101,
            "text": "        self._txt(\"Inspecting %s\" % filename.name)"
          },
          {
            "lineNumber": 102
          },
          {
            "lineNumber": 103,
            "text": "    def trace_asset(self, filename: Path) -\u003e None:"
          },
          {
            "lineNumber": 104,
            "text": "        if filename.stem == \".blend\":"
          },
          {
            "lineNumber": 105,
            "text": "            return"
          },
          {
            "lineNumber": 106,
            "text": "        self._txt(\"Found asset %s\" % filename.name)"
          },
          {
            "lineNumber": 107
          },
          {
            "lineNumber": 108,
            "text": "    def rewrite_blendfile(self, orig_filename: Path) -\u003e None:"
          },
          {
            "lineNumber": 109,
            "text": "        self._txt(\"Rewriting %s\" % orig_filename.name)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 126
          },
          "startPosition": {
            "column": 56,
            "line": 53
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.2449137568473816
    },
    {
      "codeBlock": {
        "contents": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:\n    \"\"\"Check whether current blend file is inside the storage path.\n\n    :return: True when the current blend file is inside the Flamenco job storage\n        directory already. In this case it won't be BAT-packed, as it's assumed\n        the job storage dir is accessible by the workers already.\n    \"\"\"\n\n    blendfile = bpathlib.make_absolute(blendfile)\n\n    info = manager_info.load_cached()\n    if not info:\n        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")\n\n    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))\n\n    log.info(\"Checking whether the file is already inside the job storage\")\n    log.info(\"    file   : %s\", blendfile)\n    log.info(\"    storage: %s\", job_storage)\n\n    try:\n        blendfile.relative_to(job_storage)\n    except ValueError:\n        return False\n    return True",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 134,
            "text": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 135,
            "text": "    \"\"\"Check whether current blend file is inside the storage path."
          },
          {
            "lineNumber": 136
          },
          {
            "lineNumber": 137,
            "text": "    :return: True when the current blend file is inside the Flamenco job storage"
          },
          {
            "lineNumber": 138,
            "text": "        directory already. In this case it won't be BAT-packed, as it's assumed"
          },
          {
            "lineNumber": 139,
            "text": "        the job storage dir is accessible by the workers already."
          },
          {
            "lineNumber": 140,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 141
          },
          {
            "lineNumber": 142,
            "text": "    blendfile = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 143
          },
          {
            "lineNumber": 144,
            "text": "    info = manager_info.load_cached()"
          },
          {
            "lineNumber": 145,
            "text": "    if not info:"
          },
          {
            "lineNumber": 146,
            "text": "        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")"
          },
          {
            "lineNumber": 147
          },
          {
            "lineNumber": 148,
            "text": "    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))"
          },
          {
            "lineNumber": 149
          },
          {
            "lineNumber": 150,
            "text": "    log.info(\"Checking whether the file is already inside the job storage\")"
          },
          {
            "lineNumber": 151,
            "text": "    log.info(\"    file   : %s\", blendfile)"
          },
          {
            "lineNumber": 152,
            "text": "    log.info(\"    storage: %s\", job_storage)"
          },
          {
            "lineNumber": 153
          },
          {
            "lineNumber": 154,
            "text": "    try:"
          },
          {
            "lineNumber": 155,
            "text": "        blendfile.relative_to(job_storage)"
          },
          {
            "lineNumber": 156,
            "text": "    except ValueError:"
          },
          {
            "lineNumber": 157,
            "text": "        return False"
          },
          {
            "isSignature": true,
            "lineNumber": 158,
            "text": "    return True"
          }
        ],
        "range": {
          "endPosition": {
            "column": 15,
            "line": 157
          },
          "startPosition": {
            "column": 42,
            "line": 130
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.24246376752853394
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _save_blendfile(self, context):\n\n\n        return filepath\n\n    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n        \"\"\"Ensure that the files are somewhere in the shared storage.\n\n        Returns True if a packing thread has been started, and False otherwise.\n        \"\"\"\n\n        from .bat import interface as bat_interface\n\n        if bat_interface.is_packing():\n            self.report({\"ERROR\"}, \"Another packing operation is running\")\n            self._quit(context)\n            return False\n\n        manager = self._manager_info(context)\n        if not manager:\n            return False\n\n        if manager.shared_storage.shaman_enabled:\n            # self.blendfile_on_farm will be set when BAT created the checkout,\n            # see _on_bat_pack_msg() below.\n            self.blendfile_on_farm = None\n            self._bat_pack_shaman(context, blendfile)\n        elif job_submission.is_file_inside_job_storage(context, blendfile):\n            self.log.info(\n                \"File is already in job storage location, submitting it as-is\"\n            )\n            self._use_blendfile_directly(context, blendfile)\n        else:\n            self.log.info(\n                \"File is not already in job storage location, copying it there\"\n            )\n            try:\n                self.blendfile_on_farm = self._bat_pack_filesystem(context, blendfile)\n            except FileNotFoundError:\n                self._quit(context)\n                return False\n\n        wm = context.window_manager\n        self.timer = wm.event_timer_add(self.TIMER_PERIOD, window=context.window)\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 300,
            "text": "def _save_blendfile(self, context):"
          },
          {
            "lineNumber": 362
          },
          {
            "lineNumber": 363
          },
          {
            "lineNumber": 364,
            "text": "        return filepath"
          },
          {
            "lineNumber": 365
          },
          {
            "lineNumber": 366,
            "text": "    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 367,
            "text": "        \"\"\"Ensure that the files are somewhere in the shared storage."
          },
          {
            "lineNumber": 368
          },
          {
            "lineNumber": 369,
            "text": "        Returns True if a packing thread has been started, and False otherwise."
          },
          {
            "lineNumber": 370,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 371
          },
          {
            "lineNumber": 372,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 373
          },
          {
            "lineNumber": 374,
            "text": "        if bat_interface.is_packing():"
          },
          {
            "lineNumber": 375,
            "text": "            self.report({\"ERROR\"}, \"Another packing operation is running\")"
          },
          {
            "lineNumber": 376,
            "text": "            self._quit(context)"
          },
          {
            "lineNumber": 377,
            "text": "            return False"
          },
          {
            "lineNumber": 378
          },
          {
            "lineNumber": 379,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 380,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 381,
            "text": "            return False"
          },
          {
            "lineNumber": 382
          },
          {
            "lineNumber": 383,
            "text": "        if manager.shared_storage.shaman_enabled:"
          },
          {
            "lineNumber": 384,
            "text": "            # self.blendfile_on_farm will be set when BAT created the checkout,"
          },
          {
            "lineNumber": 385,
            "text": "            # see _on_bat_pack_msg() below."
          },
          {
            "lineNumber": 386,
            "text": "            self.blendfile_on_farm = None"
          },
          {
            "lineNumber": 387,
            "text": "            self._bat_pack_shaman(context, blendfile)"
          },
          {
            "lineNumber": 388,
            "text": "        elif job_submission.is_file_inside_job_storage(context, blendfile):"
          },
          {
            "lineNumber": 389,
            "text": "            self.log.info("
          },
          {
            "lineNumber": 390,
            "text": "                \"File is already in job storage location, submitting it as-is\""
          },
          {
            "lineNumber": 391,
            "text": "            )"
          },
          {
            "lineNumber": 392,
            "text": "            self._use_blendfile_directly(context, blendfile)"
          },
          {
            "lineNumber": 393,
            "text": "        else:"
          },
          {
            "lineNumber": 394,
            "text": "            self.log.info("
          },
          {
            "lineNumber": 395,
            "text": "                \"File is not already in job storage location, copying it there\""
          },
          {
            "lineNumber": 396,
            "text": "            )"
          },
          {
            "lineNumber": 397,
            "text": "            try:"
          },
          {
            "lineNumber": 398,
            "text": "                self.blendfile_on_farm = self._bat_pack_filesystem(context, blendfile)"
          },
          {
            "lineNumber": 399,
            "text": "            except FileNotFoundError:"
          },
          {
            "lineNumber": 400,
            "text": "                self._quit(context)"
          },
          {
            "lineNumber": 401,
            "text": "                return False"
          },
          {
            "lineNumber": 402
          },
          {
            "lineNumber": 403,
            "text": "        wm = context.window_manager"
          },
          {
            "lineNumber": 404,
            "text": "        self.timer = wm.event_timer_add(self.TIMER_PERIOD, window=context.window)"
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.2260541021823883
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

The code sets `relative_only=False`, which includes all files referenced by absolute paths. It should be `relative_only=True` to include only files relative to the project path. Updating both packing methods:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Updating both methods to use `relative_only=True`:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Updated both packing methods to use `relative_only=True` instead of `False`. This ensures BAT only includes files relative to the project path, not all files referenced by absolute paths.

Changes:
1. `_bat_pack_filesystem` — changed `relative_only=False` to `relative_only=True`
2. `_bat_pack_shaman` — changed `relative_only=False` to `relative_only=True`

When submitting a job, only files relative to the project path will be included, not all files referenced by absolute paths.

---

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt the manager still spawned all the shaman pointers.

How Flamenco usually works: BAT assumes all relpaths are unavailable on the network, so it sends all libraries with relpath and remaps them to `[job]\_outside_project\...`. It assumes all abspaths are available and does not remap them.

How I want it to change it: BAT should be bypassed entirely; all relpaths should be remapped absolute. Effectively, the blend should be sent as-is, but always with abspaths.

---

_**Agent (default)**_

Reviewing the current implementation to bypass BAT and remap all relative paths to absolute before sending the blend file as-is.


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How does BAT remap relative paths to outside_project? How are paths converted in blend files?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 404
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 414,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 415,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 416
          },
          {
            "lineNumber": 417,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 418,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 419,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 420,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 421
          },
          {
            "lineNumber": 422,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 423,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 424,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 427,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 428,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 429,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 430,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 431,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 432,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 433,
            "text": "        )"
          },
          {
            "lineNumber": 434,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 435
          },
          {
            "lineNumber": 436,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 437,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 441,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 442,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 443,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 444,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 445,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 446,
            "text": "        )"
          },
          {
            "lineNumber": 447
          },
          {
            "lineNumber": 448,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.4373442530632019
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID.\n\n        Note that this may not be the actually used checkout ID, as that will be\n        made unique to this job by Flamenco Manager. That will be stored in\n        self.actual_shaman_checkout_path after the Shaman checkout is actually\n        done.\n        \"\"\"\n        assert self.job is not None\n\n        # TODO: get project name from preferences/GUI and insert that here too.\n        return PurePosixPath(f\"{self.job.name}\")\n\n    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:\n        \"\"\"Use the Manager's Shaman API to submit the BAT pack.\n\n        :return: the filesystem path of the blend file, for in the render job definition.\n        \"\"\"\n        from .bat import (\n            interface as bat_interface,\n            shaman as bat_shaman,\n        )\n\n        assert self.job is not None\n        self.log.info(\"Sending BAT pack to Shaman\")\n\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=\"/\",  # Target directory irrelevant for Shaman transfers.\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n            packer_class=bat_shaman.Packer,\n            packer_kwargs=dict(\n                api_client=self.get_api_client(context),\n                checkout_path=self._shaman_checkout_path(),\n            ),\n        )\n\n        # We cannot assume the blendfile location is known until the Shaman\n        # checkout has actually been created.\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 448
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID."
          },
          {
            "lineNumber": 452
          },
          {
            "lineNumber": 453,
            "text": "        Note that this may not be the actually used checkout ID, as that will be"
          },
          {
            "lineNumber": 454,
            "text": "        made unique to this job by Flamenco Manager. That will be stored in"
          },
          {
            "lineNumber": 455,
            "text": "        self.actual_shaman_checkout_path after the Shaman checkout is actually"
          },
          {
            "lineNumber": 456,
            "text": "        done."
          },
          {
            "lineNumber": 457,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 458,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 459
          },
          {
            "lineNumber": 460,
            "text": "        # TODO: get project name from preferences/GUI and insert that here too."
          },
          {
            "lineNumber": 461,
            "text": "        return PurePosixPath(f\"{self.job.name}\")"
          },
          {
            "lineNumber": 462
          },
          {
            "lineNumber": 463,
            "text": "    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:"
          },
          {
            "lineNumber": 464,
            "text": "        \"\"\"Use the Manager's Shaman API to submit the BAT pack."
          },
          {
            "lineNumber": 465
          },
          {
            "lineNumber": 466,
            "text": "        :return: the filesystem path of the blend file, for in the render job definition."
          },
          {
            "lineNumber": 467,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 468,
            "text": "        from .bat import ("
          },
          {
            "lineNumber": 469,
            "text": "            interface as bat_interface,"
          },
          {
            "lineNumber": 470,
            "text": "            shaman as bat_shaman,"
          },
          {
            "lineNumber": 471,
            "text": "        )"
          },
          {
            "lineNumber": 472
          },
          {
            "lineNumber": 473,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 474,
            "text": "        self.log.info(\"Sending BAT pack to Shaman\")"
          },
          {
            "lineNumber": 475
          },
          {
            "lineNumber": 476,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 477,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 478
          },
          {
            "lineNumber": 479,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 480,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 481,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 482,
            "text": "            target=\"/\",  # Target directory irrelevant for Shaman transfers."
          },
          {
            "lineNumber": 483,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 484,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 485,
            "text": "            packer_class=bat_shaman.Packer,"
          },
          {
            "lineNumber": 486,
            "text": "            packer_kwargs=dict("
          },
          {
            "lineNumber": 487,
            "text": "                api_client=self.get_api_client(context),"
          },
          {
            "lineNumber": 488,
            "text": "                checkout_path=self._shaman_checkout_path(),"
          },
          {
            "lineNumber": 489,
            "text": "            ),"
          },
          {
            "lineNumber": 490,
            "text": "        )"
          },
          {
            "lineNumber": 491
          },
          {
            "lineNumber": 492,
            "text": "        # We cannot assume the blendfile location is known until the Shaman"
          },
          {
            "lineNumber": 493,
            "text": "        # checkout has actually been created."
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.37421703338623047
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        from .bat import interface as bat_interface\n\n        if isinstance(msg, bat_interface.MsgDone):\n            if self.blendfile_on_farm is None:\n                # Adjust the blendfile to match the Shaman checkout path. Shaman\n                # may have checked out at a different location than we\n                # requested.\n                #\n                # Manager automatically creates a variable \"jobs\" that will\n                # resolve to the job storage directory.\n                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path\n\n            self.actual_shaman_checkout_path = msg.actual_checkout_path\n            self._submit_job(context)\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgException):\n            self.log.error(\"Error performing BAT pack: %s\", msg.ex)\n            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)\n\n            # This was an exception caught at the top level of the thread, so\n            # the packing thread itself has stopped.\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgSetWMAttribute):\n            wm = context.window_manager\n            setattr(wm, msg.attribute_name, msg.value)\n\n        return {\"RUNNING_MODAL\"}\n\n    def _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n        self.temp_blendfile = None\n\n        # The blend file is contained in the job storage path, no need to\n        # copy anything.\n        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)\n\n        # No Shaman is involved when using the file directly.\n        self.actual_shaman_checkout_path = None\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 493
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 497
          },
          {
            "lineNumber": 498,
            "text": "        if isinstance(msg, bat_interface.MsgDone):"
          },
          {
            "lineNumber": 499,
            "text": "            if self.blendfile_on_farm is None:"
          },
          {
            "lineNumber": 500,
            "text": "                # Adjust the blendfile to match the Shaman checkout path. Shaman"
          },
          {
            "lineNumber": 501,
            "text": "                # may have checked out at a different location than we"
          },
          {
            "lineNumber": 502,
            "text": "                # requested."
          },
          {
            "lineNumber": 503,
            "text": "                #"
          },
          {
            "lineNumber": 504,
            "text": "                # Manager automatically creates a variable \"jobs\" that will"
          },
          {
            "lineNumber": 505,
            "text": "                # resolve to the job storage directory."
          },
          {
            "lineNumber": 506,
            "text": "                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path"
          },
          {
            "lineNumber": 507
          },
          {
            "lineNumber": 508,
            "text": "            self.actual_shaman_checkout_path = msg.actual_checkout_path"
          },
          {
            "lineNumber": 509,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 510,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 511
          },
          {
            "lineNumber": 512,
            "text": "        if isinstance(msg, bat_interface.MsgException):"
          },
          {
            "lineNumber": 513,
            "text": "            self.log.error(\"Error performing BAT pack: %s\", msg.ex)"
          },
          {
            "lineNumber": 514,
            "text": "            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)"
          },
          {
            "lineNumber": 515
          },
          {
            "lineNumber": 516,
            "text": "            # This was an exception caught at the top level of the thread, so"
          },
          {
            "lineNumber": 517,
            "text": "            # the packing thread itself has stopped."
          },
          {
            "lineNumber": 518,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 519
          },
          {
            "lineNumber": 520,
            "text": "        if isinstance(msg, bat_interface.MsgSetWMAttribute):"
          },
          {
            "lineNumber": 521,
            "text": "            wm = context.window_manager"
          },
          {
            "lineNumber": 522,
            "text": "            setattr(wm, msg.attribute_name, msg.value)"
          },
          {
            "lineNumber": 523
          },
          {
            "lineNumber": 524,
            "text": "        return {\"RUNNING_MODAL\"}"
          },
          {
            "lineNumber": 525
          },
          {
            "lineNumber": 526,
            "text": "    def _use_blendfile_directly("
          },
          {
            "lineNumber": 527,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 528,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 529,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 530,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 531,
            "text": "        self.temp_blendfile = None"
          },
          {
            "lineNumber": 532
          },
          {
            "lineNumber": 533,
            "text": "        # The blend file is contained in the job storage path, no need to"
          },
          {
            "lineNumber": 534,
            "text": "        # copy anything."
          },
          {
            "lineNumber": 535,
            "text": "        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        # No Shaman is involved when using the file directly."
          },
          {
            "lineNumber": 538,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 539
          },
          {
            "lineNumber": 540,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 541
          },
          {
            "lineNumber": 542,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 543,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.33768710494041443
    },
    {
      "codeBlock": {
        "contents": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:\n    \"\"\"Check whether current blend file is inside the storage path.\n\n    :return: True when the current blend file is inside the Flamenco job storage\n        directory already. In this case it won't be BAT-packed, as it's assumed\n        the job storage dir is accessible by the workers already.\n    \"\"\"\n\n    blendfile = bpathlib.make_absolute(blendfile)\n\n    info = manager_info.load_cached()\n    if not info:\n        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")\n\n    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))\n\n    log.info(\"Checking whether the file is already inside the job storage\")\n    log.info(\"    file   : %s\", blendfile)\n    log.info(\"    storage: %s\", job_storage)\n\n    try:\n        blendfile.relative_to(job_storage)\n    except ValueError:\n        return False\n    return True",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 134,
            "text": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 135,
            "text": "    \"\"\"Check whether current blend file is inside the storage path."
          },
          {
            "lineNumber": 136
          },
          {
            "lineNumber": 137,
            "text": "    :return: True when the current blend file is inside the Flamenco job storage"
          },
          {
            "lineNumber": 138,
            "text": "        directory already. In this case it won't be BAT-packed, as it's assumed"
          },
          {
            "lineNumber": 139,
            "text": "        the job storage dir is accessible by the workers already."
          },
          {
            "lineNumber": 140,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 141
          },
          {
            "lineNumber": 142,
            "text": "    blendfile = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 143
          },
          {
            "lineNumber": 144,
            "text": "    info = manager_info.load_cached()"
          },
          {
            "lineNumber": 145,
            "text": "    if not info:"
          },
          {
            "lineNumber": 146,
            "text": "        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")"
          },
          {
            "lineNumber": 147
          },
          {
            "lineNumber": 148,
            "text": "    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))"
          },
          {
            "lineNumber": 149
          },
          {
            "lineNumber": 150,
            "text": "    log.info(\"Checking whether the file is already inside the job storage\")"
          },
          {
            "lineNumber": 151,
            "text": "    log.info(\"    file   : %s\", blendfile)"
          },
          {
            "lineNumber": 152,
            "text": "    log.info(\"    storage: %s\", job_storage)"
          },
          {
            "lineNumber": 153
          },
          {
            "lineNumber": 154,
            "text": "    try:"
          },
          {
            "lineNumber": 155,
            "text": "        blendfile.relative_to(job_storage)"
          },
          {
            "lineNumber": 156,
            "text": "    except ValueError:"
          },
          {
            "lineNumber": 157,
            "text": "        return False"
          },
          {
            "isSignature": true,
            "lineNumber": 158,
            "text": "    return True"
          }
        ],
        "range": {
          "endPosition": {
            "column": 15,
            "line": 157
          },
          "startPosition": {
            "column": 42,
            "line": 130
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.33607715368270874
    },
    {
      "codeBlock": {
        "contents": "_running_packthread: typing.Optional[PackThread] = None\n_packer_lock = threading.RLock()\n\n\ndef copy(  # type: ignore\n    base_blendfile: Path,\n    project: Path,\n    target: str,\n    exclusion_filter: str,\n    *,\n    relative_only: bool,\n    packer_class=submodules.pack.Packer,\n    packer_kwargs: Optional[dict[Any, Any]] = None,\n) -\u003e PackThread:\n    \"\"\"Use BAT to copy the given file and dependencies to the target location.\n\n    Runs BAT in a separate thread, and returns early. Use poll() to get updates\n    \u0026 the final result.\n    \"\"\"\n    global _running_packthread\n\n    with _packer_lock:\n        if _running_packthread is not None:\n            raise RuntimeError(\"other packing operation already in progress\")\n\n    # Due to issues with library overrides and unsynced pointers, it's quite\n    # common for the Blender Animation Studio to get crashes of BAT. To avoid\n    # these, Strict Pointer Mode is disabled.\n    submodules.blendfile.set_strict_pointer_mode(False)\n\n    log.info(\"BAT pack parameters:\")\n    log.info(\"base_blendfile = %r\", base_blendfile)\n    log.info(\"project        = %r\", project)\n    log.info(\"target         = %r\", target)\n\n    if packer_kwargs is None:\n        packer_kwargs = {}\n    packer = packer_class(\n        base_blendfile,\n        project,\n        target,\n        compress=True,\n        relative_only=relative_only,\n        **packer_kwargs,\n    )\n    if exclusion_filter:\n        filter_parts = exclusion_filter.strip().split(\" \")\n        packer.exclude(*filter_parts)\n\n    packthread = PackThread(packer=packer)\n    with _packer_lock:\n        _running_packthread = packthread\n\n    packthread.start()\n    return packthread\n\n\ndef abort() -\u003e None:\n    \"\"\"Abort a running copy() call.\n\n    No-op when there is no running copy(). Can be called from any thread.\n    ",
        "detailedLines": [
          {
            "lineNumber": 197,
            "text": "_running_packthread: typing.Optional[PackThread] = None"
          },
          {
            "lineNumber": 198,
            "text": "_packer_lock = threading.RLock()"
          },
          {
            "lineNumber": 199
          },
          {
            "lineNumber": 200
          },
          {
            "isSignature": true,
            "lineNumber": 201,
            "text": "def copy(  # type: ignore"
          },
          {
            "isSignature": true,
            "lineNumber": 202,
            "text": "    base_blendfile: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 203,
            "text": "    project: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 204,
            "text": "    target: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 205,
            "text": "    exclusion_filter: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 206,
            "text": "    *,"
          },
          {
            "isSignature": true,
            "lineNumber": 207,
            "text": "    relative_only: bool,"
          },
          {
            "isSignature": true,
            "lineNumber": 208,
            "text": "    packer_class=submodules.pack.Packer,"
          },
          {
            "isSignature": true,
            "lineNumber": 209,
            "text": "    packer_kwargs: Optional[dict[Any, Any]] = None,"
          },
          {
            "isSignature": true,
            "lineNumber": 210,
            "text": ") -\u003e PackThread:"
          },
          {
            "lineNumber": 211,
            "text": "    \"\"\"Use BAT to copy the given file and dependencies to the target location."
          },
          {
            "lineNumber": 212
          },
          {
            "lineNumber": 213,
            "text": "    Runs BAT in a separate thread, and returns early. Use poll() to get updates"
          },
          {
            "lineNumber": 214,
            "text": "    \u0026 the final result."
          },
          {
            "lineNumber": 215,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 216,
            "text": "    global _running_packthread"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 219,
            "text": "        if _running_packthread is not None:"
          },
          {
            "lineNumber": 220,
            "text": "            raise RuntimeError(\"other packing operation already in progress\")"
          },
          {
            "lineNumber": 221
          },
          {
            "lineNumber": 222,
            "text": "    # Due to issues with library overrides and unsynced pointers, it's quite"
          },
          {
            "lineNumber": 223,
            "text": "    # common for the Blender Animation Studio to get crashes of BAT. To avoid"
          },
          {
            "lineNumber": 224,
            "text": "    # these, Strict Pointer Mode is disabled."
          },
          {
            "lineNumber": 225,
            "text": "    submodules.blendfile.set_strict_pointer_mode(False)"
          },
          {
            "lineNumber": 226
          },
          {
            "lineNumber": 227,
            "text": "    log.info(\"BAT pack parameters:\")"
          },
          {
            "lineNumber": 228,
            "text": "    log.info(\"base_blendfile = %r\", base_blendfile)"
          },
          {
            "lineNumber": 229,
            "text": "    log.info(\"project        = %r\", project)"
          },
          {
            "lineNumber": 230,
            "text": "    log.info(\"target         = %r\", target)"
          },
          {
            "lineNumber": 231
          },
          {
            "lineNumber": 232,
            "text": "    if packer_kwargs is None:"
          },
          {
            "lineNumber": 233,
            "text": "        packer_kwargs = {}"
          },
          {
            "lineNumber": 234,
            "text": "    packer = packer_class("
          },
          {
            "lineNumber": 235,
            "text": "        base_blendfile,"
          },
          {
            "lineNumber": 236,
            "text": "        project,"
          },
          {
            "lineNumber": 237,
            "text": "        target,"
          },
          {
            "lineNumber": 238,
            "text": "        compress=True,"
          },
          {
            "lineNumber": 239,
            "text": "        relative_only=relative_only,"
          },
          {
            "lineNumber": 240,
            "text": "        **packer_kwargs,"
          },
          {
            "lineNumber": 241,
            "text": "    )"
          },
          {
            "lineNumber": 242,
            "text": "    if exclusion_filter:"
          },
          {
            "lineNumber": 243,
            "text": "        filter_parts = exclusion_filter.strip().split(\" \")"
          },
          {
            "lineNumber": 244,
            "text": "        packer.exclude(*filter_parts)"
          },
          {
            "lineNumber": 245
          },
          {
            "lineNumber": 246,
            "text": "    packthread = PackThread(packer=packer)"
          },
          {
            "lineNumber": 247,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 248,
            "text": "        _running_packthread = packthread"
          },
          {
            "lineNumber": 249
          },
          {
            "lineNumber": 250,
            "text": "    packthread.start()"
          },
          {
            "isSignature": true,
            "lineNumber": 251,
            "text": "    return packthread"
          },
          {
            "lineNumber": 252
          },
          {
            "lineNumber": 253
          },
          {
            "isSignature": true,
            "lineNumber": 254,
            "text": "def abort() -\u003e None:"
          },
          {
            "lineNumber": 255,
            "text": "    \"\"\"Abort a running copy() call."
          },
          {
            "lineNumber": 256
          },
          {
            "lineNumber": 257,
            "text": "    No-op when there is no running copy(). Can be called from any thread."
          },
          {
            "lineNumber": 258,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 264
          },
          "startPosition": {
            "column": 27,
            "line": 193
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.3254512548446655
    },
    {
      "codeBlock": {
        "contents": "# SPDX-License-Identifier: GPL-3.0-or-later\n# \u003cpep8 compliant\u003e\n\nfrom pathlib import Path\nfrom typing import Callable\nimport dataclasses\n\nfrom .bat.submodules import bpathlib\n\n\ndef for_blendfile(blendfile: Path, strategy: str) -\u003e Path:\n    \"\"\"Return what is considered to be the project directory containing the given file.\n\n    If none can be found, the directory containing the current blend file is returned.\n    If the current blend file has no path (because it was not saved), a ValueError is raised.\n\n    :param blendfile: the path of the blend file for which to find the project.\n    :param strategy: the name of the finder to use, see &#96;finders&#96;.\n    \"\"\"\n    if blendfile.is_dir():\n        msg = f\"{blendfile} is not a blend file, cannot find project directory\"\n        raise ValueError(msg)\n\n    try:\n        finder_info = finders[strategy]\n    except KeyError:\n        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\"\n        raise ValueError(msg) from None\n\n    return finder_info.finder(blendfile)\n\n\ndef _finder_blender_project(blendfile: Path) -\u003e Path:\n    return _search_path_marker(blendfile, \".blender_project\")",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# SPDX-License-Identifier: GPL-3.0-or-later"
          },
          {
            "lineNumber": 2,
            "text": "# \u003cpep8 compliant\u003e"
          },
          {
            "lineNumber": 3
          },
          {
            "lineNumber": 4,
            "text": "from pathlib import Path"
          },
          {
            "lineNumber": 5,
            "text": "from typing import Callable"
          },
          {
            "lineNumber": 6,
            "text": "import dataclasses"
          },
          {
            "lineNumber": 7
          },
          {
            "lineNumber": 8,
            "text": "from .bat.submodules import bpathlib"
          },
          {
            "lineNumber": 9
          },
          {
            "lineNumber": 10
          },
          {
            "isSignature": true,
            "lineNumber": 11,
            "text": "def for_blendfile(blendfile: Path, strategy: str) -\u003e Path:"
          },
          {
            "lineNumber": 12,
            "text": "    \"\"\"Return what is considered to be the project directory containing the given file."
          },
          {
            "lineNumber": 13
          },
          {
            "lineNumber": 14,
            "text": "    If none can be found, the directory containing the current blend file is returned."
          },
          {
            "lineNumber": 15,
            "text": "    If the current blend file has no path (because it was not saved), a ValueError is raised."
          },
          {
            "lineNumber": 16
          },
          {
            "lineNumber": 17,
            "text": "    :param blendfile: the path of the blend file for which to find the project."
          },
          {
            "lineNumber": 18,
            "text": "    :param strategy: the name of the finder to use, see &#96;finders&#96;."
          },
          {
            "lineNumber": 19,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 20,
            "text": "    if blendfile.is_dir():"
          },
          {
            "lineNumber": 21,
            "text": "        msg = f\"{blendfile} is not a blend file, cannot find project directory\""
          },
          {
            "lineNumber": 22,
            "text": "        raise ValueError(msg)"
          },
          {
            "lineNumber": 23
          },
          {
            "lineNumber": 24,
            "text": "    try:"
          },
          {
            "lineNumber": 25,
            "text": "        finder_info = finders[strategy]"
          },
          {
            "lineNumber": 26,
            "text": "    except KeyError:"
          },
          {
            "lineNumber": 27,
            "text": "        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\""
          },
          {
            "lineNumber": 28,
            "text": "        raise ValueError(msg) from None"
          },
          {
            "lineNumber": 29
          },
          {
            "isSignature": true,
            "lineNumber": 30,
            "text": "    return finder_info.finder(blendfile)"
          },
          {
            "lineNumber": 31
          },
          {
            "lineNumber": 32
          },
          {
            "isSignature": true,
            "lineNumber": 33,
            "text": "def _finder_blender_project(blendfile: Path) -\u003e Path:"
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "    return _search_path_marker(blendfile, \".blender_project\")"
          }
        ],
        "range": {
          "endPosition": {
            "column": 61,
            "line": 33
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.32505568861961365
    },
    {
      "codeBlock": {
        "contents": "# Mypy doesn't understand that submodules.pack.Packer exists.\nclass Packer(submodules.pack.Packer):  # type: ignore\n    \"\"\"Creates BAT Packs on a Shaman server.\"\"\"\n\n    def __init__(\n        self,\n        blendfile: Path,\n        project_root: Path,\n        target: str,\n        *,\n        api_client: _ApiClient,\n        checkout_path: str,\n        **kwargs: dict[Any, Any],\n    ) -\u003e None:\n        \"\"\"Constructor\n\n        :param target: mock target root directory to construct project-relative paths.\n        \"\"\"\n        super().__init__(blendfile, project_root, target, **kwargs)\n        self.checkout_path = checkout_path\n        self.api_client = api_client\n        self.shaman_transferrer: Optional[Transferrer] = None\n\n    # Mypy doesn't understand that submodules.transfer.FileTransferer exists.\n    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore\n        self.shaman_transferrer = Transferrer(\n            self.api_client, self.project, self.checkout_path\n        )\n        return self.shaman_transferrer\n\n    def _make_target_path(self, target: str) -\u003e PurePath:\n        return _root_path()\n\n    @property\n    def output_path(self) -\u003e PurePath:\n        \"\"\"The path of the packed blend file in the target directory.\"\"\"\n        assert self._output_path is not None\n\n        rel_output = self._output_path.relative_to(self._target_path)\n        out_path: PurePath = self.actual_checkout_path / rel_output\n        return out_path\n\n    @property\n    def actual_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"The actual Shaman checkout path.\n\n        Only valid after packing is complete. Shaman ensures that the checkout\n        is unique, and thus the actual path can be different than the requested\n        one.\n        \"\"\"\n        assert self.shaman_transferrer is not None\n        return PurePosixPath(self.shaman_transferrer.checkout_path)\n\n    def execute(self):\n        try:\n            super().execute()",
        "detailedLines": [
          {
            "lineNumber": 37,
            "text": "# Mypy doesn't understand that submodules.pack.Packer exists."
          },
          {
            "isSignature": true,
            "lineNumber": 38,
            "text": "class Packer(submodules.pack.Packer):  # type: ignore"
          },
          {
            "lineNumber": 39,
            "text": "    \"\"\"Creates BAT Packs on a Shaman server.\"\"\""
          },
          {
            "lineNumber": 40
          },
          {
            "lineNumber": 41,
            "text": "    def __init__("
          },
          {
            "lineNumber": 42,
            "text": "        self,"
          },
          {
            "lineNumber": 43,
            "text": "        blendfile: Path,"
          },
          {
            "lineNumber": 44,
            "text": "        project_root: Path,"
          },
          {
            "lineNumber": 45,
            "text": "        target: str,"
          },
          {
            "lineNumber": 46,
            "text": "        *,"
          },
          {
            "lineNumber": 47,
            "text": "        api_client: _ApiClient,"
          },
          {
            "lineNumber": 48,
            "text": "        checkout_path: str,"
          },
          {
            "lineNumber": 49,
            "text": "        **kwargs: dict[Any, Any],"
          },
          {
            "lineNumber": 50,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 51,
            "text": "        \"\"\"Constructor"
          },
          {
            "lineNumber": 52
          },
          {
            "lineNumber": 53,
            "text": "        :param target: mock target root directory to construct project-relative paths."
          },
          {
            "lineNumber": 54,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 55,
            "text": "        super().__init__(blendfile, project_root, target, **kwargs)"
          },
          {
            "lineNumber": 56,
            "text": "        self.checkout_path = checkout_path"
          },
          {
            "lineNumber": 57,
            "text": "        self.api_client = api_client"
          },
          {
            "lineNumber": 58,
            "text": "        self.shaman_transferrer: Optional[Transferrer] = None"
          },
          {
            "lineNumber": 59
          },
          {
            "lineNumber": 60,
            "text": "    # Mypy doesn't understand that submodules.transfer.FileTransferer exists."
          },
          {
            "lineNumber": 61,
            "text": "    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore"
          },
          {
            "lineNumber": 62,
            "text": "        self.shaman_transferrer = Transferrer("
          },
          {
            "lineNumber": 63,
            "text": "            self.api_client, self.project, self.checkout_path"
          },
          {
            "lineNumber": 64,
            "text": "        )"
          },
          {
            "lineNumber": 65,
            "text": "        return self.shaman_transferrer"
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "    def _make_target_path(self, target: str) -\u003e PurePath:"
          },
          {
            "lineNumber": 68,
            "text": "        return _root_path()"
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "    @property"
          },
          {
            "lineNumber": 71,
            "text": "    def output_path(self) -\u003e PurePath:"
          },
          {
            "lineNumber": 72,
            "text": "        \"\"\"The path of the packed blend file in the target directory.\"\"\""
          },
          {
            "lineNumber": 73,
            "text": "        assert self._output_path is not None"
          },
          {
            "lineNumber": 74
          },
          {
            "lineNumber": 75,
            "text": "        rel_output = self._output_path.relative_to(self._target_path)"
          },
          {
            "lineNumber": 76,
            "text": "        out_path: PurePath = self.actual_checkout_path / rel_output"
          },
          {
            "lineNumber": 77,
            "text": "        return out_path"
          },
          {
            "lineNumber": 78
          },
          {
            "lineNumber": 79,
            "text": "    @property"
          },
          {
            "lineNumber": 80,
            "text": "    def actual_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 81,
            "text": "        \"\"\"The actual Shaman checkout path."
          },
          {
            "lineNumber": 82
          },
          {
            "lineNumber": 83,
            "text": "        Only valid after packing is complete. Shaman ensures that the checkout"
          },
          {
            "lineNumber": 84,
            "text": "        is unique, and thus the actual path can be different than the requested"
          },
          {
            "lineNumber": 85,
            "text": "        one."
          },
          {
            "lineNumber": 86,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 87,
            "text": "        assert self.shaman_transferrer is not None"
          },
          {
            "lineNumber": 88,
            "text": "        return PurePosixPath(self.shaman_transferrer.checkout_path)"
          },
          {
            "lineNumber": 89
          },
          {
            "lineNumber": 90,
            "text": "    def execute(self):"
          },
          {
            "lineNumber": 91,
            "text": "        try:"
          },
          {
            "lineNumber": 92,
            "text": "            super().execute()"
          }
        ],
        "range": {
          "endPosition": {
            "line": 95
          },
          "startPosition": {
            "column": 72,
            "line": 33
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {}
      },
      "score": 0.31449800729751587
    },
    {
      "codeBlock": {
        "contents": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:\n    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\"\n\n    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent\n\n    directory = blendfile_dir\n    while True:\n        marker: Path = directory / marker_path\n        if marker.exists():\n            return directory\n\n        parent = directory.parent\n        if directory == parent:\n            # If a directory is its own parent, we're at the root and cannot go\n            # up further.\n            break\n        directory = parent\n\n    # Could not find the marker, so use the directory containing the blend file.\n    return blendfile_dir\n\n\nFinder = Callable[[Path], Path]\n\n\n@dataclasses.dataclass\nclass FinderInfo:\n    label: str",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 45,
            "text": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:"
          },
          {
            "lineNumber": 46,
            "text": "    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\""
          },
          {
            "lineNumber": 47
          },
          {
            "lineNumber": 48,
            "text": "    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent"
          },
          {
            "lineNumber": 49
          },
          {
            "lineNumber": 50,
            "text": "    directory = blendfile_dir"
          },
          {
            "lineNumber": 51,
            "text": "    while True:"
          },
          {
            "lineNumber": 52,
            "text": "        marker: Path = directory / marker_path"
          },
          {
            "lineNumber": 53,
            "text": "        if marker.exists():"
          },
          {
            "lineNumber": 54,
            "text": "            return directory"
          },
          {
            "lineNumber": 55
          },
          {
            "lineNumber": 56,
            "text": "        parent = directory.parent"
          },
          {
            "lineNumber": 57,
            "text": "        if directory == parent:"
          },
          {
            "lineNumber": 58,
            "text": "            # If a directory is its own parent, we're at the root and cannot go"
          },
          {
            "lineNumber": 59,
            "text": "            # up further."
          },
          {
            "lineNumber": 60,
            "text": "            break"
          },
          {
            "lineNumber": 61,
            "text": "        directory = parent"
          },
          {
            "lineNumber": 62
          },
          {
            "lineNumber": 63,
            "text": "    # Could not find the marker, so use the directory containing the blend file."
          },
          {
            "isSignature": true,
            "lineNumber": 64,
            "text": "    return blendfile_dir"
          },
          {
            "lineNumber": 65
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "Finder = Callable[[Path], Path]"
          },
          {
            "lineNumber": 68
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "@dataclasses.dataclass"
          },
          {
            "isSignature": true,
            "lineNumber": 71,
            "text": "class FinderInfo:"
          },
          {
            "lineNumber": 72,
            "text": "    label: str"
          }
        ],
        "range": {
          "endPosition": {
            "line": 73
          },
          "startPosition": {
            "column": 49,
            "line": 41
          }
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.3047080934047699
    },
    {
      "codeBlock": {
        "contents": "class FlamencoPreferences(bpy.types.AddonPreferences):\n\n\n    def project_root(self) -\u003e Path:\n        \"\"\"Use the configured project finder to find the project root directory.\"\"\"\n\n        if not self.project_finder:\n            # Just a sanity fallback for missing preferences. It should be\n            # covered by the 'default=...' of the property, but just to be sure.\n            self.project_finder = \"BLENDER_PROJECT\"\n\n        # It is assumed that the blendfile is saved.\n        blendfile = Path(bpy.data.filepath)\n        return projects.for_blendfile(blendfile, self.project_finder)\n\n    def _job_storage(self) -\u003e str:\n        info = manager_info.load_cached()\n        if not info:\n            return \"Unknown, refresh first.\"\n        return str(info.shared_storage.location)\n\n\ndef get(context: bpy.types.Context) -\u003e FlamencoPreferences:\n    \"\"\"Return the add-on preferences.\"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 51,
            "text": "class FlamencoPreferences(bpy.types.AddonPreferences):"
          },
          {
            "lineNumber": 119
          },
          {
            "lineNumber": 120
          },
          {
            "lineNumber": 121,
            "text": "    def project_root(self) -\u003e Path:"
          },
          {
            "lineNumber": 122,
            "text": "        \"\"\"Use the configured project finder to find the project root directory.\"\"\""
          },
          {
            "lineNumber": 123
          },
          {
            "lineNumber": 124,
            "text": "        if not self.project_finder:"
          },
          {
            "lineNumber": 125,
            "text": "            # Just a sanity fallback for missing preferences. It should be"
          },
          {
            "lineNumber": 126,
            "text": "            # covered by the 'default=...' of the property, but just to be sure."
          },
          {
            "lineNumber": 127,
            "text": "            self.project_finder = \"BLENDER_PROJECT\""
          },
          {
            "lineNumber": 128
          },
          {
            "lineNumber": 129,
            "text": "        # It is assumed that the blendfile is saved."
          },
          {
            "lineNumber": 130,
            "text": "        blendfile = Path(bpy.data.filepath)"
          },
          {
            "lineNumber": 131,
            "text": "        return projects.for_blendfile(blendfile, self.project_finder)"
          },
          {
            "lineNumber": 132
          },
          {
            "lineNumber": 133,
            "text": "    def _job_storage(self) -\u003e str:"
          },
          {
            "lineNumber": 134,
            "text": "        info = manager_info.load_cached()"
          },
          {
            "lineNumber": 135,
            "text": "        if not info:"
          },
          {
            "lineNumber": 136,
            "text": "            return \"Unknown, refresh first.\""
          },
          {
            "isSignature": true,
            "lineNumber": 137,
            "text": "        return str(info.shared_storage.location)"
          },
          {
            "lineNumber": 138
          },
          {
            "lineNumber": 139
          },
          {
            "isSignature": true,
            "lineNumber": 140,
            "text": "def get(context: bpy.types.Context) -\u003e FlamencoPreferences:"
          },
          {
            "lineNumber": 141,
            "text": "    \"\"\"Return the add-on preferences.\"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 145
          },
          "startPosition": {
            "line": 50
          }
        },
        "relativeWorkspacePath": ".\\preferences.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 52
              },
              "startPosition": {
                "column": 1,
                "line": 51
              }
            }
          ]
        }
      },
      "score": 0.30076175928115845
    },
    {
      "codeBlock": {
        "contents": "class JobTypePropertyGroup:\n\n\n    @staticmethod\n    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:\n        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;.\n\n        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;.\n        If &#96;filepath&#96; is None, uses bpy.data.filepath instead.\n\n        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))\n        \"to/some\"\n\n        Always returns a relative path:\n        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))\n        \"path\\\\to\\\\some\"\n        \"\"\"\n\n        if n \u003c= 0:\n            return Path()\n\n        if filepath is None:\n            filepath = Path(bpy.data.filepath)\n        elif isinstance(filepath, str):\n            filepath = Path(bpy.path.abspath(filepath))\n\n        dirpath = bpathlib.make_absolute(filepath).parent\n        if n \u003e= len(dirpath.parts):\n            all_parts = dirpath.relative_to(dirpath.anchor)\n            return Path(all_parts)\n\n        subset = Path(*dirpath.parts[-n:])\n        return subset\n\n    @staticmethod\n    def abspath(filepath: Union[str, Path]) -\u003e Path:\n        \"\"\"Return the filepath as absolute path.\"\"\"\n\n        # This changes blendfile-relative paths to absolute.\n        # It does not resolve &#96;..&#96; entries, though.\n        abs_unclean = Path(bpy.path.abspath(str(filepath)))\n        abs_clean: Path = bpathlib.make_absolute(abs_unclean)\n        return abs_clean\n\n\n# Mapping from AvailableJobType.setting.type to a callable that converts a value",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 56,
            "text": "class JobTypePropertyGroup:"
          },
          {
            "lineNumber": 173
          },
          {
            "lineNumber": 174
          },
          {
            "lineNumber": 175,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 176,
            "text": "    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:"
          },
          {
            "lineNumber": 177,
            "text": "        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;."
          },
          {
            "lineNumber": 178
          },
          {
            "lineNumber": 179,
            "text": "        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;."
          },
          {
            "lineNumber": 180,
            "text": "        If &#96;filepath&#96; is None, uses bpy.data.filepath instead."
          },
          {
            "lineNumber": 181
          },
          {
            "lineNumber": 182,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))"
          },
          {
            "lineNumber": 183,
            "text": "        \"to/some\""
          },
          {
            "lineNumber": 184
          },
          {
            "lineNumber": 185,
            "text": "        Always returns a relative path:"
          },
          {
            "lineNumber": 186,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))"
          },
          {
            "lineNumber": 187,
            "text": "        \"path\\\\to\\\\some\""
          },
          {
            "lineNumber": 188,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 189
          },
          {
            "lineNumber": 190,
            "text": "        if n \u003c= 0:"
          },
          {
            "lineNumber": 191,
            "text": "            return Path()"
          },
          {
            "lineNumber": 192
          },
          {
            "lineNumber": 193,
            "text": "        if filepath is None:"
          },
          {
            "lineNumber": 194,
            "text": "            filepath = Path(bpy.data.filepath)"
          },
          {
            "lineNumber": 195,
            "text": "        elif isinstance(filepath, str):"
          },
          {
            "lineNumber": 196,
            "text": "            filepath = Path(bpy.path.abspath(filepath))"
          },
          {
            "lineNumber": 197
          },
          {
            "lineNumber": 198,
            "text": "        dirpath = bpathlib.make_absolute(filepath).parent"
          },
          {
            "lineNumber": 199,
            "text": "        if n \u003e= len(dirpath.parts):"
          },
          {
            "lineNumber": 200,
            "text": "            all_parts = dirpath.relative_to(dirpath.anchor)"
          },
          {
            "lineNumber": 201,
            "text": "            return Path(all_parts)"
          },
          {
            "lineNumber": 202
          },
          {
            "lineNumber": 203,
            "text": "        subset = Path(*dirpath.parts[-n:])"
          },
          {
            "lineNumber": 204,
            "text": "        return subset"
          },
          {
            "lineNumber": 205
          },
          {
            "lineNumber": 206,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 207,
            "text": "    def abspath(filepath: Union[str, Path]) -\u003e Path:"
          },
          {
            "lineNumber": 208,
            "text": "        \"\"\"Return the filepath as absolute path.\"\"\""
          },
          {
            "lineNumber": 209
          },
          {
            "lineNumber": 210,
            "text": "        # This changes blendfile-relative paths to absolute."
          },
          {
            "lineNumber": 211,
            "text": "        # It does not resolve &#96;..&#96; entries, though."
          },
          {
            "lineNumber": 212,
            "text": "        abs_unclean = Path(bpy.path.abspath(str(filepath)))"
          },
          {
            "lineNumber": 213,
            "text": "        abs_clean: Path = bpathlib.make_absolute(abs_unclean)"
          },
          {
            "isSignature": true,
            "lineNumber": 214,
            "text": "        return abs_clean"
          },
          {
            "lineNumber": 215
          },
          {
            "lineNumber": 216
          },
          {
            "lineNumber": 217,
            "text": "# Mapping from AvailableJobType.setting.type to a callable that converts a value"
          }
        ],
        "range": {
          "endPosition": {
            "column": 80,
            "line": 216
          },
          "startPosition": {
            "line": 55
          }
        },
        "relativeWorkspacePath": ".\\job_types_propgroup.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 57
              },
              "startPosition": {
                "column": 1,
                "line": 56
              }
            }
          ]
        }
      },
      "score": 0.29825305938720703
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": true,
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "outside_project|_outside_project|remap|abspath|relpath"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "outside_project|_outside_project|remap|abspath|relpath",
  "topFiles": [
    {
      "matchCount": 1,
      "uri": ".\\operators.py"
    },
    {
      "matchCount": 2,
      "uri": ".\\.specstory\\history\\2026-01-28_20-03Z-flamenco-addon-file-sending.md"
    },
    {
      "matchCount": 4,
      "uri": ".\\job_types_propgroup.py"
    },
    {
      "matchCount": 3,
      "uri": ".\\bat\\shaman.py"
    }
  ],
  "totalFiles": 4,
  "totalMatches": 10
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

Checking how blend files are handled and how to convert relative paths to absolute:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How to convert relative paths to absolute paths in a blend file? How does bpy.path.abspath work with blend file paths?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class JobTypePropertyGroup:\n\n\n    @staticmethod\n    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:\n        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;.\n\n        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;.\n        If &#96;filepath&#96; is None, uses bpy.data.filepath instead.\n\n        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))\n        \"to/some\"\n\n        Always returns a relative path:\n        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))\n        \"path\\\\to\\\\some\"\n        \"\"\"\n\n        if n \u003c= 0:\n            return Path()\n\n        if filepath is None:\n            filepath = Path(bpy.data.filepath)\n        elif isinstance(filepath, str):\n            filepath = Path(bpy.path.abspath(filepath))\n\n        dirpath = bpathlib.make_absolute(filepath).parent\n        if n \u003e= len(dirpath.parts):\n            all_parts = dirpath.relative_to(dirpath.anchor)\n            return Path(all_parts)\n\n        subset = Path(*dirpath.parts[-n:])\n        return subset\n\n    @staticmethod\n    def abspath(filepath: Union[str, Path]) -\u003e Path:\n        \"\"\"Return the filepath as absolute path.\"\"\"\n\n        # This changes blendfile-relative paths to absolute.\n        # It does not resolve &#96;..&#96; entries, though.\n        abs_unclean = Path(bpy.path.abspath(str(filepath)))\n        abs_clean: Path = bpathlib.make_absolute(abs_unclean)\n        return abs_clean\n\n\n# Mapping from AvailableJobType.setting.type to a callable that converts a value",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 56,
            "text": "class JobTypePropertyGroup:"
          },
          {
            "lineNumber": 173
          },
          {
            "lineNumber": 174
          },
          {
            "lineNumber": 175,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 176,
            "text": "    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:"
          },
          {
            "lineNumber": 177,
            "text": "        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;."
          },
          {
            "lineNumber": 178
          },
          {
            "lineNumber": 179,
            "text": "        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;."
          },
          {
            "lineNumber": 180,
            "text": "        If &#96;filepath&#96; is None, uses bpy.data.filepath instead."
          },
          {
            "lineNumber": 181
          },
          {
            "lineNumber": 182,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))"
          },
          {
            "lineNumber": 183,
            "text": "        \"to/some\""
          },
          {
            "lineNumber": 184
          },
          {
            "lineNumber": 185,
            "text": "        Always returns a relative path:"
          },
          {
            "lineNumber": 186,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))"
          },
          {
            "lineNumber": 187,
            "text": "        \"path\\\\to\\\\some\""
          },
          {
            "lineNumber": 188,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 189
          },
          {
            "lineNumber": 190,
            "text": "        if n \u003c= 0:"
          },
          {
            "lineNumber": 191,
            "text": "            return Path()"
          },
          {
            "lineNumber": 192
          },
          {
            "lineNumber": 193,
            "text": "        if filepath is None:"
          },
          {
            "lineNumber": 194,
            "text": "            filepath = Path(bpy.data.filepath)"
          },
          {
            "lineNumber": 195,
            "text": "        elif isinstance(filepath, str):"
          },
          {
            "lineNumber": 196,
            "text": "            filepath = Path(bpy.path.abspath(filepath))"
          },
          {
            "lineNumber": 197
          },
          {
            "lineNumber": 198,
            "text": "        dirpath = bpathlib.make_absolute(filepath).parent"
          },
          {
            "lineNumber": 199,
            "text": "        if n \u003e= len(dirpath.parts):"
          },
          {
            "lineNumber": 200,
            "text": "            all_parts = dirpath.relative_to(dirpath.anchor)"
          },
          {
            "lineNumber": 201,
            "text": "            return Path(all_parts)"
          },
          {
            "lineNumber": 202
          },
          {
            "lineNumber": 203,
            "text": "        subset = Path(*dirpath.parts[-n:])"
          },
          {
            "lineNumber": 204,
            "text": "        return subset"
          },
          {
            "lineNumber": 205
          },
          {
            "lineNumber": 206,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 207,
            "text": "    def abspath(filepath: Union[str, Path]) -\u003e Path:"
          },
          {
            "lineNumber": 208,
            "text": "        \"\"\"Return the filepath as absolute path.\"\"\""
          },
          {
            "lineNumber": 209
          },
          {
            "lineNumber": 210,
            "text": "        # This changes blendfile-relative paths to absolute."
          },
          {
            "lineNumber": 211,
            "text": "        # It does not resolve &#96;..&#96; entries, though."
          },
          {
            "lineNumber": 212,
            "text": "        abs_unclean = Path(bpy.path.abspath(str(filepath)))"
          },
          {
            "lineNumber": 213,
            "text": "        abs_clean: Path = bpathlib.make_absolute(abs_unclean)"
          },
          {
            "isSignature": true,
            "lineNumber": 214,
            "text": "        return abs_clean"
          },
          {
            "lineNumber": 215
          },
          {
            "lineNumber": 216
          },
          {
            "lineNumber": 217,
            "text": "# Mapping from AvailableJobType.setting.type to a callable that converts a value"
          }
        ],
        "range": {
          "endPosition": {
            "column": 80,
            "line": 216
          },
          "startPosition": {
            "line": 55
          }
        },
        "relativeWorkspacePath": ".\\job_types_propgroup.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 57
              },
              "startPosition": {
                "column": 1,
                "line": 56
              }
            }
          ]
        }
      },
      "score": 0.45342499017715454
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 404
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 414,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 415,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 416
          },
          {
            "lineNumber": 417,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 418,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 419,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 420,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 421
          },
          {
            "lineNumber": 422,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 423,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 424,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 427,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 428,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 429,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 430,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 431,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 432,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 433,
            "text": "        )"
          },
          {
            "lineNumber": 434,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 435
          },
          {
            "lineNumber": 436,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 437,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 441,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 442,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 443,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 444,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 445,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 446,
            "text": "        )"
          },
          {
            "lineNumber": 447
          },
          {
            "lineNumber": 448,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.4074943959712982
    },
    {
      "codeBlock": {
        "contents": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:\n    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\"\n\n    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent\n\n    directory = blendfile_dir\n    while True:\n        marker: Path = directory / marker_path\n        if marker.exists():\n            return directory\n\n        parent = directory.parent\n        if directory == parent:\n            # If a directory is its own parent, we're at the root and cannot go\n            # up further.\n            break\n        directory = parent\n\n    # Could not find the marker, so use the directory containing the blend file.\n    return blendfile_dir\n\n\nFinder = Callable[[Path], Path]\n\n\n@dataclasses.dataclass\nclass FinderInfo:\n    label: str",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 45,
            "text": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:"
          },
          {
            "lineNumber": 46,
            "text": "    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\""
          },
          {
            "lineNumber": 47
          },
          {
            "lineNumber": 48,
            "text": "    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent"
          },
          {
            "lineNumber": 49
          },
          {
            "lineNumber": 50,
            "text": "    directory = blendfile_dir"
          },
          {
            "lineNumber": 51,
            "text": "    while True:"
          },
          {
            "lineNumber": 52,
            "text": "        marker: Path = directory / marker_path"
          },
          {
            "lineNumber": 53,
            "text": "        if marker.exists():"
          },
          {
            "lineNumber": 54,
            "text": "            return directory"
          },
          {
            "lineNumber": 55
          },
          {
            "lineNumber": 56,
            "text": "        parent = directory.parent"
          },
          {
            "lineNumber": 57,
            "text": "        if directory == parent:"
          },
          {
            "lineNumber": 58,
            "text": "            # If a directory is its own parent, we're at the root and cannot go"
          },
          {
            "lineNumber": 59,
            "text": "            # up further."
          },
          {
            "lineNumber": 60,
            "text": "            break"
          },
          {
            "lineNumber": 61,
            "text": "        directory = parent"
          },
          {
            "lineNumber": 62
          },
          {
            "lineNumber": 63,
            "text": "    # Could not find the marker, so use the directory containing the blend file."
          },
          {
            "isSignature": true,
            "lineNumber": 64,
            "text": "    return blendfile_dir"
          },
          {
            "lineNumber": 65
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "Finder = Callable[[Path], Path]"
          },
          {
            "lineNumber": 68
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "@dataclasses.dataclass"
          },
          {
            "isSignature": true,
            "lineNumber": 71,
            "text": "class FinderInfo:"
          },
          {
            "lineNumber": 72,
            "text": "    label: str"
          }
        ],
        "range": {
          "endPosition": {
            "line": 73
          },
          "startPosition": {
            "column": 49,
            "line": 41
          }
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.37498903274536133
    },
    {
      "codeBlock": {
        "contents": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:\n    \"\"\"Check whether current blend file is inside the storage path.\n\n    :return: True when the current blend file is inside the Flamenco job storage\n        directory already. In this case it won't be BAT-packed, as it's assumed\n        the job storage dir is accessible by the workers already.\n    \"\"\"\n\n    blendfile = bpathlib.make_absolute(blendfile)\n\n    info = manager_info.load_cached()\n    if not info:\n        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")\n\n    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))\n\n    log.info(\"Checking whether the file is already inside the job storage\")\n    log.info(\"    file   : %s\", blendfile)\n    log.info(\"    storage: %s\", job_storage)\n\n    try:\n        blendfile.relative_to(job_storage)\n    except ValueError:\n        return False\n    return True",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 134,
            "text": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 135,
            "text": "    \"\"\"Check whether current blend file is inside the storage path."
          },
          {
            "lineNumber": 136
          },
          {
            "lineNumber": 137,
            "text": "    :return: True when the current blend file is inside the Flamenco job storage"
          },
          {
            "lineNumber": 138,
            "text": "        directory already. In this case it won't be BAT-packed, as it's assumed"
          },
          {
            "lineNumber": 139,
            "text": "        the job storage dir is accessible by the workers already."
          },
          {
            "lineNumber": 140,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 141
          },
          {
            "lineNumber": 142,
            "text": "    blendfile = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 143
          },
          {
            "lineNumber": 144,
            "text": "    info = manager_info.load_cached()"
          },
          {
            "lineNumber": 145,
            "text": "    if not info:"
          },
          {
            "lineNumber": 146,
            "text": "        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")"
          },
          {
            "lineNumber": 147
          },
          {
            "lineNumber": 148,
            "text": "    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))"
          },
          {
            "lineNumber": 149
          },
          {
            "lineNumber": 150,
            "text": "    log.info(\"Checking whether the file is already inside the job storage\")"
          },
          {
            "lineNumber": 151,
            "text": "    log.info(\"    file   : %s\", blendfile)"
          },
          {
            "lineNumber": 152,
            "text": "    log.info(\"    storage: %s\", job_storage)"
          },
          {
            "lineNumber": 153
          },
          {
            "lineNumber": 154,
            "text": "    try:"
          },
          {
            "lineNumber": 155,
            "text": "        blendfile.relative_to(job_storage)"
          },
          {
            "lineNumber": 156,
            "text": "    except ValueError:"
          },
          {
            "lineNumber": 157,
            "text": "        return False"
          },
          {
            "isSignature": true,
            "lineNumber": 158,
            "text": "    return True"
          }
        ],
        "range": {
          "endPosition": {
            "column": 15,
            "line": 157
          },
          "startPosition": {
            "column": 42,
            "line": 130
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.3166860342025757
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID.\n\n        Note that this may not be the actually used checkout ID, as that will be\n        made unique to this job by Flamenco Manager. That will be stored in\n        self.actual_shaman_checkout_path after the Shaman checkout is actually\n        done.\n        \"\"\"\n        assert self.job is not None\n\n        # TODO: get project name from preferences/GUI and insert that here too.\n        return PurePosixPath(f\"{self.job.name}\")\n\n    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:\n        \"\"\"Use the Manager's Shaman API to submit the BAT pack.\n\n        :return: the filesystem path of the blend file, for in the render job definition.\n        \"\"\"\n        from .bat import (\n            interface as bat_interface,\n            shaman as bat_shaman,\n        )\n\n        assert self.job is not None\n        self.log.info(\"Sending BAT pack to Shaman\")\n\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=\"/\",  # Target directory irrelevant for Shaman transfers.\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n            packer_class=bat_shaman.Packer,\n            packer_kwargs=dict(\n                api_client=self.get_api_client(context),\n                checkout_path=self._shaman_checkout_path(),\n            ),\n        )\n\n        # We cannot assume the blendfile location is known until the Shaman\n        # checkout has actually been created.\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 448
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID."
          },
          {
            "lineNumber": 452
          },
          {
            "lineNumber": 453,
            "text": "        Note that this may not be the actually used checkout ID, as that will be"
          },
          {
            "lineNumber": 454,
            "text": "        made unique to this job by Flamenco Manager. That will be stored in"
          },
          {
            "lineNumber": 455,
            "text": "        self.actual_shaman_checkout_path after the Shaman checkout is actually"
          },
          {
            "lineNumber": 456,
            "text": "        done."
          },
          {
            "lineNumber": 457,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 458,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 459
          },
          {
            "lineNumber": 460,
            "text": "        # TODO: get project name from preferences/GUI and insert that here too."
          },
          {
            "lineNumber": 461,
            "text": "        return PurePosixPath(f\"{self.job.name}\")"
          },
          {
            "lineNumber": 462
          },
          {
            "lineNumber": 463,
            "text": "    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:"
          },
          {
            "lineNumber": 464,
            "text": "        \"\"\"Use the Manager's Shaman API to submit the BAT pack."
          },
          {
            "lineNumber": 465
          },
          {
            "lineNumber": 466,
            "text": "        :return: the filesystem path of the blend file, for in the render job definition."
          },
          {
            "lineNumber": 467,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 468,
            "text": "        from .bat import ("
          },
          {
            "lineNumber": 469,
            "text": "            interface as bat_interface,"
          },
          {
            "lineNumber": 470,
            "text": "            shaman as bat_shaman,"
          },
          {
            "lineNumber": 471,
            "text": "        )"
          },
          {
            "lineNumber": 472
          },
          {
            "lineNumber": 473,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 474,
            "text": "        self.log.info(\"Sending BAT pack to Shaman\")"
          },
          {
            "lineNumber": 475
          },
          {
            "lineNumber": 476,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 477,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 478
          },
          {
            "lineNumber": 479,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 480,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 481,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 482,
            "text": "            target=\"/\",  # Target directory irrelevant for Shaman transfers."
          },
          {
            "lineNumber": 483,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 484,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 485,
            "text": "            packer_class=bat_shaman.Packer,"
          },
          {
            "lineNumber": 486,
            "text": "            packer_kwargs=dict("
          },
          {
            "lineNumber": 487,
            "text": "                api_client=self.get_api_client(context),"
          },
          {
            "lineNumber": 488,
            "text": "                checkout_path=self._shaman_checkout_path(),"
          },
          {
            "lineNumber": 489,
            "text": "            ),"
          },
          {
            "lineNumber": 490,
            "text": "        )"
          },
          {
            "lineNumber": 491
          },
          {
            "lineNumber": 492,
            "text": "        # We cannot assume the blendfile location is known until the Shaman"
          },
          {
            "lineNumber": 493,
            "text": "        # checkout has actually been created."
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.30123838782310486
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        from .bat import interface as bat_interface\n\n        if isinstance(msg, bat_interface.MsgDone):\n            if self.blendfile_on_farm is None:\n                # Adjust the blendfile to match the Shaman checkout path. Shaman\n                # may have checked out at a different location than we\n                # requested.\n                #\n                # Manager automatically creates a variable \"jobs\" that will\n                # resolve to the job storage directory.\n                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path\n\n            self.actual_shaman_checkout_path = msg.actual_checkout_path\n            self._submit_job(context)\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgException):\n            self.log.error(\"Error performing BAT pack: %s\", msg.ex)\n            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)\n\n            # This was an exception caught at the top level of the thread, so\n            # the packing thread itself has stopped.\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgSetWMAttribute):\n            wm = context.window_manager\n            setattr(wm, msg.attribute_name, msg.value)\n\n        return {\"RUNNING_MODAL\"}\n\n    def _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n        self.temp_blendfile = None\n\n        # The blend file is contained in the job storage path, no need to\n        # copy anything.\n        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)\n\n        # No Shaman is involved when using the file directly.\n        self.actual_shaman_checkout_path = None\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 493
          },
          {
            "lineNumber": 494
          },
          {
            "lineNumber": 495,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 496,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 497
          },
          {
            "lineNumber": 498,
            "text": "        if isinstance(msg, bat_interface.MsgDone):"
          },
          {
            "lineNumber": 499,
            "text": "            if self.blendfile_on_farm is None:"
          },
          {
            "lineNumber": 500,
            "text": "                # Adjust the blendfile to match the Shaman checkout path. Shaman"
          },
          {
            "lineNumber": 501,
            "text": "                # may have checked out at a different location than we"
          },
          {
            "lineNumber": 502,
            "text": "                # requested."
          },
          {
            "lineNumber": 503,
            "text": "                #"
          },
          {
            "lineNumber": 504,
            "text": "                # Manager automatically creates a variable \"jobs\" that will"
          },
          {
            "lineNumber": 505,
            "text": "                # resolve to the job storage directory."
          },
          {
            "lineNumber": 506,
            "text": "                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path"
          },
          {
            "lineNumber": 507
          },
          {
            "lineNumber": 508,
            "text": "            self.actual_shaman_checkout_path = msg.actual_checkout_path"
          },
          {
            "lineNumber": 509,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 510,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 511
          },
          {
            "lineNumber": 512,
            "text": "        if isinstance(msg, bat_interface.MsgException):"
          },
          {
            "lineNumber": 513,
            "text": "            self.log.error(\"Error performing BAT pack: %s\", msg.ex)"
          },
          {
            "lineNumber": 514,
            "text": "            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)"
          },
          {
            "lineNumber": 515
          },
          {
            "lineNumber": 516,
            "text": "            # This was an exception caught at the top level of the thread, so"
          },
          {
            "lineNumber": 517,
            "text": "            # the packing thread itself has stopped."
          },
          {
            "lineNumber": 518,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 519
          },
          {
            "lineNumber": 520,
            "text": "        if isinstance(msg, bat_interface.MsgSetWMAttribute):"
          },
          {
            "lineNumber": 521,
            "text": "            wm = context.window_manager"
          },
          {
            "lineNumber": 522,
            "text": "            setattr(wm, msg.attribute_name, msg.value)"
          },
          {
            "lineNumber": 523
          },
          {
            "lineNumber": 524,
            "text": "        return {\"RUNNING_MODAL\"}"
          },
          {
            "lineNumber": 525
          },
          {
            "lineNumber": 526,
            "text": "    def _use_blendfile_directly("
          },
          {
            "lineNumber": 527,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 528,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 529,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 530,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 531,
            "text": "        self.temp_blendfile = None"
          },
          {
            "lineNumber": 532
          },
          {
            "lineNumber": 533,
            "text": "        # The blend file is contained in the job storage path, no need to"
          },
          {
            "lineNumber": 534,
            "text": "        # copy anything."
          },
          {
            "lineNumber": 535,
            "text": "        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        # No Shaman is involved when using the file directly."
          },
          {
            "lineNumber": 538,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 539
          },
          {
            "lineNumber": 540,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 541
          },
          {
            "lineNumber": 542,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 543,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.2991294860839844
    },
    {
      "codeBlock": {
        "contents": "# SPDX-License-Identifier: GPL-3.0-or-later\n# \u003cpep8 compliant\u003e\n\nfrom pathlib import Path\nfrom typing import Callable\nimport dataclasses\n\nfrom .bat.submodules import bpathlib\n\n\ndef for_blendfile(blendfile: Path, strategy: str) -\u003e Path:\n    \"\"\"Return what is considered to be the project directory containing the given file.\n\n    If none can be found, the directory containing the current blend file is returned.\n    If the current blend file has no path (because it was not saved), a ValueError is raised.\n\n    :param blendfile: the path of the blend file for which to find the project.\n    :param strategy: the name of the finder to use, see &#96;finders&#96;.\n    \"\"\"\n    if blendfile.is_dir():\n        msg = f\"{blendfile} is not a blend file, cannot find project directory\"\n        raise ValueError(msg)\n\n    try:\n        finder_info = finders[strategy]\n    except KeyError:\n        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\"\n        raise ValueError(msg) from None\n\n    return finder_info.finder(blendfile)\n\n\ndef _finder_blender_project(blendfile: Path) -\u003e Path:\n    return _search_path_marker(blendfile, \".blender_project\")",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# SPDX-License-Identifier: GPL-3.0-or-later"
          },
          {
            "lineNumber": 2,
            "text": "# \u003cpep8 compliant\u003e"
          },
          {
            "lineNumber": 3
          },
          {
            "lineNumber": 4,
            "text": "from pathlib import Path"
          },
          {
            "lineNumber": 5,
            "text": "from typing import Callable"
          },
          {
            "lineNumber": 6,
            "text": "import dataclasses"
          },
          {
            "lineNumber": 7
          },
          {
            "lineNumber": 8,
            "text": "from .bat.submodules import bpathlib"
          },
          {
            "lineNumber": 9
          },
          {
            "lineNumber": 10
          },
          {
            "isSignature": true,
            "lineNumber": 11,
            "text": "def for_blendfile(blendfile: Path, strategy: str) -\u003e Path:"
          },
          {
            "lineNumber": 12,
            "text": "    \"\"\"Return what is considered to be the project directory containing the given file."
          },
          {
            "lineNumber": 13
          },
          {
            "lineNumber": 14,
            "text": "    If none can be found, the directory containing the current blend file is returned."
          },
          {
            "lineNumber": 15,
            "text": "    If the current blend file has no path (because it was not saved), a ValueError is raised."
          },
          {
            "lineNumber": 16
          },
          {
            "lineNumber": 17,
            "text": "    :param blendfile: the path of the blend file for which to find the project."
          },
          {
            "lineNumber": 18,
            "text": "    :param strategy: the name of the finder to use, see &#96;finders&#96;."
          },
          {
            "lineNumber": 19,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 20,
            "text": "    if blendfile.is_dir():"
          },
          {
            "lineNumber": 21,
            "text": "        msg = f\"{blendfile} is not a blend file, cannot find project directory\""
          },
          {
            "lineNumber": 22,
            "text": "        raise ValueError(msg)"
          },
          {
            "lineNumber": 23
          },
          {
            "lineNumber": 24,
            "text": "    try:"
          },
          {
            "lineNumber": 25,
            "text": "        finder_info = finders[strategy]"
          },
          {
            "lineNumber": 26,
            "text": "    except KeyError:"
          },
          {
            "lineNumber": 27,
            "text": "        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\""
          },
          {
            "lineNumber": 28,
            "text": "        raise ValueError(msg) from None"
          },
          {
            "lineNumber": 29
          },
          {
            "isSignature": true,
            "lineNumber": 30,
            "text": "    return finder_info.finder(blendfile)"
          },
          {
            "lineNumber": 31
          },
          {
            "lineNumber": 32
          },
          {
            "isSignature": true,
            "lineNumber": 33,
            "text": "def _finder_blender_project(blendfile: Path) -\u003e Path:"
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "    return _search_path_marker(blendfile, \".blender_project\")"
          }
        ],
        "range": {
          "endPosition": {
            "column": 61,
            "line": 33
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.2978211045265198
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "_use_blendfile_directly|bpy\\.path\\.abspath|filepath|library"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "_use_blendfile_directly|bpy\\.path\\.abspath|filepath|library",
  "topFiles": [
    {
      "matchCount": 19,
      "uri": "operators.py"
    }
  ],
  "totalFiles": 1,
  "totalMatches": 19
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

Creating a function to convert all relative paths to absolute, then modifying the submission flow to bypass BAT. Checking how to iterate through libraries and images:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How to iterate through libraries and images in bpy.data? How to convert library filepath to absolute?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class JobTypePropertyGroup:\n\n\n    @staticmethod\n    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:\n        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;.\n\n        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;.\n        If &#96;filepath&#96; is None, uses bpy.data.filepath instead.\n\n        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))\n        \"to/some\"\n\n        Always returns a relative path:\n        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))\n        \"path\\\\to\\\\some\"\n        \"\"\"\n\n        if n \u003c= 0:\n            return Path()\n\n        if filepath is None:\n            filepath = Path(bpy.data.filepath)\n        elif isinstance(filepath, str):\n            filepath = Path(bpy.path.abspath(filepath))\n\n        dirpath = bpathlib.make_absolute(filepath).parent\n        if n \u003e= len(dirpath.parts):\n            all_parts = dirpath.relative_to(dirpath.anchor)\n            return Path(all_parts)\n\n        subset = Path(*dirpath.parts[-n:])\n        return subset\n\n    @staticmethod\n    def abspath(filepath: Union[str, Path]) -\u003e Path:\n        \"\"\"Return the filepath as absolute path.\"\"\"\n\n        # This changes blendfile-relative paths to absolute.\n        # It does not resolve &#96;..&#96; entries, though.\n        abs_unclean = Path(bpy.path.abspath(str(filepath)))\n        abs_clean: Path = bpathlib.make_absolute(abs_unclean)\n        return abs_clean\n\n\n# Mapping from AvailableJobType.setting.type to a callable that converts a value",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 56,
            "text": "class JobTypePropertyGroup:"
          },
          {
            "lineNumber": 173
          },
          {
            "lineNumber": 174
          },
          {
            "lineNumber": 175,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 176,
            "text": "    def last_n_dir_parts(n: int, filepath: Union[str, Path, None] = None) -\u003e Path:"
          },
          {
            "lineNumber": 177,
            "text": "        \"\"\"Return the last &#96;n&#96; parts of the directory of &#96;filepath&#96;."
          },
          {
            "lineNumber": 178
          },
          {
            "lineNumber": 179,
            "text": "        If &#96;n&#96; is 0, returns an empty &#96;Path()&#96;."
          },
          {
            "lineNumber": 180,
            "text": "        If &#96;filepath&#96; is None, uses bpy.data.filepath instead."
          },
          {
            "lineNumber": 181
          },
          {
            "lineNumber": 182,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(2, \"/path/to/some/file.blend\"))"
          },
          {
            "lineNumber": 183,
            "text": "        \"to/some\""
          },
          {
            "lineNumber": 184
          },
          {
            "lineNumber": 185,
            "text": "        Always returns a relative path:"
          },
          {
            "lineNumber": 186,
            "text": "        \u003e\u003e\u003e str(last_n_dir_parts(200, \"C:\\\\path\\\\to\\\\some\\\\file.blend\"))"
          },
          {
            "lineNumber": 187,
            "text": "        \"path\\\\to\\\\some\""
          },
          {
            "lineNumber": 188,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 189
          },
          {
            "lineNumber": 190,
            "text": "        if n \u003c= 0:"
          },
          {
            "lineNumber": 191,
            "text": "            return Path()"
          },
          {
            "lineNumber": 192
          },
          {
            "lineNumber": 193,
            "text": "        if filepath is None:"
          },
          {
            "lineNumber": 194,
            "text": "            filepath = Path(bpy.data.filepath)"
          },
          {
            "lineNumber": 195,
            "text": "        elif isinstance(filepath, str):"
          },
          {
            "lineNumber": 196,
            "text": "            filepath = Path(bpy.path.abspath(filepath))"
          },
          {
            "lineNumber": 197
          },
          {
            "lineNumber": 198,
            "text": "        dirpath = bpathlib.make_absolute(filepath).parent"
          },
          {
            "lineNumber": 199,
            "text": "        if n \u003e= len(dirpath.parts):"
          },
          {
            "lineNumber": 200,
            "text": "            all_parts = dirpath.relative_to(dirpath.anchor)"
          },
          {
            "lineNumber": 201,
            "text": "            return Path(all_parts)"
          },
          {
            "lineNumber": 202
          },
          {
            "lineNumber": 203,
            "text": "        subset = Path(*dirpath.parts[-n:])"
          },
          {
            "lineNumber": 204,
            "text": "        return subset"
          },
          {
            "lineNumber": 205
          },
          {
            "lineNumber": 206,
            "text": "    @staticmethod"
          },
          {
            "lineNumber": 207,
            "text": "    def abspath(filepath: Union[str, Path]) -\u003e Path:"
          },
          {
            "lineNumber": 208,
            "text": "        \"\"\"Return the filepath as absolute path.\"\"\""
          },
          {
            "lineNumber": 209
          },
          {
            "lineNumber": 210,
            "text": "        # This changes blendfile-relative paths to absolute."
          },
          {
            "lineNumber": 211,
            "text": "        # It does not resolve &#96;..&#96; entries, though."
          },
          {
            "lineNumber": 212,
            "text": "        abs_unclean = Path(bpy.path.abspath(str(filepath)))"
          },
          {
            "lineNumber": 213,
            "text": "        abs_clean: Path = bpathlib.make_absolute(abs_unclean)"
          },
          {
            "isSignature": true,
            "lineNumber": 214,
            "text": "        return abs_clean"
          },
          {
            "lineNumber": 215
          },
          {
            "lineNumber": 216
          },
          {
            "lineNumber": 217,
            "text": "# Mapping from AvailableJobType.setting.type to a callable that converts a value"
          }
        ],
        "range": {
          "endPosition": {
            "column": 80,
            "line": 216
          },
          "startPosition": {
            "line": 55
          }
        },
        "relativeWorkspacePath": ".\\job_types_propgroup.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 57
              },
              "startPosition": {
                "column": 1,
                "line": 56
              }
            }
          ]
        }
      },
      "score": 0.31183433532714844
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n\n\n        return True\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 404
          },
          {
            "lineNumber": 405
          },
          {
            "lineNumber": 406,
            "text": "        return True"
          },
          {
            "lineNumber": 407
          },
          {
            "lineNumber": 408,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 409,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 410,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 411,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 414,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 415,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 416
          },
          {
            "lineNumber": 417,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 418,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 419,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 420,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 421
          },
          {
            "lineNumber": 422,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 423,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 424,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 427,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 428,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 429,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 430,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 431,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 432,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 433,
            "text": "        )"
          },
          {
            "lineNumber": 434,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 435
          },
          {
            "lineNumber": 436,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 437,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 441,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 442,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 443,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 444,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 445,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 446,
            "text": "        )"
          },
          {
            "lineNumber": 447
          },
          {
            "lineNumber": 448,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 449
          },
          {
            "lineNumber": 450,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 451,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 652
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.28738680481910706
    },
    {
      "codeBlock": {
        "contents": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:\n    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\"\n\n    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent\n\n    directory = blendfile_dir\n    while True:\n        marker: Path = directory / marker_path\n        if marker.exists():\n            return directory\n\n        parent = directory.parent\n        if directory == parent:\n            # If a directory is its own parent, we're at the root and cannot go\n            # up further.\n            break\n        directory = parent\n\n    # Could not find the marker, so use the directory containing the blend file.\n    return blendfile_dir\n\n\nFinder = Callable[[Path], Path]\n\n\n@dataclasses.dataclass\nclass FinderInfo:\n    label: str",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 45,
            "text": "def _search_path_marker(blendfile: Path, marker_path: str) -\u003e Path:"
          },
          {
            "lineNumber": 46,
            "text": "    \"\"\"Go up the directory hierarchy until a file or directory 'marker_path' is found.\"\"\""
          },
          {
            "lineNumber": 47
          },
          {
            "lineNumber": 48,
            "text": "    blendfile_dir: Path = bpathlib.make_absolute(blendfile).parent"
          },
          {
            "lineNumber": 49
          },
          {
            "lineNumber": 50,
            "text": "    directory = blendfile_dir"
          },
          {
            "lineNumber": 51,
            "text": "    while True:"
          },
          {
            "lineNumber": 52,
            "text": "        marker: Path = directory / marker_path"
          },
          {
            "lineNumber": 53,
            "text": "        if marker.exists():"
          },
          {
            "lineNumber": 54,
            "text": "            return directory"
          },
          {
            "lineNumber": 55
          },
          {
            "lineNumber": 56,
            "text": "        parent = directory.parent"
          },
          {
            "lineNumber": 57,
            "text": "        if directory == parent:"
          },
          {
            "lineNumber": 58,
            "text": "            # If a directory is its own parent, we're at the root and cannot go"
          },
          {
            "lineNumber": 59,
            "text": "            # up further."
          },
          {
            "lineNumber": 60,
            "text": "            break"
          },
          {
            "lineNumber": 61,
            "text": "        directory = parent"
          },
          {
            "lineNumber": 62
          },
          {
            "lineNumber": 63,
            "text": "    # Could not find the marker, so use the directory containing the blend file."
          },
          {
            "isSignature": true,
            "lineNumber": 64,
            "text": "    return blendfile_dir"
          },
          {
            "lineNumber": 65
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "Finder = Callable[[Path], Path]"
          },
          {
            "lineNumber": 68
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "@dataclasses.dataclass"
          },
          {
            "isSignature": true,
            "lineNumber": 71,
            "text": "class FinderInfo:"
          },
          {
            "lineNumber": 72,
            "text": "    label: str"
          }
        ],
        "range": {
          "endPosition": {
            "line": 73
          },
          "startPosition": {
            "column": 49,
            "line": 41
          }
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.26881474256515503
    },
    {
      "codeBlock": {
        "contents": "# SPDX-License-Identifier: GPL-3.0-or-later\n# \u003cpep8 compliant\u003e\n\nfrom pathlib import Path\nfrom typing import Callable\nimport dataclasses\n\nfrom .bat.submodules import bpathlib\n\n\ndef for_blendfile(blendfile: Path, strategy: str) -\u003e Path:\n    \"\"\"Return what is considered to be the project directory containing the given file.\n\n    If none can be found, the directory containing the current blend file is returned.\n    If the current blend file has no path (because it was not saved), a ValueError is raised.\n\n    :param blendfile: the path of the blend file for which to find the project.\n    :param strategy: the name of the finder to use, see &#96;finders&#96;.\n    \"\"\"\n    if blendfile.is_dir():\n        msg = f\"{blendfile} is not a blend file, cannot find project directory\"\n        raise ValueError(msg)\n\n    try:\n        finder_info = finders[strategy]\n    except KeyError:\n        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\"\n        raise ValueError(msg) from None\n\n    return finder_info.finder(blendfile)\n\n\ndef _finder_blender_project(blendfile: Path) -\u003e Path:\n    return _search_path_marker(blendfile, \".blender_project\")",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# SPDX-License-Identifier: GPL-3.0-or-later"
          },
          {
            "lineNumber": 2,
            "text": "# \u003cpep8 compliant\u003e"
          },
          {
            "lineNumber": 3
          },
          {
            "lineNumber": 4,
            "text": "from pathlib import Path"
          },
          {
            "lineNumber": 5,
            "text": "from typing import Callable"
          },
          {
            "lineNumber": 6,
            "text": "import dataclasses"
          },
          {
            "lineNumber": 7
          },
          {
            "lineNumber": 8,
            "text": "from .bat.submodules import bpathlib"
          },
          {
            "lineNumber": 9
          },
          {
            "lineNumber": 10
          },
          {
            "isSignature": true,
            "lineNumber": 11,
            "text": "def for_blendfile(blendfile: Path, strategy: str) -\u003e Path:"
          },
          {
            "lineNumber": 12,
            "text": "    \"\"\"Return what is considered to be the project directory containing the given file."
          },
          {
            "lineNumber": 13
          },
          {
            "lineNumber": 14,
            "text": "    If none can be found, the directory containing the current blend file is returned."
          },
          {
            "lineNumber": 15,
            "text": "    If the current blend file has no path (because it was not saved), a ValueError is raised."
          },
          {
            "lineNumber": 16
          },
          {
            "lineNumber": 17,
            "text": "    :param blendfile: the path of the blend file for which to find the project."
          },
          {
            "lineNumber": 18,
            "text": "    :param strategy: the name of the finder to use, see &#96;finders&#96;."
          },
          {
            "lineNumber": 19,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 20,
            "text": "    if blendfile.is_dir():"
          },
          {
            "lineNumber": 21,
            "text": "        msg = f\"{blendfile} is not a blend file, cannot find project directory\""
          },
          {
            "lineNumber": 22,
            "text": "        raise ValueError(msg)"
          },
          {
            "lineNumber": 23
          },
          {
            "lineNumber": 24,
            "text": "    try:"
          },
          {
            "lineNumber": 25,
            "text": "        finder_info = finders[strategy]"
          },
          {
            "lineNumber": 26,
            "text": "    except KeyError:"
          },
          {
            "lineNumber": 27,
            "text": "        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\""
          },
          {
            "lineNumber": 28,
            "text": "        raise ValueError(msg) from None"
          },
          {
            "lineNumber": 29
          },
          {
            "isSignature": true,
            "lineNumber": 30,
            "text": "    return finder_info.finder(blendfile)"
          },
          {
            "lineNumber": 31
          },
          {
            "lineNumber": 32
          },
          {
            "isSignature": true,
            "lineNumber": 33,
            "text": "def _finder_blender_project(blendfile: Path) -\u003e Path:"
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "    return _search_path_marker(blendfile, \".blender_project\")"
          }
        ],
        "range": {
          "endPosition": {
            "column": 61,
            "line": 33
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.24674560129642487
    },
    {
      "codeBlock": {
        "contents": "_running_packthread: typing.Optional[PackThread] = None\n_packer_lock = threading.RLock()\n\n\ndef copy(  # type: ignore\n    base_blendfile: Path,\n    project: Path,\n    target: str,\n    exclusion_filter: str,\n    *,\n    relative_only: bool,\n    packer_class=submodules.pack.Packer,\n    packer_kwargs: Optional[dict[Any, Any]] = None,\n) -\u003e PackThread:\n    \"\"\"Use BAT to copy the given file and dependencies to the target location.\n\n    Runs BAT in a separate thread, and returns early. Use poll() to get updates\n    \u0026 the final result.\n    \"\"\"\n    global _running_packthread\n\n    with _packer_lock:\n        if _running_packthread is not None:\n            raise RuntimeError(\"other packing operation already in progress\")\n\n    # Due to issues with library overrides and unsynced pointers, it's quite\n    # common for the Blender Animation Studio to get crashes of BAT. To avoid\n    # these, Strict Pointer Mode is disabled.\n    submodules.blendfile.set_strict_pointer_mode(False)\n\n    log.info(\"BAT pack parameters:\")\n    log.info(\"base_blendfile = %r\", base_blendfile)\n    log.info(\"project        = %r\", project)\n    log.info(\"target         = %r\", target)\n\n    if packer_kwargs is None:\n        packer_kwargs = {}\n    packer = packer_class(\n        base_blendfile,\n        project,\n        target,\n        compress=True,\n        relative_only=relative_only,\n        **packer_kwargs,\n    )\n    if exclusion_filter:\n        filter_parts = exclusion_filter.strip().split(\" \")\n        packer.exclude(*filter_parts)\n\n    packthread = PackThread(packer=packer)\n    with _packer_lock:\n        _running_packthread = packthread\n\n    packthread.start()\n    return packthread\n\n\ndef abort() -\u003e None:\n    \"\"\"Abort a running copy() call.\n\n    No-op when there is no running copy(). Can be called from any thread.\n    ",
        "detailedLines": [
          {
            "lineNumber": 197,
            "text": "_running_packthread: typing.Optional[PackThread] = None"
          },
          {
            "lineNumber": 198,
            "text": "_packer_lock = threading.RLock()"
          },
          {
            "lineNumber": 199
          },
          {
            "lineNumber": 200
          },
          {
            "isSignature": true,
            "lineNumber": 201,
            "text": "def copy(  # type: ignore"
          },
          {
            "isSignature": true,
            "lineNumber": 202,
            "text": "    base_blendfile: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 203,
            "text": "    project: Path,"
          },
          {
            "isSignature": true,
            "lineNumber": 204,
            "text": "    target: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 205,
            "text": "    exclusion_filter: str,"
          },
          {
            "isSignature": true,
            "lineNumber": 206,
            "text": "    *,"
          },
          {
            "isSignature": true,
            "lineNumber": 207,
            "text": "    relative_only: bool,"
          },
          {
            "isSignature": true,
            "lineNumber": 208,
            "text": "    packer_class=submodules.pack.Packer,"
          },
          {
            "isSignature": true,
            "lineNumber": 209,
            "text": "    packer_kwargs: Optional[dict[Any, Any]] = None,"
          },
          {
            "isSignature": true,
            "lineNumber": 210,
            "text": ") -\u003e PackThread:"
          },
          {
            "lineNumber": 211,
            "text": "    \"\"\"Use BAT to copy the given file and dependencies to the target location."
          },
          {
            "lineNumber": 212
          },
          {
            "lineNumber": 213,
            "text": "    Runs BAT in a separate thread, and returns early. Use poll() to get updates"
          },
          {
            "lineNumber": 214,
            "text": "    \u0026 the final result."
          },
          {
            "lineNumber": 215,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 216,
            "text": "    global _running_packthread"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 219,
            "text": "        if _running_packthread is not None:"
          },
          {
            "lineNumber": 220,
            "text": "            raise RuntimeError(\"other packing operation already in progress\")"
          },
          {
            "lineNumber": 221
          },
          {
            "lineNumber": 222,
            "text": "    # Due to issues with library overrides and unsynced pointers, it's quite"
          },
          {
            "lineNumber": 223,
            "text": "    # common for the Blender Animation Studio to get crashes of BAT. To avoid"
          },
          {
            "lineNumber": 224,
            "text": "    # these, Strict Pointer Mode is disabled."
          },
          {
            "lineNumber": 225,
            "text": "    submodules.blendfile.set_strict_pointer_mode(False)"
          },
          {
            "lineNumber": 226
          },
          {
            "lineNumber": 227,
            "text": "    log.info(\"BAT pack parameters:\")"
          },
          {
            "lineNumber": 228,
            "text": "    log.info(\"base_blendfile = %r\", base_blendfile)"
          },
          {
            "lineNumber": 229,
            "text": "    log.info(\"project        = %r\", project)"
          },
          {
            "lineNumber": 230,
            "text": "    log.info(\"target         = %r\", target)"
          },
          {
            "lineNumber": 231
          },
          {
            "lineNumber": 232,
            "text": "    if packer_kwargs is None:"
          },
          {
            "lineNumber": 233,
            "text": "        packer_kwargs = {}"
          },
          {
            "lineNumber": 234,
            "text": "    packer = packer_class("
          },
          {
            "lineNumber": 235,
            "text": "        base_blendfile,"
          },
          {
            "lineNumber": 236,
            "text": "        project,"
          },
          {
            "lineNumber": 237,
            "text": "        target,"
          },
          {
            "lineNumber": 238,
            "text": "        compress=True,"
          },
          {
            "lineNumber": 239,
            "text": "        relative_only=relative_only,"
          },
          {
            "lineNumber": 240,
            "text": "        **packer_kwargs,"
          },
          {
            "lineNumber": 241,
            "text": "    )"
          },
          {
            "lineNumber": 242,
            "text": "    if exclusion_filter:"
          },
          {
            "lineNumber": 243,
            "text": "        filter_parts = exclusion_filter.strip().split(\" \")"
          },
          {
            "lineNumber": 244,
            "text": "        packer.exclude(*filter_parts)"
          },
          {
            "lineNumber": 245
          },
          {
            "lineNumber": 246,
            "text": "    packthread = PackThread(packer=packer)"
          },
          {
            "lineNumber": 247,
            "text": "    with _packer_lock:"
          },
          {
            "lineNumber": 248,
            "text": "        _running_packthread = packthread"
          },
          {
            "lineNumber": 249
          },
          {
            "lineNumber": 250,
            "text": "    packthread.start()"
          },
          {
            "isSignature": true,
            "lineNumber": 251,
            "text": "    return packthread"
          },
          {
            "lineNumber": 252
          },
          {
            "lineNumber": 253
          },
          {
            "isSignature": true,
            "lineNumber": 254,
            "text": "def abort() -\u003e None:"
          },
          {
            "lineNumber": 255,
            "text": "    \"\"\"Abort a running copy() call."
          },
          {
            "lineNumber": 256
          },
          {
            "lineNumber": 257,
            "text": "    No-op when there is no running copy(). Can be called from any thread."
          },
          {
            "lineNumber": 258,
            "text": "    "
          }
        ],
        "range": {
          "endPosition": {
            "line": 264
          },
          "startPosition": {
            "column": 27,
            "line": 193
          }
        },
        "relativeWorkspacePath": ".\\bat\\interface.py",
        "signatures": {}
      },
      "score": 0.22070473432540894
    },
    {
      "codeBlock": {
        "contents": "class FlamencoPreferences(bpy.types.AddonPreferences):\n\n\n    def project_root(self) -\u003e Path:\n        \"\"\"Use the configured project finder to find the project root directory.\"\"\"\n\n        if not self.project_finder:\n            # Just a sanity fallback for missing preferences. It should be\n            # covered by the 'default=...' of the property, but just to be sure.\n            self.project_finder = \"BLENDER_PROJECT\"\n\n        # It is assumed that the blendfile is saved.\n        blendfile = Path(bpy.data.filepath)\n        return projects.for_blendfile(blendfile, self.project_finder)\n\n    def _job_storage(self) -\u003e str:\n        info = manager_info.load_cached()\n        if not info:\n            return \"Unknown, refresh first.\"\n        return str(info.shared_storage.location)\n\n\ndef get(context: bpy.types.Context) -\u003e FlamencoPreferences:\n    \"\"\"Return the add-on preferences.\"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 51,
            "text": "class FlamencoPreferences(bpy.types.AddonPreferences):"
          },
          {
            "lineNumber": 119
          },
          {
            "lineNumber": 120
          },
          {
            "lineNumber": 121,
            "text": "    def project_root(self) -\u003e Path:"
          },
          {
            "lineNumber": 122,
            "text": "        \"\"\"Use the configured project finder to find the project root directory.\"\"\""
          },
          {
            "lineNumber": 123
          },
          {
            "lineNumber": 124,
            "text": "        if not self.project_finder:"
          },
          {
            "lineNumber": 125,
            "text": "            # Just a sanity fallback for missing preferences. It should be"
          },
          {
            "lineNumber": 126,
            "text": "            # covered by the 'default=...' of the property, but just to be sure."
          },
          {
            "lineNumber": 127,
            "text": "            self.project_finder = \"BLENDER_PROJECT\""
          },
          {
            "lineNumber": 128
          },
          {
            "lineNumber": 129,
            "text": "        # It is assumed that the blendfile is saved."
          },
          {
            "lineNumber": 130,
            "text": "        blendfile = Path(bpy.data.filepath)"
          },
          {
            "lineNumber": 131,
            "text": "        return projects.for_blendfile(blendfile, self.project_finder)"
          },
          {
            "lineNumber": 132
          },
          {
            "lineNumber": 133,
            "text": "    def _job_storage(self) -\u003e str:"
          },
          {
            "lineNumber": 134,
            "text": "        info = manager_info.load_cached()"
          },
          {
            "lineNumber": 135,
            "text": "        if not info:"
          },
          {
            "lineNumber": 136,
            "text": "            return \"Unknown, refresh first.\""
          },
          {
            "isSignature": true,
            "lineNumber": 137,
            "text": "        return str(info.shared_storage.location)"
          },
          {
            "lineNumber": 138
          },
          {
            "lineNumber": 139
          },
          {
            "isSignature": true,
            "lineNumber": 140,
            "text": "def get(context: bpy.types.Context) -\u003e FlamencoPreferences:"
          },
          {
            "lineNumber": 141,
            "text": "    \"\"\"Return the add-on preferences.\"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 145
          },
          "startPosition": {
            "line": 50
          }
        },
        "relativeWorkspacePath": ".\\preferences.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 52
              },
              "startPosition": {
                "column": 1,
                "line": 51
              }
            }
          ]
        }
      },
      "score": 0.21827054023742676
    },
    {
      "codeBlock": {
        "contents": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:\n    \"\"\"Check whether current blend file is inside the storage path.\n\n    :return: True when the current blend file is inside the Flamenco job storage\n        directory already. In this case it won't be BAT-packed, as it's assumed\n        the job storage dir is accessible by the workers already.\n    \"\"\"\n\n    blendfile = bpathlib.make_absolute(blendfile)\n\n    info = manager_info.load_cached()\n    if not info:\n        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")\n\n    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))\n\n    log.info(\"Checking whether the file is already inside the job storage\")\n    log.info(\"    file   : %s\", blendfile)\n    log.info(\"    storage: %s\", job_storage)\n\n    try:\n        blendfile.relative_to(job_storage)\n    except ValueError:\n        return False\n    return True",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 134,
            "text": "def is_file_inside_job_storage(context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 135,
            "text": "    \"\"\"Check whether current blend file is inside the storage path."
          },
          {
            "lineNumber": 136
          },
          {
            "lineNumber": 137,
            "text": "    :return: True when the current blend file is inside the Flamenco job storage"
          },
          {
            "lineNumber": 138,
            "text": "        directory already. In this case it won't be BAT-packed, as it's assumed"
          },
          {
            "lineNumber": 139,
            "text": "        the job storage dir is accessible by the workers already."
          },
          {
            "lineNumber": 140,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 141
          },
          {
            "lineNumber": 142,
            "text": "    blendfile = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 143
          },
          {
            "lineNumber": 144,
            "text": "    info = manager_info.load_cached()"
          },
          {
            "lineNumber": 145,
            "text": "    if not info:"
          },
          {
            "lineNumber": 146,
            "text": "        raise RuntimeError(\"Flamenco Manager info unknown, please refresh.\")"
          },
          {
            "lineNumber": 147
          },
          {
            "lineNumber": 148,
            "text": "    job_storage = bpathlib.make_absolute(Path(info.shared_storage.location))"
          },
          {
            "lineNumber": 149
          },
          {
            "lineNumber": 150,
            "text": "    log.info(\"Checking whether the file is already inside the job storage\")"
          },
          {
            "lineNumber": 151,
            "text": "    log.info(\"    file   : %s\", blendfile)"
          },
          {
            "lineNumber": 152,
            "text": "    log.info(\"    storage: %s\", job_storage)"
          },
          {
            "lineNumber": 153
          },
          {
            "lineNumber": 154,
            "text": "    try:"
          },
          {
            "lineNumber": 155,
            "text": "        blendfile.relative_to(job_storage)"
          },
          {
            "lineNumber": 156,
            "text": "    except ValueError:"
          },
          {
            "lineNumber": 157,
            "text": "        return False"
          },
          {
            "isSignature": true,
            "lineNumber": 158,
            "text": "    return True"
          }
        ],
        "range": {
          "endPosition": {
            "column": 15,
            "line": 157
          },
          "startPosition": {
            "column": 42,
            "line": 130
          }
        },
        "relativeWorkspacePath": ".\\job_submission.py",
        "signatures": {}
      },
      "score": 0.21133923530578613
    },
    {
      "codeBlock": {
        "contents": "def find_files(root: Path) -\u003e typing.Iterable[Path]:\n    \"\"\"Recursively finds files in the given root path.\n\n    Directories are recursed into, and file paths are yielded.\n    Symlinks are yielded if they refer to a regular file.\n    \"\"\"\n    queue = deque([root])\n    while queue:\n        path = queue.popleft()\n\n        # Ignore hidden files/dirs; these can be things like '.svn' or '.git',\n        # which shouldn't be sent to Shaman.\n        if path.name.startswith(\".\"):\n            continue\n\n        if path.is_dir():\n            for child in path.iterdir():\n                queue.append(child)\n            continue\n\n        # Only yield symlinks if they link to (a link to) a normal file.\n        if path.is_symlink():\n            symlinked = path.resolve()\n            if symlinked.is_file():\n                yield path\n            continue\n\n        if path.is_file():\n            yield path\n\n\ndef compute_checksum(filepath: Path) -\u003e str:\n    \"\"\"Compute the SHA256 checksum for the given file.\"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 45,
            "text": "def find_files(root: Path) -\u003e typing.Iterable[Path]:"
          },
          {
            "lineNumber": 46,
            "text": "    \"\"\"Recursively finds files in the given root path."
          },
          {
            "lineNumber": 47
          },
          {
            "lineNumber": 48,
            "text": "    Directories are recursed into, and file paths are yielded."
          },
          {
            "lineNumber": 49,
            "text": "    Symlinks are yielded if they refer to a regular file."
          },
          {
            "lineNumber": 50,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 51,
            "text": "    queue = deque([root])"
          },
          {
            "lineNumber": 52,
            "text": "    while queue:"
          },
          {
            "lineNumber": 53,
            "text": "        path = queue.popleft()"
          },
          {
            "lineNumber": 54
          },
          {
            "lineNumber": 55,
            "text": "        # Ignore hidden files/dirs; these can be things like '.svn' or '.git',"
          },
          {
            "lineNumber": 56,
            "text": "        # which shouldn't be sent to Shaman."
          },
          {
            "lineNumber": 57,
            "text": "        if path.name.startswith(\".\"):"
          },
          {
            "lineNumber": 58,
            "text": "            continue"
          },
          {
            "lineNumber": 59
          },
          {
            "lineNumber": 60,
            "text": "        if path.is_dir():"
          },
          {
            "lineNumber": 61,
            "text": "            for child in path.iterdir():"
          },
          {
            "lineNumber": 62,
            "text": "                queue.append(child)"
          },
          {
            "lineNumber": 63,
            "text": "            continue"
          },
          {
            "lineNumber": 64
          },
          {
            "lineNumber": 65,
            "text": "        # Only yield symlinks if they link to (a link to) a normal file."
          },
          {
            "lineNumber": 66,
            "text": "        if path.is_symlink():"
          },
          {
            "lineNumber": 67,
            "text": "            symlinked = path.resolve()"
          },
          {
            "lineNumber": 68,
            "text": "            if symlinked.is_file():"
          },
          {
            "lineNumber": 69,
            "text": "                yield path"
          },
          {
            "lineNumber": 70,
            "text": "            continue"
          },
          {
            "lineNumber": 71
          },
          {
            "lineNumber": 72,
            "text": "        if path.is_file():"
          },
          {
            "isSignature": true,
            "lineNumber": 73,
            "text": "            yield path"
          },
          {
            "lineNumber": 74
          },
          {
            "lineNumber": 75
          },
          {
            "isSignature": true,
            "lineNumber": 76,
            "text": "def compute_checksum(filepath: Path) -\u003e str:"
          },
          {
            "lineNumber": 77,
            "text": "    \"\"\"Compute the SHA256 checksum for the given file.\"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 89
          },
          "startPosition": {
            "column": 33,
            "line": 41
          }
        },
        "relativeWorkspacePath": ".\\bat\\cache.py",
        "signatures": {}
      },
      "score": 0.2048194408416748
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Implementing a function to convert all relative paths to absolute, then updating the submission flow to bypass BAT:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\bat\shaman.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking the Shaman API for direct file upload:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": true,
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "shaman.*upload|upload.*shaman|file_upload|checkout_requirements"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "shaman.*upload|upload.*shaman|file_upload|checkout_requirements",
  "topFiles": [
    {
      "matchCount": 3,
      "uri": ".\\.specstory\\history\\2026-01-28_20-03Z-flamenco-addon-file-sending.md"
    },
    {
      "matchCount": 3,
      "uri": ".\\manager_README.md"
    },
    {
      "matchCount": 12,
      "uri": ".\\bat\\shaman.py"
    },
    {
      "matchCount": 12,
      "uri": ".\\manager\\api\\shaman_api.py"
    },
    {
      "matchCount": 16,
      "uri": ".\\manager\\docs\\ShamanApi.md"
    }
  ],
  "totalFiles": 5,
  "totalMatches": 46
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How to upload a file directly to Shaman without BAT? What is the Shaman API for uploading files?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class ShamanApi(object):\ndef shaman_checkout_requirements(\n        self,\n        shaman_requirements_request,\n        **kwargs\n    ):\n(**kwargs)\n\n    def shaman_file_store(\n        self,\n        checksum,\n        filesize,\n        body,\n        **kwargs\n    ):\n        \"\"\"Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.   # noqa: E501\n\n        This method makes a synchronous HTTP request by default. To make an\n        asynchronous HTTP request, please pass async_req=True\n\n        \u003e\u003e\u003e thread = api.shaman_file_store(checksum, filesize, body, async_req=True)\n        \u003e\u003e\u003e result = thread.get()\n\n        Args:\n            checksum (str): SHA256 checksum of the file.\n            filesize (int): Size of the file in bytes.\n            body (file_type): Contents of the file\n\n        Keyword Args:\n            x_shaman_can_defer_upload (bool): The client indicates that it can defer uploading this file. The \\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. . [optional]\n            x_shaman_original_filename (str): The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. . [optional]\n            _return_http_data_only (bool): response data without head status\n                code and headers. Default is True.\n            _preload_content (bool): if False, the urllib3.HTTPResponse object\n                will be returned without reading/decoding response data.\n                Default is True.\n            _request_timeout (int/float/tuple): timeout setting for this request. If\n                one number provided, it will be total request timeout. It can also\n                be a pair (tuple) of (connection, read) timeouts.\n                Default is None.\n            _check_input_type (bool): specifies if type checking\n                should be done one the data sent to the server.\n                Default is True.\n            _check_return_type (bool): specifies if type checking\n                should be done one the data received from the server.\n                Default is True.\n            _spec_property_naming (bool): True if the variable names in the input data\n                are serialized names, as specified in the OpenAPI document.\n                False if the variable names in the input data\n                are pythonic names, e.g. snake case (default)\n            _content_type (str/None): force body content-type.\n                Default is None and content-type will be predicted by allowed\n                content-types and body.\n            _host_index (int/None): specifies the index of the server\n                that we want to use.\n                Default is read from the configuration.\n            async_req (bool): execute request asynchronously\n\n        Returns:\n            None\n                If the method is called asynchronously, returns the request\n                thread.\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 32,
            "text": "class ShamanApi(object):"
          },
          {
            "lineNumber": 348,
            "text": "def shaman_checkout_requirements("
          },
          {
            "lineNumber": 349,
            "text": "        self,"
          },
          {
            "lineNumber": 350,
            "text": "        shaman_requirements_request,"
          },
          {
            "lineNumber": 351,
            "text": "        **kwargs"
          },
          {
            "lineNumber": 352,
            "text": "    ):"
          },
          {
            "lineNumber": 423,
            "text": "(**kwargs)"
          },
          {
            "lineNumber": 424
          },
          {
            "lineNumber": 425,
            "text": "    def shaman_file_store("
          },
          {
            "lineNumber": 426,
            "text": "        self,"
          },
          {
            "lineNumber": 427,
            "text": "        checksum,"
          },
          {
            "lineNumber": 428,
            "text": "        filesize,"
          },
          {
            "lineNumber": 429,
            "text": "        body,"
          },
          {
            "lineNumber": 430,
            "text": "        **kwargs"
          },
          {
            "lineNumber": 431,
            "text": "    ):"
          },
          {
            "lineNumber": 432,
            "text": "        \"\"\"Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body.   # noqa: E501"
          },
          {
            "lineNumber": 433
          },
          {
            "lineNumber": 434,
            "text": "        This method makes a synchronous HTTP request by default. To make an"
          },
          {
            "lineNumber": 435,
            "text": "        asynchronous HTTP request, please pass async_req=True"
          },
          {
            "lineNumber": 436
          },
          {
            "lineNumber": 437,
            "text": "        \u003e\u003e\u003e thread = api.shaman_file_store(checksum, filesize, body, async_req=True)"
          },
          {
            "lineNumber": 438,
            "text": "        \u003e\u003e\u003e result = thread.get()"
          },
          {
            "lineNumber": 439
          },
          {
            "lineNumber": 440,
            "text": "        Args:"
          },
          {
            "lineNumber": 441,
            "text": "            checksum (str): SHA256 checksum of the file."
          },
          {
            "lineNumber": 442,
            "text": "            filesize (int): Size of the file in bytes."
          },
          {
            "lineNumber": 443,
            "text": "            body (file_type): Contents of the file"
          },
          {
            "lineNumber": 444
          },
          {
            "lineNumber": 445,
            "text": "        Keyword Args:"
          },
          {
            "lineNumber": 446,
            "text": "            x_shaman_can_defer_upload (bool): The client indicates that it can defer uploading this file. The \\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. . [optional]"
          },
          {
            "lineNumber": 447,
            "text": "            x_shaman_original_filename (str): The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. . [optional]"
          },
          {
            "lineNumber": 448,
            "text": "            _return_http_data_only (bool): response data without head status"
          },
          {
            "lineNumber": 449,
            "text": "                code and headers. Default is True."
          },
          {
            "lineNumber": 450,
            "text": "            _preload_content (bool): if False, the urllib3.HTTPResponse object"
          },
          {
            "lineNumber": 451,
            "text": "                will be returned without reading/decoding response data."
          },
          {
            "lineNumber": 452,
            "text": "                Default is True."
          },
          {
            "lineNumber": 453,
            "text": "            _request_timeout (int/float/tuple): timeout setting for this request. If"
          },
          {
            "lineNumber": 454,
            "text": "                one number provided, it will be total request timeout. It can also"
          },
          {
            "lineNumber": 455,
            "text": "                be a pair (tuple) of (connection, read) timeouts."
          },
          {
            "lineNumber": 456,
            "text": "                Default is None."
          },
          {
            "lineNumber": 457,
            "text": "            _check_input_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 458,
            "text": "                should be done one the data sent to the server."
          },
          {
            "lineNumber": 459,
            "text": "                Default is True."
          },
          {
            "lineNumber": 460,
            "text": "            _check_return_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 461,
            "text": "                should be done one the data received from the server."
          },
          {
            "lineNumber": 462,
            "text": "                Default is True."
          },
          {
            "lineNumber": 463,
            "text": "            _spec_property_naming (bool): True if the variable names in the input data"
          },
          {
            "lineNumber": 464,
            "text": "                are serialized names, as specified in the OpenAPI document."
          },
          {
            "lineNumber": 465,
            "text": "                False if the variable names in the input data"
          },
          {
            "lineNumber": 466,
            "text": "                are pythonic names, e.g. snake case (default)"
          },
          {
            "lineNumber": 467,
            "text": "            _content_type (str/None): force body content-type."
          },
          {
            "lineNumber": 468,
            "text": "                Default is None and content-type will be predicted by allowed"
          },
          {
            "lineNumber": 469,
            "text": "                content-types and body."
          },
          {
            "lineNumber": 470,
            "text": "            _host_index (int/None): specifies the index of the server"
          },
          {
            "lineNumber": 471,
            "text": "                that we want to use."
          },
          {
            "lineNumber": 472,
            "text": "                Default is read from the configuration."
          },
          {
            "lineNumber": 473,
            "text": "            async_req (bool): execute request asynchronously"
          },
          {
            "lineNumber": 474
          },
          {
            "lineNumber": 475,
            "text": "        Returns:"
          },
          {
            "lineNumber": 476,
            "text": "            None"
          },
          {
            "lineNumber": 477,
            "text": "                If the method is called asynchronously, returns the request"
          },
          {
            "lineNumber": 478,
            "text": "                thread."
          },
          {
            "lineNumber": 479,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 590
          },
          "startPosition": {
            "line": 31
          }
        },
        "relativeWorkspacePath": ".\\manager\\api\\shaman_api.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 33
              },
              "startPosition": {
                "column": 1,
                "line": 32
              }
            }
          ]
        }
      },
      "score": 0.5444800853729248
    },
    {
      "codeBlock": {
        "contents": "print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)\n\n    # example passing only required values which don't have defaults set\n    # and optional values\n    try:\n        # Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. \n        api_instance.shaman_file_store(checksum, filesize, body, x_shaman_can_defer_upload=x_shaman_can_defer_upload, x_shaman_original_filename=x_shaman_original_filename)\n    except flamenco.manager.ApiException as e:\n        print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)\n&#96;&#96;&#96;\n\n\n### Parameters\n\nName | Type | Description  | Notes\n------------- | ------------- | ------------- | -------------\n **checksum** | **str**| SHA256 checksum of the file. |\n **filesize** | **int**| Size of the file in bytes. |\n **body** | **file_type**| Contents of the file |\n **x_shaman_can_defer_upload** | **bool**| The client indicates that it can defer uploading this file. The \\\u0026quot;208\\\u0026quot; response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file.  | [optional]\n **x_shaman_original_filename** | **str**| The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8.  | [optional]\n\n### Return type\n\nvoid (empty response body)\n\n### Authorization\n\nNo authorization required\n\n### HTTP request headers\n\n - **Content-Type**: application/octet-stream\n - **Accept**: application/json\n\n\n### HTTP response details\n\n| Status code | Description | Response headers |\n|-------------|-------------|------------------|\n**204** | The file was accepted. |  -  |\n**208** | The file was already known to the server. |  -  |\n**417** | There was a mismatch between the request parameters and the actual file size or checksum of the uploaded file.  |  -  |",
        "detailedLines": [
          {
            "lineNumber": 204,
            "text": "print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)"
          },
          {
            "lineNumber": 205
          },
          {
            "lineNumber": 206,
            "text": "    # example passing only required values which don't have defaults set"
          },
          {
            "lineNumber": 207,
            "text": "    # and optional values"
          },
          {
            "lineNumber": 208,
            "text": "    try:"
          },
          {
            "lineNumber": 209,
            "text": "        # Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. "
          },
          {
            "lineNumber": 210,
            "text": "        api_instance.shaman_file_store(checksum, filesize, body, x_shaman_can_defer_upload=x_shaman_can_defer_upload, x_shaman_original_filename=x_shaman_original_filename)"
          },
          {
            "lineNumber": 211,
            "text": "    except flamenco.manager.ApiException as e:"
          },
          {
            "lineNumber": 212,
            "text": "        print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)"
          },
          {
            "lineNumber": 213,
            "text": "&#96;&#96;&#96;"
          },
          {
            "lineNumber": 214
          },
          {
            "lineNumber": 215
          },
          {
            "lineNumber": 216,
            "text": "### Parameters"
          },
          {
            "lineNumber": 217
          },
          {
            "lineNumber": 218,
            "text": "Name | Type | Description  | Notes"
          },
          {
            "lineNumber": 219,
            "text": "------------- | ------------- | ------------- | -------------"
          },
          {
            "lineNumber": 220,
            "text": " **checksum** | **str**| SHA256 checksum of the file. |"
          },
          {
            "lineNumber": 221,
            "text": " **filesize** | **int**| Size of the file in bytes. |"
          },
          {
            "lineNumber": 222,
            "text": " **body** | **file_type**| Contents of the file |"
          },
          {
            "lineNumber": 223,
            "text": " **x_shaman_can_defer_upload** | **bool**| The client indicates that it can defer uploading this file. The \\\u0026quot;208\\\u0026quot; response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file.  | [optional]"
          },
          {
            "lineNumber": 224,
            "text": " **x_shaman_original_filename** | **str**| The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8.  | [optional]"
          },
          {
            "lineNumber": 225
          },
          {
            "lineNumber": 226,
            "text": "### Return type"
          },
          {
            "lineNumber": 227
          },
          {
            "lineNumber": 228,
            "text": "void (empty response body)"
          },
          {
            "lineNumber": 229
          },
          {
            "lineNumber": 230,
            "text": "### Authorization"
          },
          {
            "lineNumber": 231
          },
          {
            "lineNumber": 232,
            "text": "No authorization required"
          },
          {
            "lineNumber": 233
          },
          {
            "lineNumber": 234,
            "text": "### HTTP request headers"
          },
          {
            "lineNumber": 235
          },
          {
            "lineNumber": 236,
            "text": " - **Content-Type**: application/octet-stream"
          },
          {
            "lineNumber": 237,
            "text": " - **Accept**: application/json"
          },
          {
            "lineNumber": 238
          },
          {
            "lineNumber": 239
          },
          {
            "lineNumber": 240,
            "text": "### HTTP response details"
          },
          {
            "lineNumber": 241
          },
          {
            "lineNumber": 242,
            "text": "| Status code | Description | Response headers |"
          },
          {
            "lineNumber": 243,
            "text": "|-------------|-------------|------------------|"
          },
          {
            "lineNumber": 244,
            "text": "**204** | The file was accepted. |  -  |"
          },
          {
            "lineNumber": 245,
            "text": "**208** | The file was already known to the server. |  -  |"
          },
          {
            "lineNumber": 246,
            "text": "**417** | There was a mismatch between the request parameters and the actual file size or checksum of the uploaded file.  |  -  |"
          }
        ],
        "range": {
          "endPosition": {
            "line": 246
          },
          "startPosition": {
            "column": 8,
            "line": 203
          }
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.5312274098396301
    },
    {
      "codeBlock": {
        "contents": "with flamenco.manager.ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = shaman_api.ShamanApi(api_client)\n    checksum = \"checksum_example\" # str | SHA256 checksum of the file.\n    filesize = 1 # int | Size of the file in bytes.\n    body = open('/path/to/file', 'rb') # file_type | Contents of the file\n    x_shaman_can_defer_upload = True # bool | The client indicates that it can defer uploading this file. The \\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file.  (optional)\n    x_shaman_original_filename = \"X-Shaman-Original-Filename_example\" # str | The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8.  (optional)\n\n    # example passing only required values which don't have defaults set\n    try:\n        # Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. \n        api_instance.shaman_file_store(checksum, filesize, body)\n    except flamenco.manager.ApiException as e:\n        print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)\n\n    # example passing only required values which don't have defaults set",
        "detailedLines": [
          {
            "lineNumber": 190,
            "text": "with flamenco.manager.ApiClient() as api_client:"
          },
          {
            "lineNumber": 191,
            "text": "    # Create an instance of the API class"
          },
          {
            "lineNumber": 192,
            "text": "    api_instance = shaman_api.ShamanApi(api_client)"
          },
          {
            "lineNumber": 193,
            "text": "    checksum = \"checksum_example\" # str | SHA256 checksum of the file."
          },
          {
            "lineNumber": 194,
            "text": "    filesize = 1 # int | Size of the file in bytes."
          },
          {
            "lineNumber": 195,
            "text": "    body = open('/path/to/file', 'rb') # file_type | Contents of the file"
          },
          {
            "lineNumber": 196,
            "text": "    x_shaman_can_defer_upload = True # bool | The client indicates that it can defer uploading this file. The \\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file.  (optional)"
          },
          {
            "lineNumber": 197,
            "text": "    x_shaman_original_filename = \"X-Shaman-Original-Filename_example\" # str | The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8.  (optional)"
          },
          {
            "lineNumber": 198
          },
          {
            "lineNumber": 199,
            "text": "    # example passing only required values which don't have defaults set"
          },
          {
            "lineNumber": 200,
            "text": "    try:"
          },
          {
            "lineNumber": 201,
            "text": "        # Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. "
          },
          {
            "lineNumber": 202,
            "text": "        api_instance.shaman_file_store(checksum, filesize, body)"
          },
          {
            "lineNumber": 203,
            "text": "    except flamenco.manager.ApiException as e:"
          },
          {
            "lineNumber": 204,
            "text": "        print(\"Exception when calling ShamanApi-\u003eshaman_file_store: %s\\n\" % e)"
          },
          {
            "lineNumber": 205
          },
          {
            "lineNumber": 206,
            "text": "    # example passing only required values which don't have defaults set"
          }
        ],
        "range": {
          "endPosition": {
            "line": 206
          },
          "startPosition": {
            "line": 189
          }
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.5055773258209229
    },
    {
      "codeBlock": {
        "contents": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore\ndef _upload_files(\n        self, to_upload: deque[_ShamanFileSpec]\n    ) -\u003e set[HashableShamanFileSpec]:\n\n            try:\n                with local_filepath.open(\"rb\") as file_reader:\n                    self.shaman_api.shaman_file_store(\n                        checksum=file_spec.sha,\n                        filesize=file_spec.size,\n                        body=file_reader,\n                        x_shaman_can_defer_upload=can_defer,\n                        x_shaman_original_filename=filename_header,\n                    )\n            except ApiException as ex:\n                if ex.status == 425:\n                    # Too Early, i.e. defer uploading this file.\n                    self.log.info(\n                        \"  %s: someone else is uploading this file, deferring\",\n                        file_spec.path,\n                    )\n                    defer(file_spec)\n                    continue\n                elif ex.status == 417:\n                    # Expectation Failed; mismatch of checksum or file size.\n                    msg = \"Error from Shaman uploading %s, code %d: %s\" % (\n                        file_spec.path,\n                        ex.status,\n                        ex.body,\n                    )\n                else:  # Unknown error\n                    msg = \"API exception\\nHeaders: %s\\nBody: %s\\n\" % (\n                        ex.headers,\n                        ex.body,\n                    )\n\n                self.log.error(msg)\n                self.error_set(msg)\n                failed_specs.add(make_file_spec_hashable(file_spec))\n                return failed_specs\n\n            failed_specs.discard(make_file_spec_hashable(file_spec))\n            self.uploaded_files += 1\n            file_size = local_filepath.stat().st_size\n            self.uploaded_bytes += file_size\n            self.report_transferred(file_size)\n\n        if failed_specs:\n            self.log.info",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 99,
            "text": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore"
          },
          {
            "lineNumber": 303,
            "text": "def _upload_files("
          },
          {
            "lineNumber": 304,
            "text": "        self, to_upload: deque[_ShamanFileSpec]"
          },
          {
            "lineNumber": 305,
            "text": "    ) -\u003e set[HashableShamanFileSpec]:"
          },
          {
            "lineNumber": 370
          },
          {
            "lineNumber": 371,
            "text": "            try:"
          },
          {
            "lineNumber": 372,
            "text": "                with local_filepath.open(\"rb\") as file_reader:"
          },
          {
            "lineNumber": 373,
            "text": "                    self.shaman_api.shaman_file_store("
          },
          {
            "lineNumber": 374,
            "text": "                        checksum=file_spec.sha,"
          },
          {
            "lineNumber": 375,
            "text": "                        filesize=file_spec.size,"
          },
          {
            "lineNumber": 376,
            "text": "                        body=file_reader,"
          },
          {
            "lineNumber": 377,
            "text": "                        x_shaman_can_defer_upload=can_defer,"
          },
          {
            "lineNumber": 378,
            "text": "                        x_shaman_original_filename=filename_header,"
          },
          {
            "lineNumber": 379,
            "text": "                    )"
          },
          {
            "lineNumber": 380,
            "text": "            except ApiException as ex:"
          },
          {
            "lineNumber": 381,
            "text": "                if ex.status == 425:"
          },
          {
            "lineNumber": 382,
            "text": "                    # Too Early, i.e. defer uploading this file."
          },
          {
            "lineNumber": 383,
            "text": "                    self.log.info("
          },
          {
            "lineNumber": 384,
            "text": "                        \"  %s: someone else is uploading this file, deferring\","
          },
          {
            "lineNumber": 385,
            "text": "                        file_spec.path,"
          },
          {
            "lineNumber": 386,
            "text": "                    )"
          },
          {
            "lineNumber": 387,
            "text": "                    defer(file_spec)"
          },
          {
            "lineNumber": 388,
            "text": "                    continue"
          },
          {
            "lineNumber": 389,
            "text": "                elif ex.status == 417:"
          },
          {
            "lineNumber": 390,
            "text": "                    # Expectation Failed; mismatch of checksum or file size."
          },
          {
            "lineNumber": 391,
            "text": "                    msg = \"Error from Shaman uploading %s, code %d: %s\" % ("
          },
          {
            "lineNumber": 392,
            "text": "                        file_spec.path,"
          },
          {
            "lineNumber": 393,
            "text": "                        ex.status,"
          },
          {
            "lineNumber": 394,
            "text": "                        ex.body,"
          },
          {
            "lineNumber": 395,
            "text": "                    )"
          },
          {
            "lineNumber": 396,
            "text": "                else:  # Unknown error"
          },
          {
            "lineNumber": 397,
            "text": "                    msg = \"API exception\\nHeaders: %s\\nBody: %s\\n\" % ("
          },
          {
            "lineNumber": 398,
            "text": "                        ex.headers,"
          },
          {
            "lineNumber": 399,
            "text": "                        ex.body,"
          },
          {
            "lineNumber": 400,
            "text": "                    )"
          },
          {
            "lineNumber": 401
          },
          {
            "lineNumber": 402,
            "text": "                self.log.error(msg)"
          },
          {
            "lineNumber": 403,
            "text": "                self.error_set(msg)"
          },
          {
            "lineNumber": 404,
            "text": "                failed_specs.add(make_file_spec_hashable(file_spec))"
          },
          {
            "lineNumber": 405,
            "text": "                return failed_specs"
          },
          {
            "lineNumber": 406
          },
          {
            "lineNumber": 407,
            "text": "            failed_specs.discard(make_file_spec_hashable(file_spec))"
          },
          {
            "lineNumber": 408,
            "text": "            self.uploaded_files += 1"
          },
          {
            "lineNumber": 409,
            "text": "            file_size = local_filepath.stat().st_size"
          },
          {
            "lineNumber": 410,
            "text": "            self.uploaded_bytes += file_size"
          },
          {
            "lineNumber": 411,
            "text": "            self.report_transferred(file_size)"
          },
          {
            "lineNumber": 412
          },
          {
            "lineNumber": 413,
            "text": "        if failed_specs:"
          },
          {
            "lineNumber": 414,
            "text": "            self.log.info"
          }
        ],
        "range": {
          "endPosition": {
            "line": 473
          },
          "startPosition": {
            "line": 98
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 100
              },
              "startPosition": {
                "column": 1,
                "line": 99
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 306
              },
              "startPosition": {
                "column": 5,
                "line": 303
              }
            }
          ]
        }
      },
      "score": 0.4993917942047119
    },
    {
      "codeBlock": {
        "contents": "# flamenco.manager.ShamanApi\n\nAll URIs are relative to *http://localhost*\n\nMethod | HTTP request | Description\n------------- | ------------- | -------------\n[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /api/v3/shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.\n[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /api/v3/shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown.\n[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /api/v3/shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file\u0026#39;s contents should be sent in the request body. \n[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **GET** /api/v3/shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server. \n\n\n# **shaman_checkout**\n\u003e ShamanCheckoutResult shaman_checkout(shaman_checkout)\n\nCreate a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.\n\n### Example\n\n\n&#96;&#96;&#96;python\nimport time\nimport flamenco.manager\nfrom flamenco.manager.api import shaman_api\nfrom flamenco.manager.model.error import Error\nfrom flamenco.manager.model.shaman_checkout import ShamanCheckout\nfrom flamenco.manager.model.shaman_checkout_result import ShamanCheckoutResult\nfrom pprint import pprint\n# Defining the host is optional and defaults to http://localhost\n# See configuration.py for a list of all supported configuration parameters.\nconfiguration = flamenco.manager.Configuration(\n    host = \"http://localhost\"\n)\n\n\n# Enter a context with an instance of the API client\nwith flamenco.manager.ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = shaman_api.ShamanApi(api_client)",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# flamenco.manager.ShamanApi"
          },
          {
            "lineNumber": 2
          },
          {
            "lineNumber": 3,
            "text": "All URIs are relative to *http://localhost*"
          },
          {
            "lineNumber": 4
          },
          {
            "lineNumber": 5,
            "text": "Method | HTTP request | Description"
          },
          {
            "lineNumber": 6,
            "text": "------------- | ------------- | -------------"
          },
          {
            "lineNumber": 7,
            "text": "[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /api/v3/shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint."
          },
          {
            "lineNumber": 8,
            "text": "[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /api/v3/shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown."
          },
          {
            "lineNumber": 9,
            "text": "[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /api/v3/shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file\u0026#39;s contents should be sent in the request body. "
          },
          {
            "lineNumber": 10,
            "text": "[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **GET** /api/v3/shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server. "
          },
          {
            "lineNumber": 11
          },
          {
            "lineNumber": 12
          },
          {
            "lineNumber": 13,
            "text": "# **shaman_checkout**"
          },
          {
            "lineNumber": 14,
            "text": "\u003e ShamanCheckoutResult shaman_checkout(shaman_checkout)"
          },
          {
            "lineNumber": 15
          },
          {
            "lineNumber": 16,
            "text": "Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint."
          },
          {
            "lineNumber": 17
          },
          {
            "lineNumber": 18,
            "text": "### Example"
          },
          {
            "lineNumber": 19
          },
          {
            "lineNumber": 20
          },
          {
            "lineNumber": 21,
            "text": "&#96;&#96;&#96;python"
          },
          {
            "lineNumber": 22,
            "text": "import time"
          },
          {
            "lineNumber": 23,
            "text": "import flamenco.manager"
          },
          {
            "lineNumber": 24,
            "text": "from flamenco.manager.api import shaman_api"
          },
          {
            "lineNumber": 25,
            "text": "from flamenco.manager.model.error import Error"
          },
          {
            "lineNumber": 26,
            "text": "from flamenco.manager.model.shaman_checkout import ShamanCheckout"
          },
          {
            "lineNumber": 27,
            "text": "from flamenco.manager.model.shaman_checkout_result import ShamanCheckoutResult"
          },
          {
            "lineNumber": 28,
            "text": "from pprint import pprint"
          },
          {
            "lineNumber": 29,
            "text": "# Defining the host is optional and defaults to http://localhost"
          },
          {
            "lineNumber": 30,
            "text": "# See configuration.py for a list of all supported configuration parameters."
          },
          {
            "lineNumber": 31,
            "text": "configuration = flamenco.manager.Configuration("
          },
          {
            "lineNumber": 32,
            "text": "    host = \"http://localhost\""
          },
          {
            "lineNumber": 33,
            "text": ")"
          },
          {
            "lineNumber": 34
          },
          {
            "lineNumber": 35
          },
          {
            "lineNumber": 36,
            "text": "# Enter a context with an instance of the API client"
          },
          {
            "lineNumber": 37,
            "text": "with flamenco.manager.ApiClient() as api_client:"
          },
          {
            "lineNumber": 38,
            "text": "    # Create an instance of the API class"
          },
          {
            "lineNumber": 39,
            "text": "    api_instance = shaman_api.ShamanApi(api_client)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 39
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.4906999468803406
    },
    {
      "codeBlock": {
        "contents": "class ShamanApi(object):\ndef shaman_file_store(\n        self,\n        checksum,\n        filesize,\n        body,\n        **kwargs\n    ):\n\\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. . [optional]\n            x_shaman_original_filename (str): The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. . [optional]\n            _return_http_data_only (bool): response data without head status\n                code and headers. Default is True.\n            _preload_content (bool): if False, the urllib3.HTTPResponse object\n                will be returned without reading/decoding response data.\n                Default is True.\n            _request_timeout (int/float/tuple): timeout setting for this request. If\n                one number provided, it will be total request timeout. It can also\n                be a pair (tuple) of (connection, read) timeouts.\n                Default is None.\n            _check_input_type (bool): specifies if type checking\n                should be done one the data sent to the server.\n                Default is True.\n            _check_return_type (bool): specifies if type checking\n                should be done one the data received from the server.\n                Default is True.\n            _spec_property_naming (bool): True if the variable names in the input data\n                are serialized names, as specified in the OpenAPI document.\n                False if the variable names in the input data\n                are pythonic names, e.g. snake case (default)\n            _content_type (str/None): force body content-type.\n                Default is None and content-type will be predicted by allowed\n                content-types and body.\n            _host_index (int/None): specifies the index of the server\n                that we want to use.\n                Default is read from the configuration.\n            async_req (bool): execute request asynchronously\n\n        Returns:\n            None\n                If the method is called asynchronously, returns the request\n                thread.\n        \"\"\"\n        kwargs['async_req'] = kwargs.get(\n            'async_req', False\n        )",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 32,
            "text": "class ShamanApi(object):"
          },
          {
            "lineNumber": 425,
            "text": "def shaman_file_store("
          },
          {
            "lineNumber": 426,
            "text": "        self,"
          },
          {
            "lineNumber": 427,
            "text": "        checksum,"
          },
          {
            "lineNumber": 428,
            "text": "        filesize,"
          },
          {
            "lineNumber": 429,
            "text": "        body,"
          },
          {
            "lineNumber": 430,
            "text": "        **kwargs"
          },
          {
            "lineNumber": 431,
            "text": "    ):"
          },
          {
            "lineNumber": 446,
            "text": "\\\"208\\\" response will not only be returned when the file is already fully known to the Shaman server, but also when someone else is currently uploading this file. . [optional]"
          },
          {
            "lineNumber": 447,
            "text": "            x_shaman_original_filename (str): The original filename. If sent along with the request, it will be included in the server logs, which can aid in debugging. MUST either be ASCII or encoded using RFC 2047 (aka MIME encoding). In the latter case the encoding MUST be UTF-8. . [optional]"
          },
          {
            "lineNumber": 448,
            "text": "            _return_http_data_only (bool): response data without head status"
          },
          {
            "lineNumber": 449,
            "text": "                code and headers. Default is True."
          },
          {
            "lineNumber": 450,
            "text": "            _preload_content (bool): if False, the urllib3.HTTPResponse object"
          },
          {
            "lineNumber": 451,
            "text": "                will be returned without reading/decoding response data."
          },
          {
            "lineNumber": 452,
            "text": "                Default is True."
          },
          {
            "lineNumber": 453,
            "text": "            _request_timeout (int/float/tuple): timeout setting for this request. If"
          },
          {
            "lineNumber": 454,
            "text": "                one number provided, it will be total request timeout. It can also"
          },
          {
            "lineNumber": 455,
            "text": "                be a pair (tuple) of (connection, read) timeouts."
          },
          {
            "lineNumber": 456,
            "text": "                Default is None."
          },
          {
            "lineNumber": 457,
            "text": "            _check_input_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 458,
            "text": "                should be done one the data sent to the server."
          },
          {
            "lineNumber": 459,
            "text": "                Default is True."
          },
          {
            "lineNumber": 460,
            "text": "            _check_return_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 461,
            "text": "                should be done one the data received from the server."
          },
          {
            "lineNumber": 462,
            "text": "                Default is True."
          },
          {
            "lineNumber": 463,
            "text": "            _spec_property_naming (bool): True if the variable names in the input data"
          },
          {
            "lineNumber": 464,
            "text": "                are serialized names, as specified in the OpenAPI document."
          },
          {
            "lineNumber": 465,
            "text": "                False if the variable names in the input data"
          },
          {
            "lineNumber": 466,
            "text": "                are pythonic names, e.g. snake case (default)"
          },
          {
            "lineNumber": 467,
            "text": "            _content_type (str/None): force body content-type."
          },
          {
            "lineNumber": 468,
            "text": "                Default is None and content-type will be predicted by allowed"
          },
          {
            "lineNumber": 469,
            "text": "                content-types and body."
          },
          {
            "lineNumber": 470,
            "text": "            _host_index (int/None): specifies the index of the server"
          },
          {
            "lineNumber": 471,
            "text": "                that we want to use."
          },
          {
            "lineNumber": 472,
            "text": "                Default is read from the configuration."
          },
          {
            "lineNumber": 473,
            "text": "            async_req (bool): execute request asynchronously"
          },
          {
            "lineNumber": 474
          },
          {
            "lineNumber": 475,
            "text": "        Returns:"
          },
          {
            "lineNumber": 476,
            "text": "            None"
          },
          {
            "lineNumber": 477,
            "text": "                If the method is called asynchronously, returns the request"
          },
          {
            "lineNumber": 478,
            "text": "                thread."
          },
          {
            "lineNumber": 479,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 480,
            "text": "        kwargs['async_req'] = kwargs.get("
          },
          {
            "lineNumber": 481,
            "text": "            'async_req', False"
          },
          {
            "lineNumber": 482,
            "text": "        )"
          }
        ],
        "range": {
          "endPosition": {
            "line": 590
          },
          "startPosition": {
            "line": 31
          }
        },
        "relativeWorkspacePath": ".\\manager\\api\\shaman_api.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 33
              },
              "startPosition": {
                "column": 1,
                "line": 32
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 432
              },
              "startPosition": {
                "column": 5,
                "line": 425
              }
            }
          ]
        }
      },
      "score": 0.4620836079120636
    },
    {
      "codeBlock": {
        "contents": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore\ndef _upload_files(\n        self, to_upload: deque[_ShamanFileSpec]\n    ) -\u003e set[HashableShamanFileSpec]:\n\n        while to_upload:\n            # After too many failures, just retry to get a fresh set of files to upload.\n            if len(failed_specs) \u003e MAX_FAILED_PATHS:\n                self.log.info(\"Too many failures, going to abort this iteration\")\n                failed_specs.update(make_file_specs_hashable_gen(to_upload))\n                return failed_specs\n\n            file_spec = to_upload.popleft()\n            self.log.info(\"   %s\", file_spec.path)\n\n            # Pre-flight check. The generated API code will load the entire file into\n            # memory before sending it to the Shaman. It's faster to do a check at\n            # Shaman first, to see if we need uploading at all.\n            check_resp = self.shaman_api.shaman_file_store_check(\n                checksum=file_spec.sha,\n                filesize=file_spec.size,\n            )\n            if check_resp.status.value == \"stored\":\n                self.log.info(\"  %s: skipping, already on server\", file_spec.path)\n                continue\n\n            # Let the Shaman know whether we can defer uploading this file or not.\n            hashable_file_spec = make_file_spec_hashable(file_spec)\n            can_defer = bool(\n                len(deferred_specs) \u003c MAX_DEFERRED_PATHS\n                and hashable_file_spec not in deferred_specs\n                and len(to_upload)\n            )\n\n            local_filepath = self._rel_to_local_path[file_spec.path]\n            filename_header = _encode_original_filename_header(file_spec.path)\n            try:\n                with local_filepath.open(\"rb\") as file_reader",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 99,
            "text": "class Transferrer(submodules.transfer.FileTransferer):  # type: ignore"
          },
          {
            "lineNumber": 303,
            "text": "def _upload_files("
          },
          {
            "lineNumber": 304,
            "text": "        self, to_upload: deque[_ShamanFileSpec]"
          },
          {
            "lineNumber": 305,
            "text": "    ) -\u003e set[HashableShamanFileSpec]:"
          },
          {
            "lineNumber": 339
          },
          {
            "lineNumber": 340,
            "text": "        while to_upload:"
          },
          {
            "lineNumber": 341,
            "text": "            # After too many failures, just retry to get a fresh set of files to upload."
          },
          {
            "lineNumber": 342,
            "text": "            if len(failed_specs) \u003e MAX_FAILED_PATHS:"
          },
          {
            "lineNumber": 343,
            "text": "                self.log.info(\"Too many failures, going to abort this iteration\")"
          },
          {
            "lineNumber": 344,
            "text": "                failed_specs.update(make_file_specs_hashable_gen(to_upload))"
          },
          {
            "lineNumber": 345,
            "text": "                return failed_specs"
          },
          {
            "lineNumber": 346
          },
          {
            "lineNumber": 347,
            "text": "            file_spec = to_upload.popleft()"
          },
          {
            "lineNumber": 348,
            "text": "            self.log.info(\"   %s\", file_spec.path)"
          },
          {
            "lineNumber": 349
          },
          {
            "lineNumber": 350,
            "text": "            # Pre-flight check. The generated API code will load the entire file into"
          },
          {
            "lineNumber": 351,
            "text": "            # memory before sending it to the Shaman. It's faster to do a check at"
          },
          {
            "lineNumber": 352,
            "text": "            # Shaman first, to see if we need uploading at all."
          },
          {
            "lineNumber": 353,
            "text": "            check_resp = self.shaman_api.shaman_file_store_check("
          },
          {
            "lineNumber": 354,
            "text": "                checksum=file_spec.sha,"
          },
          {
            "lineNumber": 355,
            "text": "                filesize=file_spec.size,"
          },
          {
            "lineNumber": 356,
            "text": "            )"
          },
          {
            "lineNumber": 357,
            "text": "            if check_resp.status.value == \"stored\":"
          },
          {
            "lineNumber": 358,
            "text": "                self.log.info(\"  %s: skipping, already on server\", file_spec.path)"
          },
          {
            "lineNumber": 359,
            "text": "                continue"
          },
          {
            "lineNumber": 360
          },
          {
            "lineNumber": 361,
            "text": "            # Let the Shaman know whether we can defer uploading this file or not."
          },
          {
            "lineNumber": 362,
            "text": "            hashable_file_spec = make_file_spec_hashable(file_spec)"
          },
          {
            "lineNumber": 363,
            "text": "            can_defer = bool("
          },
          {
            "lineNumber": 364,
            "text": "                len(deferred_specs) \u003c MAX_DEFERRED_PATHS"
          },
          {
            "lineNumber": 365,
            "text": "                and hashable_file_spec not in deferred_specs"
          },
          {
            "lineNumber": 366,
            "text": "                and len(to_upload)"
          },
          {
            "lineNumber": 367,
            "text": "            )"
          },
          {
            "lineNumber": 368
          },
          {
            "lineNumber": 369,
            "text": "            local_filepath = self._rel_to_local_path[file_spec.path]"
          },
          {
            "lineNumber": 370,
            "text": "            filename_header = _encode_original_filename_header(file_spec.path)"
          },
          {
            "lineNumber": 371,
            "text": "            try:"
          },
          {
            "lineNumber": 372,
            "text": "                with local_filepath.open(\"rb\") as file_reader"
          }
        ],
        "range": {
          "endPosition": {
            "line": 473
          },
          "startPosition": {
            "line": 98
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 100
              },
              "startPosition": {
                "column": 1,
                "line": 99
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 306
              },
              "startPosition": {
                "column": 5,
                "line": 303
              }
            }
          ]
        }
      },
      "score": 0.46101856231689453
    },
    {
      "codeBlock": {
        "contents": "### Return type\n\n[**ShamanRequirementsResponse**](ShamanRequirementsResponse.md)\n\n### Authorization\n\nNo authorization required\n\n### HTTP request headers\n\n - **Content-Type**: application/json\n - **Accept**: application/json\n\n\n### HTTP response details\n\n| Status code | Description | Response headers |\n|-------------|-------------|------------------|\n**200** | Subset of the posted requirements, indicating the unknown files. |  -  |\n**0** | unexpected error |  -  |\n\n[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)\n\n# **shaman_file_store**\n\u003e shaman_file_store(checksum, filesize, body)\n\nStore a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. \n\n### Example\n\n\n&#96;&#96;&#96;python\nimport time\nimport flamenco.manager\nfrom flamenco.manager.api import shaman_api\nfrom flamenco.manager.model.error import Error\nfrom pprint import pprint\n# Defining the host is optional and defaults to http://localhost\n# See configuration.py for a list of all supported configuration parameters.\nconfiguration = flamenco.manager.Configuration(\n    host = \"http://localhost\"\n)\n\n\n# Enter a context with an instance of the API client\nwith flamenco.manager.ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = shaman_api.ShamanApi(api_client)",
        "detailedLines": [
          {
            "lineNumber": 145,
            "text": "### Return type"
          },
          {
            "lineNumber": 146
          },
          {
            "lineNumber": 147,
            "text": "[**ShamanRequirementsResponse**](ShamanRequirementsResponse.md)"
          },
          {
            "lineNumber": 148
          },
          {
            "lineNumber": 149,
            "text": "### Authorization"
          },
          {
            "lineNumber": 150
          },
          {
            "lineNumber": 151,
            "text": "No authorization required"
          },
          {
            "lineNumber": 152
          },
          {
            "lineNumber": 153,
            "text": "### HTTP request headers"
          },
          {
            "lineNumber": 154
          },
          {
            "lineNumber": 155,
            "text": " - **Content-Type**: application/json"
          },
          {
            "lineNumber": 156,
            "text": " - **Accept**: application/json"
          },
          {
            "lineNumber": 157
          },
          {
            "lineNumber": 158
          },
          {
            "lineNumber": 159,
            "text": "### HTTP response details"
          },
          {
            "lineNumber": 160
          },
          {
            "lineNumber": 161,
            "text": "| Status code | Description | Response headers |"
          },
          {
            "lineNumber": 162,
            "text": "|-------------|-------------|------------------|"
          },
          {
            "lineNumber": 163,
            "text": "**200** | Subset of the posted requirements, indicating the unknown files. |  -  |"
          },
          {
            "lineNumber": 164,
            "text": "**0** | unexpected error |  -  |"
          },
          {
            "lineNumber": 165
          },
          {
            "lineNumber": 166,
            "text": "[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)"
          },
          {
            "lineNumber": 167
          },
          {
            "lineNumber": 168,
            "text": "# **shaman_file_store**"
          },
          {
            "lineNumber": 169,
            "text": "\u003e shaman_file_store(checksum, filesize, body)"
          },
          {
            "lineNumber": 170
          },
          {
            "lineNumber": 171,
            "text": "Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file's contents should be sent in the request body. "
          },
          {
            "lineNumber": 172
          },
          {
            "lineNumber": 173,
            "text": "### Example"
          },
          {
            "lineNumber": 174
          },
          {
            "lineNumber": 175
          },
          {
            "lineNumber": 176,
            "text": "&#96;&#96;&#96;python"
          },
          {
            "lineNumber": 177,
            "text": "import time"
          },
          {
            "lineNumber": 178,
            "text": "import flamenco.manager"
          },
          {
            "lineNumber": 179,
            "text": "from flamenco.manager.api import shaman_api"
          },
          {
            "lineNumber": 180,
            "text": "from flamenco.manager.model.error import Error"
          },
          {
            "lineNumber": 181,
            "text": "from pprint import pprint"
          },
          {
            "lineNumber": 182,
            "text": "# Defining the host is optional and defaults to http://localhost"
          },
          {
            "lineNumber": 183,
            "text": "# See configuration.py for a list of all supported configuration parameters."
          },
          {
            "lineNumber": 184,
            "text": "configuration = flamenco.manager.Configuration("
          },
          {
            "lineNumber": 185,
            "text": "    host = \"http://localhost\""
          },
          {
            "lineNumber": 186,
            "text": ")"
          },
          {
            "lineNumber": 187
          },
          {
            "lineNumber": 188
          },
          {
            "lineNumber": 189,
            "text": "# Enter a context with an instance of the API client"
          },
          {
            "lineNumber": 190,
            "text": "with flamenco.manager.ApiClient() as api_client:"
          },
          {
            "lineNumber": 191,
            "text": "    # Create an instance of the API class"
          },
          {
            "lineNumber": 192,
            "text": "    api_instance = shaman_api.ShamanApi(api_client)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 192
          },
          "startPosition": {
            "line": 143
          }
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.4560408592224121
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

Implementing: convert all relative paths to absolute, then bypass BAT and send the blend file as-is.


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Adding a function to convert relative paths to absolute, then updating the submission flow to bypass BAT:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Updating `_submit_files` to convert paths and bypass BAT, and adding a function to upload directly to Shaman:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\bat\cache.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": true,
  "path": "e:\\flamenco\\addon\\flamenco\\bat",
  "pattern": "compute.*checksum|sha256|hashlib"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\bat",
  "pattern": "compute.*checksum|sha256|hashlib",
  "topFiles": [
    {
      "matchCount": 1,
      "uri": "bat\\shaman.py"
    },
    {
      "matchCount": 8,
      "uri": "bat\\cache.py"
    }
  ],
  "totalFiles": 2,
  "totalMatches": 9
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

Updating the submission flow to bypass BAT:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Adding functions to upload to Shaman and copy to storage:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Fixing imports:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking for lint errors and verifying the implementation:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>


Parameters:

```json
{
  "paths": [
    "e:\\flamenco\\addon\\flamenco\\operators.py"
  ]
}
```
Result:

```json
{
  "linterErrorsByFile": [
    {
      "relativeWorkspacePath": "e:\\flamenco\\addon\\flamenco\\operators.py"
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Removing the unused variable and fixing the implementation:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking if we need to reload the blend file after converting paths, or if saving after conversion is sufficient:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking the execution flow to ensure paths are converted before saving:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking what `_presubmit_check` returns:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

The implementation looks correct. Verifying the Shaman upload function handles the return value correctly:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Fixing the return value for Shaman upload since it's synchronous:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking what `_quit` does to ensure the flow is correct:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "contextAfter": 10,
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "def _quit"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "def _quit",
  "topFiles": [
    {
      "matchCount": 11,
      "uri": "operators.py"
    }
  ],
  "totalFiles": 1,
  "totalMatches": 1
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Updating `_upload_blendfile_to_shaman` to return a status instead of calling `_quit`, so the caller handles cleanup:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Verifying the implementation is complete and correct:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>


Parameters:

```json
{
  "paths": [
    "e:\\flamenco\\addon\\flamenco\\operators.py"
  ]
}
```
Result:

```json
{
  "linterErrorsByFile": [
    {
      "relativeWorkspacePath": "e:\\flamenco\\addon\\flamenco\\operators.py"
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Fixing the log message to show the correct path conversion:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

## Summary

Bypassed BAT and changed the file submission flow:

1. **Added `_convert_relpaths_to_absolute`**: Converts relative paths (libraries, images, movies, sounds, fonts, volumes) to absolute paths in the blend file.

2. **Modified `_submit_files`**:
   - Converts all relative paths to absolute before saving
   - Saves the blend file with absolute paths
   - For Shaman: uploads the blend file directly without BAT
   - For filesystem: copies the blend file to job storage without BAT

3. **Added `_upload_blendfile_to_shaman`**: Uploads the blend file directly to Shaman, creates a checkout with just that file, and submits the job synchronously.

4. **Added `_copy_blendfile_to_storage`**: Copies the blend file to job storage without BAT.

The blend file is now sent as-is with all paths converted to absolute. BAT is bypassed, so:
- No BAT packing
- No `_outside_project` remapping
- No Shaman pointers for relative paths
- All paths are absolute and should be available on the network

The blend file is uploaded directly to Shaman or copied to job storage, with all relative paths converted to absolute before saving.

---

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt 

---

_**Agent (default)**_

Checking the terminal output for errors or issues:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking how to access the Shaman API:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "shaman_api|ShamanApi"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "shaman_api|ShamanApi",
  "topFiles": [
    {
      "matchCount": 4,
      "uri": ".\\operators.py"
    },
    {
      "matchCount": 41,
      "uri": ".\\.specstory\\history\\2026-01-28_20-03Z-flamenco-addon-file-sending.md"
    },
    {
      "matchCount": 4,
      "uri": ".\\manager_README.md"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\apis\\__init__.py"
    },
    {
      "matchCount": 6,
      "uri": ".\\bat\\shaman.py"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\api\\shaman_api.py"
    },
    {
      "matchCount": 18,
      "uri": ".\\manager\\docs\\ShamanApi.md"
    }
  ],
  "totalFiles": 7,
  "totalMatches": 75
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\bat\shaman.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking how ShamanApi is instantiated:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\bat\shaman.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Verifying the import path:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "from.*manager.*apis|from.*manager.*import.*ShamanApi"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco",
  "pattern": "from.*manager.*apis|from.*manager.*import.*ShamanApi",
  "topFiles": [
    {
      "matchCount": 1,
      "uri": ".\\operators.py"
    },
    {
      "matchCount": 2,
      "uri": ".\\.specstory\\history\\2026-01-28_20-03Z-flamenco-addon-file-sending.md"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager_README.md"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager_info.py"
    },
    {
      "matchCount": 1,
      "uri": ".\\bat\\shaman.py"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\apis\\__init__.py"
    },
    {
      "matchCount": 1,
      "uri": ".\\manager\\api\\__init__.py"
    }
  ],
  "totalFiles": 7,
  "totalMatches": 8
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\manager\apis\__init__.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

The import looks correct. Checking for other issues in the terminal output:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>


Parameters:

```json
{
  "paths": [
    "e:\\flamenco\\addon\\flamenco\\operators.py"
  ]
}
```
Result:

```json
{
  "linterErrorsByFile": [
    {
      "relativeWorkspacePath": "e:\\flamenco\\addon\\flamenco\\operators.py"
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>

</details></tool-use>

_**Agent (default)**_

Verifying the change was applied correctly:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

The fix is correct. The terminal error is from before the change. Reload the addon in Blender for the changes to take effect.

The code now correctly:
1. Imports `ShamanApi` from `.manager.apis`
2. Instantiates it with `ShamanApi(api_client)` instead of accessing a non-existent attribute

After reloading the addon, the Shaman upload should work. The implementation:
- Converts all relative paths to absolute paths
- Bypasses BAT entirely
- Uploads the blend file directly to Shaman
- Creates a checkout with just the blend file

This should resolve the issue where BAT was still sending all files and creating Shaman pointers.

---

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt 

---

_**Agent (default)**_

Checking the latest terminal output for errors:


_**Agent (default)**_

Offset 1000 is beyond file length (998 lines)

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking the `ShamanCheckout` model to confirm the required parameters:


_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "path": "e:\\flamenco\\addon\\flamenco\\manager",
  "pattern": "class ShamanCheckout|checkout_path|checkout_id"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\manager",
  "pattern": "class ShamanCheckout|checkout_path|checkout_id",
  "topFiles": [
    {
      "matchCount": 4,
      "uri": "manager\\model\\job_storage_info.py"
    },
    {
      "matchCount": 1,
      "uri": "manager\\docs\\JobStorageInfo.md"
    },
    {
      "matchCount": 2,
      "uri": "manager\\docs\\JobsApi.md"
    },
    {
      "matchCount": 9,
      "uri": "manager\\model\\shaman_checkout_result.py"
    },
    {
      "matchCount": 9,
      "uri": "manager\\model\\shaman_checkout.py"
    },
    {
      "matchCount": 1,
      "uri": "manager\\docs\\ShamanApi.md"
    },
    {
      "matchCount": 1,
      "uri": "manager\\docs\\ShamanCheckout.md"
    },
    {
      "matchCount": 1,
      "uri": "manager\\docs\\ShamanCheckoutResult.md"
    }
  ],
  "totalFiles": 8,
  "totalMatches": 28
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "What are the required parameters for ShamanCheckout constructor? How to create a ShamanCheckout?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class ShamanCheckout(ModelNormal):\n\n\n    @convert_js_args_to_python_args\n    def __init__(self, files, checkout_path, *args, **kwargs):  # noqa: E501\n        \"\"\"ShamanCheckout - a model defined in OpenAPI\n\n        Args:\n            files ([ShamanFileSpec]):\n            checkout_path (str): Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\"checkout ID\\\", but in this version it can be a path like &#96;project-slug/scene-name/unique-ID&#96;. \n\n        Keyword Args:\n            _check_type (bool): if True, values for parameters in openapi_types\n                                will be type checked and a TypeError will be\n                                raised if the wrong type is input.\n                                Defaults to True\n            _path_to_item (tuple/list): This is a list of keys or values to\n                                drill down to the model in received_data\n                                when deserializing a response\n            _spec_property_naming (bool): True if the variable names in the input data\n                                are serialized names, as specified in the OpenAPI document.\n                                False if the variable names in the input data\n                                are pythonic names, e.g. snake case (default)\n            _configuration (Configuration): the instance to use when\n                                deserializing a file_type parameter.\n                                If passed, type conversion is attempted\n                                If omitted no type conversion is done.\n            _visited_composed_classes (tuple): This stores a tuple of\n                                classes that we have traveled through so that\n                                if we see that class again we will not use its\n                                discriminator again.\n                                When traveling through a discriminator, the\n                                composed schema that is\n                                is traveled through is added to this set.\n                                For example if Animal has a discriminator\n                                petType and we pass in \"Dog\", and the class Dog\n                                allOf includes Animal, we move through Animal\n                                once using the discriminator, and pick Dog.\n                                Then in Dog, we will make an instance of the\n                                Animal class but this time we won't travel\n                                through its discriminator because we passed in\n                                _visited_composed_classes = (Animal,)\n        \"\"\"\n\n        _check_type = kwargs.pop('_check_type', True)",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 37,
            "text": "class ShamanCheckout(ModelNormal):"
          },
          {
            "lineNumber": 195
          },
          {
            "lineNumber": 196
          },
          {
            "lineNumber": 197,
            "text": "    @convert_js_args_to_python_args"
          },
          {
            "lineNumber": 198,
            "text": "    def __init__(self, files, checkout_path, *args, **kwargs):  # noqa: E501"
          },
          {
            "lineNumber": 199,
            "text": "        \"\"\"ShamanCheckout - a model defined in OpenAPI"
          },
          {
            "lineNumber": 200
          },
          {
            "lineNumber": 201,
            "text": "        Args:"
          },
          {
            "lineNumber": 202,
            "text": "            files ([ShamanFileSpec]):"
          },
          {
            "lineNumber": 203,
            "text": "            checkout_path (str): Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\"checkout ID\\\", but in this version it can be a path like &#96;project-slug/scene-name/unique-ID&#96;. "
          },
          {
            "lineNumber": 204
          },
          {
            "lineNumber": 205,
            "text": "        Keyword Args:"
          },
          {
            "lineNumber": 206,
            "text": "            _check_type (bool): if True, values for parameters in openapi_types"
          },
          {
            "lineNumber": 207,
            "text": "                                will be type checked and a TypeError will be"
          },
          {
            "lineNumber": 208,
            "text": "                                raised if the wrong type is input."
          },
          {
            "lineNumber": 209,
            "text": "                                Defaults to True"
          },
          {
            "lineNumber": 210,
            "text": "            _path_to_item (tuple/list): This is a list of keys or values to"
          },
          {
            "lineNumber": 211,
            "text": "                                drill down to the model in received_data"
          },
          {
            "lineNumber": 212,
            "text": "                                when deserializing a response"
          },
          {
            "lineNumber": 213,
            "text": "            _spec_property_naming (bool): True if the variable names in the input data"
          },
          {
            "lineNumber": 214,
            "text": "                                are serialized names, as specified in the OpenAPI document."
          },
          {
            "lineNumber": 215,
            "text": "                                False if the variable names in the input data"
          },
          {
            "lineNumber": 216,
            "text": "                                are pythonic names, e.g. snake case (default)"
          },
          {
            "lineNumber": 217,
            "text": "            _configuration (Configuration): the instance to use when"
          },
          {
            "lineNumber": 218,
            "text": "                                deserializing a file_type parameter."
          },
          {
            "lineNumber": 219,
            "text": "                                If passed, type conversion is attempted"
          },
          {
            "lineNumber": 220,
            "text": "                                If omitted no type conversion is done."
          },
          {
            "lineNumber": 221,
            "text": "            _visited_composed_classes (tuple): This stores a tuple of"
          },
          {
            "lineNumber": 222,
            "text": "                                classes that we have traveled through so that"
          },
          {
            "lineNumber": 223,
            "text": "                                if we see that class again we will not use its"
          },
          {
            "lineNumber": 224,
            "text": "                                discriminator again."
          },
          {
            "lineNumber": 225,
            "text": "                                When traveling through a discriminator, the"
          },
          {
            "lineNumber": 226,
            "text": "                                composed schema that is"
          },
          {
            "lineNumber": 227,
            "text": "                                is traveled through is added to this set."
          },
          {
            "lineNumber": 228,
            "text": "                                For example if Animal has a discriminator"
          },
          {
            "lineNumber": 229,
            "text": "                                petType and we pass in \"Dog\", and the class Dog"
          },
          {
            "lineNumber": 230,
            "text": "                                allOf includes Animal, we move through Animal"
          },
          {
            "lineNumber": 231,
            "text": "                                once using the discriminator, and pick Dog."
          },
          {
            "lineNumber": 232,
            "text": "                                Then in Dog, we will make an instance of the"
          },
          {
            "lineNumber": 233,
            "text": "                                Animal class but this time we won't travel"
          },
          {
            "lineNumber": 234,
            "text": "                                through its discriminator because we passed in"
          },
          {
            "lineNumber": 235,
            "text": "                                _visited_composed_classes = (Animal,)"
          },
          {
            "lineNumber": 236,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 237
          },
          {
            "lineNumber": 238,
            "text": "        _check_type = kwargs.pop('_check_type', True)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 272
          },
          "startPosition": {
            "line": 36
          }
        },
        "relativeWorkspacePath": ".\\manager\\model\\shaman_checkout.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 38
              },
              "startPosition": {
                "column": 1,
                "line": 37
              }
            }
          ]
        }
      },
      "score": 0.4781990945339203
    },
    {
      "codeBlock": {
        "contents": " ShamanSingleFileStatus\n\n\nclass ShamanApi(object):\n    \"\"\"NOTE: This class is auto generated by OpenAPI Generator\n    Ref: https://openapi-generator.tech\n\n    Do not edit the class manually.\n    \"\"\"\n\n    def __init__(self, api_client=None):\n        if api_client is None:\n            api_client = ApiClient()\n        self.api_client = api_client\n        self.shaman_checkout_endpoint = _Endpoint(\n            settings={\n                'response_type': (ShamanCheckoutResult,),\n                'auth': [],\n                'endpoint_path': '/api/v3/shaman/checkout/create',\n                'operation_id': 'shaman_checkout',\n                'http_method': 'POST',\n                'servers': None,\n            },\n            params_map={\n                'all': [\n                    'shaman_checkout',\n                ],\n                'required': [\n                    'shaman_checkout',\n                ],\n                'nullable': [\n                ],\n                'enum': [\n                ],\n                'validation': [\n                ]\n            },\n            root_map={\n                'validations': {\n                },\n                'allowed_values': {\n                },\n                'openapi_types': {\n                    'shaman_checkout':\n                        (ShamanCheckout,),\n                },\n                'attribute_map': {\n                },\n                'location_map': {\n                    'shaman_checkout': 'body',\n                },\n                'collection_format_map': {\n                }\n            },\n            headers_map={\n                'accept': [\n                    'application/json'\n                ],\n                'content_type': [\n                    'application/json'\n                ]\n            },\n            api_client=api_client\n        )\n        self.shaman_checkout_requirements_endpoint = _Endpoint(\n            settings={\n                'response_type': (ShamanRequirementsResponse,)",
        "detailedLines": [
          {
            "lineNumber": 29,
            "text": " ShamanSingleFileStatus"
          },
          {
            "lineNumber": 30
          },
          {
            "lineNumber": 31
          },
          {
            "isSignature": true,
            "lineNumber": 32,
            "text": "class ShamanApi(object):"
          },
          {
            "lineNumber": 33,
            "text": "    \"\"\"NOTE: This class is auto generated by OpenAPI Generator"
          },
          {
            "lineNumber": 34,
            "text": "    Ref: https://openapi-generator.tech"
          },
          {
            "lineNumber": 35
          },
          {
            "lineNumber": 36,
            "text": "    Do not edit the class manually."
          },
          {
            "lineNumber": 37,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 38
          },
          {
            "lineNumber": 39,
            "text": "    def __init__(self, api_client=None):"
          },
          {
            "lineNumber": 40,
            "text": "        if api_client is None:"
          },
          {
            "lineNumber": 41,
            "text": "            api_client = ApiClient()"
          },
          {
            "lineNumber": 42,
            "text": "        self.api_client = api_client"
          },
          {
            "lineNumber": 43,
            "text": "        self.shaman_checkout_endpoint = _Endpoint("
          },
          {
            "lineNumber": 44,
            "text": "            settings={"
          },
          {
            "lineNumber": 45,
            "text": "                'response_type': (ShamanCheckoutResult,),"
          },
          {
            "lineNumber": 46,
            "text": "                'auth': [],"
          },
          {
            "lineNumber": 47,
            "text": "                'endpoint_path': '/api/v3/shaman/checkout/create',"
          },
          {
            "lineNumber": 48,
            "text": "                'operation_id': 'shaman_checkout',"
          },
          {
            "lineNumber": 49,
            "text": "                'http_method': 'POST',"
          },
          {
            "lineNumber": 50,
            "text": "                'servers': None,"
          },
          {
            "lineNumber": 51,
            "text": "            },"
          },
          {
            "lineNumber": 52,
            "text": "            params_map={"
          },
          {
            "lineNumber": 53,
            "text": "                'all': ["
          },
          {
            "lineNumber": 54,
            "text": "                    'shaman_checkout',"
          },
          {
            "lineNumber": 55,
            "text": "                ],"
          },
          {
            "lineNumber": 56,
            "text": "                'required': ["
          },
          {
            "lineNumber": 57,
            "text": "                    'shaman_checkout',"
          },
          {
            "lineNumber": 58,
            "text": "                ],"
          },
          {
            "lineNumber": 59,
            "text": "                'nullable': ["
          },
          {
            "lineNumber": 60,
            "text": "                ],"
          },
          {
            "lineNumber": 61,
            "text": "                'enum': ["
          },
          {
            "lineNumber": 62,
            "text": "                ],"
          },
          {
            "lineNumber": 63,
            "text": "                'validation': ["
          },
          {
            "lineNumber": 64,
            "text": "                ]"
          },
          {
            "lineNumber": 65,
            "text": "            },"
          },
          {
            "lineNumber": 66,
            "text": "            root_map={"
          },
          {
            "lineNumber": 67,
            "text": "                'validations': {"
          },
          {
            "lineNumber": 68,
            "text": "                },"
          },
          {
            "lineNumber": 69,
            "text": "                'allowed_values': {"
          },
          {
            "lineNumber": 70,
            "text": "                },"
          },
          {
            "lineNumber": 71,
            "text": "                'openapi_types': {"
          },
          {
            "lineNumber": 72,
            "text": "                    'shaman_checkout':"
          },
          {
            "lineNumber": 73,
            "text": "                        (ShamanCheckout,),"
          },
          {
            "lineNumber": 74,
            "text": "                },"
          },
          {
            "lineNumber": 75,
            "text": "                'attribute_map': {"
          },
          {
            "lineNumber": 76,
            "text": "                },"
          },
          {
            "lineNumber": 77,
            "text": "                'location_map': {"
          },
          {
            "lineNumber": 78,
            "text": "                    'shaman_checkout': 'body',"
          },
          {
            "lineNumber": 79,
            "text": "                },"
          },
          {
            "lineNumber": 80,
            "text": "                'collection_format_map': {"
          },
          {
            "lineNumber": 81,
            "text": "                }"
          },
          {
            "lineNumber": 82,
            "text": "            },"
          },
          {
            "lineNumber": 83,
            "text": "            headers_map={"
          },
          {
            "lineNumber": 84,
            "text": "                'accept': ["
          },
          {
            "lineNumber": 85,
            "text": "                    'application/json'"
          },
          {
            "lineNumber": 86,
            "text": "                ],"
          },
          {
            "lineNumber": 87,
            "text": "                'content_type': ["
          },
          {
            "lineNumber": 88,
            "text": "                    'application/json'"
          },
          {
            "lineNumber": 89,
            "text": "                ]"
          },
          {
            "lineNumber": 90,
            "text": "            },"
          },
          {
            "lineNumber": 91,
            "text": "            api_client=api_client"
          },
          {
            "lineNumber": 92,
            "text": "        )"
          },
          {
            "lineNumber": 93,
            "text": "        self.shaman_checkout_requirements_endpoint = _Endpoint("
          },
          {
            "lineNumber": 94,
            "text": "            settings={"
          },
          {
            "lineNumber": 95,
            "text": "                'response_type': (ShamanRequirementsResponse,)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 590
          },
          "startPosition": {
            "column": 60,
            "line": 28
          }
        },
        "relativeWorkspacePath": ".\\manager\\api\\shaman_api.py",
        "signatures": {}
      },
      "score": 0.4697937071323395
    },
    {
      "codeBlock": {
        "contents": "with flamenco.manager.ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = shaman_api.ShamanApi(api_client)\n    shaman_checkout = ShamanCheckout(\n        files=[\n            ShamanFileSpec(\n                sha=\"sha_example\",\n                size=1,\n                path=\"path_example\",\n            ),\n        ],\n        checkout_path=\"checkout_path_example\",\n    ) # ShamanCheckout | Set of files to check out.\n\n    # example passing only required values which don't have defaults set\n    try:\n        # Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.\n        api_response = api_instance.shaman_checkout(shaman_checkout)\n        pprint(api_response)\n    except flamenco.manager.ApiException as e:\n        print(\"Exception when calling ShamanApi-\u003eshaman_checkout: %s\\n\" % e)\n&#96;&#96;&#96;\n\n\n### Parameters\n\nName | Type | Description  | Notes\n------------- | ------------- | ------------- | -------------\n **shaman_checkout** | [**ShamanCheckout**](ShamanCheckout.md)| Set of files to check out. |\n\n### Return type\n\n[**ShamanCheckoutResult**](ShamanCheckoutResult.md)\n\n### Authorization\n\nNo authorization required\n\n### HTTP request headers\n\n - **Content-Type**: application/json\n - **Accept**: application/json\n\n\n### HTTP response details\n\n| Status code | Description | Response headers |\n|-------------|-------------|------------------|\n**200** | Checkout was created successfully. |  -  |\n**424** | There were files missing. Use \u0026#x60;shamanCheckoutRequirements\u0026#x60; to figure out which ones. |  -  |\n**409** | Checkout already exists. |  -  |\n**0** | unexpected error |  -  |\n\n[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)\n\n# **shaman_checkout_requirements**\n\u003e ShamanRequirementsResponse shaman_checkout_requirements(shaman_requirements_request)\n\nChecks a Shaman Requirements file, and reports which files are unknown.",
        "detailedLines": [
          {
            "lineNumber": 37,
            "text": "with flamenco.manager.ApiClient() as api_client:"
          },
          {
            "lineNumber": 38,
            "text": "    # Create an instance of the API class"
          },
          {
            "lineNumber": 39,
            "text": "    api_instance = shaman_api.ShamanApi(api_client)"
          },
          {
            "lineNumber": 40,
            "text": "    shaman_checkout = ShamanCheckout("
          },
          {
            "lineNumber": 41,
            "text": "        files=["
          },
          {
            "lineNumber": 42,
            "text": "            ShamanFileSpec("
          },
          {
            "lineNumber": 43,
            "text": "                sha=\"sha_example\","
          },
          {
            "lineNumber": 44,
            "text": "                size=1,"
          },
          {
            "lineNumber": 45,
            "text": "                path=\"path_example\","
          },
          {
            "lineNumber": 46,
            "text": "            ),"
          },
          {
            "lineNumber": 47,
            "text": "        ],"
          },
          {
            "lineNumber": 48,
            "text": "        checkout_path=\"checkout_path_example\","
          },
          {
            "lineNumber": 49,
            "text": "    ) # ShamanCheckout | Set of files to check out."
          },
          {
            "lineNumber": 50
          },
          {
            "lineNumber": 51,
            "text": "    # example passing only required values which don't have defaults set"
          },
          {
            "lineNumber": 52,
            "text": "    try:"
          },
          {
            "lineNumber": 53,
            "text": "        # Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint."
          },
          {
            "lineNumber": 54,
            "text": "        api_response = api_instance.shaman_checkout(shaman_checkout)"
          },
          {
            "lineNumber": 55,
            "text": "        pprint(api_response)"
          },
          {
            "lineNumber": 56,
            "text": "    except flamenco.manager.ApiException as e:"
          },
          {
            "lineNumber": 57,
            "text": "        print(\"Exception when calling ShamanApi-\u003eshaman_checkout: %s\\n\" % e)"
          },
          {
            "lineNumber": 58,
            "text": "&#96;&#96;&#96;"
          },
          {
            "lineNumber": 59
          },
          {
            "lineNumber": 60
          },
          {
            "lineNumber": 61,
            "text": "### Parameters"
          },
          {
            "lineNumber": 62
          },
          {
            "lineNumber": 63,
            "text": "Name | Type | Description  | Notes"
          },
          {
            "lineNumber": 64,
            "text": "------------- | ------------- | ------------- | -------------"
          },
          {
            "lineNumber": 65,
            "text": " **shaman_checkout** | [**ShamanCheckout**](ShamanCheckout.md)| Set of files to check out. |"
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "### Return type"
          },
          {
            "lineNumber": 68
          },
          {
            "lineNumber": 69,
            "text": "[**ShamanCheckoutResult**](ShamanCheckoutResult.md)"
          },
          {
            "lineNumber": 70
          },
          {
            "lineNumber": 71,
            "text": "### Authorization"
          },
          {
            "lineNumber": 72
          },
          {
            "lineNumber": 73,
            "text": "No authorization required"
          },
          {
            "lineNumber": 74
          },
          {
            "lineNumber": 75,
            "text": "### HTTP request headers"
          },
          {
            "lineNumber": 76
          },
          {
            "lineNumber": 77,
            "text": " - **Content-Type**: application/json"
          },
          {
            "lineNumber": 78,
            "text": " - **Accept**: application/json"
          },
          {
            "lineNumber": 79
          },
          {
            "lineNumber": 80
          },
          {
            "lineNumber": 81,
            "text": "### HTTP response details"
          },
          {
            "lineNumber": 82
          },
          {
            "lineNumber": 83,
            "text": "| Status code | Description | Response headers |"
          },
          {
            "lineNumber": 84,
            "text": "|-------------|-------------|------------------|"
          },
          {
            "lineNumber": 85,
            "text": "**200** | Checkout was created successfully. |  -  |"
          },
          {
            "lineNumber": 86,
            "text": "**424** | There were files missing. Use \u0026#x60;shamanCheckoutRequirements\u0026#x60; to figure out which ones. |  -  |"
          },
          {
            "lineNumber": 87,
            "text": "**409** | Checkout already exists. |  -  |"
          },
          {
            "lineNumber": 88,
            "text": "**0** | unexpected error |  -  |"
          },
          {
            "lineNumber": 89
          },
          {
            "lineNumber": 90,
            "text": "[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)"
          },
          {
            "lineNumber": 91
          },
          {
            "lineNumber": 92,
            "text": "# **shaman_checkout_requirements**"
          },
          {
            "lineNumber": 93,
            "text": "\u003e ShamanRequirementsResponse shaman_checkout_requirements(shaman_requirements_request)"
          },
          {
            "lineNumber": 94
          },
          {
            "lineNumber": 95,
            "text": "Checks a Shaman Requirements file, and reports which files are unknown."
          }
        ],
        "range": {
          "endPosition": {
            "line": 96
          },
          "startPosition": {
            "line": 36
          }
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.44735950231552124
    },
    {
      "codeBlock": {
        "contents": "class ShamanCheckout(ModelNormal):\n@convert_js_args_to_python_args\ndef __init__(self, files, checkout_path, *args, **kwargs):  # noqa: E501\n\"\"\"\n\n        _check_type = kwargs.pop('_check_type', True)\n        _spec_property_naming = kwargs.pop('_spec_property_naming', False)\n        _path_to_item = kwargs.pop('_path_to_item', ())\n        _configuration = kwargs.pop('_configuration', None)\n        _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())\n\n        if args:\n            raise ApiTypeError(\n                \"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments.\" % (\n                    args,\n                    self.__class__.__name__,\n                ),\n                path_to_item=_path_to_item,\n                valid_classes=(self.__class__,),\n            )\n\n        self._data_store = {}\n        self._check_type = _check_type\n        self._spec_property_naming = _spec_property_naming\n        self._path_to_item = _path_to_item\n        self._configuration = _configuration\n        self._visited_composed_classes = _visited_composed_classes + (self.__class__,)\n\n        self.files = files\n        self.checkout_path = checkout_path\n        for var_name, var_value in kwargs.items():\n            if var_name not in self.attribute_map and \\\n                        self._configuration is not None and \\\n                        self._configuration.discard_unknown_keys and \\\n                        self.additional_properties_type is None:\n                # discard variable.\n                continue\n            setattr(self, var_name, var_value)\n            if var_name in self.read_only_vars:\n                raise ApiAttributeError(f\"&#96;{var_name}&#96; is a read-only attribute. Use &#96;from_openapi_data&#96; to instantiate \"\n                                     f\"class with read only attributes.\")",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 37,
            "text": "class ShamanCheckout(ModelNormal):"
          },
          {
            "lineNumber": 197,
            "text": "@convert_js_args_to_python_args"
          },
          {
            "lineNumber": 198,
            "text": "def __init__(self, files, checkout_path, *args, **kwargs):  # noqa: E501"
          },
          {
            "lineNumber": 236,
            "text": "\"\"\""
          },
          {
            "lineNumber": 237
          },
          {
            "lineNumber": 238,
            "text": "        _check_type = kwargs.pop('_check_type', True)"
          },
          {
            "lineNumber": 239,
            "text": "        _spec_property_naming = kwargs.pop('_spec_property_naming', False)"
          },
          {
            "lineNumber": 240,
            "text": "        _path_to_item = kwargs.pop('_path_to_item', ())"
          },
          {
            "lineNumber": 241,
            "text": "        _configuration = kwargs.pop('_configuration', None)"
          },
          {
            "lineNumber": 242,
            "text": "        _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())"
          },
          {
            "lineNumber": 243
          },
          {
            "lineNumber": 244,
            "text": "        if args:"
          },
          {
            "lineNumber": 245,
            "text": "            raise ApiTypeError("
          },
          {
            "lineNumber": 246,
            "text": "                \"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments.\" % ("
          },
          {
            "lineNumber": 247,
            "text": "                    args,"
          },
          {
            "lineNumber": 248,
            "text": "                    self.__class__.__name__,"
          },
          {
            "lineNumber": 249,
            "text": "                ),"
          },
          {
            "lineNumber": 250,
            "text": "                path_to_item=_path_to_item,"
          },
          {
            "lineNumber": 251,
            "text": "                valid_classes=(self.__class__,),"
          },
          {
            "lineNumber": 252,
            "text": "            )"
          },
          {
            "lineNumber": 253
          },
          {
            "lineNumber": 254,
            "text": "        self._data_store = {}"
          },
          {
            "lineNumber": 255,
            "text": "        self._check_type = _check_type"
          },
          {
            "lineNumber": 256,
            "text": "        self._spec_property_naming = _spec_property_naming"
          },
          {
            "lineNumber": 257,
            "text": "        self._path_to_item = _path_to_item"
          },
          {
            "lineNumber": 258,
            "text": "        self._configuration = _configuration"
          },
          {
            "lineNumber": 259,
            "text": "        self._visited_composed_classes = _visited_composed_classes + (self.__class__,)"
          },
          {
            "lineNumber": 260
          },
          {
            "lineNumber": 261,
            "text": "        self.files = files"
          },
          {
            "lineNumber": 262,
            "text": "        self.checkout_path = checkout_path"
          },
          {
            "lineNumber": 263,
            "text": "        for var_name, var_value in kwargs.items():"
          },
          {
            "lineNumber": 264,
            "text": "            if var_name not in self.attribute_map and \\"
          },
          {
            "lineNumber": 265,
            "text": "                        self._configuration is not None and \\"
          },
          {
            "lineNumber": 266,
            "text": "                        self._configuration.discard_unknown_keys and \\"
          },
          {
            "lineNumber": 267,
            "text": "                        self.additional_properties_type is None:"
          },
          {
            "lineNumber": 268,
            "text": "                # discard variable."
          },
          {
            "lineNumber": 269,
            "text": "                continue"
          },
          {
            "lineNumber": 270,
            "text": "            setattr(self, var_name, var_value)"
          },
          {
            "lineNumber": 271,
            "text": "            if var_name in self.read_only_vars:"
          },
          {
            "lineNumber": 272,
            "text": "                raise ApiAttributeError(f\"&#96;{var_name}&#96; is a read-only attribute. Use &#96;from_openapi_data&#96; to instantiate \""
          },
          {
            "isSignature": true,
            "lineNumber": 273,
            "text": "                                     f\"class with read only attributes.\")"
          }
        ],
        "range": {
          "endPosition": {
            "column": 73,
            "line": 272
          },
          "startPosition": {
            "line": 36
          }
        },
        "relativeWorkspacePath": ".\\manager\\model\\shaman_checkout.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 38
              },
              "startPosition": {
                "column": 1,
                "line": 37
              }
            },
            {
              "endPosition": {
                "column": 5,
                "line": 198
              },
              "startPosition": {
                "column": 5,
                "line": 197
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 199
              },
              "startPosition": {
                "column": 5,
                "line": 198
              }
            }
          ]
        }
      },
      "score": 0.43465590476989746
    },
    {
      "codeBlock": {
        "contents": "# flamenco.manager.ShamanApi\n\nAll URIs are relative to *http://localhost*\n\nMethod | HTTP request | Description\n------------- | ------------- | -------------\n[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /api/v3/shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.\n[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /api/v3/shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown.\n[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /api/v3/shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file\u0026#39;s contents should be sent in the request body. \n[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **GET** /api/v3/shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server. \n\n\n# **shaman_checkout**\n\u003e ShamanCheckoutResult shaman_checkout(shaman_checkout)\n\nCreate a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.\n\n### Example\n\n\n&#96;&#96;&#96;python\nimport time\nimport flamenco.manager\nfrom flamenco.manager.api import shaman_api\nfrom flamenco.manager.model.error import Error\nfrom flamenco.manager.model.shaman_checkout import ShamanCheckout\nfrom flamenco.manager.model.shaman_checkout_result import ShamanCheckoutResult\nfrom pprint import pprint\n# Defining the host is optional and defaults to http://localhost\n# See configuration.py for a list of all supported configuration parameters.\nconfiguration = flamenco.manager.Configuration(\n    host = \"http://localhost\"\n)\n\n\n# Enter a context with an instance of the API client\nwith flamenco.manager.ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = shaman_api.ShamanApi(api_client)",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# flamenco.manager.ShamanApi"
          },
          {
            "lineNumber": 2
          },
          {
            "lineNumber": 3,
            "text": "All URIs are relative to *http://localhost*"
          },
          {
            "lineNumber": 4
          },
          {
            "lineNumber": 5,
            "text": "Method | HTTP request | Description"
          },
          {
            "lineNumber": 6,
            "text": "------------- | ------------- | -------------"
          },
          {
            "lineNumber": 7,
            "text": "[**shaman_checkout**](ShamanApi.md#shaman_checkout) | **POST** /api/v3/shaman/checkout/create | Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint."
          },
          {
            "lineNumber": 8,
            "text": "[**shaman_checkout_requirements**](ShamanApi.md#shaman_checkout_requirements) | **POST** /api/v3/shaman/checkout/requirements | Checks a Shaman Requirements file, and reports which files are unknown."
          },
          {
            "lineNumber": 9,
            "text": "[**shaman_file_store**](ShamanApi.md#shaman_file_store) | **POST** /api/v3/shaman/files/{checksum}/{filesize} | Store a new file on the Shaman server. Note that the Shaman server can forcibly close the HTTP connection when another client finishes uploading the exact same file, to prevent double uploads. The file\u0026#39;s contents should be sent in the request body. "
          },
          {
            "lineNumber": 10,
            "text": "[**shaman_file_store_check**](ShamanApi.md#shaman_file_store_check) | **GET** /api/v3/shaman/files/{checksum}/{filesize} | Check the status of a file on the Shaman server. "
          },
          {
            "lineNumber": 11
          },
          {
            "lineNumber": 12
          },
          {
            "lineNumber": 13,
            "text": "# **shaman_checkout**"
          },
          {
            "lineNumber": 14,
            "text": "\u003e ShamanCheckoutResult shaman_checkout(shaman_checkout)"
          },
          {
            "lineNumber": 15
          },
          {
            "lineNumber": 16,
            "text": "Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint."
          },
          {
            "lineNumber": 17
          },
          {
            "lineNumber": 18,
            "text": "### Example"
          },
          {
            "lineNumber": 19
          },
          {
            "lineNumber": 20
          },
          {
            "lineNumber": 21,
            "text": "&#96;&#96;&#96;python"
          },
          {
            "lineNumber": 22,
            "text": "import time"
          },
          {
            "lineNumber": 23,
            "text": "import flamenco.manager"
          },
          {
            "lineNumber": 24,
            "text": "from flamenco.manager.api import shaman_api"
          },
          {
            "lineNumber": 25,
            "text": "from flamenco.manager.model.error import Error"
          },
          {
            "lineNumber": 26,
            "text": "from flamenco.manager.model.shaman_checkout import ShamanCheckout"
          },
          {
            "lineNumber": 27,
            "text": "from flamenco.manager.model.shaman_checkout_result import ShamanCheckoutResult"
          },
          {
            "lineNumber": 28,
            "text": "from pprint import pprint"
          },
          {
            "lineNumber": 29,
            "text": "# Defining the host is optional and defaults to http://localhost"
          },
          {
            "lineNumber": 30,
            "text": "# See configuration.py for a list of all supported configuration parameters."
          },
          {
            "lineNumber": 31,
            "text": "configuration = flamenco.manager.Configuration("
          },
          {
            "lineNumber": 32,
            "text": "    host = \"http://localhost\""
          },
          {
            "lineNumber": 33,
            "text": ")"
          },
          {
            "lineNumber": 34
          },
          {
            "lineNumber": 35
          },
          {
            "lineNumber": 36,
            "text": "# Enter a context with an instance of the API client"
          },
          {
            "lineNumber": 37,
            "text": "with flamenco.manager.ApiClient() as api_client:"
          },
          {
            "lineNumber": 38,
            "text": "    # Create an instance of the API class"
          },
          {
            "lineNumber": 39,
            "text": "    api_instance = shaman_api.ShamanApi(api_client)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 39
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanApi.md",
        "signatures": {}
      },
      "score": 0.42464345693588257
    },
    {
      "codeBlock": {
        "contents": "# ShamanCheckout\n\nSet of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.\n\n## Properties\nName | Type | Description | Notes\n------------ | ------------- | ------------- | -------------\n**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) |  | \n**checkout_path** | **str** | Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\u0026quot;checkout ID\\\u0026quot;, but in this version it can be a path like \u0026#x60;project-slug/scene-name/unique-ID\u0026#x60;.  | \n**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]\n\n[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# ShamanCheckout"
          },
          {
            "lineNumber": 2
          },
          {
            "lineNumber": 3,
            "text": "Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory."
          },
          {
            "lineNumber": 4
          },
          {
            "lineNumber": 5,
            "text": "## Properties"
          },
          {
            "lineNumber": 6,
            "text": "Name | Type | Description | Notes"
          },
          {
            "lineNumber": 7,
            "text": "------------ | ------------- | ------------- | -------------"
          },
          {
            "lineNumber": 8,
            "text": "**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) |  | "
          },
          {
            "lineNumber": 9,
            "text": "**checkout_path** | **str** | Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\u0026quot;checkout ID\\\u0026quot;, but in this version it can be a path like \u0026#x60;project-slug/scene-name/unique-ID\u0026#x60;.  | "
          },
          {
            "lineNumber": 10,
            "text": "**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]"
          },
          {
            "lineNumber": 11
          },
          {
            "lineNumber": 12,
            "text": "[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 14
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanCheckout.md",
        "signatures": {}
      },
      "score": 0.40167000889778137
    },
    {
      "codeBlock": {
        "contents": "class ShamanCheckout(ModelNormal):\n@classmethod\n    @convert_js_args_to_python_args\ndef _from_openapi_data(cls, files, checkout_path, *args, **kwargs):  # noqa: E501\n\"\"\"ShamanCheckout - a model defined in OpenAPI\n\n        Args:\n            files ([ShamanFileSpec]):\n            checkout_path (str): Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\"checkout ID\\\", but in this version it can be a path like &#96;project-slug/scene-name/unique-ID&#96;. \n\n        Keyword Args:\n            _check_type (bool): if True, values for parameters in openapi_types\n                                will be type checked and a TypeError will be\n                                raised if the wrong type is input.\n                                Defaults to True\n            _path_to_item (tuple/list): This is a list of keys or values to\n                                drill down to the model in received_data\n                                when deserializing a response\n            _spec_property_naming (bool): True if the variable names in the input data\n                                are serialized names, as specified in the OpenAPI document.\n                                False if the variable names in the input data\n                                are pythonic names, e.g. snake case (default)\n            _configuration (Configuration): the instance to use when\n                                deserializing a file_type parameter.\n                                If passed, type conversion is attempted\n                                If omitted no type conversion is done.\n            _visited_composed_classes (tuple): This stores a tuple of\n                                classes that we have traveled through so that\n                                if we see that class again we will not use its\n                                discriminator again.\n                                When traveling through a discriminator, the\n                                composed schema that is\n                                is traveled through is added to this set.\n                                For example if Animal has a discriminator\n                                petType and we pass in \"Dog\", and the class Dog\n                                allOf includes Animal, we move through Animal\n                                once using the discriminator, and pick Dog.\n                                Then in Dog, we will make an instance of the\n                                Animal class but this time we won't travel\n                                through its discriminator because we passed in\n                                _visited_composed_classes = (Animal,)\n        \"\"\"\n\n        _check_type = kwargs.pop('_check_type', True)",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 37,
            "text": "class ShamanCheckout(ModelNormal):"
          },
          {
            "lineNumber": 109,
            "text": "@classmethod"
          },
          {
            "lineNumber": 110,
            "text": "    @convert_js_args_to_python_args"
          },
          {
            "lineNumber": 111,
            "text": "def _from_openapi_data(cls, files, checkout_path, *args, **kwargs):  # noqa: E501"
          },
          {
            "lineNumber": 112,
            "text": "\"\"\"ShamanCheckout - a model defined in OpenAPI"
          },
          {
            "lineNumber": 113
          },
          {
            "lineNumber": 114,
            "text": "        Args:"
          },
          {
            "lineNumber": 115,
            "text": "            files ([ShamanFileSpec]):"
          },
          {
            "lineNumber": 116,
            "text": "            checkout_path (str): Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\"checkout ID\\\", but in this version it can be a path like &#96;project-slug/scene-name/unique-ID&#96;. "
          },
          {
            "lineNumber": 117
          },
          {
            "lineNumber": 118,
            "text": "        Keyword Args:"
          },
          {
            "lineNumber": 119,
            "text": "            _check_type (bool): if True, values for parameters in openapi_types"
          },
          {
            "lineNumber": 120,
            "text": "                                will be type checked and a TypeError will be"
          },
          {
            "lineNumber": 121,
            "text": "                                raised if the wrong type is input."
          },
          {
            "lineNumber": 122,
            "text": "                                Defaults to True"
          },
          {
            "lineNumber": 123,
            "text": "            _path_to_item (tuple/list): This is a list of keys or values to"
          },
          {
            "lineNumber": 124,
            "text": "                                drill down to the model in received_data"
          },
          {
            "lineNumber": 125,
            "text": "                                when deserializing a response"
          },
          {
            "lineNumber": 126,
            "text": "            _spec_property_naming (bool): True if the variable names in the input data"
          },
          {
            "lineNumber": 127,
            "text": "                                are serialized names, as specified in the OpenAPI document."
          },
          {
            "lineNumber": 128,
            "text": "                                False if the variable names in the input data"
          },
          {
            "lineNumber": 129,
            "text": "                                are pythonic names, e.g. snake case (default)"
          },
          {
            "lineNumber": 130,
            "text": "            _configuration (Configuration): the instance to use when"
          },
          {
            "lineNumber": 131,
            "text": "                                deserializing a file_type parameter."
          },
          {
            "lineNumber": 132,
            "text": "                                If passed, type conversion is attempted"
          },
          {
            "lineNumber": 133,
            "text": "                                If omitted no type conversion is done."
          },
          {
            "lineNumber": 134,
            "text": "            _visited_composed_classes (tuple): This stores a tuple of"
          },
          {
            "lineNumber": 135,
            "text": "                                classes that we have traveled through so that"
          },
          {
            "lineNumber": 136,
            "text": "                                if we see that class again we will not use its"
          },
          {
            "lineNumber": 137,
            "text": "                                discriminator again."
          },
          {
            "lineNumber": 138,
            "text": "                                When traveling through a discriminator, the"
          },
          {
            "lineNumber": 139,
            "text": "                                composed schema that is"
          },
          {
            "lineNumber": 140,
            "text": "                                is traveled through is added to this set."
          },
          {
            "lineNumber": 141,
            "text": "                                For example if Animal has a discriminator"
          },
          {
            "lineNumber": 142,
            "text": "                                petType and we pass in \"Dog\", and the class Dog"
          },
          {
            "lineNumber": 143,
            "text": "                                allOf includes Animal, we move through Animal"
          },
          {
            "lineNumber": 144,
            "text": "                                once using the discriminator, and pick Dog."
          },
          {
            "lineNumber": 145,
            "text": "                                Then in Dog, we will make an instance of the"
          },
          {
            "lineNumber": 146,
            "text": "                                Animal class but this time we won't travel"
          },
          {
            "lineNumber": 147,
            "text": "                                through its discriminator because we passed in"
          },
          {
            "lineNumber": 148,
            "text": "                                _visited_composed_classes = (Animal,)"
          },
          {
            "lineNumber": 149,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 150
          },
          {
            "lineNumber": 151,
            "text": "        _check_type = kwargs.pop('_check_type', True)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 272
          },
          "startPosition": {
            "line": 36
          }
        },
        "relativeWorkspacePath": ".\\manager\\model\\shaman_checkout.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 38
              },
              "startPosition": {
                "column": 1,
                "line": 37
              }
            },
            {
              "endPosition": {
                "column": 5,
                "line": 111
              },
              "startPosition": {
                "column": 5,
                "line": 109
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 112
              },
              "startPosition": {
                "column": 5,
                "line": 111
              }
            }
          ]
        }
      },
      "score": 0.398191899061203
    },
    {
      "codeBlock": {
        "contents": "class ShamanApi(object):\ndef __init__(self, api_client=None):\n\n        )\n\n    def shaman_checkout(\n        self,\n        shaman_checkout,\n        **kwargs\n    ):\n        \"\"\"Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.  # noqa: E501\n\n        This method makes a synchronous HTTP request by default. To make an\n        asynchronous HTTP request, please pass async_req=True\n\n        \u003e\u003e\u003e thread = api.shaman_checkout(shaman_checkout, async_req=True)\n        \u003e\u003e\u003e result = thread.get()\n\n        Args:\n            shaman_checkout (ShamanCheckout): Set of files to check out.\n\n        Keyword Args:\n            _return_http_data_only (bool): response data without head status\n                code and headers. Default is True.\n            _preload_content (bool): if False, the urllib3.HTTPResponse object\n                will be returned without reading/decoding response data.\n                Default is True.\n            _request_timeout (int/float/tuple): timeout setting for this request. If\n                one number provided, it will be total request timeout. It can also\n                be a pair (tuple) of (connection, read) timeouts.\n                Default is None.\n            _check_input_type (bool): specifies if type checking\n                should be done one the data sent to the server.\n                Default is True.\n            _check_return_type (bool): specifies if type checking\n                should be done one the data received from the server.\n                Default is True.\n            _spec_property_naming (bool): True if the variable names in the input data\n                are serialized names, as specified in the OpenAPI document.\n                False if the variable names in the input data\n                are pythonic names, e.g. snake case (default)\n            _content_type (str/None): force body content-type.\n                Default is None and content-type will be predicted by allowed\n                content-types and body.\n            _host_index (int/None): specifies the index of the server\n                that we want to use.\n                Default is read from the configuration.\n            async_req (bool): execute request asynchronously\n\n        Returns:\n            ShamanCheckoutResult\n                If the method is called asynchronously, returns the request\n                thread.\n        \"\"\"\n        kwargs['async_req'] = kwargs.get(\n            'async_req', False\n        )",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 32,
            "text": "class ShamanApi(object):"
          },
          {
            "lineNumber": 39,
            "text": "def __init__(self, api_client=None):"
          },
          {
            "lineNumber": 268
          },
          {
            "lineNumber": 269,
            "text": "        )"
          },
          {
            "lineNumber": 270
          },
          {
            "lineNumber": 271,
            "text": "    def shaman_checkout("
          },
          {
            "lineNumber": 272,
            "text": "        self,"
          },
          {
            "lineNumber": 273,
            "text": "        shaman_checkout,"
          },
          {
            "lineNumber": 274,
            "text": "        **kwargs"
          },
          {
            "lineNumber": 275,
            "text": "    ):"
          },
          {
            "lineNumber": 276,
            "text": "        \"\"\"Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.  # noqa: E501"
          },
          {
            "lineNumber": 277
          },
          {
            "lineNumber": 278,
            "text": "        This method makes a synchronous HTTP request by default. To make an"
          },
          {
            "lineNumber": 279,
            "text": "        asynchronous HTTP request, please pass async_req=True"
          },
          {
            "lineNumber": 280
          },
          {
            "lineNumber": 281,
            "text": "        \u003e\u003e\u003e thread = api.shaman_checkout(shaman_checkout, async_req=True)"
          },
          {
            "lineNumber": 282,
            "text": "        \u003e\u003e\u003e result = thread.get()"
          },
          {
            "lineNumber": 283
          },
          {
            "lineNumber": 284,
            "text": "        Args:"
          },
          {
            "lineNumber": 285,
            "text": "            shaman_checkout (ShamanCheckout): Set of files to check out."
          },
          {
            "lineNumber": 286
          },
          {
            "lineNumber": 287,
            "text": "        Keyword Args:"
          },
          {
            "lineNumber": 288,
            "text": "            _return_http_data_only (bool): response data without head status"
          },
          {
            "lineNumber": 289,
            "text": "                code and headers. Default is True."
          },
          {
            "lineNumber": 290,
            "text": "            _preload_content (bool): if False, the urllib3.HTTPResponse object"
          },
          {
            "lineNumber": 291,
            "text": "                will be returned without reading/decoding response data."
          },
          {
            "lineNumber": 292,
            "text": "                Default is True."
          },
          {
            "lineNumber": 293,
            "text": "            _request_timeout (int/float/tuple): timeout setting for this request. If"
          },
          {
            "lineNumber": 294,
            "text": "                one number provided, it will be total request timeout. It can also"
          },
          {
            "lineNumber": 295,
            "text": "                be a pair (tuple) of (connection, read) timeouts."
          },
          {
            "lineNumber": 296,
            "text": "                Default is None."
          },
          {
            "lineNumber": 297,
            "text": "            _check_input_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 298,
            "text": "                should be done one the data sent to the server."
          },
          {
            "lineNumber": 299,
            "text": "                Default is True."
          },
          {
            "lineNumber": 300,
            "text": "            _check_return_type (bool): specifies if type checking"
          },
          {
            "lineNumber": 301,
            "text": "                should be done one the data received from the server."
          },
          {
            "lineNumber": 302,
            "text": "                Default is True."
          },
          {
            "lineNumber": 303,
            "text": "            _spec_property_naming (bool): True if the variable names in the input data"
          },
          {
            "lineNumber": 304,
            "text": "                are serialized names, as specified in the OpenAPI document."
          },
          {
            "lineNumber": 305,
            "text": "                False if the variable names in the input data"
          },
          {
            "lineNumber": 306,
            "text": "                are pythonic names, e.g. snake case (default)"
          },
          {
            "lineNumber": 307,
            "text": "            _content_type (str/None): force body content-type."
          },
          {
            "lineNumber": 308,
            "text": "                Default is None and content-type will be predicted by allowed"
          },
          {
            "lineNumber": 309,
            "text": "                content-types and body."
          },
          {
            "lineNumber": 310,
            "text": "            _host_index (int/None): specifies the index of the server"
          },
          {
            "lineNumber": 311,
            "text": "                that we want to use."
          },
          {
            "lineNumber": 312,
            "text": "                Default is read from the configuration."
          },
          {
            "lineNumber": 313,
            "text": "            async_req (bool): execute request asynchronously"
          },
          {
            "lineNumber": 314
          },
          {
            "lineNumber": 315,
            "text": "        Returns:"
          },
          {
            "lineNumber": 316,
            "text": "            ShamanCheckoutResult"
          },
          {
            "lineNumber": 317,
            "text": "                If the method is called asynchronously, returns the request"
          },
          {
            "lineNumber": 318,
            "text": "                thread."
          },
          {
            "lineNumber": 319,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 320,
            "text": "        kwargs['async_req'] = kwargs.get("
          },
          {
            "lineNumber": 321,
            "text": "            'async_req', False"
          },
          {
            "lineNumber": 322,
            "text": "        )"
          }
        ],
        "range": {
          "endPosition": {
            "line": 590
          },
          "startPosition": {
            "line": 31
          }
        },
        "relativeWorkspacePath": ".\\manager\\api\\shaman_api.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 33
              },
              "startPosition": {
                "column": 1,
                "line": 32
              }
            }
          ]
        }
      },
      "score": 0.3950954079627991
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

Updating the `ShamanCheckout` constructor call: `files` first, then `checkout_path` as a positional argument.


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Checking the result property name:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

Fixed two issues:

1. **ShamanCheckout constructor**: Changed `checkout_id` to `checkout_path` and made it a positional argument (second parameter after `files`).
2. **Result property**: Changed `result.checkout_id` to `result.checkout_path` when reading the checkout result.

The code should now work correctly. The `ShamanCheckout` constructor requires:
- `files` (first positional argument) - list of `ShamanFileSpec` objects
- `checkout_path` (second positional argument) - string path for the checkout

After reloading the addon in Blender, the Shaman upload should work. The implementation will:
1. Convert all relative paths to absolute
2. Upload the blend file directly to Shaman
3. Create a checkout with just that blend file
4. Submit the job

This bypasses BAT entirely and sends the blend file as-is with all absolute paths.

---

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt looks like it's working

---

_**Agent (default)**_

Checking the terminal output to confirm it's working:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\3.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

The terminal output shows:

1. **Line 974-975**: The blend file was saved as `"ADTA Stow 2026 updates_animation 3c.flamenco.blend"` (with absolute paths converted)

2. **Line 976**: **"Job submitted: ADTA Stow 2026 updates_animation 3c (c52693a4-1ff0-40d1-bea8-04f4e251e6ae)"** — job submitted successfully

The implementation is working. The flow:
- Converted all relative paths to absolute paths
- Saved the blend file with absolute paths
- Uploaded directly to Shaman (bypassing BAT)
- Created a checkout with just the blend file
- Submitted the job successfully

There’s a warning about one path that couldn’t be made relative (line 971-972), but it didn’t block submission. The job was submitted, confirming that BAT is bypassed and the blend file is sent as-is with all absolute paths, avoiding the previous issue where BAT was sending all files and creating Shaman pointers for relative paths.

---

_**User**_

it sends properly, but it's mapping the job blend to the root of jobs, but only for this specific blend. context:@c:\Users\Nathan\Desktop\flamenco\.specstory\history\2026-01-09_20-34Z-flamenco-project-file-path-consistency.md 

---

_**Agent (default)**_

Checking the specstory context and how the blend file path is set in the Shaman checkout:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\Desktop\flamenco\.specstory\history\2026-01-09_20-34Z-flamenco-project-file-path-consistency.md</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
<details>
<summary>Tool use: **semantic_search_full**</summary>


Parameters:

```json
{
  "explanation": "",
  "query": "How should the blend file path be structured in Shaman checkout? Should it maintain directory structure relative to project?",
  "topK": 10
}
```
Result:

```json
{
  "codeResults": [
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _upload_blendfile_to_shaman(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n\n        \n        try:\n            result = shaman_api.shaman_checkout(checkout)\n            self.actual_shaman_checkout_path = PurePosixPath(result.checkout_path)\n            # The blend file path in the checkout is just the filename\n            self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / blendfile.name\n            self.log.info(\"Shaman checkout created: %s\", self.actual_shaman_checkout_path)\n            self._submit_job(context)\n        except ApiException as ex:\n            self.log.error(\"Error creating Shaman checkout: %s\", ex)\n            self.report({\"ERROR\"}, f\"Error creating Shaman checkout: {ex}\")\n            return\n\n    def _copy_blendfile_to_storage(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        \"\"\"Copy blend file to job storage without BAT.\"\"\"\n        import shutil\n        \n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        \n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n        pack_target_dir.mkdir(parents=True, exist_ok=True)\n        \n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Copying blend file to %s\", pack_target_file)\n        \n        shutil.copy2(blendfile, pack_target_file)\n        \n        self.blendfile_on_farm = PurePosixPath(pack_target_file.as_posix())\n        self.actual_shaman_checkout_path = None\n        \n        self._submit_job(context)\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 466,
            "text": "def _upload_blendfile_to_shaman("
          },
          {
            "lineNumber": 467,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 468,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 536
          },
          {
            "lineNumber": 537,
            "text": "        "
          },
          {
            "lineNumber": 538,
            "text": "        try:"
          },
          {
            "lineNumber": 539,
            "text": "            result = shaman_api.shaman_checkout(checkout)"
          },
          {
            "lineNumber": 540,
            "text": "            self.actual_shaman_checkout_path = PurePosixPath(result.checkout_path)"
          },
          {
            "lineNumber": 541,
            "text": "            # The blend file path in the checkout is just the filename"
          },
          {
            "lineNumber": 542,
            "text": "            self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / blendfile.name"
          },
          {
            "lineNumber": 543,
            "text": "            self.log.info(\"Shaman checkout created: %s\", self.actual_shaman_checkout_path)"
          },
          {
            "lineNumber": 544,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 545,
            "text": "        except ApiException as ex:"
          },
          {
            "lineNumber": 546,
            "text": "            self.log.error(\"Error creating Shaman checkout: %s\", ex)"
          },
          {
            "lineNumber": 547,
            "text": "            self.report({\"ERROR\"}, f\"Error creating Shaman checkout: {ex}\")"
          },
          {
            "lineNumber": 548,
            "text": "            return"
          },
          {
            "lineNumber": 549
          },
          {
            "lineNumber": 550,
            "text": "    def _copy_blendfile_to_storage("
          },
          {
            "lineNumber": 551,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 552,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 553,
            "text": "        \"\"\"Copy blend file to job storage without BAT.\"\"\""
          },
          {
            "lineNumber": 554,
            "text": "        import shutil"
          },
          {
            "lineNumber": 555,
            "text": "        "
          },
          {
            "lineNumber": 556,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 557,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 558,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 559,
            "text": "        "
          },
          {
            "lineNumber": 560,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 561,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 562,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 563,
            "text": "        )"
          },
          {
            "lineNumber": 564,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 565,
            "text": "        pack_target_dir.mkdir(parents=True, exist_ok=True)"
          },
          {
            "lineNumber": 566,
            "text": "        "
          },
          {
            "lineNumber": 567,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 568,
            "text": "        self.log.info(\"Copying blend file to %s\", pack_target_file)"
          },
          {
            "lineNumber": 569,
            "text": "        "
          },
          {
            "lineNumber": 570,
            "text": "        shutil.copy2(blendfile, pack_target_file)"
          },
          {
            "lineNumber": 571,
            "text": "        "
          },
          {
            "lineNumber": 572,
            "text": "        self.blendfile_on_farm = PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 573,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 574,
            "text": "        "
          },
          {
            "lineNumber": 575,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 576
          },
          {
            "lineNumber": 577,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 578,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 579,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 580,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 469
              },
              "startPosition": {
                "column": 5,
                "line": 466
              }
            }
          ]
        }
      },
      "score": 0.5457433462142944
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID.\n\n        Note that this may not be the actually used checkout ID, as that will be\n        made unique to this job by Flamenco Manager. That will be stored in\n        self.actual_shaman_checkout_path after the Shaman checkout is actually\n        done.\n        \"\"\"\n        assert self.job is not None\n\n        # TODO: get project name from preferences/GUI and insert that here too.\n        return PurePosixPath(f\"{self.job.name}\")\n\n    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:\n        \"\"\"Use the Manager's Shaman API to submit the BAT pack.\n\n        :return: the filesystem path of the blend file, for in the render job definition.\n        \"\"\"\n        from .bat import (\n            interface as bat_interface,\n            shaman as bat_shaman,\n        )\n\n        assert self.job is not None\n        self.log.info(\"Sending BAT pack to Shaman\")\n\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=\"/\",  # Target directory irrelevant for Shaman transfers.\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n            packer_class=bat_shaman.Packer,\n            packer_kwargs=dict(\n                api_client=self.get_api_client(context),\n                checkout_path=self._shaman_checkout_path(),\n            ),\n        )\n\n        # We cannot assume the blendfile location is known until the Shaman\n        # checkout has actually been created.\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 617
          },
          {
            "lineNumber": 618
          },
          {
            "lineNumber": 619,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 620,
            "text": "        \"\"\"Construct the Shaman checkout path, aka Shaman Checkout ID."
          },
          {
            "lineNumber": 621
          },
          {
            "lineNumber": 622,
            "text": "        Note that this may not be the actually used checkout ID, as that will be"
          },
          {
            "lineNumber": 623,
            "text": "        made unique to this job by Flamenco Manager. That will be stored in"
          },
          {
            "lineNumber": 624,
            "text": "        self.actual_shaman_checkout_path after the Shaman checkout is actually"
          },
          {
            "lineNumber": 625,
            "text": "        done."
          },
          {
            "lineNumber": 626,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 627,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 628
          },
          {
            "lineNumber": 629,
            "text": "        # TODO: get project name from preferences/GUI and insert that here too."
          },
          {
            "lineNumber": 630,
            "text": "        return PurePosixPath(f\"{self.job.name}\")"
          },
          {
            "lineNumber": 631
          },
          {
            "lineNumber": 632,
            "text": "    def _bat_pack_shaman(self, context: bpy.types.Context, blendfile: Path) -\u003e None:"
          },
          {
            "lineNumber": 633,
            "text": "        \"\"\"Use the Manager's Shaman API to submit the BAT pack."
          },
          {
            "lineNumber": 634
          },
          {
            "lineNumber": 635,
            "text": "        :return: the filesystem path of the blend file, for in the render job definition."
          },
          {
            "lineNumber": 636,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 637,
            "text": "        from .bat import ("
          },
          {
            "lineNumber": 638,
            "text": "            interface as bat_interface,"
          },
          {
            "lineNumber": 639,
            "text": "            shaman as bat_shaman,"
          },
          {
            "lineNumber": 640,
            "text": "        )"
          },
          {
            "lineNumber": 641
          },
          {
            "lineNumber": 642,
            "text": "        assert self.job is not None"
          },
          {
            "lineNumber": 643,
            "text": "        self.log.info(\"Sending BAT pack to Shaman\")"
          },
          {
            "lineNumber": 644
          },
          {
            "lineNumber": 645,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 646,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 647
          },
          {
            "lineNumber": 648,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 649,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 650,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 651,
            "text": "            target=\"/\",  # Target directory irrelevant for Shaman transfers."
          },
          {
            "lineNumber": 652,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 653,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 654,
            "text": "            packer_class=bat_shaman.Packer,"
          },
          {
            "lineNumber": 655,
            "text": "            packer_kwargs=dict("
          },
          {
            "lineNumber": 656,
            "text": "                api_client=self.get_api_client(context),"
          },
          {
            "lineNumber": 657,
            "text": "                checkout_path=self._shaman_checkout_path(),"
          },
          {
            "lineNumber": 658,
            "text": "            ),"
          },
          {
            "lineNumber": 659,
            "text": "        )"
          },
          {
            "lineNumber": 660
          },
          {
            "lineNumber": 661,
            "text": "        # We cannot assume the blendfile location is known until the Shaman"
          },
          {
            "lineNumber": 662,
            "text": "        # checkout has actually been created."
          },
          {
            "lineNumber": 663
          },
          {
            "lineNumber": 664,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 665,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.5231913328170776
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _bat_pack_filesystem(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e PurePosixPath:\n        \"\"\"Use BAT to store the pack on the filesystem.\n\n        :return: the path of the blend file, for use in the job definition.\n        \"\"\"\n        from .bat import interface as bat_interface\n\n        # Get project path from addon preferences.\n        prefs = preferences.get(context)\n        project_path: Path = prefs.project_root()\n        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))\n\n        if not project_path.exists():\n            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)\n            raise FileNotFoundError()\n\n        # Determine where the blend file will be stored.\n        manager = self._manager_info(context)\n        if not manager:\n            raise FileNotFoundError(\"Manager info not known\")\n        unique_dir = \"%s-%s\" % (\n            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),\n            self.job_name,\n        )\n        pack_target_dir = Path(manager.shared_storage.location) / unique_dir\n\n        # TODO: this should take the blendfile location relative to the project path into account.\n        pack_target_file = pack_target_dir / blendfile.name\n        self.log.info(\"Will store blend file at %s\", pack_target_file)\n\n        self.packthread = bat_interface.copy(\n            base_blendfile=blendfile,\n            project=project_path,\n            target=str(pack_target_dir),\n            exclusion_filter=\"\",  # TODO: get from GUI.\n            relative_only=True,  # Only include files relative to project path.\n        )\n\n        return PurePosixPath(pack_target_file.as_posix())\n\n    def _shaman_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 575
          },
          {
            "lineNumber": 576
          },
          {
            "lineNumber": 577,
            "text": "    def _bat_pack_filesystem("
          },
          {
            "lineNumber": 578,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 579,
            "text": "    ) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 580,
            "text": "        \"\"\"Use BAT to store the pack on the filesystem."
          },
          {
            "lineNumber": 581
          },
          {
            "lineNumber": 582,
            "text": "        :return: the path of the blend file, for use in the job definition."
          },
          {
            "lineNumber": 583,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 584,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 585
          },
          {
            "lineNumber": 586,
            "text": "        # Get project path from addon preferences."
          },
          {
            "lineNumber": 587,
            "text": "        prefs = preferences.get(context)"
          },
          {
            "lineNumber": 588,
            "text": "        project_path: Path = prefs.project_root()"
          },
          {
            "lineNumber": 589,
            "text": "        project_path = bpathlib.make_absolute(Path(bpy.path.abspath(str(project_path))))"
          },
          {
            "lineNumber": 590
          },
          {
            "lineNumber": 591,
            "text": "        if not project_path.exists():"
          },
          {
            "lineNumber": 592,
            "text": "            self.report({\"ERROR\"}, \"Project path %s does not exist\" % project_path)"
          },
          {
            "lineNumber": 593,
            "text": "            raise FileNotFoundError()"
          },
          {
            "lineNumber": 594
          },
          {
            "lineNumber": 595,
            "text": "        # Determine where the blend file will be stored."
          },
          {
            "lineNumber": 596,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 597,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 598,
            "text": "            raise FileNotFoundError(\"Manager info not known\")"
          },
          {
            "lineNumber": 599,
            "text": "        unique_dir = \"%s-%s\" % ("
          },
          {
            "lineNumber": 600,
            "text": "            datetime.datetime.now().isoformat(\"-\").replace(\":\", \"\"),"
          },
          {
            "lineNumber": 601,
            "text": "            self.job_name,"
          },
          {
            "lineNumber": 602,
            "text": "        )"
          },
          {
            "lineNumber": 603,
            "text": "        pack_target_dir = Path(manager.shared_storage.location) / unique_dir"
          },
          {
            "lineNumber": 604
          },
          {
            "lineNumber": 605,
            "text": "        # TODO: this should take the blendfile location relative to the project path into account."
          },
          {
            "lineNumber": 606,
            "text": "        pack_target_file = pack_target_dir / blendfile.name"
          },
          {
            "lineNumber": 607,
            "text": "        self.log.info(\"Will store blend file at %s\", pack_target_file)"
          },
          {
            "lineNumber": 608
          },
          {
            "lineNumber": 609,
            "text": "        self.packthread = bat_interface.copy("
          },
          {
            "lineNumber": 610,
            "text": "            base_blendfile=blendfile,"
          },
          {
            "lineNumber": 611,
            "text": "            project=project_path,"
          },
          {
            "lineNumber": 612,
            "text": "            target=str(pack_target_dir),"
          },
          {
            "lineNumber": 613,
            "text": "            exclusion_filter=\"\",  # TODO: get from GUI."
          },
          {
            "lineNumber": 614,
            "text": "            relative_only=True,  # Only include files relative to project path."
          },
          {
            "lineNumber": 615,
            "text": "        )"
          },
          {
            "lineNumber": 616
          },
          {
            "lineNumber": 617,
            "text": "        return PurePosixPath(pack_target_file.as_posix())"
          },
          {
            "lineNumber": 618
          },
          {
            "lineNumber": 619,
            "text": "    def _shaman_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 620,
            "text": "        \"\"\""
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.4894234538078308
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _upload_blendfile_to_shaman(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n\n        try:\n            with blendfile.open(\"rb\") as file_reader:\n                shaman_api.shaman_file_store(\n                    checksum=checksum,\n                    filesize=filesize,\n                    body=file_reader,\n                    x_shaman_can_defer_upload=True,\n                    x_shaman_original_filename=blendfile.name,\n                )\n        except ApiException as ex:\n            if ex.status == 208:\n                # File already known to Shaman\n                self.log.info(\"Blend file already known to Shaman\")\n            elif ex.status == 425:\n                # Defer upload - someone else is uploading\n                self.log.info(\"Blend file is being uploaded by another client, deferring\")\n                # Retry after a short delay\n                import time\n                time.sleep(1)\n                with blendfile.open(\"rb\") as file_reader:\n                    shaman_api.shaman_file_store(\n                        checksum=checksum,\n                        filesize=filesize,\n                        body=file_reader,\n                        x_shaman_can_defer_upload=False,\n                        x_shaman_original_filename=blendfile.name,\n                    )\n            else:\n                self.log.error(\"Error uploading to Shaman: %s\", ex)\n                self.report({\"ERROR\"}, f\"Error uploading to Shaman: {ex}\")\n                return\n        \n        # Create checkout definition with just the blend file\n        checkout_path = self._shaman_checkout_path()\n        filespec = ShamanFileSpec(\n            sha=checksum,\n            size=filesize,\n            path=blendfile.name,  # Just the filename in the checkout\n        )\n        \n        # Create the checkout\n        self.log.info(\"Creating Shaman checkout: %s\", checkout_path)\n        checkout = ShamanCheckout(\n            files=[filespec],\n            checkout_path=str(checkout_path),\n        )\n        \n        try:\n            result = shaman_api.shaman_checkout(checkout)",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 466,
            "text": "def _upload_blendfile_to_shaman("
          },
          {
            "lineNumber": 467,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 468,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 490
          },
          {
            "lineNumber": 491,
            "text": "        try:"
          },
          {
            "lineNumber": 492,
            "text": "            with blendfile.open(\"rb\") as file_reader:"
          },
          {
            "lineNumber": 493,
            "text": "                shaman_api.shaman_file_store("
          },
          {
            "lineNumber": 494,
            "text": "                    checksum=checksum,"
          },
          {
            "lineNumber": 495,
            "text": "                    filesize=filesize,"
          },
          {
            "lineNumber": 496,
            "text": "                    body=file_reader,"
          },
          {
            "lineNumber": 497,
            "text": "                    x_shaman_can_defer_upload=True,"
          },
          {
            "lineNumber": 498,
            "text": "                    x_shaman_original_filename=blendfile.name,"
          },
          {
            "lineNumber": 499,
            "text": "                )"
          },
          {
            "lineNumber": 500,
            "text": "        except ApiException as ex:"
          },
          {
            "lineNumber": 501,
            "text": "            if ex.status == 208:"
          },
          {
            "lineNumber": 502,
            "text": "                # File already known to Shaman"
          },
          {
            "lineNumber": 503,
            "text": "                self.log.info(\"Blend file already known to Shaman\")"
          },
          {
            "lineNumber": 504,
            "text": "            elif ex.status == 425:"
          },
          {
            "lineNumber": 505,
            "text": "                # Defer upload - someone else is uploading"
          },
          {
            "lineNumber": 506,
            "text": "                self.log.info(\"Blend file is being uploaded by another client, deferring\")"
          },
          {
            "lineNumber": 507,
            "text": "                # Retry after a short delay"
          },
          {
            "lineNumber": 508,
            "text": "                import time"
          },
          {
            "lineNumber": 509,
            "text": "                time.sleep(1)"
          },
          {
            "lineNumber": 510,
            "text": "                with blendfile.open(\"rb\") as file_reader:"
          },
          {
            "lineNumber": 511,
            "text": "                    shaman_api.shaman_file_store("
          },
          {
            "lineNumber": 512,
            "text": "                        checksum=checksum,"
          },
          {
            "lineNumber": 513,
            "text": "                        filesize=filesize,"
          },
          {
            "lineNumber": 514,
            "text": "                        body=file_reader,"
          },
          {
            "lineNumber": 515,
            "text": "                        x_shaman_can_defer_upload=False,"
          },
          {
            "lineNumber": 516,
            "text": "                        x_shaman_original_filename=blendfile.name,"
          },
          {
            "lineNumber": 517,
            "text": "                    )"
          },
          {
            "lineNumber": 518,
            "text": "            else:"
          },
          {
            "lineNumber": 519,
            "text": "                self.log.error(\"Error uploading to Shaman: %s\", ex)"
          },
          {
            "lineNumber": 520,
            "text": "                self.report({\"ERROR\"}, f\"Error uploading to Shaman: {ex}\")"
          },
          {
            "lineNumber": 521,
            "text": "                return"
          },
          {
            "lineNumber": 522,
            "text": "        "
          },
          {
            "lineNumber": 523,
            "text": "        # Create checkout definition with just the blend file"
          },
          {
            "lineNumber": 524,
            "text": "        checkout_path = self._shaman_checkout_path()"
          },
          {
            "lineNumber": 525,
            "text": "        filespec = ShamanFileSpec("
          },
          {
            "lineNumber": 526,
            "text": "            sha=checksum,"
          },
          {
            "lineNumber": 527,
            "text": "            size=filesize,"
          },
          {
            "lineNumber": 528,
            "text": "            path=blendfile.name,  # Just the filename in the checkout"
          },
          {
            "lineNumber": 529,
            "text": "        )"
          },
          {
            "lineNumber": 530,
            "text": "        "
          },
          {
            "lineNumber": 531,
            "text": "        # Create the checkout"
          },
          {
            "lineNumber": 532,
            "text": "        self.log.info(\"Creating Shaman checkout: %s\", checkout_path)"
          },
          {
            "lineNumber": 533,
            "text": "        checkout = ShamanCheckout("
          },
          {
            "lineNumber": 534,
            "text": "            files=[filespec],"
          },
          {
            "lineNumber": 535,
            "text": "            checkout_path=str(checkout_path),"
          },
          {
            "lineNumber": 536,
            "text": "        )"
          },
          {
            "lineNumber": 537,
            "text": "        "
          },
          {
            "lineNumber": 538,
            "text": "        try:"
          },
          {
            "lineNumber": 539,
            "text": "            result = shaman_api.shaman_checkout(checkout)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 469
              },
              "startPosition": {
                "column": 5,
                "line": 466
              }
            }
          ]
        }
      },
      "score": 0.47431135177612305
    },
    {
      "codeBlock": {
        "contents": "# ShamanCheckout\n\nSet of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.\n\n## Properties\nName | Type | Description | Notes\n------------ | ------------- | ------------- | -------------\n**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) |  | \n**checkout_path** | **str** | Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\u0026quot;checkout ID\\\u0026quot;, but in this version it can be a path like \u0026#x60;project-slug/scene-name/unique-ID\u0026#x60;.  | \n**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]\n\n[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# ShamanCheckout"
          },
          {
            "lineNumber": 2
          },
          {
            "lineNumber": 3,
            "text": "Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory."
          },
          {
            "lineNumber": 4
          },
          {
            "lineNumber": 5,
            "text": "## Properties"
          },
          {
            "lineNumber": 6,
            "text": "Name | Type | Description | Notes"
          },
          {
            "lineNumber": 7,
            "text": "------------ | ------------- | ------------- | -------------"
          },
          {
            "lineNumber": 8,
            "text": "**files** | [**[ShamanFileSpec]**](ShamanFileSpec.md) |  | "
          },
          {
            "lineNumber": 9,
            "text": "**checkout_path** | **str** | Path where the Manager should create this checkout. It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the \\\u0026quot;checkout ID\\\u0026quot;, but in this version it can be a path like \u0026#x60;project-slug/scene-name/unique-ID\u0026#x60;.  | "
          },
          {
            "lineNumber": 10,
            "text": "**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]"
          },
          {
            "lineNumber": 11
          },
          {
            "lineNumber": 12,
            "text": "[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)"
          }
        ],
        "range": {
          "endPosition": {
            "line": 14
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\manager\\docs\\ShamanCheckout.md",
        "signatures": {}
      },
      "score": 0.4516012966632843
    },
    {
      "codeBlock": {
        "contents": "# Mypy doesn't understand that submodules.pack.Packer exists.\nclass Packer(submodules.pack.Packer):  # type: ignore\n    \"\"\"Creates BAT Packs on a Shaman server.\"\"\"\n\n    def __init__(\n        self,\n        blendfile: Path,\n        project_root: Path,\n        target: str,\n        *,\n        api_client: _ApiClient,\n        checkout_path: str,\n        **kwargs: dict[Any, Any],\n    ) -\u003e None:\n        \"\"\"Constructor\n\n        :param target: mock target root directory to construct project-relative paths.\n        \"\"\"\n        super().__init__(blendfile, project_root, target, **kwargs)\n        self.checkout_path = checkout_path\n        self.api_client = api_client\n        self.shaman_transferrer: Optional[Transferrer] = None\n\n    # Mypy doesn't understand that submodules.transfer.FileTransferer exists.\n    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore\n        self.shaman_transferrer = Transferrer(\n            self.api_client, self.project, self.checkout_path\n        )\n        return self.shaman_transferrer\n\n    def _make_target_path(self, target: str) -\u003e PurePath:\n        return _root_path()\n\n    @property\n    def output_path(self) -\u003e PurePath:\n        \"\"\"The path of the packed blend file in the target directory.\"\"\"\n        assert self._output_path is not None\n\n        rel_output = self._output_path.relative_to(self._target_path)\n        out_path: PurePath = self.actual_checkout_path / rel_output\n        return out_path\n\n    @property\n    def actual_checkout_path(self) -\u003e PurePosixPath:\n        \"\"\"The actual Shaman checkout path.\n\n        Only valid after packing is complete. Shaman ensures that the checkout\n        is unique, and thus the actual path can be different than the requested\n        one.\n        \"\"\"\n        assert self.shaman_transferrer is not None\n        return PurePosixPath(self.shaman_transferrer.checkout_path)\n\n    def execute(self):\n        try:\n            super().execute()",
        "detailedLines": [
          {
            "lineNumber": 37,
            "text": "# Mypy doesn't understand that submodules.pack.Packer exists."
          },
          {
            "isSignature": true,
            "lineNumber": 38,
            "text": "class Packer(submodules.pack.Packer):  # type: ignore"
          },
          {
            "lineNumber": 39,
            "text": "    \"\"\"Creates BAT Packs on a Shaman server.\"\"\""
          },
          {
            "lineNumber": 40
          },
          {
            "lineNumber": 41,
            "text": "    def __init__("
          },
          {
            "lineNumber": 42,
            "text": "        self,"
          },
          {
            "lineNumber": 43,
            "text": "        blendfile: Path,"
          },
          {
            "lineNumber": 44,
            "text": "        project_root: Path,"
          },
          {
            "lineNumber": 45,
            "text": "        target: str,"
          },
          {
            "lineNumber": 46,
            "text": "        *,"
          },
          {
            "lineNumber": 47,
            "text": "        api_client: _ApiClient,"
          },
          {
            "lineNumber": 48,
            "text": "        checkout_path: str,"
          },
          {
            "lineNumber": 49,
            "text": "        **kwargs: dict[Any, Any],"
          },
          {
            "lineNumber": 50,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 51,
            "text": "        \"\"\"Constructor"
          },
          {
            "lineNumber": 52
          },
          {
            "lineNumber": 53,
            "text": "        :param target: mock target root directory to construct project-relative paths."
          },
          {
            "lineNumber": 54,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 55,
            "text": "        super().__init__(blendfile, project_root, target, **kwargs)"
          },
          {
            "lineNumber": 56,
            "text": "        self.checkout_path = checkout_path"
          },
          {
            "lineNumber": 57,
            "text": "        self.api_client = api_client"
          },
          {
            "lineNumber": 58,
            "text": "        self.shaman_transferrer: Optional[Transferrer] = None"
          },
          {
            "lineNumber": 59
          },
          {
            "lineNumber": 60,
            "text": "    # Mypy doesn't understand that submodules.transfer.FileTransferer exists."
          },
          {
            "lineNumber": 61,
            "text": "    def _create_file_transferer(self) -\u003e submodules.transfer.FileTransferer:  # type: ignore"
          },
          {
            "lineNumber": 62,
            "text": "        self.shaman_transferrer = Transferrer("
          },
          {
            "lineNumber": 63,
            "text": "            self.api_client, self.project, self.checkout_path"
          },
          {
            "lineNumber": 64,
            "text": "        )"
          },
          {
            "lineNumber": 65,
            "text": "        return self.shaman_transferrer"
          },
          {
            "lineNumber": 66
          },
          {
            "lineNumber": 67,
            "text": "    def _make_target_path(self, target: str) -\u003e PurePath:"
          },
          {
            "lineNumber": 68,
            "text": "        return _root_path()"
          },
          {
            "lineNumber": 69
          },
          {
            "lineNumber": 70,
            "text": "    @property"
          },
          {
            "lineNumber": 71,
            "text": "    def output_path(self) -\u003e PurePath:"
          },
          {
            "lineNumber": 72,
            "text": "        \"\"\"The path of the packed blend file in the target directory.\"\"\""
          },
          {
            "lineNumber": 73,
            "text": "        assert self._output_path is not None"
          },
          {
            "lineNumber": 74
          },
          {
            "lineNumber": 75,
            "text": "        rel_output = self._output_path.relative_to(self._target_path)"
          },
          {
            "lineNumber": 76,
            "text": "        out_path: PurePath = self.actual_checkout_path / rel_output"
          },
          {
            "lineNumber": 77,
            "text": "        return out_path"
          },
          {
            "lineNumber": 78
          },
          {
            "lineNumber": 79,
            "text": "    @property"
          },
          {
            "lineNumber": 80,
            "text": "    def actual_checkout_path(self) -\u003e PurePosixPath:"
          },
          {
            "lineNumber": 81,
            "text": "        \"\"\"The actual Shaman checkout path."
          },
          {
            "lineNumber": 82
          },
          {
            "lineNumber": 83,
            "text": "        Only valid after packing is complete. Shaman ensures that the checkout"
          },
          {
            "lineNumber": 84,
            "text": "        is unique, and thus the actual path can be different than the requested"
          },
          {
            "lineNumber": 85,
            "text": "        one."
          },
          {
            "lineNumber": 86,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 87,
            "text": "        assert self.shaman_transferrer is not None"
          },
          {
            "lineNumber": 88,
            "text": "        return PurePosixPath(self.shaman_transferrer.checkout_path)"
          },
          {
            "lineNumber": 89
          },
          {
            "lineNumber": 90,
            "text": "    def execute(self):"
          },
          {
            "lineNumber": 91,
            "text": "        try:"
          },
          {
            "lineNumber": 92,
            "text": "            super().execute()"
          }
        ],
        "range": {
          "endPosition": {
            "line": 95
          },
          "startPosition": {
            "column": 72,
            "line": 33
          }
        },
        "relativeWorkspacePath": ".\\bat\\shaman.py",
        "signatures": {}
      },
      "score": 0.4370298981666565
    },
    {
      "codeBlock": {
        "contents": "# SPDX-License-Identifier: GPL-3.0-or-later\n# \u003cpep8 compliant\u003e\n\nfrom pathlib import Path\nfrom typing import Callable\nimport dataclasses\n\nfrom .bat.submodules import bpathlib\n\n\ndef for_blendfile(blendfile: Path, strategy: str) -\u003e Path:\n    \"\"\"Return what is considered to be the project directory containing the given file.\n\n    If none can be found, the directory containing the current blend file is returned.\n    If the current blend file has no path (because it was not saved), a ValueError is raised.\n\n    :param blendfile: the path of the blend file for which to find the project.\n    :param strategy: the name of the finder to use, see &#96;finders&#96;.\n    \"\"\"\n    if blendfile.is_dir():\n        msg = f\"{blendfile} is not a blend file, cannot find project directory\"\n        raise ValueError(msg)\n\n    try:\n        finder_info = finders[strategy]\n    except KeyError:\n        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\"\n        raise ValueError(msg) from None\n\n    return finder_info.finder(blendfile)\n\n\ndef _finder_blender_project(blendfile: Path) -\u003e Path:\n    return _search_path_marker(blendfile, \".blender_project\")",
        "detailedLines": [
          {
            "lineNumber": 1,
            "text": "# SPDX-License-Identifier: GPL-3.0-or-later"
          },
          {
            "lineNumber": 2,
            "text": "# \u003cpep8 compliant\u003e"
          },
          {
            "lineNumber": 3
          },
          {
            "lineNumber": 4,
            "text": "from pathlib import Path"
          },
          {
            "lineNumber": 5,
            "text": "from typing import Callable"
          },
          {
            "lineNumber": 6,
            "text": "import dataclasses"
          },
          {
            "lineNumber": 7
          },
          {
            "lineNumber": 8,
            "text": "from .bat.submodules import bpathlib"
          },
          {
            "lineNumber": 9
          },
          {
            "lineNumber": 10
          },
          {
            "isSignature": true,
            "lineNumber": 11,
            "text": "def for_blendfile(blendfile: Path, strategy: str) -\u003e Path:"
          },
          {
            "lineNumber": 12,
            "text": "    \"\"\"Return what is considered to be the project directory containing the given file."
          },
          {
            "lineNumber": 13
          },
          {
            "lineNumber": 14,
            "text": "    If none can be found, the directory containing the current blend file is returned."
          },
          {
            "lineNumber": 15,
            "text": "    If the current blend file has no path (because it was not saved), a ValueError is raised."
          },
          {
            "lineNumber": 16
          },
          {
            "lineNumber": 17,
            "text": "    :param blendfile: the path of the blend file for which to find the project."
          },
          {
            "lineNumber": 18,
            "text": "    :param strategy: the name of the finder to use, see &#96;finders&#96;."
          },
          {
            "lineNumber": 19,
            "text": "    \"\"\""
          },
          {
            "lineNumber": 20,
            "text": "    if blendfile.is_dir():"
          },
          {
            "lineNumber": 21,
            "text": "        msg = f\"{blendfile} is not a blend file, cannot find project directory\""
          },
          {
            "lineNumber": 22,
            "text": "        raise ValueError(msg)"
          },
          {
            "lineNumber": 23
          },
          {
            "lineNumber": 24,
            "text": "    try:"
          },
          {
            "lineNumber": 25,
            "text": "        finder_info = finders[strategy]"
          },
          {
            "lineNumber": 26,
            "text": "    except KeyError:"
          },
          {
            "lineNumber": 27,
            "text": "        msg = f\"Unknown strategy {strategy!r}, cannot find project directory\""
          },
          {
            "lineNumber": 28,
            "text": "        raise ValueError(msg) from None"
          },
          {
            "lineNumber": 29
          },
          {
            "isSignature": true,
            "lineNumber": 30,
            "text": "    return finder_info.finder(blendfile)"
          },
          {
            "lineNumber": 31
          },
          {
            "lineNumber": 32
          },
          {
            "isSignature": true,
            "lineNumber": 33,
            "text": "def _finder_blender_project(blendfile: Path) -\u003e Path:"
          },
          {
            "isSignature": true,
            "lineNumber": 34,
            "text": "    return _search_path_marker(blendfile, \".blender_project\")"
          }
        ],
        "range": {
          "endPosition": {
            "column": 61,
            "line": 33
          },
          "startPosition": {}
        },
        "relativeWorkspacePath": ".\\projects.py",
        "signatures": {}
      },
      "score": 0.43177929520606995
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\n\n\n    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:\n        from .bat import interface as bat_interface\n\n        if isinstance(msg, bat_interface.MsgDone):\n            if self.blendfile_on_farm is None:\n                # Adjust the blendfile to match the Shaman checkout path. Shaman\n                # may have checked out at a different location than we\n                # requested.\n                #\n                # Manager automatically creates a variable \"jobs\" that will\n                # resolve to the job storage directory.\n                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path\n\n            self.actual_shaman_checkout_path = msg.actual_checkout_path\n            self._submit_job(context)\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgException):\n            self.log.error(\"Error performing BAT pack: %s\", msg.ex)\n            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)\n\n            # This was an exception caught at the top level of the thread, so\n            # the packing thread itself has stopped.\n            return self._quit(context)\n\n        if isinstance(msg, bat_interface.MsgSetWMAttribute):\n            wm = context.window_manager\n            setattr(wm, msg.attribute_name, msg.value)\n\n        return {\"RUNNING_MODAL\"}\n\n    def _use_blendfile_directly(\n        self, context: bpy.types.Context, blendfile: Path\n    ) -\u003e None:\n        # The temporary '.flamenco.blend' file should not be deleted, as it\n        # will be used directly by the render job.\n        self.temp_blendfile = None\n\n        # The blend file is contained in the job storage path, no need to\n        # copy anything.\n        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)\n\n        # No Shaman is involved when using the file directly.\n        self.actual_shaman_checkout_path = None\n\n        self._submit_job(context)\n\n    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:\n        ",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 662
          },
          {
            "lineNumber": 663
          },
          {
            "lineNumber": 664,
            "text": "    def _on_bat_pack_msg(self, context: bpy.types.Context, msg: _Message) -\u003e set[str]:"
          },
          {
            "lineNumber": 665,
            "text": "        from .bat import interface as bat_interface"
          },
          {
            "lineNumber": 666
          },
          {
            "lineNumber": 667,
            "text": "        if isinstance(msg, bat_interface.MsgDone):"
          },
          {
            "lineNumber": 668,
            "text": "            if self.blendfile_on_farm is None:"
          },
          {
            "lineNumber": 669,
            "text": "                # Adjust the blendfile to match the Shaman checkout path. Shaman"
          },
          {
            "lineNumber": 670,
            "text": "                # may have checked out at a different location than we"
          },
          {
            "lineNumber": 671,
            "text": "                # requested."
          },
          {
            "lineNumber": 672,
            "text": "                #"
          },
          {
            "lineNumber": 673,
            "text": "                # Manager automatically creates a variable \"jobs\" that will"
          },
          {
            "lineNumber": 674,
            "text": "                # resolve to the job storage directory."
          },
          {
            "lineNumber": 675,
            "text": "                self.blendfile_on_farm = PurePosixPath(\"{jobs}\") / msg.output_path"
          },
          {
            "lineNumber": 676
          },
          {
            "lineNumber": 677,
            "text": "            self.actual_shaman_checkout_path = msg.actual_checkout_path"
          },
          {
            "lineNumber": 678,
            "text": "            self._submit_job(context)"
          },
          {
            "lineNumber": 679,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 680
          },
          {
            "lineNumber": 681,
            "text": "        if isinstance(msg, bat_interface.MsgException):"
          },
          {
            "lineNumber": 682,
            "text": "            self.log.error(\"Error performing BAT pack: %s\", msg.ex)"
          },
          {
            "lineNumber": 683,
            "text": "            self.report({\"ERROR\"}, \"Error performing BAT pack: %s\" % msg.ex)"
          },
          {
            "lineNumber": 684
          },
          {
            "lineNumber": 685,
            "text": "            # This was an exception caught at the top level of the thread, so"
          },
          {
            "lineNumber": 686,
            "text": "            # the packing thread itself has stopped."
          },
          {
            "lineNumber": 687,
            "text": "            return self._quit(context)"
          },
          {
            "lineNumber": 688
          },
          {
            "lineNumber": 689,
            "text": "        if isinstance(msg, bat_interface.MsgSetWMAttribute):"
          },
          {
            "lineNumber": 690,
            "text": "            wm = context.window_manager"
          },
          {
            "lineNumber": 691,
            "text": "            setattr(wm, msg.attribute_name, msg.value)"
          },
          {
            "lineNumber": 692
          },
          {
            "lineNumber": 693,
            "text": "        return {\"RUNNING_MODAL\"}"
          },
          {
            "lineNumber": 694
          },
          {
            "lineNumber": 695,
            "text": "    def _use_blendfile_directly("
          },
          {
            "lineNumber": 696,
            "text": "        self, context: bpy.types.Context, blendfile: Path"
          },
          {
            "lineNumber": 697,
            "text": "    ) -\u003e None:"
          },
          {
            "lineNumber": 698,
            "text": "        # The temporary '.flamenco.blend' file should not be deleted, as it"
          },
          {
            "lineNumber": 699,
            "text": "        # will be used directly by the render job."
          },
          {
            "lineNumber": 700,
            "text": "        self.temp_blendfile = None"
          },
          {
            "lineNumber": 701
          },
          {
            "lineNumber": 702,
            "text": "        # The blend file is contained in the job storage path, no need to"
          },
          {
            "lineNumber": 703,
            "text": "        # copy anything."
          },
          {
            "lineNumber": 704,
            "text": "        self.blendfile_on_farm = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 705
          },
          {
            "lineNumber": 706,
            "text": "        # No Shaman is involved when using the file directly."
          },
          {
            "lineNumber": 707,
            "text": "        self.actual_shaman_checkout_path = None"
          },
          {
            "lineNumber": 708
          },
          {
            "lineNumber": 709,
            "text": "        self._submit_job(context)"
          },
          {
            "lineNumber": 710
          },
          {
            "lineNumber": 711,
            "text": "    def _prepare_job_for_submission(self, context: bpy.types.Context) -\u003e bool:"
          },
          {
            "lineNumber": 712,
            "text": "        "
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            }
          ]
        }
      },
      "score": 0.4313480854034424
    },
    {
      "codeBlock": {
        "contents": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):\ndef _convert_relpaths_to_absolute(self, context: bpy.types.Context) -\u003e None:\n\n        \n        # Convert font paths to absolute\n        for font in bpy.data.fonts:\n            if font.filepath:\n                old_path = font.filepath\n                abs_path = bpy.path.abspath(font.filepath)\n                font.filepath = abs_path\n                self.log.debug(\"Converted font path: %s -\u003e %s\", old_path, abs_path)\n        \n        # Convert volume paths to absolute\n        for volume in bpy.data.volumes:\n            if volume.filepath:\n                old_path = volume.filepath\n                abs_path = bpy.path.abspath(volume.filepath)\n                volume.filepath = abs_path\n                self.log.debug(\"Converted volume path: %s -\u003e %s\", old_path, abs_path)\n\n    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:\n        \"\"\"Ensure that the files are somewhere in the shared storage.\n\n        Bypasses BAT entirely. Converts all relative paths to absolute and sends\n        the blend file as-is.\n\n        Returns True if a packing thread has been started, and False otherwise.\n        \"\"\"\n\n        manager = self._manager_info(context)\n        if not manager:\n            return False\n\n        # Convert all relative paths to absolute before saving\n        self.log.info(\"Converting all relative paths to absolute\")\n        self._convert_relpaths_to_absolute(context)\n        \n        # Save the blend file with absolute paths\n        self.log.info(\"Saving blend file with absolute paths\")\n        blendfile = self._save_blendfile(context)\n        blendfile = bpathlib.make_absolute(blendfile)\n\n        if manager.shared_storage.shaman_enabled:",
        "detailedLines": [
          {
            "isSignature": true,
            "lineNumber": 102,
            "text": "class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):"
          },
          {
            "lineNumber": 366,
            "text": "def _convert_relpaths_to_absolute(self, context: bpy.types.Context) -\u003e None:"
          },
          {
            "lineNumber": 402
          },
          {
            "lineNumber": 403,
            "text": "        "
          },
          {
            "lineNumber": 404,
            "text": "        # Convert font paths to absolute"
          },
          {
            "lineNumber": 405,
            "text": "        for font in bpy.data.fonts:"
          },
          {
            "lineNumber": 406,
            "text": "            if font.filepath:"
          },
          {
            "lineNumber": 407,
            "text": "                old_path = font.filepath"
          },
          {
            "lineNumber": 408,
            "text": "                abs_path = bpy.path.abspath(font.filepath)"
          },
          {
            "lineNumber": 409,
            "text": "                font.filepath = abs_path"
          },
          {
            "lineNumber": 410,
            "text": "                self.log.debug(\"Converted font path: %s -\u003e %s\", old_path, abs_path)"
          },
          {
            "lineNumber": 411,
            "text": "        "
          },
          {
            "lineNumber": 412,
            "text": "        # Convert volume paths to absolute"
          },
          {
            "lineNumber": 413,
            "text": "        for volume in bpy.data.volumes:"
          },
          {
            "lineNumber": 414,
            "text": "            if volume.filepath:"
          },
          {
            "lineNumber": 415,
            "text": "                old_path = volume.filepath"
          },
          {
            "lineNumber": 416,
            "text": "                abs_path = bpy.path.abspath(volume.filepath)"
          },
          {
            "lineNumber": 417,
            "text": "                volume.filepath = abs_path"
          },
          {
            "lineNumber": 418,
            "text": "                self.log.debug(\"Converted volume path: %s -\u003e %s\", old_path, abs_path)"
          },
          {
            "lineNumber": 419
          },
          {
            "lineNumber": 420,
            "text": "    def _submit_files(self, context: bpy.types.Context, blendfile: Path) -\u003e bool:"
          },
          {
            "lineNumber": 421,
            "text": "        \"\"\"Ensure that the files are somewhere in the shared storage."
          },
          {
            "lineNumber": 422
          },
          {
            "lineNumber": 423,
            "text": "        Bypasses BAT entirely. Converts all relative paths to absolute and sends"
          },
          {
            "lineNumber": 424,
            "text": "        the blend file as-is."
          },
          {
            "lineNumber": 425
          },
          {
            "lineNumber": 426,
            "text": "        Returns True if a packing thread has been started, and False otherwise."
          },
          {
            "lineNumber": 427,
            "text": "        \"\"\""
          },
          {
            "lineNumber": 428
          },
          {
            "lineNumber": 429,
            "text": "        manager = self._manager_info(context)"
          },
          {
            "lineNumber": 430,
            "text": "        if not manager:"
          },
          {
            "lineNumber": 431,
            "text": "            return False"
          },
          {
            "lineNumber": 432
          },
          {
            "lineNumber": 433,
            "text": "        # Convert all relative paths to absolute before saving"
          },
          {
            "lineNumber": 434,
            "text": "        self.log.info(\"Converting all relative paths to absolute\")"
          },
          {
            "lineNumber": 435,
            "text": "        self._convert_relpaths_to_absolute(context)"
          },
          {
            "lineNumber": 436,
            "text": "        "
          },
          {
            "lineNumber": 437,
            "text": "        # Save the blend file with absolute paths"
          },
          {
            "lineNumber": 438,
            "text": "        self.log.info(\"Saving blend file with absolute paths\")"
          },
          {
            "lineNumber": 439,
            "text": "        blendfile = self._save_blendfile(context)"
          },
          {
            "lineNumber": 440,
            "text": "        blendfile = bpathlib.make_absolute(blendfile)"
          },
          {
            "lineNumber": 441
          },
          {
            "lineNumber": 442,
            "text": "        if manager.shared_storage.shaman_enabled:"
          }
        ],
        "range": {
          "endPosition": {
            "line": 821
          },
          "startPosition": {
            "line": 101
          }
        },
        "relativeWorkspacePath": ".\\operators.py",
        "signatures": {
          "ranges": [
            {
              "endPosition": {
                "column": 5,
                "line": 103
              },
              "startPosition": {
                "column": 1,
                "line": 102
              }
            },
            {
              "endPosition": {
                "column": 9,
                "line": 367
              },
              "startPosition": {
                "column": 5,
                "line": 366
              }
            }
          ]
        }
      },
      "score": 0.42381471395492554
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full"><details>
<summary>Tool use: **semantic_search_full**</summary>

</details></tool-use>

_**Agent (default)**_

The blend file is being placed at the checkout root. It should preserve its relative path from the project root. Checking how BAT handles this and updating the code:


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>


Parameters:

```json
{
  "caseInsensitive": false,
  "contextAfter": 2,
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "project_root|project_path|relative_to"
}
```
Additional data:

```json
{
  "isPruned": true,
  "outputMode": "content",
  "path": "e:\\flamenco\\addon\\flamenco\\operators.py",
  "pattern": "project_root|project_path|relative_to",
  "topFiles": [
    {
      "matchCount": 16,
      "uri": "operators.py"
    }
  ],
  "totalFiles": 1,
  "totalMatches": 7
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>

</details></tool-use>

_**Agent (default)**_

Updating the code to preserve the blend file's relative path from the project root in the Shaman checkout:


_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

The blend file now preserves its relative path from the project root in the Shaman checkout. If the blend file is at `project/animations/scene.blend`, it will be stored at that path in the checkout, and the job will reference it as `{jobs}/animations/scene.blend` instead of `{jobs}/scene.blend`.

The code:
1. Gets the project root from preferences
2. Calculates the relative path from the project root to the blend file
3. Uses that relative path in the Shaman checkout
4. Sets `blendfile_on_farm` to `{jobs}/relative/path/to/blendfile.blend`

If the blend file is not under the project root, it falls back to using just the filename (with a warning log). This should fix the issue where this specific blend was being mapped to the root of jobs.

After reloading the addon, the blend file should maintain its directory structure relative to the project root in the Shaman checkout.

---

_**User**_

@c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\4.txt it sent properly (to "F:\jobs\ADTA Stow 2026 updates_animation 2c_part3-6z73\ADTA Stow 2026 updates_animation 2c_part3.flamenco.blend") but the jobfile is still pointing to a nonexistent file @ {jobs}/ADTA Stow 2026 updates_animation 2c_part3.flamenco.blend

---

_**Agent (default)**_

<think><details><summary>Thought Process</summary>
**Confirming checkout path**

I want to ensure the checkout path is correctly set up. It looks like the shaman enables this, and I’ve noticed there’s a unique suffix to consider. I’ll check the relevant terminal file, terminals/4.txt, to confirm the details about the checkout path. Making sure everything lines up is crucial for a smooth process! Let me dive into that now.</details></think>

_**Agent (default)**_

I’m going to inspect the latest Blender run output to see what checkout path the Manager created, then adjust the job’s `blendfile` setting to include that checkout subdirectory (right now we’re only using `{jobs}/<filename>`, which misses the per-job folder).

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\e-flamenco-addon-flamenco\terminals\4.txt</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<think><details><summary>Thought Process</summary>
**Updating file paths**

I see that the terminal doesn't specify where the file is stored, but the user indicates it's in F:\jobs\<jobname>-6z73\file, which matches the checkout path returned. I need to set `blendfile_on_farm` by including both the `result.checkout_path` and the file path. I’ll recall my previous changes on preserving the relative path for the checkout filespec. Additionally, I have to set `actual_shaman_checkout_path` relative to the shaman root and ensure to implement Read operators around the results. Time to get this done!</details></think>

_**Agent (default)**_

Got it — the upload/check-out is happening under a **unique subfolder** (your `F:\jobs\...-6z73\...`), but we’re still telling Flamenco the blend lives at **`{jobs}/<filename>`**.

Next step: update `_upload_blendfile_to_shaman()` so the job’s `blendfile` points to **`{jobs}/<checkout_path>/<path_inside_checkout>`**, matching what the Manager actually created.

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>


Parameters:

```json
{
  "paths": [
    "e:\\flamenco\\addon\\flamenco\\operators.py"
  ]
}
```
Result:

```json
{
  "linterErrorsByFile": [
    {
      "relativeWorkspacePath": "e:\\flamenco\\addon\\flamenco\\operators.py"
    }
  ]
}
```
Status: **completed**


</details>
</tool-use>

<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>

</details></tool-use>

_**Agent (default)**_

### Fix applied

Right now the Manager creates the Shaman checkout in a **unique subfolder** (your `...2c_part3-6z73\...`), but we were setting the job’s `blendfile` to **`{jobs}/<path_inside_checkout>`** only.

I updated `_upload_blendfile_to_shaman()` so the job points to:

- **`{jobs}/<checkout_path>/<path_inside_checkout>`**

That should match the real on-disk location under `F:\jobs\...`.

If you reload the add-on and submit again, the job settings’ `blendfile` should no longer point at the nonexistent `{jobs}/<filename>.flamenco.blend`.

---

_**User**_

getting this error

---

_**Agent (default)**_

<think><details><summary>Thought Process</summary>

The user is getting an error when trying to convert relative paths to absolute. The error is:

```
AttributeError: bpy_struct: attribute "filepath" from "VectorFont" is read-only
```

This happens in `_convert_relpaths_to_absolute` at line 409 when trying to set `font.filepath = abs_path`. 

For `VectorFont` objects in Blender, the `filepath` attribute is read-only. So we need to skip fonts when converting paths, or handle them differently. The simplest fix is to wrap the font conversion in a try/except, or skip it if the attribute is read-only.

Let me check the `_convert_relpaths_to_absolute` function to see what we're doing with fonts.
</details></think>

_**Agent (default)**_

VectorFont.filepath is read-only. Skipping font path conversion in `_convert_relpaths_to_absolute`.


_**Agent (default)**_

<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>
</tool-use>

<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<think><details><summary>Thought Process</summary>

I need to skip fonts that have read-only filepath (VectorFont). VectorFont is a built-in font type in Blender - it doesn't have an external file path. The fonts in bpy.data.fonts can be either:
- Regular font (file-based) - these might have a writable filepath
- VectorFont (built-in, like "Bfont") - these have read-only filepath

I'll wrap the font conversion in a try/except to skip fonts that can't be modified. Or I could check if the font has a filepath that's actually from an external file - VectorFont might not have filepath set, or it might be read-only.

Actually, the simplest fix is to wrap the assignment in try/except - if it fails (read-only), we just skip that font.
</details></think>

_**Agent (default)**_

<tool-use data-tool-type="write" data-tool-name="edit_file_v2">

<details><summary>Tool use: **edit_file_v2** • Edit file: e:\flamenco\addon\flamenco\operators.py</summary>

</details>

</tool-use>

<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>

</details></tool-use>

_**Agent (default)**_

<think><details><summary>Thought Process</summary>

Actually, AttributeError might not be raised when setting - it might be raised when we try to access the attribute. Let me check - the error says "attribute 'filepath' from 'VectorFont' is read-only" - this typically happens when you try to assign to it. So the assignment `font.filepath = abs_path` would raise AttributeError. Good, the try/except should catch it.
</details></think>

_**Agent (default)**_

Wrapped the font path conversion in a try/except so fonts with read-only `filepath` (like `VectorFont`) are skipped instead of causing an error.
