分子轨道

发布时间 2023-03-22 21:16:14作者: 树叶本子
from manim import *

frame_width = config["frame_width"]
frame_height = config["frame_height"]

def narrator(a):
    for i in a:
        globals()[f't{str(i)}']=Text(a[i], font = "STZhongsong").to_edge(DOWN).scale(0.6)

class AtomicOrbitalTemplate(TexTemplate):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.add_to_preamble("\\usepackage{tikzorbital}")

class AtomicOrbital(VGroup):
    def __init__(
        self,
        orbital_name: str,
        **kwargs
    ):
        super().__init__(**kwargs)
        self.template = AtomicOrbitalTemplate()
        orbital = MathTex("\\begin{tikzpicture} \\orbital{%s}  \\end{tikzpicture}" % orbital_name, tex_template = self.template, stroke_width=2, fill_color=BLUE, fill_opacity=0.8, **kwargs)
        self.orbital = orbital
        self.add(orbital)

class AtomicElectronsArrangement(VGroup):
    def __init__(
            self,
            orbital_type,
            orbital_label,
            electrons_num,
            custom_orbital_num = 1,  # 用于自定义轨道,一般的 s, p, d 轨道的 orbital_num 会在下面给出
            custom_scale_orbital = False,
            **kwargs
        ):
        self.orbital_type = orbital_type
        self.orbital_label = orbital_label
        self.electrons_num = electrons_num
        self.custom_orbital_num = custom_orbital_num
        self.custom_scale_orbital = custom_scale_orbital
        super().__init__(**kwargs)

        self.orbitals = self.mob_orbitals()
        self.lines = self.mob_lines()
        self.label = self.mob_label()
        self.dots = self.mob_dots()
        self.show_dots = self.anim_show_dots()

        self.add(self.orbitals, self.lines, self.label, self.dots)

    def mob_orbitals(self):
        orbital_type = self.orbital_type
        if orbital_type == 's':
            self.orbital_num = 1
            self.line_length = 1/2
            orbital = AtomicOrbital('s')
            orbitals = VGroup(orbital)
        elif orbital_type == 'p':
            self.orbital_num = 3
            self.line_length = 1/3
            orbital1 = AtomicOrbital('px')
            orbital2 = AtomicOrbital('py')
            orbital3 = AtomicOrbital('pz')
            orbitals = VGroup(orbital1, orbital2, orbital3)
        elif orbital_type == 's_sigma':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[SMolecularOrbitals().orbitals_sigma_2 for i in range(self.orbital_num)])
        elif orbital_type == 's_sigma*':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[SMolecularOrbitals().orbitals_sigma_1 for i in range(self.orbital_num)])
        elif orbital_type == 'p_sigma':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[PMolecularOrbitals().orbitals_sigma_2 for i in range(self.orbital_num)])
        elif orbital_type == 'p_sigma*':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[PMolecularOrbitals().orbitals_sigma_1 for i in range(self.orbital_num)])
        elif orbital_type == 'p_pi':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[PMolecularOrbitals().orbitals_pi_2 for i in range(self.orbital_num)])
        elif orbital_type == 'p_pi*':
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[PMolecularOrbitals().orbitals_pi_1 for i in range(self.orbital_num)])
        elif orbital_type is None:
            self.line_length = 1/2
            self.orbital_num = self.custom_orbital_num
            orbitals = VGroup(*[VMobject() for i in range(self.orbital_num)])
        else:
            print('no such orbital type yet')
        return orbitals
    
    def mob_lines(self):
        line_length = self.line_length
        lines = VGroup(*[Line(LEFT*line_length, RIGHT*line_length) for i in range(self.orbital_num)])
        lines.arrange(buff=0.5)
        orbitals_width = 0
        if self.custom_scale_orbital:
            orbital_width = self.orbitals[0].width
            orbital_height = self.orbitals[0].height
            scale_coe = lines[0].width*0.9/orbital_width
            if orbital_height*scale_coe > 0.5:
                scale_coe_2 = 0.5/(orbital_height*scale_coe)
                scale_coe *= scale_coe_2
        else:
            for orbital in self.orbitals:
                orbitals_width += orbital.width
            if orbitals_width > lines.width*0.9:
                scale_coe = lines.width*0.9/orbitals_width
            else:
                scale_coe = 1
        for i in range(self.orbital_num):
            self.orbitals[i].scale(scale_coe)
            self.orbitals[i].move_to(lines[i])
        return lines

    def mob_label(self):
        orbital_label = self.orbital_label
        lines = self.lines
        if self.custom_orbital_num > 1:
            label = VGroup()
            for line in lines:
                single_label = MathTex(orbital_label).scale(0.6).next_to(line, DOWN)
                label.add(single_label)
        else:
            label = MathTex(orbital_label).scale(0.6).next_to(lines, DOWN)
        return label

    def mob_dots(self):
        line_length = self.line_length
        dots = VGroup()
        electrons_num = self.electrons_num
        lines = self.lines
        if electrons_num > self.orbital_num:
            for i in range(self.orbital_num):
                dot = Dot(color=BLUE).move_to(lines[i]).shift(0.2*UP + line_length*0.6*LEFT)
                arrow = MathTex(r'\uparrow').set_color(BLUE).scale(0.8).next_to(dot, 0.5*UP)
                dots.add(VGroup(dot ,arrow))
            for i in range(electrons_num - self.orbital_num):
                dot = Dot(color=RED).move_to(lines[i]).shift(0.2*UP + line_length*0.6*RIGHT)
                arrow = MathTex(r'\downarrow').set_color(RED).scale(0.8).next_to(dot, 0.5*UP)
                dots.add(VGroup(dot ,arrow))
        else:
            for i in range(electrons_num):
                dot = Dot(color=BLUE).move_to(lines[i]).shift(0.2*UP + line_length*0.6*LEFT)
                arrow = MathTex(r'\uparrow').set_color(BLUE).scale(0.8).next_to(dot, 0.5*UP)
                dots.add(VGroup(dot ,arrow))
        return dots

    def anim_show_dots(self):
        anim = Succession(*[FadeIn(dot) for dot in self.dots])  # bug:使用 animate 会使物件 opacity 变为 1 的同时回到屏幕中心
        return anim

class MolecularOrbitals(VGroup):
    def __init__(
        self,
        left_orbital_name: list = None,
        right_orbital_name: list = None,
        central_orbitals_names: list = None,
        central_orbitals_buff = 2,
        custom_orbitals_position: list = None,
        **kwargs
    ):
        self.left_orbital_name = left_orbital_name
        self.right_orbital_name = right_orbital_name
        self.central_orbitals_names = central_orbitals_names
        self.central_orbitals_buff = central_orbitals_buff
        self.custom_orbitals_position = custom_orbitals_position
        super().__init__(**kwargs)

        self.left_orbital = self.mob_left_orbital()
        self.right_orbital = self.mob_right_orbital()
        self.central_orbitals = self.mob_central_orbitals()
        self.left_lines = self.mob_left_lines()
        self.right_lines = self.mob_right_lines()
        self.lines_dots = self.mob_lines_dots()

        self.show_orbitals_show_lines_1 = self.anim_show_orbitals_show_lines_1()
        self.show_orbitals_show_lines_2 = self.anim_show_orbitals_show_lines_2()
        self.show_dots = self.anim_show_dots()

        self.add(self.left_orbital, self.right_orbital, self.central_orbitals, self.left_lines, self.right_lines)

    def mob_left_orbital(self):
        left_orbital_name = self.left_orbital_name
        custom_orbitals_position = self.custom_orbitals_position
        if left_orbital_name is None:
            left_orbital = VGroup()
        else:
            left_orbital = AtomicElectronsArrangement(*left_orbital_name)
            left_orbital.shift(frame_width/2*0.6*LEFT)
        
        if custom_orbitals_position is not None:
            left_orbital.shift(custom_orbitals_position[0]*UP)
        return left_orbital
    
    def mob_right_orbital(self):
        right_orbital_name = self.right_orbital_name
        custom_orbitals_position = self.custom_orbitals_position
        if right_orbital_name is None:
            right_orbital = VGroup()
        else:
            right_orbital = AtomicElectronsArrangement(*right_orbital_name)
            right_orbital.shift(frame_width/2*0.6*RIGHT)

        if custom_orbitals_position is not None:
            right_orbital.shift(custom_orbitals_position[1]*UP)
        return right_orbital
    
    def mob_central_orbitals(self):
        central_orbitals_names = self.central_orbitals_names
        central_orbitals = VGroup()
        for name in central_orbitals_names:
            central_orbital = AtomicElectronsArrangement(*name, custom_scale_orbital=True)
            central_orbitals.add(central_orbital)

        if self.custom_orbitals_position is not None:
            custom_orbitals_position = self.custom_orbitals_position[2:]
            n = 0
            for orbital in central_orbitals:
                orbital.shift(custom_orbitals_position[n]*UP)
                n += 1
        else:
            central_orbitals.arrange(DOWN, buff=self.central_orbitals_buff)
        return central_orbitals
    
    def mob_left_lines(self):
        left_orbital = self.left_orbital
        central_orbitals = self.central_orbitals
        lines = VGroup()
        if self.left_orbital_name is not None:
            start_point = left_orbital.lines[-1].get_right()
            for orbital in central_orbitals:
                end_point = orbital.lines[0].get_left()
                line = DashedLine(start_point, end_point)
                lines.add(line)
        return lines
    
    def mob_right_lines(self):
        right_orbital = self.right_orbital
        central_orbitals = self.central_orbitals
        lines = VGroup()
        if self.right_orbital_name is not None:
            start_point = right_orbital.lines[0].get_left()
            for orbital in central_orbitals:
                end_point = orbital.lines[-1].get_right()
                line = DashedLine(start_point, end_point)
                lines.add(line)
        return lines
    
    def mob_lines_dots(self):
        def get_lines_dots(mob):
            # return VGroup(mob.lines, mob.label, mob.dots)
            return VGroup(mob.orbitals)
        left_orbital = get_lines_dots(self.left_orbital)
        right_orbital = get_lines_dots(self.right_orbital)
        central_orbitals = VGroup(*[get_lines_dots(orbital) for orbital in self.central_orbitals])
        return VGroup(self.left_lines, self.right_lines, left_orbital, right_orbital, central_orbitals)

    def anim_show_orbitals_show_lines_1(self):
        left_lines = self.left_lines
        right_lines = self.right_lines
        left_orbital = self.left_orbital
        right_orbital = self.right_orbital
        central_orbitals = self.central_orbitals
        anim1 = DrawBorderThenFill(left_orbital.orbitals)
        anim2 = DrawBorderThenFill(right_orbital.orbitals)
        anim3_list = []
        n = 0
        for central_orbital in central_orbitals:
            anim3_list.append(DrawBorderThenFill(central_orbital.orbitals))
            anim3_list.append(FadeIn(VGroup(left_lines[n], right_lines[n])))
            n += 1
        anim3 = Succession(*anim3_list)
        anim = Succession(anim1, anim2, anim3)
        return anim
    
    def anim_show_orbitals_show_lines_2(self):
        left_orbital = self.left_orbital
        right_orbital = self.right_orbital
        central_orbitals = self.central_orbitals
        anim1 = FadeOut(left_orbital.orbitals, right_orbital.orbitals, *[central_orbital.orbitals for central_orbital in central_orbitals])
        anim2 = FadeIn(left_orbital.lines, left_orbital.label, right_orbital.lines, right_orbital.label, *[orbital.lines for orbital in central_orbitals], *[orbital.label for orbital in central_orbitals])
        anim = Succession(anim1, anim2)
        return anim

    def anim_show_dots(self):
        left_orbital = self.left_orbital
        right_orbital = self.right_orbital
        central_orbitals = self.central_orbitals
        effective_central_orbitals = VGroup()
        for central_orbital in central_orbitals:
            if len(central_orbital.dots) > 0:
                effective_central_orbitals.add(central_orbital)
        effective_central_orbitals = effective_central_orbitals[::-1]
        anim1 = left_orbital.show_dots
        anim2 = right_orbital.show_dots
        anim3 = Succession(*[central_orbital.show_dots for central_orbital in effective_central_orbitals])
        anim = Succession(anim1, anim2, anim3)
        return anim

class O2MolecularOrbitals(VGroup):
    def __init__(self):
        self.molecular_orbitals_1s = self.mob_molecular_orbitals_1s()
        self.molecular_orbitals_2s = self.mob_molecular_orbitals_2s()
        self.molecular_orbitals_2p = self.mob_molecular_orbitals_2p()
    
    def mob_molecular_orbitals_1s(self):
        left_orbital_name = ['s', '1s', 2]
        right_orbital_name = ['s', '1s', 2]
        central_orbitals_names = [['s_sigma*', '\sigma_{u}^{*}', 2], ['s_sigma', '\sigma_{g}', 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names, central_orbitals_buff=0.5)
        return molecular_orbitals
    
    def mob_molecular_orbitals_2s(self):
        left_orbital_name = ['s', '2s', 2]
        right_orbital_name = ['s', '2s', 2]
        central_orbitals_names = [['s_sigma*', '\sigma_{u}^{*}', 2], ['s_sigma', '\sigma_{g}', 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names)
        return molecular_orbitals
    
    def mob_molecular_orbitals_2p(self):
        left_orbital_name = ['p', '2p', 4]
        right_orbital_name = ['p', '2p', 4]
        central_orbitals_names = [['p_sigma*', '\sigma_{u}^{*}', 0], ['p_pi*', '\pi_{g}^{*}', 2, 2], ['p_pi', '\pi_{g}', 4, 2], ['p_sigma', '\sigma_{g}', 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names, central_orbitals_buff=0.7)
        molecular_orbitals.scale(0.9)
        return molecular_orbitals

class HFMolecularOrbitals(VGroup):
    def __init__(self):
        self.molecular_orbitals = self.mob_molecular_orbitals()
    
    def mob_molecular_orbitals(self):
        left_orbital_name = ['s', '1s', 1]
        right_orbital_name = ['p', '2p', 5]
        central_orbitals_names = [[None, '\sigma^{*}', 0], [None, '\pi', 4, 2], [None, '\sigma', 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names, custom_orbitals_position=[1, -1, 3, -1, -2.5])
        molecular_orbitals.left_lines.remove(molecular_orbitals.left_lines[1])
        return molecular_orbitals

class COMolecularOrbitals(VGroup):
    def __init__(self):
        self.molecular_orbitals_2s = self.mob_molecular_orbitals_2s()
        self.molecular_orbitals_2p = self.mob_molecular_orbitals_2p()

    def mob_molecular_orbitals_2s(self):
        left_orbital_name = ['s', '2s', 2]
        right_orbital_name = ['s', '2s', 2]
        central_orbitals_names = [['s_sigma*', '2\sigma^{*}', 2], ['s_sigma', '2\sigma', 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names, custom_orbitals_position=[1, -1, 2, -2])
        return molecular_orbitals
    
    def mob_molecular_orbitals_2p(self):
        left_orbital_name = ['p', '2p', 2]
        right_orbital_name = ['p', '2p', 4]
        central_orbitals_names = [['p_sigma*', '3\sigma^{*}', 0], ['p_pi*', '1\pi^{*}', 0, 2], ['p_sigma', '3\sigma', 2], ['p_pi', '1\pi', 4, 2]]
        molecular_orbitals = MolecularOrbitals(left_orbital_name, right_orbital_name, central_orbitals_names, custom_orbitals_position=[2, -2, 3, 2, -1.5, -3])
        molecular_orbitals.scale(0.8)
        return molecular_orbitals
    
class SMolecularOrbitals(VGroup):
    def __init__(self, **kwargs):
        self.orbitals = self.mob_orbitals()
        self.orbitals_sigma_1 = self.anim_orbitals_to_sigma_1(return_mobs=True)
        self.orbitals_sigma_2 = self.anim_orbitals_to_sigma_2(return_mobs=True)
        self.orbitals_to_sigma_1 = self.anim_orbitals_to_sigma_1()
        self.orbitals_to_sigma_2 = self.anim_orbitals_to_sigma_2()
        self.restore_orbitals = self.anim_restore_orbitals()
        super().__init__(**kwargs)

        self.add(self.orbitals)
    
    def mob_orbitals(self):
        orbital1 = Circle(stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8).shift(0.6*LEFT)
        orbital2 = Circle(stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8).shift(0.6*RIGHT)
        orbitals = VGroup(orbital1, orbital2)
        orbitals.save_state()
        return orbitals

    def anim_orbitals_to_sigma_1(self, return_mobs=False):
        orbitals = self.orbitals
        orbital1 = Ellipse(width=1.5, height=3, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8).shift(LEFT)
        orbital2 = Ellipse(width=1.5, height=3, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8).shift(RIGHT)
        anim1 = AnimationGroup(orbitals[0].animate.set(fill_color=BLUE), orbitals[1].animate.set(fill_color=RED))
        anim2 = Wait()
        anim3 = AnimationGroup(Transform(orbitals[0], orbital1), Transform(orbitals[1], orbital2))
        anim4 = Wait(2)
        if return_mobs:
            return VGroup(orbital1.set(fill_color=BLUE), orbital2.set(fill_color=BLUE))
        else:
            return Succession(anim1, anim2, anim3, anim4)

    def anim_orbitals_to_sigma_2(self, return_mobs=False):
        orbitals = self.orbitals
        orbital = Ellipse(width=3, height=2, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)
        anim1 = AnimationGroup(orbitals[0].animate.set(fill_color=BLUE), orbitals[1].animate.set(fill_color=BLUE))
        anim2 = Wait()
        anim3 = Transform(orbitals, orbital)
        anim4 = Wait(2)
        if return_mobs:
            return orbital.set(fill_color=BLUE)
        else:
            return Succession(anim1, anim2, anim3, anim4)

    def anim_restore_orbitals(self):
        orbitals = self.orbitals
        anim = Restore(orbitals)
        return anim

class PMolecularOrbitals(VGroup):
    def __init__(self, **kwargs):
        self.orbitals = self.mob_orbitals()
        self.orbitals_sigma_1 = self.anim_orbitals_to_sigma_1(return_mobs=True)
        self.orbitals_sigma_2 = self.anim_orbitals_to_sigma_2(return_mobs=True)
        self.orbitals_pi_1 = self.anim_orbitals_to_pi_1(return_mobs=True)
        self.orbitals_pi_2 = self.anim_orbitals_to_pi_2(return_mobs=True)
        self.orbitals_to_sigma_1 = self.anim_orbitals_to_sigma_1()
        self.orbitals_to_sigma_2 = self.anim_orbitals_to_sigma_2()
        self.orbitals_to_pi_1 = self.anim_orbitals_to_pi_1()
        self.orbitals_to_pi_2 = self.anim_orbitals_to_pi_2()
        self.orbitals_to_no_interaction = self.anim_orbitals_to_no_interaction()
        self.restore_orbitals = self.anim_restore_orbitals()
        super().__init__(**kwargs)

        self.add(self.orbitals)
    
    def mob_orbitals(self):
        orbital1 = VGroup(*[Ellipse(width=1, height=1.5, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8) for i in range(2)]).arrange(buff=0).shift(0.8*LEFT)
        orbital2 = VGroup(*[Ellipse(width=1, height=1.5, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8) for i in range(2)]).arrange(buff=0).shift(0.8*RIGHT)
        orbitals = VGroup(orbital1, orbital2)
        orbitals.save_state()
        return orbitals

    def anim_orbitals_to_sigma_1(self, return_mobs=False):
        orbitals = self.orbitals
        orbital1 = VGroup(Circle(radius=0.5, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8), Ellipse(width=0.8, height=1.8, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)).arrange(buff=0).shift(LEFT)
        orbital2 = VGroup(Ellipse(width=0.8, height=1.8, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8), Circle(radius=0.5, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)).arrange(buff=0).shift(RIGHT)
        anim1 = AnimationGroup(orbitals[0][0].animate.set(fill_color=RED), orbitals[0][1].animate.set(fill_color=BLUE), orbitals[1][0].animate.set(fill_color=RED), orbitals[1][1].animate.set(fill_color=BLUE))
        anim2 = Wait()
        anim3 = AnimationGroup(Transform(orbitals[0][0], orbital1[0]), Transform(orbitals[0][1], orbital1[1]), Transform(orbitals[1][0], orbital2[0]), Transform(orbitals[1][1], orbital2[1]))
        anim4 = Wait(2)
        if return_mobs:
            return VGroup(orbital1.set(fill_color=BLUE), orbital2.set(fill_color=BLUE))
        else:
            return Succession(anim1, anim2, anim3, anim4)

    def anim_orbitals_to_sigma_2(self, return_mobs=False):
        orbitals = self.orbitals
        orbital1 = Ellipse(width=1, height=0.8, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8)
        orbital2 = Ellipse(width=1.5, height=1, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)
        orbital3 = Ellipse(width=1, height=0.8, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8)
        VGroup(orbital1, orbital2, orbital3).arrange(buff=0)
        anim1 = AnimationGroup(orbitals[0][0].animate.set(fill_color=RED), orbitals[0][1].animate.set(fill_color=BLUE), orbitals[1][0].animate.set(fill_color=BLUE), orbitals[1][1].animate.set(fill_color=RED))
        anim2 = Wait()
        anim3 = AnimationGroup(Transform(orbitals[0][0], orbital1), Transform(VGroup(orbitals[0][1], orbitals[1][0]), orbital2), Transform(orbitals[1][1], orbital3))
        anim4 = Wait(2)
        if return_mobs:
            return VGroup(orbital1.set(fill_color=BLUE), orbital2.set(fill_color=BLUE), orbital3.set(fill_color=BLUE))
        else:
            return Succession(anim1, anim2, anim3, anim4)
    
    def anim_orbitals_to_pi_1(self, return_mobs=False):
        orbitals = self.orbitals
        orbital1 = VGroup(Ellipse(width=1.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8), Ellipse(width=1.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8)).arrange(direction=DOWN, buff=0).shift(0.7*LEFT)
        orbital2 = VGroup(Ellipse(width=1.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8), Ellipse(width=1.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)).arrange(direction=DOWN, buff=0).shift(0.7*RIGHT)
        anim1 = AnimationGroup(orbitals[0][0].animate.set(fill_color=BLUE), orbitals[0][1].animate.set(fill_color=RED), orbitals[1][0].animate.set(fill_color=RED), orbitals[1][1].animate.set(fill_color=BLUE))
        anim2 = AnimationGroup(Rotate(orbitals[0], -0.5*PI), Rotate(orbitals[1], -0.5*PI))
        anim3 = AnimationGroup(ApplyMethod(orbitals[0].shift, 0.3*RIGHT), ApplyMethod(orbitals[1].shift, 0.3*LEFT))
        anim4 = Wait()
        anim5 = AnimationGroup(Transform(orbitals[0][0], orbital1[0]), Transform(orbitals[0][1], orbital1[1]), Transform(orbitals[1][0], orbital2[0]), Transform(orbitals[1][1], orbital2[1]))
        anim6 = Wait(2)
        if return_mobs:
            return VGroup(orbital1.set(fill_color=BLUE), orbital2.set(fill_color=BLUE))
        else:
            return Succession(anim1, anim2, anim3, anim4, anim5, anim6)
    
    def anim_orbitals_to_pi_2(self, return_mobs=False):
        orbitals = self.orbitals
        orbital1 = Ellipse(width=2.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.8)
        orbital2 = Ellipse(width=2.2, height=1, stroke_color=WHITE, stroke_width=2, fill_color=RED, fill_opacity=0.8)
        VGroup(orbital1, orbital2).arrange(direction=DOWN, buff=0)
        anim1 = AnimationGroup(orbitals[0][0].animate.set(fill_color=BLUE), orbitals[0][1].animate.set(fill_color=RED), orbitals[1][0].animate.set(fill_color=BLUE), orbitals[1][1].animate.set(fill_color=RED))
        anim2 = AnimationGroup(Rotate(orbitals[0], -0.5*PI), Rotate(orbitals[1], -0.5*PI))
        anim3 = AnimationGroup(ApplyMethod(orbitals[0].shift, 0.3*RIGHT), ApplyMethod(orbitals[1].shift, 0.3*LEFT))
        anim4 = Wait()
        anim5 = AnimationGroup(Transform(VGroup(orbitals[0][0], orbitals[1][0]), orbital1), Transform(VGroup(orbitals[0][1], orbitals[1][1]), orbital2))
        anim6 = Wait(2)
        if return_mobs:
            return VGroup(orbital1.set(fill_color=BLUE), orbital2.set(fill_color=BLUE))
        else:
            return Succession(anim1, anim2, anim3, anim4, anim5, anim6)
    
    def anim_orbitals_to_no_interaction(self):
        orbitals = self.orbitals
        anim = Rotate(orbitals[0], -0.5*PI)
        return anim

    def anim_restore_orbitals(self):
        orbitals = self.orbitals
        anim = Restore(orbitals)
        return anim

class SPMolecularOrbitals(VGroup):
    def __init__(self, **kwargs):
        self.orbitals = self.mob_orbitals()

    def mob_orbitals(self):
        orbital1 = Circle(radius=0.8, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8).shift(0.7*LEFT)
        orbital2 = VGroup(*[Ellipse(width=1, height=1.5, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8) for i in range(2)]).arrange(buff=0).shift(0.7*RIGHT)
        note = Text('只有当 p 轨道朝向和键轴方向一致时,才能发生作用', font = "STFangsong").to_edge(UP).scale(0.6)
        return VGroup(orbital1, orbital2, note)

class ChemicalTexTemplate(TexTemplate):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.add_to_preamble("\\usepackage{chemfig}")

class CObejct(MathTex):
    def __init__(
        self,
        chem_str: str,
        **kwargs
    ):
        self.template = ChemicalTexTemplate()
        super().__init__("\\chemfig{%s}" % chem_str, tex_template = self.template, stroke_width=2, **kwargs)  # 如果不适用 stroke_width,那么化学键会不显示

class H2O(CObejct):
    def __init__(self, **kwargs):
        super().__init__('H-[:30]O-[:-30]H')
        self.scale(2)

        self.H_orbitals = self.mob_H_orbitals()
        self.O_orbital = self.mob_O_orbital()
        self.compose_H_orbitals = self.anim_compose_H_orbitals()
        self.tex_table = self.mob_tex_table()

    def mob_H_orbitals(self):
        orbital1 = Circle(radius=0.7, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8).move_to(self[0][0])
        orbital2 = Circle(radius=0.7, stroke_color=WHITE, stroke_width=2, fill_color=GREEN, fill_opacity=0.8).move_to(self[0][3])
        return VGroup(orbital1, orbital2)
    
    def mob_O_orbital(self):
        orbital = Circle(radius=0.7, stroke_color=WHITE, stroke_width=2, fill_color=PURPLE, fill_opacity=0.8).move_to(self[0][1])
        return orbital
    
    def mob_tex_table(self):
        tex = MathTex(r'''
        \begin{array}{c|cccc|c|c}
            C_{2v} & E & C_{2} & \sigma_{v}(xz) & \sigma_{v}(yz) &          &                     \\
            \hline &   &       &                &                &          &                     \\
            A_{1}  & 1 & 1     & 1              & 1              & z        & x^{2}, y^{2}, z^{2} \\
            A_{2}  & 1 & 1     & -1             & -1             & R_{z}    & xy                  \\
            B_{1}  & 1 & -1    & 1              & -1             & x, R_{y} & xz                  \\
            B_{2}  & 1 & -1    & -1             & 1              & y, R_{x} & yz                  \\
        \end{array}
        ''')
        tex.scale(0.8)
        return tex
    
    def anim_compose_H_orbitals(self):
        orbitals = self.H_orbitals
        anim1 = AnimationGroup(orbitals[0].animate.set(fill_color=BLUE), orbitals[1].animate.set(fill_color=RED))
        anim2 = AnimationGroup(orbitals[0].animate.set(fill_color=GREEN), orbitals[1].animate.set(fill_color=GREEN))
        anim3 = AnimationGroup(orbitals[0].animate.set(fill_color=BLUE), orbitals[1].animate.set(fill_color=BLUE))
        anim4 = AnimationGroup(orbitals[0].animate.set(fill_color=GREEN), orbitals[1].animate.set(fill_color=GREEN))
        return Succession(anim1, anim2, anim3, anim4)