pyvista / pyvista-support

[moved] Please head over to the Discussions tab of the PyVista repository
https://github.com/pyvista/pyvista/discussions
60 stars 4 forks source link

3D object rotating about global axes #149

Open Aamir-M-Khan opened 4 years ago

Aamir-M-Khan commented 4 years ago

I am new to Pyvista. I am trying to rotate a box about x,y,z axes using a spherical widget. So, in the callback function, I tried the rotate_x option but it was rotating about the global axes. So, in one of the above thread , I saw that I can use the pvg.filters.RotatePoints. But its still not rotating, eventhough my data is a PolyData. I have copied the concerned part of the code below:

            _new = self.box.center_of_mass() - [0.5,0.5,0.5]
            _vec1 = np.asarray(point-_new)
            _vec2 = np.asarray(points[i,:])
            theta = angle(_vec2, _vec1)
            print(theta)
            for item in self.accelerometer:
                ret = pvg.filters.RotatePoints(origin=(0,0,0), angle=theta).apply(item)
            print(item)

The following is the complete code. For the moment, I have commented the part for y and z axes.

# -*- coding: utf-8 -*-
"""
Created on Mon Mar 30 16:26:41 2020

@author: Aamir
"""
import numpy as np
import math 
import pyvista as pv
import PVGeo as pvg
#from PVGeo.filters import RotatePoints
#from PVGeo.filters import RotationTool

#Create a unit vector
def unit_vector(vec):
    unit = vec / np.linalg.norm(vec)
    return unit

points = np.array([[0,0,0],
                   [0,0.5,0.5],
                   [0.5,0,0.5],
                   [0.5,0.5,0]])

# Find the angle between two unit vector
def angle(vec1,vec2):
    v1_unit = unit_vector(vec1)
    v2_unit = unit_vector(vec2)
    theta = (np.arccos(np.clip(np.dot(v1_unit, v2_unit), -1.0, 1.0))) * (180/np.pi)
    return theta 

class Accelerometer():
    def __init__(self,p,N):

        # Creates an accelerometer
        self.box = pv.Box()
        self.box.translate([1,1,1])
        self.box.points /= 2

        p.add_mesh(self.box,opacity = 0.3, show_edges  = True, color = "#8c8c8c")

        ray_x = pv.Line([0,0,0], [1,0,0])
        p.add_mesh(ray_x, color="r", line_width=3)
        ray_y = pv.Line([0,0,0], [0,1,0])
        p.add_mesh(ray_y, color="g", line_width=3)
        ray_z = pv.Line([0,0,0], [0,0,1])
        p.add_mesh(ray_z, color="b", line_width=3)

        self.accelerometer = [self.box,ray_x,ray_y,ray_z]
        self.N = int(N*4)

    def translation(self,point):
        _new = point - self.box.center_of_mass() + [0.5, 0.5, 0.5]
        for item in self.accelerometer:
            item.translate(_new)

        for i in range(4):
            p.sphere_widgets[self.N + i].SetCenter(_new + np.asarray(p.sphere_widgets[self.N + i].GetCenter()))

    def callback(self,point, i):
        print(i,point)
        # 3D translation in space
        if i == 0:

            _new = point - self.box.center_of_mass() + [0.5,0.5,0.5]

            for item in self.accelerometer:
                item.translate(_new)

            for k in range(3):
                p.sphere_widgets[self.N+1+k].SetCenter(_new + np.asarray(p.sphere_widgets[self.N+1+k].GetCenter()))

        #elif i == 1:
        else:
        # Rotation
            _new = self.box.center_of_mass() - [0.5,0.5,0.5]

            _vec1 = np.asarray(point-_new)
            _vec2 = np.asarray(points[i,:])
            theta = angle(_vec2, _vec1)
            print(theta)

            for item in self.accelerometer:

                ret = pvg.filters.RotatePoints(origin=(0,0,0), angle=theta).apply(item)
            print(item)

           # for k in range(3):
            #    p.sphere_widgets[self.N+1+k].SetCenter( points[1+k,:])
        """       
        elif i == 2:
            # Rotation
            _new = self.box.center_of_mass() - [0.5,0.5,0.5]

            _vec1 = np.asarray(point-_new)
            _vec2 = np.asarray(points[2,:])

            theta = angle(_vec1, _vec2)
            print(theta)

            for item in self.accelerometer:
                RotatePoints(origin=(0,0,0), angle=theta).apply(item)

            for k in range(3):
                p.sphere_widgets[self.N+1+k].SetCenter( points[1+k,:])

        elif i == 3:
            # Rotation
            _new = self.box.center_of_mass() - [0.5,0.5,0.5]

            _vec1 = np.asarray(point-_new)
            _vec2 = np.asarray(points[3,:])

            theta = angle(_vec1, _vec2)
            print(theta)

            for item in self.accelerometer:
               # RotatePoints(origin=(0,0,0), angle=theta).apply(item)

                rotated_points = RotationTool.rotate_around(dataset.item, -30.0, (0,0,0))

            for k in range(3):
                p.sphere_widgets[self.N+1+k].SetCenter(_new + points[1+k,:])

        """

if __name__ == '__main__':

    # the option Background plotter helps to manipulate in the real time
    p = pv.BackgroundPlotter()
    p.background_color = "#D4D4D4"
    p.show()

    for i in range(3):
        _gg = Accelerometer(p, i)
        p.add_sphere_widget(_gg.callback, center=points,theta_resolution=360, phi_resolution=360, color=["k", "r", "g", "b"], radius=0.05)
        _gg.translation(np.random.random(3)*10)
banesullivan commented 4 years ago

So what are you doing with the rotated mesh returned from the RotatePoints filter? Try updating the mesh in the scene with the rotated result: Try item.shallow_copy(ret)

Also, you set the origin argument of the RotatePoints to (0,0,0) which would be the same as the rotation around the global axis. Try: origin=item.bounds[0::2]

I'd like to help you get this working, but there are a lot of moving parts and I'm not exactly sure what you are trying to do...

Aamir-M-Khan commented 4 years ago

Thanks a lot for your suggestions. This would work something like this. I would import a stl file (I will add the code for this later on). Then, I will place these accelerometers on the assembly (imported in the form of the stl). For properly placing these sensors, I would require to translate and rotate them. This part of code is for that purpose. I have tried to incorporate your suggestions to my code, but this some how terminates the program. I am attaching the code again , along with some comments. Maybe, I haven't understood your suggestions correctly. PYvista.txt

akaszynski commented 4 years ago

Here's a trimmed down version of your code that works slightly better with less functionality:

# -*- coding: utf-8 -*-
"""
Created on Mon Mar 30 16:26:41 2020

@author: Aamir
"""

import numpy as np
import math 
import pyvista as pv
import PVGeo as pvg
#from PVGeo.filters import RotatePoints
#from PVGeo.filters import RotationTool

#Create a unit vector
def unit_vector(vec):
    unit = vec / np.linalg.norm(vec)
    return unit

points = np.array([[0,0,0],
                   [0,0.5,0.5],
                   [0.5,0,0.5],
                   [0.5,0.5,0]])

# Find the angle between two unit vector
def angle(vec1,vec2):
    v1_unit = unit_vector(vec1)
    v2_unit = unit_vector(vec2)
    theta = (np.arccos(np.clip(np.dot(v1_unit, v2_unit), -1.0, 1.0))) * (180/np.pi)
    return theta 

# Create an accelerometer class
class Accelerometer():
    def __init__(self,p,N):

        # Creates an accelerometer
        self.box = pv.Box()
        self.box.translate([1,1,1])
        self.box.points /= 2

        p.add_mesh(self.box,opacity = 0.3, show_edges  = True, color = "#8c8c8c")

        # Create x,y,z axis for the accelermoter box
        ray_x = pv.Line([0,0,0], [1,0,0])
        p.add_mesh(ray_x, color="r", line_width=3)
        ray_y = pv.Line([0,0,0], [0,1,0])
        p.add_mesh(ray_y, color="g", line_width=3)
        ray_z = pv.Line([0,0,0], [0,0,1])
        p.add_mesh(ray_z, color="b", line_width=3)

        self.accelerometer = [self.box,ray_x,ray_y,ray_z]
        self.N = int(N*4)

    # Create a function to translate the items of an acceleromter    
    def translation(self,point):
        _new = point - self.box.center_of_mass() + [0.5, 0.5, 0.5]
        for item in self.accelerometer:
            item.translate(_new)

        for i in range(4):
            p.sphere_widgets[self.N + i].SetCenter(_new + np.asarray(p.sphere_widgets[self.N + i].GetCenter()))

   # A callback function to be called on clicking any of the four sphere widgets
    def callback(self, point, *args):
        print(point, *args)
        # 3D translation in space (on clicking the black widget)
        # if i == 0:

        _new = point - self.box.center_of_mass() + [0.5,0.5,0.5]

        for item in self.accelerometer:
            item.translate(_new)

        for k in range(3):
            p.sphere_widgets[self.N+1+k].SetCenter(_new + np.asarray(p.sphere_widgets[self.N+1+k].GetCenter()))

        # # If red widget is clicked (rotation of the accelerometer block about x axis)
        # elif i == 1:
        # # Rotation
        #     _new = self.box.center_of_mass() - [0.5,0.5,0.5]

        #     _vec1 = np.asarray(point-_new)
        #     _vec2 = np.asarray(points[1,:])
        #     theta = angle(_vec2, _vec1)
        #     #print(theta)

        #     for item in self.accelerometer:
        #         origin = item.bounds[0::2]
        #         pvg.filters.RotatePoints(origin, theta).apply(item)

        #         for k in range(3):

        #             p.sphere_widgets[self.N+1+k].SetCenter( points[1+k,:])

        # # If green widget is clicked (rotation of the accelerometer block about y axis)      
        # elif i == 2:
        #     # Rotation
        #     _new = self.box.center_of_mass() - [0.5,0.5,0.5]

        #     _vec1 = np.asarray(point-_new)
        #     _vec2 = np.asarray(points[2,:])

        #     theta = angle(_vec1, _vec2)
        #     print(theta)

        #     for item in self.accelerometer:
        #         origin=item.bounds[0::2]
        #         pvg.filters.RotatePoints(origin, theta).apply(item)

        #     for k in range(3):
        #         p.sphere_widgets[self.N+1+k].SetCenter( points[1+k,:])

        # # If blue widget is clicked (rotation of the accelermeter block about z axis)   
        # elif i == 3:
        #     # Rotation
        #     _new = self.box.center_of_mass() - [0.5,0.5,0.5]

        #     _vec1 = np.asarray(point-_new)
        #     _vec2 = np.asarray(points[3,:])

        #     theta = angle(_vec1, _vec2)
        #     print(theta)

        #     for item in self.accelerometer:
        #         origin=item.bounds[0::2]
        #         pvg.filters.RotatePoints(origin, theta).apply(item)

        #     for k in range(3):
        #         p.sphere_widgets[self.N+1+k].SetCenter(_new + points[1+k,:])

if __name__ == '__main__':

    # the option Background plotter helps to manipulate in the real time
    p = pv.BackgroundPlotter()
    p.background_color = "#D4D4D4"
    p.show()

    # Changing the value of range, changes the number of accelerometer
    # for i in range(3):
    _gg = Accelerometer(p, 0)
    p.add_sphere_widget(_gg.callback, center=points, color=["k", "r", "g", "b"], radius=0.05)
        # _gg.translation(np.random.random(3)*10)

The issue you're having is the sphere widget callback only returns one parameter (the point). You won't be able to access i as it's not sent back. It's still sorta hard to tell what should happen here, but this might help you get started in the right direction.

Aamir-M-Khan commented 4 years ago

Thanks for your suggestions. Actually, I am able to access both points and integer i. Now, I am also able to rotate about the z axis using the pvg.Rotation point filter, but this only works for z axis and doesn't accounts for the x and y axis (as it has been coded that way). Is there any way of multiplying the polydata components? I was wondering of using the pvg.filters.Rotationtool.rotationmatrix to obtain a rotation matrix and then multiply it to my polydata, so that I can rotate the box.

Aamir-M-Khan commented 4 years ago

I am able to perform the required rotation operation, provided I perform it at the global origin. So, for each rotation operation, I was thinking of applying the following procedure:

  1. Translate the local origin of the box to global origin using the translate function.
  2. Then, performing the required rotation at the origin.
  3. Translate the box back from the global origin to the local origin of the box.

All these operations would happen in the back on clicking the widgets.

for item in self.accelerometer: t = item.translate([0,0,0]) w = t.rotate_x(theta) w.translate([_new])

But the issue that I am facing is that after the first translation, I receive an error that t is not a Polydata. Is there any way of retaining the polydata feature of item after it has been translated?

Thanks