Skip to content

Layer class

Bases: pytvpaint.utils.Removable

A Layer is parented to a clip and contains LayerInstances.

Source code in pytvpaint/layer.py
328
329
330
331
332
333
334
335
336
337
338
339
def __init__(
    self,
    layer_id: int,
    clip: Clip | None = None,
    data: george.TVPLayer | None = None,
) -> None:
    from pytvpaint.clip import Clip

    super().__init__()
    self._id = layer_id
    self._clip = clip or Clip.current_clip()
    self._data = data or george.tv_layer_info(self.id)

id: int property

The layers unique identifier.

Warning

layer ids are not persistent and are reset every time the project is opened

project: Project property

The project containing this layer.

scene: Scene property

The scene containing this layer.

clip: Clip property

The clip containing this layer.

position: int property writable

The position in the layer stack.

Note

layer positions start at 0

folder: None property writable

The layer's parent folder if any.

Raises:

Type Description
NotImplementedError

as there is currently no way to get the parent folder from a Layer.

opacity: int property writable

Get the layer opacity value.

Note

In George, this is called density, we renamed it to opacity as it seems more appropriate

color: LayerColor property writable

Get the layer color.

is_current: bool property

Returns True if the layer is the current one in the clip.

is_selected: bool property writable

Returns True if the layer is selected.

is_visible: bool property writable

Returns True if the layer is visible.

is_locked: bool property writable

Returns True if the layer is locked.

is_collapsed: bool property writable

Returns True if the layer is collapsed.

blending_mode: george.BlendingMode property writable

Get the layer blending mode value.

stencil: george.StencilMode property writable

Get the layer stencil mode value.

thumbnails_visible: bool property writable

Returns True if thumbnails are shown on that layer.

auto_break_instance: bool property writable

Get the auto break instance value.

auto_create_instance: bool property writable

Get the auto create instance value.

pre_behavior: george.LayerBehavior property writable

Get the pre-behavior value.

post_behavior: george.LayerBehavior property writable

Get the post-behavior value.

is_position_locked: bool property writable

Returns True if the layer position is locked.

preserve_transparency: george.LayerTransparency property writable

Get the preserve transparency value.

is_anim_layer: bool property

Returns True if the layer is an animation layer.

is_ctg_layer: bool property

Returns True if the layer is a CTG layer.

is_ctg_source: bool property

Returns True if the layer is a CTG layer source.

sourced_ctg_layers: list[CTGLayer] property

Returns a list of CTGLayer instances that use this layer as a source.

marks: Iterator[tuple[int, LayerColor]] property

Returns an iterator over the layer marks including the frame and the color.

Yields:

Name Type Description
frame int

the mark frame

color pytvpaint.layer.LayerColor

the mark color

selected_frames: list[int] property

Get the list of selected frames.

Returns:

Type Description
list[int]

Array of selected frame numbers

refresh_on_call = True instance-attribute

is_removed: bool property

Checks if the object is removed by trying to refresh its data.

Returns:

Name Type Description
bool bool

whether if it was removed or not

refresh() -> None

Refreshes the layer data.

Source code in pytvpaint/layer.py
341
342
343
344
345
346
347
348
349
350
351
def refresh(self) -> None:
    """Refreshes the layer data."""
    super().refresh()
    if not self.refresh_on_call and self._data:
        return

    try:
        self._data = george.tv_layer_info(self._id)
    except GeorgeError:
        self.mark_removed()
        self.refresh()

set_folder_position(folder: LayerFolder, position: int) -> None

Moves the layer to the provided position in the folder.

Note

the position is relative to the root of the layer stack and not the folder. If the position is larger than that of the last layer in the folder, then the layer will be placed last. If you want to move a layer outside it's folder then just set its position using the layer.position property and move it outside the range of the folder, meaning before the first or after the last layer in the folder.

Source code in pytvpaint/layer.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
@george.min_version_compatible(min_version="12")
def set_folder_position(self, folder: LayerFolder, position: int) -> None:
    """Moves the layer to the provided position in the folder.

    Note:
        the position is relative to the root of the layer stack and not the folder.
        If the position is larger than that of the last layer in the folder, then the layer will be placed last.
        If you want to move a layer outside it's folder then just set its position using the layer.position property
        and move it outside the range of the folder, meaning before the first or after the last layer in the folder.
    """
    value = max(0, position)
    # TVPaint will always set the position at (value - 1) if value is superior to 0, so we need to add +1 in
    #  that case to set the position correctly, I don't know why it works this way, but it honestly makes no sense
    if value != 0:
        value += 1

    self.make_current()
    george.tv_layer_move(value, folder.id)

name(value: str) -> None

Set the layer name.

Note

it uses get_unique_name to find a unique layer name across all the layers in the clip

Source code in pytvpaint/layer.py
457
458
459
460
461
462
463
464
465
466
467
468
469
470
@name.setter
@set_as_current
def name(self, value: str) -> None:
    """Set the layer name.

    Note:
        it uses `get_unique_name` to find a unique layer name across all the layers in the clip
    """
    if value == self.name:
        return

    layer_names = (layer.name for layer in self.clip.get_layers() if layer != self)
    value = utils.get_unique_name(layer_names, value)
    george.tv_layer_rename(self.id, value)

layer_type() -> george.LayerType

The layer type.

Source code in pytvpaint/layer.py
472
473
474
475
@refreshed_property
def layer_type(self) -> george.LayerType:
    """The layer type."""
    return self._data.type

start() -> int

The layer start frame according to the project's start frame.

Source code in pytvpaint/layer.py
493
494
495
496
@refreshed_property
def start(self) -> int:
    """The layer start frame according to the project's start frame."""
    return self._data.first_frame + self.project.start_frame

end() -> int

The layer end frame according to the project's start frame.

Source code in pytvpaint/layer.py
498
499
500
501
@refreshed_property
def end(self) -> int:
    """The layer end frame according to the project's start frame."""
    return self._data.last_frame + self.project.start_frame

make_current() -> None

Make the layer current, it also makes the clip current.

Source code in pytvpaint/layer.py
519
520
521
522
523
524
525
def make_current(self) -> None:
    """Make the layer current, it also makes the clip current."""
    if self.is_current:
        return
    if self.clip:
        self.clip.make_current()
    george.tv_layer_set(self.id)

convert_to_anim_layer() -> None

Converts the layer to an animation layer.

Source code in pytvpaint/layer.py
665
666
667
668
@set_as_current
def convert_to_anim_layer(self) -> None:
    """Converts the layer to an animation layer."""
    george.tv_layer_anim(self.id)

load_dependencies() -> None

Load all dependencies of the layer in memory.

Source code in pytvpaint/layer.py
706
707
708
def load_dependencies(self) -> None:
    """Load all dependencies of the layer in memory."""
    george.tv_layer_load_dependencies(self.id)

current_layer_id() -> int staticmethod

Returns the current layer id.

Source code in pytvpaint/layer.py
710
711
712
713
@staticmethod
def current_layer_id() -> int:
    """Returns the current layer id."""
    return george.tv_layer_current_id()

current_layer() -> Layer classmethod

Returns the current layer object.

Source code in pytvpaint/layer.py
715
716
717
718
719
720
@classmethod
def current_layer(cls) -> Layer:
    """Returns the current layer object."""
    from pytvpaint.clip import Clip

    return cls(layer_id=cls.current_layer_id(), clip=Clip.current_clip())

shift(new_start: int) -> None

Move the layer to a new frame.

Source code in pytvpaint/layer.py
722
723
724
725
@set_as_current
def shift(self, new_start: int) -> None:
    """Move the layer to a new frame."""
    george.tv_layer_shift(self.id, new_start - self.project.start_frame)

merge(layer: Layer, blending_mode: george.BlendingMode, stamp: bool = False, erase: bool = False, keep_color_grp: bool = True, keep_img_mark: bool = True, keep_instance_name: bool = True) -> None

Merge this layer with the given one.

Parameters:

Name Type Description Default
layer pytvpaint.layer.Layer

the layer to merge with

required
blending_mode pytvpaint.george.BlendingMode

the blending mode to use

required
stamp bool

Use stamp mode

False
erase bool

Remove the source layer

False
keep_color_grp bool

Keep the color group

True
keep_img_mark bool

Keep the image mark

True
keep_instance_name bool

Keep the instance name

True
Source code in pytvpaint/layer.py
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
@set_as_current
def merge(
    self,
    layer: Layer,
    blending_mode: george.BlendingMode,
    stamp: bool = False,
    erase: bool = False,
    keep_color_grp: bool = True,
    keep_img_mark: bool = True,
    keep_instance_name: bool = True,
) -> None:
    """Merge this layer with the given one.

    Args:
        layer: the layer to merge with
        blending_mode: the blending mode to use
        stamp: Use stamp mode
        erase: Remove the source layer
        keep_color_grp: Keep the color group
        keep_img_mark: Keep the image mark
        keep_instance_name: Keep the instance name
    """
    george.tv_layer_merge(
        layer.id,
        blending_mode,
        stamp,
        erase,
        keep_color_grp,
        keep_img_mark,
        keep_instance_name,
    )

merge_all(keep_color_grp: bool = True, keep_img_mark: bool = True, keep_instance_name: bool = True) -> None staticmethod

Merge all the layers in the stack.

Parameters:

Name Type Description Default
keep_color_grp bool

Keep the color group

True
keep_img_mark bool

Keep the image mark

True
keep_instance_name bool

Keep the instance name

True
Source code in pytvpaint/layer.py
759
760
761
762
763
764
765
766
767
768
769
770
771
772
@staticmethod
def merge_all(
    keep_color_grp: bool = True,
    keep_img_mark: bool = True,
    keep_instance_name: bool = True,
) -> None:
    """Merge all the layers in the stack.

    Args:
        keep_color_grp: Keep the color group
        keep_img_mark: Keep the image mark
        keep_instance_name: Keep the instance name
    """
    george.tv_layer_merge_all(keep_color_grp, keep_img_mark, keep_instance_name)

new(name: str, clip: Clip | None = None, color: LayerColor | None = None) -> Layer classmethod

Create a new layer.

Parameters:

Name Type Description Default
name str

the name of the new layer

required
clip pytvpaint.clip.Clip | None

the parent clip

None
color pytvpaint.layer.LayerColor | None

the layer color

None

Returns:

Name Type Description
Layer pytvpaint.layer.Layer

the new layer

Note

The layer name is checked against all other layers to have a unique name using get_unique_name. This can take a while if you have a lot of layers.

Source code in pytvpaint/layer.py
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
@classmethod
@george.undoable
def new(
    cls,
    name: str,
    clip: Clip | None = None,
    color: LayerColor | None = None,
) -> Layer:
    """Create a new layer.

    Args:
        name: the name of the new layer
        clip: the parent clip
        color: the layer color

    Returns:
        Layer: the new layer

    Note:
        The layer name is checked against all other layers to have a unique name using `get_unique_name`.
        This can take a while if you have a lot of layers.
    """
    from pytvpaint.clip import Clip

    clip = clip or Clip.current_clip()
    clip.make_current()

    name = utils.get_unique_name(clip.layer_names, name)
    layer_type = 1 if cls == Layer else 0
    layer_id = george.tv_layer_create(name, layer_type=layer_type)

    layer = cls(layer_id=layer_id, clip=clip)
    if color:
        layer.color = color

    return layer

new_anim_layer(name: str, clip: Clip | None = None, color: LayerColor | None = None) -> Layer classmethod

Create a new animation layer.

Parameters:

Name Type Description Default
name str

the name of the new layer

required
clip pytvpaint.clip.Clip | None

the parent clip

None
color pytvpaint.layer.LayerColor | None

the layer color

None

Returns:

Name Type Description
Layer pytvpaint.layer.Layer

the new animation layer

Note

It activates the thumbnail visibility

Source code in pytvpaint/layer.py
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
@classmethod
@george.undoable
def new_anim_layer(
    cls,
    name: str,
    clip: Clip | None = None,
    color: LayerColor | None = None,
) -> Layer:
    """Create a new animation layer.

    Args:
        name: the name of the new layer
        clip: the parent clip
        color: the layer color

    Returns:
        Layer: the new animation layer

    Note:
        It activates the thumbnail visibility
    """
    layer = cls.new(name, clip, color)
    layer.convert_to_anim_layer()
    layer.thumbnails_visible = True
    return layer

new_background_layer(name: str, clip: Clip | None = None, color: LayerColor | None = None, image: Path | str | None = None, stretch: bool = False) -> Layer classmethod

Create a new background layer with hold as pre- and post-behavior.

Parameters:

Name Type Description Default
name str

the name of the new layer

required
clip pytvpaint.clip.Clip | None

the parent clip

None
color pytvpaint.layer.LayerColor | None

the layer color

None
image pathlib.Path | str | None

the background image to load

None
stretch bool

whether to stretch the image to fit the view

False

Returns:

Name Type Description
Layer pytvpaint.layer.Layer

the new animation layer

Source code in pytvpaint/layer.py
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
@classmethod
@george.undoable
def new_background_layer(
    cls,
    name: str,
    clip: Clip | None = None,
    color: LayerColor | None = None,
    image: Path | str | None = None,
    stretch: bool = False,
) -> Layer:
    """Create a new background layer with hold as pre- and post-behavior.

    Args:
        name: the name of the new layer
        clip: the parent clip
        color: the layer color
        image: the background image to load
        stretch: whether to stretch the image to fit the view

    Returns:
        Layer: the new animation layer
    """
    from pytvpaint.clip import Clip

    clip = clip or Clip.current_clip()
    layer = cls.new(name, clip, color)
    layer.pre_behavior = george.LayerBehavior.HOLD
    layer.post_behavior = george.LayerBehavior.HOLD
    layer.thumbnails_visible = True

    image = Path(image or "")
    if image.is_file():
        layer.convert_to_anim_layer()
        layer.load_image(image, frame=clip.start, stretch=stretch)

    return layer

duplicate(name: str) -> Layer

Duplicate this layer.

Parameters:

Name Type Description Default
name str

the duplicated layer name

required

Returns:

Name Type Description
Layer pytvpaint.layer.Layer

the duplicated layer

Source code in pytvpaint/layer.py
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
@set_as_current
@george.undoable
def duplicate(self, name: str) -> Layer:
    """Duplicate this layer.

    Args:
        name: the duplicated layer name

    Returns:
        Layer: the duplicated layer
    """
    name = utils.get_unique_name(self.clip.layer_names, name)
    layer_id = george.tv_layer_duplicate(name)

    return Layer(layer_id=layer_id, clip=self.clip)

remove() -> None

Remove the layer from the clip.

Warning

The current instance won't be usable after this call since it will be mark removed.

Source code in pytvpaint/layer.py
890
891
892
893
894
895
896
897
898
899
def remove(self) -> None:
    """Remove the layer from the clip.

    Warning:
        The current instance won't be usable after this call since it will be mark removed.
    """
    self.clip.make_current()
    self.is_locked = False
    george.tv_layer_kill(self.id)
    self.mark_removed()

render(output_path: Path | str | FileSequence, start: int | None = None, end: int | None = None, frame_set: FrameSet | None = None, use_camera: bool = False, alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY, background_mode: george.BackgroundMode | None = None, format_opts: list[str] | None = None) -> Path | FileSequence

Render the layer to a single frame or frame sequence or movie.

Parameters:

Name Type Description Default
output_path pathlib.Path | str | fileseq.filesequence.FileSequence

a single file or file sequence pattern

required
start int | None

the start frame to render the layer's start if None. Defaults to None.

None
end int | None

the end frame to render or the layer's end if None. Defaults to None.

None
frame_set fileseq.frameset.FrameSet | None

a FrameSet with the frames/range to render. Defaults to None.

None
use_camera bool

use the camera for rendering, otherwise render the whole canvas. Defaults to False.

False
alpha_mode pytvpaint.george.AlphaSaveMode

the alpha mode for rendering. Defaults to george.AlphaSaveMode.PREMULTIPLY.

pytvpaint.george.AlphaSaveMode.PREMULTIPLY
background_mode pytvpaint.george.BackgroundMode | None

the background mode for rendering. Defaults to None.

None
format_opts list[str] | None

custom format options. Defaults to None.

None

Raises:

Type Description
ValueError

if requested range (start-end) not in clip range/bounds

ValueError

if output is a movie

FileNotFoundError

if the render failed and no files were found on disk or missing frames

Note

This functions uses the layer's range as a basis (start-end). This is different from a project range, which uses the project timeline. For more details on the differences in frame ranges and the timeline in TVPaint, please check the Usage/Rendering section of the documentation.

Warning

Even tough pytvpaint does a pretty good job of correcting the frame ranges for rendering, we're still encountering some weird edge cases where TVPaint will consider the range invalid for seemingly no reason.

Returns:

Type Description
pathlib.Path | fileseq.filesequence.FileSequence

the output file path or sequence

Source code in pytvpaint/layer.py
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
@set_as_current
def render(
    self,
    output_path: Path | str | FileSequence,
    start: int | None = None,
    end: int | None = None,
    frame_set: FrameSet | None = None,
    use_camera: bool = False,
    alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY,
    background_mode: george.BackgroundMode | None = None,
    format_opts: list[str] | None = None,
) -> Path | FileSequence:
    """Render the layer to a single frame or frame sequence or movie.

    Args:
        output_path: a single file or file sequence pattern
        start: the start frame to render the layer's start if None. Defaults to None.
        end: the end frame to render or the layer's end if None. Defaults to None.
        frame_set: a FrameSet with the frames/range to render. Defaults to None.
        use_camera: use the camera for rendering, otherwise render the whole canvas. Defaults to False.
        alpha_mode: the alpha mode for rendering. Defaults to george.AlphaSaveMode.PREMULTIPLY.
        background_mode: the background mode for rendering. Defaults to None.
        format_opts: custom format options. Defaults to None.

    Raises:
        ValueError: if requested range (start-end) not in clip range/bounds
        ValueError: if output is a movie
        FileNotFoundError: if the render failed and no files were found on disk or missing frames

    Note:
        This functions uses the layer's range as a basis (start-end). This  is different from a project range, which
        uses the project timeline. For more details on the differences in frame ranges and the timeline in TVPaint,
        please check the `Usage/Rendering` section of the documentation.

    Warning:
        Even tough pytvpaint does a pretty good job of correcting the frame ranges for rendering, we're still
        encountering some weird edge cases where TVPaint will consider the range invalid for seemingly no reason.

    Returns:
        the output file path or sequence
    """
    start = self.start if start is None else start
    end = self.end if end is None else end
    frame_set = frame_set or FrameSet(f"{start}-{end}")
    return self.clip.render(
        output_path=output_path,
        frame_set=frame_set,
        use_camera=use_camera,
        layer_selection=[self],
        alpha_mode=alpha_mode,
        background_mode=background_mode,
        format_opts=format_opts,
    )

render_frame(export_path: Path | str, frame: int | None = None, alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY, background_mode: george.BackgroundMode | None = george.BackgroundMode.NONE, format_opts: list[str] | None = None, use_camera: bool = False) -> Path

Render a frame from the layer.

Parameters:

Name Type Description Default
export_path pathlib.Path | str

the frame export path (the extension determines the output format)

required
frame int | None

the frame to render or the current frame if None. Defaults to None.

None
alpha_mode pytvpaint.george.AlphaSaveMode

the render alpha mode

pytvpaint.george.AlphaSaveMode.PREMULTIPLY
background_mode pytvpaint.george.BackgroundMode | None

the render background mode

pytvpaint.george.BackgroundMode.NONE
format_opts list[str] | None

custom output format options to pass when rendering

None
use_camera bool

use the camera for rendering, otherwise render the whole canvas. Defaults to False.

False

Raises:

Type Description
FileNotFoundError

if the render failed or output not found on disk

Returns:

Name Type Description
Path pathlib.Path

render output path

Source code in pytvpaint/layer.py
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
@set_as_current
def render_frame(
    self,
    export_path: Path | str,
    frame: int | None = None,
    alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY,
    background_mode: george.BackgroundMode | None = george.BackgroundMode.NONE,
    format_opts: list[str] | None = None,
    use_camera: bool = False,
) -> Path:
    """Render a frame from the layer.

    Args:
        export_path: the frame export path (the extension determines the output format)
        frame: the frame to render or the current frame if None. Defaults to None.
        alpha_mode: the render alpha mode
        background_mode: the render background mode
        format_opts: custom output format options to pass when rendering
        use_camera: use the camera for rendering, otherwise render the whole canvas. Defaults to False.

    Raises:
        FileNotFoundError: if the render failed or output not found on disk

    Returns:
        Path: render output path
    """
    export_path = Path(export_path)
    frame = frame or self.clip.current_frame
    self.clip.current_frame = frame

    self.clip.render(
        output_path=export_path,
        frame_set=FrameSet(frame),
        use_camera=use_camera,
        layer_selection=[self],
        alpha_mode=alpha_mode,
        background_mode=background_mode,
        format_opts=format_opts,
    )
    return Path(export_path)

render_instances(export_path: Path | str | FileSequence, frame_set: FrameSet | None = None, alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY, background_mode: george.BackgroundMode | None = None, format_opts: list[str] | None = None, use_camera: bool = False) -> Path | FileSequence

Render all layer instances in the provided range for the current layer.

Parameters:

Name Type Description Default
export_path pathlib.Path | str | fileseq.filesequence.FileSequence

the export path (the extension determines the output format)

required
frame_set fileseq.frameset.FrameSet | None

Render only the instances within the provided frameset. Defaults to None.

None
alpha_mode pytvpaint.george.AlphaSaveMode

the render alpha mode

pytvpaint.george.AlphaSaveMode.PREMULTIPLY
background_mode pytvpaint.george.BackgroundMode | None

the render background mode

None
format_opts list[str] | None

custom output format options to pass when rendering

None
use_camera bool

use the camera for rendering, otherwise render the whole canvas. Defaults to False.

False

Raises:

Type Description
ValueError

if requested range (start-end) not in layer range/bounds

ValueError

if output is a movie

FileNotFoundError

if the render failed or output not found on disk

Returns:

Name Type Description
FileSequence pathlib.Path | fileseq.filesequence.FileSequence

instances output sequence

Source code in pytvpaint/layer.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
@set_as_current
def render_instances(
    self,
    export_path: Path | str | FileSequence,
    frame_set: FrameSet | None = None,
    alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY,
    background_mode: george.BackgroundMode | None = None,
    format_opts: list[str] | None = None,
    use_camera: bool = False,
) -> Path | FileSequence:
    """Render all layer instances in the provided range for the current layer.

    Args:
        export_path: the export path (the extension determines the output format)
        frame_set: Render only the instances within the provided frameset. Defaults to None.
        alpha_mode: the render alpha mode
        background_mode: the render background mode
        format_opts: custom output format options to pass when rendering
        use_camera: use the camera for rendering, otherwise render the whole canvas. Defaults to False.

    Raises:
        ValueError: if requested range (start-end) not in layer range/bounds
        ValueError: if output is a movie
        FileNotFoundError: if the render failed or output not found on disk

    Returns:
        FileSequence: instances output sequence
    """
    frames = sorted([layer_instance.start for layer_instance in self.instances])
    if frame_set is not None:
        frame_set = FrameSet([f for f in frame_set.items if f in frames])
    else:
        frame_set = FrameSet(frames) or frame_set

    return self.clip.render(
        output_path=export_path,
        frame_set=frame_set,
        use_camera=use_camera,
        layer_selection=[self],
        alpha_mode=alpha_mode,
        background_mode=background_mode,
        format_opts=format_opts,
    )

load_image(image_path: str | Path, frame: int | None = None, stretch: bool = False) -> None

Load an image in the current layer at a given frame.

Parameters:

Name Type Description Default
image_path str | pathlib.Path

path to the image to load

required
frame int | None

the frame where the image will be loaded, if none provided, image will be loaded at current frame

None
stretch bool

whether to stretch the image to fit the view

False

Raises:

Type Description
FileNotFoundError

if the file doesn't exist at provided path

Source code in pytvpaint/layer.py
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
@set_as_current
def load_image(self, image_path: str | Path, frame: int | None = None, stretch: bool = False) -> None:
    """Load an image in the current layer at a given frame.

    Args:
        image_path: path to the image to load
        frame: the frame where the image will be loaded, if none provided, image will be loaded at current frame
        stretch: whether to stretch the image to fit the view

    Raises:
        FileNotFoundError: if the file doesn't exist at provided path
    """
    image_path = Path(image_path)
    if not image_path.exists():
        raise FileNotFoundError(f"Image not found at : {image_path}")

    frame = frame or self.clip.current_frame
    with utils.restore_current_frame(self.clip, frame):
        # if no instance at the specified frame, then create a new one
        if not self.get_instance(frame):
            self.add_instance(frame)

        george.tv_load_image(image_path.as_posix(), stretch)

get_mark_color(frame: int) -> LayerColor | None

Get the mark color at a specific frame.

Parameters:

Name Type Description Default
frame int

frame with a mark

required

Returns:

Type Description
pytvpaint.layer.LayerColor | None

LayerColor | None: the layer color if there was a mark

Source code in pytvpaint/layer.py
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
def get_mark_color(self, frame: int) -> LayerColor | None:
    """Get the mark color at a specific frame.

    Args:
        frame: frame with a mark

    Returns:
        LayerColor | None: the layer color if there was a mark
    """
    frame = frame - self.project.start_frame
    color_index = george.tv_layer_mark_get(self.id, frame)
    if not color_index:
        return None

    return self.clip.get_layer_color(by_index=color_index)

add_mark(frame: int, color: LayerColor) -> None

Add a mark to a frame.

Parameters:

Name Type Description Default
frame int

frame to put a mark on

required
color pytvpaint.layer.LayerColor

the color index

required

Raises:

Type Description
TypeError

if the layer is not an animation layer

Source code in pytvpaint/layer.py
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
def add_mark(self, frame: int, color: LayerColor) -> None:
    """Add a mark to a frame.

    Args:
        frame: frame to put a mark on
        color: the color index

    Raises:
        TypeError: if the layer is not an animation layer
    """
    if not self.is_anim_layer:
        raise TypeError(f"Can't add a mark because this is not an animation layer ({self})")
    frame = frame - self.project.start_frame
    george.tv_layer_mark_set(self.id, frame, color.index)

remove_mark(frame: int) -> None

Remove a mark.

Parameters:

Name Type Description Default
frame int

a frame number with a mark

required
Source code in pytvpaint/layer.py
1095
1096
1097
1098
1099
1100
1101
1102
def remove_mark(self, frame: int) -> None:
    """Remove a mark.

    Args:
        frame: a frame number with a mark
    """
    # Setting it at 0 clears the mark
    self.add_mark(frame, LayerColor(0, self.clip))

clear_marks() -> None

Clear all the marks in the layer.

Source code in pytvpaint/layer.py
1118
1119
1120
1121
def clear_marks(self) -> None:
    """Clear all the marks in the layer."""
    for frame, _ in self.marks:
        self.remove_mark(frame)

pan(position: tuple[int, int], move_fill: bool = False, anti_aliasing: bool = False) -> None

Apply a panning FX to the current layer.

Parameters:

Name Type Description Default
position tuple[int, int]

new position of the layer (position is calculated from the top left corner of the layer frame)

required
move_fill bool

True to moved and fill all screen, False to only move images

False
anti_aliasing bool

apply antialiasing

False
Source code in pytvpaint/layer.py
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
@set_as_current
def pan(self, position: tuple[int, int], move_fill: bool = False, anti_aliasing: bool = False) -> None:
    """Apply a panning FX to the current layer.

    Args:
        position: new position of the layer (position is calculated from the top left corner of the layer frame)
        move_fill: True to moved and fill all screen, False to only move images
        anti_aliasing: apply antialiasing
    """
    george.tv_panning(position[0], position[1], move_fill, anti_aliasing)

select_frames(start: int, end: int) -> None

Select the frames from a start and count.

Parameters:

Name Type Description Default
start int

the selection start frame

required
end int

the selected end frame

required
Source code in pytvpaint/layer.py
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
@set_as_current
def select_frames(self, start: int, end: int) -> None:
    """Select the frames from a start and count.

    Args:
        start: the selection start frame
        end: the selected end frame
    """
    if not self.is_anim_layer:
        log.warning("Selection may display weird behaviour when applied to a non animation layer")
    frame_count = (end - start) + 1
    george.tv_layer_select(start - self.clip.start, frame_count)

select_all_frames() -> None

Select all frames in the layer.

Source code in pytvpaint/layer.py
1147
1148
1149
1150
1151
@set_as_current
def select_all_frames(self) -> None:
    """Select all frames in the layer."""
    frame, frame_count = george.tv_layer_select_info(full=True)
    george.tv_layer_select(frame, frame_count)

clear_selection() -> None

Clear frame selection in the layer.

Source code in pytvpaint/layer.py
1153
1154
1155
1156
1157
@set_as_current
def clear_selection(self) -> None:
    """Clear frame selection in the layer."""
    # selecting frames after the layer's end frame will result in a empty selection, thereby clearing the selection
    george.tv_layer_select(self.end + 1, 0)

cut_selection() -> None

Cut the selected instances.

Source code in pytvpaint/layer.py
1169
1170
1171
1172
@set_as_current
def cut_selection(self) -> None:
    """Cut the selected instances."""
    george.tv_layer_cut()

copy_selection() -> None

Copy the selected instances.

Source code in pytvpaint/layer.py
1174
1175
1176
1177
@set_as_current
def copy_selection(self) -> None:
    """Copy the selected instances."""
    george.tv_layer_copy()

paste_selection(target_layer: Layer | None = None) -> None

Paste the layer into another layer (regardless of the project).

Source code in pytvpaint/layer.py
1179
1180
1181
1182
1183
def paste_selection(self, target_layer: Layer | None = None) -> None:
    """Paste the layer into another layer (regardless of the project)."""
    if target_layer:
        target_layer.make_current()
    george.tv_layer_paste()

cut() -> None

Copy the layer (cuts all instances in layer).

Source code in pytvpaint/layer.py
1185
1186
1187
1188
1189
@set_as_current
def cut(self) -> None:
    """Copy the layer (cuts all instances in layer)."""
    self.select_all_frames()
    self.cut_selection()

copy() -> None

Copy the layer (copies all instances in layer).

Source code in pytvpaint/layer.py
1191
1192
1193
1194
1195
@set_as_current
def copy(self) -> None:
    """Copy the layer (copies all instances in layer)."""
    self.select_all_frames()
    self.copy_selection()

paste() -> None

Copy the layer (copies all instances in layer).

Source code in pytvpaint/layer.py
1197
1198
1199
1200
1201
@set_as_current
def paste(self) -> None:
    """Copy the layer (copies all instances in layer)."""
    self.select_all_frames()
    self.paste_selection()

instances() -> Iterator[LayerInstance]

Iterates over the layer instances.

Yields:

Type Description
pytvpaint.layer.LayerInstance

each LayerInstance present in the layer

Source code in pytvpaint/layer.py
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
@refreshed_property
@set_as_current
def instances(self) -> Iterator[LayerInstance]:
    """Iterates over the layer instances.

    Yields:
        each LayerInstance present in the layer
    """
    # instances start at layer start
    current_instance = LayerInstance(self, self.start)

    while True:
        yield current_instance

        nex_instance = current_instance.next
        if nex_instance is None:
            break

        current_instance = nex_instance

get_instance(frame: int, strict: bool = False) -> LayerInstance | None

Get the instance at that frame.

Parameters:

Name Type Description Default
frame int

the instance frame

required
strict bool

True will only return Instance if the given frame is the start of the instance. Default is False

False

Returns:

Type Description
pytvpaint.layer.LayerInstance | None

the instance if found else None

Source code in pytvpaint/layer.py
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
def get_instance(self, frame: int, strict: bool = False) -> LayerInstance | None:
    """Get the instance at that frame.

    Args:
        frame: the instance frame
        strict: True will only return Instance if the given frame is the start of the instance. Default is False

    Returns:
        the instance if found else None
    """
    for layer_instance in self.instances:
        if strict:
            if layer_instance.start != frame:
                continue
            return layer_instance

        if not (layer_instance.start <= frame <= layer_instance.end):
            continue
        return layer_instance

    return None

get_instances(from_frame: int, to_frame: int) -> Iterator[LayerInstance]

Iterates over the layer instances and returns the one in the range (from_frame-to_frame).

Yields:

Type Description
pytvpaint.layer.LayerInstance

each LayerInstance in the range (from_frame-to_frame)

Source code in pytvpaint/layer.py
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
def get_instances(self, from_frame: int, to_frame: int) -> Iterator[LayerInstance]:
    """Iterates over the layer instances and returns the one in the range (from_frame-to_frame).

    Yields:
        each LayerInstance in the range (from_frame-to_frame)
    """
    for layer_instance in self.instances:
        if layer_instance.end < from_frame:
            continue
        if from_frame <= layer_instance.start <= to_frame:
            yield layer_instance
        if layer_instance.start > to_frame:
            break

add_instance(start: int | None = None, nb_frames: int = 1, direction: george.InsertDirection | None = None, split: bool = False) -> LayerInstance

Crates a new instance.

Parameters:

Name Type Description Default
start int | None

start frame. Defaults to clip current frame if none provided

None
nb_frames int

number of frames in the new instance. Default is 1, this is the total number of frames created.

1
direction pytvpaint.george.InsertDirection | None

direction where new frames will be added/inserted

None
split bool

True to make each added frame a new image

False

Raises:

Type Description
TypeError

if the layer is not an animation layer

ValueError

if the number of frames nb_frames is inferior or equal to 0

ValueError

if an instance already exists at the given range (start + nb_frames)

Returns:

Name Type Description
LayerInstance pytvpaint.layer.LayerInstance

new layer instance

Source code in pytvpaint/layer.py
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
@set_as_current
def add_instance(
    self,
    start: int | None = None,
    nb_frames: int = 1,
    direction: george.InsertDirection | None = None,
    split: bool = False,
) -> LayerInstance:
    """Crates a new instance.

    Args:
        start: start frame. Defaults to clip current frame if none provided
        nb_frames: number of frames in the new instance. Default is 1, this is the total number of frames created.
        direction: direction where new frames will be added/inserted
        split: True to make each added frame a new image

    Raises:
        TypeError: if the layer is not an animation layer
        ValueError: if the number of frames `nb_frames` is inferior or equal to 0
        ValueError: if an instance already exists at the given range (start + nb_frames)

    Returns:
        LayerInstance: new layer instance
    """
    if not self.is_anim_layer:
        raise TypeError("The layer needs to be an animation layer")

    if nb_frames <= 0:
        raise ValueError("Instance number of frames must be at least 1")

    if start and self.get_instance(start):
        raise ValueError(
            "An instance already exists at the designated frame range. Edit or delete it before adding a new one."
        )

    start = start if start is not None else self.clip.current_frame
    self.clip.make_current()

    temp_layer = Layer.new_anim_layer(str(uuid4()))
    temp_layer.make_current()

    with utils.restore_current_frame(self.clip, 1):
        if nb_frames > 1:
            if split:
                george.tv_layer_insert_image(count=nb_frames, direction=direction)
            else:
                layer_instance = next(temp_layer.instances)
                layer_instance.length = nb_frames

        temp_layer.select_all_frames()
        temp_layer.copy_selection()
        self.clip.current_frame = start
        self.make_current()
        self.paste_selection()
        temp_layer.remove()

    return LayerInstance(self, start)

rename_instances(mode: george.InstanceNamingMode, prefix: str | None = None, suffix: str | None = None, process: george.InstanceNamingProcess | None = None) -> None

Rename all the instances.

Parameters:

Name Type Description Default
mode pytvpaint.george.InstanceNamingMode

the instance renaming mode

required
prefix str | None

the prefix to add to each name

None
suffix str | None

the suffix to add to each name

None
process pytvpaint.george.InstanceNamingProcess | None

the instance naming process

None
Source code in pytvpaint/layer.py
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
def rename_instances(
    self,
    mode: george.InstanceNamingMode,
    prefix: str | None = None,
    suffix: str | None = None,
    process: george.InstanceNamingProcess | None = None,
) -> None:
    """Rename all the instances.

    Args:
        mode: the instance renaming mode
        prefix: the prefix to add to each name
        suffix: the suffix to add to each name
        process: the instance naming process
    """
    george.tv_instance_name(self.id, mode, prefix, suffix, process)

mark_removed() -> None

Marks the object as removed and is therefor not usable.

Source code in pytvpaint/utils.py
 98
 99
100
def mark_removed(self) -> None:
    """Marks the object as removed and is therefor not usable."""
    self._is_removed = True