{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial gives an overview of the interaction with the *PowerFactory* database using *powfacpy*. The methods introduced in this tutorial serve as a basis to do more complex things and to write more readable code using less lines of code. Furthermore, the error messages *powfacpy* provides are a great help when debugging larger scripts or in larger projects that use the python API of *PowerFactory*.  \n",
    "\n",
    "For a complete list of classes and methods, please have a look at the *API* Chapter of the documentation or at the source code.\n",
    "\n",
    "Similar to using the Python API of *PowerFactory* directly, we first need to import the powerfactory module from *PowerFactory*'s installation directory (in case you want to access PowerFactory externally using IPython/Jupyter) and get the application. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# If you use IPython/Jupyter:\n",
    "import sys\n",
    "sys.path.append(r'C:\\Program Files\\DIgSILENT\\PowerFactory 2023 SP3\\Python\\3.11') # you may use a different directory\n",
    "# Get the PF app\n",
    "import powerfactory\n",
    "app = powerfactory.GetApplication()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then import *powfacpy* and create an instance of class `PFActiveProject` (with argument `app`). This interface class is mainly used to interact with the active project in the *PowerFactory* database as we will see below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import powfacpy\n",
    "pfp = powfacpy.PFActiveProject(app)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `pfp` has an attribute `app` that can be used similar to the `app` variable we loaded from the `powerfactory` module. \n",
    "\n",
    "The *PowerFactory* project used in this tutorial can be found in the powfacpy repository under *tests\\tests_input\\powfacpy_tests.pfd*. \n",
    "\n",
    "Here are two ways to 1. show the PowerFactory application and 2. activate a project:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "app.Show()\n",
    "app.ActivateProject(r\"powfacpy\\powfacpy_tests\") # You may change the project path. \n",
    "\n",
    "pfp.app.Show()\n",
    "pfp.app.ActivateProject(r\"powfacpy\\powfacpy_tests\") # You may change the project path."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So where does `pfp` differ from the Python interface that `app` provides? Let's see."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Accessing Objects\n",
    " Let's access an object from the *PowerFactory* database. When using `app`, we would use the methods `app.GetProjectFolder`, `app.GetChildren` or `app.GetContents`. Instead, we use `pfp.get_obj` with the path of the object (**relative to the folder of the active project**) as an argument: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "mv_terminal = pfp.get_obj(\n",
    "  r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\Terminal MV\")[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `[0]` at the end is necessary because the method always returns a list (with one element in this case). \n",
    "\n",
    "Note that you can easily copy and paste the path from the data manager while selecting the object in the data tree: \n",
    "\n",
    "![object path](figures/object_path.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`pfp.get_obj` also differs in other ways from `app.GetContents` and `app.GetCalcRelevantObjects`. You can set a condition for the objects that you want to access, which is best described by an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv_terminals = pfp.get_obj(\n",
    "        r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\*.ElmTerm\",\n",
    "        condition=lambda x: x.uknom > 50)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First of all, we use a wildcard (`*.ElmTerm`) to access all terminals in the `Grid` folder. The `condition` argument is a function that defines a certain condition the objects have to fulfill and returns a boolean value. In this case, the input argument `x` is an `ElmTerm` and `x.uknom > 50` gets its attribute `uknom` (nominal voltage) and checks whether it is larger than 50 (kV). Using this `.uknom` notation is equivalent to using `getattr(x, \"uknom\")`, which is a python method that returns an attribute of any object (also *PowerFactory* objects). You can also define more complex functions, such as `lambda x: x.uknom > 50 and x.uknom < 200` to access terminals between 50 and 200 kV.\n",
    "\n",
    "It is also possible to include subfolders in the search for objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminals = pfp.get_obj(\n",
    "  r\"Network Model\\Network Data\\*.ElmTerm\", \n",
    "  include_subfolders=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will search in `Network Model\\Network Data` and all its subfolders (by default `include_subfolders` is `False`, be carefull as including all subfolders in the search can affect the performance).\n",
    "\n",
    "What's also very helpful is that `pfp.get_obj` throws an error if the path is incorrect and shows exactly where it fails. So\n",
    "\n",
    "`terminals = pfp.get_obj(r\"Network Model\\wrong_folder_name\\*.ElmTerm\")` \n",
    "\n",
    "will throw an error:\n",
    "\n",
    "PFPathError: 'wrong_folder_name' does not exist in '\\user\\powfacpy_base\\Network Model'"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By default, an exception is also raised if no objects are found in the specified folder. For example:\n",
    "\n",
    "```python\n",
    "terminals = pfp.get_obj(r\"Network Model\\Network Data\\wrong_object_name*\", include_subfolders=True)\n",
    "```\n",
    "\n",
    "returns\n",
    "\n",
    "PFPathError: 'wrong_object_name*' does not exist in '\\user\\powfacpy_base\\Network Model\\Network Data'\n",
    "\n",
    "This can be turned off by setting the optional argument `error_if_non_existent=False`, then an empty list will be returned.\n",
    "\n",
    "If you want to access objects in a folder many times and don't want to use the whole path relative to the active project every time, you can also specifiy a parent folder where the search path starts (this can also be more performant):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_folder = pfp.get_obj(r\"Network Model\\Network Data\\test_active_project_interface\\Grid\")[0]\n",
    "mv_terminal = pfp.get_obj(\"Terminal MV\",   parent_folder=grid_folder)[0]\n",
    "hv_terminal = pfp.get_obj(\"Terminal HV 1\", parent_folder=grid_folder)[0]"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also use brackets to call `get_obj` (the `PFActiveProject` class defines a `__getitem__` method that calls `get_obj` without optional arguments):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminals = pfp[r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\*.ElmTerm\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `get_obj` command can become verbose with many keyword arguments. This can be inconvenient, for example when you have to call the method many times with the same arguments. `powfacpy` provides the `get_object_partial` method which returns a `Callable`. This way you can define new variations of `get_obj`:  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_obj_subf = pfp.get_obj_partial(include_subfolders=True) \n",
    "# This can now be used with further arguments of 'get_obj' as follows (and will always assume 'include_subfolders=True'):\n",
    "terminals = get_obj_subf(\"*.ElmTerm\", parent_folder=grid_folder)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Under the hood the `partial` function from the built-in `functools` module of Python is used (it is actually pretty simple to do the above with any Python method/function). Note that for the commonly required inclusion of subfolders in the search there is actually a separate method `get_obj_including_subfolders` available (another alternative for network objects in `get_calc_relevant_obj` as described below).\n",
    "\n",
    "An alternative to `pfp.get_obj` is `pfp.get_unique_obj`. Use this function if you want to access a single unique object (i.e. an object that you expect to only exist once) from the database and want to avoid the `[0]` (which is easily forgotten)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminal_1 = pfp.get_unique_obj(\n",
    "  r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\Terminal HV 1\") "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`get_unique_obj` will throw an error if the specified path is not attributable to a single unique object (i.e. also when two or more objects are found).\n",
    "\n",
    "Finally,  yet another alternative for getting network objects is `get_calc_relevant_obj`: *powfacpy* wraps the `GetCalcRelevenatObjects` method and adds optional arguments, similar to `get_obj`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminals = pfp.get_calc_relevant_obj(\"ElmTerm\", \n",
    "                                      error_if_non_existent=False, includeOutOfService=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Accessing Project Folders and Data\n",
    "The `PFActiveProject` class defines a number of properties to directly access relevant project folders (under the hood, the `GetProjectFolder` and `GetDataFolder` methods are used). Here are some examples (you may just type `pfp.folder` and your IDE will autocomplete all the properties): "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<powerfactory.DataObject <l1>\\mfranke.IntUser\\powfacpy\\powfacpy_tests.IntPrj\\Library.IntPrjfolder</l1>>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pfp.study_cases_folder # This is a getter property returning StudyCases.IntPrjfolder \n",
    "pfp.network_model_folder\n",
    "pfp.zones_folder\n",
    "pfp.variations_folder\n",
    "pfp.operation_scenarios_folder\n",
    "# ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To get all active grids/networks (ElmNet) use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "active_networks = pfp.get_active_networks()\n",
    "# pfp.app.GetCalcRelevantObjects(\".ElmNet\") also returns the summary grid in the study case which is usually not desired."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Getting Objects from the Active Study Case\n",
    "The native `GetFromStudyCase` method returns the first object found that satisfies the criteria or creates a new object in the active study case. This is a common source of errors when more than one object satisfies the criteria and this goes unnoticed (this often happens for example when users work on the same project with different language settings). You can use the *powfacpy* method instead which prints a warning or raises an error if the the object found is not unique:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "results_obj = pfp.get_from_study_case(\"ElmRes\", if_not_unique=\"warning\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Type Hints\n",
    "Type hints are used in Python to improve the readability and code completion, but do not actually affect the excuted code (except when you do static type checking). They are often seen in function/method definitions and are preceded by a *colon*, e.g.:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(a:int, b:int) -> int:\n",
    "  return a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means that the function expects two input arguments of type integer and returns an integer. *powfacpy* defines protocol classes for (almost) all *PowerFactory* classes (more than 200) from the scripting reference (with very few exceptions) and a few additional classes (such as 'IntSite' which is not mentioned in the scripting reference). \n",
    "\n",
    "In the following example a type hint is used (':ElmTerm'). Your IDE will then autocomplete all attributes (methods and data) of the variable 'terminal':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "110.0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from powfacpy.pf_class_protocols import ElmTerm\n",
    "terminal:ElmTerm = pfp.get_unique_obj(r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\Terminal HV 1\") \n",
    "terminal.uknom"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Any modern IDE should autocomplete your code while writing:\n",
    "\n",
    "![type_hints](figures/type_hints.png)\n",
    "\n",
    "Note that this also works with lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "terminals: list[ElmTerm] = pfp.get_calc_relevant_obj(\"ElmTerm\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Setting and getting object attributes\n",
    "How about setting data in the database? You can set attributes of an object as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "pfp.set_attr(\n",
    "    r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\Terminal MV\",\n",
    "    {\"uknom\":33, \"outserv\":0})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So with only one command we set the attributes \"uknom\" and \"outserv\" of the terminal. This saves time and is also very readable code.\n",
    "Note that the method `set_attr` accepts the path (string) but also a *PowerFactory* object. For example, we could also use the object `mv_terminal` that we loaded above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "pfp.set_attr(mv_terminal, {\"uknom\":33, \"outserv\":0})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This applies to many other methods in `powfacpy`. Loading the object only once and then using the object can be more efficient than using the path string many times.\n",
    "\n",
    "If you want to get an attribute of an object, write: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "nominal_voltage = pfp.get_attr(r\"Network Model\\Network Data\\test_active_project_interface\\Grid\\Terminal MV\", \"uknom\")\n",
    "# Equivalent:\n",
    "nominal_voltage = pfp.get_attr(mv_terminal, \"uknom\")\n",
    "nominal_voltage = pfp.get_attr(\"Terminal MV\", \"uknom\", parent_folder=r\"Network Model\\Network Data\\test_active_project_interface\\Grid\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating Objects and Directories\n",
    "If you want to create a new object, use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "from powfacpy.pf_class_protocols import BlkDef, IntPrjfolder\n",
    "new_obj:BlkDef = pfp.create_by_path(r\"Library\\Dynamic Models\\dummy.BlkDef\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will create an object of class \"BlkDef\" with the name \"dummy\" in the folder \"Library\\Dynamic Models\". You can also use"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_obj:BlkDef = pfp.create_in_folder(\"dummy.BlkDef\", r\"Library\\Dynamic Models\", overwrite=True)\n",
    "# Or equivalent:\n",
    "models_folder:IntPrjfolder = pfp.get_single_obj(r\"Library\\Dynamic Models\")\n",
    "new_obj:BlkDef = pfp.create_in_folder(\"dummy.BlkDef\", models_folder, overwrite=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which will overwrite the former object (this avoids that *PowerFactory* creates *dummy(1), dummy(2), ...* every time the code is run). `overwrite=True` is the default setting. \n",
    "\n",
    "To create a directory, i.e. a nested folder structure, use"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<powerfactory.DataObject <l1>\\seberlein.IntUser\\powfacpy\\powfacpy_tests.IntPrj\\Study Cases.IntPrjfolder\\New Study Case\\Cases Normal Opration</l1>>"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pfp.create_directory(r\"New Study Case\\Cases Normal Opration\", parent_folder=\"Study Cases\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Copying objects\n",
    "The method `copy_obj` can be seen as a method that uses `get_obj` as a first step to access objects and then copies them to a target folder under the hood. Therefore, `copy_obj` shares many (optional) input arguments with `get_obj` to first get the objects to be copied. Additionally, a target folder is required. For example, to copy all objects from a source folder to a target folder, write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "source_folder = r\"Library\\Dynamic Models\\TestDummyFolder\"\n",
    "target_folder = r\"Library\\Dynamic Models\\TestCopyMultiple\"\n",
    "new_copied_objects = pfp.copy_obj(\"*\", target_folder, parent_folder=source_folder)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `parent folder` is an optional argument (used for the **source**), so you could also specify the full object path as the first argument instead of using `*` (similar to `get_obj`). To select certain objects from the source folder, you can use optional arguments like `condition` or `include_subfolders`, also similar to the `get_obj` method. The method also accepts a list of *PowerFactory* objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "list_of_objects_to_copy = pfp.get_obj(\"*\", parent_folder=source_folder)\n",
    "target_folder_obj = pfp.get_unique_obj(r\"Library\\Dynamic Models\\TestCopyMultiple\")\n",
    "new_copied_objects = pfp.copy_obj(list_of_objects_to_copy,\n",
    "    target_folder_obj, overwrite=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the target folder object (instead of a path string) is used (second argument)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to copy a single object, use `copy_single_obj`. You have the option to give it a new name (`loc_name` attribute):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_copy = pfp.copy_single_obj(r\"Library\\Dynamic Models\\Linear_interpolation\",\n",
    "    r\"Library\\Dynamic Models\\TestCopy\", new_name=\"new_dummy_name\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First input here is the path of the object to be copied, but you can also use a *PowerFactory* object."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deleting objects\n",
    "Deleting objects works in a similar fashion, because under the hood it uses `get_obj` and deletes the returned objects in the second step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "pfp.delete_obj(\"to_be_deleted*\",\n",
    "    parent_folder=r\"Library\\Dynamic Models\",\n",
    "    include_subfolders=True,\n",
    "    error_if_non_existent=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And of course you could also use *PowerFactory* objects as an input:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "objects_in_folder = pfp.get_obj(\"to_be_deleted*\", parent_folder=r\"Library\\Dynamic Models\",\n",
    "    error_if_non_existent=False)\n",
    "pfp.delete_obj(objects_in_folder)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also delete all objects in a folder:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "pfp.clear_folder(\"Library\\Dynamic Models\\TestDelete\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Further Useful Methods\n",
    "Further helpful methods are briefly described in the following.\n",
    "`get_upstream_obj` returns an upsream object that meets a condition (i.e. the method goes upwards in the folder hierarchy until a parent object is found that meets the condition).\n",
    "\n",
    "Here this is used to find the grid that contains a given object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network Model\\Network Data\\test_database_interface\\Grid\n"
     ]
    }
   ],
   "source": [
    "\n",
    "obj = pfp.get_unique_obj(r\"Network Model\\Network Data\\test_database_interface\\Grid\\Voltage source ctrl\\Frequency\")\n",
    "grid = pfp.get_upstream_obj(obj, lambda x: x.loc_name == \"Grid\")\n",
    "print(pfp.get_path_of_object(grid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`get_path_of_object` returns the path of an object relative to the active project and without any class names. The user can can directly copy the returned string to the data manager to quickly find the object in the *PowerFactory* app.\n",
    "\n",
    "![object path](figures/object_path.png)\n",
    "\n",
    "There are more methods to get the path of an object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network Model\\Network Data\\test_database_interface\\Grid\n",
      "Network Model.IntPrjfolder\\Network Data.IntPrjfolder\\test_database_interface\\Grid.ElmNet\n",
      "\\seberlein\\powfacpy\\powfacpy_tests\\Network Model\\Network Data\\test_database_interface\\Grid\n",
      "\\seberlein.IntUser\\powfacpy\\powfacpy_tests.IntPrj\\Network Model.IntPrjfolder\\Network Data.IntPrjfolder\\test_database_interface\\Grid.ElmNet\n"
     ]
    }
   ],
   "source": [
    "print(pfp.get_path_of_object(grid)) # path inside project\n",
    "print(pfp.get_path_of_obj_with_class_names(grid))\n",
    "print(pfp.get_full_path_of_object(grid)) # full path in database\n",
    "print(pfp.get_full_path_of_object_with_class_names(grid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The PFFolder Class\n",
    "Most of the functionality described above is not limited to the folder of the active project, but can be used with any folder in the database. The class 'PFFolder' can be used with any folder. Paths are then relative to that folder (and not to the active project as above, unless of course a 'parent_folder' is specified):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "test_active_project_interface\\Grid\n"
     ]
    }
   ],
   "source": [
    "network_data_folder = powfacpy.PFFolder(r\"Network Model\\Network Data\", app)\n",
    "grid = network_data_folder.get_unique_obj(\"test_active_project_interface\\Grid\") # relative to 'Network Data' folder\n",
    "print(network_data_folder.get_path_of_object(grid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that 'PFFolder' instances are iterable. For example, you can use a for-loop to iterate through the (direct) children of the folder:  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Areas\n",
      "Backbones\n",
      "Virtual Power Plants\n",
      "Boundaries\n",
      "Cable Layouts\n",
      "Circuits\n",
      "Distribution correlations\n",
      "Feeders\n",
      "Meteo Stations\n",
      "Operators\n",
      "Owners\n",
      "Paths\n",
      "Routes\n",
      "Zones\n",
      "test_plot_interface\n",
      "network_for_elmfiles\n",
      "test_case_studies\n",
      "test_dyn_sim_interface\n",
      "test_network_interface\n",
      "test_results_interface\n",
      "test_database_interface\n",
      "test_model_exchange_interfaces\n",
      "test_active_project_interface\n"
     ]
    }
   ],
   "source": [
    "for object in network_data_folder:\n",
    "  print(network_data_folder.get_path_of_object(object))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\"> <b>NOTE</b> Please have a look at the source code and the other tutorials to find further helpful stuff for your project. </div>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10.4 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "20b4f2ae9a71ebfc0af8cf0167d05f9c8f5c94fe23811db744e66ae1fdb328c7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
