🙏 Feature Request - Rhino.UI.ViewportControl - Full Featured Viewport

Hello,

To whom it may concern at McNeel (@curtisw, @stevebaer, @dale, @Gijs, @CallumSykes) can you please consider expanding the capabilities of the Rhino.UI.ViewportControl available in Eto.Forms to the full feature set of arguments available with a Rhino.View/Viewport.


Here’s a hacky example of what I would like… Obviously the “Eto UI visuals” of this example are ugly and I’m doing something really stupid to “psuedo embed it” but I’m making the point that they can be custom and whatever the developer wants to show via Eto would be possible both for the window/border itself and any UI elements on top of said viewport.

This hacky example spawns a floating Rhino.Viewport that “follows” the Eto.Form.
One cannot turn off the title bar/window style of the floating Rhino.Viewport like you can with a Rhino.UI.ViewportControl. (If the Rhino.View is set to floating = False, it does not show this title bar but then of course it’s “not floating”)


Examples of expanded capabilities:

-set display mode (can already do that with a VPC)
-set “WindowStyle” of the viewport (Default, Utility, NONE, etc.)
-run Rhino commands (since it’s a regular viewport… command line can have focus)
-use any rhino tool
-navigation controls that match the main Rhino instance (VPC seems to have hardcoded navigation mouse methods?)
-etc.


Who cares?
Well… the Rhino.UI.ViewportControl is great to have but really limited in that it is more of a “viewer”.

Having the ability to embed a fully featured Rhino.Viewport INTO an Eto.Form opens up so many great possibilities for Rhino Development.


Some more forum topics & reading on this subject:


Hacky Python Code:

#! python3

import Rhino
import Eto.Forms as forms
import Eto.Drawing as drawing
import System.Drawing as sdrawing


class MyEtoForm(forms.Form):
    def __init__(self):
        super().__init__()
        self.Title = "    Standard Viewport in Eto Form"
        self.Size = drawing.Size(800, 600)
        self.Location = drawing.Point(0, 0)
        self.MovableByWindowBackground = True
        self.WindowStyle = forms.WindowStyle.NONE

        self.window_button_size = drawing.Size(32, 32)
        self.window_button_bg_color = drawing.Color(180, 180, 180)

        self.viewport = None
        self.AddViewport()
        self.CreateWindowButtons()

        self.maximized = False

        if self.WindowStyle:
            # Add Transparent Style To The Form Background Panel
            self.Styles.Add[forms.Panel]("transparent", self.MyFormStyler)
            self.Style = "transparent"

        # Subscribe to Eto form events
        self.LocationChanged += self.OnFormMove
        self.SizeChanged += self.OnFormResize

    def OnMinimizeButtonClick(self, sender, e):
        try:
            self.Minimize()  # Minimize Eto Form

        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Minimize Button Exception: {ex}")

    def OnMaximizeButtonClick(self, sender, e):
        try:
            self.maximized = not self.maximized
            self.Maximize() if self.maximized else self.RestoreSize()  # Maximize Eto Form

        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Maximize Button Exception: {ex}")

    def OnFormClose(self, sender, e):
        try:
            if self.viewport:
                self.viewport.Close()  # Close "Embedded" Viewport
            self.Close()  # Close Eto Form

        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"Close Button Exception: {ex}")

    def CreateWindowButtons(self):

        self.header_label = forms.Label()
        self.header_label.Size = drawing.Size(300, self.window_button_size.Height)
        self.header_label.Text = self.Title
        self.header_label.BackgroundColor = self.window_button_bg_color

        self.MinimizeButton = forms.Button()
        self.MinimizeButton.Size = self.window_button_size
        self.MinimizeButton.Text = "-"
        self.MinimizeButton.BackgroundColor = self.window_button_bg_color

        self.MaximizeButton = forms.Button()
        self.MaximizeButton.Size = self.window_button_size
        self.MaximizeButton.Text = "[ ]"
        self.MaximizeButton.BackgroundColor = self.window_button_bg_color

        self.CloseButton = forms.Button()
        self.CloseButton.Size = self.window_button_size
        self.CloseButton.Text = "X"
        self.CloseButton.BackgroundColor = self.window_button_bg_color

        self.MinimizeButton.Click += self.OnMinimizeButtonClick
        self.MaximizeButton.Click += self.OnMaximizeButtonClick
        self.CloseButton.Click += self.OnFormClose

        self.layout = forms.DynamicLayout()
        self.layout.Height = self.window_button_size.Height
        self.layout.AddRow(self.header_label, self.MinimizeButton, self.MaximizeButton, self.CloseButton)
        self.layout.AddRow(None)

        self.Content = self.layout

    def AddViewport(self):
        if self.viewport:
            self.viewport.Close()
        self.display_mode = Rhino.Display.DisplayModeDescription.FindByName("Rendered")
        # Create a Rhino viewport (floating)
        view_rectangle = sdrawing.Rectangle(self.Location.X, (self.Location.Y + self.window_button_size.Height), self.Size.Width, (self.Size.Height - self.window_button_size.Height))
        self.viewport = Rhino.RhinoDoc.ActiveDoc.Views.Add("embedded_viewport", Rhino.Display.DefinedViewportProjection.Perspective, view_rectangle, True)
        self.viewport.TitleVisible = False
        self.viewport.ActiveViewport.DisplayMode = self.display_mode
        handle = self.viewport.Handle
        Rhino.RhinoApp.WriteLine(f"viewport handle: {handle}")
        # self.viewport.Topmost = True
        # self.SendToBack = True

    def OnFormMove(self, sender, e):
        self.UpdateViewportPosition()

    def OnFormResize(self, sender, e):
        self.UpdateViewportPosition()

    def UpdateViewportPosition(self):
        if self.viewport:
            self.AddViewport()
            # Rhino.RhinoApp.WriteLine("Update Viewport Location On Form Move")
            # Get the form's screen position and size
            form_rect = self.Bounds

            # Convert form position to screen coordinates
            screen_position = forms.Screen.PrimaryScreen.Bounds.Location
            new_position = sdrawing.Point(form_rect.X + screen_position.X, form_rect.Y + screen_position.Y)
            new_size = sdrawing.Size(form_rect.Width - 16, form_rect.Height - 70)

            # Update viewport position and size
            self.viewport.Position = new_position
            Rhino.RhinoApp.WriteLine(f"New Pos: {new_position}")
            self.viewport.Size = new_size

        # self.Topmost = False
        # self.SendToBack = True
        self.viewport.BringToFront = True

    def RestoreSize(self):
        try:
            Rhino.RhinoApp.WriteLine("Call Restore Size")
        except Exception as ex:
            Rhino.RhinoApp.WriteLine(f"RestoreSize Exception: {ex}")

    # UI STYLE CODE -------------------------------------------------------
    # Handle Overall Background Transparency
    def MyFormStyler(self, control):
        if self.WindowStyle:
            self.BackgroundColor = drawing.Colors.Transparent
            window = control.ControlObject
            if hasattr(window, "AllowsTransparency"):
                window.AllowsTransparency = True
            if hasattr(window, "Background"):
                brush = window.Background.Clone()
                brush.Opacity = 0
                window.Background = brush
            else:
                color = window.BackgroundColor
                window.BackgroundColor = color.FromRgba(0, 0, 0, 0)


# Create and show the Eto form
def EstablishForm():
    main_toolbar = MyEtoForm()
    main_toolbar.Owner = Rhino.UI.RhinoEtoApp.MainWindow
    main_toolbar.Show()


if __name__ == "__main__":
    EstablishForm()

Thank you for your time & consideration!

Cheers!

3 Likes

This is a rather large project and can be tricky since views get saved in 3dm files by default and the views in this case should not. I would like to better understand what exactly it is you are hoping to achieve so we can focus effort to get that bit done.

2 Likes

Hi @stevebaer,

I wanted to circle back on this and see if you all have had a chance to look into it more?

I can see posts on the forum dating to 2014 with the same request of being able to embed a fully featured viewport into xaml, c++, .net, eto forms etc. with solutions along the lines of suggesting a ViewportControl or leveraging Rhino.Inside but nothing specifically for extending the functionality of the ViewportControl or FloatingViewport to allow embedding in forms.

I’m sure you all have a lot on your plate but I would really appreciate this getting a second look as it would open up a lot of development opportunities for Rhino.

Thanks again!

Hi Michael,
Sorry, I haven’t had a chance to look at this and probably won’t be able to get to it at least in the near future

1 Like

Understood Steve, thanks for the update. This is a feature I really would like to have so please be patient with me as I poke you every so often :slight_smile:

1 Like