from __future__ import annotations
import vtk
from custom_types import *
from ui import ui_utils
import constants
import vtk.util.numpy_support as numpy_support


class GaussianData:

    def make_symmetric(self, other: GaussianData):
        # reflection = np.eye(3)
        # reflection[0, 0] = -1
        # self.total_translate = np.einsum('ad,d->a', reflection, other.total_translate)
        self.total_translate = other.total_translate.copy()
        self.total_translate[0] *= -1
        self.total_rotate = other.total_rotate.copy()
        # self.total_rotate = np.einsum('ab,bc->ac', reflection, other.total_rotate)

    @staticmethod
    def to_positive(p):
        up_dir = 1
        eye = np.eye(3)
        all_dots = (p[:, :] * eye[up_dir, None, :]).sum(-1)
        up_axis = all_dots.__abs__().argmax()
        return up_axis, all_dots[up_axis] < 0

    def permute_p(self, p):
        up_dir = 1
        p_new = np.eye(3)
        p_new[up_dir] = p[self.up_axis]
        if self.reflect_up:
            p_new[up_dir] = -p_new[up_dir]
        p_new[(up_dir + 1) % 3] = p[(self.up_axis + 1) % 3]
        p_new[(up_dir + 2) % 3, :] = np.cross(p_new[up_dir, :], p_new[(up_dir + 1) % 3, :])
        return p_new

    def rotate(self, transition: ui_utils.Transition):
        mu = self.mu_baked - transition.transition_origin
        mu = np.einsum('ab,b->a', transition.rotation, mu) + transition.transition_origin
        self.total_translate = mu - self.mu
        self.total_rotate = np.einsum('ab,bc->ac', transition.rotation, self.total_rotate)

    def stretch(self, amount):
        scale = 0.9 if amount < 0 else 1 / .9
        self.eigen = self.eigen * scale

    def translate(self, transition: ui_utils.Transition):
        self.total_translate = self.total_translate + transition.translation

    def get_view_eigen(self):
        scale = (self.mu_baked ** 2).sum() / (self.mu ** 2).sum()
        return self.eigen * scale

    def get_raw_data(self):
        # p = np.einsum('da,db->ba', self.p, self.total_rotate)
        p = np.einsum('ab,bc->ac', self.total_rotate, self.p.transpose()).transpose()
        return self.phi, self.mu_baked, self.eigen, p

    def copy_data(self):
        return [item.copy() if type(item) is ARRAY else item for item in self.get_raw_data()]

    def get_view_data(self):
        phi, mu, eigen, p = self.get_raw_data()
        p = self.permute_p(p)
        return phi, mu, eigen, p

    @property
    def mu_baked(self) -> ARRAY:
        return self.mu + self.total_translate

    @property
    def phi(self) -> float:
        return self.data[0]

    @property
    def mu(self) -> ARRAY:
        return self.data[1]

    @property
    def eigen(self) -> ARRAY:
        return self.data[2]

    @property
    def p(self) -> ARRAY:
        return self.data[3]

    @mu.setter
    def mu(self, new_mu: ARRAY):
        self.data[1] = new_mu

    @p.setter
    def p(self, new_p: ARRAY):
        self.data[3] = new_p

    @eigen.setter
    def eigen(self, new_eigen: ARRAY):
        self.data[2] = new_eigen

    def reset(self):
        self.total_translate = np.zeros(3)
        self.total_rotate = np.eye(3)

    def __getitem__(self, item):
        return self.data[item]

    def __init__(self, gaussian):
        self.recover_data = [item.copy() if type(item) is ARRAY else item for item in gaussian]
        self.data = list(gaussian)
        self.up_axis, self.reflect_up = self.to_positive(self.p)
        self.total_translate = np.zeros(3)
        self.total_rotate = np.eye(3)


class GaussianStatus(GaussianData):

    # copy_constructor
    def copy(self: GaussianStatus, render: vtk.vtkRenderer, view_style: ui_utils.ViewStyle,
             gaussian_id: Optional[Tuple[int, int]] = None, is_selected: Optional[bool] = None) -> GaussianStatus:
        if self.disabled:
            return self
        gaussian_id = self.gaussian_id if gaussian_id is None else gaussian_id
        return GaussianStatus(self.copy_data(), gaussian_id, is_selected or self.is_selected, view_style, render, 1)

    @staticmethod
    def get_new_gaussian() -> vtk.vtkSphereSource:
        return ui_utils.load_vtk_obj(f"{constants.DATA_ROOT}/ui_resources/simple_brick.obj")

    def update_gaussian_transform(self, source):
        phi, mu, eigen, p = self.get_view_data()

        # def replace_mesh(self, mesh: T_Mesh):
        #     vs, faces = mesh
        #     vs, faces = vs.detach().cpu(), faces.detach().cpu()
        #     # vs, faces = mesh_utils.scale_from_ref(mesh, *self.scale)
        #     source = vtk.vtkPolyData()
        #     new_vs_vtk = numpy_support.numpy_to_vtk(vs.numpy())
        #     cells_npy = np.column_stack(
        #         [np.full(faces.shape[0], 3, dtype=np.int64), faces.numpy().astype(np.int64)]).ravel()
        #     vs_vtk, faces_vtk = vtk.vtkPoints(), vtk.vtkCellArray()
        #     vs_vtk.SetData(new_vs_vtk)
        #     faces_vtk.SetCells(faces.shape[0], numpy_support.numpy_to_vtkIdTypeArray(cells_npy))
        #
        #     source.SetPolys(faces_vtk)
        #     self.mapper.SetInputData(source)
        #     self.is_changed = True
        #     if not self.to_init:
        #         self.to_init = True
        #         self.render.AddActor(self.actor)
        transform = vtk.vtkTransform()
        mat = vtk.vtkMatrix4x4()
        p = p * .005
        # p = p * eigen[:, None]
        for i in range(4):
            for j in range(4):
                if i > 2:
                    mat.SetElement(i, j, 0)
                elif j > 2:
                    mat.SetElement(i, j, float(mu[i]))
                    # mat.SetElement(i, j, 0)
                else:
                    mat.SetElement(i, j, p[j, i])
                # mat_t[i, j] = mat.GetElement(i,j)
        mat.SetElement(3, 3, 1)
        transform.SetMatrix(mat)
        transformFilter = vtk.vtkTransformPolyDataFilter()
        transformFilter.SetInputData(source)
        transformFilter.SetTransform(transform)
        transformFilter.Update()
        return transformFilter

    def update_gaussian(self):
        if self.disabled:
            return
        # source = self.mapper.GetInput()
        # source.SetPoints(self.init_points)
        # source = self.update_gaussian_transform(source)
        # self.mapper.SetInputConnection(source.GetOutputPort())

    def end_transition(self, transition: ui_utils.Transition) -> bool:
        if self.init_points is None:
            return False
        if transition.transition_type is ui_utils.EditType.Translating:
            self.translate(transition)
            return True
        elif transition.transition_type is ui_utils.EditType.Rotating:
            self.rotate(transition)
            return True
        elif transition.transition_type is ui_utils.EditType.Scaling:
            self.rotate(transition)
            return True
        return False

    def temporary_transition(self, transition: ui_utils.Transition) -> bool:
        if self.init_points is None:
            return False
        source = self.mapper.GetInput()
        vs = self.init_points
        if transition.transition_type is ui_utils.EditType.Translating:
            vs = vs + transition.translation[None, :]
        elif transition.transition_type is ui_utils.EditType.Rotating:
            vs = vs - transition.transition_origin[None, :]
            vs = np.einsum('ad,nd->na', transition.rotation, vs)
            vs = vs + transition.transition_origin[None, :]
        source.GetPoints().SetData(numpy_support.numpy_to_vtk(vs))
        return True

    def get_address(self):
        if self.disabled:
            return f"disabled"
        return self.actor.GetAddressAsString('')

    @staticmethod
    def add_gaussian(render, actor: Optional[vtk.vtkActor]) -> vtk.vtkActor:
        if actor is None:
            actor = vtk.vtkActor()
            mapper = vtk.vtkPolyDataMapper()
            actor.GetProperty().SetOpacity(0.3)
            actor.SetMapper(mapper)
            # source = self.get_new_gaussian()
            # init_points = source.GetPoints()
            # source = self.update_gaussian_transform(source)
            # actor, _ = ui_utils.wrap_mesh(source.GetOutput(), color)
            render.AddActor(actor)
            ui_utils.set_default_properties(actor, (1., 1., .1))
        return actor

    def replace_part(self, part_mesh: Optional[vtk.vtkPolyData]):
        if part_mesh is not None and not self.disabled:
            self.init_points = numpy_support.vtk_to_numpy(part_mesh.GetPoints().GetData())
            points = vtk.vtkPoints()
            points.SetData(numpy_support.numpy_to_vtk(self.init_points))
            part_mesh.SetPoints(points)
            self.mapper.SetInputData(part_mesh)

    def set_color(self):
        if self.disabled:
            return
        properties = self.actor.GetProperty()
        properties.SetOpacity(self.opacity)
        properties.SetColor(*self.color)

    def turn_off(self):
        if self.disabled:
            return
        self.actor.GetProperty().SetOpacity(0)
        self.actor.PickableOff()

    def turn_on(self):
        if self.disabled:
            return
        self.actor.GetProperty().SetOpacity(self.opacity)
        self.actor.PickableOn()

    @property
    def is_not_selected(self):
        return not self.is_selected

    @property
    def disabled(self):
        return self.mapper is None #or self.mapper.GetInput() is None

    def make_symmetric(self, force_include: bool):
        if self.disabled or self.twin is None or (not force_include and self.included != self.twin.included):
            return
        super(GaussianStatus, self).make_symmetric(self.twin)
        if force_include:
            self.included = self.twin.included
        self.update_gaussian()

    def apply_affine(self, button: ui_utils.Buttons, key: str):
        axis = {"left": 0, "right": 0, "up": 2, "down": 2, "a": 1, "z": 1}[key]
        sign = {"left": 1, "right": -1, "up": 1, "down": -1, "a": 1, "z": -1}[key]
        if self.disabled or button not in (ui_utils.Buttons.translate, ui_utils.Buttons.stretch, ui_utils.Buttons.rotate):
            return
        elif button is ui_utils.Buttons.translate:
            vec = np.zeros(3)
            vec[axis] = .01 * sign
            self.translate(vec)
        elif button is ui_utils.Buttons.rotate:
            self.rotate(sign * .1, axis)
        else:
            self.stretch(.01 * sign)
        self.update_gaussian()

    @property
    def opacity(self) -> float:
        if self.is_selected:
            if self.included:
                opacity = self.view_style.opacity + 0.4
            else:
                opacity = self.view_style.opacity
        else:
            if self.included:
                opacity = self.view_style.opacity + 0.2
            else:
                opacity = self.view_style.opacity
        return max(0., min(1., opacity))

    @property
    def color(self) -> Tuple[float, float, float]:
        if self.is_selected:
            return self.view_style.selected_color
        if self.included:
            return self.view_style.included_color
        else:
            return self.view_style.base_color

    def toggle_inclusion(self, included: Optional[bool] = None):
        if self.disabled:
            return
        if included is None:
            included = not self.included
        if included != self.included:
            self.included = not self.included
            self.set_color()

    def toggle_selection(self):
        if self.disabled:
            return
        self.is_selected = not self.is_selected
        self.set_color()

    def reset(self):
        if self.disabled:
            return
        super(GaussianStatus, self).reset()
        self.included = False
        self.is_selected = False
        self.set_color()
        # self.update_gaussian()

    def delete(self, render):
        if not self.disabled:
            render.RemoveActor(self.actor)
        if self.twin is not None:
            self.twin.twin = None

    @property
    def parent_id(self):
        return self.gaussian_id[0]

    @property
    def child_id(self):
        return self.gaussian_id[1]

    @property
    def mapper(self):
        if self.actor is None:
            return None
        return self.actor.GetMapper()

    def __init__(self, gaussian, gaussian_id: Tuple[int, int], is_selected: bool, view_style: ui_utils.ViewStyle,
                 render: vtk.vtkRenderer, normalized_phi: float, actor: Optional[vtk.vtkActor] = None):
        self.view_style = view_style
        super(GaussianStatus, self).__init__(gaussian)
        self.gaussian_id = gaussian_id
        self.twin: Optional[GaussianStatus] = None
        self.init_points = None
        if normalized_phi > 0.001 or actor is not None:
            self.actor = self.add_gaussian(render, actor)
            self.is_selected = is_selected
            self.included = True
            self.set_color()
        else:
            self.actor = None
            self.is_selected = False
            self.included = False

from __future__ import annotations
import vtk
from custom_types import *
from ui import ui_utils
import constants
import vtk.util.numpy_support as numpy_support


class GaussianData:

    def make_symmetric(self, other: GaussianData):
        # reflection = np.eye(3)
        # reflection[0, 0] = -1
        # self.total_translate = np.einsum('ad,d->a', reflection, other.total_translate)
        self.total_translate = other.total_translate.copy()
        self.total_translate[0] *= -1
        self.total_rotate = other.total_rotate.copy()
        # self.total_rotate = np.einsum('ab,bc->ac', reflection, other.total_rotate)

    @staticmethod
    def to_positive(p):
        up_dir = 1
        eye = np.eye(3)
        all_dots = (p[:, :] * eye[up_dir, None, :]).sum(-1)
        up_axis = all_dots.__abs__().argmax()
        return up_axis, all_dots[up_axis] < 0

    def permute_p(self, p):
        up_dir = 1
        p_new = np.eye(3)
        p_new[up_dir] = p[self.up_axis]
        if self.reflect_up:
            p_new[up_dir] = -p_new[up_dir]
        p_new[(up_dir + 1) % 3] = p[(self.up_axis + 1) % 3]
        p_new[(up_dir + 2) % 3, :] = np.cross(p_new[up_dir, :], p_new[(up_dir + 1) % 3, :])
        return p_new

    def rotate(self, transition: ui_utils.Transition):
        mu = self.mu_baked - transition.transition_origin
        mu = np.einsum('ab,b->a', transition.rotation, mu) + transition.transition_origin
        self.total_translate = mu - self.mu
        self.total_rotate = np.einsum('ab,bc->ac', transition.rotation, self.total_rotate)

    def stretch(self, amount):
        scale = 0.9 if amount < 0 else 1 / .9
        self.eigen = self.eigen * scale

    def translate(self, transition: ui_utils.Transition):
        self.total_translate = self.total_translate + transition.translation

    def get_view_eigen(self):
        scale = (self.mu_baked ** 2).sum() / (self.mu ** 2).sum()
        return self.eigen * scale

    def get_raw_data(self):
        # p = np.einsum('da,db->ba', self.p, self.total_rotate)
        p = np.einsum('ab,bc->ac', self.total_rotate, self.p.transpose()).transpose()
        return self.phi, self.mu_baked, self.eigen, p

    def copy_data(self):
        return [item.copy() if type(item) is ARRAY else item for item in self.get_raw_data()]

    def get_view_data(self):
        phi, mu, eigen, p = self.get_raw_data()
        p = self.permute_p(p)
        return phi, mu, eigen, p

    @property
    def mu_baked(self) -> ARRAY:
        return self.mu + self.total_translate

    @property
    def phi(self) -> float:
        return self.data[0]

    @property
    def mu(self) -> ARRAY:
        return self.data[1]

    @property
    def eigen(self) -> ARRAY:
        return self.data[2]

    @property
    def p(self) -> ARRAY:
        return self.data[3]

    @mu.setter
    def mu(self, new_mu: ARRAY):
        self.data[1] = new_mu

    @p.setter
    def p(self, new_p: ARRAY):
        self.data[3] = new_p

    @eigen.setter
    def eigen(self, new_eigen: ARRAY):
        self.data[2] = new_eigen

    def reset(self):
        self.total_translate = np.zeros(3)
        self.total_rotate = np.eye(3)

    def __getitem__(self, item):
        return self.data[item]

    def __init__(self, gaussian):
        self.recover_data = [item.copy() if type(item) is ARRAY else item for item in gaussian]
        self.data = list(gaussian)
        self.up_axis, self.reflect_up = self.to_positive(self.p)
        self.total_translate = np.zeros(3)
        self.total_rotate = np.eye(3)


class GaussianStatus(GaussianData):

    # copy_constructor
    def copy(self: GaussianStatus, render: vtk.vtkRenderer, view_style: ui_utils.ViewStyle,
             gaussian_id: Optional[Tuple[int, int]] = None, is_selected: Optional[bool] = None) -> GaussianStatus:
        if self.disabled:
            return self
        gaussian_id = self.gaussian_id if gaussian_id is None else gaussian_id
        return GaussianStatus(self.copy_data(), gaussian_id, is_selected or self.is_selected, view_style, render, 1)

    @staticmethod
    def get_new_gaussian() -> vtk.vtkSphereSource:
        return ui_utils.load_vtk_obj(f"{constants.DATA_ROOT}/ui_resources/simple_brick.obj")

    def update_gaussian_transform(self, source):
        phi, mu, eigen, p = self.get_view_data()

        # def replace_mesh(self, mesh: T_Mesh):
        #     vs, faces = mesh
        #     vs, faces = vs.detach().cpu(), faces.detach().cpu()
        #     # vs, faces = mesh_utils.scale_from_ref(mesh, *self.scale)
        #     source = vtk.vtkPolyData()
        #     new_vs_vtk = numpy_support.numpy_to_vtk(vs.numpy())
        #     cells_npy = np.column_stack(
        #         [np.full(faces.shape[0], 3, dtype=np.int64), faces.numpy().astype(np.int64)]).ravel()
        #     vs_vtk, faces_vtk = vtk.vtkPoints(), vtk.vtkCellArray()
        #     vs_vtk.SetData(new_vs_vtk)
        #     faces_vtk.SetCells(faces.shape[0], numpy_support.numpy_to_vtkIdTypeArray(cells_npy))
        #
        #     source.SetPolys(faces_vtk)
        #     self.mapper.SetInputData(source)
        #     self.is_changed = True
        #     if not self.to_init:
        #         self.to_init = True
        #         self.render.AddActor(self.actor)
        transform = vtk.vtkTransform()
        mat = vtk.vtkMatrix4x4()
        p = p * .005
        # p = p * eigen[:, None]
        for i in range(4):
            for j in range(4):
                if i > 2:
                    mat.SetElement(i, j, 0)
                elif j > 2:
                    mat.SetElement(i, j, float(mu[i]))
                    # mat.SetElement(i, j, 0)
                else:
                    mat.SetElement(i, j, p[j, i])
                # mat_t[i, j] = mat.GetElement(i,j)
        mat.SetElement(3, 3, 1)
        transform.SetMatrix(mat)
        transformFilter = vtk.vtkTransformPolyDataFilter()
        transformFilter.SetInputData(source)
        transformFilter.SetTransform(transform)
        transformFilter.Update()
        return transformFilter

    def update_gaussian(self):
        if self.disabled:
            return
        # source = self.mapper.GetInput()
        # source.SetPoints(self.init_points)
        # source = self.update_gaussian_transform(source)
        # self.mapper.SetInputConnection(source.GetOutputPort())

    def end_transition(self, transition: ui_utils.Transition) -> bool:
        if self.init_points is None:
            return False
        if transition.transition_type is ui_utils.EditType.Translating:
            self.translate(transition)
            return True
        elif transition.transition_type is ui_utils.EditType.Rotating:
            self.rotate(transition)
            return True
        elif transition.transition_type is ui_utils.EditType.Scaling:
            self.rotate(transition)
            return True
        return False

    def temporary_transition(self, transition: ui_utils.Transition) -> bool:
        if self.init_points is None:
            return False
        source = self.mapper.GetInput()
        vs = self.init_points
        if transition.transition_type is ui_utils.EditType.Translating:
            vs = vs + transition.translation[None, :]
        elif transition.transition_type is ui_utils.EditType.Rotating:
            vs = vs - transition.transition_origin[None, :]
            vs = np.einsum('ad,nd->na', transition.rotation, vs)
            vs = vs + transition.transition_origin[None, :]
        source.GetPoints().SetData(numpy_support.numpy_to_vtk(vs))
        return True

    def get_address(self):
        if self.disabled:
            return f"disabled"
        return self.actor.GetAddressAsString('')

    @staticmethod
    def add_gaussian(render, actor: Optional[vtk.vtkActor]) -> vtk.vtkActor:
        if actor is None:
            actor = vtk.vtkActor()
            mapper = vtk.vtkPolyDataMapper()
            actor.GetProperty().SetOpacity(0.3)
            actor.SetMapper(mapper)
            # source = self.get_new_gaussian()
            # init_points = source.GetPoints()
            # source = self.update_gaussian_transform(source)
            # actor, _ = ui_utils.wrap_mesh(source.GetOutput(), color)
            render.AddActor(actor)
            ui_utils.set_default_properties(actor, (1., 1., .1))
        return actor

    def replace_part(self, part_mesh: Optional[vtk.vtkPolyData]):
        if part_mesh is not None and not self.disabled:
            self.init_points = numpy_support.vtk_to_numpy(part_mesh.GetPoints().GetData())
            points = vtk.vtkPoints()
            points.SetData(numpy_support.numpy_to_vtk(self.init_points))
            part_mesh.SetPoints(points)
            self.mapper.SetInputData(part_mesh)

    def set_color(self):
        if self.disabled:
            return
        properties = self.actor.GetProperty()
        properties.SetOpacity(self.opacity)
        properties.SetColor(*self.color)

    def turn_off(self):
        if self.disabled:
            return
        self.actor.GetProperty().SetOpacity(0)
        self.actor.PickableOff()

    def turn_on(self):
        if self.disabled:
            return
        self.actor.GetProperty().SetOpacity(self.opacity)
        self.actor.PickableOn()

    @property
    def is_not_selected(self):
        return not self.is_selected

    @property
    def disabled(self):
        return self.mapper is None #or self.mapper.GetInput() is None

    def make_symmetric(self, force_include: bool):
        if self.disabled or self.twin is None or (not force_include and self.included != self.twin.included):
            return
        super(GaussianStatus, self).make_symmetric(self.twin)
        if force_include:
            self.included = self.twin.included
        self.update_gaussian()

    def apply_affine(self, button: ui_utils.Buttons, key: str):
        axis = {"left": 0, "right": 0, "up": 2, "down": 2, "a": 1, "z": 1}[key]
        sign = {"left": 1, "right": -1, "up": 1, "down": -1, "a": 1, "z": -1}[key]
        if self.disabled or button not in (ui_utils.Buttons.translate, ui_utils.Buttons.stretch, ui_utils.Buttons.rotate):
            return
        elif button is ui_utils.Buttons.translate:
            vec = np.zeros(3)
            vec[axis] = .01 * sign
            self.translate(vec)
        elif button is ui_utils.Buttons.rotate:
            self.rotate(sign * .1, axis)
        else:
            self.stretch(.01 * sign)
        self.update_gaussian()

    @property
    def opacity(self) -> float:
        if self.is_selected:
            if self.included:
                opacity = self.view_style.opacity + 0.4
            else:
                opacity = self.view_style.opacity
        else:
            if self.included:
                opacity = self.view_style.opacity + 0.2
            else:
                opacity = self.view_style.opacity
        return max(0., min(1., opacity))

    @property
    def color(self) -> Tuple[float, float, float]:
        if self.is_selected:
            return self.view_style.selected_color
        if self.included:
            return self.view_style.included_color
        else:
            return self.view_style.base_color

    def toggle_inclusion(self, included: Optional[bool] = None):
        if self.disabled:
            return
        if included is None:
            included = not self.included
        if included != self.included:
            self.included = not self.included
            self.set_color()

    def toggle_selection(self):
        if self.disabled:
            return
        self.is_selected = not self.is_selected
        self.set_color()

    def reset(self):
        if self.disabled:
            return
        super(GaussianStatus, self).reset()
        self.included = False
        self.is_selected = False
        self.set_color()
        # self.update_gaussian()

    def delete(self, render):
        if not self.disabled:
            render.RemoveActor(self.actor)
        if self.twin is not None:
            self.twin.twin = None

    @property
    def parent_id(self):
        return self.gaussian_id[0]

    @property
    def child_id(self):
        return self.gaussian_id[1]

    @property
    def mapper(self):
        if self.actor is None:
            return None
        return self.actor.GetMapper()

    def __init__(self, gaussian, gaussian_id: Tuple[int, int], is_selected: bool, view_style: ui_utils.ViewStyle,
                 render: vtk.vtkRenderer, normalized_phi: float, actor: Optional[vtk.vtkActor] = None):
        self.view_style = view_style
        super(GaussianStatus, self).__init__(gaussian)
        self.gaussian_id = gaussian_id
        self.twin: Optional[GaussianStatus] = None
        self.init_points = None
        if normalized_phi > 0.001 or actor is not None:
            self.actor = self.add_gaussian(render, actor)
            self.is_selected = is_selected
            self.included = True
            self.set_color()
        else:
            self.actor = None
            self.is_selected = False
            self.included = False

# reflection = np.eye(3) # reflection[0, 0] = -1 # self.total_translate = np.einsum('ad,d->a', reflection, other.total_translate) # self.total_rotate = np.einsum('ab,bc->ac', reflection, other.total_rotate) # p = np.einsum('da,db->ba', self.p, self.total_rotate) # copy_constructor # def replace_mesh(self, mesh: T_Mesh): #     vs, faces = mesh #     vs, faces = vs.detach().cpu(), faces.detach().cpu() #     # vs, faces = mesh_utils.scale_from_ref(mesh, *self.scale) #     source = vtk.vtkPolyData() #     new_vs_vtk = numpy_support.numpy_to_vtk(vs.numpy()) #     cells_npy = np.column_stack( #         [np.full(faces.shape[0], 3, dtype=np.int64), faces.numpy().astype(np.int64)]).ravel() #     vs_vtk, faces_vtk = vtk.vtkPoints(), vtk.vtkCellArray() #     vs_vtk.SetData(new_vs_vtk) #     faces_vtk.SetCells(faces.shape[0], numpy_support.numpy_to_vtkIdTypeArray(cells_npy)) # #     source.SetPolys(faces_vtk) #     self.mapper.SetInputData(source) #     self.is_changed = True #     if not self.to_init: #         self.to_init = True #         self.render.AddActor(self.actor) # p = p * eigen[:, None] # mat.SetElement(i, j, 0) # mat_t[i, j] = mat.GetElement(i,j) # source = self.mapper.GetInput() # source.SetPoints(self.init_points) # source = self.update_gaussian_transform(source) # self.mapper.SetInputConnection(source.GetOutputPort()) # source = self.get_new_gaussian() # init_points = source.GetPoints() # source = self.update_gaussian_transform(source) # actor, _ = ui_utils.wrap_mesh(source.GetOutput(), color) #or self.mapper.GetInput() is None # self.update_gaussian()