Modifying high-level classes¶
This guide explains how to create/modify the high-level Python API with classes like Project
, Clip
or Layer
.
Classes are object oriented wrappers around the raw data from TVPaint (as dataclasses).
For example the Project
class has a _data
attribute which is the main state of the object:
# pytvpaint/project.py
class Project(Refreshable):
def __init__(self, project_id: str) -> None:
super().__init__()
# ...
self._data: TVPProject = george.tv_project_info(self._id)
The type of the internal _data
attribute is TVPProject
which is the dataclass containing the data returned by TVPaint and parsed by our API.
Properties¶
We use properties to expose the attributes of the object while controlling which one can be get or set by the end user.
For example this is the id
property of the Project class, it can be read but not written (project ids are persistent across sessions):
@property
def id(self) -> str:
"""The project id.
Note:
the id is persistent on project load/close.
"""
return self._id
Refreshable properties¶
Some properties can be changed after the object instance is created in Python. This means that the state of the Python object is different from the state of the real object in TVPaint. Since the data is fetched only at init, we need a mechanism to refresh the data at each property call.
Here is an example with the refreshed_property
decorator:
@refreshed_property
def path(self) -> Path:
"""The project path on disk."""
return self._data.path
It calls the object's overriden self.refresh()
method before returning the internal value. This ensure that the value you are getting is always the right one at call time.
Make the object current¶
Some George functions only apply to the "current" object in the TVPaint instance.
To do that in an elegant way, we use the set_as_current
decorator for all the methods and properties that require that object to be the current:
@property
@set_as_current
def width(self) -> int:
"""The width of the canvas."""
return george.tv_get_width() # (1)!
- Here we use a more specific George function to get the data instead of using the
tv_project_info
function which is more expensive. We don't needrefreshed_property
neither.
It calls the object's self.make_current()
method.
Get access to children objects¶
For the Project
class, we want to give access to the scenes inside it. To do that we make a property that is a generator over Scene
objects.
@property
@set_as_current
def scenes(self) -> Iterator[Scene]:
"""Yields the project scenes."""
from pytvpaint.scene import Scene # (1)!
for scene_id in self.current_scene_ids():
yield Scene(scene_id, self) # (2)!
- We use the
from <mod> import ...
statement in the method because theScene
andProject
classes have a circular dependency. - We pass
self
as the second argument to have a reference of the project in the Scene object.
Tip
Most of the Iterator
in PyTVPaint are generators which mean it will only get the data (and send requests to TVPaint) if you want the next element.
So you can stop whenever you want:
for scene in project.scenes:
if scene.id == 787:
print(scene)
break