Jump to content
shineworld

VTK in DelphiVCL or DelphiFMX

Recommended Posts

Hi all.
Recently I've been using VTK to manage 3D Views.
Unfortunately, I've found only info on how to use it with QT Pyside6 with QVTKRenderWindowInteractor.

I will hope to use DelphiVCL (my first choice) or DelphiFMX + VTK.
Do you have any idea on how to use VTK in a TForm or any other Windows-based window object?

 

Share this post


Link to post

I don't know VTK, but I don't think that is a revolutionary approach to "surface" drawing. In the web nothing about Pascal, Delphi or FPC related is available.

Surely it's needed a handle to a graphic window. And should be a function that assign this.

 

Start from here. If there is something about "C" or "MFC" sample, will be available usefull info.

Share this post


Link to post

I have not heard of VTK and decided to take a peak. The documentation references examples using MFC and C++ Builder. However, the Win32 folder is no longer present in the Examples\GUI folder in their git repo. Searching the history of the repo, the C++ Builder examples were removed five years ago. I suspect getting support for using VTK in Delphi will be challenging, since the group stopped supporting C++ Builder years ago. That said, the old code is available via git history. For example, vtkBorlandRenderWindow.cpp. This snippet is from GetRenderWindow:

 

    // Stuff the renderwindow into our window
    FRenderWindow = vtkWin32OpenGLRenderWindow::New();
    FRenderWindow->AddObserver( vtkCommand::AbortCheckEvent, FAbortCallback);
    FRenderWindow->SetParentId(Parent->Handle);

Without a deep dive into the source, it appears VTK is using OpenGL for rendering and setting the OpenGL's window parent to a form.

 

If I wanted to use VTK from Delphi, I would attempt:

  1. Use a supported open source C++ compiler to build the C++ source to either OBJ or LIB. There are multiple OBJ file formats. Delphi can link with OMF86 or COFF for 32-bit and ELF or COFF for 64-bit. See Link object file (Delphi) for more details.
  2. Translate the VTK headers to Pascal, which will be a challenge. From what little I have read, VTK appears to use features only available in newer versions of C++. There are some open source projects for converting headers, although they may not be able to convert the VTK headers. The second one specifically does not convert C++ headers. I included it because it uses libclang, which I suspect would provide a better translation for the "pure C" portions of headers.
    1. https://github.com/yunkot/HeaderParser
    2. https://github.com/neslib/Chet
    3. There are others. Search engines are your friends. ;) Well, most of the time.
  3. Another alternative is Python4Delphi, since VTK can be used from Python and appears to actively support use from Python. An advantage of this approach is the ability to "prototype" using standalone Python and then incorporate into a Delphi app via Python4Delphi. You have likely seen Pyscripter's posts in this forum.
    1. https://github.com/pyscripter/python4delphi

Share this post


Link to post

Yes, maybe I explained myself wrongly.
I am using Python + VTK + QT.
VTK is a very good Python library for working with 3D scenes.
QT is terrible for me and I love DelphiVCL e DelphiFMX UI for Python.
Unfortunately, to do the rest of the interface, I had to switch from Python4Delphi (DelphiVCL or DelphiFMX) to QT precisely because

there is a lack of coverage to OpenGL in them, and however, found no way to integrate VTK to DelphiVCL/DelphiFM.

So I was asking if anyone had already done this or could point me to how to create a VTK component that would appear in an area

of a Python DelphiVCL TForm, as in the example in the picture below.

 

 

image.thumb.png.e7dcddcf7a0dc60333a8623b0e8f791f.png

Share this post


Link to post
2 hours ago, shineworld said:

I am using Python + VTK + QT.

LOL, I miss we are in a P4D section, sorry. I cannot help you , I never used Python4Delphi.

Share this post


Link to post
2 hours ago, DelphiUdIT said:

LOL, I miss we are in a P4D section, sorry.

Same LOL here. My first reply would have been much shorter had I paid attention to the forum. One of the dangers of reading from Recent Activity, I suppose. :classic_wink:

Share this post


Link to post

The code is a real crap, I'm just trying the right way to get the thing working,
but Python + DelphiVCL + VTK looks like it could go.
 

from delphivcl import *

from vtkmodules.vtkRenderingCore import vtkRenderWindow
from vtkmodules.vtkRenderingUI import vtkGenericRenderWindowInteractor

BASE_CLASS_NAME = WinControl

class vclVTKRenderWindowInteractor(BASE_CLASS_NAME):
    def __init__(self, owner):
        BASE_CLASS_NAME.__init__(self, owner)
        self._RenderWindow = None
        self._Iren = None

    def __getattr__(self, attr):
        """Makes the object behave like a vtkGenericRenderWindowInteractor"""
        if attr == '__vtk__':
            return lambda t=self._Iren: t
        elif hasattr(self._Iren, attr):
            return getattr(self._Iren, attr)
        else:
            raise AttributeError(self.__class__.__name__ + " has no attribute named " + attr)

    def GetRenderWindow(self):
        if self._RenderWindow is None:
            hwnd = self.Handle
            self._RenderWindow = vtkRenderWindow()
            self._RenderWindow.SetWindowInfo(str(int(hwnd)))

            self._Iren = vtkGenericRenderWindowInteractor()
            self._Iren.SetRenderWindow(self._RenderWindow)

        return self._RenderWindow

    def Render(self):
        self.update()

def vclVTKRenderWindowInteractorConeExample():
    """A simple example that uses the vclVTKRenderWindowInteractor class."""

    from vtkmodules.vtkFiltersSources import vtkConeSource
    from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
    import vtkmodules.vtkRenderingOpenGL2
    import vtkmodules.vtkInteractionStyle

    class MainView(Form):
        def __init__(self, owner):
            self.vtk_panel = vclVTKRenderWindowInteractor(self)
            self.vtk_panel.Parent = self
            self.vtk_panel.Align = 'alClient'
            self.vtk_panel.AlignWithMargins = True
            self.vtk_panel.BevelInner = 'bvNone'
            self.vtk_panel.BevelKind = 'bkFlat'
            self.vtk_panel.BevelOuter = 'bvNone'
            self.vtk_panel.Color = clCream
            self.vtk_panel.ParentBackground = False
            self.vtk_panel.Caption = 'Hello World!'

            # set main form events handlers
            self.OnClose = self.__on_form_close

        def __on_form_close(self, sender, action):
            if sender is None:
                return

            if sender == self:
                action.Value = caFree

    # initialize application
    Application.Initialize()
    Application.Title = ""

    # create and show view
    view = MainView(Application)
    try:
        def on_timer(Sender):
            coneActor.RotateX(10)
            coneActor.RotateY(5)
            coneActor.RotateY(2.5)
            widget.GetRenderWindow().Render()
            timer.interval = 100

        view.Show()

        widget = view.vtk_panel

        ren = vtkRenderer()
        widget.GetRenderWindow().AddRenderer(ren)

        cone = vtkConeSource()
        cone.SetResolution(8)

        coneMapper = vtkPolyDataMapper()
        coneMapper.SetInputConnection(cone.GetOutputPort())

        coneActor = vtkActor()
        coneActor.SetMapper(coneMapper)

        ren.AddActor(coneActor)

        widget._Iren.Initialize()  # Initialize the interactor
        widget._Iren.Start()       # Start the interactor

        timer = Timer(None)
        timer.Interval = 1000
        timer.OnTimer = on_timer

        FreeConsole()
        Application.Run()
    finally:
        view.Destroy()

if __name__ == '__main__':
    vclVTKRenderWindowInteractorConeExample()

 

 


 

  • Like 2

Share this post


Link to post

A few small steps forward...

 

Now VTK is displaying quite well but putrously I still haven't quite figured out if CustomControl is the right "hook" point for VTK.

VTK requires docking to a control with Handle.

 

But I assume Paint's native handling of the control should come disabled.

 

Also, CustomControl (TCustomControl) lacks OnMouseXXX events to capture events to send to VTK's iterator.

Perhaps the only solution is to add a control derived from TWindowControl to DelphiVCL that would accommodate what VTK requires for integration.


In the video the comparison of the same code in DelphiVCL and PySide6 (QT), which I want to get rid of as soon as possible.

 

 

Share this post


Link to post

To fully integrate VTK I had to modify the DelphiVCL sources of Python4Delphi by adding a new VTKPanel class inherited from TCustomControl that exposes OnMouseDown, OnMouseMove, OnMouseUp, OnMouseWheel, OnResize, and OnPaint:

{***
 *  TAKE CARE
 *  =========
 *  This units add extra components to integrate VTK.
 *
 **}

{$I ..\Definition.Inc}

unit WrapExtVTK;

interface

uses
  System.Classes,
  System.TypInfo,
  System.SysUtils,
  Winapi.Windows,
  Vcl.Controls,
  Vcl.Graphics,
  Vcl.StdCtrls,

  WrapDelphi,
  PythonEngine,
  WrapVclControls,
  WrapDelphiClasses;

type
  TVTKPanel = class(TCustomControl)
  private
    FOnPaint: TNotifyEvent;
  protected
    procedure Paint; override;
  published
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
  published
    property OnMouseDown;       // TMouseEvent
    property OnMouseMove;       // TMouseMoveEvent
    property OnMouseUp;         // TMouseEvent
    property OnMouseWheel;      // TMouseWheelEvent
    property OnResize;          // TNotifyEvent
  end;

  TPyDelphiVTKPanel = class (TPyDelphiControl)
  private
    function  GetDelphiObject: TCustomControl;
    procedure SetDelphiObject(const Value: TCustomControl);
  protected
  public
    class function  DelphiObjectClass : TClass; override;
    // Properties
    property DelphiObject: TCustomControl read GetDelphiObject write SetDelphiObject;
  end;

implementation

{ TVTKPanel }

procedure TVTKPanel.Paint;
begin
  inherited;

  if Assigned(FOnPaint) then
    FOnPaint(Self);
end;

{ TPyDelphiVTKPanel }

class function TPyDelphiVTKPanel.DelphiObjectClass: TClass;
begin
  Result := TVTKPanel;
end;

function TPyDelphiVTKPanel.GetDelphiObject: TCustomControl;
begin
  Result := TVTKPanel(inherited DelphiObject);
end;

procedure TPyDelphiVTKPanel.SetDelphiObject(const Value: TCustomControl);
begin
  inherited DelphiObject := Value;
end;

{ register the wrappers, the globals and the constants }

type
  TThirdPartiesCtrlsRegistration = class(TRegisteredUnit)
  public
    function Name : string; override;
    procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;
    procedure DefineFunctions(APyDelphiWrapper : TPyDelphiWrapper); override;
    procedure DefineVars(APyDelphiWrapper : TPyDelphiWrapper); override;
  end;

{ TThirdPartiesCtrlsRegistration }

procedure TThirdPartiesCtrlsRegistration.DefineFunctions(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;
end;

procedure TThirdPartiesCtrlsRegistration.DefineVars(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;
end;

function TThirdPartiesCtrlsRegistration.Name: string;
begin
  Result := 'ThirdPartiesVTK';
end;

procedure TThirdPartiesCtrlsRegistration.RegisterWrappers(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;

  APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiVTKPanel);
end;

initialization
  RegisteredUnits.Add(TThirdPartiesCtrlsRegistration.Create);
  System.Classes.RegisterClasses([TVTKPanel]);

end.

At this point now mouse management and window resizing works well,
although during Resize there is a small instant where you see the white container window.

from DelphiVCL import *

from vtkmodules.vtkRenderingCore import vtkRenderWindow
from vtkmodules.vtkRenderingUI import vtkGenericRenderWindowInteractor

class vclVTKRenderWindowInteractor(VTKPanel):
    """
    A vclVTKRenderWindowInteractor for Python and DelphiVCL.

    Uses a vtkGenericRenderWindowInteractor to handle the interactions.
    Use GetRenderWindow() to get the vtkRenderWindow.
    """

    def __init__(self, owner):
        super().__init__(self, owner)

        # set default member values
        self._render_window = None
        self._render_interactor = None
        self._render_interactor_timer = None

        # assign control events
        self.OnMouseDown = self._on_mouse_down
        self.OnMouseMove = self._on_mouse_move
        self.OnMouseWheel = self._on_mouse_wheel
        self.OnMouseUp = self._on_mouse_up
        self.OnPaint = self._on_paint
        self.OnResize = self._on_resize

    def __getattr__(self, attr):
        """Makes the object behave like a vtkGenericRenderWindowInteractor"""
        if attr == '__vtk__':
            return lambda t=self._render_interactor: t
        elif hasattr(self._render_interactor, attr):
            return getattr(self._render_interactor, attr)
        else:
            raise AttributeError(self.__class__.__name__ + " has no attribute named " + attr)

    def _on_mouse_down(self, Sender, Button, Shift, X, Y):
        ctrl = 'ssCtrl' in Shift
        shift = 'ssShift' in Shift
        self._render_interactor.SetEventInformationFlipY(X, Y, ctrl, shift)
        if Button == 0:
            self._render_interactor.InvokeEvent("LeftButtonPressEvent")
        elif Button == 1:
            self._render_interactor.InvokeEvent("RightButtonPressEvent")
        elif Button == 2:
            self._render_interactor.InvokeEvent("MiddleButtonPressEvent")

    def _on_mouse_move(self, Sender, Shift, X, Y):
        ctrl = 'ssCtrl' in Shift
        shift = 'ssShift' in Shift
        self._render_interactor.SetEventInformationFlipY(X, Y, ctrl, shift, chr(0), 0, None)
        self._render_interactor.MouseMoveEvent()

    def _on_mouse_up(self, Sender, Button, Shift, X, Y):
        ctrl = 'ssCtrl' in Shift
        shift = 'ssShift' in Shift
        self._render_interactor.SetEventInformationFlipY(X, Y, ctrl, shift)
        if Button == 0:
            self._render_interactor.InvokeEvent("LeftButtonReleaseEvent")
        elif Button == 1:
            self._render_interactor.InvokeEvent("RightButtonReleaseEvent")
        elif Button == 2:
            self._render_interactor.InvokeEvent("MiddleButtonReleaseEvent")

    def _on_mouse_wheel(self, Sender, Shift, WheelDelta, MousePos, Handled):
        x = MousePos.X
        y = MousePos.Y
        ctrl = 'ssCtrl' in Shift
        shift = 'ssShift' in Shift
        self._render_interactor.SetEventInformationFlipY(x, y, ctrl, shift, chr(0), 0, None)
        if WheelDelta > 0:
          self._render_interactor.MouseWheelForwardEvent()
        else:
          self._render_interactor.MouseWheelBackwardEvent()
        Handled.Value = True

    def _on_resize(self, Sender):
        width = Sender.ClientWidth
        height = Sender.ClientHeight
        if self._render_window:
            self._render_window.SetSize(width, height)
            self._render_interactor.ConfigureEvent()
            self.Repaint()

    def _on_paint(self, Sender):
        pass
        if not self._render_interactor is None:
            self._render_interactor.Render()

    # == BEG: xxx
    #

    def GetRenderWindow(self):
        if self._render_window is None:
            # create render window and assign handle of this component
            hwnd = self.Handle
            self._render_window = vtkRenderWindow()
            self._render_window.SetWindowInfo(str(int(hwnd)))

            # create render interactor as generic render window interactor
            self._render_interactor = vtkGenericRenderWindowInteractor()
            self._render_interactor.SetRenderWindow(self._render_window)

            # add render interactor timer events to the observer
            self._render_interactor.AddObserver('CreateTimerEvent', self.CreateTimer)
            self._render_interactor.AddObserver('DestroyTimerEvent', self.DestroyTimer)
        return self._render_window

    def Render(self):
        self.update()

    def CreateTimer(self, obj, evt):
        if self._render_interactor_timer is None:
            self._render_interactor_timer = Timer(self)
            self._render_interactor_timer.Enabled = False
            self._render_interactor_timer.Interval = 10
            self._render_interactor_timer.OnTimer = self.TimerEvent
        self._render_interactor_timer.Enabled = True

    def DestroyTimer(self, obj, evt):
        self._render_interactor_timer.Enabled = False
        return 1

    def TimerEvent(self, Sender):
        if not self._render_interactor is None:
            self._render_interactor.TimerEvent()

    #
    # == END: xxx

def vclVTKRenderWindowInteractorConeExample():
    """A simple example that uses the vclVTKRenderWindowInteractor class."""

    from vtkmodules.vtkFiltersSources import vtkConeSource
    from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
    import vtkmodules.vtkRenderingOpenGL2
    import vtkmodules.vtkInteractionStyle

    class MainView(Form):
        def __init__(self, owner):
            super().__init__(owner)
            self.vtk_panel = vclVTKRenderWindowInteractor(self)
            self.vtk_panel.Parent = self
            self.vtk_panel.Align = 'alClient'
            self.vtk_panel.AlignWithMargins = True

            # set default control values
            self.DoubleBuffered = True

            # set main form events handlers
            self.OnClose = self.__on_form_close

        def __on_form_close(self, sender, action):
            if sender is None:
                return

            if sender == self:
                action.Value = caFree

    # initialize application
    Application.Initialize()
    Application.Title = ""

    # create and show view
    view = MainView(Application)
    try:
        def on_timer(Sender):
            coneActor.RotateX(5)
            coneActor.RotateY(2.5)
            coneActor.RotateY(1)
            widget.GetRenderWindow().Render()
            timer.interval = 100

        view.Show()

        widget = view.vtk_panel

        ren = vtkRenderer()
        widget.GetRenderWindow().AddRenderer(ren)

        cone = vtkConeSource()
        cone.SetResolution(8)

        coneMapper = vtkPolyDataMapper()
        coneMapper.SetInputConnection(cone.GetOutputPort())

        coneActor = vtkActor()
        coneActor.SetMapper(coneMapper)

        ren.AddActor(coneActor)

        # initialize and start render interactor
        widget.Initialize()
        widget.Start()

        timer = Timer(None)
        timer.Interval = 1000
        timer.OnTimer = on_timer

        FreeConsole()
        Application.Run()
    finally:
        view.Destroy()

if __name__ == '__main__':
    vclVTKRenderWindowInteractorConeExample()

Final result:


Unfortunately VTKPanel will be available only with a custom DelphiVCL Python.
Will be interesting to have a native control which does same behaviour aka ExternControl or similar name to manage Python objects can work with a windowed delphivcl control using window handle.

Share this post


Link to post
Posted (edited)
20 minutes ago, shineworld said:

that exposes OnMouseDown, OnMouseMove, OnMouseUp, OnMouseWheel, OnResize, and OnPaint

WrapDelphi now automatically exposes all events.   However, there has been no release of delphivcl recently incorporating these changes.  If you create a custom delphivcl using the latest sources you do not need your custom panel.  Panel should expose the above events.

Edited by pyscripter

Share this post


Link to post
Posted (edited)
11 minutes ago, pyscripter said:

WrapDelphi now automatically exposes all events.   However, there has been no release of delphivcl recently incorporating these changes.

OK, I can understand the reasons for this.

In my suite I will deploy a custom delphivcl renamed with a different name to avoid any collision with the native version of Embarcadero and installed in python as an offline whl package.

The important thing for me was the ability to REMOVE the use of QT and be able to use the VTK 3D management package.
At 90% I only program with Delphi, and Python + DelphiVCL + VTK is only for me to create support and research programs.

At the end of it all I posted my solution so if anyone else finds themselves in need of using DelphiVCL + VTK they have a starting point for doing so.

Edited by shineworld
  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×