def __init__( self, reactants: List[ChemObject] = [], products: List[ChemObject] = [], **arrow_kwargs, ): raise DeprecationWarning( "ReactionVGroup doesn't work, and will probably be removed in the future" ) digest_config(self, arrow_kwargs) arrow = ChemArrow( self._type, self.length, self.angle, self.style, self.text_up, self.text_down, ) r = self.get_side_of_equation(reactants) p = self.get_side_of_equation(products) self.set_same_height_for_all_mobjects(r) self.set_same_height_for_all_mobjects(p) self.set_same_height_for_all_mobjects([VGroup(*r), arrow, VGroup(*p)]) VGroup.__init__(*[*r, arrow, *p])
def get_subdivision_braces_and_labels( self, parts, labels, direction, buff=SMALL_BUFF, min_num_quads=1, ): label_mobs = VGroup() braces = VGroup() for label, part in zip(labels, parts): brace = Brace(part, direction, min_num_quads=min_num_quads, buff=buff) if isinstance(label, (Mobject, OpenGLMobject)): label_mob = label else: label_mob = MathTex(label) label_mob.scale(self.default_label_scale_val) label_mob.next_to(brace, direction, buff) braces.add(brace) label_mobs.add(label_mob) parts.braces = braces parts.labels = label_mobs parts.label_kwargs = { "labels": label_mobs.copy(), "direction": direction, "buff": buff, } return VGroup(parts.braces, parts.labels)
def __init__(self, tabledict, **kwargs): #__init__ is called everytime Table() is called. for item in kwargs: #Add everything that has been given in kwargs to the config. self.CONFIG[item] = kwargs[ item] #Using digest config gave AttributeError: 'dict' object has no attribute '__dict__' if not self.CONFIG["tabledict"]: self.tabledict: dict = tabledict self.buff_length: Union[float, int] = self.CONFIG["buff_length"] self.vbuff_length = self.buff_length if self.CONFIG[ "vbuff_length"] is None else self.CONFIG["vbuff_length"] self.hbuff_length = self.buff_length if self.CONFIG[ "hbuff_length"] is None else self.CONFIG["hbuff_length"] self.line_color: Union[str, hex] = self.CONFIG["line_color"] self.raw_string_color: Union[str, hex] = self.CONFIG["raw_string_color"] self.unchanged: bool = True # self.cell_height # self.cell_length VGroup.__init__(self) #Initialise Table as VGroup self.make_table() #Make the table with the parameters in CONFIG self.move_to(ORIGIN)
def _add_x_axis_labels(self): """Essentially ``:meth:~.NumberLine.add_labels``, but differs in that the direction of the label with respect to the x_axis changes to UP or DOWN depending on the value. UP for negative values and DOWN for positive values. """ val_range = np.arange( 0.5, len(self.bar_names), 1 ) # 0.5 shifted so that labels are centered, not on ticks labels = VGroup() for i, (value, bar_name) in enumerate(zip(val_range, self.bar_names)): # to accommodate negative bars, the label may need to be # below or above the x_axis depending on the value of the bar if self.values[i] < 0: direction = UP else: direction = DOWN bar_name_label = self.x_axis.label_constructor(bar_name) bar_name_label.font_size = self.x_axis.font_size bar_name_label.next_to( self.x_axis.number_to_point(value), direction=direction, buff=self.x_axis.line_to_number_buff, ) labels.add(bar_name_label) self.x_axis.labels = labels self.x_axis.add(labels)
def __init__(self, shell_config=None, cloud_radius=None): VGroup.__init__(self) if not shell_config: self.shell_config = self.CONFIG["shell_config"] if not cloud_radius: self.cloud_radius = self.CONFIG["cloud_radius"] self.make_atom() self.move_to(ORIGIN)
def create_faces( self, face_coords: list[list[list | np.ndarray]], ) -> VGroup: """Creates VGroup of faces from a list of face coordinates.""" face_group = VGroup() for face in face_coords: face_group.add(Polygon(*face, **self.faces_config)) return face_group
def add_ticks(self): """Adds ticks to the number line. Ticks can be accessed after creation via ``self.ticks``.""" ticks = VGroup() elongated_tick_size = self.tick_size * self.longer_tick_multiple for x in self.get_tick_range(): size = self.tick_size if x in self.numbers_with_elongated_ticks: size = elongated_tick_size ticks.add(self.get_tick(x, size)) self.add(ticks) self.ticks = ticks
def add_numbers( self, x_values: Iterable[float] | None = None, excluding: Iterable[float] | None = None, font_size: float | None = None, label_constructor: VMobject | None = None, **kwargs, ): """Adds :class:`~.DecimalNumber` mobjects representing their position at each tick of the number line. The numbers can be accessed after creation via ``self.numbers``. Parameters ---------- x_values An iterable of the values used to position and create the labels. Defaults to the output produced by :meth:`~.NumberLine.get_tick_range` excluding A list of values to exclude from :attr:`x_values`. font_size The font size of the labels. Defaults to the ``font_size`` attribute of the number line. label_constructor The :class:`~.VMobject` class that will be used to construct the label. Defaults to the ``label_constructor`` attribute of the number line if not specified. """ if x_values is None: x_values = self.get_tick_range() if excluding is None: excluding = self.numbers_to_exclude if font_size is None: font_size = self.font_size if label_constructor is None: label_constructor = self.label_constructor numbers = VGroup() for x in x_values: if x in excluding: continue numbers.add( self.get_number_mobject( x, font_size=font_size, label_constructor=label_constructor, **kwargs, )) self.add(numbers) self.numbers = numbers return self
def get_bar_labels( self, color: Color | None = None, font_size: float = 24, buff: float = MED_SMALL_BUFF, label_constructor: VMobject = Tex, ): """Annotates each bar with its corresponding value. Use ``self.bar_labels`` to access the labels after creation. Parameters ---------- color The color of each label. By default ``None`` and is based on the parent's bar color. font_size The font size of each label. buff The distance from each label to its bar. By default 0.4. label_constructor The Mobject class to construct the labels, by default :class:`~.Tex`. Examples -------- .. manim:: GetBarLabelsExample :save_last_frame: class GetBarLabelsExample(Scene): def construct(self): chart = BarChart(values=[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], y_range=[0, 10, 1]) c_bar_lbls = chart.get_bar_labels( color=WHITE, label_constructor=MathTex, font_size=36 ) self.add(chart, c_bar_lbls) """ bar_labels = VGroup() for bar, value in zip(self.bars, self.values): bar_lbl = label_constructor(str(value)) if color is None: bar_lbl.set_color(bar.get_fill_color()) else: bar_lbl.set_color(color) bar_lbl.font_size = font_size pos = UP if (value >= 0) else DOWN bar_lbl.next_to(bar, pos, buff=buff) bar_labels.add(bar_lbl) return bar_labels
def _setup_in_uv_space(self): u_values, v_values = self._get_u_values_and_v_values() faces = VGroup() for i in range(len(u_values) - 1): for j in range(len(v_values) - 1): u1, u2 = u_values[i : i + 2] v1, v2 = v_values[j : j + 2] face = ThreeDVMobject() face.set_points_as_corners( [ [u1, v1, 0], [u2, v1, 0], [u2, v2, 0], [u1, v2, 0], [u1, v1, 0], ], ) faces.add(face) face.u_index = i face.v_index = j face.u1 = u1 face.u2 = u2 face.v1 = v1 face.v2 = v2 faces.set_fill(color=self.fill_color, opacity=self.fill_opacity) faces.set_stroke( color=self.stroke_color, width=self.stroke_width, opacity=self.stroke_opacity, ) self.add(*faces) if self.checkerboard_colors: self.set_fill_by_checkerboard(*self.checkerboard_colors)
def get_division_along_dimension(self, p_list, dim, colors, vect): p_list = self.complete_p_list(p_list) colors = color_gradient(colors, len(p_list)) last_point = self.get_edge_center(-vect) parts = VGroup() for factor, color in zip(p_list, colors): part = SampleSpace() part.set_fill(color, 1) part.replace(self, stretch=True) part.stretch(factor, dim) part.move_to(last_point, -vect) last_point = part.get_edge_center(vect) parts.add(part) return parts
def create_creatures(self): self.teacher = Alex(color=self.teacher_color).scale(SCALE_FACTOR) self.teacher.to_corner(DR) self.students = VGroup(*[ Alex(color=c).scale(SCALE_FACTOR) for c in self.student_colors ]) self.students.arrange(RIGHT, buff=0.5) # self.students.arrange(RIGHT) self.students.scale(self.student_scale_factor) self.students.to_corner(DL) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) return [self.teacher] + list(self.students)
def get_student_changes(self, *modes, **kwargs): pairs = list(zip(self.get_students(), modes)) pairs = [(s, m) for s, m in pairs if m is not None] start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) if "look_at_arg" in kwargs: for pencil in target: pencil.look_at(kwargs["look_at_arg"]) anims = [ Transform(s, t) for s, t in zip(start, target) ] return LaggedStart( *anims, lag_ratio=kwargs.get("lag_ratio", 0.5), run_time=1, )
def make_atom(self): electron_dict = { } #This holds all the electrons in the format {"shell_n":electron} for shell in self.shell_config: electron_dict[shell] = VGroup() tempVGroup = VGroup() if self.shell_config[shell]["electron_count"] == 0: continue else: for electron_coordinate in self.get_electron_coordinates_list( self.shell_config[shell]["electron_count"] ): #for each electron coordinate in get_electron_coordinates where the radius of the inner circle is 1 and electron_count electrons are needed: electron = shading.get_surface( surface=Sphere(radius=0.08), fill_color=self.shell_config[shell]["colour"], fill_opacity=1) electron.move_to(electron_coordinate) electron_dict[shell].add(electron) tempVGroup = VGroup(*electron_dict[shell]) tempVGroup.space_out_submobjects( self.shell_config[shell]["radius"]) positivecloud = shading.get_surface(surface=Sphere(radius=2), fill_color=DARK_BLUE) # atom=VGroup(*electron_dict["shell_1"],*electron_dict["shell_2"],positivecloud) self.add(positivecloud) for shell in electron_dict: self.add(electron_dict[shell])
def __init__( self, color: Color = WHITE, height: float = 2.0, width: float = 4.0, grid_xstep: float | None = None, grid_ystep: float | None = None, mark_paths_closed=True, close_new_points=True, **kwargs, ): super().__init__(UR, UL, DL, DR, color=color, **kwargs) self.stretch_to_fit_width(width) self.stretch_to_fit_height(height) v = self.get_vertices() if grid_xstep is not None: from manim.mobject.geometry.line import Line grid_xstep = abs(grid_xstep) count = int(width / grid_xstep) grid = VGroup( *( Line( v[1] + i * grid_xstep * RIGHT, v[1] + i * grid_xstep * RIGHT + height * DOWN, color=color, ) for i in range(1, count) ) ) self.add(grid) if grid_ystep is not None: grid_ystep = abs(grid_ystep) count = int(height / grid_ystep) grid = VGroup( *( Line( v[1] + i * grid_ystep * DOWN, v[1] + i * grid_ystep * DOWN + width * RIGHT, color=color, ) for i in range(1, count) ) ) self.add(grid)
def get_parts_by_tex(self, tex, substring=True, case_sensitive=True): def test(tex1, tex2): if not case_sensitive: tex1 = tex1.lower() tex2 = tex2.lower() if substring: return tex1 in tex2 else: return tex1 == tex2 return VGroup(*(m for m in self.submobjects if test(tex, m.get_tex_string())))
def _add_bars(self): self.bars = VGroup() for i, value in enumerate(self.values): bar_h = abs(self.c2p(0, value)[1] - self.c2p(0, 0)[1]) bar_w = self.c2p(self.bar_width, 0)[0] - self.c2p(0, 0)[0] bar = Rectangle( height=bar_h, width=bar_w, stroke_width=self.bar_stroke_width, fill_opacity=self.bar_fill_opacity, ) pos = UP if (value >= 0) else DOWN bar.next_to(self.c2p(i + 0.5, 0), pos, buff=0) self.bars.add(bar) if isinstance(self.bar_colors, str): self.bars.set_color_by_gradient(self.bar_colors) else: self.bars.set_color_by_gradient(*self.bar_colors) self.add_to_back(self.bars)
def get_lines(self) -> VGroup: """Get the lines forming an angle of the :class:`Angle` class. Returns ------- :class:`~.VGroup` A :class:`~.VGroup` containing the lines that form the angle of the :class:`Angle` class. Examples -------- :: >>> line_1, line_2 = Line(ORIGIN, RIGHT), Line(ORIGIN, UR) >>> angle = Angle(line_1, line_2) >>> angle.get_lines() VGroup(Line, Line) """ return VGroup(*self.lines)
def creation_anim( self, text_anim: Animation = Write, arrow_anim: Animation = FadeInFrom, reactant_product_simultaneity=False, **kwargs, ) -> AnimationGroup: """Workaround and shortcut method to overcome the bugs in `Write`. Args: text_anim (Animation, optional): The animation on the reactants and products. Defaults to Write. arrow_anim (Animation, optional): The animation on the arrow. Defaults to FadeInFrom. reactant_product_simultaneity (bool, optional): Whether to animate the reactants and products together or not. Returns: AnimationGroup: The group of animations on the text and arrow. """ text = VGroup(self[0:2 * len(self.reactants) - 1], self[2 * len(self.reactants):]) arrow = self[2 * len(self.reactants) - 1] if "text_kwargs" not in kwargs.keys(): kwargs["text_kwargs"] = dict() if "arrow_kwargs" not in kwargs.keys(): kwargs["arrow_kwargs"] = dict() if "group_kwargs" not in kwargs.keys(): kwargs["group_kwargs"] = dict() print(kwargs["group_kwargs"]) anim_group = (AnimationGroup(text_anim(text[0]), text_anim(text[1]), arrow_anim(arrow), **kwargs) if reactant_product_simultaneity else AnimationGroup( text_anim(text), arrow_anim(arrow), **kwargs)) try: print(anim_group.run_time) except Exception: pass return anim_group
def setup( self, creatures_start_on_screen = True, default_creature_start_corner = DR, default_creature_kwargs= { "color": GREY_BROWN, "flip_at_start": True, }, total_wait_time = 0, seconds_to_blink = 3, ): self.creatures_start_on_screen = creatures_start_on_screen self.default_creature_start_corner = default_creature_start_corner self.default_creature_kwargs = default_creature_kwargs self.total_wait_time = total_wait_time self.seconds_to_blink = seconds_to_blink self.creatures = VGroup(*self.create_creatures()) self.creature = self.get_primary_creature() if self.creatures_start_on_screen: self.add(*self.creatures)
class BarChart(Axes): """Creates a bar chart. Inherits from :class:`~.Axes`, so it shares its methods and attributes. Each axis inherits from :class:`~.NumberLine`, so pass in ``x_axis_config``/``y_axis_config`` to control their attributes. Parameters ---------- values An iterable of values that determines the height of each bar. Accepts negative values. bar_names An iterable of names for each bar. Does not have to match the length of ``values``. y_range 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 The length of the x-axis. If ``None``, it is automatically calculated based on the number of values and the width of the screen. y_length The length of the y-axis. bar_colors The color for the bars. Accepts a single color or an iterable of colors. If the length of``bar_colors`` does not match that of ``values``, intermediate colors will be automatically determined. bar_width The length of a bar. Must be between 0 and 1. bar_fill_opacity The fill opacity of the bars. bar_stroke_width The stroke width of the bars. Examples -------- .. manim:: BarChartExample :save_last_frame: class BarChartExample(Scene): def construct(self): chart = BarChart( values=[-5, 40, -10, 20, -3], bar_names=["one", "two", "three", "four", "five"], y_range=[-20, 50, 10], y_length=6, x_length=10, x_axis_config={"font_size": 36}, ) c_bar_lbls = chart.get_bar_labels(font_size=48) self.add(chart, c_bar_lbls) """ def __init__( self, values: Iterable[float], bar_names: Iterable[str] | None = None, y_range: Sequence[float] | None = None, x_length: float | None = None, y_length: float | None = config.frame_height - 4, bar_colors: str | Iterable[str] | None = [ "#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600", ], bar_width: float = 0.6, bar_fill_opacity: float = 0.7, bar_stroke_width: float = 3, **kwargs, ): self.values = values self.bar_names = bar_names self.bar_colors = bar_colors self.bar_width = bar_width self.bar_fill_opacity = bar_fill_opacity self.bar_stroke_width = bar_stroke_width x_range = [0, len(self.values), 1] if y_range is None: y_range = [ min(0, min(self.values)), max(0, max(self.values)), round(max(self.values) / y_length, 2), ] elif len(y_range) == 2: y_range = [*y_range, round(max(self.values) / y_length, 2)] if x_length is None: x_length = min(len(self.values), config.frame_width - 2) x_axis_config = {"font_size": 24, "label_constructor": Tex} self._update_default_configs( (x_axis_config,), (kwargs.pop("x_axis_config", None),) ) self.bars = None self.x_labels = None self.bar_labels = None super().__init__( x_range=x_range, y_range=y_range, x_length=x_length, y_length=y_length, x_axis_config=x_axis_config, tips=kwargs.pop("tips", False), **kwargs, ) self._add_bars() if self.bar_names is not None: self._add_x_axis_labels() self.y_axis.add_numbers() def _add_x_axis_labels(self): """Essentially ``:meth:~.NumberLine.add_labels``, but differs in that the direction of the label with respect to the x_axis changes to UP or DOWN depending on the value. UP for negative values and DOWN for positive values. """ val_range = np.arange( 0.5, len(self.bar_names), 1 ) # 0.5 shifted so that labels are centered, not on ticks labels = VGroup() for i, (value, bar_name) in enumerate(zip(val_range, self.bar_names)): # to accommodate negative bars, the label may need to be # below or above the x_axis depending on the value of the bar if self.values[i] < 0: direction = UP else: direction = DOWN bar_name_label = self.x_axis.label_constructor(bar_name) bar_name_label.font_size = self.x_axis.font_size bar_name_label.next_to( self.x_axis.number_to_point(value), direction=direction, buff=self.x_axis.line_to_number_buff, ) labels.add(bar_name_label) self.x_axis.labels = labels self.x_axis.add(labels) def _add_bars(self): self.bars = VGroup() for i, value in enumerate(self.values): bar_h = abs(self.c2p(0, value)[1] - self.c2p(0, 0)[1]) bar_w = self.c2p(self.bar_width, 0)[0] - self.c2p(0, 0)[0] bar = Rectangle( height=bar_h, width=bar_w, stroke_width=self.bar_stroke_width, fill_opacity=self.bar_fill_opacity, ) pos = UP if (value >= 0) else DOWN bar.next_to(self.c2p(i + 0.5, 0), pos, buff=0) self.bars.add(bar) if isinstance(self.bar_colors, str): self.bars.set_color_by_gradient(self.bar_colors) else: self.bars.set_color_by_gradient(*self.bar_colors) self.add_to_back(self.bars) def get_bar_labels( self, color: Color | None = None, font_size: float = 24, buff: float = MED_SMALL_BUFF, label_constructor: VMobject = Tex, ): """Annotates each bar with its corresponding value. Use ``self.bar_labels`` to access the labels after creation. Parameters ---------- color The color of each label. By default ``None`` and is based on the parent's bar color. font_size The font size of each label. buff The distance from each label to its bar. By default 0.4. label_constructor The Mobject class to construct the labels, by default :class:`~.Tex`. Examples -------- .. manim:: GetBarLabelsExample :save_last_frame: class GetBarLabelsExample(Scene): def construct(self): chart = BarChart(values=[10, 9, 8, 7, 6, 5, 4, 3, 2, 1], y_range=[0, 10, 1]) c_bar_lbls = chart.get_bar_labels( color=WHITE, label_constructor=MathTex, font_size=36 ) self.add(chart, c_bar_lbls) """ bar_labels = VGroup() for bar, value in zip(self.bars, self.values): bar_lbl = label_constructor(str(value)) if color is None: bar_lbl.set_color(bar.get_fill_color()) else: bar_lbl.set_color(color) bar_lbl.font_size = font_size pos = UP if (value >= 0) else DOWN bar_lbl.next_to(bar, pos, buff=buff) bar_labels.add(bar_lbl) return bar_labels def change_bar_values(self, values: Iterable[float]): """Updates the height of the bars of the chart. Parameters ---------- values The values that will be used to update the height of the bars. Does not have to match the number of bars. Examples -------- .. manim:: ChangeBarValuesExample :save_last_frame: class ChangeBarValuesExample(Scene): def construct(self): values=[-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10] chart = BarChart( values, y_range=[-10, 10, 2], y_axis_config={"font_size": 24}, ) self.add(chart) chart.change_bar_values(list(reversed(values))) self.add(chart.get_bar_labels(font_size=24)) """ for i, (bar, value) in enumerate(zip(self.bars, values)): chart_val = self.values[i] if chart_val > 0: bar_lim = bar.get_bottom() aligned_edge = DOWN else: bar_lim = bar.get_top() aligned_edge = UP try: quotient = value / chart_val if quotient < 0: aligned_edge = UP if chart_val > 0 else DOWN # if the bar is already positive, then we now want to move it # so that it is negative. So, we move the top edge of the bar # to the location of the previous bottom # if already negative, then we move the bottom edge of the bar # to the location of the previous top bar.stretch_to_fit_height(quotient * bar.height) except ZeroDivisionError: bar.height = 0 bar.move_to(bar_lim, aligned_edge) self.values[: len(values)] = values
class PencilCreature(SVGMobject): def __init__( self, # File type="pencil", mode="normal", # Style color=BLUE_E, background_color="#e5d4c3", pupil_to_eye_width_ratio=0.4, pupil_dot_to_pupil_width_ratio=0.3, # Position start_corner=None, flip_at_start=False, # Size corner_scale_factor=0.75, **kwargs, ): # File self.mode = "ask" self.type = "pencil" self.file_name = str(SVG_DIR / f"{type}_{mode}.svg") # Style self.color = color self.background_color = background_color self.pupil_to_eye_width_ratio = pupil_to_eye_width_ratio self.pupil_dot_to_pupil_width_ratio = pupil_dot_to_pupil_width_ratio # Position self.start_corner = start_corner self.flip_at_start = flip_at_start # Size self.corner_scale_factor = corner_scale_factor SVGMobject.__init__(self, file_name=self.file_name, color=self.color, **kwargs) if self.flip_at_start: self.flip() if self.start_corner is not None: self.to_corner(self.start_corner) def name_parts(self): # body self.body = VGroup(*[ self.submobjects[BODY_INDEX], self.submobjects[SPITE_INDEX], ]) self.spite = self.submobjects[SPITE_INDEX] self.background_body = VGroup(*[ self.submobjects[LEFT_CIRCLE], self.submobjects[MIDDLE_CIRCLE], self.submobjects[RIGHT_CIRCLE], self.submobjects[BCKGRD_BODY_INDEX] ]) # arms self.arms = VGroup(*[ self.submobjects[LEFT_ARM_INDEX], self.submobjects[RIGHT_ARM_INDEX] ]) # mouth self.mouth = self.submobjects[MOUTH_INDEX] # eyes and around self.eyebrow = VGroup(*[ self.submobjects[LEFT_EYEBROW_INDEX], self.submobjects[RIGHT_EYEBROW_INDEX] ]) self.pupils = VGroup(*[ self.submobjects[LEFT_PUPIL_INDEX], self.submobjects[RIGHT_PUPIL_INDEX] ]) self.eyes = VGroup(*[ self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX] ]) self.eye_parts = VGroup(self.eyes, self.pupils) # garbage draws self.garbage = self.submobjects[GARBAGE] def init_colors(self): SVGMobject.init_colors(self) self.name_parts() self.garbage.set_color(RED) self.garbage.set_stroke(width=0) self.body.set_fill(self.color, opacity=1) self.body.set_stroke(width=0) self.background_body.set_fill(self.background_color, opacity=1) self.background_body.set_stroke(width=0) self.arms.set_fill(opacity=0) self.arms.set_stroke(color=WHITE, opacity=1, width=2) self.mouth.set_fill(BLACK, opacity=1) self.mouth.set_stroke(width=0) self.eyes.set_fill(WHITE, opacity=1) self.eyes.set_stroke(width=0) self.eyebrow.set_fill(opacity=0) self.eyebrow.set_stroke(color=WHITE, opacity=1, width=2) self.init_pupils() return self def init_pupils(self): # Instead of what is drawn, make new circles. # This is mostly because the paths associated # with the eyes in all the drawings got slightly # messed up. for eye, pupil in zip(self.eyes, self.pupils): pupil_r = eye.get_width() / 2 pupil_r *= self.pupil_to_eye_width_ratio dot_r = pupil_r dot_r *= self.pupil_dot_to_pupil_width_ratio new_pupil = Circle( radius=pupil_r, color=BLACK, fill_opacity=1, stroke_width=0, ) dot = Circle( radius=dot_r, color=WHITE, fill_opacity=1, stroke_width=0, ) new_pupil.move_to(pupil) pupil.become(new_pupil) dot.shift( new_pupil.get_boundary_point(UL) - dot.get_boundary_point(UL)) pupil.add(dot) def copy(self): copy_mobject = SVGMobject.copy(self) copy_mobject.name_parts() return copy_mobject def set_color(self, color): self.body.set_fill(color) self.color = color return self def change_mode(self, mode): new_self = self.__class__(mode=mode) new_self.match_style(self) new_self.match_height(self) if self.is_flipped() != new_self.is_flipped(): new_self.flip() new_self.shift(self.eyes.get_center() - new_self.eyes.get_center()) if hasattr(self, "purposeful_looking_direction"): new_self.look(self.purposeful_looking_direction) self.become(new_self) self.mode = mode return self def get_mode(self): return self.mode def look(self, direction): norm = get_norm(direction) if norm == 0: return direction /= norm self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): c = eye.get_center() right = eye.get_right() - c up = eye.get_top() - c vect = direction[0] * right + direction[1] * up v_norm = get_norm(vect) p_radius = 0.5 * pupil.get_width() vect *= (v_norm - 0.75 * p_radius) / v_norm pupil.move_to(c + vect) self.pupils[1].align_to(self.pupils[0], DOWN) return self def look_at(self, point_or_mobject): if isinstance(point_or_mobject, Mobject): point = point_or_mobject.get_center() else: point = point_or_mobject self.look(point - self.eyes.get_center()) return self def change(self, new_mode, look_at_arg=None): self.change_mode(new_mode) if look_at_arg is not None: self.look_at(look_at_arg) return self def get_looking_direction(self): vect = self.pupils.get_center() - self.eyes.get_center() return normalize(vect) def get_look_at_spot(self): return self.eyes.get_center() + self.get_looking_direction() def is_flipped(self): return self.eyes.submobjects[0].get_center()[0] > \ self.eyes.submobjects[1].get_center()[0] def blink(self): eye_parts = self.eye_parts eye_bottom_y = eye_parts.get_bottom()[1] eye_parts.apply_function(lambda p: [p[0], eye_bottom_y, p[2]]) return self def look_at_u(self): for eye, pupil in zip(self.eyes, self.pupils): pupil.move_to(eye.get_center()) def to_corner(self, vect=None, **kwargs): if vect is not None: SVGMobject.to_corner(self, vect, **kwargs) else: self.scale(self.corner_scale_factor) self.to_corner(DOWN + LEFT, **kwargs) return self def get_bubble(self, content, **kwargs): bubble = Bubble(**kwargs) if content: if isinstance(content, str): bubble.write(content) else: bubble.add_content(content) bubble.resize_to_content() bubble.pin_to(self) self.bubble = bubble return bubble def make_eye_contact(self, creature): self.look_at(creature.eyes) creature.look_at(self.eyes) return self def get_all_creature_modes(self): result = [] prefix = "{}_".format(self.type) suffix = ".svg" for file in os.listdir(SVG_DIR): if file.startswith(prefix) and file.endswith(suffix): result.append(file[len(prefix):-len(suffix)]) return result
def name_parts(self): # body self.body = VGroup(*[ self.submobjects[BODY_INDEX], self.submobjects[SPITE_INDEX], ]) self.spite = self.submobjects[SPITE_INDEX] self.background_body = VGroup(*[ self.submobjects[LEFT_CIRCLE], self.submobjects[MIDDLE_CIRCLE], self.submobjects[RIGHT_CIRCLE], self.submobjects[BCKGRD_BODY_INDEX] ]) # arms self.arms = VGroup(*[ self.submobjects[LEFT_ARM_INDEX], self.submobjects[RIGHT_ARM_INDEX] ]) # mouth self.mouth = self.submobjects[MOUTH_INDEX] # eyes and around self.eyebrow = VGroup(*[ self.submobjects[LEFT_EYEBROW_INDEX], self.submobjects[RIGHT_EYEBROW_INDEX] ]) self.pupils = VGroup(*[ self.submobjects[LEFT_PUPIL_INDEX], self.submobjects[RIGHT_PUPIL_INDEX] ]) self.eyes = VGroup(*[ self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX] ]) self.eye_parts = VGroup(self.eyes, self.pupils) # garbage draws self.garbage = self.submobjects[GARBAGE]
def create_creatures(self): """ Likely updated for subclasses """ return VGroup(self.create_creature())
def interpolate_mobject(self, alpha): self.mobject.become(self.starting_mobject) VGroup(*self.mobject.get_face(self.face[0])).rotate( alpha * self.angle, self.axis)
def __init__( self, file_name=None, code=None, tab_width=3, line_spacing=0.3, font_size=24, font="Monospac821 BT", stroke_width=0, margin=0.3, indentation_chars=" ", background="rectangle", # or window background_stroke_width=1, background_stroke_color=WHITE, corner_radius=0.2, insert_line_no=True, line_no_from=1, line_no_buff=0.4, style="vim", language=None, generate_html_file=False, **kwargs, ): super().__init__( stroke_width=stroke_width, **kwargs, ) self.background_stroke_color = background_stroke_color self.background_stroke_width = background_stroke_width self.tab_width = tab_width self.line_spacing = line_spacing self.font = font self.font_size = font_size self.margin = margin self.indentation_chars = indentation_chars self.background = background self.corner_radius = corner_radius self.insert_line_no = insert_line_no self.line_no_from = line_no_from self.line_no_buff = line_no_buff self.style = style self.language = language self.generate_html_file = generate_html_file self.file_path = None self.file_name = file_name if self.file_name: self._ensure_valid_file() with open(self.file_path, encoding="utf-8") as f: self.code_string = f.read() elif code: self.code_string = code else: raise ValueError( "Neither a code file nor a code string have been specified.", ) if isinstance(self.style, str): self.style = self.style.lower() self._gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12 : strati + 19] self._gen_code_json() self.code = self._gen_colored_lines() if self.insert_line_no: self.line_numbers = self._gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: foreground = VGroup(self.code, self.line_numbers) else: foreground = self.code rect = SurroundingRectangle( foreground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=self.background_stroke_width, stroke_color=self.background_stroke_color, fill_opacity=1, ) rect.round_corners(self.corner_radius) self.background_mobject = rect else: if self.insert_line_no: foreground = VGroup(self.code, self.line_numbers) else: foreground = self.code height = foreground.height + 0.1 * 3 + 2 * self.margin width = foreground.width + 0.1 * 3 + 2 * self.margin rect = RoundedRectangle( corner_radius=self.corner_radius, height=height, width=width, stroke_width=self.background_stroke_width, stroke_color=self.background_stroke_color, color=self.background_color, fill_opacity=1, ) red_button = Dot(radius=0.1, stroke_width=0, color="#ff5f56") red_button.shift(LEFT * 0.1 * 3) yellow_button = Dot(radius=0.1, stroke_width=0, color="#ffbd2e") green_button = Dot(radius=0.1, stroke_width=0, color="#27c93f") green_button.shift(RIGHT * 0.1 * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05), ) self.background_mobject = VGroup(rect, buttons) x = (height - foreground.height) / 2 - 0.1 * 3 self.background_mobject.shift(foreground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: super().__init__( self.background_mobject, self.line_numbers, self.code, **kwargs ) else: super().__init__( self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), self.code, **kwargs, ) self.move_to(np.array([0, 0, 0]))
def get_number_mobjects(self, *numbers, **kwargs) -> VGroup: if len(numbers) == 0: numbers = self.default_numbers_to_display() return VGroup( [self.get_number_mobject(number, **kwargs) for number in numbers])
class Code(VGroup): """A highlighted source code listing. An object ``listing`` of :class:`.Code` is a :class:`.VGroup` consisting of three objects: - The background, ``listing.background_mobject``. This is either a :class:`.Rectangle` (if the listing has been initialized with ``background="rectangle"``, the default option) or a :class:`.VGroup` resembling a window (if ``background="window"`` has been passed). - The line numbers, ``listing.line_numbers`` (a :class:`.Paragraph` object). - The highlighted code itself, ``listing.code`` (a :class:`.Paragraph` object). .. WARNING:: Using a :class:`.Transform` on text with leading whitespace (and in this particular case: code) can look `weird <https://github.com/3b1b/manim/issues/1067>`_. Consider using :meth:`remove_invisible_chars` to resolve this issue. Examples -------- Normal usage:: listing = Code( "helloworldcpp.cpp", tab_width=4, background_stroke_width=1, background_stroke_color=WHITE, insert_line_no=True, style=Code.styles_list[15], background="window", language="cpp", ) We can also render code passed as a string (but note that the language has to be specified in this case): .. manim:: CodeFromString :save_last_frame: class CodeFromString(Scene): def construct(self): code = '''from manim import Scene, Square class FadeInSquare(Scene): def construct(self): s = Square() self.play(FadeIn(s)) self.play(s.animate.scale(2)) self.wait() ''' rendered_code = Code(code=code, tab_width=4, background="window", language="Python", font="Monospace") self.add(rendered_code) Parameters ---------- file_name : :class:`str` Name of the code file to display. code : :class:`str` If ``file_name`` is not specified, a code string can be passed directly. tab_width : :class:`int`, optional Number of space characters corresponding to a tab character. Defaults to 3. line_spacing : :class:`float`, optional Amount of space between lines in relation to font size. Defaults to 0.3, which means 30% of font size. font_size : class:`float`, optional A number which scales displayed code. Defaults to 24. font : :class:`str`, optional The name of the text font to be used. Defaults to ``"Monospac821 BT"``. stroke_width : class:`float`, optional Stroke width for text. 0 is recommended, and the default. margin: class :`float`, optional Inner margin of text from the background. Defaults to 0.3. indentation_chars : :class:`str`, optional "Indentation chars" refers to the spaces/tabs at the beginning of a given code line. Defaults to ``" "`` (spaces). background : :class:`str`, optional Defines the background's type. Currently supports only ``"rectangle"`` (default) and ``"window"``. background_stroke_width : class:`float`, optional Defines the stroke width of the background. Defaults to 1. background_stroke_color : class:`str`, optional Defines the stroke color for the background. Defaults to ``WHITE``. corner_radius : :class:`float`, optional Defines the corner radius for the background. Defaults to 0.2. insert_line_no : :class:`bool`, optional Defines whether line numbers should be inserted in displayed code. Defaults to ``True``. line_no_from : :class:`int`, optional Defines the first line's number in the line count. Defaults to 1. line_no_buff : :class:`float`, optional Defines the spacing between line numbers and displayed code. Defaults to 0.4. style : :class:`str`, optional Defines the style type of displayed code. You can see possible names of styles in with :attr:`styles_list`. Defaults to ``"vim"``. language : Optional[:class:`str`], optional Specifies the programming language the given code was written in. If ``None`` (the default), the language will be automatically detected. For the list of possible options, visit https://pygments.org/docs/lexers/ and look for 'aliases or short names'. generate_html_file : :class:`bool`, optional Defines whether to generate highlighted html code to the folder `assets/codes/generated_html_files`. Defaults to `False`. Attributes ---------- background_mobject : :class:`~.VGroup` The background of the code listing. line_numbers : :class:`~.Paragraph` The line numbers for the code listing. Empty, if ``insert_line_no=False`` has been specified. code : :class:`~.Paragraph` The highlighted code. """ # tuples in the form (name, aliases, filetypes, mimetypes) # 'language' is aliases or short names # For more information about pygments.lexers visit https://pygments.org/docs/lexers/ # from pygments.lexers import get_all_lexers # all_lexers = get_all_lexers() styles_list = list(get_all_styles()) # For more information about pygments.styles visit https://pygments.org/docs/styles/ def __init__( self, file_name=None, code=None, tab_width=3, line_spacing=0.3, font_size=24, font="Monospac821 BT", stroke_width=0, margin=0.3, indentation_chars=" ", background="rectangle", # or window background_stroke_width=1, background_stroke_color=WHITE, corner_radius=0.2, insert_line_no=True, line_no_from=1, line_no_buff=0.4, style="vim", language=None, generate_html_file=False, **kwargs, ): super().__init__( stroke_width=stroke_width, **kwargs, ) self.background_stroke_color = background_stroke_color self.background_stroke_width = background_stroke_width self.tab_width = tab_width self.line_spacing = line_spacing self.font = font self.font_size = font_size self.margin = margin self.indentation_chars = indentation_chars self.background = background self.corner_radius = corner_radius self.insert_line_no = insert_line_no self.line_no_from = line_no_from self.line_no_buff = line_no_buff self.style = style self.language = language self.generate_html_file = generate_html_file self.file_path = None self.file_name = file_name if self.file_name: self._ensure_valid_file() with open(self.file_path, encoding="utf-8") as f: self.code_string = f.read() elif code: self.code_string = code else: raise ValueError( "Neither a code file nor a code string have been specified.", ) if isinstance(self.style, str): self.style = self.style.lower() self._gen_html_string() strati = self.html_string.find("background:") self.background_color = self.html_string[strati + 12 : strati + 19] self._gen_code_json() self.code = self._gen_colored_lines() if self.insert_line_no: self.line_numbers = self._gen_line_numbers() self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff) if self.background == "rectangle": if self.insert_line_no: foreground = VGroup(self.code, self.line_numbers) else: foreground = self.code rect = SurroundingRectangle( foreground, buff=self.margin, color=self.background_color, fill_color=self.background_color, stroke_width=self.background_stroke_width, stroke_color=self.background_stroke_color, fill_opacity=1, ) rect.round_corners(self.corner_radius) self.background_mobject = rect else: if self.insert_line_no: foreground = VGroup(self.code, self.line_numbers) else: foreground = self.code height = foreground.height + 0.1 * 3 + 2 * self.margin width = foreground.width + 0.1 * 3 + 2 * self.margin rect = RoundedRectangle( corner_radius=self.corner_radius, height=height, width=width, stroke_width=self.background_stroke_width, stroke_color=self.background_stroke_color, color=self.background_color, fill_opacity=1, ) red_button = Dot(radius=0.1, stroke_width=0, color="#ff5f56") red_button.shift(LEFT * 0.1 * 3) yellow_button = Dot(radius=0.1, stroke_width=0, color="#ffbd2e") green_button = Dot(radius=0.1, stroke_width=0, color="#27c93f") green_button.shift(RIGHT * 0.1 * 3) buttons = VGroup(red_button, yellow_button, green_button) buttons.shift( UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05), ) self.background_mobject = VGroup(rect, buttons) x = (height - foreground.height) / 2 - 0.1 * 3 self.background_mobject.shift(foreground.get_center()) self.background_mobject.shift(UP * x) if self.insert_line_no: super().__init__( self.background_mobject, self.line_numbers, self.code, **kwargs ) else: super().__init__( self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), self.code, **kwargs, ) self.move_to(np.array([0, 0, 0])) def _ensure_valid_file(self): """Function to validate file.""" if self.file_name is None: raise Exception("Must specify file for Code") possible_paths = [ os.path.join(os.path.join("assets", "codes"), self.file_name), self.file_name, ] for path in possible_paths: if os.path.exists(path): self.file_path = path return error = ( f"From: {os.getcwd()}, could not find {self.file_name} at either " + f"of these locations: {possible_paths}" ) raise OSError(error) def _gen_line_numbers(self): """Function to generate line_numbers. Returns ------- :class:`~.Paragraph` The generated line_numbers according to parameters. """ line_numbers_array = [] for line_no in range(0, self.code_json.__len__()): number = str(self.line_no_from + line_no) line_numbers_array.append(number) line_numbers = Paragraph( *list(line_numbers_array), line_spacing=self.line_spacing, alignment="right", font_size=self.font_size, font=self.font, disable_ligatures=True, stroke_width=self.stroke_width, ) for i in line_numbers: i.set_color(self.default_color) return line_numbers def _gen_colored_lines(self): """Function to generate code. Returns ------- :class:`~.Paragraph` The generated code according to parameters. """ lines_text = [] for line_no in range(0, self.code_json.__len__()): line_str = "" for word_index in range(self.code_json[line_no].__len__()): line_str = line_str + self.code_json[line_no][word_index][0] lines_text.append(self.tab_spaces[line_no] * "\t" + line_str) code = Paragraph( *list(lines_text), line_spacing=self.line_spacing, tab_width=self.tab_width, font_size=self.font_size, font=self.font, disable_ligatures=True, stroke_width=self.stroke_width, ) for line_no in range(code.__len__()): line = code.chars[line_no] line_char_index = self.tab_spaces[line_no] for word_index in range(self.code_json[line_no].__len__()): line[ line_char_index : line_char_index + self.code_json[line_no][word_index][0].__len__() ].set_color(self.code_json[line_no][word_index][1]) line_char_index += self.code_json[line_no][word_index][0].__len__() return code def _gen_html_string(self): """Function to generate html string with code highlighted and stores in variable html_string.""" self.html_string = _hilite_me( self.code_string, self.language, self.style, self.insert_line_no, "border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;", self.file_path, self.line_no_from, ) if self.generate_html_file: os.makedirs( os.path.join("assets", "codes", "generated_html_files"), exist_ok=True, ) with open( os.path.join( "assets", "codes", "generated_html_files", self.file_name + ".html", ), "w", ) as file: file.write(self.html_string) def _gen_code_json(self): """Function to background_color, generate code_json and tab_spaces from html_string. background_color is just background color of displayed code. code_json is 2d array with rows as line numbers and columns as a array with length 2 having text and text's color value. tab_spaces is 2d array with rows as line numbers and columns as corresponding number of indentation_chars in front of that line in code. """ if ( self.background_color == "#111111" or self.background_color == "#272822" or self.background_color == "#202020" or self.background_color == "#000000" ): self.default_color = "#ffffff" else: self.default_color = "#000000" # print(self.default_color,self.background_color) for i in range(3, -1, -1): self.html_string = self.html_string.replace("</" + " " * i, "</") # handle pygments bug # https://github.com/pygments/pygments/issues/961 self.html_string = self.html_string.replace("<span></span>", "") for i in range(10, -1, -1): self.html_string = self.html_string.replace( "</span>" + " " * i, " " * i + "</span>", ) self.html_string = self.html_string.replace("background-color:", "background:") if self.insert_line_no: start_point = self.html_string.find("</td><td><pre") start_point = start_point + 9 else: start_point = self.html_string.find("<pre") self.html_string = self.html_string[start_point:] # print(self.html_string) lines = self.html_string.split("\n") lines = lines[0 : lines.__len__() - 2] start_point = lines[0].find(">") lines[0] = lines[0][start_point + 1 :] # print(lines) self.code_json = [] self.tab_spaces = [] code_json_line_index = -1 for line_index in range(0, lines.__len__()): # print(lines[line_index]) self.code_json.append([]) code_json_line_index = code_json_line_index + 1 if lines[line_index].startswith(self.indentation_chars): start_point = lines[line_index].find("<") starting_string = lines[line_index][:start_point] indentation_chars_count = lines[line_index][:start_point].count( self.indentation_chars, ) if ( starting_string.__len__() != indentation_chars_count * self.indentation_chars.__len__() ): lines[line_index] = ( "\t" * indentation_chars_count + starting_string[ starting_string.rfind(self.indentation_chars) + self.indentation_chars.__len__() : ] + lines[line_index][start_point:] ) else: lines[line_index] = ( "\t" * indentation_chars_count + lines[line_index][start_point:] ) indentation_chars_count = 0 if lines[line_index]: while lines[line_index][indentation_chars_count] == "\t": indentation_chars_count = indentation_chars_count + 1 self.tab_spaces.append(indentation_chars_count) # print(lines[line_index]) lines[line_index] = self._correct_non_span(lines[line_index]) # print(lines[line_index]) words = lines[line_index].split("<span") for word_index in range(1, words.__len__()): color_index = words[word_index].find("color:") if color_index == -1: color = self.default_color else: starti = words[word_index][color_index:].find("#") color = words[word_index][ color_index + starti : color_index + starti + 7 ] start_point = words[word_index].find(">") end_point = words[word_index].find("</span>") text = words[word_index][start_point + 1 : end_point] text = html.unescape(text) if text != "": # print(text, "'" + color + "'") self.code_json[code_json_line_index].append([text, color]) # print(self.code_json) def _correct_non_span(self, line_str): """Function put text color to those strings that don't have one according to background_color of displayed code. Parameters --------- line_str : :class:`str` Takes a html element's string to put color to it according to background_color of displayed code. Returns ------- :class:`str` The generated html element's string with having color attributes. """ words = line_str.split("</span>") line_str = "" for i in range(0, words.__len__()): if i != words.__len__() - 1: j = words[i].find("<span") else: j = words[i].__len__() temp = "" starti = -1 for k in range(0, j): if words[i][k] == "\t" and starti == -1: continue else: if starti == -1: starti = k temp = temp + words[i][k] if temp != "": if i != words.__len__() - 1: temp = ( '<span style="color:' + self.default_color + '">' + words[i][starti:j] + "</span>" ) else: temp = ( '<span style="color:' + self.default_color + '">' + words[i][starti:j] ) temp = temp + words[i][j:] words[i] = temp if words[i] != "": line_str = line_str + words[i] + "</span>" return line_str
def add_labels( self, dict_values: dict[float, str | float | VMobject], direction: Sequence[float] = None, buff: float | None = None, font_size: float | None = None, label_constructor: VMobject | None = None, ): """Adds specifically positioned labels to the :class:`~.NumberLine` using a ``dict``. The labels can be accessed after creation via ``self.labels``. Parameters ---------- dict_values A dictionary consisting of the position along the number line and the mobject to be added: ``{1: Tex("Monday"), 3: Tex("Tuesday")}``. :attr:`label_constructor` will be used to construct the labels if the value is not a mobject (``str`` or ``float``). direction Determines the direction at which the label is positioned next to the line. buff The distance of the label from the line. font_size The font size of the mobject to be positioned. label_constructor The :class:`~.VMobject` class that will be used to construct the label. Defaults to the ``label_constructor`` attribute of the number line if not specified. Raises ------ AttributeError If the label does not have a ``font_size`` attribute, an ``AttributeError`` is raised. """ direction = self.label_direction if direction is None else direction buff = self.line_to_number_buff if buff is None else buff font_size = self.font_size if font_size is None else font_size label_constructor = (self.label_constructor if label_constructor is None else label_constructor) labels = VGroup() for x, label in dict_values.items(): # TODO: remove this check and ability to call # this method via CoordinateSystem.add_coordinates() # must be explicitly called if isinstance(label, str) and self.label_constructor is MathTex: label = Tex(label) else: label = self._create_label_tex(label) if hasattr(label, "font_size"): label.font_size = font_size else: raise AttributeError( f"{label} is not compatible with add_labels.") label.next_to(self.number_to_point(x), direction=direction, buff=buff) labels.add(label) self.labels = labels self.add(labels) return self
def get_on_screen_creatures(self): mobjects = self.get_mobject_family_members() return VGroup(*[ pencil for pencil in self.get_creatures() if pencil in mobjects ])