marcomusy / vedo

A python module for scientific analysis of 3D data based on VTK and Numpy
https://vedo.embl.es
MIT License
2.05k stars 266 forks source link

Arrow object's top point #1163

Closed smoothumut closed 4 months ago

smoothumut commented 4 months ago

Hi Marco, I have been working with Arrows and I have noticed that in vedo >= 5.0 there is no tip_point for Arrow object as there was in previous versions. there is self.base and self.top but they are not changing when a transformation is applied to the Arrow object In the example below you can see it

    from vedo import Plane, show, Arrow, Point, Group

    plane = Plane(pos=(0, 0, 0), normal=(0, 0, 1))

    center = plane.center

    arrow = Arrow(center, center + plane.normal, c='red')

    print("arrow.top",arrow.top)

    arrow.rotate(30)

    print("arrow.top",arrow.top)

    show(plane, arrow,axes=3)

May be I am missing something, but If you think this is a problem and need to be fixed, I can modify the Arrow code as I show below. This fix solved my issues in my code Please let me know if you want a PR for this

    class Arrow(Mesh):
        """
        Build a 3D arrow from `start_pt` to `end_pt` of section size `s`,
        expressed as the fraction of the window size.
        """

        def __init__(
            self,
            start_pt=(0, 0, 0),
            end_pt=(1, 0, 0),
            s=None,
            shaft_radius=None,
            head_radius=None,
            head_length=None,
            res=12,
            c="r4",
            alpha=1.0,
        ) -> None:
            """
            If `c` is a `float` less than 1, the arrow is rendered as a in a color scale
            from white to red.

            .. note:: If `s=None` the arrow is scaled proportionally to its length

            ![](https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/GeometricObjects/TestOrientedArrow.png)
            """
            # in case user is passing meshs
            if isinstance(start_pt, vtki.vtkActor):
                start_pt = start_pt.GetPosition()
            if isinstance(end_pt, vtki.vtkActor):
                end_pt = end_pt.GetPosition()

            axis = np.asarray(end_pt) - np.asarray(start_pt)
            length = float(np.linalg.norm(axis))
            if length:
                axis = axis / length
            if len(axis) < 3:  # its 2d
                theta = np.pi / 2
                start_pt = [start_pt[0], start_pt[1], 0.0]
                end_pt = [end_pt[0], end_pt[1], 0.0]
            else:
                theta = np.arccos(axis[2])
            phi = np.arctan2(axis[1], axis[0])
            self.source = vtki.new("ArrowSource")
            self.source.SetShaftResolution(res)
            self.source.SetTipResolution(res)

            if s:
                sz = 0.02
                self.source.SetTipRadius(sz)
                self.source.SetShaftRadius(sz / 1.75)
                self.source.SetTipLength(sz * 15)

            if head_length:
                self.source.SetTipLength(head_length)
            if head_radius:
                self.source.SetTipRadius(head_radius)
            if shaft_radius:
                self.source.SetShaftRadius(shaft_radius)

            self.source.Update()

            t = vtki.vtkTransform()
            t.Translate(start_pt)
            t.RotateZ(np.rad2deg(phi))
            t.RotateY(np.rad2deg(theta))
            t.RotateY(-90)  # put it along Z
            if s:
                sz = 800 * s
                t.Scale(length, sz, sz)
            else:
                t.Scale(length, length, length)

            tf = vtki.new("TransformPolyDataFilter")
            tf.SetInputData(self.source.GetOutput())
            tf.SetTransform(t)
            tf.Update()

            super().__init__(tf.GetOutput(), c, alpha)

            self.transform = LinearTransform().translate(start_pt)
            # self.pos(start_pt)

            self.phong().lighting("plastic")
            self.actor.PickableOff()
            self.actor.DragableOff()
            self.base = np.array(start_pt, dtype=float)  # used by pyplot
            self.top  = np.array(end_pt,   dtype=float)  # used by pyplot
            self.top_index = None  #### I have commented 
            result = utils.closest(end_pt,self.vertices, return_ids=True)     #### I have added 
            self.top_index = result[1]     ### I have added
            self.fill = True                    # used by pyplot.__iadd__()
            self.s = s if s is not None else 1  # used by pyplot.__iadd__()
            self.name = "Arrow"

    #### I have added this method from previous versions
        def top_point(self, return_index=False):
            """Return the coordinates of the tip of the Arrow, or the point index."""
            if return_index:
                return self.top_index
            return self.vertices[self.top_index]
marcomusy commented 4 months ago

I think I have got a faster way of doing it - just pushed it to master.

from vedo import Plane, show, Arrow

plane = Plane()
arrow = Arrow(plane.center, plane.center+plane.normal, res=11)

arrow.rotate_x(30).shift([0.3, 0.2, 0.1]).print()
print("arrow.base", arrow.base, arrow.base_point())
print("arrow.top ", arrow.top,  arrow.top_point(), arrow.top_index)

show(plane, arrow, arrow.labels("id"), axes=1)

image

smoothumut commented 4 months ago

thank you very much 👍 I am going to change mine in my code too