shineworld 73 Posted March 3 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
DelphiUdIT 145 Posted March 3 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
JonRobertson 63 Posted March 3 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: 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. 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. https://github.com/yunkot/HeaderParser https://github.com/neslib/Chet There are others. Search engines are your friends. ;) Well, most of the time. 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. https://github.com/pyscripter/python4delphi Share this post Link to post
shineworld 73 Posted March 3 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. Share this post Link to post
DelphiUdIT 145 Posted March 3 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
stijnsanders 35 Posted March 3 I have never done much 3D (which I regret), I haven't done any Vulkan, but I just happen to remember something from when I was trying to port the old pixelcity to Delphi, that the wglCreateContext and wglMakeCurrent calls are used for more than only OpenGL. Could it be that they are the key to get Vulkan working with VCL? Share this post Link to post
JonRobertson 63 Posted March 3 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. Share this post Link to post
shineworld 73 Posted March 4 Perhaps the solution is to watch what VTK do with tkinter to implement the vtkTkRenderWindowInteractor. I will study and try to do a close thing with a compatible DelphiVCL Windows based component. https://github.com/Kitware/VTK/blob/master/Wrapping/Python/vtkmodules/tk/vtkTkRenderWindowInteractor.py Share this post Link to post
shineworld 73 Posted June 20 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() 2 Share this post Link to post
shineworld 73 Posted June 21 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
shineworld 73 Posted June 24 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
pyscripter 612 Posted June 24 (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 June 24 by pyscripter Share this post Link to post
shineworld 73 Posted June 24 (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 June 24 by shineworld 1 Share this post Link to post