This commit is contained in:
Tristan Schulz 2026-06-21 11:57:15 +00:00 committed by GitHub
commit 188c9508db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 107 additions and 83 deletions

View file

@ -807,10 +807,11 @@ class Dot(Circle):
point: Point3DLike = ORIGIN,
radius: float = DEFAULT_DOT_RADIUS,
stroke_width: float = 0,
fill_opacity: float = 1.0,
fill_opacity: float | None = None,
color: ParsableManimColor = WHITE,
**kwargs: Any,
) -> None:
self.submobjects = []
super().__init__(
arc_center=point,
radius=radius,

View file

@ -127,7 +127,7 @@ class BackgroundRectangle(SurroundingRectangle):
buff=buff,
**kwargs,
)
self.original_fill_opacity: float = self.fill_opacity
self.original_fill_opacity: float = self.get_fill_opacities()
def pointwise_become_partial(self, mobject: Mobject, a: Any, b: float) -> Self:
self.set_fill(opacity=b * self.original_fill_opacity)

View file

@ -227,6 +227,7 @@ class BarChart(Axes):
bar_names
A sequence of names for each bar. Does not have to match the length of ``values``.
y_range
Takes the form (min, max, stepsize)
The y_axis range of values. If ``None``, the range will be calculated based on the
min/max of ``values`` and the step will be calculated based on ``y_length``.
x_length

View file

@ -106,7 +106,7 @@ class Mobject:
def __init__(
self,
color: ParsableManimColor | list[ParsableManimColor] = WHITE,
color: ParsableManimColor | list[ParsableManimColor] | None = WHITE,
name: str | None = None,
dim: int = 3,
target: Mobject | None = None,
@ -451,7 +451,9 @@ class Mobject:
return result
def __repr__(self) -> str:
return str(self.name)
if hasattr(self, "name"):
return str(self.name)
return str(self.__class__.__name__)
def reset_points(self) -> Self:
"""Sets :attr:`points` to be an empty array."""

View file

@ -111,7 +111,7 @@ class DecimalNumber(VMobject, metaclass=ConvertToOpenGL):
self.include_background_rectangle = include_background_rectangle
self.edge_to_fix = edge_to_fix
self._font_size = font_size
self.fill_opacity = fill_opacity
self.set_fill_opacity(fill_opacity)
self.initial_config = kwargs.copy()
self.initial_config.update(
@ -132,7 +132,6 @@ class DecimalNumber(VMobject, metaclass=ConvertToOpenGL):
)
self._set_submobjects_from_number(number)
self.init_colors()
@property
def font_size(self) -> float:

View file

@ -14,7 +14,7 @@ __all__ = [
import itertools as it
import sys
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any, Literal, cast
import numpy as np
from PIL.Image import Image
@ -39,6 +39,7 @@ from manim.utils.bezier import (
)
from manim.utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
from manim.utils.iterables import (
listify,
make_even,
resize_array,
stretch_array_to_length,
@ -108,13 +109,14 @@ class VMobject(Mobject):
def __init__(
self,
color: ParsableManimColor | list[ParsableManimColor] | None = None,
fill_color: ParsableManimColor | None = None,
fill_opacity: float = 0.0,
fill_opacity: float | None = None,
stroke_color: ParsableManimColor | None = None,
stroke_opacity: float = 1.0,
stroke_opacity: float | None = None,
stroke_width: float = DEFAULT_STROKE_WIDTH,
background_stroke_color: ParsableManimColor | None = BLACK,
background_stroke_opacity: float = 1.0,
background_stroke_opacity: float | None = None,
background_stroke_width: float = 0,
sheen_factor: float = 0.0,
joint_type: LineJointType | None = None,
@ -130,14 +132,7 @@ class VMobject(Mobject):
cap_style: CapStyleType = CapStyleType.AUTO,
**kwargs: Any,
):
self.fill_opacity = fill_opacity
self.stroke_opacity = stroke_opacity
self.stroke_width = stroke_width
if background_stroke_color is not None:
self.background_stroke_color: ManimColor = ManimColor(
background_stroke_color
)
self.background_stroke_opacity: float = background_stroke_opacity
self.background_stroke_width: float = background_stroke_width
self.sheen_factor: float = sheen_factor
self.joint_type: LineJointType = (
@ -159,17 +154,35 @@ class VMobject(Mobject):
0, 1, n_points_per_cubic_curve
)
self.cap_style: CapStyleType = cap_style
super().__init__(**kwargs)
self.submobjects: list[VMobject]
# TODO: Find where color overwrites are happening and remove the color doubling
# if "color" in kwargs:
# fill_color = kwargs["color"]
# stroke_color = kwargs["color"]
# TODO: Refactor color initialization
# This must be after init
self.submobjects: list[VMobject]
# if fill_color is not None or stroke_color is not None:
# color = None
if background_stroke_color is not None:
self.background_stroke_color: ManimColor | list[ManimColor] = (
ManimColor.parse(background_stroke_color)
)
if background_stroke_opacity is not None:
self.background_stroke_color = self.background_stroke_color.opacity(
background_stroke_opacity
)
print("Before", color, fill_color, stroke_color)
super().__init__(color=color, **kwargs)
print("After", self.color, self.fill_color, self.stroke_color)
if fill_color is not None:
self.fill_color = ManimColor.parse(fill_color)
if fill_opacity is not None:
self.fill_color = self.fill_color.opacity(fill_opacity)
if stroke_color is not None:
self.stroke_color = ManimColor.parse(stroke_color)
if stroke_opacity is not None:
self.stroke_color = self.stroke_color.opacity(stroke_opacity)
def _assert_valid_submobjects(self, submobjects: Iterable[VMobject]) -> Self:
return self._assert_valid_submobjects_internal(submobjects, VMobject)
@ -191,21 +204,9 @@ class VMobject(Mobject):
# Colors
def init_colors(self, propagate_colors: bool = True) -> Self:
self.set_fill(
color=self.fill_color,
opacity=self.fill_opacity,
family=propagate_colors,
)
self.set_stroke(
color=self.stroke_color,
width=self.stroke_width,
opacity=self.stroke_opacity,
family=propagate_colors,
)
self.set_background_stroke(
color=self.background_stroke_color,
width=self.background_stroke_width,
opacity=self.background_stroke_opacity,
family=propagate_colors,
)
self.set_sheen(
@ -221,9 +222,7 @@ class VMobject(Mobject):
return self
def generate_rgbas_array(
self,
color: ParsableManimColor | Iterable[ManimColor] | None,
opacity: float | Iterable[float],
self, color: ParsableManimColor | Iterable[ManimColor] | None
) -> FloatRGBA:
"""
First arg can be either a color, or a tuple/list of colors.
@ -233,17 +232,10 @@ class VMobject(Mobject):
will automatically be added for the gradient
"""
colors: list[ManimColor] = [
ManimColor(c) if (c is not None) else BLACK for c in tuplify(color)
ManimColor(c) if (c is not None) else BLACK
for c in cast(tuple[ParsableManimColor, ...], tuplify(color))
]
opacities: list[float] = [
o if (o is not None) else 0.0 for o in tuplify(opacity)
]
rgbas: FloatRGBA_Array = np.array(
[
c.to_rgba_with_alpha(o)
for c, o in zip(*make_even(colors, opacities), strict=True)
],
)
rgbas: FloatRGBA_Array = np.array([c.to_rgba() for c in colors])
sheen_factor = self.get_sheen_factor()
if sheen_factor != 0 and len(rgbas) == 1:
@ -257,9 +249,10 @@ class VMobject(Mobject):
self,
array_name: str,
color: ParsableManimColor | Iterable[ManimColor] | None = None,
opacity: float | None = None,
) -> Self:
rgbas = self.generate_rgbas_array(color, opacity)
if color is None:
return self
rgbas = self.generate_rgbas_array(color)
if not hasattr(self, array_name):
setattr(self, array_name, rgbas)
return self
@ -273,15 +266,14 @@ class VMobject(Mobject):
rgbas = stretch_array_to_length(rgbas, len(curr_rgbas))
# Only update rgb if color was not None, and only
# update alpha channel if opacity was passed in
if color is not None:
curr_rgbas[:, :3] = rgbas[:, :3]
if opacity is not None:
curr_rgbas[:, 3] = rgbas[:, 3]
curr_rgbas[:, :4] = rgbas[:, :4]
print(array_name, curr_rgbas)
setattr(self, array_name, curr_rgbas)
return self
def set_fill(
self,
color: ParsableManimColor | None = None,
color: ParsableManimColor | list[ParsableManimColor] | None = None,
opacity: float | None = None,
family: bool = True,
) -> Self:
@ -320,23 +312,33 @@ class VMobject(Mobject):
--------
:meth:`~.VMobject.set_style`
"""
new_color: list[ManimColor] = listify(self.get_fill_color())
if color is not None:
new_color: list[ManimColor] = listify(ManimColor.parse(color))
if opacity is not None:
new_color = [c.opacity(opacity) for c in new_color]
if family:
for submobject in self.submobjects:
submobject.set_fill(color, opacity, family)
self.update_rgbas_array("fill_rgbas", color, opacity)
self.update_rgbas_array("fill_rgbas", new_color)
self.fill_rgbas: FloatRGBA_Array
if opacity is not None:
self.fill_opacity = opacity
return self
def set_stroke(
self,
color: ParsableManimColor = None,
color: ParsableManimColor | list[ParsableManimColor] | None = None,
width: float | None = None,
opacity: float | None = None,
background=False,
family: bool = True,
) -> Self:
new_color: list[ManimColor] = listify(self.get_fill_color())
if color is not None:
new_color: list[ManimColor] = listify(ManimColor.parse(color))
if opacity is not None:
new_color = [c.opacity(opacity) for c in new_color]
if family:
for submobject in self.submobjects:
submobject.set_stroke(color, width, opacity, background, family)
@ -348,16 +350,14 @@ class VMobject(Mobject):
array_name = "stroke_rgbas"
width_name = "stroke_width"
opacity_name = "stroke_opacity"
self.update_rgbas_array(array_name, color, opacity)
self.update_rgbas_array(array_name, new_color)
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 and background:
if isinstance(color, (list, tuple)):
self.background_stroke_color = ManimColor.parse(color)
else:
self.background_stroke_color = ManimColor(color)
self.background_stroke_color = ManimColor.parse(color)
return self
def set_cap_style(self, cap_style: CapStyleType) -> Self:
@ -587,6 +587,10 @@ class VMobject(Mobject):
"""
return self.get_fill_opacities()[0]
def set_fill_opacity(self, opacity: float):
self.fill_color = [x.opacity(opacity) for x in tuplify(self.fill_color)]
return self
# TODO: Does this just do a copy?
# TODO: I have the feeling that this function should not return None, does that have any usage ?
def get_fill_colors(self) -> list[ManimColor | None]:

View file

@ -166,7 +166,9 @@ class ManimColor:
alpha: float = 1.0,
) -> None:
if value is None:
self._internal_value = np.array((0, 0, 0, alpha), dtype=ManimColorDType)
self._internal_value = np.array(
(1.0, 1.0, 1.0, alpha), dtype=ManimColorDType
)
elif isinstance(value, ManimColor):
# logger.info(
# "ManimColor was passed another ManimColor. This is probably not what "
@ -629,6 +631,39 @@ class ManimColor:
new[-1] = alpha
return self._construct_from_space(new)
@overload
def opacity(self, opacity: float) -> ManimColor:
"""Returns a new ManimColor with the same color and the given opacity
Parameters
----------
opacity : float
The opacity for the new ManimColor
Returns
-------
ManimColor
The new ManimColor object with changed opacity
"""
@overload
def opacity(self, opacity: None) -> float:
"""Returns the opacity of the current ManimColor in a range from zero to one
Returns
-------
float
The opacity of the ManimColor
"""
def opacity(self, opacity=None):
"""Returns a new ManimColor with the same color and a new opacity or changes the opacity"""
if opacity is None:
return self._internal_value[3]
tmp = self._internal_value.copy()
tmp[3] = opacity
return ManimColor.parse(tmp)
def interpolate(self, other: Self, alpha: float) -> Self:
"""Interpolate between the current and the given :class:`ManimColor`, and return
the result.
@ -744,24 +779,6 @@ class ManimColor:
return dark
return self._from_internal(BLACK._internal_value)
def opacity(self, opacity: float) -> Self:
"""Create a new :class:`ManimColor` with the given opacity and the same color
values as before.
Parameters
----------
opacity
The new opacity value to be used.
Returns
-------
ManimColor
The new :class:`ManimColor` with the same color values and the new opacity.
"""
tmp = self._internal_space.copy()
tmp[-1] = opacity
return self._construct_from_space(tmp)
def into(self, class_type: type[ManimColorT]) -> ManimColorT:
"""Convert the current color into a different colorspace given by ``class_type``,
without changing the :attr:`_internal_value`.
@ -965,7 +982,7 @@ class ManimColor:
raise NotImplementedError
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.to_hex()}')"
return f"{self.__class__.__name__}('{self.to_hex(True)}')"
def __str__(self) -> str:
return f"{self.to_hex()}"