Skip to content

Layer class

Bases: pytvpaint.utils.Removable

A Layer is inside a clip and contains drawings.

Source code in pytvpaint/layer.py
334
335
336
337
338
339
340
def __init__(self, layer_id: int, clip: Clip | None = None) -> None:
    from pytvpaint.clip import Clip

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

id: int property

The layers unique identifier.

Warning

layer ids are not persistent across project load/close

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

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.

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

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() -> None

Refreshes the layer data.

Source code in pytvpaint/layer.py
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()

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
420
421
422
423
424
425
426
427
428
429
430
431
@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
    value = utils.get_unique_name(self.clip.layer_names, value)
    george.tv_layer_rename(self.id, value)

layer_type() -> george.LayerType

The layer type.

Source code in pytvpaint/layer.py
433
434
435
436
@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
454
455
456
457
@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
459
460
461
462
@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
480
481
482
483
484
485
486
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
626
627
628
629
@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
636
637
638
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
640
641
642
643
@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
645
646
647
648
649
650
@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
652
653
654
655
@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
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
@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
689
690
691
692
693
694
695
696
697
698
699
700
701
702
@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 staticmethod

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
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
@staticmethod
@george.undoable
def new(
    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_id = george.tv_layer_create(name)

    layer = Layer(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
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
@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
766
767
768
769
770
771
772
773
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
@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
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
@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
819
820
821
822
823
824
825
826
827
828
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, use_camera: bool = False, alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY, background_mode: george.BackgroundMode | None = None, format_opts: list[str] | None = None) -> None

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
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.

Source code in pytvpaint/layer.py
830
831
832
833
834
835
836
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
873
874
875
876
877
@set_as_current
def render(
    self,
    output_path: Path | str | FileSequence,
    start: int | None = None,
    end: int | 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,
) -> None:
    """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.
        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.
    """
    start = self.start if start is None else start
    end = self.end if end is None else end
    self.clip.render(
        output_path=output_path,
        start=start,
        end=end,
        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) -> 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

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
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
@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,
) -> 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

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

    Returns:
        Path: render output path
    """
    export_path = Path(export_path)
    save_format = george.SaveFormat.from_extension(export_path.suffix)
    export_path.parent.mkdir(parents=True, exist_ok=True)

    frame = frame or self.clip.current_frame
    self.clip.current_frame = frame

    with utils.render_context(
        alpha_mode,
        background_mode,
        save_format,
        format_opts,
        layer_selection=[self],
    ):
        george.tv_save_image(export_path)

    if not export_path.exists():
        raise FileNotFoundError(
            f"Could not find rendered image ({frame}) at : {export_path.as_posix()}"
        )

    return export_path

render_instances(export_path: Path | str | FileSequence, start: int | None = None, end: int | None = None, alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY, background_mode: george.BackgroundMode | None = None, format_opts: list[str] | None = None) -> 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
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
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

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 fileseq.filesequence.FileSequence

instances output sequence

Source code in pytvpaint/layer.py
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
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
@set_as_current
def render_instances(
    self,
    export_path: Path | str | FileSequence,
    start: int | None = None,
    end: int | None = None,
    alpha_mode: george.AlphaSaveMode = george.AlphaSaveMode.PREMULTIPLY,
    background_mode: george.BackgroundMode | None = None,
    format_opts: list[str] | None = None,
) -> 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)
        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.
        alpha_mode: the render alpha mode
        background_mode: the render background mode
        format_opts: custom output format options to pass when rendering

    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
    """
    file_sequence, start, end, is_sequence, is_image = utils.handle_output_range(
        export_path, self.start, self.end, start, end
    )

    if start < self.start or end > self.end:
        raise ValueError(
            f"Render ({start}-{end}) not in clip range ({(self.start, self.end)})"
        )
    if not is_image:
        raise ValueError(
            f"Video formats ({file_sequence.extension()}) are not supported for instance rendering !"
        )

    # render to output
    frames = []
    for layer_instance in self.instances:
        cur_frame = layer_instance.start
        instance_output = Path(file_sequence.frame(cur_frame))
        self.render_frame(
            instance_output, cur_frame, alpha_mode, background_mode, format_opts
        )
        frames.append(str(cur_frame))

    file_sequence.setFrameSet(FrameSet(",".join(frames)))
    return file_sequence

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
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
@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
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
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
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
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
1039
1040
1041
1042
1043
1044
1045
1046
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
1062
1063
1064
1065
def clear_marks(self) -> None:
    """Clear all the marks in the layer."""
    for frame, _ in self.marks:
        self.remove_mark(frame)

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
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
@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
1082
1083
1084
1085
1086
@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
1088
1089
1090
1091
1092
@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
1104
1105
1106
1107
@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
1109
1110
1111
1112
@set_as_current
def copy_selection(self) -> None:
    """Copy the selected instances."""
    george.tv_layer_copy()

paste_selection() -> None

Paste the previously copied instances.

Source code in pytvpaint/layer.py
1114
1115
1116
1117
@set_as_current
def paste_selection(self) -> None:
    """Paste the previously copied instances."""
    george.tv_layer_paste()

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
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
@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
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
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
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
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
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
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
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
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)