Repository: ManimCommunity/manim
Pull request: Changed ``color``, ``stroke_color`` and ``fill_color`` attributes to properties
<!-- Thank you for contributing to Manim! Learn more about the process in our contributing guidelines: https://docs.manim.community/en/latest/contributing.html -->

## Overview: What does this pull request change?
<!-- If there is more information than the PR title that should be added to our release changelog, add it in the following changelog section. This is optional, but recommended for larger pull requests. -->
<!--changelog-start-->
<!--changelog-end-->

- `color`, `stroke_color` and `fill_color` are now properties in `VMobject`, making them synchronized with `set_*` and `get_*` methods
- Surrounding code depending on these attributes was also updated to work properly with them

## Motivation and Explanation: Why and how do your changes improve the library?
<!-- Optional for bugfixes, small enhancements, and documentation-related PRs. Otherwise, please give a short reasoning for your changes. -->
Fixes #2324.

<!-- Thank you again for contributing! Do not modify the lines below, they are for reviewers. -->
## Reviewer Checklist
- [ ] The PR title is descriptive enough for the changelog, and the PR is labeled correctly
- [ ] If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
- [ ] If applicable: newly added functions and classes are tested


Problem statement:
Attribute not updated when animated with `.animate` 
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->
Mobject attributes are not updated when animated with `.animate`, but are successfully changed when done outside of animations


## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->
The attribute should be updated.


## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

```py
class TS(Scene):
    def construct(self):
        c1 = Circle(fill_color=BLUE, fill_opacity=1)

        c1.set_stroke(PURPLE)
        # stroke is changed here, then becomes white 
        c1.set_stroke(WHITE)

        self.add(c1)
        self.play(c1.animate.set_stroke(color=RED)) # visually becomes red, but attribute does not match

        if c1.stroke_color == RED: # stroke_color  attr is `WHITE` not `RED`, so nothing is animated.
            a = Rectangle().to_edge(UP)
            self.play(DrawBorderThenFill(a))
        
        breakpoint()
```


## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->

Hints from issue discussion:
maybe related to #2320?
It's not related to #2320.

If I've understood correctly, `c1.animate.set_stroke(...)` works by cloning `c1` into `c1.target`, then calling set_stroke on the target. The animation then Tranforms from `c1` to `c1.target`, but never actually edits `c1` beyond that.

I would propose a decent fix would be to that `c1.animate.set_stroke` should call `c1.set_stroke` at the appropriate time, possibly during the `finish` step of the animation.

Commit message: [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Patch to review:
diff --git a/manim/mobject/geometry.py b/manim/mobject/geometry.py
index f0f7d07690..db31f8c689 100644
--- a/manim/mobject/geometry.py
+++ b/manim/mobject/geometry.py
@@ -2558,8 +2558,8 @@ def construct(self):
     """
 
     def __init__(self, corner_radius=0.5, **kwargs):
-        self.corner_radius = corner_radius
         super().__init__(**kwargs)
+        self.corner_radius = corner_radius
         self.round_corners(self.corner_radius)
 
 
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
index 7ea9dbec39..226400d397 100644
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -91,7 +91,6 @@ def __init_subclass__(cls, **kwargs):
         cls._original__init__ = cls.__init__
 
     def __init__(self, color=WHITE, name=None, dim=3, target=None, z_index=0):
-        self.color = Color(color) if color else None
         self.name = self.__class__.__name__ if name is None else name
         self.dim = dim
         self.target = target
@@ -100,6 +99,8 @@ def __init__(self, color=WHITE, name=None, dim=3, target=None, z_index=0):
         self.submobjects = []
         self.updaters = []
         self.updating_suspended = False
+        self.color = Color(color) if color else None
+
         self.reset_points()
         self.generate_points()
         self.init_colors()
diff --git a/manim/mobject/opengl_mobject.py b/manim/mobject/opengl_mobject.py
index 51fab13e82..3104e0b684 100644
--- a/manim/mobject/opengl_mobject.py
+++ b/manim/mobject/opengl_mobject.py
@@ -96,7 +96,6 @@ def __init__(
         self.data = getattr(self, "data", {})
         self.uniforms = getattr(self, "uniforms", {})
 
-        self.color = Color(color) if color else None
         self.opacity = opacity
         self.dim = dim  # TODO, get rid of this
         # Lighting parameters
@@ -129,6 +128,7 @@ def __init__(
         self.init_updaters()
         # self.init_event_listners()
         self.init_points()
+        self.color = Color(color) if color else None
         self.init_colors()
 
         self.shader_indices = None
@@ -646,7 +646,7 @@ def assemble_family(self):
         return self
 
     def get_family(self, recurse=True):
-        if recurse:
+        if recurse and hasattr(self, "family"):
             return self.family
         else:
             return [self]
@@ -1874,13 +1874,18 @@ def set_rgba_array(self, color=None, opacity=None, name="rgbas", recurse=True):
         # Color only
         if color is not None and opacity is None:
             for mob in self.get_family(recurse):
-                mob.data[name] = resize_array(mob.data[name], len(rgbs))
+                mob.data[name] = resize_array(
+                    mob.data[name] if name in mob.data else np.empty((1, 3)), len(rgbs)
+                )
                 mob.data[name][:, :3] = rgbs
 
         # Opacity only
         if color is None and opacity is not None:
             for mob in self.get_family(recurse):
-                mob.data[name] = resize_array(mob.data[name], len(opacities))
+                mob.data[name] = resize_array(
+                    mob.data[name] if name in mob.data else np.empty((1, 3)),
+                    len(opacities),
+                )
                 mob.data[name][:, 3] = opacities
 
         # Color and opacity
@@ -2619,7 +2624,7 @@ def get_shader_vert_indices(self):
 
     @property
     def submobjects(self):
-        return self._submobjects
+        return self._submobjects if hasattr(self, "_submobjects") else []
 
     @submobjects.setter
     def submobjects(self, submobject_list):
diff --git a/manim/mobject/shape_matchers.py b/manim/mobject/shape_matchers.py
index c17f2b9783..4e8be84bfb 100644
--- a/manim/mobject/shape_matchers.py
+++ b/manim/mobject/shape_matchers.py
@@ -44,15 +44,14 @@ def construct(self):
     def __init__(
         self, mobject, color=YELLOW, buff=SMALL_BUFF, corner_radius=0.0, **kwargs
     ):
-        self.color = color
-        self.buff = buff
         super().__init__(
             color=color,
-            width=mobject.width + 2 * self.buff,
-            height=mobject.height + 2 * self.buff,
+            width=mobject.width + 2 * buff,
+            height=mobject.height + 2 * buff,
             corner_radius=corner_radius,
             **kwargs
         )
+        self.buff = buff
         self.move_to(mobject)
 
 
diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py
index 3781cff55c..98bb46964b 100644
--- a/manim/mobject/svg/svg_mobject.py
+++ b/manim/mobject/svg/svg_mobject.py
@@ -9,11 +9,12 @@
 import re
 import string
 import warnings
-from typing import Dict, List
+from typing import Dict, List, Optional
 from xml.dom.minidom import Element as MinidomElement
 from xml.dom.minidom import parse as minidom_parse
 
 import numpy as np
+from colour import Color
 
 from ... import config, logger
 from ...constants import *
@@ -76,6 +77,10 @@ def __init__(
         should_subdivide_sharp_curves=False,
         should_remove_null_curves=False,
         color=None,
+        *,
+        fill_color=None,
+        stroke_color=None,
+        stroke_opacity=1.0,
         **kwargs,
     ):
         self.def_map = {}
@@ -91,8 +96,21 @@ def __init__(
             if config.renderer == "opengl"
             else {}
         )
+        self._initial_svg_style = self.generate_style(
+            Color(color) if color else None,
+            Color(fill_color) if fill_color else None,
+            Color(stroke_color) if stroke_color else None,
+            fill_opacity,
+            stroke_opacity,
+        )
         super().__init__(
-            color=color, fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs
+            color=color,
+            fill_opacity=fill_opacity,
+            stroke_width=stroke_width,
+            fill_color=fill_color,
+            stroke_opacity=stroke_opacity,
+            stroke_color=stroke_color,
+            **kwargs,
         )
         self._move_into_position(width, height)
 
@@ -136,7 +154,7 @@ def generate_points(self):
         for node in doc.childNodes:
             if not isinstance(node, MinidomElement) or node.tagName != "svg":
                 continue
-            mobjects = self._get_mobjects_from(node, self.generate_style())
+            mobjects = self._get_mobjects_from(node, self._initial_svg_style)
             if self.unpack_groups:
                 self.add(*mobjects)
             else:
@@ -230,17 +248,25 @@ def _get_mobjects_from(
 
         return result
 
-    def generate_style(self):
+    def generate_style(
+        self,
+        color: Color | None,
+        fill_color: Color | None,
+        stroke_color: Color | None,
+        fill_opacity: float,
+        stroke_opacity: float,
+    ):
         style = {
-            "fill-opacity": self.fill_opacity,
-            "stroke-opacity": self.stroke_opacity,
+            "fill-opacity": fill_opacity,
+            "stroke-opacity": stroke_opacity,
         }
-        if self.color:
-            style["fill"] = style["stroke"] = self.color.get_hex_l()
-        if self.fill_color:
-            style["fill"] = self.fill_color
-        if self.stroke_color:
-            style["stroke"] = self.stroke_color
+        if color:
+            style["fill"] = style["stroke"] = color.get_hex_l()
+        if fill_color:
+            style["fill"] = fill_color.hex_l
+        if stroke_color:
+            style["stroke"] = stroke_color.hex_l
+
         return style
 
     def _path_string_to_mobject(self, path_string: str, style: dict):
diff --git a/manim/mobject/svg/text_mobject.py b/manim/mobject/svg/text_mobject.py
index 9b0767e1d9..ba95d35822 100644
--- a/manim/mobject/svg/text_mobject.py
+++ b/manim/mobject/svg/text_mobject.py
@@ -427,7 +427,6 @@ def __init__(
         disable_ligatures: bool = False,
         **kwargs,
     ):
-        self.color = color
         self.line_spacing = line_spacing
         self.font = font
         self._font_size = float(font_size)
@@ -471,7 +470,7 @@ def __init__(
             )
         else:
             self.line_spacing = self._font_size + self._font_size * self.line_spacing
-        file_name = self._text2svg()
+        file_name = self._text2svg(color)
         PangoUtils.remove_last_M(file_name)
         super().__init__(
             file_name,
@@ -594,10 +593,10 @@ def _set_color_by_t2g(self, t2g=None):
             for start, end in self._find_indexes(word, self.text):
                 self.chars[start:end].set_color_by_gradient(*gradient)
 
-    def _text2hash(self):
+    def _text2hash(self, color: Color):
         """Generates ``sha256`` hash for file name."""
         settings = (
-            "PANGO" + self.font + self.slant + self.weight + self.color
+            "PANGO" + self.font + self.slant + self.weight + color
         )  # to differentiate Text and CairoText
         settings += str(self.t2f) + str(self.t2s) + str(self.t2w) + str(self.t2c)
         settings += str(self.line_spacing) + str(self._font_size)
@@ -671,7 +670,7 @@ def _get_settings_from_gradient(
                     settings.append(TextSetting(i, i + 1, **args))
         return settings
 
-    def _text2settings(self):
+    def _text2settings(self, color: Color):
         """Converts the texts and styles to a setting for parsing."""
         t2xs = [
             (self.t2f, "font"),
@@ -679,7 +678,9 @@ def _text2settings(self):
             (self.t2w, "weight"),
             (self.t2c, "color"),
         ]
-        setting_args = {arg: getattr(self, arg) for _, arg in t2xs}
+        setting_args = {
+            arg: getattr(self, arg) if arg != "color" else color for _, arg in t2xs
+        }
 
         settings = self._get_settings_from_t2xs(t2xs)
         settings.extend(self._get_settings_from_gradient(setting_args))
@@ -733,7 +734,7 @@ def _text2settings(self):
                 setting.line_num = 0
         return settings
 
-    def _text2svg(self):
+    def _text2svg(self, color: Color):
         """Convert the text to SVG using Pango."""
         size = self._font_size
         line_spacing = self.line_spacing
@@ -743,13 +744,13 @@ def _text2svg(self):
         dir_name = config.get_dir("text_dir")
         if not os.path.exists(dir_name):
             os.makedirs(dir_name)
-        hash_name = self._text2hash()
+        hash_name = self._text2hash(color)
         file_name = os.path.join(dir_name, hash_name) + ".svg"
 
         if os.path.exists(file_name):
             svg_file = file_name
         else:
-            settings = self._text2settings()
+            settings = self._text2settings(color)
             width = config["pixel_width"]
             height = config["pixel_height"]
 
@@ -1100,7 +1101,6 @@ def __init__(
         **kwargs,
     ):
         self.text = text
-        self.color = color
         self.line_spacing = line_spacing
         self.font = font
         self._font_size = float(font_size)
@@ -1133,7 +1133,7 @@ def __init__(
         else:
             self.line_spacing = self._font_size + self._font_size * self.line_spacing
 
-        file_name = self._text2svg()
+        file_name = self._text2svg(Color(color) if color else None)
         PangoUtils.remove_last_M(file_name)
         super().__init__(
             file_name,
@@ -1212,10 +1212,10 @@ def font_size(self, font_val):
         else:
             self.scale(font_val / self.font_size)
 
-    def _text2hash(self):
+    def _text2hash(self, color: Color):
         """Generates ``sha256`` hash for file name."""
         settings = (
-            "MARKUPPANGO" + self.font + self.slant + self.weight + self.color
+            "MARKUPPANGO" + self.font + self.slant + self.weight + color.hex_l
         )  # to differentiate from classical Pango Text
         settings += str(self.line_spacing) + str(self._font_size)
         settings += str(self.disable_ligatures)
@@ -1225,7 +1225,7 @@ def _text2hash(self):
         hasher.update(id_str.encode())
         return hasher.hexdigest()[:16]
 
-    def _text2svg(self):
+    def _text2svg(self, color: Optional[Color]):
         """Convert the text to SVG using Pango."""
         size = self._font_size
         line_spacing = self.line_spacing
@@ -1235,14 +1235,19 @@ def _text2svg(self):
         dir_name = config.get_dir("text_dir")
         if not os.path.exists(dir_name):
             os.makedirs(dir_name)
-        hash_name = self._text2hash()
+        hash_name = self._text2hash(color)
         file_name = os.path.join(dir_name, hash_name) + ".svg"
         if os.path.exists(file_name):
             svg_file = file_name
         else:
+            final_text = (
+                f'<span foreground="{color}">{self.text}</span>'
+                if color is not None
+                else self.text
+            )
             logger.debug(f"Setting Text {self.text}")
             svg_file = MarkupUtils.text2svg(
-                f'<span foreground="{self.color}">{self.text}</span>',
+                final_text,
                 self.font,
                 self.slant,
                 self.weight,
diff --git a/manim/mobject/types/opengl_vectorized_mobject.py b/manim/mobject/types/opengl_vectorized_mobject.py
index a88929bcc7..550da8cd2b 100644
--- a/manim/mobject/types/opengl_vectorized_mobject.py
+++ b/manim/mobject/types/opengl_vectorized_mobject.py
@@ -95,9 +95,7 @@ def __init__(
         **kwargs,
     ):
         self.data = {}
-        self.fill_color = fill_color
         self.fill_opacity = fill_opacity
-        self.stroke_color = stroke_color
         self.stroke_opacity = stroke_opacity
         self.stroke_width = stroke_width
         self.draw_stroke_behind_fill = draw_stroke_behind_fill
@@ -129,6 +127,11 @@ def __init__(
         super().__init__(**kwargs)
         self.refresh_unit_normal()
 
+        if fill_color:
+            self.fill_color = Color(fill_color)
+        if stroke_color:
+            self.stroke_color = Color(stroke_color)
+
     def get_group_class(self):
         return OpenGLVGroup
 
@@ -197,11 +200,6 @@ def construct(self):
         --------
         :meth:`~.OpenGLVMobject.set_style`
         """
-        if color is not None:
-            if isinstance(color, str):
-                self.fill_color = Color(color)
-            else:
-                self.fill_color = color
         if opacity is not None:
             self.fill_opacity = opacity
         if recurse:
@@ -219,11 +217,6 @@ def set_stroke(
         background=None,
         recurse=True,
     ):
-        if color is not None:
-            if isinstance(color, str):
-                self.stroke_color = Color(color)
-            else:
-                self.stroke_color = color
         if opacity is not None:
             self.stroke_opacity = opacity
         if recurse:
@@ -309,10 +302,6 @@ def match_style(self, vmobject, recurse=True):
         return self
 
     def set_color(self, color, opacity=None, recurse=True):
-        if isinstance(color, str):
-            self.color = Color(color)
-        else:
-            self.color = color
         if opacity is not None:
             self.opacity = opacity
 
@@ -339,13 +328,13 @@ def fade(self, darkness=0.5, recurse=True):
         return self
 
     def get_fill_colors(self):
-        return [rgb_to_hex(rgba[:3]) for rgba in self.fill_rgba]
+        return [Color(rgb_to_hex(rgba[:3])) for rgba in self.fill_rgba]
 
     def get_fill_opacities(self):
         return self.fill_rgba[:, 3]
 
     def get_stroke_colors(self):
-        return [rgb_to_hex(rgba[:3]) for rgba in self.stroke_rgba]
+        return [Color(rgb_to_hex(rgba[:3])) for rgba in self.stroke_rgba]
 
     def get_stroke_opacities(self):
         return self.stroke_rgba[:, 3]
@@ -383,6 +372,15 @@ def get_color(self):
             return self.get_stroke_color()
         return self.get_fill_color()
 
+    def get_colors(self):
+        if self.has_stroke():
+            return self.get_stroke_colors()
+        return self.get_fill_colors()
+
+    stroke_color = property(get_stroke_color, set_stroke)
+    color = property(get_color, set_color)
+    fill_color = property(get_fill_color, set_fill)
+
     def has_stroke(self):
         return any(self.get_stroke_widths()) and any(self.get_stroke_opacities())
 
diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py
index 2f1d34a39a..51d13d8b72 100644
--- a/manim/mobject/types/vectorized_mobject.py
+++ b/manim/mobject/types/vectorized_mobject.py
@@ -70,6 +70,8 @@ class VMobject(Mobject):
         This is within a pixel
     """
 
+    sheen_factor = 0.0
+
     def __init__(
         self,
         fill_color=None,
@@ -92,9 +94,7 @@ def __init__(
         n_points_per_cubic_curve=4,
         **kwargs,
     ):
-        self.fill_color = fill_color
         self.fill_opacity = fill_opacity
-        self.stroke_color = stroke_color
         self.stroke_opacity = stroke_opacity
         self.stroke_width = stroke_width
         self.background_stroke_color = background_stroke_color
@@ -113,18 +113,23 @@ def __init__(
         self.n_points_per_cubic_curve = n_points_per_cubic_curve
         super().__init__(**kwargs)
 
+        if fill_color:
+            self.fill_color = fill_color
+        if stroke_color:
+            self.stroke_color = stroke_color
+
     def get_group_class(self):
         return VGroup
 
     # Colors
     def init_colors(self, propagate_colors=True):
         self.set_fill(
-            color=self.fill_color or self.color,
+            color=self.fill_color,
             opacity=self.fill_opacity,
             family=propagate_colors,
         )
         self.set_stroke(
-            color=self.stroke_color or self.color,
+            color=self.stroke_color,
             width=self.stroke_width,
             opacity=self.stroke_opacity,
             family=propagate_colors,
@@ -239,8 +244,6 @@ def construct(self):
         self.update_rgbas_array("fill_rgbas", color, opacity)
         if opacity is not None:
             self.fill_opacity = opacity
-        if color is not None:
-            self.fill_color = color
         return self
 
     def set_stroke(
@@ -258,19 +261,17 @@ def set_stroke(
             array_name = "background_stroke_rgbas"
             width_name = "background_stroke_width"
             opacity_name = "background_stroke_opacity"
-            color_name = "background_stroke_color"
         else:
             array_name = "stroke_rgbas"
             width_name = "stroke_width"
             opacity_name = "stroke_opacity"
-            color_name = "stroke_color"
         self.update_rgbas_array(array_name, color, opacity)
         if width is not None:
             setattr(self, width_name, width)
         if opacity is not None:
             setattr(self, opacity_name, opacity)
-        if color is not None:
-            setattr(self, color_name, color)
+        if color is not None and background:
+            self.background_stroke_color = color
         return self
 
     def set_background_stroke(self, **kwargs):
@@ -357,12 +358,6 @@ def match_style(self, vmobject, family=True):
     def set_color(self, color, family=True):
         self.set_fill(color, family=family)
         self.set_stroke(color, family=family)
-
-        # check if a list of colors is passed to color
-        if isinstance(color, str):
-            self.color = colour.Color(color)
-        else:
-            self.color = color
         return self
 
     def set_opacity(self, opacity, family=True):
@@ -395,6 +390,8 @@ def get_fill_color(self):
         """
         return self.get_fill_colors()[0]
 
+    fill_color = property(get_fill_color, set_fill)
+
     def get_fill_opacity(self):
         """
         If there are multiple opacities, this returns the
@@ -403,7 +400,10 @@ def get_fill_opacity(self):
         return self.get_fill_opacities()[0]
 
     def get_fill_colors(self):
-        return [colour.Color(rgb=rgba[:3]) for rgba in self.get_fill_rgbas()]
+        return [
+            colour.Color(rgb=rgba[:3]) if rgba.any() else None
+            for rgba in self.get_fill_rgbas()
+        ]
 
     def get_fill_opacities(self):
         return self.get_fill_rgbas()[:, 3]
@@ -421,6 +421,8 @@ def get_stroke_rgbas(self, background=False):
     def get_stroke_color(self, background=False):
         return self.get_stroke_colors(background)[0]
 
+    stroke_color = property(get_stroke_color, set_stroke)
+
     def get_stroke_width(self, background=False):
         if background:
             width = self.background_stroke_width
@@ -435,7 +437,8 @@ def get_stroke_opacity(self, background=False):
 
     def get_stroke_colors(self, background=False):
         return [
-            colour.Color(rgb=rgba[:3]) for rgba in self.get_stroke_rgbas(background)
+            colour.Color(rgb=rgba[:3]) if rgba.any() else None
+            for rgba in self.get_stroke_rgbas(background)
         ]
 
     def get_stroke_opacities(self, background=False):
@@ -446,6 +449,8 @@ def get_color(self):
             return self.get_stroke_color()
         return self.get_fill_color()
 
+    color = property(get_color, set_color)
+
     def set_sheen_direction(self, direction: np.ndarray, family=True):
         """Sets the direction of the applied sheen.
 
diff --git a/manim/utils/color.py b/manim/utils/color.py
index 0b024c1faa..3842508d24 100644
--- a/manim/utils/color.py
+++ b/manim/utils/color.py
@@ -473,7 +473,7 @@ def rgba_to_color(rgba: Iterable[float]) -> Color:
 
 
 def rgb_to_hex(rgb: Iterable[float]) -> str:
-    return "#" + "".join("%02x" % int(255 * x) for x in rgb)
+    return "#" + "".join("%02x" % round(255 * x) for x in rgb)
 
 
 def hex_to_rgb(hex_code: str) -> np.ndarray:
diff --git a/manim/utils/config_ops.py b/manim/utils/config_ops.py
index 354cc2e679..f67c543565 100644
--- a/manim/utils/config_ops.py
+++ b/manim/utils/config_ops.py
@@ -58,10 +58,10 @@ def __set_name__(self, obj, name):
         self.name = name
 
     def __get__(self, obj, owner):
-        return obj.__dict__["data"][self.name]
+        return obj.data[self.name]
 
     def __set__(self, obj, array: np.ndarray):
-        obj.__dict__["data"][self.name] = array
+        obj.data[self.name] = array
 
 
 class _Uniforms:
diff --git a/tests/opengl/test_color_opengl.py b/tests/opengl/test_color_opengl.py
index 61d9273072..3e9e29b80d 100644
--- a/tests/opengl/test_color_opengl.py
+++ b/tests/opengl/test_color_opengl.py
@@ -102,67 +102,67 @@ def test_set_color_handles_lists_of_strs(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.color.hex == "#fff"
     m.set_color([BLACK, BLUE, GREEN])
-    assert m.color[0] == BLACK
-    assert m.color[1] == BLUE
-    assert m.color[2] == GREEN
+    assert m.get_colors()[0] == Color(BLACK)
+    assert m.get_colors()[1] == Color(BLUE)
+    assert m.get_colors()[2] == Color(GREEN)
 
-    assert m.fill_color[0] == BLACK
-    assert m.fill_color[1] == BLUE
-    assert m.fill_color[2] == GREEN
+    assert m.get_fill_colors()[0] == Color(BLACK)
+    assert m.get_fill_colors()[1] == Color(BLUE)
+    assert m.get_fill_colors()[2] == Color(GREEN)
 
-    assert m.stroke_color[0] == BLACK
-    assert m.stroke_color[1] == BLUE
-    assert m.stroke_color[2] == GREEN
+    assert m.get_stroke_colors()[0] == Color(BLACK)
+    assert m.get_stroke_colors()[1] == Color(BLUE)
+    assert m.get_stroke_colors()[2] == Color(GREEN)
 
 
 def test_set_color_handles_lists_of_color_objects(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.color.hex == "#fff"
     m.set_color([Color(PURE_BLUE), Color(PURE_GREEN), Color(PURE_RED)])
-    assert m.color[0].hex == "#00f"
-    assert m.color[1].hex == "#0f0"
-    assert m.color[2].hex == "#f00"
+    assert m.get_colors()[0].hex == "#00f"
+    assert m.get_colors()[1].hex == "#0f0"
+    assert m.get_colors()[2].hex == "#f00"
 
-    assert m.fill_color[0].hex == "#00f"
-    assert m.fill_color[1].hex == "#0f0"
-    assert m.fill_color[2].hex == "#f00"
+    assert m.get_fill_colors()[0].hex == "#00f"
+    assert m.get_fill_colors()[1].hex == "#0f0"
+    assert m.get_fill_colors()[2].hex == "#f00"
 
-    assert m.stroke_color[0].hex == "#00f"
-    assert m.stroke_color[1].hex == "#0f0"
-    assert m.stroke_color[2].hex == "#f00"
+    assert m.get_stroke_colors()[0].hex == "#00f"
+    assert m.get_stroke_colors()[1].hex == "#0f0"
+    assert m.get_stroke_colors()[2].hex == "#f00"
 
 
 def test_set_fill_handles_lists_of_strs(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.fill_color.hex == "#fff"
     m.set_fill([BLACK, BLUE, GREEN])
-    assert m.fill_color[0] == BLACK
-    assert m.fill_color[1] == BLUE
-    assert m.fill_color[2] == GREEN
+    assert m.get_fill_colors()[0] == Color(BLACK)
+    assert m.get_fill_colors()[1] == Color(BLUE)
+    assert m.get_fill_colors()[2] == Color(GREEN)
 
 
 def test_set_fill_handles_lists_of_color_objects(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.fill_color.hex == "#fff"
     m.set_fill([Color(PURE_BLUE), Color(PURE_GREEN), Color(PURE_RED)])
-    assert m.fill_color[0].hex == "#00f"
-    assert m.fill_color[1].hex == "#0f0"
-    assert m.fill_color[2].hex == "#f00"
+    assert m.get_fill_colors()[0].hex == "#00f"
+    assert m.get_fill_colors()[1].hex == "#0f0"
+    assert m.get_fill_colors()[2].hex == "#f00"
 
 
 def test_set_stroke_handles_lists_of_strs(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.stroke_color.hex == "#fff"
     m.set_stroke([BLACK, BLUE, GREEN])
-    assert m.stroke_color[0] == BLACK
-    assert m.stroke_color[1] == BLUE
-    assert m.stroke_color[2] == GREEN
+    assert m.get_stroke_colors()[0] == Color(BLACK)
+    assert m.get_stroke_colors()[1] == Color(BLUE)
+    assert m.get_stroke_colors()[2] == Color(GREEN)
 
 
 def test_set_stroke_handles_lists_of_color_objects(using_opengl_renderer):
     m = OpenGLVMobject()
     assert m.stroke_color.hex == "#fff"
     m.set_stroke([Color(PURE_BLUE), Color(PURE_GREEN), Color(PURE_RED)])
-    assert m.stroke_color[0].hex == "#00f"
-    assert m.stroke_color[1].hex == "#0f0"
-    assert m.stroke_color[2].hex == "#f00"
+    assert m.get_stroke_colors()[0].hex == "#00f"
+    assert m.get_stroke_colors()[1].hex == "#0f0"
+    assert m.get_stroke_colors()[2].hex == "#f00"
diff --git a/tests/test_stroke.py b/tests/test_stroke.py
index ad6b0edac8..5a25f34661 100644
--- a/tests/test_stroke.py
+++ b/tests/test_stroke.py
@@ -1,12 +1,14 @@
 from __future__ import annotations
 
+from colour import Color
+
 import manim.utils.color as C
 from manim import VMobject
 
 
 def test_stroke_props_in_ctor():
     m = VMobject(stroke_color=C.ORANGE, stroke_width=10)
-    assert m.stroke_color == C.ORANGE
+    assert m.stroke_color == Color(C.ORANGE)
     assert m.stroke_width == 10
 
 
@@ -15,7 +17,7 @@ def test_set_stroke():
     m.set_stroke(color=C.ORANGE, width=2, opacity=0.8)
     assert m.stroke_width == 2
     assert m.stroke_opacity == 0.8
-    assert m.stroke_color == C.ORANGE
+    assert m.stroke_color == Color(C.ORANGE)
 
 
 def test_set_background_stroke():
diff --git a/tests/test_svg_mobject.py b/tests/test_svg_mobject.py
index d6fb81adbc..41c6e15dc6 100644
--- a/tests/test_svg_mobject.py
+++ b/tests/test_svg_mobject.py
@@ -10,13 +10,13 @@
 def test_set_fill_color():
     expected_color = "#FF862F"
     svg = SVGMobject(get_svg_resource("heart.svg"), fill_color=expected_color)
-    assert svg.fill_color == expected_color
+    assert svg.fill_color == Color(expected_color)
 
 
 def test_set_stroke_color():
     expected_color = "#FFFDDD"
     svg = SVGMobject(get_svg_resource("heart.svg"), stroke_color=expected_color)
-    assert svg.stroke_color == expected_color
+    assert svg.stroke_color == Color(expected_color)
 
 
 def test_set_color_sets_fill_and_stroke():
@@ -46,7 +46,7 @@ def test_fill_overrides_color():
         color="#123123",
         fill_color=expected_color,
     )
-    assert svg.fill_color == expected_color
+    assert svg.fill_color == Color(expected_color)
 
 
 def test_stroke_overrides_color():
@@ -56,7 +56,7 @@ def test_stroke_overrides_color():
         color="#334433",
         stroke_color=expected_color,
     )
-    assert svg.stroke_color == expected_color
+    assert svg.stroke_color == Color(expected_color)
 
 
 def test_string_to_numbers():


Code review comments:
diff_hunk: @@ -9,11 +9,12 @@
 import re
 import string
 import warnings
-from typing import Dict, List
+from typing import Dict, List, Optional
path: manim/mobject/svg/svg_mobject.py
text: @user1:
These imports are not used.

diff_hunk: @@ -1225,7 +1225,7 @@ def _text2hash(self):
         hasher.update(id_str.encode())
         return hasher.hexdigest()[:16]
 
-    def _text2svg(self):
+    def _text2svg(self, color: Optional[Color]):
path: manim/mobject/svg/text_mobject.py
text: @user1:
```suggestion
    def _text2svg(self, color: Color | None):
```