Spaces:
Sleeping
Sleeping
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) | |
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 | |
def mu_baked(self) -> ARRAY: | |
return self.mu + self.total_translate | |
def phi(self) -> float: | |
return self.data[0] | |
def mu(self) -> ARRAY: | |
return self.data[1] | |
def eigen(self) -> ARRAY: | |
return self.data[2] | |
def p(self) -> ARRAY: | |
return self.data[3] | |
def mu(self, new_mu: ARRAY): | |
self.data[1] = new_mu | |
def p(self, new_p: ARRAY): | |
self.data[3] = new_p | |
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) | |
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('') | |
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() | |
def is_not_selected(self): | |
return not self.is_selected | |
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() | |
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)) | |
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 | |
def parent_id(self): | |
return self.gaussian_id[0] | |
def child_id(self): | |
return self.gaussian_id[1] | |
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 | |